Skip to content

Commit e41760d

Browse files
committed
test(plugin-security): spy on the real engine instead of a literal double; bound the bootstrap double's find
Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent af7b0a3 commit e41760d

2 files changed

Lines changed: 64 additions & 43 deletions

File tree

‎packages/plugins/plugin-security/src/bootstrap-platform-admin-refused-existence-read.test.ts‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,30 @@
1414
*
1515
* The double below has no `update` or `delete` member — the seed pass without
1616
* `resync` calls neither — so it is not an engine double those dispatch
17-
* contracts govern. Its `find` either answers from its rows or throws, which
18-
* are exactly the two inputs the pass must tell apart.
17+
* contracts govern. Its `find` answers from its rows, bounded by the caller's
18+
* `limit`; the refused read is a spy laid over it, so the two inputs the pass
19+
* must tell apart — an answer, and a throw — come from one table.
1920
*/
2021

21-
import { describe, it, expect } from 'vitest';
22+
import { describe, it, expect, afterEach, vi } from 'vitest';
2223
import { bootstrapPlatformAdmin } from './bootstrap-platform-admin.js';
2324

24-
function makeQl(opts: { refuseReads: boolean; rows?: any[] }) {
25-
const rows: any[] = (opts.rows ?? []).map((r) => ({ ...r }));
25+
afterEach(() => { vi.restoreAllMocks(); });
26+
27+
function makeQl(seedRows: any[] = []) {
28+
const rows: any[] = seedRows.map((r) => ({ ...r }));
2629
const inserted: any[] = [];
2730
return {
2831
rows,
2932
inserted,
3033
async find(object: string, q: any) {
31-
if (object === 'sys_permission_set' && opts.refuseReads) {
32-
throw new Error('fake outage: sys_permission_set read refused');
33-
}
3434
if (object !== 'sys_permission_set') return [];
3535
const where = q?.where ?? {};
36-
return rows.filter((r) => Object.entries(where).every(([k, v]) => {
36+
const matched = rows.filter((r) => Object.entries(where).every(([k, v]) => {
3737
if (k.startsWith('$')) throw new Error(`fake driver: unsupported operator ${k}`);
3838
return r[k] === v;
3939
}));
40+
return typeof q?.limit === 'number' ? matched.slice(0, q.limit) : matched;
4041
},
4142
async insert(object: string, data: any) {
4243
if (object !== 'sys_permission_set') return null;
@@ -47,6 +48,15 @@ function makeQl(opts: { refuseReads: boolean; rows?: any[] }) {
4748
};
4849
}
4950

51+
/** Every `sys_permission_set` read now throws; every other read still answers. */
52+
function refuseSetReads(ql: ReturnType<typeof makeQl>): void {
53+
const answer = ql.find.bind(ql);
54+
vi.spyOn(ql, 'find').mockImplementation(async (object: string, q: any) => {
55+
if (object === 'sys_permission_set') throw new Error('fake outage: sys_permission_set read refused');
56+
return answer(object, q);
57+
});
58+
}
59+
5060
const set = (name: string) => ({ name, label: name, objects: {}, systemPermissions: [] }) as any;
5161

5262
function capture() {
@@ -56,7 +66,8 @@ function capture() {
5666

5767
describe('#22169 — bootstrapPlatformAdmin declines the insert when its existence read was refused', () => {
5868
it('a refused read inserts NOTHING, and says so once for the whole pass', async () => {
59-
const ql = makeQl({ refuseReads: true, rows: [{ id: 'ps_existing', name: 'admin_full_access' }] });
69+
const ql = makeQl([{ id: 'ps_existing', name: 'admin_full_access' }]);
70+
refuseSetReads(ql);
6071
const { warns, logger } = capture();
6172

6273
const r = await bootstrapPlatformAdmin(ql, [set('admin_full_access'), set('member_default'), set('viewer_readonly')], { logger });
@@ -73,7 +84,7 @@ describe('#22169 — bootstrapPlatformAdmin declines the insert when its existen
7384
});
7485

7586
it('control: an EMPTY answer is still "absent" — a fresh install seeds every default set', async () => {
76-
const ql = makeQl({ refuseReads: false });
87+
const ql = makeQl();
7788
const { warns, logger } = capture();
7889

7990
const r = await bootstrapPlatformAdmin(ql, [set('admin_full_access'), set('member_default')], { logger });
@@ -84,7 +95,7 @@ describe('#22169 — bootstrapPlatformAdmin declines the insert when its existen
8495
});
8596

8697
it('control: an existing row is found and left alone — no insert, no unreadable line', async () => {
87-
const ql = makeQl({ refuseReads: false, rows: [{ id: 'ps_existing', name: 'member_default' }] });
98+
const ql = makeQl([{ id: 'ps_existing', name: 'member_default' }]);
8899
const { warns, logger } = capture();
89100

90101
await bootstrapPlatformAdmin(ql, [set('member_default')], { logger });

‎packages/plugins/plugin-security/src/permission-set-projection-heal-by-id.test.ts‎

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,15 @@
2727
* the only shape that can hold duplicates, and a table that already holds them
2828
* can never build the index again.
2929
*
30-
* The engine is OBSERVED, never replaced: every verb forwards to it, and the
31-
* dispatch-shaped verbs open with the producer's own predicates
32-
* (`check:engine-double-contract`). The protocol is a counting stub — what is
33-
* pinned about it is whether it is CALLED, which a stub answers exactly.
30+
* The engine is SPIED, never replaced: each spied verb counts and then runs
31+
* the real method, so no double stands between the heal and the engine's own
32+
* dispatch contract. The protocol is a counting stub — what is pinned about it
33+
* is whether it is CALLED, which a stub answers exactly.
3434
*/
3535

36-
import { describe, it, expect, afterEach } from 'vitest';
36+
import { describe, it, expect, afterEach, vi } from 'vitest';
3737
import { ObjectQL } from '@objectstack/objectql';
3838
import { SqlDriver } from '@objectstack/driver-sql';
39-
import { assertEngineFindOnePredicate, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
4039

4140
import { SysPermissionSet } from './objects/sys-permission-set.object.js';
4241
import {
@@ -81,6 +80,7 @@ const driftedRow = (id: string, name: string) => ({
8180

8281
const engines: ObjectQL[] = [];
8382
afterEach(async () => {
83+
vi.restoreAllMocks();
8484
while (engines.length) {
8585
try { await engines.pop()?.destroy(); } catch { /* noop */ }
8686
}
@@ -111,33 +111,43 @@ async function boot(declaredNames: string[], rows: Array<Record<string, any>>):
111111
return engine;
112112
}
113113

114-
/** The real engine with its verbs counted; `refuseNameReads` makes every by-name read of the table throw. */
114+
/**
115+
* The real engine with its verbs SPIED — every call still runs the real method,
116+
* so the engine's own dispatch contract answers it; the spies only count and,
117+
* when `refuseNameReads` is set, make a by-name read of the table throw.
118+
*
119+
* `withRegistry: false` hands the heal the same engine with `registry` hidden,
120+
* the shape of a kernel whose SchemaRegistry is not readable — the one shape
121+
* whose trust rule still reads the layered item.
122+
*/
115123
function observe(engine: any, opts: { withRegistry?: boolean } = {}) {
116-
const log = { updatedIds: [] as string[], inserts: 0, nameReads: 0 };
124+
const log = { updatedIds: [] as string[], inserts: 0 };
117125
const state = { refuseNameReads: false };
118-
const ql: any = {
119-
...(opts.withRegistry === false ? {} : { registry: engine.registry }),
120-
find: async (o: string, q?: any, opt?: any) => {
121-
if (o === 'sys_permission_set' && q?.where && typeof q.where === 'object' && 'name' in q.where) {
122-
log.nameReads += 1;
123-
if (state.refuseNameReads) throw new Error('fake outage: the by-name read was refused');
124-
}
125-
return engine.find(o, q, opt);
126-
},
127-
findOne: (o: string, q?: any, opt?: any) => {
128-
assertEngineFindOnePredicate(o, q);
129-
return engine.findOne(o, q, opt);
130-
},
131-
insert: async (o: string, d: any, opt?: any) => {
132-
if (o === 'sys_permission_set') log.inserts += 1;
133-
return engine.insert(o, d, opt);
134-
},
135-
update: async (o: string, d: any, opt?: any) => {
136-
assertEngineUpdateDispatch(d, opt);
137-
if (o === 'sys_permission_set') log.updatedIds.push(String(d?.id));
138-
return engine.update(o, d, opt);
139-
},
140-
};
126+
const realFind = engine.find.bind(engine);
127+
const realInsert = engine.insert.bind(engine);
128+
const realUpdate = engine.update.bind(engine);
129+
vi.spyOn(engine, 'find').mockImplementation(async (o: any, q?: any, opt?: any) => {
130+
const byName = o === 'sys_permission_set' && q?.where && typeof q.where === 'object' && 'name' in q.where;
131+
if (byName && state.refuseNameReads) throw new Error('fake outage: the by-name read was refused');
132+
return realFind(o, q, opt);
133+
});
134+
vi.spyOn(engine, 'insert').mockImplementation(async (o: any, d: any, opt?: any) => {
135+
if (o === 'sys_permission_set') log.inserts += 1;
136+
return realInsert(o, d, opt);
137+
});
138+
vi.spyOn(engine, 'update').mockImplementation(async (o: any, d: any, opt?: any) => {
139+
if (o === 'sys_permission_set') log.updatedIds.push(String(d?.id));
140+
return realUpdate(o, d, opt);
141+
});
142+
const ql = opts.withRegistry === false
143+
? new Proxy(engine, {
144+
get(target, prop) {
145+
if (prop === 'registry') return undefined;
146+
const value = Reflect.get(target, prop, target);
147+
return typeof value === 'function' ? value.bind(target) : value;
148+
},
149+
})
150+
: engine;
141151
return { ql, log, state };
142152
}
143153

0 commit comments

Comments
 (0)