Skip to content

Commit bc80c7f

Browse files
committed
test(plugin-auth): the slug guard's registering engine doubles keep ObjectQL's findOne contract
The doubles that answer the registration question are engine doubles in the engine-double gate's sense, so their findOne routes through assertEngineFindOnePredicate, and the pinned ledger records the new pin (`check-engine-double-contract --write`). Claude-Session: https://claude.ai/code/session_01WMQprn46CND82KmY8sZWBu Co-authored-by: Claude <noreply@anthropic.com>
1 parent b275f81 commit bc80c7f

2 files changed

Lines changed: 45 additions & 24 deletions

File tree

‎packages/plugins/plugin-auth/src/auth-manager.org-slug-guard-system-context.test.ts‎

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
*/
2929

3030
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
31-
import { ObjectQL } from '@objectstack/objectql';
31+
import { ObjectQL, assertEngineFindOnePredicate } from '@objectstack/objectql';
3232
import { AuthManager } from './auth-manager';
3333
import { authIdentityObjects } from './manifest';
3434

@@ -199,6 +199,26 @@ async function expectReadFaultRefusal(attempt: Promise<unknown>, object: string)
199199
/** The registration answer an ObjectQL engine gives through `getSchema`. */
200200
const schemaFor = (...registered: string[]) => vi.fn((object: string) => (registered.includes(object) ? { name: object } : undefined));
201201

202+
/**
203+
* An engine double that answers the registration question (`getSchema`) beside
204+
* its two reads. Its `findOne` keeps ObjectQL's own predicate contract
205+
* (`assertEngineFindOnePredicate`), so it is no looser than the engine it
206+
* stands in for.
207+
*/
208+
function registeringEngine(
209+
registered: string[],
210+
answers: { findOne: () => Promise<unknown>; find: () => Promise<unknown> },
211+
) {
212+
return {
213+
getSchema: schemaFor(...registered),
214+
findOne: vi.fn(async (object: string, q?: Query) => {
215+
assertEngineFindOnePredicate(object, q as never);
216+
return answers.findOne();
217+
}),
218+
find: vi.fn(answers.find),
219+
};
220+
}
221+
202222
describe('organization slug guard — fails closed when a read cannot answer (#21941)', () => {
203223
it('an organization read that throws is refused (503), and the environment read is never made', async () => {
204224
// SUPERSEDED PIN, quoted — what the guard answered before:
@@ -217,13 +237,12 @@ describe('organization slug guard — fails closed when a read cannot answer (#2
217237

218238
it('an environment read that throws is refused (503) — on an engine that registers the object', async () => {
219239
// SUPERSEDED PIN, quoted — `find` threw and the hook resolved `undefined`.
220-
const engine = {
221-
getSchema: schemaFor('sys_organization', 'sys_environment'),
222-
findOne: vi.fn(async () => ORG),
223-
find: vi.fn(async () => {
240+
const engine = registeringEngine(['sys_organization', 'sys_environment'], {
241+
findOne: async () => ORG,
242+
find: async () => {
224243
throw new Error('connection reset');
225-
}),
226-
};
244+
},
245+
});
227246
const update = await slugGuard(engine);
228247
await expectReadFaultRefusal(update('acme-new'), 'sys_environment');
229248
expect(engine.find).toHaveBeenCalledTimes(1);
@@ -241,30 +260,27 @@ describe('organization slug guard — fails closed when a read cannot answer (#2
241260
});
242261

243262
it('a healthy read on a registered object still refuses the change while an active environment references the org, and allows it otherwise', async () => {
244-
const refusing = {
245-
getSchema: schemaFor('sys_organization', 'sys_environment'),
246-
findOne: vi.fn(async () => ORG),
247-
find: vi.fn(async () => ENVS),
248-
};
263+
const refusing = registeringEngine(['sys_organization', 'sys_environment'], {
264+
findOne: async () => ORG,
265+
find: async () => ENVS,
266+
});
249267
await expectSlugRefusal((await slugGuard(refusing))('acme-new'));
250-
const allowing = {
251-
getSchema: schemaFor('sys_organization', 'sys_environment'),
252-
findOne: vi.fn(async () => ORG),
253-
find: vi.fn(async () => [{ id: 'e2', status: 'archived' }]),
254-
};
268+
const allowing = registeringEngine(['sys_organization', 'sys_environment'], {
269+
findOne: async () => ORG,
270+
find: async () => [{ id: 'e2', status: 'archived' }],
271+
});
255272
await expect((await slugGuard(allowing))('acme-new')).resolves.toBeUndefined();
256273
});
257274

258275
it('an engine that does not register `sys_environment` declares the guard inapplicable — nothing is read', async () => {
259-
const engine = {
260-
getSchema: schemaFor('sys_organization'),
261-
findOne: vi.fn(async () => {
276+
const engine = registeringEngine(['sys_organization'], {
277+
findOne: async () => {
262278
throw new Error('must not be read');
263-
}),
264-
find: vi.fn(async () => {
279+
},
280+
find: async () => {
265281
throw new Error('must not be read');
266-
}),
267-
};
282+
},
283+
});
268284
const update = await slugGuard(engine);
269285
await expect(update('acme-new')).resolves.toBeUndefined();
270286
expect(engine.getSchema).toHaveBeenCalledWith('sys_environment');

‎scripts/engine-double-contract.pinned.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,6 +2716,11 @@
27162716
"verb": "findOne",
27172717
"pinned": 1
27182718
},
2719+
{
2720+
"file": "packages/plugins/plugin-auth/src/auth-manager.org-slug-guard-system-context.test.ts",
2721+
"verb": "findOne",
2722+
"pinned": 1
2723+
},
27192724
{
27202725
"file": "packages/plugins/plugin-auth/src/auth-manager.test.ts",
27212726
"verb": "delete",

0 commit comments

Comments
 (0)