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:
@@ -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>...]"`;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user