spb/autoscholar
Public
1import { mkdtemp, rm } from "node:fs/promises";2import { tmpdir } from "node:os";3import { join } from "node:path";4import { afterEach, beforeEach, describe, expect, it } from "vitest";5import { assertSafeKey, FilesystemStorage, sha256Hex } from "./index.js";67describe("FilesystemStorage", () => {8 let root: string;9 let store: FilesystemStorage;10 beforeEach(async () => {11 root = await mkdtemp(join(tmpdir(), "as-store-"));12 store = new FilesystemStorage(root);13 });14 afterEach(() => rm(root, { recursive: true, force: true }));1516 it("stores, hashes and reads back", async () => {17 const obj = await store.put("runs/x/a1/manuscript/main.tex", Buffer.from("hello"));18 expect(obj.sha256).toBe(sha256Hex("hello"));19 expect((await store.get("runs/x/a1/manuscript/main.tex")).toString()).toBe("hello");20 expect(await store.head("runs/x/a1/manuscript/main.tex")).toMatchObject({ bytes: 5 });21 });2223 it("is write-once and rejects different content under the same key", async () => {24 await store.put("k/a", Buffer.from("one"));25 await store.put("k/a", Buffer.from("one")); // idempotent26 await expect(store.put("k/a", Buffer.from("two"), sha256Hex("two"))).rejects.toThrow(/different digest/);27 });2829 it("rejects digest mismatch", async () => {30 await expect(store.put("k/b", Buffer.from("x"), "0".repeat(64))).rejects.toThrow(/digest mismatch/);31 expect(await store.exists("k/b")).toBe(false);32 });3334 it("rejects path traversal", () => {35 expect(() => assertSafeKey("../etc/passwd")).toThrow();36 expect(() => assertSafeKey("runs/../../x")).toThrow();37 expect(() => assertSafeKey("/abs")).toThrow();38 expect(() => store.absolutePath("runs/x/../../../y")).toThrow();39 });4041 it("accepts artifacts named *.sha256 next to their siblings (no sidecar collision)", async () => {42 await store.put("c/tool.c", Buffer.from("int main(){}"));43 const obj = await store.put("c/tool.c.sha256", Buffer.from("abc tool.c\n"));44 expect(obj.bytes).toBe(12);45 expect((await store.get("c/tool.c")).toString()).toBe("int main(){}");46 expect((await store.head("c/tool.c"))?.sha256).toBe(sha256Hex("int main(){}"));47 expect(await store.list("c")).toEqual(["c/tool.c", "c/tool.c.sha256"]);48 });4950 it("lists keys under a prefix", async () => {51 await store.put("p/a.txt", Buffer.from("a"));52 await store.put("p/sub/b.txt", Buffer.from("b"));53 expect(await store.list("p")).toEqual(["p/a.txt", "p/sub/b.txt"]);54 });55});56