@@ -5,6 +5,7 @@ package session
55
66import (
77 "context"
8+ "errors"
89 "fmt"
910 "testing"
1011 "time"
@@ -13,6 +14,24 @@ import (
1314 "github.com/stretchr/testify/require"
1415)
1516
17+ // mockClosableSession is a test session that implements io.Closer
18+ type mockClosableSession struct {
19+ * ProxySession
20+ closeCalled bool
21+ closeError error
22+ }
23+
24+ func newMockClosableSession (id string ) * mockClosableSession {
25+ return & mockClosableSession {
26+ ProxySession : NewProxySession (id ),
27+ }
28+ }
29+
30+ func (m * mockClosableSession ) Close () error {
31+ m .closeCalled = true
32+ return m .closeError
33+ }
34+
1635// TestLocalStorage tests the LocalStorage implementation
1736func TestLocalStorage (t * testing.T ) {
1837 t .Parallel ()
@@ -302,6 +321,146 @@ func TestLocalStorage(t *testing.T) {
302321 // Should not error, just stop early
303322 assert .NoError (t , err )
304323 })
324+
325+ t .Run ("DeleteExpired calls Close on io.Closer sessions" , func (t * testing.T ) {
326+ t .Parallel ()
327+ storage := NewLocalStorage ()
328+ defer storage .Close ()
329+
330+ ctx := context .Background ()
331+
332+ // Create a closable session (implements io.Closer)
333+ closableSession := newMockClosableSession ("closable-session" )
334+ closableSession .updated = time .Now ().Add (- 2 * time .Hour )
335+
336+ // Create a regular session (does not implement io.Closer)
337+ regularSession := NewProxySession ("regular-session" )
338+ regularSession .updated = time .Now ().Add (- 2 * time .Hour )
339+
340+ // Store both sessions
341+ err := storage .Store (ctx , closableSession )
342+ require .NoError (t , err )
343+ err = storage .Store (ctx , regularSession )
344+ require .NoError (t , err )
345+
346+ // Delete sessions older than 1 hour
347+ cutoff := time .Now ().Add (- 1 * time .Hour )
348+ err = storage .DeleteExpired (ctx , cutoff )
349+ require .NoError (t , err )
350+
351+ // Both sessions should be deleted
352+ _ , err = storage .Load (ctx , "closable-session" )
353+ assert .Equal (t , ErrSessionNotFound , err )
354+ _ , err = storage .Load (ctx , "regular-session" )
355+ assert .Equal (t , ErrSessionNotFound , err )
356+
357+ // Close() should have been called on the closable session
358+ assert .True (t , closableSession .closeCalled , "Close() should have been called on closable session" )
359+ })
360+
361+ t .Run ("DeleteExpired continues deletion even if Close fails" , func (t * testing.T ) {
362+ t .Parallel ()
363+ storage := NewLocalStorage ()
364+ defer storage .Close ()
365+
366+ ctx := context .Background ()
367+
368+ // Create a closable session that returns an error on Close()
369+ failingSession := newMockClosableSession ("failing-session" )
370+ failingSession .closeError = errors .New ("close failed" )
371+ failingSession .updated = time .Now ().Add (- 2 * time .Hour )
372+
373+ // Store the session
374+ err := storage .Store (ctx , failingSession )
375+ require .NoError (t , err )
376+
377+ // Delete expired sessions
378+ cutoff := time .Now ().Add (- 1 * time .Hour )
379+ err = storage .DeleteExpired (ctx , cutoff )
380+ require .NoError (t , err )
381+
382+ // Session should still be deleted despite Close() error
383+ _ , err = storage .Load (ctx , "failing-session" )
384+ assert .Equal (t , ErrSessionNotFound , err )
385+
386+ // Close() should have been called
387+ assert .True (t , failingSession .closeCalled , "Close() should have been called" )
388+ })
389+
390+ t .Run ("DeleteExpired handles non-io.Closer sessions without error" , func (t * testing.T ) {
391+ t .Parallel ()
392+ storage := NewLocalStorage ()
393+ defer storage .Close ()
394+
395+ ctx := context .Background ()
396+
397+ // Create multiple regular sessions (do not implement io.Closer)
398+ for i := 0 ; i < 5 ; i ++ {
399+ session := NewProxySession (fmt .Sprintf ("session-%d" , i ))
400+ session .updated = time .Now ().Add (- 2 * time .Hour )
401+ err := storage .Store (ctx , session )
402+ require .NoError (t , err )
403+ }
404+
405+ // Delete expired sessions
406+ cutoff := time .Now ().Add (- 1 * time .Hour )
407+ err := storage .DeleteExpired (ctx , cutoff )
408+ require .NoError (t , err )
409+
410+ // All sessions should be deleted
411+ for i := 0 ; i < 5 ; i ++ {
412+ _ , err := storage .Load (ctx , fmt .Sprintf ("session-%d" , i ))
413+ assert .Equal (t , ErrSessionNotFound , err )
414+ }
415+ })
416+
417+ t .Run ("DeleteExpired with mixed session types" , func (t * testing.T ) {
418+ t .Parallel ()
419+ storage := NewLocalStorage ()
420+ defer storage .Close ()
421+
422+ ctx := context .Background ()
423+
424+ // Create a mix of closable and regular expired sessions
425+ closable1 := newMockClosableSession ("closable-1" )
426+ closable1 .updated = time .Now ().Add (- 2 * time .Hour )
427+ closable2 := newMockClosableSession ("closable-2" )
428+ closable2 .updated = time .Now ().Add (- 2 * time .Hour )
429+
430+ regular1 := NewProxySession ("regular-1" )
431+ regular1 .updated = time .Now ().Add (- 2 * time .Hour )
432+ regular2 := NewProxySession ("regular-2" )
433+ regular2 .updated = time .Now ().Add (- 2 * time .Hour )
434+
435+ // Store all sessions
436+ err := storage .Store (ctx , closable1 )
437+ require .NoError (t , err )
438+ err = storage .Store (ctx , closable2 )
439+ require .NoError (t , err )
440+ err = storage .Store (ctx , regular1 )
441+ require .NoError (t , err )
442+ err = storage .Store (ctx , regular2 )
443+ require .NoError (t , err )
444+
445+ // Delete expired sessions
446+ cutoff := time .Now ().Add (- 1 * time .Hour )
447+ err = storage .DeleteExpired (ctx , cutoff )
448+ require .NoError (t , err )
449+
450+ // All sessions should be deleted
451+ _ , err = storage .Load (ctx , "closable-1" )
452+ assert .Equal (t , ErrSessionNotFound , err )
453+ _ , err = storage .Load (ctx , "closable-2" )
454+ assert .Equal (t , ErrSessionNotFound , err )
455+ _ , err = storage .Load (ctx , "regular-1" )
456+ assert .Equal (t , ErrSessionNotFound , err )
457+ _ , err = storage .Load (ctx , "regular-2" )
458+ assert .Equal (t , ErrSessionNotFound , err )
459+
460+ // Close() should have been called on closable sessions
461+ assert .True (t , closable1 .closeCalled , "Close() should have been called on closable-1" )
462+ assert .True (t , closable2 .closeCalled , "Close() should have been called on closable-2" )
463+ })
305464}
306465
307466// TestManagerWithStorage tests the Manager with the Storage interface
0 commit comments