@@ -320,3 +320,157 @@ func TestHandleSessions_FiltersEmptyJSONLFiles(t *testing.T) {
320320 t .Fatalf ("detail status = %d, want %d, body=%s" , detailRec .Code , http .StatusNotFound , detailRec .Body .String ())
321321 }
322322}
323+
324+ func TestHandleListSessions_MissingDirectory (t * testing.T ) {
325+ configPath , cleanup := setupOAuthTestEnv (t )
326+ defer cleanup ()
327+
328+ // Don't create the sessions dir — it should return empty list with 200.
329+ h := NewHandler (configPath )
330+ mux := http .NewServeMux ()
331+ h .RegisterRoutes (mux )
332+
333+ rec := httptest .NewRecorder ()
334+ req := httptest .NewRequest (http .MethodGet , "/api/sessions" , nil )
335+ mux .ServeHTTP (rec , req )
336+
337+ if rec .Code != http .StatusOK {
338+ t .Fatalf ("status = %d, want %d, body=%s" , rec .Code , http .StatusOK , rec .Body .String ())
339+ }
340+
341+ var items []sessionListItem
342+ if err := json .Unmarshal (rec .Body .Bytes (), & items ); err != nil {
343+ t .Fatalf ("Unmarshal() error = %v" , err )
344+ }
345+ if len (items ) != 0 {
346+ t .Fatalf ("len(items) = %d, want 0" , len (items ))
347+ }
348+ }
349+
350+ func TestHandleListSessions_UnreadableDirectory (t * testing.T ) {
351+ configPath , cleanup := setupOAuthTestEnv (t )
352+ defer cleanup ()
353+
354+ dir := sessionsTestDir (t , configPath )
355+
356+ // Remove all permissions so ReadDir fails with a permission error.
357+ if err := os .Chmod (dir , 0o000 ); err != nil {
358+ t .Fatalf ("Chmod() error = %v" , err )
359+ }
360+ t .Cleanup (func () { os .Chmod (dir , 0o755 ) })
361+
362+ h := NewHandler (configPath )
363+ mux := http .NewServeMux ()
364+ h .RegisterRoutes (mux )
365+
366+ rec := httptest .NewRecorder ()
367+ req := httptest .NewRequest (http .MethodGet , "/api/sessions" , nil )
368+ mux .ServeHTTP (rec , req )
369+
370+ if rec .Code != http .StatusInternalServerError {
371+ t .Fatalf ("status = %d, want %d, body=%s" , rec .Code , http .StatusInternalServerError , rec .Body .String ())
372+ }
373+ }
374+
375+ func TestHandleListSessions_MalformedSessionFileSkipped (t * testing.T ) {
376+ configPath , cleanup := setupOAuthTestEnv (t )
377+ defer cleanup ()
378+
379+ dir := sessionsTestDir (t , configPath )
380+
381+ // Write a valid session.
382+ store , err := memory .NewJSONLStore (dir )
383+ if err != nil {
384+ t .Fatalf ("NewJSONLStore() error = %v" , err )
385+ }
386+ validKey := picoSessionPrefix + "valid-session"
387+ if err := store .AddFullMessage (nil , validKey , providers.Message {
388+ Role : "user" ,
389+ Content : "hello" ,
390+ }); err != nil {
391+ t .Fatalf ("AddFullMessage() error = %v" , err )
392+ }
393+
394+ // Write a malformed legacy JSON session alongside it.
395+ corruptName := sanitizeSessionKey (picoSessionPrefix + "corrupt-session" ) + ".json"
396+ if err := os .WriteFile (filepath .Join (dir , corruptName ), []byte ("{invalid json" ), 0o644 ); err != nil {
397+ t .Fatalf ("WriteFile() error = %v" , err )
398+ }
399+
400+ h := NewHandler (configPath )
401+ mux := http .NewServeMux ()
402+ h .RegisterRoutes (mux )
403+
404+ rec := httptest .NewRecorder ()
405+ req := httptest .NewRequest (http .MethodGet , "/api/sessions" , nil )
406+ mux .ServeHTTP (rec , req )
407+
408+ if rec .Code != http .StatusOK {
409+ t .Fatalf ("status = %d, want %d, body=%s" , rec .Code , http .StatusOK , rec .Body .String ())
410+ }
411+
412+ var items []sessionListItem
413+ if err := json .Unmarshal (rec .Body .Bytes (), & items ); err != nil {
414+ t .Fatalf ("Unmarshal() error = %v" , err )
415+ }
416+ if len (items ) != 1 {
417+ t .Fatalf ("len(items) = %d, want 1 (valid session only)" , len (items ))
418+ }
419+ if items [0 ].ID != "valid-session" {
420+ t .Fatalf ("items[0].ID = %q, want %q" , items [0 ].ID , "valid-session" )
421+ }
422+ }
423+
424+ func TestHandleListSessions_SortsByTimeNotString (t * testing.T ) {
425+ configPath , cleanup := setupOAuthTestEnv (t )
426+ defer cleanup ()
427+
428+ dir := sessionsTestDir (t , configPath )
429+ store , err := memory .NewJSONLStore (dir )
430+ if err != nil {
431+ t .Fatalf ("NewJSONLStore() error = %v" , err )
432+ }
433+
434+ // Create "older" session first, then "newer" session.
435+ // The newer session should appear first in results.
436+ olderKey := picoSessionPrefix + "older"
437+ if err := store .AddFullMessage (nil , olderKey , providers.Message {
438+ Role : "user" , Content : "old message" ,
439+ }); err != nil {
440+ t .Fatalf ("AddFullMessage(older) error = %v" , err )
441+ }
442+
443+ newerKey := picoSessionPrefix + "newer"
444+ if err := store .AddFullMessage (nil , newerKey , providers.Message {
445+ Role : "user" , Content : "new message" ,
446+ }); err != nil {
447+ t .Fatalf ("AddFullMessage(newer) error = %v" , err )
448+ }
449+
450+ h := NewHandler (configPath )
451+ mux := http .NewServeMux ()
452+ h .RegisterRoutes (mux )
453+
454+ rec := httptest .NewRecorder ()
455+ req := httptest .NewRequest (http .MethodGet , "/api/sessions" , nil )
456+ mux .ServeHTTP (rec , req )
457+
458+ if rec .Code != http .StatusOK {
459+ t .Fatalf ("status = %d, want %d" , rec .Code , http .StatusOK )
460+ }
461+
462+ var items []sessionListItem
463+ if err := json .Unmarshal (rec .Body .Bytes (), & items ); err != nil {
464+ t .Fatalf ("Unmarshal() error = %v" , err )
465+ }
466+ if len (items ) != 2 {
467+ t .Fatalf ("len(items) = %d, want 2" , len (items ))
468+ }
469+ // The session created last should sort first (most recent).
470+ if items [0 ].ID != "newer" {
471+ t .Fatalf ("items[0].ID = %q, want %q (most recent first)" , items [0 ].ID , "newer" )
472+ }
473+ if items [1 ].ID != "older" {
474+ t .Fatalf ("items[1].ID = %q, want %q" , items [1 ].ID , "older" )
475+ }
476+ }
0 commit comments