test(e2e): restore the download-side 404 coverage the mint pre-check displaced

Both tests in export.spec.ts are still named "ZIP download 404s…" but now
assert only that `/export/ticket` refuses. That move was right — the mint
pre-validates, and refusing there spends none of the guest's three daily
downloads — but it left `resolve_export_file` unasserted on the download path
itself, so deleting that check would not have turned anything red.

It cannot be covered by minting against an already-dead archive, because the
mint refuses first. The order has to be: real release → mint while healthy →
retire the generation → download. Which is also precisely what happens to a
ticket already in flight when the host takes a photo down mid-download.

Carries its own positive control: the ticket is asserted to serve a 200 before
the epoch moves, so the 404 afterwards cannot be green for the wrong reason —
an expired or never-valid ticket would 404 too.
This commit is contained in:
fabi
2026-08-12 22:23:33 +02:00
parent 6475199670
commit b5a1580368

View File

@@ -0,0 +1,73 @@
/**
* A ticket minted against a LIVE archive must stop working the moment that archive is retired.
*
* This is the download-side half of the stale-keepsake guarantee, and it was left unasserted. Both
* tests in `export.spec.ts` are named "ZIP download 404s…" but now assert only that the *mint*
* refuses — correctly, since `/export/ticket` pre-validates and refusing there spends none of the
* guest's three daily downloads. The consequence is that nothing exercised `resolve_export_file`
* on the download path itself, so removing that check would not have turned anything red.
*
* It cannot be tested by minting against an already-dead archive (the mint refuses first), so the
* order has to be: real release → mint while healthy → retire → download. Which is also exactly
* what a host taking a photo down mid-download does to a ticket already in flight.
*/
import { test, expect } from '../../fixtures/test';
import { seedUpload } from '../../helpers/seed';
import { BASE } from '../../helpers/env';
const SLUG = 'e2e-test-event';
test.describe('Export — a retired generation cannot be downloaded', () => {
test('a ticket minted before the epoch moved is refused at the download, not served stale', async ({
host,
db,
}) => {
test.setTimeout(60_000);
const bearer = { Authorization: `Bearer ${host.jwt}` };
await seedUpload(host.jwt, { caption: 'about to go stale' });
const rel = await fetch(`${BASE}/api/v1/host/gallery/release`, {
method: 'POST',
headers: bearer,
});
expect(rel.status).toBe(204);
await expect
.poll(
async () => {
const res = await fetch(`${BASE}/api/v1/export/status`, { headers: bearer });
return (await res.json()).zip?.status;
},
{ timeout: 45_000, intervals: [500] }
)
.toBe('done');
// Mint while everything is healthy. This must succeed, or the assertion below proves nothing —
// a 404 on a ticket that was never valid would be green for the wrong reason.
const mint = await fetch(`${BASE}/api/v1/export/ticket?kind=zip`, {
method: 'POST',
headers: bearer,
});
expect(mint.status, 'the ticket must be mintable while the archive is live').toBe(200);
const { ticket } = await mint.json();
expect(ticket).toBeTruthy();
// And it genuinely works right now — the positive control for the negative below.
const before = await fetch(`${BASE}/api/v1/export/zip?ticket=${encodeURIComponent(ticket)}`);
expect(before.status, 'the ticket must serve the archive while it is current').toBe(200);
// Now retire the generation, which is what a reopen or a post-release takedown does.
await db.setExportZipReady(SLUG, false);
// The SAME ticket — still unexpired, still under its redemption cap, session still valid —
// must now be refused. Readiness is derived at READ time from `job.epoch = event.export_epoch`,
// so this is the check that stops a superseded archive being served to a guest who happened to
// hold a ticket when the host moderated.
const after = await fetch(`${BASE}/api/v1/export/zip?ticket=${encodeURIComponent(ticket)}`);
expect(
after.status,
'a retired archive must 404 on the download path, not be served from a still-valid ticket'
).toBe(404);
});
});