Skip to content

Commit 9489265

Browse files
committed
test(service-datasource): start-ordering pins read objects from the engine registry; door pin asserts the verdict
After merging main, the federation service reads objects from the engine registry and datasource definitions from the metadata service resolved at use. The start-ordering unit file's harness now serves its objects from an objectql registry fake, its re-ask case is told apart by the datasource definition, and its no-metadata case has no registry either. The door pin now asserts the runtime-saved object's real verdict. Claude-Session: https://claude.ai/code/session_011K3zqE8Pv1Evw5hc8tZCnN Co-authored-by: Claude <noreply@anthropic.com>
1 parent 80dfcc3 commit 9489265

2 files changed

Lines changed: 71 additions & 28 deletions

File tree

‎packages/qa/dogfood/test/external-validate-sees-runtime-save.dogfood.test.ts‎

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@
1515
// only, and listed the saved one after a restart. The service now reads the
1616
// engine registry, resolved when validation runs.
1717
//
18-
// ## What this file pins, and what it leaves to the seam pins
18+
// ## What this file pins
1919
//
20-
// The listing, through the doors an operator uses: the code-defined objects,
21-
// an object saved through `PUT /meta/object/:name`, and an object imported
22-
// through `…/external/tables/:remote/import`. Under this harness the
23-
// federation service is mounted as an extra plugin and finds no `metadata`
24-
// service at its `init()`, so it holds no datasource definition and every row
25-
// it answers is `ok` with no comparison made. The VERDICT on a runtime-saved
26-
// object is therefore pinned beside the plugin
27-
// (`packages/services/service-datasource/src/__tests__/external-validate-reads-live-registry.test.ts`),
28-
// and was measured on `objectstack dev`, where the definition is read.
20+
// Through the doors an operator uses: the code-defined objects are listed;
21+
// an object saved through `PUT /meta/object/:name` is listed with a real
22+
// verdict (it declares a column the remote lacks, and exactly that column is
23+
// reported); an object imported through `…/external/tables/:remote/import` is
24+
// listed beside them. The verify harness composes no metadata plugin, so its
25+
// `metadata` service is the kernel's fallback registered after the
26+
// federation service's `init()`, as on `objectstack start`; the service reads
27+
// it when validation runs ([#21876]), so each row here is a real comparison.
28+
// The seam pins sit beside the plugin
29+
// (`packages/services/service-datasource/src/__tests__/external-validate-reads-live-registry.test.ts`).
2930
//
3031
// The working directory is a temporary one because the showcase's external
3132
// datasource and its fixture both name a cwd-relative SQLite file: this
@@ -45,16 +46,30 @@ const DATASOURCE = 'showcase_external';
4546
const CODE_DEFINED = ['showcase_ext_customer', 'showcase_ext_order'];
4647
/** Saved at runtime through the metadata door, bound to the remote `customers` table. */
4748
const SAVED = 'dogfood_ext_cust_21842';
49+
/** A column the saved object declares and the remote `customers` table does not have. */
50+
const MISSING = 'loyalty_tier';
4851
/** Imported at runtime from the remote `orders` table. */
4952
const IMPORTED = 'dogfood_ext_ord_21842';
5053

5154
const validatePath = `/datasources/${DATASOURCE}/external/validate`;
5255

56+
interface Row {
57+
object: string;
58+
ok: boolean;
59+
diffs: Array<{ kind: string; column?: string; severity: string }>;
60+
}
61+
62+
function rowsOf(json: unknown): Row[] {
63+
const body = json as { data?: { results?: Row[] } } | undefined;
64+
return body?.data?.results ?? [];
65+
}
66+
5367
function listedObjects(json: unknown): string[] {
54-
const body = json as { data?: { results?: Array<{ object?: unknown }> } } | undefined;
55-
return (body?.data?.results ?? []).map((r) => String(r.object)).sort();
68+
return rowsOf(json).map((r) => String(r.object)).sort();
5669
}
5770

