kill-the-newsletter/test.ts

273 lines
9.3 KiB
TypeScript
Raw Normal View History

2020-07-23 17:11:41 +02:00
import { webServer, emailServer, BASE_URL, EMAIL_DOMAIN, EMAIL_PORT } from ".";
2020-03-19 15:48:31 +01:00
import nodemailer from "nodemailer";
2020-03-20 18:48:53 +01:00
import axios from "axios";
import qs from "qs";
2020-07-15 21:29:08 +02:00
import { JSDOM } from "jsdom";
2020-03-18 20:21:44 +01:00
2020-03-19 02:44:21 +01:00
test("create feed", async () => {
2020-03-20 20:08:04 +01:00
const identifier = await createFeed();
2020-07-23 17:11:41 +02:00
const feed = await getFeed(identifier);
const entry = feed.querySelector("feed > entry:first-of-type")!;
const alternate = await getAlternate(
entry.querySelector("link")!.getAttribute("href")!
);
expect(feed.querySelector("feed > title")!.textContent).toBe("My Feed");
expect(entry.querySelector("title")!.textContent).toBe(
"“My Feed” Inbox Created"
);
expect(alternate.querySelector("p")!.textContent).toMatch(
"Sign up for the newsletter with"
);
2020-03-18 20:21:44 +01:00
});
2020-03-19 01:16:00 +01:00
2020-03-19 05:20:28 +01:00
describe("receive email", () => {
2020-03-31 20:14:44 +02:00
test("updated field is updated", async () => {
const identifier = await createFeed();
const before = await getFeed(identifier);
await emailClient.sendMail({
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-31 20:14:44 +02:00
subject: "New Message",
2020-04-03 23:06:43 +02:00
html: "<p>HTML content</p>",
2020-03-31 20:14:44 +02:00
});
const after = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
expect(after.querySelector("feed > updated")!.textContent).not.toBe(
before.querySelector("feed > updated")!.textContent
2020-03-31 20:14:44 +02:00
);
});
2020-03-19 05:20:28 +01:00
test("HTML content", async () => {
2020-03-20 20:08:04 +01:00
const identifier = await createFeed();
2020-03-21 16:58:28 +01:00
await emailClient.sendMail({
2020-03-19 05:20:28 +01:00
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-19 05:20:28 +01:00
subject: "New Message",
2020-04-03 23:06:43 +02:00
html: "<p>HTML content</p>",
2020-03-19 05:20:28 +01:00
});
2020-03-21 16:58:28 +01:00
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
const alternate = await getAlternate(
entry.querySelector("link")!.getAttribute("href")!
);
expect(entry.querySelector("author > name")!.textContent).toBe(
"publisher@example.com"
);
expect(entry.querySelector("title")!.textContent).toBe("New Message");
expect(entry.querySelector("content")!.textContent).toMatch("HTML content");
expect(alternate.querySelector("p")!.textContent).toMatch("HTML content");
2020-03-19 05:20:28 +01:00
});
test("text content", async () => {
2020-03-20 20:08:04 +01:00
const identifier = await createFeed();
2020-03-21 16:58:28 +01:00
await emailClient.sendMail({
2020-03-19 05:20:28 +01:00
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-19 05:20:28 +01:00
subject: "New Message",
2020-04-03 23:06:43 +02:00
text: "TEXT content",
2020-03-19 05:20:28 +01:00
});
2020-03-21 16:58:28 +01:00
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
const alternate = await getAlternate(
entry.querySelector("link")!.getAttribute("href")!
);
expect(entry.querySelector("content")!.textContent).toMatch("TEXT content");
expect(alternate.querySelector("p")!.textContent).toMatch("TEXT content");
2020-03-19 05:20:28 +01:00
});
2020-03-23 22:43:48 +01:00
test("rich text content", async () => {
const identifier = await createFeed();
await emailClient.sendMail({
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-23 22:43:48 +01:00
subject: "New Message",
2020-08-04 23:57:49 +02:00
text: "TEXT content\n\nhttps://leafac.com\n\nMore text",
2020-03-23 22:43:48 +01:00
});
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
const alternate = await getAlternate(
entry.querySelector("link")!.getAttribute("href")!
);
expect(alternate.querySelector("a")!.getAttribute("href")).toBe(
2020-08-04 23:57:49 +02:00
"https://leafac.com"
2020-07-23 17:11:41 +02:00
);
2020-03-23 22:43:48 +01:00
});
2020-03-31 20:14:44 +02:00
test("invalid XML character in HTML", async () => {
const identifier = await createFeed();
await emailClient.sendMail({
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-31 20:14:44 +02:00
subject: "New Message",
2020-05-05 08:12:57 +02:00
html: "<p>Invalid XML character (backspace): |\b|💩</p>",
2020-03-31 20:14:44 +02:00
});
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
expect(entry.querySelector("content")!.textContent).toMatchInlineSnapshot(`
"<p>Invalid XML character (backspace): ||💩</p>
"
`);
2020-03-31 20:14:44 +02:00
});
test("invalid XML character in text", async () => {
const identifier = await createFeed();
await emailClient.sendMail({
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-31 20:14:44 +02:00
subject: "New Message",
2020-05-05 08:12:57 +02:00
text: "Invalid XML character (backspace): |\b|💩",
2020-03-31 20:14:44 +02:00
});
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
expect(entry.querySelector("content")!.textContent).toMatchInlineSnapshot(
`"<p>Invalid XML character (backspace): |&#x8;|&#x1F4A9;</p>"`
2020-05-05 08:12:57 +02:00
);
2020-03-31 20:14:44 +02:00
});
2020-07-23 17:11:41 +02:00
test("missing from", async () => {
2020-03-23 16:08:57 +01:00
const identifier = await createFeed();
await emailClient.sendMail({
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-04-03 23:06:43 +02:00
subject: "New Message",
2020-07-23 17:11:41 +02:00
html: "<p>HTML content</p>",
2020-03-23 16:08:57 +01:00
});
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
expect(entry.querySelector("author > name")!.textContent).toBe("");
expect(entry.querySelector("title")!.textContent).toBe("New Message");
});
test("nonexistent to", async () => {
await emailClient.sendMail({
from: "publisher@example.com",
to: `nonexistent@${EMAIL_DOMAIN}`,
subject: "New Message",
html: "<p>HTML content</p>",
});
2020-03-23 16:08:57 +01:00
});
2020-07-23 17:11:41 +02:00
test("missing subject", async () => {
2020-03-23 16:08:57 +01:00
const identifier = await createFeed();
await emailClient.sendMail({
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-04-03 23:06:43 +02:00
html: "<p>HTML content</p>",
2020-03-23 16:08:57 +01:00
});
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
const entry = feed.querySelector("feed > entry:first-of-type")!;
expect(entry.querySelector("title")!.textContent).toBe("");
expect(entry.querySelector("author > name")!.textContent).toBe(
"publisher@example.com"
);
});
test("missing content", async () => {
const identifier = await createFeed();
await emailClient.sendMail({
from: "publisher@example.com",
to: `${identifier}@${EMAIL_DOMAIN}`,
subject: "New Message",
});
const feed = await getFeed(identifier);
const entry = feed.querySelector("feed > entry:first-of-type")!;
expect(entry.querySelector("content")!.textContent!.trim()).toBe("");
expect(entry.querySelector("title")!.textContent).toBe("New Message");
2020-03-23 16:08:57 +01:00
});
2020-03-19 05:20:28 +01:00
test("truncation", async () => {
2020-03-20 20:08:04 +01:00
const identifier = await createFeed();
2020-07-23 17:11:41 +02:00
const alternatesURLs = new Array<string>();
for (const repetition of [...new Array(4).keys()]) {
2020-03-21 16:58:28 +01:00
await emailClient.sendMail({
2020-03-19 05:20:28 +01:00
from: "publisher@example.com",
2020-04-06 15:48:34 +02:00
to: `${identifier}@${EMAIL_DOMAIN}`,
2020-03-19 05:20:28 +01:00
subject: "New Message",
2020-04-03 23:06:43 +02:00
text: `REPETITION ${repetition} `.repeat(10_000),
2020-03-19 05:20:28 +01:00
});
2020-07-23 17:11:41 +02:00
const feed = await getFeed(identifier);
const entry = feed.querySelector("feed > entry:first-of-type")!;
alternatesURLs.push(entry.querySelector("link")!.getAttribute("href")!);
}
2020-03-21 16:58:28 +01:00
const feed = await getFeed(identifier);
2020-07-23 17:11:41 +02:00
expect(
feed.querySelector("entry:first-of-type > content")!.textContent
).toMatch("REPETITION 3");
expect(
feed.querySelector("entry:last-of-type > content")!.textContent
).toMatch("REPETITION 1");
expect((await getAlternate(alternatesURLs[3]!)).textContent).toMatch(
"REPETITION 3"
);
await expect(getAlternate(alternatesURLs[0]!)).rejects.toThrowError();
2020-03-21 16:58:28 +01:00
});
2020-03-22 15:23:05 +01:00
2020-05-05 08:12:57 +02:00
test("too big entry", async () => {
const identifier = await createFeed();
await emailClient.sendMail({
from: "publisher@example.com",
to: `${identifier}@${EMAIL_DOMAIN}`,
subject: "New Message",
2020-07-23 17:11:41 +02:00
text: "TOO BIG".repeat(100_000),
2020-05-05 08:12:57 +02:00
});
2020-07-23 17:11:41 +02:00
expect((await getFeed(identifier)).querySelector("entry")).toBeNull();
2020-05-05 08:12:57 +02:00
await emailClient.sendMail({
from: "publisher@example.com",
to: `${identifier}@${EMAIL_DOMAIN}`,
subject: "New Message",
text: `NORMAL SIZE`,
});
2020-07-23 17:11:41 +02:00
expect(
(await getFeed(identifier)).querySelector("entry > content")!.textContent
).toMatchInlineSnapshot(`"<p>NORMAL SIZE</p>"`);
2020-03-23 16:24:14 +01:00
});
2020-03-19 05:20:28 +01:00
});
2020-03-19 04:36:02 +01:00
test("noindex header", async () => {
const identifier = await createFeed();
const feed = await getFeed(identifier);
const entry = feed.querySelector("feed > entry:first-of-type")!;
const alternatePath = entry.querySelector("link")!.getAttribute("href")!;
expect((await webClient.get(`/`)).headers["x-robots-tag"]).toBeUndefined();
expect(
(await webClient.get(`/feeds/${identifier}.xml`)).headers["x-robots-tag"]
).toBe("noindex");
expect((await webClient.get(alternatePath)).headers["x-robots-tag"]).toBe(
"noindex"
);
});
2020-07-23 17:11:41 +02:00
const webClient = axios.create({
baseURL: BASE_URL,
2020-07-15 21:29:08 +02:00
});
2020-07-23 17:11:41 +02:00
const emailClient = nodemailer.createTransport(
`smtp://${EMAIL_DOMAIN}:${EMAIL_PORT}`
);
2020-03-19 01:16:00 +01:00
afterAll(() => {
2020-03-20 22:31:46 +01:00
webServer.close();
2020-03-31 18:57:56 +02:00
emailServer.close();
2020-03-19 01:16:00 +01:00
});
2020-03-19 04:36:02 +01:00
async function createFeed(): Promise<string> {
2020-07-23 17:11:41 +02:00
return JSDOM.fragment(
(
await webClient.post(
"/",
qs.stringify({
name: "My Feed",
})
)
).data
)
.querySelector("code")!
.textContent!.split("@")[0];
2020-03-19 04:36:02 +01:00
}
2020-03-19 05:20:28 +01:00
2020-07-23 17:11:41 +02:00
async function getFeed(identifier: string): Promise<Document> {
return new JSDOM((await webClient.get(`/feeds/${identifier}.xml`)).data, {
contentType: "text/xml",
}).window.document;
2020-03-19 05:20:28 +01:00
}
2020-07-15 21:29:08 +02:00
2020-07-23 17:11:41 +02:00
async function getAlternate(url: string): Promise<DocumentFragment> {
return JSDOM.fragment((await webClient.get(url)).data);
2020-07-15 21:29:08 +02:00
}