From 77649ce266dca325ff160130bb32bfb6183f9069 Mon Sep 17 00:00:00 2001 From: clawbot Date: Wed, 23 Sep 2026 04:19:37 +0000 Subject: [PATCH] Test the live-photo hash check's error paths (closes #117) Three downloadFile tests cover a live photo whose ZIP names an unknown compression method, one whose ZIP has no image entry and one with no video entry. Each checks that nothing is stored and the error names the file ID; the unreadable ZIP is also checked not to be retried. Each test fails when its check in livePhotoHasher is removed. Model: opus-5-5 --- TODO.md | 4 ++++ test/download/download.test.ts | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/TODO.md b/TODO.md index 172c0d7..f32023f 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,10 @@ Tag v1.0.0. # Completed Steps +- 2026-09-23: Tested the live-photo hash check's error paths (issue 117). Tests + download a live photo whose ZIP names an unknown compression method, one whose + ZIP has no image entry and one with no video entry, and check that nothing is + stored and the error names the file ID; the unreadable one is not retried. - 2026-09-23: Checked downloaded originals against their recorded content hash (issue 68). `downloadFile`, which `quak get`, the content cache and backup all use, hashes the decrypted bytes (unkeyed BLAKE2b-512, standard base64) and diff --git a/test/download/download.test.ts b/test/download/download.test.ts index 304dbd9..4e7e03e 100644 --- a/test/download/download.test.ts +++ b/test/download/download.test.ts @@ -1747,4 +1747,42 @@ describe("downloadFile content hash", () => { expect(readdirSync(t.dir)).toEqual([]); }); + + it("rejects a live photo that is not a readable ZIP and does not retry", async () => { + // Bytes 8-9 of a ZIP entry's local header name its compression + // method; 99 is one no reader knows, so the entry cannot be read. + const zip = livePhotoZip.slice(); + zip[8] = 99; + zip[9] = 0; + const t = setup(zip, { fileType: "livePhoto", hash: livePhotoHash }); + + await expect(t.run()).rejects.toThrow( + /file 999: live photo is not a readable ZIP/, + ); + + expect(readdirSync(t.dir)).toEqual([]); + expect(t.requests()).toBe(1); + }); + + it("rejects a live photo ZIP with no image entry", async () => { + const zip = zipSync({ "video.mov": patternBytes(900, 82) }); + const t = setup(zip, { fileType: "livePhoto", hash: livePhotoHash }); + + await expect(t.run()).rejects.toThrow( + /file 999: live photo ZIP does not hold both an image and a video/, + ); + + expect(readdirSync(t.dir)).toEqual([]); + }); + + it("rejects a live photo ZIP with no video entry", async () => { + const zip = zipSync({ "image.heic": patternBytes(500, 81) }); + const t = setup(zip, { fileType: "livePhoto", hash: livePhotoHash }); + + await expect(t.run()).rejects.toThrow( + /file 999: live photo ZIP does not hold both an image and a video/, + ); + + expect(readdirSync(t.dir)).toEqual([]); + }); });