da172a3117
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
SCHEMA_VERSION,
|
|
emptyArtifact,
|
|
serializeArtifact,
|
|
type Artifact,
|
|
} from "../src/model";
|
|
|
|
function sampleArtifact(): Artifact {
|
|
return {
|
|
schemaVersion: SCHEMA_VERSION,
|
|
document: { path: "docs/spec.md" },
|
|
anchors: {
|
|
a2: { fingerprint: { text: "beta", before: "x", after: "y", lineHint: 5 } },
|
|
a1: { fingerprint: { text: "alpha", before: "", after: "", lineHint: 1 } },
|
|
},
|
|
threads: [
|
|
{
|
|
id: "t1",
|
|
anchorId: "a1",
|
|
status: "open",
|
|
messages: [
|
|
{ id: "m1", author: { kind: "human", id: "ben" }, body: "hi", createdAt: "2026-06-10T00:00:00.000Z" },
|
|
],
|
|
},
|
|
],
|
|
attributions: [],
|
|
proposals: [],
|
|
};
|
|
}
|
|
|
|
describe("serializeArtifact", () => {
|
|
it("round-trips through JSON.parse", () => {
|
|
const a = sampleArtifact();
|
|
const parsed = JSON.parse(serializeArtifact(a)) as Artifact;
|
|
expect(parsed).toEqual(a);
|
|
});
|
|
|
|
it("emits stable, sorted anchor keys and a trailing newline regardless of insertion order", () => {
|
|
const a = sampleArtifact();
|
|
const out = serializeArtifact(a);
|
|
expect(out.endsWith("\n")).toBe(true);
|
|
// a1 must serialize before a2 even though a2 was inserted first
|
|
expect(out.indexOf('"a1"')).toBeLessThan(out.indexOf('"a2"'));
|
|
// byte-identical on re-serialize
|
|
expect(serializeArtifact(JSON.parse(out))).toBe(out);
|
|
});
|
|
|
|
it("emptyArtifact has the shared F3/F4 extension points present and empty", () => {
|
|
const e = emptyArtifact("docs/x.md");
|
|
expect(e.schemaVersion).toBe(SCHEMA_VERSION);
|
|
expect(e.document.path).toBe("docs/x.md");
|
|
expect(e.anchors).toEqual({});
|
|
expect(e.threads).toEqual([]);
|
|
expect(e.attributions).toEqual([]);
|
|
expect(e.proposals).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("cross-rung identity (F5 SLICE-2)", () => {
|
|
it("round-trips email on human provenance, omitted when absent", () => {
|
|
const a = emptyArtifact("docs/x.md");
|
|
a.anchors["a_1"] = { fingerprint: { text: "alpha", before: "", after: "", lineHint: 0 } };
|
|
a.threads.push({
|
|
id: "t_1",
|
|
anchorId: "a_1",
|
|
status: "open",
|
|
messages: [
|
|
{ id: "m_1", author: { kind: "human", id: "ben", email: "ben@wiggleverse.org" }, body: "hi", createdAt: "2026-06-10T00:00:00.000Z" },
|
|
{ id: "m_2", author: { kind: "human", id: "anon" }, body: "yo", createdAt: "2026-06-10T00:00:01.000Z" },
|
|
],
|
|
});
|
|
const out = serializeArtifact(a);
|
|
const parsed = JSON.parse(out) as Artifact;
|
|
expect(parsed.threads[0].messages[0].author).toEqual({ kind: "human", id: "ben", email: "ben@wiggleverse.org" });
|
|
expect("email" in parsed.threads[0].messages[1].author).toBe(false);
|
|
expect(serializeArtifact(parsed)).toBe(out);
|
|
});
|
|
|
|
it("round-trips agent.onBehalfOf and omits it when absent", () => {
|
|
const a = emptyArtifact("docs/x.md");
|
|
a.attributions.push({
|
|
id: "at_1",
|
|
anchorId: "a_1",
|
|
author: {
|
|
kind: "agent",
|
|
id: "claude",
|
|
agent: { sdk: "@cline/sdk", model: "sonnet", sessionId: "run-1", onBehalfOf: { id: "benstull", email: "ben@wiggleverse.org" } },
|
|
},
|
|
createdAt: "2026-06-10T00:00:00.000Z",
|
|
updatedAt: "2026-06-10T00:00:00.000Z",
|
|
});
|
|
const out = serializeArtifact(a);
|
|
const parsed = JSON.parse(out) as Artifact;
|
|
const author = parsed.attributions[0].author;
|
|
expect(author.kind).toBe("agent");
|
|
if (author.kind === "agent") {
|
|
expect(author.agent.onBehalfOf).toEqual({ id: "benstull", email: "ben@wiggleverse.org" });
|
|
}
|
|
expect(out.includes("onBehalfOf")).toBe(true);
|
|
const a2 = emptyArtifact("docs/x.md");
|
|
expect(serializeArtifact(a2).includes("onBehalfOf")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("attributions[] (F3 SLICE-1)", () => {
|
|
it("round-trips an attribution record through serialize → parse", () => {
|
|
const a = emptyArtifact("docs/x.md");
|
|
a.anchors["a_1"] = { fingerprint: { text: "alpha", before: "", after: " beta", lineHint: 0 } };
|
|
a.attributions.push({
|
|
id: "at_1",
|
|
anchorId: "a_1",
|
|
author: { kind: "agent", id: "claude", agent: { sdk: "@cline/sdk", model: "sonnet", sessionId: "run-1" } },
|
|
createdAt: "2026-06-10T00:00:00.000Z",
|
|
updatedAt: "2026-06-10T00:00:00.000Z",
|
|
turnId: "turn-1",
|
|
});
|
|
const parsed = JSON.parse(serializeArtifact(a)) as Artifact;
|
|
expect(parsed.attributions).toEqual(a.attributions);
|
|
});
|
|
|
|
it("omits turnId when undefined and serializes stably", () => {
|
|
const a = emptyArtifact("docs/x.md");
|
|
a.attributions.push({
|
|
id: "at_2",
|
|
anchorId: "a_2",
|
|
author: { kind: "human", id: "ben" },
|
|
createdAt: "2026-06-10T00:00:00.000Z",
|
|
updatedAt: "2026-06-10T00:00:00.000Z",
|
|
});
|
|
const s1 = serializeArtifact(a);
|
|
expect(s1.includes("turnId")).toBe(false);
|
|
const s2 = serializeArtifact(JSON.parse(s1) as Artifact);
|
|
expect(s2).toBe(s1);
|
|
});
|
|
});
|