import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { assertSafeKey, FilesystemStorage, sha256Hex } from "./index.js"; describe("FilesystemStorage", () => { let root: string; let store: FilesystemStorage; beforeEach(async () => { root = await mkdtemp(join(tmpdir(), "as-store-")); store = new FilesystemStorage(root); }); afterEach(() => rm(root, { recursive: true, force: true })); it("stores, hashes and reads back", async () => { const obj = await store.put("runs/x/a1/manuscript/main.tex", Buffer.from("hello")); expect(obj.sha256).toBe(sha256Hex("hello")); expect((await store.get("runs/x/a1/manuscript/main.tex")).toString()).toBe("hello"); expect(await store.head("runs/x/a1/manuscript/main.tex")).toMatchObject({ bytes: 5 }); }); it("is write-once and rejects different content under the same key", async () => { await store.put("k/a", Buffer.from("one")); await store.put("k/a", Buffer.from("one")); // idempotent await expect(store.put("k/a", Buffer.from("two"), sha256Hex("two"))).rejects.toThrow(/different digest/); }); it("rejects digest mismatch", async () => { await expect(store.put("k/b", Buffer.from("x"), "0".repeat(64))).rejects.toThrow(/digest mismatch/); expect(await store.exists("k/b")).toBe(false); }); it("rejects path traversal", () => { expect(() => assertSafeKey("../etc/passwd")).toThrow(); expect(() => assertSafeKey("runs/../../x")).toThrow(); expect(() => assertSafeKey("/abs")).toThrow(); expect(() => store.absolutePath("runs/x/../../../y")).toThrow(); }); it("accepts artifacts named *.sha256 next to their siblings (no sidecar collision)", async () => { await store.put("c/tool.c", Buffer.from("int main(){}")); const obj = await store.put("c/tool.c.sha256", Buffer.from("abc tool.c\n")); expect(obj.bytes).toBe(12); expect((await store.get("c/tool.c")).toString()).toBe("int main(){}"); expect((await store.head("c/tool.c"))?.sha256).toBe(sha256Hex("int main(){}")); expect(await store.list("c")).toEqual(["c/tool.c", "c/tool.c.sha256"]); }); it("lists keys under a prefix", async () => { await store.put("p/a.txt", Buffer.from("a")); await store.put("p/sub/b.txt", Buffer.from("b")); expect(await store.list("p")).toEqual(["p/a.txt", "p/sub/b.txt"]); }); });