// The CLI presents a file by its own decrypted metadata, not the PhotoRecord // projection (issue #52). For a renamed file the two disagree: the projection // prefers `editedName` and reports `editedTime` in milliseconds, while the CLI // must print the raw `metadata.title` and `metadata.creationTime` (microseconds) // and name downloads after the raw title, byte-identical to the pre-library CLI. // // This locks in that contrast: the shared output helpers emit the raw values, // and the projection of the same file emits the edited ones — so a regression // that re-sourced the CLI from the projection would fail here. import { describe, it, expect } from "vitest"; import { fileListRow, fileListLine, originalName, thumbnailName, } from "../../src/cli-output.js"; import { deriveRecords } from "../../src/library/records.js"; import type { EnteFile } from "../../src/model/types.js"; // Microseconds, as Ente stores times. const RAW_CREATION = 1700000000000000; const EDITED_TIME = 1710000000000000; const RAW_TITLE = "IMG_0001.HEIC"; const EDITED_NAME = "Sunset.heic"; // A file the user has renamed and re-dated: basic metadata holds the original // title and capture time; public magic metadata holds the edits. const renamedFile: EnteFile = { id: 100, collectionID: 10, ownerID: 42, key: new Uint8Array(), metadata: { title: RAW_TITLE, fileType: "image", creationTime: RAW_CREATION, modificationTime: RAW_CREATION, }, pubMagicMetadata: { editedName: EDITED_NAME, editedTime: EDITED_TIME }, file: { decryptionHeader: "" }, thumbnail: { decryptionHeader: "" }, updationTime: RAW_CREATION, }; describe("CLI file output (issue #52)", () => { it("emits the raw title and microsecond creationTime for --json", () => { expect(fileListRow(renamedFile)).toEqual({ id: 100, title: RAW_TITLE, fileType: "image", creationTime: RAW_CREATION, collectionID: 10, }); }); it("emits the raw title in the human column", () => { expect(fileListLine(renamedFile)).toBe(`100\timage\t${RAW_TITLE}`); }); it("names downloads after the raw title", () => { expect(originalName(renamedFile)).toBe(RAW_TITLE); expect(thumbnailName(renamedFile)).toBe(`thumb_${RAW_TITLE}`); }); it("sanitizes the title when naming `quak get` downloads", () => { // Without `--out`, the server-supplied title names the file, so it must // not be able to point outside the working directory. const hostile = { ...renamedFile, metadata: { ...renamedFile.metadata, title: "../../.bashrc" }, }; expect(originalName(hostile)).toBe("__.._.bashrc"); expect(thumbnailName(hostile)).toBe("thumb___.._.bashrc"); const untitled = { ...renamedFile, metadata: { ...renamedFile.metadata, title: "" }, }; expect(originalName(untitled)).toBe("file-100"); expect(thumbnailName(untitled)).toBe("thumb_file-100"); }); it("does not use the editedName/editedTime projection", () => { const record = deriveRecords([], [renamedFile]).photos.get(100); // The projection prefers the edits and reports milliseconds; the CLI // helpers above deliberately do not. expect(record?.title).toBe(EDITED_NAME); expect(record?.takenAt).toBe(Math.floor(EDITED_TIME / 1000)); expect(fileListRow(renamedFile).title).not.toBe(record?.title); expect(fileListRow(renamedFile).creationTime).not.toBe(record?.takenAt); }); });