Files
ocas/packages/core/src/store.test.ts
T
xiaoju 56e3253829 feat(core): MemoryStore returns OcasStore (cas + var + tag)
Rewrite createMemoryStore() to return an OcasStore with three sub-stores:
cas, var, tag. The cas sub-store keeps the existing Map-based logic, but
its put is now synchronous; the in-memory VarStore and TagStore are new
Map-based implementations of the types defined in #39.

The legacy Store.put signature is widened to Hash | Promise<Hash> so that
the new sync cas can still be passed to helpers (bootstrap, gc, render,
schema, …) that have not yet been migrated to the unified surface.

Tests in packages/core were mechanically updated from store.* to
store.cas.* and new test suites for VarStore (var-store.test.ts) and
TagStore (tag-store.test.ts) cover the spec from issue #40.

Closes #40

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 06:30:01 +00:00

156 lines
6.3 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { BOOTSTRAP_STORE } from "./bootstrap-capable.js";
import { createMemoryStore } from "./store.js";
describe("A. createMemoryStore – shape", () => {
test("A1. returns an object with cas, var, tag sub-stores", () => {
const store = createMemoryStore();
expect(typeof store.cas).toBe("object");
expect(store.cas).not.toBeNull();
expect(typeof store.var).toBe("object");
expect(store.var).not.toBeNull();
expect(typeof store.tag).toBe("object");
expect(store.tag).not.toBeNull();
});
test("A2. cas/var/tag are independent objects", () => {
const store = createMemoryStore();
expect(store.cas).not.toBe(store.var as unknown as typeof store.cas);
expect(store.cas).not.toBe(store.tag as unknown as typeof store.cas);
expect(store.var).not.toBe(store.tag as unknown as typeof store.var);
});
});
describe("B. cas sub-store – meta and schema indexes", () => {
test("B1. listMeta and listSchemas are empty on a fresh store", () => {
const store = createMemoryStore();
expect(store.cas.listMeta()).toEqual([]);
expect(store.cas.listSchemas()).toEqual([]);
});
test("B2. self-referencing put adds hash to metaSet and listSchemas", () => {
const store = createMemoryStore();
const hash = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
expect(store.cas.listMeta().map((e) => e.hash)).toContain(hash);
expect(store.cas.listSchemas().map((e) => e.hash)).toContain(hash);
});
test("B3. regular put does not add hash to metaSet", () => {
const store = createMemoryStore();
const metaHash = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const schemaHash = store.cas.put(metaHash, { type: "string" });
expect(store.cas.listMeta().map((e) => e.hash)).not.toContain(schemaHash);
expect(store.cas.listMeta().map((e) => e.hash)).toContain(metaHash);
});
test("B4. schema typed by meta-schema appears in listSchemas", () => {
const store = createMemoryStore();
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const s = store.cas.put(m, { type: "string" });
const schemas = store.cas.listSchemas().map((e) => e.hash);
expect(schemas).toContain(m);
expect(schemas).toContain(s);
const meta = store.cas.listMeta().map((e) => e.hash);
expect(meta).toContain(m);
expect(meta).not.toContain(s);
});
test("B5. multiple meta-schemas (versioning)", () => {
const store = createMemoryStore();
const m1 = store.cas[BOOTSTRAP_STORE]({
type: "object",
title: "v1",
}) as string;
const m2 = store.cas[BOOTSTRAP_STORE]({
type: "object",
title: "v2",
}) as string;
const s1 = store.cas.put(m1, { type: "string" });
const s2 = store.cas.put(m2, { type: "number" });
const meta = store.cas.listMeta().map((e) => e.hash);
expect(meta).toContain(m1);
expect(meta).toContain(m2);
expect(meta).toHaveLength(2);
const schemas = store.cas.listSchemas().map((e) => e.hash);
expect(schemas).toContain(m1);
expect(schemas).toContain(m2);
expect(schemas).toContain(s1);
expect(schemas).toContain(s2);
});
test("B6. idempotent self-referencing put does not duplicate", () => {
const store = createMemoryStore();
const payload = { type: "object", title: "dup" };
const h1 = store.cas[BOOTSTRAP_STORE](payload) as string;
const h2 = store.cas[BOOTSTRAP_STORE](payload) as string;
expect(h1).toBe(h2);
const meta = store.cas.listMeta().map((e) => e.hash);
expect(meta.filter((h) => h === h1)).toHaveLength(1);
});
test("B7. delete removes hash from metaSet and listSchemas", () => {
const store = createMemoryStore();
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const s = store.cas.put(m, { type: "string" });
expect(store.cas.listMeta().map((e) => e.hash)).toContain(m);
expect(store.cas.listSchemas().map((e) => e.hash)).toContain(s);
store.cas.delete(m);
expect(store.cas.listMeta().map((e) => e.hash)).not.toContain(m);
expect(store.cas.listSchemas().map((e) => e.hash)).not.toContain(s);
expect(store.cas.listSchemas().map((e) => e.hash)).not.toContain(m);
});
test("B8. cas.put returns Hash synchronously (not a Promise)", () => {
const store = createMemoryStore();
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const result = store.cas.put(m, { type: "string" });
expect(typeof result).toBe("string");
expect(result).toHaveLength(13);
});
test("B9. has/get/delete reflect put state", () => {
const store = createMemoryStore();
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const h = store.cas.put(m, { type: "string" });
expect(store.cas.has(h)).toBe(true);
expect(store.cas.get(h)?.payload).toEqual({ type: "string" });
expect(store.cas.has("0000000000000")).toBe(false);
expect(store.cas.get("0000000000000")).toBeNull();
expect(store.cas.delete(h)).toBe(true);
expect(store.cas.delete(h)).toBe(false);
expect(store.cas.has(h)).toBe(false);
});
});
describe("E. cross-store independence", () => {
test("E1. tagging a hash does not surface it in cas.listByType", () => {
const store = createMemoryStore();
const fakeTarget = "0000000000000";
store.tag.tag(fakeTarget, [{ op: "set", key: "env", value: "prod" }]);
expect(store.cas.listByType(fakeTarget)).toEqual([]);
});
test("E2. setting a variable does not mutate the CAS node", () => {
const store = createMemoryStore();
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const s = store.cas.put(m, { type: "string" });
const value = store.cas.put(s, "hello");
const before = store.cas.get(value);
store.var.set("@app/x", value);
const after = store.cas.get(value);
expect(after).toEqual(before);
});
test("E3. cas.delete does not cascade-delete a variable referencing it", () => {
const store = createMemoryStore();
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
const s = store.cas.put(m, { type: "string" });
const value = store.cas.put(s, "hello");
store.var.set("@app/x", value);
store.cas.delete(value);
const got = store.var.get("@app/x", s);
expect(got).not.toBeNull();
expect(got?.value).toBe(value);
});
});