feat(domain): pendingConfirmation flag on the Feed aggregate

This commit is contained in:
Julien Herr committed 2026-05-25 08:58:26 +02:00
1 parent d561b6b81f
commit 79bb4902b9
3 files changed
+103

No files matched your search

+76
View File
@@ -233,6 +233,82 @@ describe("Feed events", () => {
});
});
function newFeed(): Feed {
return Feed.create(
FeedId.generate(),
{
title: "T",
description: "",
language: "en",
allowedSenders: [],
blockedSenders: [],
},
{ mailboxId: MailboxId.unchecked("alpha.beta.10") },
);
}
function confirmationEmail(
key: string,
confirmation?: { links: string[] },
): EmailMetadata {
return {
key,
subject: "s",
receivedAt: Date.now(),
size: 10,
...(confirmation ? { confirmation } : {}),
};
}
describe("Feed pendingConfirmation", () => {
it("is false on a fresh feed", () => {
expect(newFeed().pendingConfirmation).toBe(false);
});
it("is raised when a confirmation email is ingested", () => {
const feed = newFeed();
feed.ingest(confirmationEmail("k1", { links: ["https://x/confirm"] }), {
maxBytes: 1_000_000,
});
expect(feed.pendingConfirmation).toBe(true);
});
it("stays false for a non-confirmation email", () => {
const feed = newFeed();
feed.ingest(confirmationEmail("k1"), { maxBytes: 1_000_000 });
expect(feed.pendingConfirmation).toBe(false);
});
it("is cleared by dismissConfirmation", () => {
const feed = newFeed();
feed.ingest(confirmationEmail("k1", { links: ["https://x/confirm"] }), {
maxBytes: 1_000_000,
});
feed.dismissConfirmation();
expect(feed.pendingConfirmation).toBe(false);
});
it("does not re-raise after dismiss when removing an unrelated email", () => {
const feed = newFeed();
feed.ingest(confirmationEmail("k1", { links: ["https://x/confirm"] }), {
maxBytes: 1_000_000,
});
feed.ingest(confirmationEmail("k2"), { maxBytes: 1_000_000 });
feed.dismissConfirmation();
feed.removeEmails(["k2"]);
expect(feed.pendingConfirmation).toBe(false);
});
it("clears when the last confirmation email is removed", () => {
const feed = newFeed();
feed.ingest(confirmationEmail("k1", { links: ["https://x/confirm"] }), {
maxBytes: 1_000_000,
});
feed.removeEmails(["k1"]);
expect(feed.pendingConfirmation).toBe(false);
});
});
describe("FeedRepository.load / save round-trip", () => {
it("persists a created feed and reflects later mutations", async () => {
const repo = new FeedRepository(mockEnv().EMAIL_STORAGE);
+19
View File
@@ -155,6 +155,11 @@ export class Feed {
return this._metadata.iconDomain;
}
/** True while at least one unactioned confirmation email is present. */
get pendingConfirmation(): boolean {
return this._metadata.pendingConfirmation ?? false;
}
allowedSenders(): string[] {
return [...this._state.allowedSenders];
}
@@ -258,6 +263,10 @@ export class Feed {
};
}
if (entry.confirmation) {
this._metadata.pendingConfirmation = true;
}
this._events.push({
type: "EmailIngested",
feedId: this.id,
@@ -295,9 +304,19 @@ export class Feed {
(target.has(entry.key) ? removed : kept).push(entry);
}
this._metadata.emails = kept;
// Lower-only: clear when no confirmation email remains. Never re-raise here,
// so an admin "dismiss" survives deletion of unrelated emails.
if (!kept.some((e) => e.confirmation)) {
this._metadata.pendingConfirmation = false;
}
return { removed };
}
/** Mark the pending confirmation as handled — "stop reminding me". */
dismissConfirmation(): void {
this._metadata.pendingConfirmation = false;
}
/**
* The single edit path. Apply the patch (only the fields it carries) and
* recompute expiry when the application supplies a `Lifetime` — an absent