Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const WELL_FORMED = {

const PRESCRIPTION = /`RestApiEndpoint\.handlerStatus`.*was removed.*17/s;

describe('[#13823] RestApiEndpoint.handlerStatus retirement', () => {
describe('RestApiEndpoint.handlerStatus retirement', () => {
// All three former values, INCLUDING the documented default: the old
// docblock's `@default 'implemented'` was prose only — the key never carried
// a Zod `.default()`, so no built artifact materialised it and there is no
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('[#13823] RestApiEndpoint.handlerStatus retirement', () => {
});
});

describe('[#13823] api/HandlerStatus + api/RouteCoverage{Entry,Report} def retirement', () => {
describe('api/HandlerStatus + api/RouteCoverage{Entry,Report} def retirement', () => {
/** The 6 names the three retired defs exported (3 schema consts + 3 types). */
const RETIRED_NAMES = [
'HandlerStatusSchema',
Expand All @@ -176,7 +176,7 @@ describe('[#13823] api/HandlerStatus + api/RouteCoverage{Entry,Report} def retir

// ── ABSENCE (every entry, not just ./api) ─────────────────────────────
for (const name of RETIRED_NAMES) {
expect(holdersOf(name), `${name} must have zero holders after #13823`).toEqual([]);
expect(holdersOf(name), `${name} must have zero holders after its retirement`).toEqual([]);
}

// ── SURVIVAL ──────────────────────────────────────────────────────────
Expand Down Expand Up @@ -205,7 +205,7 @@ describe('[#13823] api/HandlerStatus + api/RouteCoverage{Entry,Report} def retir
});
});

describe('[#13823] ADR-0087 registration', () => {
describe('handlerStatus retirement — ADR-0087 registration', () => {
it('declares the tombstoned key and the three removed defs under major 18, with the D3 entry', () => {
expect(RETIRED_KEYS_BY_MAJOR[18]).toContain('api/RestApiEndpoint:handlerStatus');
for (const def of ['api/HandlerStatus', 'api/RouteCoverageEntry', 'api/RouteCoverageReport']) {
Expand Down
4 changes: 2 additions & 2 deletions packages/spec/src/api/plugin-rest-api.schema-refs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function resolvesToZodSchema(name: string): boolean {
return !!candidate && typeof (candidate as { safeParse?: unknown }).safeParse === 'function';
}

describe('#3899 — catalog schema references resolve and sit on the right methods', () => {
describe('catalog schema references resolve and sit on the right methods', () => {
it('every requestSchema names a Zod schema exported from @objectstack/spec/api', () => {
for (const ep of allEndpoints()) {
if (!ep.requestSchema) continue;
Expand Down Expand Up @@ -87,7 +87,7 @@ describe('#3899 — catalog schema references resolve and sit on the right metho
}
});

it('at least the routes wired in #3899 still declare their request schemas', () => {
it('at least the routes wired to the request-schema gate still declare their request schemas', () => {
// Anti-erosion floor: the gate above is vacuous if declarations are simply
// deleted. These five are validated at their mounted routes.
const declared = new Map(
Expand Down
4 changes: 2 additions & 2 deletions packages/spec/src/api/plugin-rest-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ describe('plugin-rest-api.zod', () => {
expect(paths).not.toContain('/preferences');
});

it('declares no AI routes — this table cannot vouch for a Cloud/EE surface (#3718)', () => {
it('declares no AI routes — this table cannot vouch for a Cloud/EE surface', () => {
// DEFAULT_AI_ROUTES used to sit here declaring `/nlq`, `/suggest` and
// `/insights`, and this test asserted its shape — three endpoints no repo
// has ever mounted, checked for `toHaveLength(3)`. Shape is not
Expand Down Expand Up @@ -691,7 +691,7 @@ describe('plugin-rest-api.zod', () => {
// than a bare unrecognized-key error, and the value survives at the same
// magnitude. Asserting the message, not just `.toThrow()`: a bare throw stays
// green when the schema throws for some unrelated reason.
describe('RestApiEndpoint / RestApiPluginConfig durations carry their unit (#15677)', () => {
describe('RestApiEndpoint / RestApiPluginConfig durations carry their unit in the key name', () => {
const endpoint = {
method: 'GET' as const, path: '/api/v1/discovery',
handler: 'getDiscovery', category: 'discovery' as const,
Expand Down
92 changes: 46 additions & 46 deletions packages/spec/src/api/protocol.test.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/spec/src/api/registry-retirement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const RETIRED_NAMES = [
'ApiDiscoveryResponseSchema',
] as const;

describe('[#4939] ApiRegistry family retired from `@objectstack/spec/api`', () => {
describe('ApiRegistry family retired from `@objectstack/spec/api`', () => {
it('exports none of the retired names', async () => {
const api = await import('./index');

Expand Down Expand Up @@ -86,7 +86,7 @@ describe('[#4939] ApiRegistry family retired from `@objectstack/spec/api`', () =
expect(
'ConflictResolutionStrategy' in api,
'two independent ratchets pin this as a ./api export: spec/src/automation/' +
'sync-retirement.test.ts (#4738, the fourth ConflictResolution relative) and, ' +
'sync-retirement.test.ts (the fourth ConflictResolution relative) and, ' +
'cross-repo, objectui offline-nav-performance-spec-parity.test.ts',
).toBe(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('rest_api retirement — `api.documentation.enabled`, a tombstone insid
});
});

describe('rest_api retirement — `api.documentation.version` (#20294), a second tombstone inside the live block', () => {
describe('rest_api retirement — `api.documentation.version`, a second tombstone inside the live block', () => {
// The old authored spellings: a release number, a semver-looking protocol
// version, and an empty string.
for (const version of ['2.3.0', '17.4.0', '']) {
Expand Down Expand Up @@ -254,7 +254,7 @@ describe('rest_api retirement — ADR-0087 registration', () => {
expect(step.conversionIds.filter((id) => /response-format|documentation-enabled/.test(id))).toEqual([]);
});

it('[#20294] declares `documentation.version` under major 18 with its own family D3 entry, and no D2 conversion', () => {
it('declares `documentation.version` under major 18 with its own family D3 entry, and no D2 conversion', () => {
expect(RETIRED_KEYS_BY_MAJOR[18]).toContain('api/RestApiConfig:documentation.version');
const step = MIGRATIONS_BY_MAJOR[18]!;
const entry = step.semantic.find((e) => e.id === 'rest-api-documentation-version-retired');
Expand Down
38 changes: 19 additions & 19 deletions packages/spec/src/api/rest-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('RestApiConfigSchema', () => {
expect(config.enableDiscovery).toBe(false);
});

it('[#11983] enableSearch defaults to true — search is ON unless opted out', () => {
it('enableSearch defaults to true — search is ON unless opted out', () => {
// Pinning the MATERIALIZED default (not just the declaration) is what makes
// a later `.optional()` — which would hand `undefined` to the REST layer —
// fail here rather than silently change the mount decision.
Expand All @@ -88,7 +88,7 @@ describe('RestApiConfigSchema', () => {
expect(config.enableSearch).toBe(true);
});

it('[#11983] enableSearch: false is the declared deployment-wide opt-out, and it SURVIVES the parse', () => {
it('enableSearch: false is the declared deployment-wide opt-out, and it SURVIVES the parse', () => {
// Before this key had a declared seat, this exact parse was the measured
// trap: `RestApiConfigSchema` is not `.strict()`, so it STRIPPED the
// undeclared key and any consumer of the parsed output silently got search
Expand All @@ -103,7 +103,7 @@ describe('RestApiConfigSchema', () => {
expect(RestApiConfigSchema.parse({ enableSearch: true }).enableSearch).toBe(true);
});

it('[#11983] enableSearch is authorable without a cast, and is a boolean (compile-time)', () => {
it('enableSearch is authorable without a cast, and is a boolean (compile-time)', () => {
// The declaration's REASON for existing: `objectstack.config.ts` authors
// the key by name and `packages/rest`'s `normalizeConfig` reads it. Both go
// through this input type, so this is the pin that says the key no longer
Expand Down Expand Up @@ -205,7 +205,7 @@ describe('CrudEndpointsConfigSchema', () => {
expect(config.operations?.delete).toBe(false);
});

it('[#14691] REJECTS `patterns` with the retirement prescription — the key never customized a route', () => {
it('REJECTS `patterns` with the retirement prescription — the key never customized a route', () => {
expect(() => CrudEndpointsConfigSchema.parse({
patterns: {
create: { method: 'POST', path: '/objects/{object}' },
Expand All @@ -221,14 +221,14 @@ describe('CrudEndpointsConfigSchema', () => {
expect(config.dataPrefix).toBe('/objects');
});

it('[#14691] REJECTS `objectParamStyle` — both former enum values, with the prescription', () => {
it('REJECTS `objectParamStyle` — both former enum values, with the prescription', () => {
for (const objectParamStyle of ['path', 'query']) {
expect(() => CrudEndpointsConfigSchema.parse({ objectParamStyle }), objectParamStyle)
.toThrow(/`crud\.objectParamStyle` was removed.*path segment.*Delete the key/s);
}
});

it('[#14691] the tombstones reject one key each, not the config — every live key still parses', () => {
it('the tombstones reject one key each, not the config — every live key still parses', () => {
const config = CrudEndpointsConfigSchema.parse({
operations: { list: false },
dataPrefix: '/records',
Expand Down Expand Up @@ -266,7 +266,7 @@ describe('MetadataEndpointsConfigSchema', () => {
expect(config.enableCache).toBe(false);
});

it('[#14691] REJECTS `cacheTtl` — including the negative value the old contract accepted', () => {
it('REJECTS `cacheTtl` — including the negative value the old contract accepted', () => {
for (const cacheTtl of [7200, 0, -1]) {
expect(() => MetadataEndpointsConfigSchema.parse({ cacheTtl }), String(cacheTtl))
.toThrow(/`metadata\.cacheTtl` was removed.*takes no TTL.*Delete the key/s);
Expand All @@ -290,7 +290,7 @@ describe('MetadataEndpointsConfigSchema', () => {
expect(config.endpoints?.maintenance).toBe(true);
});

it('[#14691] REJECTS `endpoints.schema` — it gated a route that does not exist', () => {
it('REJECTS `endpoints.schema` — it gated a route that does not exist', () => {
expect(() => MetadataEndpointsConfigSchema.parse({ endpoints: { schema: false } }))
.toThrow(/`metadata\.endpoints\.schema` was removed.*does not exist.*Delete the key/s);
});
Expand Down Expand Up @@ -386,12 +386,12 @@ describe('BatchEndpointsConfigSchema', () => {
expect(config.operations).not.toHaveProperty('upsertMany');
});

it('[#14691] REJECTS `operations.upsertMany` — it gated a route that was never built', () => {
it('REJECTS `operations.upsertMany` — it gated a route that was never built', () => {
expect(() => BatchEndpointsConfigSchema.parse({ operations: { upsertMany: false } }))
.toThrow(/`batch\.operations\.upsertMany` was removed.*never built.*Delete the key.*'upsert'/s);
});

it('[#14691] REJECTS `defaultAtomic` — atomicity is the per-request `options.atomic`', () => {
it('REJECTS `defaultAtomic` — atomicity is the per-request `options.atomic`', () => {
for (const defaultAtomic of [true, false]) {
expect(() => BatchEndpointsConfigSchema.parse({ defaultAtomic }), String(defaultAtomic))
.toThrow(/`batch\.defaultAtomic` was removed.*options\.atomic.*Delete the key/s);
Expand All @@ -400,27 +400,27 @@ describe('BatchEndpointsConfigSchema', () => {
});

describe('RouteGenerationConfigSchema', () => {
it('should accept the empty sub-object — and materialize nothing, every key being a tombstone (#14691)', () => {
it('should accept the empty sub-object — and materialize nothing, every key being a tombstone', () => {
const config = RouteGenerationConfigSchema.parse({});

expect(config).toEqual({});
});

it('[#14691] REJECTS `includeObjects` / `excludeObjects` — the object declares its own exposure', () => {
it('REJECTS `includeObjects` / `excludeObjects` — the object declares its own exposure', () => {
expect(() => RouteGenerationConfigSchema.parse({ includeObjects: ['account', 'contact'] }))
.toThrow(/`routes\.includeObjects` was removed.*Delete the key.*enable\.apiEnabled.*enable\.apiMethods/s);
expect(() => RouteGenerationConfigSchema.parse({ excludeObjects: ['system_log'] }))
.toThrow(/`routes\.excludeObjects` was removed.*Delete the key.*enable\.apiEnabled.*enable\.apiMethods/s);
});

it('[#14691] REJECTS `nameTransform` — every former enum value, `none` included', () => {
it('REJECTS `nameTransform` — every former enum value, `none` included', () => {
for (const nameTransform of ['none', 'plural', 'kebab-case', 'camelCase']) {
expect(() => RouteGenerationConfigSchema.parse({ nameTransform }), nameTransform)
.toThrow(/`routes\.nameTransform` was removed.*Delete the key.*canonical id/s);
}
});

it('[#14691] REJECTS `overrides` — the per-object record turned nothing on or off', () => {
it('REJECTS `overrides` — the per-object record turned nothing on or off', () => {
expect(() => RouteGenerationConfigSchema.parse({
overrides: {
account: { enabled: true, basePath: '/accounts' },
Expand Down Expand Up @@ -473,7 +473,7 @@ describe('RestServerConfigSchema', () => {
expect(config.routes).toEqual({});
});

it('[#14691] a retired sub-object key is refused THROUGH the whole config, with its own prescription', () => {
it('a retired sub-object key is refused THROUGH the whole config, with its own prescription', () => {
// The tombstones live on the sub-schemas; this pins that composing them
// under `RestServerConfigSchema` changes nothing — the same authored key is
// refused at the whole-config parse with the same string.
Expand Down Expand Up @@ -697,7 +697,7 @@ describe('Integration Tests', () => {
// tests were removed with the schemas (#4579, ADR-0049 enforce-or-remove).
// The retirement itself is pinned below.

describe('[#4579] `RestServerConfig.openApi31` retirement', () => {
describe('`RestServerConfig.openApi31` retirement', () => {
it('REJECTS an authored openApi31 block, with the fix in the message', () => {
// Tombstoned, not deleted: RestServerConfigSchema is not `.strict()`, so a
// plain deletion would silently strip the key — the author's webhook
Expand Down Expand Up @@ -739,7 +739,7 @@ describe('[#4579] `RestServerConfig.openApi31` retirement', () => {
// so the load-bearing pin is the compiler-API test below, with anti-vacuity
// guards (a resolution failure would otherwise make every assertion pass
// vacuously); sabotage-verified in the PR (re-adding an export turns it red).
describe('[#4579] the OpenApi31 block schemas are not exported from any entry point', () => {
describe('the OpenApi31 block schemas are not exported from any entry point', () => {
const REMOVED_NAMES = [
'OpenApi31ExtensionsSchema',
'OpenApi31Extensions',
Expand All @@ -765,7 +765,7 @@ describe('[#4579] the OpenApi31 block schemas are not exported from any entry po
expect(apiNames).toContain('RestApiConfigSchema');

for (const removed of REMOVED_NAMES) {
expect(holdersOf(removed), `no entry may export ${removed} (#4579)`).toEqual([]);
expect(holdersOf(removed), `no entry may export ${removed}`).toEqual([]);
}
});

Expand All @@ -789,7 +789,7 @@ describe('[#4579] the OpenApi31 block schemas are not exported from any entry po
// and removed; WebhookEvent(Schema) was first renamed OpenApiWebhookEvent(Schema)
// (#4572) and then removed outright with the openApi31 block (#4579).
// Pin: this module declares neither the bare names nor the renamed ones.
it('does not re-expose the bare WebhookEvent/WebhookConfig names from ./api (#4572)', async () => {
it('does not re-expose the bare WebhookEvent/WebhookConfig names from ./api', async () => {
const restServer = await import('./rest-server.zod');
expect('WebhookEventSchema' in restServer).toBe(false);
expect('WebhookConfigSchema' in restServer).toBe(false);
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/api/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ describe('Integration Tests', () => {
// than a bare unrecognized-key error, and the value survives at the same
// magnitude. Asserting the message, not just `.toThrow()`: a bare throw stays
// green when the schema throws for some unrelated reason.
describe('RouteDefinition.timeout \u2192 timeoutMs (#15677)', () => {
describe('RouteDefinition.timeout \u2192 timeoutMs', () => {
const base = { method: 'GET' as const, path: '/api/test', handler: 'test_handler' };

it('REFUSES the retired `timeout` spelling with the rename in the message', () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/spec/src/api/sortability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const OPPORTUNITY_LIKE = {
},
};

describe('#10235 resolveObjectSortability — the closed category set', () => {
describe('resolveObjectSortability — the closed category set', () => {
it('marks a formula column unsortable with the refusal-backed reason', () => {
const { fields } = resolveObjectSortability(OPPORTUNITY_LIKE);
expect(fields.expected_revenue).toEqual({
Expand Down Expand Up @@ -111,7 +111,7 @@ describe('#10235 resolveObjectSortability — the closed category set', () => {
});
});

describe('#10235 the #7865 anchor category — accepted, caveated, never refused', () => {
describe('the unprovisioned injected-anchor category — accepted, caveated, never refused', () => {
/** An ADR-0015 external object as the registry serves it: injected anchors present. */
const EXTERNAL = {
name: 'ext_customer',
Expand Down Expand Up @@ -167,7 +167,7 @@ describe('#10235 the #7865 anchor category — accepted, caveated, never refused
});
});

describe('#10235 wire validity — the projection parses under its own schema', () => {
describe('wire validity — the projection parses under its own schema', () => {
it.each([
['oracle-shaped object', OPPORTUNITY_LIKE],
['external object', {
Expand Down
4 changes: 2 additions & 2 deletions packages/spec/src/api/storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ describe('FileDownloadUrlResponseSchema', () => {
expect(resp.data.url).toBe('/api/v1/storage/_local/raw/eyJrIjoi.c2ln');
});

it('should reject the bare pre-#3689 shape', () => {
it('should reject the bare shape from before the shared success envelope', () => {
expect(() =>
FileDownloadUrlResponseSchema.parse({ url: 'https://cdn.example.com/f1.png' })
).toThrow();
Expand All @@ -655,7 +655,7 @@ describe('RawUploadResponseSchema', () => {
expect(resp.data.key).toBe('user/f1.png');
});

it('should reject the pre-#3689 `{ ok, key }` shape', () => {
it('should reject the `{ ok, key }` shape from before the shared success envelope', () => {
expect(() => RawUploadResponseSchema.parse({ ok: true, key: 'user/f1.png' })).toThrow();
});

Expand Down
6 changes: 3 additions & 3 deletions packages/spec/src/api/validate-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { BatchOptionsSchema } from './batch.zod';
import { DroppedFieldsEventSchema } from '../data/data-engine.zod';

describe('ValidateDataRequest (#6037)', () => {
describe('ValidateDataRequest — asks the write path for its verdict instead of predicting it', () => {
it('accepts a single candidate row', () => {
const r = ValidateDataRequestSchema.safeParse({ object: 'lead', data: { company: 'Acme' } });
expect(r.success).toBe(true);
Expand Down Expand Up @@ -54,7 +54,7 @@ describe('ValidateDataRequest (#6037)', () => {
});
});

describe('ValidateDataResponse (#6037)', () => {
describe('ValidateDataResponse — the verdict the write path would reach, persisting nothing', () => {
const ok = {
object: 'lead',
mode: 'insert' as const,
Expand Down Expand Up @@ -163,7 +163,7 @@ describe('ValidateDataResponse — per-row droppedFields', () => {
});
});

describe('the #4052 non-repeat', () => {
describe('the non-repeat of the retired `validateOnly` dry-run flag', () => {
it('does not reuse the retired `validateOnly` spelling', () => {
// `BatchOptions.validateOnly` is tombstoned: it promised a dry run that
// never existed. The new operation had to avoid that vocabulary so the
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/api/websocket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ describe('WebSocketConfigSchema', () => {
// than a bare unrecognized-key error, and the value survives at the same
// magnitude. Asserting the message, not just `.toThrow()`: a bare throw stays
// green when the schema throws for some unrelated reason.
describe('WebSocket durations carry their unit (#15677)', () => {
describe('WebSocket durations carry their unit in the key name', () => {
const url = 'wss://example.com/ws';

it.each([
Expand Down
Loading
Loading