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("lists keys under a prefix", async () => {42 await store.put("p/a.txt", Buffer.from("a"));43 await store.put("p/sub/b.txt", Buffer.from("b"));44 expect(await store.list("p")).toEqual(["p/a.txt", "p/sub/b.txt"]);45 });46});47