@@ -78,17 +78,23 @@ function chainable(rows: unknown[], calls: QueryCall[], query: number) {
7878 return self ;
7979}
8080
81- function repositoryWithSelects ( selectResults : unknown [ ] [ ] , insertResults : unknown [ ] [ ] = [ ] ) {
81+ function repositoryWithSelects (
82+ selectResults : unknown [ ] [ ] ,
83+ insertResults : unknown [ ] [ ] = [ ] ,
84+ deleteResults : unknown [ ] [ ] = [ ] ,
85+ ) {
8286 const calls : QueryCall [ ] = [ ] ;
8387 let query = 0 ;
8488 let insert = 0 ;
89+ let deletion = 0 ;
8590 const tx = {
8691 select : mock ( ( ) => {
8792 const current = query ++ ;
8893 return chainable ( selectResults [ current ] ?? [ ] , calls , current ) ;
8994 } ) ,
9095 insert : mock ( ( ) => chainable ( insertResults [ insert ++ ] ?? [ ] , calls , query ++ ) ) ,
9196 update : mock ( ( ) => chainable ( [ ] , calls , query ++ ) ) ,
97+ delete : mock ( ( ) => chainable ( deleteResults [ deletion ++ ] ?? [ ] , calls , query ++ ) ) ,
9298 } ;
9399 const db = {
94100 transaction : mock ( async ( handler : ( executor : typeof tx ) => Promise < unknown > ) => handler ( tx ) ) ,
@@ -98,6 +104,7 @@ function repositoryWithSelects(selectResults: unknown[][], insertResults: unknow
98104 repository : new OperatorRepository ( db as never , audit as unknown as AuditLogService ) ,
99105 tx,
100106 calls,
107+ audit,
101108 } ;
102109}
103110
@@ -151,6 +158,61 @@ function createActionInput(argumentsValue: Record<string, unknown>) {
151158 } ;
152159}
153160
161+ describe ( 'OperatorRepository.deleteSession' , ( ) => {
162+ it ( 'locks and deletes an owned session with no active turn' , async ( ) => {
163+ const { repository, tx, calls, audit } = repositoryWithSelects (
164+ [ [ session ] , [ ] ] ,
165+ [ ] ,
166+ [ [ session ] ] ,
167+ ) ;
168+
169+ await expect (
170+ repository . deleteSession ( {
171+ sessionId : SESSION_ID ,
172+ owner : { organizationId : auth . organizationId ! , userId : auth . userId ! } ,
173+ auth,
174+ } ) ,
175+ ) . resolves . toEqual ( session ) ;
176+
177+ expect ( calls . find ( ( call ) => call . query === 0 && call . method === 'for' ) ?. args ) . toEqual ( [
178+ 'update' ,
179+ ] ) ;
180+ expect ( tx . delete ) . toHaveBeenCalledTimes ( 1 ) ;
181+ expect ( audit . recordDurableWithExecutor ) . toHaveBeenCalledWith ( tx , auth , {
182+ action : 'operator.session.delete' ,
183+ resourceType : 'operator_session' ,
184+ resourceId : SESSION_ID ,
185+ resourceName : 'Session' ,
186+ } ) ;
187+ } ) ;
188+
189+ it ( 'rejects deletion while a durable turn is active' , async ( ) => {
190+ const { repository, tx } = repositoryWithSelects ( [ [ session ] , [ { id : ACTIVE_TURN_ID } ] ] ) ;
191+
192+ await expect (
193+ repository . deleteSession ( {
194+ sessionId : SESSION_ID ,
195+ owner : { organizationId : auth . organizationId ! , userId : auth . userId ! } ,
196+ auth,
197+ } ) ,
198+ ) . rejects . toThrow ( 'Stop or wait for the active Operator turn before deleting' ) ;
199+ expect ( tx . delete ) . not . toHaveBeenCalled ( ) ;
200+ } ) ;
201+
202+ it ( 'does not delete a session outside the requested owner scope' , async ( ) => {
203+ const { repository, tx } = repositoryWithSelects ( [ [ ] ] ) ;
204+
205+ await expect (
206+ repository . deleteSession ( {
207+ sessionId : SESSION_ID ,
208+ owner : { organizationId : 'another-org' , userId : 'another-user' } ,
209+ auth,
210+ } ) ,
211+ ) . resolves . toBeUndefined ( ) ;
212+ expect ( tx . delete ) . not . toHaveBeenCalled ( ) ;
213+ } ) ;
214+ } ) ;
215+
154216describe ( 'OperatorRepository.createTurn' , ( ) => {
155217 it ( 'locks the session before rejecting a distinct active turn' , async ( ) => {
156218 const { repository, tx, calls } = repositoryWithSelects ( [
0 commit comments