From 2ed097e207e69127bc2133b943ec52fca583ff24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Sun, 31 May 2026 19:29:25 +0800 Subject: [PATCH] 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 --- .../src/__snapshots__/e2e.test.ts.snap | 181 +++++++----------- packages/cli-json-cas/src/e2e.test.ts | 57 ++++-- 2 files changed, 117 insertions(+), 121 deletions(-) diff --git a/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap b/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap index ef245e9..c3aa2b8 100644 --- a/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap +++ b/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap @@ -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 [--tag ...]"`; diff --git a/packages/cli-json-cas/src/e2e.test.ts b/packages/cli-json-cas/src/e2e.test.ts index b86307d..058dec2 100644 --- a/packages/cli-json-cas/src/e2e.test.ts +++ b/packages/cli-json-cas/src/e2e.test.ts @@ -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 = {}; + for (const [k, val] of Object.entries(v as Record)) { + 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; + 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 () => {