fix(backgrounds): report a partial restore instead of throwing (#293)

restoreOriginalsForItem rethrew anything that was not NoOriginalToRestoreError, which handed asyncRoute a bare 500 and discarded how far the restore had already got. That breaks the invariant the feature is built on: photos restored before the failure really are back, and an admin standing in front of the modal needs the count to decide whether pressing the button again is worth anything. RestoreSummary now carries `failed` and the loop stops and reports, exactly the shape and the reasoning removeBackgroundsForItem already had.

The restore-originals route gains the missing 404 for an item that does not exist — remove-backgrounds always had it, and the two handlers are copy-paste rather than a shared helper, so nothing would have caught them diverging. draftingWorker's .catch is now only reachable if the image-listing query itself throws, since removeBackgroundsForItem no longer rejects over a single photo; its comment says so rather than describing behaviour that has moved.

The `failed` branch is covered by a unit test that stubs the database module in its own module registry. It cannot honestly be an integration test: the only failure the function can report is a database fault, and the only way to inject one into a real run is to interfere with the single pool every integration suite in the --runInBand process shares and that afterAll calls pool.end() on. Two tests that did exactly that are removed here — they left the suite reporting a failure against its own afterAll and leaking a handle that stopped it exiting. Nor is the fault reachable through data alone: the swap's WHERE original_image_path IS NOT NULL guarantees the value it writes into the NOT NULL image_path, and item_images carries no unique, check or foreign-key constraint on either column, so no row can be seeded that makes the statement fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 12:02:23 -05:00
co-authored by Claude Opus 5
parent dbb63bc3d2
commit 445c9c4a22
6 changed files with 141 additions and 14 deletions
@@ -381,7 +381,7 @@ describe('putting every original back', () => {
const summary = await restoreOriginalsForItem(itemId);
expect(summary).toEqual({ total: 2, restored: 2 });
expect(summary).toEqual({ total: 2, restored: 2, failed: false });
const { rows: after } = await pool.query<{ image_path: string }>(
`SELECT image_path FROM item_images WHERE item_id = $1 ORDER BY sort_order`,
@@ -412,6 +412,6 @@ describe('putting every original back', () => {
);
await removeImageBackground(images[0]!.id);
expect(await restoreOriginalsForItem(itemId)).toEqual({ total: 2, restored: 1 });
expect(await restoreOriginalsForItem(itemId)).toEqual({ total: 2, restored: 1, failed: false });
});
});