72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
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();
|
|
});
|
|
});
|