diff --git a/TODO.md b/TODO.md index e7f30fc..e6c2c8a 100644 --- a/TODO.md +++ b/TODO.md @@ -21,6 +21,11 @@ declares one. # Completed Steps +- 2026-09-28: Tested the live-photo writer's fsyncs (issue 130). A test checks + that the image's and the video's temp files are fsynced before either is + renamed into place, and the directory after both renames, as the `writeAtomic` + test does for one file. + - 2026-09-23: Live photos are stored as their image and their video (issue 107). A live photo, which Ente stores as one ZIP, is unpacked as it downloads into `.` for the image and for the video, each with its extension from diff --git a/test/download/download.test.ts b/test/download/download.test.ts index b361ae8..e08a948 100644 --- a/test/download/download.test.ts +++ b/test/download/download.test.ts @@ -1909,6 +1909,24 @@ describe("downloadFile live photos", () => { ]); }); + it("fsyncs each part's temp file before the renames and the directory after", async () => { + const t = setup(livePhotoZip(), livePhoto); + + await t.run(); + + // The same durability contract as `writeAtomic`, for two files: both + // temp files are on disk before either is renamed into place, and the + // directory is fsynced once both renames are done. + const [imageTmp, videoTmp] = renameHook.calls.map((c) => c.from); + expect(durabilityHook.events).toEqual([ + `sync:w:${imageTmp}`, + `sync:w:${videoTmp}`, + `rename:${join(t.dir, "f.heic")}`, + `rename:${join(t.dir, "f.mov")}`, + `sync:r:${t.dir}`, + ]); + }); + it("stores neither part when the video cannot be renamed into place", async () => { const t = setup(livePhotoZip(), livePhoto); renameHook.failWith = new Error("simulated rename failure");