Skip to content

Commit ecbb6fd

Browse files
claude[bot]claude
andauthored
fix(examples): require the master on showcase field-zoo's f_master_detail (#14449)
* fix(examples): require the master on showcase field-zoo's f_master_detail `showcase_field_zoo.f_master_detail` was the only `master_detail` declaration across the loadable authored corpora that did not set `required: true` — a detail record cannot exist without its master, and `objectstack validate` was warning about exactly this one field (`relationship/master-detail-required`). Both seeded zoo specimens already supply the value, so no data changes and nothing turns red; this is example hygiene, and it zeroes the migration list a future promotion of that rule from `warning` to `error` would produce. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21 * test(dogfood): give the temporal-storage zoo fixtures a real master `showcase_field_zoo.f_master_detail` is now required, and this suite creates its zoo rows through the real REST write path supplying only `name` plus a temporal field. Every insert answered 400 "Master-Detail → Project is required", failing at `write f_time hm: expected 400 to be 201`. The suite now seeds its own master chain in `beforeAll` — `showcase_account`, then `showcase_project`, which declares a required lookup to the account, so the order is forced — and passes that id to each of the four zoo inserts. The epoch-rejection case gets it too, so the 400 it asserts is still about `f_time` rather than about a missing master. Created rather than read out of the showcase seed on purpose: this file's assertions rely on it owning every row it reads. Also corrects the comment that claimed the object "requires only `name`". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 253da34 commit ecbb6fd

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

examples/app-showcase/src/data/objects/field-zoo.object.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export const FieldZoo = ObjectSchema.create({
104104
// this is the seedable half of the `multiple: true` reference surface —
105105
// see `f_users` below for the half that a fresh boot cannot seed.
106106
f_lookups: Field.lookup('showcase_account', { label: 'Lookup → Accounts (multiple)', multiple: true }),
107-
f_master_detail: Field.masterDetail('showcase_project', { label: 'Master-Detail → Project' }),
107+
f_master_detail: Field.masterDetail('showcase_project', { label: 'Master-Detail → Project', required: true }),
108108
f_tree: { type: 'tree', label: 'Tree (self/category)', reference: 'showcase_category' },
109109

110110
// ── User (lookup specialized to sys_user) ────────────────────────────

packages/qa/dogfood/test/temporal-storage-e2e.dogfood.test.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,55 @@ const DATE_SHAPES: Array<[string, string, string]> = [
7474
describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)', () => {
7575
let stack: VerifyStack;
7676
let token: string;
77+
/** The master every zoo row below hangs off — see the note in `beforeAll`. */
78+
let masterId: string;
7779

7880
beforeAll(async () => {
7981
stack = await bootStack(showcaseStack);
8082
token = await stack.signIn();
8183

84+
// `showcase_field_zoo.f_master_detail` is a REQUIRED master_detail, so
85+
// every zoo row below needs a real `showcase_project` to hang off.
86+
//
87+
// It is CREATED here rather than resolved out of the showcase seed, to keep
88+
// the property this file's assertions rest on: it owns every row it reads,
89+
// so it cannot be perturbed by what another suite's seed data happens to
90+
// contain. `showcase_project` in turn declares a REQUIRED lookup to
91+
// `showcase_account`, so the account has to exist first — seeding them in
92+
// the wrong order is refused rather than silently writing a project that
93+
// points at nothing (#4441).
94+
const newId = async (object: string, body: Record<string, unknown>): Promise<string> => {
95+
const res = await stack.apiAs(token, 'POST', `/data/${object}`, body);
96+
expect(
97+
res.status,
98+
`seed ${object}: ${res.status} ${await res.clone().text()}`,
99+
).toBeLessThan(300);
100+
const json = (await res.json()) as { id?: string; record?: { id?: string } };
101+
const id = json.id ?? json.record?.id;
102+
expect(id, `no id returned seeding ${object}`).toBeTruthy();
103+
return id as string;
104+
};
105+
106+
const accountId = await newId('showcase_account', {
107+
name: `${P}_ref_account`,
108+
status: 'active',
109+
});
110+
masterId = await newId('showcase_project', {
111+
name: `${P}_ref_project`,
112+
// `planned` is the state machine's declared initial state — anything else
113+
// is refused with `invalid_initial_state`.
114+
status: 'planned',
115+
account: accountId,
116+
});
117+
82118
// `Field.time` fixtures — written through the REAL REST write path, which
83119
// is the half #3994 fixed. Writing them via the engine would bypass
84120
// `formatInput` and prove nothing.
85121
for (const [key, value] of TIME_SHAPES) {
86122
const res = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', {
87123
name: `${P}_time_${key}`,
88124
f_time: value,
125+
f_master_detail: masterId,
89126
});
90127
expect(res.status, `write f_time ${key}`).toBe(201);
91128
}
@@ -94,16 +131,19 @@ describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)'
94131
(await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', {
95132
name: `${P}_time_early`,
96133
f_time: '08:00:00',
134+
f_master_detail: masterId,
97135
})).status,
98136
).toBe(201);
99137

100138
// `Field.date` fixtures on the same object — field-zoo carries all three
101-
// temporal types and requires only `name`, so the fixture needs no lookup
102-
// targets and cannot be perturbed by another suite's seed data.
139+
// temporal types, so one object covers them and the rows cannot be
140+
// perturbed by another suite's seed data. Its required master is the one
141+
// seeded above; nothing else here needs a lookup target.
103142
for (const [key, written] of DATE_SHAPES) {
104143
const res = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', {
105144
name: `${P}_date_${key}`,
106145
f_date: written,
146+
f_master_detail: masterId,
107147
});
108148
expect(res.status, `write f_date ${key}`).toBe(201);
109149
}
@@ -198,6 +238,9 @@ describe('dogfood: temporal storage is one shape end-to-end (#3912/#3994/#4033)'
198238
const res = await stack.apiAs(token, 'POST', '/data/showcase_field_zoo', {
199239
name: `${P}_time_epoch_rejected`,
200240
f_time: Date.UTC(2026, 0, 15, 14, 30, 0, 500),
241+
// A valid master, so the 400 asserted below is about `f_time` and not
242+
// about the required master_detail.
243+
f_master_detail: masterId,
201244
});
202245
expect(res.status).toBe(400);
203246
// Asserted via the standard envelope code + the offending field, rather

0 commit comments

Comments
 (0)