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([]); + }); });