2022-03-03 00:31:15 +00:00
|
|
|
|
import 'mocha'
|
2022-06-01 02:30:23 +00:00
|
|
|
|
import * as chai from 'chai'
|
2022-03-03 00:31:15 +00:00
|
|
|
|
import { expect } from 'chai'
|
|
|
|
|
|
import 'chai/register-should'
|
|
|
|
|
|
import fs from 'fs'
|
2022-06-01 02:30:23 +00:00
|
|
|
|
import {
|
|
|
|
|
|
findNewsletterUrl,
|
2022-07-26 14:08:10 +00:00
|
|
|
|
generateUniqueUrl,
|
|
|
|
|
|
getTitleFromEmailSubject,
|
2022-07-26 10:37:04 +00:00
|
|
|
|
isProbablyArticle,
|
2022-06-01 02:30:23 +00:00
|
|
|
|
isProbablyNewsletter,
|
2022-07-28 02:26:15 +00:00
|
|
|
|
parseEmailAddress,
|
2022-06-01 02:30:23 +00:00
|
|
|
|
parsePageMetadata,
|
|
|
|
|
|
parsePreparedContent,
|
|
|
|
|
|
} from '../../src/utils/parser'
|
2022-03-16 22:29:39 +00:00
|
|
|
|
import nock from 'nock'
|
2022-06-01 02:30:23 +00:00
|
|
|
|
import chaiAsPromised from 'chai-as-promised'
|
2022-07-26 10:37:04 +00:00
|
|
|
|
import { User } from '../../src/entity/user'
|
|
|
|
|
|
import { createTestUser, deleteTestUser } from '../db'
|
2022-06-01 02:30:23 +00:00
|
|
|
|
|
|
|
|
|
|
chai.use(chaiAsPromised)
|
2022-03-03 07:09:10 +00:00
|
|
|
|
|
|
|
|
|
|
const load = (path: string): string => {
|
|
|
|
|
|
return fs.readFileSync(path, 'utf8')
|
|
|
|
|
|
}
|
2022-03-03 00:31:15 +00:00
|
|
|
|
|
|
|
|
|
|
describe('isProbablyNewsletter', () => {
|
2022-06-01 02:30:23 +00:00
|
|
|
|
it('returns true for substack newsletter', async () => {
|
2022-03-03 04:38:11 +00:00
|
|
|
|
const html = load('./test/utils/data/substack-forwarded-newsletter.html')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
await expect(isProbablyNewsletter(html)).to.eventually.be.true
|
2022-03-03 00:31:15 +00:00
|
|
|
|
})
|
2022-06-01 02:30:23 +00:00
|
|
|
|
it('returns true for private forwarded substack newsletter', async () => {
|
|
|
|
|
|
const html = load(
|
|
|
|
|
|
'./test/utils/data/substack-private-forwarded-newsletter.html'
|
|
|
|
|
|
)
|
|
|
|
|
|
await expect(isProbablyNewsletter(html)).to.eventually.be.true
|
2022-03-03 21:42:30 +00:00
|
|
|
|
})
|
2022-06-01 02:30:23 +00:00
|
|
|
|
it('returns false for substack welcome email', async () => {
|
2022-03-03 04:38:11 +00:00
|
|
|
|
const html = load('./test/utils/data/substack-forwarded-welcome-email.html')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
await expect(isProbablyNewsletter(html)).to.eventually.be.false
|
2022-03-03 00:31:15 +00:00
|
|
|
|
})
|
2022-06-01 02:30:23 +00:00
|
|
|
|
it('returns true for beehiiv.com newsletter', async () => {
|
2022-03-07 23:05:38 +00:00
|
|
|
|
const html = load('./test/utils/data/beehiiv-newsletter.html')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
await expect(isProbablyNewsletter(html)).to.eventually.be.true
|
2022-03-07 23:05:38 +00:00
|
|
|
|
})
|
2022-03-03 00:31:15 +00:00
|
|
|
|
})
|
2022-03-03 07:09:10 +00:00
|
|
|
|
|
|
|
|
|
|
describe('findNewsletterUrl', async () => {
|
2022-03-07 23:05:38 +00:00
|
|
|
|
it('gets the URL from the header if it is a substack newsletter', async () => {
|
2022-07-14 10:43:35 +00:00
|
|
|
|
nock('https://email.mg2.substack.com')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
.head(
|
2022-07-14 10:43:35 +00:00
|
|
|
|
'/c/eJxNkk2TojAQhn-N3KTyQfg4cGDGchdnYcsZx9K5UCE0EMVAkTiKv36iHnarupNUd7rfVJ4W3EDTj1M89No496Uw0wCxgovuwBgYnbOGsZBVjDHzKPWYU8VehUMWOlIX9Qhw4rKLzXgGZziXnRTcyF7dK0iIGMVOG_OS1aTmKPRDilgVhTQUPCQIcE0x-MFTmJ8rCUpA3KtuenR2urg1ZtAzmszI0tq_Z7m66y-ilQo0uAqMTQ7WRX8auJKg56blZg7WB-iHDuYEBzO6NP0R1IwuYFphQbbTjnTH9NBfs80nym4Zyj8uUvyKbtUyGr5eUz9fNDQ7JCxfJDo9dW1lY9lmj_JNivPbGmf2Pt_lN9tDit9b-WeTetni85Z9pDpVOd7L1E_Vy7egayNO23ZP34eSeLJeux1b0rer_xaZ7ykS78nuSjMY-nL98rparNZNcv07JCjN06_EkTFBxBqOUMACErnELUNMSxTUjLDQZwzcqa4bRjCfeejUEFefS224OLr2S5wxPtij7lVrs80d2CNseRV2P52VNFMBipcdVE-U5jkRD7hFAwpGOylVwU2Mfc9qBh7DoR89yVnWXhgQFHnIsbpVb6tU_B-hH_2yzWY'
|
2022-06-01 02:30:23 +00:00
|
|
|
|
)
|
2022-07-14 10:43:35 +00:00
|
|
|
|
.reply(302, undefined, {
|
|
|
|
|
|
Location:
|
|
|
|
|
|
'https://newsletter.slowchinese.net/p/companies-that-eat-people-217',
|
|
|
|
|
|
})
|
|
|
|
|
|
.get('/p/companies-that-eat-people-217')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
.reply(200, '')
|
2022-03-03 07:09:10 +00:00
|
|
|
|
const html = load('./test/utils/data/substack-forwarded-newsletter.html')
|
|
|
|
|
|
const url = await findNewsletterUrl(html)
|
|
|
|
|
|
// Not sure if the redirects from substack expire, this test could eventually fail
|
2022-06-01 02:30:23 +00:00
|
|
|
|
expect(url).to.startWith(
|
|
|
|
|
|
'https://newsletter.slowchinese.net/p/companies-that-eat-people-217'
|
|
|
|
|
|
)
|
2022-03-03 07:09:10 +00:00
|
|
|
|
})
|
2022-03-07 23:49:44 +00:00
|
|
|
|
it('gets the URL from the header if it is a beehiiv newsletter', async () => {
|
2022-03-31 18:21:41 +00:00
|
|
|
|
nock('https://u23463625.ct.sendgrid.net')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
.head(
|
|
|
|
|
|
'/ss/c/AX1lEgEQaxtvFxLaVo0GBo_geajNrlI1TGeIcmMViR3pL3fEDZnbbkoeKcaY62QZk0KPFudUiUXc_uMLerV4nA/3k5/3TFZmreTR0qKSCgowABnVg/h30/zzLik7UXd1H_n4oyd5W8Xu639AYQQB2UXz-CsssSnno'
|
|
|
|
|
|
)
|
|
|
|
|
|
.reply(302, undefined, {
|
|
|
|
|
|
Location: 'https://www.milkroad.com/p/talked-guy-spent-30m-beeple',
|
|
|
|
|
|
})
|
|
|
|
|
|
.get('/p/talked-guy-spent-30m-beeple')
|
|
|
|
|
|
.reply(200, '')
|
2022-03-07 23:05:38 +00:00
|
|
|
|
const html = load('./test/utils/data/beehiiv-newsletter.html')
|
2022-03-07 23:49:44 +00:00
|
|
|
|
const url = await findNewsletterUrl(html)
|
2022-06-01 02:30:23 +00:00
|
|
|
|
expect(url).to.startWith(
|
|
|
|
|
|
'https://www.milkroad.com/p/talked-guy-spent-30m-beeple'
|
|
|
|
|
|
)
|
2022-03-07 23:05:38 +00:00
|
|
|
|
})
|
2022-03-03 07:09:10 +00:00
|
|
|
|
it('returns undefined if it is not a newsletter', async () => {
|
|
|
|
|
|
const html = load('./test/utils/data/substack-forwarded-welcome-email.html')
|
|
|
|
|
|
const url = await findNewsletterUrl(html)
|
|
|
|
|
|
expect(url).to.be.undefined
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2022-03-03 21:42:30 +00:00
|
|
|
|
|
2022-03-04 03:40:02 +00:00
|
|
|
|
describe('parseMetadata', async () => {
|
|
|
|
|
|
it('gets author, title, image, description', async () => {
|
|
|
|
|
|
const html = load('./test/utils/data/substack-post.html')
|
|
|
|
|
|
const metadata = await parsePageMetadata(html)
|
|
|
|
|
|
expect(metadata?.author).to.deep.equal('Omnivore')
|
|
|
|
|
|
expect(metadata?.title).to.deep.equal('Code Block Syntax Highlighting')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
expect(metadata?.previewImage).to.deep.equal(
|
|
|
|
|
|
'https://cdn.substack.com/image/fetch/w_1200,h_600,c_fill,f_jpg,q_auto:good,fl_progressive:steep,g_auto/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F2ab1f7e8-2ca7-4011-8ccb-43d0b3bd244f_1490x2020.png'
|
|
|
|
|
|
)
|
|
|
|
|
|
expect(metadata?.description).to.deep.equal(
|
|
|
|
|
|
'Highlighted <code> in Omnivore'
|
|
|
|
|
|
)
|
2022-03-04 03:40:02 +00:00
|
|
|
|
})
|
|
|
|
|
|
})
|
2022-03-14 04:23:47 +00:00
|
|
|
|
|
|
|
|
|
|
describe('parsePreparedContent', async () => {
|
|
|
|
|
|
it('gets published date when JSONLD fails to load', async () => {
|
|
|
|
|
|
const html = load('./test/utils/data/stratechery-blog-post.html')
|
2022-06-01 02:30:23 +00:00
|
|
|
|
const result = await parsePreparedContent('https://example.com/', {
|
|
|
|
|
|
document: html,
|
|
|
|
|
|
pageInfo: {},
|
|
|
|
|
|
})
|
|
|
|
|
|
expect(result.parsedContent?.publishedDate?.getTime()).to.equal(
|
|
|
|
|
|
new Date('2016-04-05T15:27:51+00:00').getTime()
|
2022-03-14 04:23:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2022-03-16 22:29:39 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
describe('parsePreparedContent', async () => {
|
|
|
|
|
|
nock('https://oembeddata').get('/').reply(200, {
|
2022-06-01 02:30:23 +00:00
|
|
|
|
version: '1.0',
|
|
|
|
|
|
provider_name: 'Hippocratic Adventures',
|
|
|
|
|
|
provider_url: 'https://www.hippocraticadventures.com',
|
|
|
|
|
|
title:
|
|
|
|
|
|
'The Ultimate Guide to Practicing Medicine in Singapore – Part 2',
|
2022-03-16 22:29:39 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('gets metadata from external JSONLD if available', async () => {
|
|
|
|
|
|
const html = `<html>
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<link rel="alternate" type="application/json+oembed" href="https://oembeddata">
|
|
|
|
|
|
</link
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>body</body>
|
|
|
|
|
|
</html>`
|
2022-06-01 02:30:23 +00:00
|
|
|
|
const result = await parsePreparedContent('https://example.com/', {
|
|
|
|
|
|
document: html,
|
|
|
|
|
|
pageInfo: {},
|
|
|
|
|
|
})
|
|
|
|
|
|
expect(result.parsedContent?.title).to.equal(
|
|
|
|
|
|
'The Ultimate Guide to Practicing Medicine in Singapore – Part 2'
|
|
|
|
|
|
)
|
2022-03-16 22:29:39 +00:00
|
|
|
|
})
|
|
|
|
|
|
})
|
2022-07-26 10:37:04 +00:00
|
|
|
|
|
|
|
|
|
|
describe('isProbablyArticle', () => {
|
|
|
|
|
|
let user: User
|
|
|
|
|
|
|
|
|
|
|
|
before(async () => {
|
|
|
|
|
|
user = await createTestUser('fakeUser')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
after(async () => {
|
|
|
|
|
|
await deleteTestUser(user.name)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('returns true when email is signed up with us', async () => {
|
|
|
|
|
|
const email = user.email
|
|
|
|
|
|
expect(await isProbablyArticle(email, 'test subject')).to.be.true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('returns true when subject has omnivore: prefix', async () => {
|
|
|
|
|
|
const subject = 'omnivore: test subject'
|
|
|
|
|
|
expect(await isProbablyArticle('test-email', subject)).to.be.true
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2022-07-26 14:08:10 +00:00
|
|
|
|
|
|
|
|
|
|
describe('generateUniqueUrl', () => {
|
|
|
|
|
|
it('generates a unique URL', () => {
|
|
|
|
|
|
const url1 = generateUniqueUrl()
|
|
|
|
|
|
const url2 = generateUniqueUrl()
|
|
|
|
|
|
|
|
|
|
|
|
expect(url1).to.not.eql(url2)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
describe('getTitleFromEmailSubject', () => {
|
|
|
|
|
|
it('returns the title from the email subject', () => {
|
|
|
|
|
|
const title = 'test subject'
|
|
|
|
|
|
const subject = `omnivore: ${title}`
|
|
|
|
|
|
expect(getTitleFromEmailSubject(subject)).to.eql(title)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2022-07-28 02:26:15 +00:00
|
|
|
|
|
|
|
|
|
|
describe('parseEmailAddress', () => {
|
2022-07-28 02:37:28 +00:00
|
|
|
|
it('returns the name and address when in name <address> format', () => {
|
|
|
|
|
|
const name = 'test name'
|
|
|
|
|
|
const address = 'tester@omnivore.app'
|
|
|
|
|
|
const parsed = parseEmailAddress(`${name} <${address}>`)
|
|
|
|
|
|
expect(parsed.name).to.eql(name)
|
|
|
|
|
|
expect(parsed.address).to.eql(address)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('returns the address when in address format', () => {
|
|
|
|
|
|
const address = 'tester@omnivore.app'
|
|
|
|
|
|
const parsed = parseEmailAddress(address)
|
|
|
|
|
|
expect(parsed.name).to.eql('')
|
|
|
|
|
|
expect(parsed.address).to.eql(address)
|
2022-07-28 02:26:15 +00:00
|
|
|
|
})
|
|
|
|
|
|
})
|