Skip to content

Commit 0cff02f

Browse files
committed
test(plugin-audit): the actor-name pins' store double holds the caller's bound
The double's find now applies the caller's limit after the filter, by presence, and reads are counted by a spy outside it, so check:objectql-double-limit grades it conforming instead of unjudged. Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent d2152c4 commit 0cff02f

1 file changed

Lines changed: 27 additions & 22 deletions

File tree

‎packages/plugins/plugin-audit/src/activity-actor-name.test.ts‎

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* row of a predicate write, and this is its hot path.
2525
*/
2626

27-
import { describe, it, expect } from 'vitest';
27+
import { describe, it, expect, vi } from 'vitest';
2828
import { ObjectQL } from '@objectstack/objectql';
2929
import { installAuditWriters } from './audit-writers.js';
3030

@@ -73,10 +73,12 @@ const bizQuiet = {
7373
fields: { id: f('id', 'text', { primaryKey: true }), title: f('title', 'text') },
7474
};
7575

76-
/** Copy-returning, read-counting store — the shape `audit-lookup-summary.test.ts` measures with. */
76+
/**
77+
* A copy-returning store that holds the caller's bound. Reads are counted by a
78+
* spy on `find`, outside the double, so the double itself stays a plain query.
79+
*/
7780
function makeCountingDriver() {
7881
const stores = new Map<string, Map<string, Record<string, unknown>>>();
79-
const reads: Record<string, number> = {};
8082
const storeFor = (o: string) => {
8183
let s = stores.get(o);
8284
if (!s) { s = new Map(); stores.set(o, s); }
@@ -101,18 +103,18 @@ function makeCountingDriver() {
101103
}
102104
return true;
103105
};
106+
const select = (object: string, where: any, limit?: number) => {
107+
const rows = Array.from(storeFor(object).values()).filter((r) => matches(r, where));
108+
// The caller's bound, after the filter, by presence.
109+
const page = typeof limit === 'number' ? rows.slice(0, limit) : rows;
110+
return page.map(copy);
111+
};
104112
const driver: any = {
105113
name: 'memory', version: '0.0.0', supports: {} as any,
106114
async connect() {}, async disconnect() {}, async checkHealth() { return true; },
107115
async execute() { return null; },
108-
async find(object: string, ast: any) {
109-
reads[object] = (reads[object] ?? 0) + 1;
110-
return Array.from(storeFor(object).values()).filter((r) => matches(r, ast?.where)).map(copy);
111-
},
112-
async findOne(object: string, ast: any) {
113-
for (const r of storeFor(object).values()) if (matches(r, ast?.where)) return copy(r);
114-
return null;
115-
},
116+
async find(object: string, ast: any) { return select(object, ast?.where, ast?.limit); },
117+
async findOne(object: string, ast: any) { return select(object, ast?.where, 1)[0] ?? null; },
116118
async create(object: string, data: Record<string, unknown>) {
117119
nextId += 1;
118120
const id = (data.id as string) ?? `r_${nextId}`;
@@ -153,7 +155,10 @@ function makeCountingDriver() {
153155
async beginTransaction() { return { commit: async () => {}, rollback: async () => {} }; },
154156
async commit() {}, async rollback() {},
155157
};
156-
return { driver, reads, storeFor };
158+
const findSpy = vi.spyOn(driver, 'find');
159+
/** How many `find` calls have read `object` so far. */
160+
const readsOf = (object: string) => findSpy.mock.calls.filter(([o]) => o === object).length;
161+
return { driver, readsOf, storeFor };
157162
}
158163

159164
const OWNER_PACKAGE = 'com.objectstack.test.activity-actor-name';
@@ -229,8 +234,8 @@ describe('[#22510] sys_activity.actor_name — the acting user\'s name, written
229234
});
230235

231236
it('a write with no user leaves actor_name empty (ADR-0118 D1: the system actor is null) and reads no user', async () => {
232-
const { engine, storeFor, reads } = await boot();
233-
const before = reads.sys_user ?? 0;
237+
const { engine, storeFor, readsOf } = await boot();
238+
const before = readsOf('sys_user');
234239

235240
await engine.insert('biz_task', { id: 't5', title: 'Nightly', status: 'open' }, { context: { isSystem: true } } as any);
236241
// A service principal on `actor` is not a user either (ADR-0118 D5: user or system).
@@ -245,36 +250,36 @@ describe('[#22510] sys_activity.actor_name — the acting user\'s name, written
245250
expect(row.actor_id).toBeNull();
246251
expect(row.actor_name ?? null).toBeNull();
247252
}
248-
expect((reads.sys_user ?? 0) - before).toBe(0);
253+
expect(readsOf('sys_user') - before).toBe(0);
249254
});
250255
});
251256

252257
describe('[#22510] the name costs one read per user, not one per row', () => {
253258
it('a predicate update over three rows reads sys_user ONCE, and a later write by the same user not at all', async () => {
254-
const { engine, storeFor, reads } = await boot();
259+
const { engine, storeFor, readsOf } = await boot();
255260
for (const id of ['p1', 'p2', 'p3']) {
256261
await engine.insert('biz_task', { id, title: id, status: 'open' });
257262
}
258-
const before = reads.sys_user ?? 0;
263+
const before = readsOf('sys_user');
259264

260265
await engine.update('biz_task', { status: 'done' }, { where: { status: 'open' }, multi: true, ...as(ADA) } as any);
261266

262267
const updated = Array.from(storeFor('sys_activity').values()).filter((r) => r.type === 'updated');
263268
expect(updated).toHaveLength(3);
264269
for (const row of updated) expect(row.actor_name).toBe('Ada Lovelace');
265-
expect((reads.sys_user ?? 0) - before).toBe(1);
270+
expect(readsOf('sys_user') - before).toBe(1);
266271

267272
await engine.update('biz_task', { title: 'renamed' }, { where: { id: 'p1' }, ...as(ADA) } as any);
268-
expect((reads.sys_user ?? 0) - before).toBe(1);
273+
expect(readsOf('sys_user') - before).toBe(1);
269274
});
270275

271276
it('an object with `enable.activities: false` writes no activity row and reads no user', async () => {
272-
const { engine, storeFor, reads } = await boot();
273-
const before = reads.sys_user ?? 0;
277+
const { engine, storeFor, readsOf } = await boot();
278+
const before = readsOf('sys_user');
274279

275280
await engine.insert('biz_quiet', { id: 'q1', title: 'Quiet' }, as(ADA) as any);
276281

277282
expect(activityFor(storeFor, 'q1')).toHaveLength(0);
278-
expect((reads.sys_user ?? 0) - before).toBe(0);
283+
expect(readsOf('sys_user') - before).toBe(0);
279284
});
280285
});

0 commit comments

Comments
 (0)