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' ;
2828import { ObjectQL } from '@objectstack/objectql' ;
2929import { 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+ */
7780function 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
159164const 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
252257describe ( '[#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