Skip to content

Commit 26c8f41

Browse files
committed
test(rest): flip #7134's stored-row BOUNDARY pin — the lookup route now answers (#7467)
PR #7468 landed the stored-row fold with a 'BOUNDARY: STILL 403' case annotated to be revisited the day publicPicker became declarable. This is the revisit: the same real saveMetaItem persists a groups-authored form whose field carries a spec-valid publicPicker, and the same real route handler answers 200 with projected data. The picker-less 403 half stays pinned in public-form-lookup-picker.test.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CnMk7vfrt2zW7xvwLS3JDf
1 parent 5d22372 commit 26c8f41

1 file changed

Lines changed: 48 additions & 35 deletions

File tree

packages/rest/src/public-form-routes.stored-row.test.ts

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
* refused the submit outright (#6920)
1717
* - `GET /forms/:slug/lookup/:field` answered 403 for every field (#3022)
1818
*
19-
* The first two clear on the stored-row path and are pinned below. ⚠️ The THIRD
20-
* does not, and not because the fold missed it: the route's `publicPicker`
21-
* opt-in is not a spec-declared key, so a form carrying one is refused 422 at
22-
* `saveMetaItem` and never becomes a row. That is measured, and pinned as a
23-
* boundary case rather than dropped — see the case for the reasoning.
19+
* The first two clear on the stored-row path and are pinned below. The THIRD
20+
* cleared later, in two steps: the fold reached its `sections` walk on this
21+
* card, and #7467 made the route's `publicPicker` opt-in spec-declarable (it
22+
* used to be refused 422 at `saveMetaItem`, so no stored row could carry one).
23+
* What was pinned here as a `BOUNDARY: STILL 403` case is now the flipped
24+
* pin below: a stored form carrying a picker gets a real lookup answer.
2425
*
2526
* ⛔ The fix is NOT `?? match.form?.groups` here. The #6926 guardrail stands: a
2627
* lenient consumer is where AI-authored metadata errors hide, and it leaves the
@@ -115,12 +116,11 @@ const DATA = { provider: 'object', object: 'lead' };
115116
/**
116117
* The section every case declares.
117118
*
118-
* ⚠️ It deliberately carries NO `publicPicker`, and cannot: measured on this
119-
* branch, `ViewMetadataSchema` refuses that key outright ("Unrecognized key(s)
120-
* on this view/page schema: `publicPicker`", ADR-0089 D3a), so a form declaring
121-
* one is a 422 at `saveMetaItem` and never becomes a stored row at all. See the
122-
* lookup-route case below for what that means for the third degradation #7134
123-
* listed.
119+
* It carries NO `publicPicker`, which — since #7467 declared that key — is a
120+
* choice rather than a necessity (when this file was written the schema
121+
* refused the key outright, so no stored row could carry one). A picker-less
122+
* field keeps the lookup route at 403, which the flipped case below contrasts
123+
* against its own picker-carrying fixture.
124124
*/
125125
const SECTION = {
126126
label: 'About you',
@@ -183,9 +183,11 @@ function routesOver(storedView: any) {
183183
return [];
184184
}),
185185
createData,
186-
// The lookup route's search, once it gets past the 403.
187-
queryData: vi.fn().mockResolvedValue({ records: [{ id: 'usr_1', name: 'Ada' }] }),
188-
findData: vi.fn().mockResolvedValue({ records: [{ id: 'usr_1', name: 'Ada' }] }),
186+
// The lookup route's search, once it gets past the 403. The route reads
187+
// `result.data` (or `result.items`) — the flipped #7467 case below
188+
// asserts the projected rows, so the stub answers in that shape.
189+
queryData: vi.fn().mockResolvedValue({ data: [{ id: 'usr_1', name: 'Ada' }] }),
190+
findData: vi.fn().mockResolvedValue({ data: [{ id: 'usr_1', name: 'Ada' }] }),
189191
};
190192
const rest = new RestServer(mockServer() as any, protocol, { api: { requireAuth: false } } as any);
191193
(rest as any).resolveExecCtx = async () => ({ userId: 'test-user' });
@@ -229,31 +231,42 @@ describe('#7134 a Studio-saved form authored with `groups` no longer degrades on
229231
expect(createData.mock.calls[0][0].data).toEqual({ company: 'Acme', owner: 'usr_1' });
230232
});
231233

