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
24 changes: 24 additions & 0 deletions .changeset/20513-metadata-core-case-labels-state-the-case.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@objectstack/metadata-core': patch
---

The shared engine case tables and the published contract suites in metadata-core no longer cite tracker numbers in their case labels; each label states its case in words

Clause-②: no

Several labels these tables and suites ship ended with an issue-tracker number where the case belonged. A
test driven from them printed that number as part of its name, and a failing assertion quoted it as the
reason. The number goes; where the label did not already say what the case is, it now does.

- `ENGINE_DELETE_DISPATCH_CASES`, `ENGINE_UPDATE_DISPATCH_CASES` and `ENGINE_FINDONE_PREDICATE_CASES`:
the `what` labels of 22 rows. Among them, the compare-and-set rows now say the by-id path would drop the
CAS guard; the payload-id rows say which declared `where.id` would be silently dropped; and the falsy
`where.id` boundary says it is a scalar, so neither the different-row refusal nor the non-scalar refusal
applies.
- `@objectstack/metadata-core/testing`: the repository contract suite's `serialized-form identity` group
title, and two `why` texts of `OBJECT_SCHEMA_MASK_CASES` (the empty-readable-set refusal, and the
write-capable exemption, which now names the schema write gate, `manage_metadata`).

Text only: no case is added, removed or re-ordered, and no `options`, `data`, `expect`, `expectId`, `id`,
`readable` or `context` value moves. A suite that selects or skips these cases by their label text (a
`-t` filter, a skip list) needs the new spelling.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ describe('[#5517] the mongod opt-in gate', () => {

expect(mongod).toBeUndefined();
expect(warnings).toHaveLength(1);
expect(warnings[0]).toContain('#5517');
expect(warnings[0]).toContain(
'retired from default test runs because concurrent downloads made green runs exit 1',
);
expect(warnings[0]).toContain(MONGOD_TESTS_ENV);
expect(warnings[0]).toContain('SKIP gate probe');
// The property that makes the skip cost nothing: the module that would
Expand Down Expand Up @@ -202,7 +204,7 @@ describe('[#5517] the abandoned-download rejection guard', () => {
listeners[0](renameEnoent());
expect(reraised).toEqual([]);
expect(warned).toHaveLength(1);
expect(warned[0]).toContain('#5517');
expect(warned[0]).toContain('download abandoned by a concurrent-download race');

const real = new Error('a genuine unhandled rejection');
listeners[0](real);
Expand Down
10 changes: 5 additions & 5 deletions packages/drivers/driver-mongodb/src/test-mongod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,16 @@ export function printMongodNotice(
write(`${message}\n`);
}

/** The one line a gated-off suite prints. Names the issue and the switch. */
/** The one line a gated-off suite prints. Names the reason and the switch. */
export function mongodSkipReason(suite: string, env: NodeJS.ProcessEnv = process.env): string {
const raw = env[MONGOD_TESTS_ENV];
const misset = raw === undefined || raw === ''
? ''
: ` (${MONGOD_TESTS_ENV} is set to "${raw}", which does NOT enable it — only "1" does)`;
return (
`[driver-mongodb] SKIP ${suite} — needs a real mongod, and mongodb-memory-server would `
+ 'download a ~123 MB binary; retired from default test runs by #5517 (concurrent downloads '
+ `made green runs exit 1). Set ${MONGOD_TESTS_ENV}=1 to run it${misset}.`
+ 'download a ~123 MB binary; retired from default test runs because concurrent downloads '
+ `made green runs exit 1. Set ${MONGOD_TESTS_ENV}=1 to run it${misset}.`
);
}

Expand Down Expand Up @@ -246,7 +246,7 @@ export function installAbandonedDownloadGuard(options: RejectionGuardOptions = {
return;
}
warn(
'[driver-mongodb] Ignoring the abandoned MongoDB binary download of #5517 '
'[driver-mongodb] Ignoring the MongoDB binary download abandoned by a concurrent-download race '
+ `(${(reason as Error).message}). Another worker won the race and renamed the archive; `
+ 'the suite that lost it has already degraded to a named skip, and this rejection must '
+ 'not fail an otherwise green run.',
Expand Down Expand Up @@ -299,7 +299,7 @@ export async function createTestMongod(suite: string): Promise<MongoMemoryServer
() => reject(new Error(
`timed out after ${ACQUIRE_TIMEOUT_MS / 1000}s waiting for the MongoDB binary `
+ '(fastdl.mongodb.org unreachable or hanging, or another worker holds the '
+ 'download — #5517)',
+ 'download)',
)),
ACQUIRE_TIMEOUT_MS,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/metadata-core/src/contract-suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function runRepositoryContractTests(
// Asserting only one face would have left the other implementation's
// divergence unpinned, which is the whole reason this lives in the
// shared contract suite rather than beside either bug.
describe('serialized-form identity (#7856)', () => {
describe('serialized-form identity', () => {
for (const shape of SERIALISATION_SHAPES) {
it(`version identifies the stored bytes — ${shape.label}`, async () => {
const repo = await factory();
Expand Down
8 changes: 4 additions & 4 deletions packages/metadata-core/src/engine-delete-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,14 @@ export const ENGINE_DELETE_DISPATCH_CASES: readonly EngineDeleteDispatchCase[] =
// there is no predicate the by-id path could drop, and LifecycleService's
// guarded reap relies on this shape for per-record cascade handling
// (`engine-data-events.test.ts` pins the event contract of the same shape).
{ what: 'scalar where.id with multi:true and NOTHING else in where — still one by-id delete (#11009)', options: { where: { id: 'rec_1' }, multi: true }, expect: 'by-id' },
{ what: 'scalar where.id with multi:true and NOTHING else in where — still one by-id delete', options: { where: { id: 'rec_1' }, multi: true }, expect: 'by-id' },
{ what: 'multi with a predicate', options: { where: { rule_id: 'r1' }, multi: true }, expect: 'multi' },
{ what: 'multi with no predicate at all', options: { multi: true }, expect: 'multi' },
{ what: 'multi alongside an $in id set', options: { where: { id: { $in: ['a', 'b'] } }, multi: true }, expect: 'multi' },
// [#11009] The compare-and-set spelling: a scalar `where.id` beside real
// predicate keys WITH a declared `multi` is a predicate call — every key
// rides the AST to `driver.deleteMany`, so the condition is honoured.
{ what: 'scalar where.id + extra predicate keys + multi:true — the predicate path honours ALL of it (#11009)', options: { where: { id: 'rec_1', status: 'stale' }, multi: true }, expect: 'multi' },
{ what: 'scalar where.id + extra predicate keys + multi:true — the predicate path honours ALL of it', options: { where: { id: 'rec_1', status: 'stale' }, multi: true }, expect: 'multi' },
// ── The FALSY scalars (objectstack#5747). `0` and `''` are scalars, so
// `scalarDeleteId` returns them — but the engine's `if (input.id)` is a
// truthiness test, so neither identifies a row. With a declared bulk
Expand Down Expand Up @@ -317,6 +317,6 @@ export const ENGINE_DELETE_DISPATCH_CASES: readonly EngineDeleteDispatchCase[] =
// update-side cases. Each used to dispatch `by-id` and silently DISCARD
// every `where` key other than `id`; now the refusal names the dropped
// keys and prescribes the predicate path (`multi: true`).
{ what: 'scalar where.id alongside other predicates, NO multi — the guard would be silently dropped (#11009)', options: { where: { id: 'rec_1', tenant: 't1' } }, expect: 'reject' },
{ what: 'scalar where.id + a CAS operator predicate, multi explicitly false (#11009)', options: { where: { id: 'rec_1', status: { $in: ['done'] } }, multi: false }, expect: 'reject' },
{ what: 'scalar where.id alongside other predicates, NO multi — the guard would be silently dropped', options: { where: { id: 'rec_1', tenant: 't1' } }, expect: 'reject' },
{ what: 'scalar where.id + a CAS operator predicate, multi explicitly false — refused, the by-id path would drop the CAS guard', options: { where: { id: 'rec_1', status: { $in: ['done'] } }, multi: false }, expect: 'reject' },
];
6 changes: 3 additions & 3 deletions packages/metadata-core/src/engine-findone-predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export const ENGINE_FINDONE_PREDICATE_CASES: readonly EngineFindOnePredicateCase
{ what: 'an operator predicate', query: { where: { id: { $in: ['a', 'b'] } } }, expect: 'selective' },
// The alias the engine folds on every entry point (#4346). Before the fold,
// `findOne({ filter })` matched the first row of the WHOLE table.
{ what: "the 'filter' alias alone — folded into 'where' before the guard (#4346)", query: { filter: { status: 'open' } }, expect: 'selective' },
{ what: "the 'filter' alias alone — folded into 'where' before the guard", query: { filter: { status: 'open' } }, expect: 'selective' },
{ what: "an explicit null 'where' beside a real 'filter' — null is a withdrawal, not a value", query: { where: null, filter: { status: 'open' } }, expect: 'selective' },
// A FilterArray that is a well-formed AST lowers to a condition.
{ what: 'a non-empty FilterArray — lowered to a condition before the guard', query: { where: ['status', '=', 'open'] }, expect: 'selective' },
Expand All @@ -274,12 +274,12 @@ export const ENGINE_FINDONE_PREDICATE_CASES: readonly EngineFindOnePredicateCase
// a running server throws on — which is the whole of #11957.
{ what: 'no query at all', query: undefined, expect: 'reject' },
{ what: 'an empty query bag', query: {}, expect: 'reject' },
{ what: "an empty 'where' object — the match-every-row shape (#3896's reading)", query: { where: {} }, expect: 'reject' },
{ what: "an empty 'where' object — read as match-every-row, so NOT a predicate", query: { where: {} }, expect: 'reject' },
{ what: "an explicitly null 'where'", query: { where: null }, expect: 'reject' },
// THE #11767 SHAPE. An empty FilterArray is truthy, so every hand-written
// `if (!query?.where)` copy accepts it; the engine's lowering deletes the key
// and the guard refuses. This one row is what the card was filed for.
{ what: "an empty FilterArray 'where: []' — truthy, and NOT a predicate (#11767)", query: { where: [] }, expect: 'reject' },
{ what: "an empty FilterArray 'where: []' — truthy, and NOT a predicate", query: { where: [] }, expect: 'reject' },
{ what: "a null 'filter' alias — a withdrawal, so nothing folds", query: { filter: null }, expect: 'reject' },
{ what: 'an empty orderBy array', query: { orderBy: [] }, expect: 'reject' },
{ what: 'a projection and a limit but nothing selective', query: { fields: ['id', 'name'], limit: 1 }, expect: 'reject' },
Expand Down
Loading
Loading