From 767545f4abd6c3148a7e7745479209b6f3b7d677 Mon Sep 17 00:00:00 2001 From: sneak Date: Wed, 14 Oct 2026 06:37:26 +0000 Subject: [PATCH] Test: lib.backup() takes no new original once onProgress has thrown or its lock is lost The onProgress test now runs eight photos with a pool of five and holds only the first five downloads, so a backup that went on after the throw would download and store the other three. The end-to-end test of a removed backup.lock expects the backup to stop after its three originals, before it fetches ML data. Model: opus-5-5 --- test/cli/backup-concurrency.test.ts | 51 ++++++++++++++++++----------- test/cli/backup-end-to-end.test.ts | 8 ++--- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/test/cli/backup-concurrency.test.ts b/test/cli/backup-concurrency.test.ts index be17b94..f3c476c 100644 --- a/test/cli/backup-concurrency.test.ts +++ b/test/cli/backup-concurrency.test.ts @@ -397,19 +397,33 @@ describe("lib.backup()", () => { }); // A throw from `onProgress` fails the backup: `lib.backup()` rejects with - // it. It rejects only once it has finished with every original it was - // working on, so nothing is written into the backup after it has ended - // and removed its lock. Here, with a content pool of eight, all eight - // downloads wait at once. Photo 103's is let go first and answered 404, - // and the line reporting that failure throws while the other seven wait. - it("when onProgress throws, rejects only once the originals it was working on are in place", async () => { + // it. From then on the backup starts no further original. It finishes the + // ones it is working on, and rejects only once they are in place, so + // nothing is written into the backup after it has ended and removed its + // lock. Here the first five downloads wait. Photo 103's is let go first + // and answered 404, and the line reporting that failure throws while the + // other four wait. Only those five downloads are held: a backup that went + // on would store the other three photos. + it("when onProgress throws, starts no further original, and rejects once the originals it was working on are in place", async () => { const dir = mkdtempSync(join(root, "backup-")); - const holding = holdingServer(103); - const lib = await openLibrary( - dir, - holding.fetch, - new RequestPools({ contentConcurrency: 8 }), - ); + const downloads: number[] = []; + const held: { id: number; release: () => void }[] = []; + const fetch: typeof globalThis.fetch = async (input, init) => { + const id = downloadOf(input); + if (id !== undefined) { + downloads.push(id); + if (downloads.length <= 5) { + await new Promise((release) => + held.push({ id, release }), + ); + } + if (id === 103) { + return new Response("not found", { status: 404 }); + } + } + return server(input, init); + }; + const lib = await openLibrary(dir, fetch); const lines: string[] = []; try { const backup = lib.backup({ @@ -432,12 +446,12 @@ describe("lib.backup()", () => { return err; }); - await vi.waitFor(() => expect(holding.held).toHaveLength(8), { + await vi.waitFor(() => expect(held).toHaveLength(5), { interval: 5, timeout: 5000, }); - const at103 = holding.held.findIndex((h) => h.id === 103); - holding.held.splice(at103, 1)[0]!.release(); + const at103 = held.findIndex((h) => h.id === 103); + held.splice(at103, 1)[0]!.release(); await vi.waitFor( () => expect( @@ -445,14 +459,13 @@ describe("lib.backup()", () => { ).toBe(true), { interval: 5, timeout: 5000 }, ); - for (const download of holding.held) download.release(); + for (const download of held) download.release(); expect(await rejection).toEqual( new Error("the progress output is closed"), ); - expect(storedWhenRejected).toEqual( - PHOTO_IDS.filter((id) => id !== 103), - ); + expect(downloads.sort()).toEqual([101, 102, 103, 104, 105]); + expect(storedWhenRejected).toEqual([101, 102, 104, 105]); expect(lockedWhenRejected).toBe(false); } finally { await lib.close(); diff --git a/test/cli/backup-end-to-end.test.ts b/test/cli/backup-end-to-end.test.ts index 0ec1fd7..45865ba 100644 --- a/test/cli/backup-end-to-end.test.ts +++ b/test/cli/backup-end-to-end.test.ts @@ -705,9 +705,10 @@ describe("a backup whose lock is lost", () => { }; // The backup downloads up to five originals at once, so all three are - // under way when the lock is lost, and beach.jpg is stored. `quak backup` - // prints why as one line, with no stack trace, and exits 1. - it("stops quak backup before its next file when backup.lock is removed, and exits 1", async () => { + // under way when the lock is lost, and beach.jpg is stored. The backup + // then stops before it fetches ML data. `quak backup` prints why as one + // line, with no stack trace, and exits 1. + it("stops quak backup before it fetches ML data when backup.lock is removed, and exits 1", async () => { const result = backup(dir, {}, fetch); await heldRequested; rmSync(lockPath, { recursive: true }); @@ -722,7 +723,6 @@ describe("a backup whose lock is lost", () => { "Fetching original beach.jpg (100)...\n" + "Fetching original IMG_0002.HEIC (300)...\n" + "Fetching original toast.mp4 (200)...\n" + - "Fetching ML data...\n" + `quak: lost the lock on ${dir}: ENOENT: no such file or directory, stat '${lockPath}'\n`, ); expect(downloads.sort()).toEqual([100, 200, 300]);