Files
united-workforce/packages/workflow-cas/__tests__/reachable.test.ts
T
xiaoju 6f000512d2 feat: Phase 1 — CAS thread storage types + helpers
- Add StartNode, StateNode, ContentMerkleNode types to workflow-protocol
- Add collectRefs() to workflow-cas — extracts CAS hashes from StateNode payload
- Add findReachableHashes() to workflow-cas — recursive mark traversal via refs[]
- Tests: 7 pass (collect-refs + reachable)

Refs #155, closes #156

小橘 <xiaoju@shazhou.work>
2026-05-09 07:30:47 +00:00

70 lines
2.1 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import type { CasStore } from "@uncaged/workflow-protocol";
import { stringify } from "yaml";
import { findReachableHashes } from "../src/reachable.js";
function yamlBlob(refs: readonly string[]): string {
return stringify({ type: "node", payload: {}, refs: [...refs] }, { indent: 2 });
}
function memoryCas(entries: Record<string, string>): CasStore {
const map = { ...entries };
return {
async put(): Promise<string> {
throw new Error("memoryCas.put not used in tests");
},
async get(hash: string): Promise<string | null> {
return map[hash] ?? null;
},
async delete(): Promise<void> {},
async list(): Promise<string[]> {
return Object.keys(map);
},
};
}
describe("findReachableHashes", () => {
test("walks refs recursively from a single root", async () => {
const cas = memoryCas({
R1: yamlBlob(["R2"]),
R2: yamlBlob(["R3"]),
R3: yamlBlob([]),
});
const reachable = await findReachableHashes(["R1"], cas);
expect([...reachable].sort()).toEqual(["R1", "R2", "R3"]);
});
test("union of reachability from multiple roots", async () => {
const cas = memoryCas({
A: yamlBlob(["X"]),
B: yamlBlob(["Y"]),
X: yamlBlob([]),
Y: yamlBlob(["Z"]),
Z: yamlBlob([]),
});
const reachable = await findReachableHashes(["A", "B"], cas);
expect([...reachable].sort()).toEqual(["A", "B", "X", "Y", "Z"]);
});
test("handles cycles via visited set", async () => {
const cas = memoryCas({
C1: yamlBlob(["C2"]),
C2: yamlBlob(["C1"]),
});
const reachable = await findReachableHashes(["C1"], cas);
expect(reachable.size).toBe(2);
expect(reachable.has("C1")).toBe(true);
expect(reachable.has("C2")).toBe(true);
});
test("does not throw when a ref points to a missing blob", async () => {
const cas = memoryCas({
H1: yamlBlob(["MISSINGHASH0000000000001"]),
});
const reachable = await findReachableHashes(["H1"], cas);
expect(reachable.has("H1")).toBe(true);
expect(reachable.has("MISSINGHASH0000000000001")).toBe(false);
});
});