diff --git a/package.json b/package.json index 71240df..bc5c1ed 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@types/mailparser": "^2.7.0", "@types/node": "^13.9.1", "@types/node-fetch": "^2.5.5", + "@types/nodemailer": "^6.4.0", "@types/react": "^16.9.23", "@types/react-dom": "^16.9.5", "@types/smtp-server": "^3.5.3", @@ -25,6 +26,7 @@ "concurrently": "^5.1.0", "jest": "^25.1.0", "node-fetch": "^2.6.0", + "nodemailer": "^6.4.5", "nodemon": "^2.0.2", "prettier": "^1.19.1", "typescript": "^3.8.3" diff --git a/src/index.tsx b/src/index.tsx index b778336..f0ef9e1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -33,6 +33,7 @@ const webApp = express() }); export const emailServer = new SMTPServer({ + authOptional: true, async onData(stream, session, callback) { const paths = session.envelope.rcptTo.flatMap(({ address }) => { const match = address.match(/^(\w+)@kill-the-newsletter.com$/); @@ -44,7 +45,7 @@ export const emailServer = new SMTPServer({ }); if (paths.length === 0) return callback(); const email = await simpleParser(stream); - const entry = Entry({ + const { entry } = Entry({ title: email.subject, author: email.from.text, content: typeof email.html !== "boolean" ? email.html : email.textAsHtml @@ -54,8 +55,10 @@ export const emailServer = new SMTPServer({ fs.readFileSync(path).toString() ); xml.feed.updated = now(); + if (xml.feed.entry === undefined) xml.feed.entry = []; xml.feed.entry.unshift(entry); - while (renderXML(xml).length > 500_000) xml.feed.entry.pop(); + while (xml.feed.entry.length > 0 && renderXML(xml).length > 500_000) + xml.feed.entry.pop(); fs.writeFileSync(path, renderXML(xml)); } callback(); @@ -275,7 +278,7 @@ function feedURL(token: string) { return `https://www.kill-the-newsletter.com/feeds/${token}.xml`; } -function feedEmail(token: string) { +export function feedEmail(token: string) { return `${token}@kill-the-newsletter.com`; } diff --git a/src/test.ts b/src/test.ts index 6eb729c..4b5f947 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,17 +1,61 @@ -import { developmentWebServer, emailServer, feedPath } from "."; +import { developmentWebServer, emailServer, feedPath, feedEmail } from "."; +import { createTransport } from "nodemailer"; import fetch from "node-fetch"; import fs from "fs"; test("create feed", async () => { const token = await createFeed(); - const feed = fs.readFileSync(feedPath(token)).toString(); - expect(feed).toMatch("My Feed"); + expect(readFeed(token)).toMatch("My Feed"); }); -test("receive email", async () => { - const token = await createFeed(); -}) +describe("receive email", () => { + const transporter = createTransport({ + host: "localhost", + port: 2525, + tls: { rejectUnauthorized: false } + }); + + test("HTML content", async () => { + const token = await createFeed(); + await transporter.sendMail({ + from: "publisher@example.com", + to: feedEmail(token), + subject: "New Message", + html: "

HTML content

" + }); + const feed = readFeed(token); + expect(feed).toMatch("publisher@example.com"); + expect(feed).toMatch("New Message"); + expect(feed).toMatch("HTML content"); + }); + + test("text content", async () => { + const token = await createFeed(); + await transporter.sendMail({ + from: "publisher@example.com", + to: feedEmail(token), + subject: "New Message", + text: "TEXT content" + }); + const feed = readFeed(token); + expect(feed).toMatch("TEXT content"); + }); + + test("truncation", async () => { + const token = await createFeed(); + for (const repetition of [...new Array(4).keys()]) + await transporter.sendMail({ + from: "publisher@example.com", + to: feedEmail(token), + subject: "New Message", + text: `REPETITION ${repetition} `.repeat(10_000) + }); + const feed = readFeed(token); + expect(feed).toMatch("REPETITION 3"); + expect(feed).not.toMatch("REPETITION 0"); + }, 10_000); +}); afterAll(() => { developmentWebServer.close(); @@ -26,3 +70,7 @@ async function createFeed(): Promise { const responseText = await response.text(); return responseText.match(/(\w{20}).xml/)![1]; } + +function readFeed(token: string): string { + return fs.readFileSync(feedPath(token)).toString(); +}