feat(attachments): list downloadable attachments on admin email detail page

The admin email detail view loaded the full email but never rendered its
attachments, so there was no way to download them from the admin UI (only
the public entry view and the feed enclosure exposed them).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Julien HerrandClaude Opus 4.7 committed 2026-05-23 18:11:29 +02:00
1 parent 9141cf89bd
commit 6cd2d425a2
4 files changed
+135 -1

No files matched your search

+61
View File
@@ -729,6 +729,67 @@ describe("Admin Routes", () => {
expect(indicatorCount).toBe(1);
});
it("lists attachments with download links on the email detail page", async () => {
const authCookie = await loginAndGetCookie();
const feedId = "detail-feed";
const emailKey = `feed:${feedId}:1`;
await mockEnv.EMAIL_STORAGE.put(
emailKey,
JSON.stringify({
subject: "With attachments",
from: "[email protected]",
content: "<p>hello</p>",
receivedAt: 1,
headers: {},
attachments: [
{
id: "att-123",
filename: "report final.pdf",
contentType: "application/pdf",
size: 2048,
},
],
}),
);
const res = await request(`/admin/emails/${emailKey}`, {
headers: { Cookie: authCookie },
});
expect(res.status).toBe(200);
const body = await res.text();
expect(body).toContain("Attachments");
expect(body).toContain(
`/files/att-123/${encodeURIComponent("report final.pdf")}`,
);
expect(body).toContain("report final.pdf");
expect(body).toContain("2.0 KB");
});
it("does not render an attachments section when the email has none", async () => {
const authCookie = await loginAndGetCookie();
const feedId = "detail-feed";
const emailKey = `feed:${feedId}:2`;
await mockEnv.EMAIL_STORAGE.put(
emailKey,
JSON.stringify({
subject: "No attachments",
from: "[email protected]",
content: "<p>hello</p>",
receivedAt: 2,
headers: {},
}),
);
const res = await request(`/admin/emails/${emailKey}`, {
headers: { Cookie: authCookie },
});
expect(res.status).toBe(200);
const body = await res.text();
expect(body).not.toContain("Attachments");
});
it("form-based bulk-delete also removes R2 attachments", async () => {
const r2Env = createMockEnv({ withR2: true }) as unknown as Env;
const bucket = r2Env.ATTACHMENT_BUCKET as unknown as {