import { afterEach, describe, expect, test } from "bun:test"; import { createHash } from "node:crypto"; import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { FilesystemCas } from "../../server/cas"; import type { Sha256Digest } from "../../shared/build-protocol"; const roots: string[] = []; afterEach(async () => { await Promise.all( roots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); function digest(value: string): Sha256Digest { return `sha256:${createHash("sha256").update(value).digest("hex")}`; } describe("filesystem CAS", () => { test("stores, deduplicates, and verifies blobs", async () => { const root = await mkdtemp(join(tmpdir(), "kuber-cas-")); roots.push(root); const cas = new FilesystemCas(root); const expected = digest("hello"); expect(await cas.put(Buffer.from("hello"), expected)).toBe(expected); expect(await cas.has(expected)).toBe(true); expect(Buffer.from(await cas.get(expected)).toString()).toBe("hello"); expect( await Promise.all( Array.from({ length: 8 }, () => cas.put(Buffer.from("hello"))), ), ).toEqual(Array(8).fill(expected)); await expect(cas.put(Buffer.from("other"), expected)).rejects.toThrow( "digest mismatch", ); }); test("detects corruption on reads and existing writes", async () => { const root = await mkdtemp(join(tmpdir(), "kuber-cas-")); roots.push(root); const cas = new FilesystemCas(root); const expected = await cas.put(Buffer.from("hello")); const hex = expected.slice(7); await writeFile( join(root, "sha256", hex.slice(0, 2), hex.slice(2)), "tampered", ); await expect(cas.get(expected)).rejects.toThrow("Corrupt CAS blob"); await expect(cas.put(Buffer.from("hello"))).rejects.toThrow( "Corrupt CAS blob", ); }); test("does not follow a blob-path symlink", async () => { const root = await mkdtemp(join(tmpdir(), "kuber-cas-")); const outside = await mkdtemp(join(tmpdir(), "kuber-cas-outside-")); roots.push(root, outside); const cas = new FilesystemCas(root); const expected = digest("hello"); const hex = expected.slice(7); await writeFile(join(outside, "blob"), "hello"); await mkdir(join(root, "sha256", hex.slice(0, 2)), { recursive: true }); await symlink( join(outside, "blob"), join(root, "sha256", hex.slice(0, 2), hex.slice(2)), ); expect(await cas.has(expected)).toBe(false); await expect(cas.get(expected)).rejects.toThrow(); }); });