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:
73
e2e/specs/06-export/download-retired-epoch.spec.ts
Normal file
73
e2e/specs/06-export/download-retired-epoch.spec.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user