71+
const rowOf = (json: unknown, object: string) => rowsOf(json).find((r) => r.object === object);
72+
5873
describe('external validate lists a federated object saved at runtime, with no restart (showcase)', () => {
5974
let stack: VerifyStack;
6075
let token: string;
@@ -94,20 +109,30 @@ describe('external validate lists a federated object saved at runtime, with no r
94109
expect(listedObjects(validated.json)).toEqual(CODE_DEFINED);
95110
});
96111

97-
it('lists an object saved through PUT /meta/object/:name, and still the code-defined ones', async () => {
112+
it('lists an object saved through PUT /meta/object/:name with a real verdict, and still the code-defined ones', async () => {
98113
const saved = await call('PUT', `/meta/object/${SAVED}`, {
99114
name: SAVED,
100115
label: 'Dogfood External Customer',
101116
sharingModel: 'public_read_write',
102117
datasource: DATASOURCE,
103118
external: { remoteName: 'customers' },
104-
fields: { name: { type: 'text', label: 'Name' }, email: { type: 'text', label: 'Email' } },
119+
fields: {
120+
name: { type: 'text', label: 'Name' },
121+
email: { type: 'text', label: 'Email' },
122+
[MISSING]: { type: 'text', label: 'Loyalty Tier' },
123+
},
105124
});
106125
expect(saved.status, JSON.stringify(saved.json)).toBe(200);
107126

108127
const validated = await call('POST', validatePath);
109128
expect(validated.status, JSON.stringify(validated.json)).toBe(200);
110129
expect(listedObjects(validated.json)).toEqual([...CODE_DEFINED, SAVED].sort());
130+
// Compared, not waved through: exactly the declared column the remote lacks.
131+
expect(rowOf(validated.json, SAVED)).toMatchObject({
132+
ok: false,
133+
diffs: [{ kind: 'missing_column', remoteName: 'customers', column: MISSING, severity: 'error' }],
134+
});
135+
for (const name of CODE_DEFINED) expect(rowOf(validated.json, name)?.ok, name).toBe(true);
111136
});
112137

113138
it('lists an object imported from a remote table, beside the saved and code-defined ones', async () => {
@@ -117,5 +142,9 @@ describe('external validate lists a federated object saved at runtime, with no r
117142
const validated = await call('POST', validatePath);
118143
expect(validated.status, JSON.stringify(validated.json)).toBe(200);
119144
expect(listedObjects(validated.json)).toEqual([...CODE_DEFINED, IMPORTED, SAVED].sort());
145+
expect(rowOf(validated.json, IMPORTED), JSON.stringify(rowOf(validated.json, IMPORTED))).toMatchObject({
146+
ok: true,
147+
diffs: [],
148+
});
120149
});
121150
});

‎packages/services/service-datasource/src/__tests__/external-metadata-read-at-use.test.ts‎

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,24 @@ interface Harness {
136136
services: Map<string, unknown>;
137137
}
138138

139-
/** A kernel context whose `getService` throws on an unregistered name, as the kernel's does. */
140-
function harness(): Harness {
139+
/**
140+
* A kernel context whose `getService` throws on an unregistered name, as the kernel's does.
141+
*
142+
* [#21842] The federated objects live in the engine's object registry on the
143+
* `objectql` service, which is where the federation service reads objects
144+
* (the registry a runtime save writes through to). The `metadata` fake still
145+
* holds its own copy, as the kernel's fallback does after the startup bridge;
146+
* the readers this file pins are the ones that stay on the metadata service.
147+
*/
148+
function harness(opts: { registry?: boolean } = {}): Harness {
141149
const services = new Map<string, unknown>();
142150
services.set('data', { introspectDatasource: vi.fn(async () => remoteSchema()) });
151+
if (opts.registry !== false) {
152+
const objects = new Map<string, unknown>([[CUSTOMER.name, CUSTOMER], [ORDER.name, ORDER]]);
153+
services.set('objectql', {
154+
registry: { getObject: (n: string) => objects.get(n), getAllObjects: () => [...objects.values()] },
155+
});
156+
}
143157
const ctx = {
144158
getService: (name: string) => {
145159
if (!services.has(name)) throw new Error(`Service '${name}' not found`);
@@ -257,18 +271,16 @@ describe('the federation service reads a metadata service registered after init
257271

258272
it('asks for the service again at each use, never remembering the first answer', async () => {
259273
const { h, service } = await startOrdering();
260-
expect(objectsOf(await service.validateAll())).toEqual(['wh_customer', 'wh_order']);
261-
262-
const replacement = metadataFake();
263-
await replacement.register('object', 'wh_extra', {
264-
name: 'wh_extra',
265-
datasource: 'warehouse',
266-
external: { remoteName: 'orders' },
267-
fields: {},
268-
});
269-
h.services.set('metadata', replacement.service);
274+
const customerDiffs = async () =>
275+
(await service.validateAll()).results.find((r) => r.object === 'wh_customer')?.diffs;
276+
expect(await customerDiffs()).toEqual([expect.objectContaining({ kind: 'missing_column', column: 'email' })]);
277+
278+
// [#21842] Objects come from the engine registry, so the replacement is
279+
// told apart by the datasource definition it answers: a `managed`
280+
// datasource is not compared against its remote at all.
281+
h.services.set('metadata', metadataFake({ name: 'warehouse', schemaMode: 'managed' }).service);
270282

271-
expect(objectsOf(await service.validateAll())).toEqual(['wh_customer', 'wh_extra', 'wh_order']);
283+
expect(await customerDiffs()).toEqual([]);
272284
});
273285
});
274286

@@ -292,7 +304,9 @@ describe('control: a metadata service registered before init (the dev ordering)
292304

293305
describe('with no metadata service at all, every reader keeps its fallback', () => {
294306
it('validate and the sweep answer an empty report, the draft a bare name, the catalog an unpersisted snapshot', async () => {
295-
const service = await federation(harness());
307+
// [#21842] No engine registry either: that is where validate's objects
308+
// come from, so with neither service the report has nothing to list.
309+
const service = await federation(harness({ registry: false }));
296310

297311
expect(await service.validateAll()).toEqual({ ok: true, results: [] });
298312
expect((await service.generateObjectDraft('warehouse', 'customers')).name).toBe('customers');

0 commit comments

Comments
 (0)