mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-15 00:37:33 +01:00
feat(admin): track email delivery attempts
This commit is contained in:
1 parent
104af7d261
commit
6400760fcb
24 files changed
+376
-44
No files matched your search
@@ -0,0 +1,97 @@
|
||||
const { setImmediate } = require("node:timers");
|
||||
const { expect } = require("chai");
|
||||
require("ts-node/register/transpile-only");
|
||||
const Users = require("../src/core/model/users/users.model").default;
|
||||
const history = require("../src/core/email-history");
|
||||
const admin = require("../src/server/routes/admin").default;
|
||||
const { emailHistoryQuery } = require("../src/server/routes/admin-emails");
|
||||
|
||||
describe("email delivery history", () => {
|
||||
const restores = [];
|
||||
const stub = (obj, key, value) => { const old = obj[key]; restores.push(() => obj[key] = old); obj[key] = value; };
|
||||
let rows, updates, failWrite, failCompletion;
|
||||
const metadata = { kind: "repository-expiration", recipient: "owner@example.com", ownerId: "owner", resourceId: "repo", resourceKind: "repository" };
|
||||
beforeEach(() => {
|
||||
rows = []; updates = []; failWrite = false; failCompletion = false;
|
||||
Object.defineProperty(Users.db, "readyState", { configurable: true, value: 1 });
|
||||
restores.push(() => { delete Users.db.readyState; });
|
||||
stub(Users.db, "collection", () => ({ createIndex: async () => {},
|
||||
insertOne: async row => { if (failWrite) throw Error("database unavailable"); rows.push(row); },
|
||||
updateOne: async (filter, update) => { if (failCompletion) throw Error("database unavailable"); updates.push({ filter, update }); },
|
||||
}));
|
||||
stub(global, "fetch", async () => ({ ok: true, status: 200 }));
|
||||
});
|
||||
afterEach(() => { while (restores.length) restores.pop()(); });
|
||||
it("records recipient and outcome without retaining credentials or the message", async () => {
|
||||
await history.sendTrackedEmail(metadata, "https://api.resend.com/emails", { headers: { Authorization: "secret" }, body: "private content" });
|
||||
expect(rows).to.have.length(1);
|
||||
expect(rows[0]).to.include({ recipient: metadata.recipient, state: "sending" });
|
||||
expect(JSON.stringify(rows)).not.to.include("secret");
|
||||
expect(JSON.stringify(rows)).not.to.include("private content");
|
||||
expect(updates[0].filter._id).to.equal(rows[0]._id);
|
||||
expect(updates[0].update.$set).to.include({ state: "accepted", httpStatus: 200 });
|
||||
});
|
||||
it("starts delivery without waiting for audit storage", async () => {
|
||||
let release, started = false;
|
||||
stub(Users.db, "collection", () => ({ insertOne: () => new Promise(resolve => { release = resolve; }), updateOne: async () => {} }));
|
||||
stub(global, "fetch", async () => { started = true; return { ok: true, status: 200 }; });
|
||||
const sending = history.sendTrackedEmail(metadata, "url", {});
|
||||
expect(started).to.equal(true);
|
||||
await new Promise(resolve => setImmediate(resolve)); release(); await sending;
|
||||
});
|
||||
it("records rejection separately from an unknown outcome", async () => {
|
||||
stub(global, "fetch", async () => ({ ok: false, status: 429 }));
|
||||
await history.sendTrackedEmail(metadata, "url", {});
|
||||
expect(updates[0].update.$set).to.include({ state: "rejected", httpStatus: 429 });
|
||||
stub(global, "fetch", async () => { throw Error("timeout"); });
|
||||
try { await history.sendTrackedEmail(metadata, "url", {}); throw Error("expected failure"); }
|
||||
catch (error) { expect(error.message).to.equal("timeout"); }
|
||||
expect(updates[1].update.$set.state).to.equal("unknown");
|
||||
});
|
||||
it("keeps delivery independent of audit write failures", async () => {
|
||||
failWrite = true;
|
||||
expect((await history.sendTrackedEmail(metadata, "url", {})).ok).to.equal(true);
|
||||
failWrite = false; failCompletion = true;
|
||||
expect((await history.sendTrackedEmail(metadata, "url", {})).ok).to.equal(true);
|
||||
});
|
||||
it("validates filters and uses a stable cursor for equal timestamps", () => {
|
||||
const date = "2026-01-01T00:00:00.000Z", id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
|
||||
expect(emailHistoryQuery({ state: "accepted", kind: "owner-access", before: date + "|" + id })).to.deep.equal({
|
||||
state: "accepted", kind: "owner-access", $or: [{ createdAt: { $lt: new Date(date) } }, { createdAt: new Date(date), _id: { $lt: id } }],
|
||||
});
|
||||
for (const query of [{ state: { $ne: "" } }, { kind: "invalid" }, { before: "bad" }, { before: [] }]) expect(() => emailHistoryQuery(query)).to.throw();
|
||||
});
|
||||
it("returns bounded pages with a cursor and no browser caching", async () => {
|
||||
const router = require("../src/server/routes/admin-emails").default;
|
||||
const handler = router.stack.find(layer => layer.route?.path === "/").route.stack[0].handle;
|
||||
const records = Array.from({ length: 51 }, (_, index) => ({ _id: String(index), createdAt: new Date("2026-01-01"), state: "accepted" }));
|
||||
let limit, filter, sort, result, cache;
|
||||
stub(history, "emailHistoryCollection", () => ({ find: query => {
|
||||
filter = query;
|
||||
return { sort: value => { sort = value; return { limit: count => { limit = count; return { toArray: async () => records }; } }; } };
|
||||
} }));
|
||||
await handler({ query: { state: "accepted" } }, { set: (key, value) => { cache = [key, value]; }, json: value => { result = value; } });
|
||||
expect(limit).to.equal(51); expect(sort).to.deep.equal({ createdAt: -1, _id: -1 });
|
||||
expect(filter).to.deep.equal({ state: "accepted" });
|
||||
expect(result.entries).to.have.length(50);
|
||||
expect(result.nextCursor).to.equal("2026-01-01T00:00:00.000Z|49");
|
||||
expect(cache).to.deep.equal(["Cache-Control", "no-store"]);
|
||||
});
|
||||
it("returns an explicit error for malformed queries and database failures", async () => {
|
||||
const router = require("../src/server/routes/admin-emails").default;
|
||||
const handler = router.stack.find(layer => layer.route?.path === "/").route.stack[0].handle;
|
||||
let code, body;
|
||||
const res = { set() {}, status(value) { code = value; return this; }, json(value) { body = value; } };
|
||||
await handler({ query: { before: "broken" } }, res);
|
||||
expect(code).to.equal(400);
|
||||
stub(history, "emailHistoryCollection", () => { throw Error("private database info"); });
|
||||
await handler({ query: {} }, res);
|
||||
expect(code).to.equal(503); expect(body.error).not.to.include("private");
|
||||
});
|
||||
it("mounts history behind authentication and administrator checks", () => {
|
||||
const emails = require("../src/server/routes/admin-emails").default;
|
||||
const index = admin.stack.findIndex(layer => layer.handle === emails);
|
||||
expect(index).to.be.greaterThan(1);
|
||||
expect(admin.stack.slice(0, index).filter(layer => !layer.route && layer.handle.length >= 3).length).to.be.at.least(2);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user