fix(cli): make e2e snapshots stable across machines and runs

- Strip volatile fields (timestamp, created, updated) from JSON before
  snapshotting using a stripVolatile helper
- Remove toMatchSnapshot() from test 2.1 to avoid embedding machine-
  specific tmp paths in the snapshot; use toContain assertions instead
- Replace GC count snapshot with structural shape assertions so counts
  don't need to match exact phase-history state

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 19:29:25 +08:00
parent b0d5b05457
commit 2ed097e207
2 changed files with 117 additions and 121 deletions
@@ -28,14 +28,13 @@ AGSJVKM01WNKZ (unnamed)
`;
exports[`Phase 1: CAS Core 1.6 get returns node JSON (snapshot) 1`] = `
"{
"type": "7XX5H51CVD9H0",
{
"payload": {
"age": 30,
"name": "Alice"
"name": "Alice",
},
"timestamp": 1780226624080
}"
"type": "7XX5H51CVD9H0",
}
`;
exports[`Phase 1: CAS Core 1.9 verify returns ok for valid node 1`] = `"ok"`;
@@ -45,14 +44,13 @@ exports[`Phase 1: CAS Core 1.10 refs lists direct references (snapshot) 1`] = `"
exports[`Phase 1: CAS Core 1.11 walk shows traversal tree (snapshot) 1`] = `"ERARPP19YJT05"`;
exports[`Phase 1: CAS Core 1.13 cat returns full node (snapshot) 1`] = `
"{
"type": "7XX5H51CVD9H0",
{
"payload": {
"age": 30,
"name": "Alice"
"name": "Alice",
},
"timestamp": 1780226624080
}"
"type": "7XX5H51CVD9H0",
}
`;
exports[`Phase 1: CAS Core 1.14 cat --payload returns only payload (snapshot) 1`] = `
@@ -62,186 +60,164 @@ exports[`Phase 1: CAS Core 1.14 cat --payload returns only payload (snapshot) 1`
}"
`;
exports[`Phase 2: Schema Validation 2.1 put {name:123} against string-schema fails with non-zero exit 1`] = `"Validation failed: payload in /var/folders/_x/kgw53jp56ngdfz2x1ly96wyc0000gn/T/json-cas-e2e-WXS50Y/bad-node.json does not match schema 7XX5H51CVD9H0"`;
exports[`Phase 2: Schema Validation 2.2 schema validate on valid node returns valid 1`] = `"valid"`;
exports[`Phase 2: Schema Validation 2.3 put against non-existent schema hash fails 1`] = `"Schema not found: AAAAAAAAAAAAA"`;
exports[`Phase 3: Variable System 3.1 var set creates variable 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": {
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624515,
"tags": {},
"labels": []
}
}"
"value": "ERARPP19YJT05",
},
}
`;
exports[`Phase 3: Variable System 3.2 var get returns variable 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": {
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624515,
"tags": {},
"labels": []
}
}"
"value": "ERARPP19YJT05",
},
}
`;
exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": [
{
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624515,
"tags": {},
"labels": []
}
]
}"
"value": "ERARPP19YJT05",
},
],
}
`;
exports[`Phase 3: Variable System 3.4 var list prefix filters by prefix 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": [
{
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624515,
"tags": {},
"labels": []
}
]
}"
"value": "ERARPP19YJT05",
},
],
}
`;
exports[`Phase 3: Variable System 3.5 var set upsert updates existing variable 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": {
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "F68P1BZ46YDXM",
"created": 1780226624515,
"updated": 1780226624700,
"tags": {},
"labels": []
}
}"
"value": "F68P1BZ46YDXM",
},
}
`;
exports[`Phase 3: Variable System 3.6 var tag adds kv tag and label 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": {
"labels": [
"important",
],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624771,
"tags": {
"env": "prod"
"env": "prod",
},
"labels": [
"important"
]
}
}"
"value": "ERARPP19YJT05",
},
}
`;
exports[`Phase 3: Variable System 3.7 var list --tag env:prod filters by kv tag 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": [
{
"labels": [
"important",
],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624771,
"tags": {
"env": "prod"
"env": "prod",
},
"labels": [
"important"
]
}
]
}"
"value": "ERARPP19YJT05",
},
],
}
`;
exports[`Phase 3: Variable System 3.8 var list --tag important filters by label 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": [
{
"labels": [
"important",
],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624771,
"tags": {
"env": "prod"
"env": "prod",
},
"labels": [
"important"
]
}
]
}"
"value": "ERARPP19YJT05",
},
],
}
`;
exports[`Phase 3: Variable System 3.9 var tag remove deletes label 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": {
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624878,
"tags": {
"env": "prod"
"env": "prod",
},
"labels": []
}
}"
"value": "ERARPP19YJT05",
},
}
`;
exports[`Phase 3: Variable System 3.10 var delete removes variable 1`] = `
"{
{
"type": "E1D32N3GT69Q8",
"value": [
{
"labels": [],
"name": "myapp/config",
"schema": "7XX5H51CVD9H0",
"value": "ERARPP19YJT05",
"created": 1780226624515,
"updated": 1780226624878,
"tags": {
"env": "prod"
"env": "prod",
},
"labels": []
}
]
}"
"value": "ERARPP19YJT05",
},
],
}
`;
exports[`Phase 3: Variable System 3.11 var get deleted variable returns not found 1`] = `"Error: Variable not found: name=myapp/config, schema=7XX5H51CVD9H0"`;
@@ -276,15 +252,6 @@ exports[`Phase 5: Render 5.1 render fills payload variables 1`] = `"Hello Alice!
exports[`Phase 5: Render 5.2 render --resolution with different value 1`] = `"Hello Alice!"`;
exports[`Phase 6: GC 6.1 gc runs without error 1`] = `
"{
"total": 13,
"reachable": 5,
"collected": 8,
"scanned": 2
}"
`;
exports[`Phase 7: Edge Cases 7.1 get non-existent hash errors gracefully 1`] = `"Node not found: AAAAAAAAAAAAA"`;
exports[`Phase 7: Edge Cases 7.3 var set empty name errors 1`] = `"Usage: json-cas var set <name> <hash> [--tag <tag>...]"`;
+43 -14
View File
@@ -34,6 +34,26 @@ async function runCli(
return { stdout, stderr, exitCode };
}
/**
* Parse JSON and strip volatile fields (timestamp, created, updated)
* so snapshots are stable across runs.
*/
function stripVolatile(json: string): unknown {
const strip = (v: unknown): unknown => {
if (Array.isArray(v)) return v.map(strip);
if (v !== null && typeof v === "object") {
const out: Record<string, unknown> = {};
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
if (k === "timestamp" || k === "created" || k === "updated") continue;
out[k] = strip(val);
}
return out;
}
return v;
};
return strip(JSON.parse(json));
}
// ---- Phase 1: CAS Core ----
describe("Phase 1: CAS Core", () => {
@@ -88,7 +108,7 @@ describe("Phase 1: CAS Core", () => {
test("1.6 get returns node JSON (snapshot)", async () => {
const { stdout, exitCode } = await runCli(["get", nodeHash]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("1.7 has returns true for existing node", async () => {
@@ -131,7 +151,7 @@ describe("Phase 1: CAS Core", () => {
test("1.13 cat returns full node (snapshot)", async () => {
const { stdout, exitCode } = await runCli(["cat", nodeHash]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("1.14 cat --payload returns only payload (snapshot)", async () => {
@@ -158,7 +178,8 @@ describe("Phase 2: Schema Validation", () => {
expect(exitCode).not.toBe(0);
expect(stdout).toBe("");
expect(stderr).toContain("Validation failed");
expect(stderr).toMatchSnapshot();
expect(stderr).toContain(typeHash);
// Do NOT snapshot stderr — it embeds a machine-specific tmp path
});
test("2.2 schema validate on valid node returns valid", async () => {
@@ -190,7 +211,7 @@ describe("Phase 3: Variable System", () => {
nodeHash,
]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("3.2 var get returns variable", async () => {
@@ -202,21 +223,21 @@ describe("Phase 3: Variable System", () => {
typeHash,
]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
expect(stdout).toContain(nodeHash);
});
test("3.3 var list shows all variables", async () => {
const { stdout, exitCode } = await runCli(["var", "list"]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
expect(stdout).toContain("myapp/config");
});
test("3.4 var list prefix filters by prefix", async () => {
const { stdout, exitCode } = await runCli(["var", "list", "myapp/"]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
expect(stdout).toContain("myapp/config");
});
@@ -231,7 +252,7 @@ describe("Phase 3: Variable System", () => {
node2Hash.trim(),
]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
// Restore original value
await runCli(["var", "set", "myapp/config", nodeHash]);
});
@@ -247,7 +268,7 @@ describe("Phase 3: Variable System", () => {
"important",
]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("3.7 var list --tag env:prod filters by kv tag", async () => {
@@ -259,7 +280,7 @@ describe("Phase 3: Variable System", () => {
]);
expect(exitCode).toBe(0);
expect(stdout).toContain("myapp/config");
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("3.8 var list --tag important filters by label", async () => {
@@ -271,7 +292,7 @@ describe("Phase 3: Variable System", () => {
]);
expect(exitCode).toBe(0);
expect(stdout).toContain("myapp/config");
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("3.9 var tag remove deletes label", async () => {
@@ -284,7 +305,7 @@ describe("Phase 3: Variable System", () => {
":important",
]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
// Verify label is gone
const { stdout: listOut } = await runCli([
"var",
@@ -302,7 +323,7 @@ describe("Phase 3: Variable System", () => {
"myapp/config",
]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
expect(stripVolatile(stdout)).toMatchSnapshot();
});
test("3.11 var get deleted variable returns not found", async () => {
@@ -414,7 +435,15 @@ describe("Phase 6: GC", () => {
test("6.1 gc runs without error", async () => {
const { exitCode, stdout } = await runCli(["gc"]);
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
// Assert structural shape only — exact counts depend on phase history
const result = JSON.parse(stdout) as Record<string, unknown>;
expect(typeof result["total"]).toBe("number");
expect(typeof result["reachable"]).toBe("number");
expect(typeof result["collected"]).toBe("number");
expect(typeof result["scanned"]).toBe("number");
expect(result["total"] as number).toBeGreaterThanOrEqual(
result["reachable"] as number,
);
});
test("6.2 gc preserves node referenced by a var", async () => {