@@ -135,8 +135,8 @@ func newValkeyTestClient(t *testing.T) (*valkeyStore, *miniredis.Miniredis) {
135135 rs := & valkeyStore {
136136 cli : client ,
137137 sessionPrefix : "session:" ,
138- expiryIndexKey : "sandbox :expiry" ,
139- lastActivityIndexKey : "sandbox :last_activity" ,
138+ expiryIndexKey : "session :expiry" ,
139+ lastActivityIndexKey : "session :last_activity" ,
140140 }
141141 return rs , mr
142142}
@@ -360,3 +360,79 @@ func TestValkeyStore_UpdateSandboxLastActivity(t *testing.T) {
360360 assert .Error (t , err )
361361 assert .True (t , errors .Is (err , ErrNotFound ))
362362}
363+
364+ func TestValkeyStore_ListSandboxesByKind (t * testing.T ) {
365+ ctx := context .Background ()
366+ c , _ := newValkeyTestClient (t )
367+
368+ now := time .Now ().UTC ()
369+ sb1 := newTestSandbox ("sb-1" , "sess-1" , now .Add (1 * time .Hour ))
370+ sb1 .Kind = types .AgentRuntimeKind
371+
372+ sb2 := newTestSandbox ("sb-2" , "sess-2" , now .Add (1 * time .Hour ))
373+ sb2 .Kind = types .CodeInterpreterKind
374+
375+ sb3 := newTestSandbox ("sb-3" , "sess-3" , now .Add (1 * time .Hour ))
376+ sb3 .Kind = types .AgentRuntimeKind
377+
378+ assert .NoError (t , c .StoreSandbox (ctx , sb1 ))
379+ assert .NoError (t , c .StoreSandbox (ctx , sb2 ))
380+ assert .NoError (t , c .StoreSandbox (ctx , sb3 ))
381+
382+ // List AgentRuntimeKind
383+ list , err := c .ListSandboxesByKind (ctx , types .AgentRuntimeKind )
384+ assert .NoError (t , err )
385+ assert .Len (t , list , 2 )
386+
387+ ids := map [string ]bool {}
388+ for _ , sb := range list {
389+ ids [sb .SessionID ] = true
390+ }
391+ assert .True (t , ids ["sess-1" ])
392+ assert .True (t , ids ["sess-3" ])
393+
394+ // List CodeInterpreterKind
395+ list2 , err := c .ListSandboxesByKind (ctx , types .CodeInterpreterKind )
396+ assert .NoError (t , err )
397+ assert .Len (t , list2 , 1 )
398+ assert .Equal (t , "sess-2" , list2 [0 ].SessionID )
399+
400+ // List unknown kind
401+ list3 , err := c .ListSandboxesByKind (ctx , "unknown-kind" )
402+ assert .NoError (t , err )
403+ assert .Len (t , list3 , 0 )
404+ }
405+
406+ // TestValkeyStore_LoadSandboxesBySessionIDs_OrphanedZSetEntry verifies that
407+ // loadSandboxesBySessionIDs skips session IDs whose hash key has been evicted
408+ // from Valkey (orphaned sorted-set entry) instead of aborting the entire batch.
409+ func TestValkeyStore_LoadSandboxesBySessionIDs_OrphanedZSetEntry (t * testing.T ) {
410+ ctx := context .Background ()
411+ c , mr := newValkeyTestClient (t )
412+
413+ now := time .Now ().UTC ().Truncate (time .Second )
414+
415+ sb1 := newTestSandbox ("sb-orphan" , "sess-orphan" , now .Add (- 1 * time .Hour ))
416+ sb2 := newTestSandbox ("sb-alive" , "sess-alive" , now .Add (- 2 * time .Hour ))
417+
418+ if err := c .StoreSandbox (ctx , sb1 ); err != nil {
419+ t .Fatalf ("StoreSandbox sb1 error: %v" , err )
420+ }
421+ if err := c .StoreSandbox (ctx , sb2 ); err != nil {
422+ t .Fatalf ("StoreSandbox sb2 error: %v" , err )
423+ }
424+
425+ // Simulate Valkey evicting the hash key for sb1 while leaving its zset entry.
426+ mr .Del (c .sessionKey ("sess-orphan" ))
427+
428+ result , err := c .loadSandboxesBySessionIDs (ctx , []string {"sess-orphan" , "sess-alive" })
429+ if err != nil {
430+ t .Fatalf ("expected no error with orphaned zset entry, got: %v" , err )
431+ }
432+ if len (result ) != 1 {
433+ t .Fatalf ("expected 1 sandbox (the non-evicted one), got %d" , len (result ))
434+ }
435+ if result [0 ].SandboxID != "sb-alive" {
436+ t .Fatalf ("expected sb-alive, got %s" , result [0 ].SandboxID )
437+ }
438+ }
0 commit comments