Skip to content

Commit 280a0e7

Browse files
dmealingclaude
andcommitted
test(#213): real-PG round-trip gate for the write-through entity read-view
The memory's migrate gate (emit → apply to a real engine → introspect → re-diff must be EMPTY) now covers the FR-024 §7 entity read-view end to end, not just the unit-level DDL shape. Adds a convergence test to view-lifecycle-pg: a write-through entity (writable table + non-primary replica view + a derived origin.passthrough column, over a required-FK join) migrates once against real Postgres, then a second migrate is a genuine NO-OP. Gates BOTH #213 holes on a live engine: the `orders` table is introspected with exactly [id, customerId] (hole 1 — the derived customerName never leaks onto the table), and `v_order_with_customer` is introspected with [id, customerId, customerName] (hole 2 — the replica view IS emitted, o.* + the joined derived column). Convergence proves the emitted view fingerprints + applies cleanly and the re-diff sees no drift. Verified: view-lifecycle-pg 10/10 pass (9 prior + this) against a Postgres testcontainer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGQ7oSuNcjhsMHWwZzhBwr
1 parent f487278 commit 280a0e7

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

server/typescript/packages/integration-tests/test/view-lifecycle-pg.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,45 @@ describe("view lifecycle — real Postgres", () => {
199199
await assertConverged(second.expected);
200200
});
201201

202+
// #213 — a write-through ENTITY (FR-024 §7 read-view): the writable table carries
203+
// NO derived column, its replica view IS emitted, and the whole thing converges on
204+
// a real engine (emit → apply → introspect → re-diff EMPTY). This gates BOTH holes
205+
// end-to-end: hole 1 (derived-field leak into the table) and hole 2 (view never
206+
// emitted). literal naming strategy → column names are the field names verbatim.
207+
test("CONVERGENCE: a write-through entity's table (no derived col) + replica view migrate once, then NO-OP (#213)", async () => {
208+
const m = JSON.stringify({ "metadata.root": { package: "acme", children: [
209+
{ "object.entity": { name: "Customer", children: [
210+
{ "source.rdb": { "@table": "customers" } },
211+
{ "field.long": { name: "id" } },
212+
{ "field.string": { name: "name" } },
213+
{ "identity.primary": { name: "pk", "@fields": "id" } },
214+
] } },
215+
{ "object.entity": { name: "Order", children: [
216+
{ "source.rdb": { "@role": "primary", "@table": "orders" } },
217+
{ "source.rdb": { "@role": "replica", "@kind": "view", "@table": "v_order_with_customer" } },
218+
{ "field.long": { name: "id" } },
219+
{ "field.long": { name: "customerId", "@required": true } },
220+
{ "field.string": { name: "customerName", children: [
221+
{ "origin.passthrough": { "@from": "Customer.name", "@via": "Order.customer" } } ] } },
222+
{ "relationship.association": { name: "customer", "@objectRef": "Customer", "@cardinality": "one" } },
223+
{ "identity.primary": { name: "pk", "@fields": "id" } },
224+
{ "identity.reference": { name: "ref_customer", "@fields": "customerId", "@references": "Customer" } },
225+
] } },
226+
]}});
227+
const { expected, up } = await migrate(m);
228+
229+
// The writable table is created WITHOUT the derived column; the replica view is created.
230+
expect(up).toContain(`CREATE TABLE "orders"`);
231+
expect(up).toContain(`CREATE VIEW "v_order_with_customer" AS`);
232+
// The orders TABLE carries only the stored fields — never the derived customerName.
233+
expect(await viewCols("orders")).toEqual(["id", "customerId"]);
234+
// The VIEW exposes o.* (stored) + the derived join column.
235+
expect(await viewCols("v_order_with_customer")).toEqual(["id", "customerId", "customerName"]);
236+
237+
// THE gate: a second migrate against the just-migrated DB is a no-op.
238+
await assertConverged(expected);
239+
});
240+
202241
// -------------------------------------------------------------------------
203242
// Non-destructive replace — the part that protects downstream applications.
204243
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)