This commit is contained in:
Leandro Facchinetti 2020-03-19 00:20:28 -04:00
parent bc07c01ba7
commit 4131c21645
3 changed files with 62 additions and 9 deletions

View File

@ -18,6 +18,7 @@
"@types/mailparser": "^2.7.0", "@types/mailparser": "^2.7.0",
"@types/node": "^13.9.1", "@types/node": "^13.9.1",
"@types/node-fetch": "^2.5.5", "@types/node-fetch": "^2.5.5",
"@types/nodemailer": "^6.4.0",
"@types/react": "^16.9.23", "@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5", "@types/react-dom": "^16.9.5",
"@types/smtp-server": "^3.5.3", "@types/smtp-server": "^3.5.3",
@ -25,6 +26,7 @@
"concurrently": "^5.1.0", "concurrently": "^5.1.0",
"jest": "^25.1.0", "jest": "^25.1.0",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"nodemailer": "^6.4.5",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"typescript": "^3.8.3" "typescript": "^3.8.3"

View File

@ -33,6 +33,7 @@ const webApp = express()
}); });
export const emailServer = new SMTPServer({ export const emailServer = new SMTPServer({
authOptional: true,
async onData(stream, session, callback) { async onData(stream, session, callback) {
const paths = session.envelope.rcptTo.flatMap(({ address }) => { const paths = session.envelope.rcptTo.flatMap(({ address }) => {
const match = address.match(/^(\w+)@kill-the-newsletter.com$/); const match = address.match(/^(\w+)@kill-the-newsletter.com$/);
@ -44,7 +45,7 @@ export const emailServer = new SMTPServer({
}); });
if (paths.length === 0) return callback(); if (paths.length === 0) return callback();
const email = await simpleParser(stream); const email = await simpleParser(stream);
const entry = Entry({ const { entry } = Entry({
title: email.subject, title: email.subject,
author: email.from.text, author: email.from.text,
content: typeof email.html !== "boolean" ? email.html : email.textAsHtml content: typeof email.html !== "boolean" ? email.html : email.textAsHtml
@ -54,8 +55,10 @@ export const emailServer = new SMTPServer({
fs.readFileSync(path).toString() fs.readFileSync(path).toString()
); );
xml.feed.updated = now(); xml.feed.updated = now();
if (xml.feed.entry === undefined) xml.feed.entry = [];
xml.feed.entry.unshift(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)); fs.writeFileSync(path, renderXML(xml));
} }
callback(); callback();
@ -275,7 +278,7 @@ function feedURL(token: string) {
return `https://www.kill-the-newsletter.com/feeds/${token}.xml`; 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`; return `${token}@kill-the-newsletter.com`;
} }

View File

@ -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 fetch from "node-fetch";
import fs from "fs"; import fs from "fs";
test("create feed", async () => { test("create feed", async () => {
const token = await createFeed(); 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 () => { describe("receive email", () => {
const token = await createFeed(); 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: "<p>HTML content</p>"
});
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(() => { afterAll(() => {
developmentWebServer.close(); developmentWebServer.close();
@ -26,3 +70,7 @@ async function createFeed(): Promise<string> {
const responseText = await response.text(); const responseText = await response.text();
return responseText.match(/(\w{20}).xml/)![1]; return responseText.match(/(\w{20}).xml/)![1];
} }
function readFeed(token: string): string {
return fs.readFileSync(feedPath(token)).toString();
}