Skip to content

Commit c557f93

Browse files
os-zhuangclaude
andauthored
test(dogfood): complete the field-zoo round-trip matrix over every existing field type (#2033)
The matrix covered ~26 of field-zoo's ~47 types; this closes the gap so every existing authorable field type has a real HTTP round-trip proof, not just a static registration. No source change was needed — all the newly covered types already round-trip correctly, which is the point: this locks the existing surface in as a regression gate. Added coverage: - text family: markdown, html, richtext, code, signature, qrcode - temporal: datetime (instant preserved exactly) - structured JSON: vector, composite, repeater, location, address, image, file, avatar - relational: lookup, master_detail, tree (value fidelity — id string round-trips; FK/$expand covered elsewhere) - security: secret (encrypt-on-write, masked to SECRET_MASK on read), password (persists; generic path is opaque, auth owns hashing) f_summary is intentionally excluded (computed roll-up, null on a childless fixture row). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c890c59 commit c557f93

1 file changed

Lines changed: 47 additions & 6 deletions

File tree

packages/dogfood/test/field-zoo-roundtrip.dogfood.test.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@
1313

1414
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
1515
import showcaseStack from '@objectstack/example-showcase';
16+
import { SECRET_MASK } from '@objectstack/objectql';
1617
import { bootDogfoodStack, type DogfoodStack } from '../src/harness.js';
1718

1819
// A field-type coverage entry. `write` is the value POSTed; `expect` describes
1920
// how the value must come back. `equal` = exact (or set-equal for arrays);
20-
// `computed`/`present` cover server-owned fields you don't write.
21+
// `computed`/`present` cover server-owned fields you don't write; `masked`
22+
// covers `secret`, which is encrypt-on-write and masked on read.
2123
type Check =
2224
| { kind: 'equal'; write: unknown }
2325
| { kind: 'setEqual'; write: unknown[] }
24-
| { kind: 'present' } // server-assigned, just must be non-null
26+
| { kind: 'present'; write?: unknown } // written but only asserted non-null (e.g. one-way/opaque)
27+
| { kind: 'masked'; write: unknown } // secret: POSTed plaintext must read back as SECRET_MASK
2528
| { kind: 'computed'; expected: unknown }; // derived, asserted not written
2629

2730
interface FieldCase {
@@ -64,13 +67,45 @@ const MATRIX: FieldCase[] = [
6467
{ field: 'f_tags', type: 'tags', check: { kind: 'setEqual', write: ['alpha', 'beta', 'gamma'] } },
6568
// numeric scalar — same fidelity class as rating/slider (was TEXT-affinity)
6669
{ field: 'f_progress', type: 'progress', check: { kind: 'equal', write: 60 } },
70+
// rich text — all plain strings on the wire
71+
{ field: 'f_markdown', type: 'markdown', check: { kind: 'equal', write: '# Heading\n\nbody' } },
72+
{ field: 'f_html', type: 'html', check: { kind: 'equal', write: '<p>hi</p>' } },
73+
{ field: 'f_richtext', type: 'richtext', check: { kind: 'equal', write: '<b>rich</b>' } },
74+
{ field: 'f_code', type: 'code', check: { kind: 'equal', write: '{\n "a": 1\n}' } },
75+
{ field: 'f_signature', type: 'signature', check: { kind: 'equal', write: 'data:image/png;base64,AAAA' } },
76+
{ field: 'f_qrcode', type: 'qrcode', check: { kind: 'equal', write: 'https://objectstack.ai' } },
77+
// temporal — instant
78+
{ field: 'f_datetime', type: 'datetime', check: { kind: 'equal', write: '2024-03-15T14:30:00.000Z' } },
6779
// structured JSON
6880
{ field: 'f_json', type: 'json', check: { kind: 'equal', write: { a: 1, b: [2, 3] } } },
6981
{ field: 'f_color', type: 'color', check: { kind: 'equal', write: '#FF8800' } },
82+
{ field: 'f_vector', type: 'vector', check: { kind: 'equal', write: [0.1, 0.2, 0.3] } },
7083
// object-valued types that must store/parse as JSON, not stringify to TEXT
7184
{ field: 'f_record', type: 'record', check: { kind: 'equal', write: { home: '+1', work: '+2' } } },
7285
{ field: 'f_video', type: 'video', check: { kind: 'equal', write: { url: 'https://cdn/v.mp4', duration: 12 } } },
7386
{ field: 'f_audio', type: 'audio', check: { kind: 'equal', write: { url: 'https://cdn/a.mp3', duration: 30 } } },
87+
{ field: 'f_composite', type: 'composite', check: { kind: 'equal', write: { label: 'x', n: 1 } } },
88+
{ field: 'f_repeater', type: 'repeater', check: { kind: 'equal', write: [{ a: 1 }, { a: 2 }] } },
89+
{ field: 'f_location', type: 'location', check: { kind: 'equal', write: { lat: 37.77, lng: -122.42 } } },
90+
{ field: 'f_address', type: 'address', check: { kind: 'equal', write: { street: '1 Main', city: 'SF', country: 'US' } } },
91+
{ field: 'f_image', type: 'image', check: { kind: 'equal', write: { url: 'https://cdn/i.png', alt: 'i' } } },
92+
{ field: 'f_file', type: 'file', check: { kind: 'equal', write: { url: 'https://cdn/f.pdf', name: 'f.pdf', size: 1024 } } },
93+
{ field: 'f_avatar', type: 'avatar', check: { kind: 'equal', write: { url: 'https://cdn/a.png' } } },
94+
// relational — store a reference id as a string and read it back verbatim.
95+
// FK enforcement is off in this harness, so this asserts value fidelity
96+
// (id string → id string), not referential integrity / $expand (covered
97+
// elsewhere). The point here is the stored type doesn't drift.
98+
{ field: 'f_lookup', type: 'lookup', check: { kind: 'equal', write: 'acc_synthetic_0001' } },
99+
{ field: 'f_master_detail', type: 'master_detail', check: { kind: 'equal', write: 'proj_synthetic_0001' } },
100+
{ field: 'f_tree', type: 'tree', check: { kind: 'equal', write: 'cat_synthetic_0001' } },
101+
// security — `secret` encrypts on write and masks on read; `password` is a
102+
// credential type the generic CRUD path stores opaquely (auth owns hashing),
103+
// so assert it merely persists rather than asserting a plaintext contract.
104+
{ field: 'f_secret', type: 'secret', check: { kind: 'masked', write: 'topsecret-value' } },
105+
{ field: 'f_password', type: 'password', check: { kind: 'present', write: 'p@ssw0rd!' } },
106+
// NB: f_summary (roll-up) is intentionally absent — it's a computed
107+
// aggregate over related records, null on a childless fixture row; its
108+
// semantics are covered by dedicated roll-up tests, not value fidelity.
74109
// computed / system — not written, must materialize
75110
{ field: 'f_autonumber', type: 'autonumber', check: { kind: 'present' } },
76111
// f_number(42) * f_percent(75) / 100 = 31.5
@@ -85,11 +120,11 @@ describe('dogfood: field-type capability matrix round-trips over HTTP (#2004)',
85120
stack = await bootDogfoodStack(showcaseStack);
86121
const token = await stack.signIn();
87122

88-
// Build the create body from every `equal`/`setEqual` entry (+ required name).
123+
// Build the create body from every entry that carries a `write` value
124+
// (+ required name). `present`/`computed` server-owned fields are skipped.
89125
const body: Record<string, unknown> = { name: 'zoo-roundtrip' };
90126
for (const c of MATRIX) {
91-
if (c.check.kind === 'equal') body[c.field] = c.check.write;
92-
else if (c.check.kind === 'setEqual') body[c.field] = c.check.write;
127+
if ('write' in c.check && c.check.write !== undefined) body[c.field] = c.check.write;
93128
}
94129

95130
const created = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', body);
@@ -127,7 +162,13 @@ describe('dogfood: field-type capability matrix round-trips over HTTP (#2004)',
127162
break;
128163
}
129164
case 'present':
130-
expect(actual ?? null, `${c.field} should be server-assigned`).not.toBeNull();
165+
expect(actual ?? null, `${c.field} should be persisted`).not.toBeNull();
166+
break;
167+
case 'masked':
168+
// secret never leaves the engine as plaintext — the write value is
169+
// encrypted into sys_secret and the read path returns the mask.
170+
expect(actual, `${c.field} should not echo plaintext`).not.toEqual(c.check.write);
171+
expect(actual).toEqual(SECRET_MASK);
131172
break;
132173
case 'computed':
133174
expect(Number(actual)).toBeCloseTo(Number(c.check.expected), 5);

0 commit comments

Comments
 (0)