From d5d7908ff6ab88b334b2d490f58309ab3965a403 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 | 5 +++++ test/download/download.test.ts | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/TODO.md b/TODO.md index 0d5dc4c..b487621 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,11 @@ 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: `quak logout` ends the session on the server (issue 108). It calls `POST /users/logout` through the new `Client.logoutOnServer()`, then deletes `session.json` even when that call fails, says so and exits 1. It prints the 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([]); + }); }); -- 2.54.0