@@ -11,19 +11,33 @@ vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock)
1111
1212const { values, lists, seen, persist, redis } = vi . hoisted ( ( ) => {
1313 const values = new Map < string , string > ( )
14+ const expiresAt = new Map < string , number > ( )
1415 const lists = new Map < string , string [ ] > ( )
1516 const seen = new Map < string , Set < string > > ( )
17+ const live = ( key : string ) => {
18+ if ( ( expiresAt . get ( key ) ?? Number . POSITIVE_INFINITY ) <= Date . now ( ) ) {
19+ values . delete ( key )
20+ expiresAt . delete ( key )
21+ }
22+ return values . has ( key )
23+ }
1624 return {
1725 values,
1826 lists,
1927 seen,
2028 persist : vi . fn ( async ( ) => { } ) ,
2129 redis : {
22- set : vi . fn ( async ( key : string , value : string ) => {
30+ set : vi . fn ( async ( key : string , value : string , _mode ?: 'EX' , ttlSeconds ?: number ) => {
2331 values . set ( key , value )
32+ if ( ttlSeconds ) expiresAt . set ( key , Date . now ( ) + ttlSeconds * 1000 )
33+ } ) ,
34+ expire : vi . fn ( async ( key : string , ttlSeconds : number ) => {
35+ if ( ! live ( key ) ) return 0
36+ expiresAt . set ( key , Date . now ( ) + ttlSeconds * 1000 )
37+ return 1
2438 } ) ,
25- get : vi . fn ( async ( key : string ) => values . get ( key ) ?? null ) ,
26- exists : vi . fn ( async ( key : string ) => ( values . has ( key ) ? 1 : 0 ) ) ,
39+ get : vi . fn ( async ( key : string ) => ( live ( key ) ? ( values . get ( key ) ?? null ) : null ) ) ,
40+ exists : vi . fn ( async ( key : string ) => ( live ( key ) ? 1 : 0 ) ) ,
2741 del : vi . fn ( async ( key : string ) => {
2842 values . delete ( key )
2943 lists . delete ( key )
@@ -183,6 +197,33 @@ describe('sandbox resource ownership and publication', () => {
183197 } )
184198 } )
185199
200+ it ( 'keeps the callback scope live for a call that outlasts its expiry, and revokes it after' , async ( ) => {
201+ vi . useFakeTimers ( )
202+ try {
203+ const finish = Promise . withResolvers < void > ( )
204+ let token = ''
205+ const call = withSandboxResourceScope (
206+ identity ,
207+ new AbortController ( ) . signal ,
208+ undefined ,
209+ async ( ) => {
210+ token =
211+ ( await sandboxResourceEndpoint ( 'https://sim.test' , args , 'secret' ) ) . split ( '/' ) . at ( - 1 ) ??
212+ ''
213+ await finish . promise
214+ }
215+ )
216+ await vi . advanceTimersByTimeAsync ( 15 * 60_000 )
217+ expect ( await readSandboxResourceScope ( token , 'secret' ) ) . not . toBeNull ( )
218+ finish . resolve ( )
219+ await vi . advanceTimersByTimeAsync ( 1_000 )
220+ await call
221+ expect ( await readSandboxResourceScope ( token , 'secret' ) ) . toBeNull ( )
222+ } finally {
223+ vi . useRealTimers ( )
224+ }
225+ } )
226+
186227 it ( 'revokes on cancellation and never emits an abandoned callback' , async ( ) => {
187228 const controller = new AbortController ( )
188229 const emit = vi . fn ( )
0 commit comments