232-
it('BOUNDARY: the lookup route is STILL 403 — for a different reason, which is not this card\'s', async () => {
233-
// #7134 listed `GET /forms/:slug/lookup/:field` answering 403 for every
234-
// field as the third degradation. On the stored-row path that half does
235-
// NOT clear, and the honest reason is worth pinning rather than
236-
// quietly dropping.
234+
it('FLIPPED [#7467]: the lookup route answers — a stored form carrying a publicPicker gets real data', async () => {
235+
// This case was born as `BOUNDARY: the lookup route is STILL 403 — for
236+
// a different reason`: #7134 listed the lookup route's blanket 403 as
237+
// the third degradation, the fold reached its `sections` walk (the two
238+
// routes above are the same walk, and they changed), but the route's
239+
// `publicPicker` opt-in was declared in NO schema — `ViewMetadataSchema`
240+
// is strict, so a form carrying one was refused 422 by `saveMetaItem`
241+
// and could never reach a row. The original assertion said so and was
242+
// annotated to be revisited the day a spec-side home landed.
237243
//
238-
// The route's opt-in is `publicPicker` on the field declaration. That
239-
// key is not declared anywhere in `packages/spec` (grepped: zero hits),
240-
// and `ViewMetadataSchema` is strict — so a form carrying one is
241-
// refused 422 by `saveMetaItem` and can never reach a row. The lookup
242-
// route therefore answers 403 on the stored-row path both before and
243-
// after this change: before because the fold left it no `sections` to
244-
// walk, after because there is no picker to find in them.
244+
// #7467 landed it (maintainer ruling: declare — `FormFieldSchema.
245+
// publicPicker`, mirroring exactly what this route reads), so this is
246+
// the revisit: the SAME real write path now persists the picker, the
247+
// SAME real route handler gets past the 403, and the third degradation
248+
// clears end-to-end. `object` is declared on the picker because the
249+
// route's field-def fallback reads only legacy spellings — #7486.
245250
//
246-
// The fold DOES reach this route's section walk — the two routes above
247-
// are the same walk, and they changed. What is missing is a spec-side
248-
// home for `publicPicker`, which is a separate card (filed from this
249-
// one; enforced-in-rest, undeclared-in-spec — the mirror of ADR-0049's
250-
// usual direction). This assertion goes RED the day that lands, which
251-
// is when someone should revisit it.
252-
const { lookup } = routesOver(await persistedBody('lead.contact', studioForm('groups')));
251+
// The picker-less half of the old pin is not lost: a stored form whose
252+
// field declares no picker still answers 403, pinned in
253+
// `public-form-lookup-picker.test.ts` (the opt-in stays an opt-in).
254+
const withPicker = {
255+
...studioForm('groups'),
256+
config: {
257+
type: 'simple', data: DATA, sharing: SHARING,
258+
groups: [{
259+
label: 'About you',
260+
fields: ['company', { field: 'owner', publicPicker: { displayFields: ['name'], object: 'sys_user' } }],
261+
}],
262+
},
263+
};
264+
const { lookup } = routesOver(await persistedBody('lead.contact', withPicker));
253265
const res = mockRes();
254266
await lookup.handler({ params: { slug: 'contact', field: 'owner' }, query: {} } as any, res);
255-
expect(res.statusCode).toBe(403);
256-
expect(res.body.code).toBe('LOOKUP_NOT_PUBLIC');
267+
expect(res.statusCode).toBe(200);
268+
expect(res.body.data).toEqual([{ id: 'usr_1', name: 'Ada' }]);
269+
expect(res.body.displayFields).toEqual(['name']);
257270
});
258271

259272
it('the stored row itself is the canonical spelling — nothing here reads `groups`', async () => {

0 commit comments

Comments
 (0)