test(e2e): follow the restore to its job before reading the record

The restore route answers 202 with a job id and leaves the work to the
backup-restore queue. The journey still expected the synchronous 200 and
read the record straight after the click. It now asserts the queued
answer and its status route, polls that route until the job reports
succeeded (a failed job ends the wait with its code), and only then
checks what the restore put back and dropped. The job runs on the
pg-boss worker inside the server under test, as the manual backup pass
earlier in the same file already relies on.
This commit is contained in:
Marc-André Bombeck committed 2026-09-25 13:48:31 +02:00
1 parent a46d0b41c8
commit 42e35ffbf9
1 file changed
+33 -2
+33 -2
View File
@@ -532,7 +532,38 @@ test.describe("Backup and restore, through the settings surfaces", () => {
await openBackupsConsole(page);
const response = await restoreThroughDialog(page, requireSnapshot());
expect(response.status()).toBe(200);
// The restore runs on the `backup-restore` queue: the request answers 202
// with the job and its status route, and the worker inside the server
// under test does the work. Nothing below is read until the job says it
// is done, so every assertion is about the finished restore rather than
// a moment in the middle of it.
expect(response.status()).toBe(202);
const queued = (await response.json()) as {
data: { jobId: string; status: string; statusUrl: string };
};
expect(queued.data.status).toBe("queued");
expect(queued.data.statusUrl).toBe(
`/api/admin/backups/restores/${queued.data.jobId}`,
);
await expect
.poll(
async () => {
const res = await page.request.get(queued.data.statusUrl);
const body = (await res.json()) as {
data: {
status: string;
failure: { code: string } | null;
} | null;
};
// A failed job ends the wait with its code in the message instead
// of running the clock out on a state that will never change.
return body.data?.status === "failed"
? `failed: ${body.data.failure?.code ?? "unknown"}`
: body.data?.status;
},
{ timeout: 60_000, intervals: [250, 500, 1_000] },
)
.toBe("succeeded");
const afterRestore = await storedMeasurements();
@@ -564,7 +595,7 @@ test.describe("Backup and restore, through the settings surfaces", () => {
// And the reading that arrived after the snapshot is gone.
// `docs/ops/backup-restore.md` says the restore "replaces the account's
// data tables", and the route's own words are "one transaction replaces
// data tables", and `restore-backup.ts` puts it as "one transaction replaces
// every serialized owner-scoped class". Replacing is not merging: whoever
// restores yesterday's copy loses today's readings, and that is the
// documented behaviour rather than an accident of the implementation.