Files
LambdaMail/services/webmail/src/compose-draft-integrity.test.ts
T
Lucas Rayan f330c4324e Add end-to-end tests for TLS RPT report ingestion and WebSocket event handling
- Implemented tests for ingesting TLS RPT reports from emails and ensuring they are stored correctly in the database.
- Added tests to verify that the same report can be ingested multiple times without duplication.
- Created tests for backfilling reports that were previously delivered to the inbox.
- Developed WebSocket event handling tests to ensure events are correctly routed to subscribers based on mailbox ownership.
- Included tests for the mail API to validate session handling, mailbox isolation, and error mapping.
- Ensured that the API rejects unauthorized requests and handles malformed JSON gracefully.
2026-08-21 09:55:37 -03:00

103 lines
4.1 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
/**
* The composer's draft handling, and the delete action the mail list was
* missing entirely.
*
* Every check here is a defect the user hit: a message sent from the composer
* left its autosaved draft behind, that draft was empty because the autosave
* never saw the body, and no screen offered any way to remove it.
*/
const read = (path: string) => readFileSync(resolve(process.cwd(), path), "utf8");
const code = (path: string) =>
read(path)
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/\{\/\*[\s\S]*?\*\/\}/g, "")
.replace(/^\s*\/\/.*$/gm, "");
const COMPOSE = "src/app/(user)/user/compose/page.tsx";
const MAIL_PAGE = "src/app/(user)/user/mail/[folder]/page.tsx";
describe("the draft a sent message leaves behind", () => {
// Sending posted to /mail/send without ever naming the draft it came from,
// so the draft stayed in Drafts as a duplicate of mail already on its way.
it("tells the server which draft the sent message came from", () => {
const compose = code(COMPOSE);
expect(compose).toMatch(/draft_uid:\s*draftUidRef\.current/);
});
it("posts the draft UID on the send request, not only on the autosave", () => {
const compose = code(COMPOSE);
const sendCall = compose.slice(compose.indexOf('"/api/v1/mail/send"'));
expect(sendCall.slice(0, 600)).toContain("draft_uid");
});
});
describe("what the autosave actually stores", () => {
// The body lived in a contenteditable read through a ref, and the debounce
// depended only on the header fields. Typing a message therefore never
// re-triggered a save, so the stored draft held the recipient and nothing
// else - the empty draft the user could see and could not delete.
it("re-runs when the body changes, not only the recipient and subject", () => {
const compose = code(COMPOSE);
const effect = compose.slice(compose.indexOf("const timer = setTimeout(() => void saveDraft()"));
const deps = effect.slice(effect.indexOf("}, ["), effect.indexOf("]);") + 3);
expect(deps).toContain("body");
});
it("keeps the body in state so a change can be observed at all", () => {
const compose = code(COMPOSE);
expect(compose).toMatch(/useState.*\n?.*body|const \[body, setBody\]/);
expect(compose).toContain("setBody");
});
it("does not save a draft that has no content anywhere", () => {
const compose = code(COMPOSE);
expect(compose).toMatch(/if \(!to && !subject && !body/);
});
});
describe("deleting a message", () => {
// There was no delete anywhere in the webmail: no button, and no route
// behind one. Anything the user wanted rid of stayed forever.
it("offers a delete action on the message list", () => {
const page = code(MAIL_PAGE);
expect(page).toContain("/api/v1/mail/delete");
});
it("refreshes the counters after deleting, so the badges follow", () => {
const page = code(MAIL_PAGE);
const del = page.slice(page.indexOf('"/api/v1/mail/delete"'));
expect(del.slice(0, 900)).toContain("notifyMailStateChanged");
});
it("removes the deleted row from the list without a full reload", () => {
const page = code(MAIL_PAGE);
const del = page.slice(page.indexOf("const deleteMessage"));
expect(del.slice(0, 1200)).toMatch(/setMessages\(/);
});
});
describe("the Reports folder", () => {
// DMARC and TLS-RPT reports are parsed on arrival and filed here instead of
// the inbox. The sidebar is a fixed list, so without an entry the folder
// exists and receives mail that no screen can reach.
it("is reachable from the sidebar", () => {
const layout = code("src/app/(user)/user/layout.tsx");
expect(layout).toContain("/user/mail/reports");
});
it("is named through the translator, not hardcoded", () => {
const layout = code("src/app/(user)/user/layout.tsx");
expect(layout).toMatch(/t\("mail\.reports"\)/);
});
it("has a title on the message list too", () => {
const page = code(MAIL_PAGE);
expect(page).toMatch(/reports:\s*t\("mail\.reports"\)/);
});
});