Skip to content

Commit fbf3584

Browse files
committed
test(metadata-protocol,dogfood): enumerate every member kind of a cross-package container, and the single default
Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude <noreply@anthropic.com>
1 parent 73da927 commit fbf3584

2 files changed

Lines changed: 218 additions & 55 deletions

File tree

‎packages/metadata-protocol/src/view-container-runtime-expansion.test.ts‎

Lines changed: 166 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
*/
3030
import { describe, expect, it } from 'vitest';
3131
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch, assertEngineFindOnePredicate, isCodeArtifactBody } from '@objectstack/metadata-core';
32-
import { expandViewContainer } from '@objectstack/spec/ui';
32+
import { expandViewContainer, ViewSchema } from '@objectstack/spec/ui';
3333
import { ObjectStackProtocolImplementation } from './index.js';
3434

3535
interface Row {
@@ -398,17 +398,20 @@ describe('#13407 org-scoped and environment-scoped runtime containers are served
398398
});
399399

400400
/**
401-
* #21334 — a bare-`list` container on ANOTHER package's object must not take
402-
* that package's `<object>.default`.
401+
* #21334 — a container on ANOTHER package's object must not take any of that
402+
* package's names, nor its default.
403403
*
404-
* The spec names a bare `list` `<object>.default`. A container saved under any
405-
* other name — in another package, or in none — for an object a code package
406-
* ships used to expand there and REPLACE the packaged default on the object
407-
* door (`GET /meta/view?object=`), wearing the shadowed artifact's `_packageId`,
404+
* The spec names every expanded view `<object>.<key>` (a bare `list` takes
405+
* `<object>.default`). A container saved under any other name — in another
406+
* package, or in none — for an object a code package ships used to expand
407+
* there and REPLACE the packaged views on the object door
408+
* (`GET /meta/view?object=`), wearing the shadowed artifact's `_packageId`,
408409
* while the by-name read kept the packaged item on an environment-scoped kernel
409410
* (and served the shadow too on an unscoped one, through the registry's bare
410411
* key). ADR-0005 keys an overlay by its own name and ADR-0126 rules out a silent
411-
* override; the ruling takes the arm "expand under the container's own name".
412+
* override. Triage's ruling takes the arm "expand under the container's own
413+
* name"; the seat's answer extends it to every member of the container, and
414+
* rules that such a container never declares the object's default.
412415
*
413416
* The registry double here is the real `SchemaRegistry`'s shape where this card
414417
* turns on it: loader entries under `<package>:<name>`, a hydrated row under
@@ -419,7 +422,7 @@ describe('#13407 org-scoped and environment-scoped runtime containers are served
419422
* composition, through the REST doors, is
420423
* `view-container-cross-package-default.dogfood.test.ts`.
421424
*/
422-
describe('#21334 a bare-list container on another package\'s object never takes that package\'s <object>.default', () => {
425+
describe('#21334 a container on another package\'s object never takes that package\'s names or its default', () => {
423426
const SHOWCASE = 'com.example.showcase';
424427
const REPAIR = 'com.example.repairassets';
425428
const TASK = 'showcase_task';
@@ -428,13 +431,22 @@ describe('#21334 a bare-list container on another package\'s object never takes
428431
const data = { provider: 'object', object: TASK };
429432
const PACKAGED_COLUMNS = ['title', 'project', 'assignee', 'status', 'priority', 'due_date', 'progress']
430433
.map((field) => ({ field }));
431-
/** What the showcase ships: a `defineView` container with a bare default list and one keyed view. */
434+
/**
435+
* What the showcase ships for `showcase_task`: a `defineView` container with
436+
* one member of every kind the expander knows, so every probe below aims at
437+
* a name the package really ships.
438+
*/
432439
const packagedTaskViews = {
433440
list: { label: 'All Tasks', type: 'grid', data, columns: PACKAGED_COLUMNS },
434441
listViews: {
435442
in_progress: { label: 'In Progress', type: 'grid', data, columns: PACKAGED_COLUMNS.slice(0, 4) },
436443
},
444+
form: { label: 'Task Form', type: 'simple', sections: [{ label: 'Main', fields: ['title'] }] },
445+
formViews: {
446+
edit: { label: 'Edit Task', type: 'simple', sections: [{ label: 'Edit', fields: ['title', 'status'] }] },
447+
},
437448
};
449+
const PACKAGED = expandViewContainer(TASK, packagedTaskViews).map((vi) => ({ ...(vi as any) }));
438450
/** The card's own probe body, verbatim. */
439451
const probe = (name: string) => ({ name, object: TASK, list: { type: 'grid', columns: ['title', 'status'] } });
440452

@@ -522,29 +534,138 @@ describe('#21334 a bare-list container on another package\'s object never takes
522534
expect(v?._packageId).toBe(SHOWCASE);
523535
expect(v?.isDefault).toBe(true);
524536
};
537+
/** (a) Every name the showcase ships answers the packaged view, once, on BOTH doors — the same row. */
538+
const expectEveryPackagedNameIntact = async (protocol: Protocol, organizationId?: string) => {
539+
const served = await objectDoor(protocol, organizationId);
540+
for (const shipped of PACKAGED) {
541+
const listed = named(served, shipped.name);
542+
expect(listed, `exactly one item answers ${shipped.name} on the object door`).toHaveLength(1);
543+
expect({ label: listed[0].label, config: listed[0].config, _packageId: listed[0]._packageId })
544+
.toEqual({ label: shipped.label, config: shipped.config, _packageId: SHOWCASE });
545+
const read = await byNameDoor(protocol, shipped.name, organizationId);
546+
expect({ label: read?.label, config: read?.config, _packageId: read?._packageId })
547+
.toEqual({ label: shipped.label, config: shipped.config, _packageId: SHOWCASE });
548+
}
549+
};
550+
/** (c) The object's only defaults are the ones its owning package declares. */
551+
const expectOnlyPackagedDefaults = (served: any[]) => {
552+
for (const viewKind of ['list', 'form']) {
553+
expect(
554+
served.filter((v) => v.viewKind === viewKind && v.isDefault).map((v) => v.name),
555+
`the ${viewKind} default stays the owning package's`,
556+
).toEqual(PACKAGED.filter((v) => v.viewKind === viewKind && v.isDefault).map((v) => v.name));
557+
}
558+
};
559+
560+
/**
561+
* Every member kind the spec's expander places, derived FROM the expander:
562+
* each top-level key of the container schema is offered a single view and a
563+
* record of views, and a key that yields an expanded item is a member kind.
564+
* A single-view member is enumerated twice — bare, and naming its own key.
565+
* A kind the spec adds later shows up here, and the enumeration below fails
566+
* until it is placed.
567+
*/
568+
function memberKindsOfTheExpander(): string[] {
569+
const slots = Object.keys((ViewSchema as unknown as { shape?: Record<string, unknown> }).shape ?? {});
570+
expect(slots.length, 'the container schema\'s own keys are readable').toBeGreaterThan(0);
571+
const view = { type: 'grid', label: 'probe' };
572+
const kinds: string[] = [];
573+
for (const slot of slots) {
574+
if (expandViewContainer('o', { [slot]: { ...view } }).length > 0) {
575+
kinds.push(slot, `${slot}#named`);
576+
} else if (expandViewContainer('o', { [slot]: { k: { ...view } } }).some((vi) => vi.name === 'o.k')) {
577+
kinds.push(`${slot}.*`);
578+
}
579+
}
580+
return kinds.sort();
581+
}
582+
583+
const OWN = 'os_qa_probe';
584+
const listView = { type: 'grid', columns: ['title', 'status'] };
585+
const formView = { label: 'Probe Form', type: 'simple', sections: [{ label: 'Probe', fields: ['title'] }] };
586+
/**
587+
* One case per member kind: a container on `showcase_task` with ONLY that
588+
* member, whose key aims at a name the showcase ships, and the one name the
589+
* member must be served under instead.
590+
*/
591+
const MEMBER_CASES: Record<string, { member: Record<string, unknown>; authored: unknown; shadows: string; servedAs: string }> = {
592+
list: { member: { list: listView }, authored: listView, shadows: DEFAULT, servedAs: `${TASK}.${OWN}` },
593+
'list#named': {
594+
member: { list: { ...listView, name: 'in_progress' } }, authored: { ...listView, name: 'in_progress' },
595+
shadows: `${TASK}.in_progress`, servedAs: `${TASK}.${OWN}.in_progress`,
596+
},
597+
'listViews.*': {
598+
member: { listViews: { in_progress: listView } }, authored: listView,
599+
shadows: `${TASK}.in_progress`, servedAs: `${TASK}.${OWN}.in_progress`,
600+
},
601+
form: { member: { form: formView }, authored: formView, shadows: `${TASK}.form`, servedAs: `${TASK}.${OWN}.form` },
602+
'form#named': {
603+
member: { form: { ...formView, name: 'edit' } }, authored: { ...formView, name: 'edit' },
604+
shadows: `${TASK}.edit`, servedAs: `${TASK}.${OWN}.edit`,
605+
},
606+
'formViews.*': {
607+
member: { formViews: { edit: formView } }, authored: formView,
608+
shadows: `${TASK}.edit`, servedAs: `${TASK}.${OWN}.edit`,
609+
},
610+
};
611+
612+
it('the enumeration covers every member kind the spec\'s expander places, and nothing else', () => {
613+
expect(Object.keys(MEMBER_CASES).sort()).toEqual(memberKindsOfTheExpander());
614+
// Each probe really aims at a shipped name: without the fix it IS that name.
615+
for (const [kind, c] of Object.entries(MEMBER_CASES)) {
616+
const names = expandViewContainer(TASK, c.member).map((vi) => vi.name);
617+
expect(names, kind).toEqual([c.shadows]);
618+
expect(PACKAGED.map((v) => v.name), kind).toContain(c.shadows);
619+
}
620+
});
525621

526622
const KERNELS = [
527623
['an environment-scoped kernel (the standalone stack stamps env_local)', 'env_local'],
528624
['an unscoped kernel (write-through hydrates the registry)', undefined],
529625
] as const;
530626
const CONTAINERS = [
531-
{ arm: 'package-scoped (saved into another writable package)', name: 'os_qa_shadow_probe', packageId: REPAIR, organizationId: undefined, ownPackage: REPAIR },
532-
{ arm: 'package-less, environment-wide', name: 'os_qa_shadow_probe2', packageId: undefined, organizationId: undefined, ownPackage: undefined },
533-
{ arm: 'package-less, organization-scoped', name: 'os_qa_shadow_probe3', packageId: undefined, organizationId: ORG, ownPackage: undefined },
627+
{ arm: 'package-scoped (saved into another writable package)', packageId: REPAIR, organizationId: undefined, ownPackage: REPAIR },
628+
{ arm: 'package-less, environment-wide', packageId: undefined, organizationId: undefined, ownPackage: undefined },
629+
{ arm: 'package-less, organization-scoped', packageId: undefined, organizationId: ORG, ownPackage: undefined },
534630
] as const;
631+
const save = (protocol: Protocol, name: string, item: unknown, c: (typeof CONTAINERS)[number]) =>
632+
protocol.saveMetaItem({
633+
type: 'view', name, item,
634+
...(c.packageId ? { packageId: c.packageId } : {}),
635+
...scoped(c.organizationId),
636+
} as any);
535637

536638
for (const [kernel, environmentId] of KERNELS) {
537639
describe(`on ${kernel}`, () => {
538640
for (const c of CONTAINERS) {
539-
it(`${c.arm}: the packaged default is unchanged on BOTH doors, and they answer the same row`, async () => {
540-
const { protocol } = showcaseHarness(environmentId);
541-
expectPackagedDefault(named(await objectDoor(protocol, c.organizationId), DEFAULT)[0]);
641+
for (const [kind, m] of Object.entries(MEMBER_CASES)) {
642+
it(`${c.arm}, member ${kind}: no packaged name is replaced on either door, its own name is served with its own package, and it claims no default`, async () => {
643+
const { protocol } = showcaseHarness(environmentId);
644+
await save(protocol, OWN, { name: OWN, object: TASK, ...m.member }, c);
645+
646+
// (a)
647+
await expectEveryPackagedNameIntact(protocol, c.organizationId);
648+
// (b)
649+
const served = await objectDoor(protocol, c.organizationId);
650+
const own = served.filter((v) => String(v.name).startsWith(`${TASK}.${OWN}`));
651+
expect(own.map((v) => v.name)).toEqual([m.servedAs]);
652+
expect(own[0].object).toBe(TASK);
653+
expect(own[0]._packageId).toBe(c.ownPackage);
654+
expect(own[0]._provenance, 'never the packaged artifact\'s provenance').not.toBe('package');
655+
expect(own[0]._diagnostics?.valid, 'a qualified ViewItem name the spec accepts').toBe(true);
656+
expect(own[0].config, 'the member as authored, nothing lent left on it').toEqual(m.authored);
657+
// (c)
658+
expect(own[0].isDefault).toBeUndefined();
659+
expectOnlyPackagedDefaults(served);
660+
// The by-name door answers the container's own name with its row.
661+
const row = await byNameDoor(protocol, OWN, c.organizationId);
662+
expect(row?.object).toBe(TASK);
663+
});
664+
}
542665

543-
await protocol.saveMetaItem({
544-
type: 'view', name: c.name, item: probe(c.name),
545-
...(c.packageId ? { packageId: c.packageId } : {}),
546-
...scoped(c.organizationId),
547-
} as any);
666+
it(`${c.arm}: the card's probe — the packaged default unchanged on BOTH doors, and the object keeps ONE list default`, async () => {
667+
const { protocol } = showcaseHarness(environmentId);
668+
await save(protocol, 'os_qa_shadow_probe', probe('os_qa_shadow_probe'), c);
548669

549670
const listed = named(await objectDoor(protocol, c.organizationId), DEFAULT);
550671
expect(listed, 'exactly one item answers <object>.default on the object door').toHaveLength(1);
@@ -553,31 +674,13 @@ describe('#21334 a bare-list container on another package\'s object never takes
553674
expectPackagedDefault(read);
554675
expect({ label: read.label, config: read.config, _packageId: read._packageId })
555676
.toEqual({ label: listed[0].label, config: listed[0].config, _packageId: listed[0]._packageId });
556-
});
557-
558-
it(`${c.arm}: the container's own view is served under its own name, carrying its own package`, async () => {
559-
const { protocol } = showcaseHarness(environmentId);
560-
await protocol.saveMetaItem({
561-
type: 'view', name: c.name, item: probe(c.name),
562-
...(c.packageId ? { packageId: c.packageId } : {}),
563-
...scoped(c.organizationId),
564-
} as any);
565-
566-
const own = named(await objectDoor(protocol, c.organizationId), `${TASK}.${c.name}`);
567-
expect(own).toHaveLength(1);
568-
expect(own[0].viewKind).toBe('list');
569-
expect(own[0].config, 'the list as authored, nothing lent left on it').toEqual(probe(c.name).list);
570-
expect(own[0]._packageId).toBe(c.ownPackage);
571-
expect(own[0]._provenance, 'never the packaged artifact\'s provenance').not.toBe('package');
572-
expect(own[0]._diagnostics?.valid, 'a qualified ViewItem name the spec accepts').toBe(true);
573-
574-
// The by-name door answers the container's own name with its row.
575-
const row = await byNameDoor(protocol, c.name, c.organizationId);
576-
expect(row.list).toEqual(probe(c.name).list);
677+
const served = await objectDoor(protocol, c.organizationId);
678+
expect(served.filter((v) => v.viewKind === 'list' && v.isDefault).map((v) => v.name)).toEqual([DEFAULT]);
679+
expect(named(served, `${TASK}.os_qa_shadow_probe`)[0]?.config).toEqual(probe('os_qa_shadow_probe').list);
577680
});
578681
}
579682

580-
it('CONTROL — a container of the object\'s OWN package still expands to <object>.default', async () => {
683+
it('CONTROL — a container of the object\'s OWN package still expands to <object>.default, as its default', async () => {
581684
const { protocol, rows } = showcaseHarness(environmentId);
582685
// A row bound to the package that owns the object (an installed
583686
// package's own stored view), written straight to the store.
@@ -590,6 +693,7 @@ describe('#21334 a bare-list container on another package\'s object never takes
590693
expect(listed).toHaveLength(1);
591694
expect(listed[0].config).toEqual(probe('os_qa_same_pkg').list);
592695
expect(listed[0]._packageId).toBe(SHOWCASE);
696+
expect(listed[0].isDefault).toBe(true);
593697
expect(named(await objectDoor(protocol), `${TASK}.os_qa_same_pkg`)).toEqual([]);
594698
});
595699

@@ -604,7 +708,8 @@ describe('#21334 a bare-list container on another package\'s object never takes
604708
expect(listed).toHaveLength(1);
605709
expect(listed[0].label).toBe('Customized');
606710
expect(listed[0]._packageId).toBe(SHOWCASE);
607-
expect(named(await objectDoor(protocol), `${TASK}.${TASK}`)).toEqual([]);
711+
expect(listed[0].isDefault).toBe(true);
712+
expect((await objectDoor(protocol)).filter((v) => String(v.name).startsWith(`${TASK}.${TASK}`))).toEqual([]);
608713
});
609714

610715
it('CONTROL — the sanctioned override (a write to <object>.default by name) is served on both doors', async () => {
@@ -633,24 +738,31 @@ describe('#21334 a bare-list container on another package\'s object never takes
633738
await protocol.saveMetaItem({ type: 'view', name: 'probe_x', item: container, packageId: REPAIR } as any);
634739

635740
const served = await objectDoor(protocol);
636-
expect(named(served, `${TASK}.probe_x`)[0]?.label).toBe('Keyed');
637-
const bare = named(served, `${TASK}.probe_x_2`)[0];
638-
expect(bare?.config).toEqual(container.list);
639-
expectPackagedDefault(named(served, DEFAULT)[0]);
640-
expect(named(served, DEFAULT)).toHaveLength(1);
741+
expect(named(served, `${TASK}.probe_x.probe_x`)[0]?.label).toBe('Keyed');
742+
expect(named(served, `${TASK}.probe_x`)[0]?.config).toEqual(container.list);
743+
await expectEveryPackagedNameIntact(protocol);
744+
});
745+
746+
it('a container named after a key the owning package ships keeps its bare list off that name', async () => {
747+
const { protocol } = showcaseHarness('env_local');
748+
await protocol.saveMetaItem({ type: 'view', name: 'in_progress', item: probe('in_progress'), packageId: REPAIR } as any);
749+
750+
const served = await objectDoor(protocol);
751+
expect(named(served, `${TASK}.in_progress.in_progress`)[0]?.config).toEqual(probe('in_progress').list);
752+
expect(named(served, `${TASK}.in_progress.in_progress`)[0]?._packageId).toBe(REPAIR);
753+
await expectEveryPackagedNameIntact(protocol);
641754
});
642755

643-
it('a stored container with no name of its own does not expand its bare list under another package\'s name', async () => {
756+
it('a stored container with no name of its own expands nothing on another package\'s object', async () => {
644757
const { protocol, rows } = showcaseHarness('env_local');
645-
const nameless = { object: TASK, list: { type: 'grid', columns: ['title'] }, listViews: { mine: { label: 'Mine', type: 'grid', columns: ['title'] } } };
758+
const nameless = { object: TASK, list: { type: 'grid', columns: ['title'] }, listViews: { in_progress: { label: 'Mine', type: 'grid', columns: ['title'] } } };
646759
rows.set('nameless-row', {
647760
id: 'r_nameless', type: 'view', name: 'os_qa_nameless', organization_id: null,
648761
package_id: REPAIR, state: 'active', metadata: JSON.stringify(nameless),
649762
});
650763

651764
const served = await objectDoor(protocol);
652-
expect(named(served, DEFAULT)).toHaveLength(1);
653-
expectPackagedDefault(named(served, DEFAULT)[0]);
654-
expect(named(served, `${TASK}.mine`)[0]?._packageId).toBe(REPAIR);
765+
expect(served.filter((v) => v._packageId === REPAIR)).toEqual([]);
766+
await expectEveryPackagedNameIntact(protocol);
655767
});
656768
});

0 commit comments

Comments
 (0)