@@ -2,6 +2,7 @@ package datastore
22
33import (
44 "context"
5+ "maps"
56 "os"
67 "testing"
78 "time"
@@ -328,3 +329,186 @@ func TestDatastorePersist_CleanupEmpty(t *testing.T) {
328329 t .Logf ("Cleanup count = %d (found existing expired entries)" , count )
329330 }
330331}
332+
333+ func TestDatastorePersist_Keys (t * testing.T ) {
334+ ctx := context .Background ()
335+ dp , cleanup := createTestStore [string , string ](t , ctx )
336+ defer cleanup ()
337+
338+ // Set entries with different prefixes
339+ entries := map [string ]string {
340+ "user:alice" : "alice-data" ,
341+ "user:bob" : "bob-data" ,
342+ "user:charlie" : "charlie-data" ,
343+ "post:1" : "post-1-data" ,
344+ "post:2" : "post-2-data" ,
345+ "other" : "other-data" ,
346+ }
347+
348+ for k , v := range entries {
349+ if err := dp .Set (ctx , k , v , time.Time {}); err != nil {
350+ t .Fatalf ("Set %s: %v" , k , err )
351+ }
352+ }
353+
354+ tests := []struct {
355+ name string
356+ prefix string
357+ want []string
358+ }{
359+ {"user prefix" , "user:" , []string {"user:alice" , "user:bob" , "user:charlie" }},
360+ {"post prefix" , "post:" , []string {"post:1" , "post:2" }},
361+ {"other prefix" , "other" , []string {"other" }},
362+ {"no match" , "nomatch:" , []string {}},
363+ {"empty prefix" , "" , []string {"other" , "post:1" , "post:2" , "user:alice" , "user:bob" , "user:charlie" }},
364+ }
365+
366+ for _ , tt := range tests {
367+ t .Run (tt .name , func (t * testing.T ) {
368+ var keys []string
369+ for k := range dp .Keys (ctx , tt .prefix ) {
370+ keys = append (keys , k )
371+ }
372+
373+ if len (keys ) != len (tt .want ) {
374+ t .Errorf ("Keys() returned %d keys; want %d. Got: %v, Want: %v" , len (keys ), len (tt .want ), keys , tt .want )
375+ return
376+ }
377+
378+ // Create map for comparison
379+ keyMap := make (map [string ]bool )
380+ for _ , k := range keys {
381+ keyMap [k ] = true
382+ }
383+
384+ for _ , wantKey := range tt .want {
385+ if ! keyMap [wantKey ] {
386+ t .Errorf ("Keys() missing key %q" , wantKey )
387+ }
388+ }
389+ })
390+ }
391+
392+ // Cleanup all test entries
393+ for k := range entries {
394+ if err := dp .Delete (ctx , k ); err != nil {
395+ t .Logf ("Delete error: %v" , err )
396+ }
397+ }
398+ }
399+
400+ func TestDatastorePersist_Range (t * testing.T ) {
401+ ctx := context .Background ()
402+ dp , cleanup := createTestStore [string , string ](t , ctx )
403+ defer cleanup ()
404+
405+ // Set entries with different prefixes
406+ entries := map [string ]string {
407+ "user:alice" : "alice-data" ,
408+ "user:bob" : "bob-data" ,
409+ "user:charlie" : "charlie-data" ,
410+ "post:1" : "post-1-data" ,
411+ "post:2" : "post-2-data" ,
412+ "other" : "other-data" ,
413+ }
414+
415+ for k , v := range entries {
416+ if err := dp .Set (ctx , k , v , time.Time {}); err != nil {
417+ t .Fatalf ("Set %s: %v" , k , err )
418+ }
419+ }
420+
421+ tests := []struct {
422+ name string
423+ prefix string
424+ want map [string ]string
425+ }{
426+ {"user prefix" , "user:" , map [string ]string {
427+ "user:alice" : "alice-data" ,
428+ "user:bob" : "bob-data" ,
429+ "user:charlie" : "charlie-data" ,
430+ }},
431+ {"post prefix" , "post:" , map [string ]string {
432+ "post:1" : "post-1-data" ,
433+ "post:2" : "post-2-data" ,
434+ }},
435+ {"other prefix" , "other" , map [string ]string {
436+ "other" : "other-data" ,
437+ }},
438+ {"no match" , "nomatch:" , map [string ]string {}},
439+ {"empty prefix" , "" , entries },
440+ }
441+
442+ for _ , tt := range tests {
443+ t .Run (tt .name , func (t * testing.T ) {
444+ result := maps .Collect (dp .Range (ctx , tt .prefix ))
445+
446+ if len (result ) != len (tt .want ) {
447+ t .Errorf ("Range() returned %d entries; want %d" , len (result ), len (tt .want ))
448+ return
449+ }
450+
451+ for k , wantVal := range tt .want {
452+ gotVal , found := result [k ]
453+ if ! found {
454+ t .Errorf ("Range() missing key %q" , k )
455+ continue
456+ }
457+ if gotVal != wantVal {
458+ t .Errorf ("Range() key %q = %q; want %q" , k , gotVal , wantVal )
459+ }
460+ }
461+ })
462+ }
463+
464+ // Cleanup all test entries
465+ for k := range entries {
466+ if err := dp .Delete (ctx , k ); err != nil {
467+ t .Logf ("Delete error: %v" , err )
468+ }
469+ }
470+ }
471+
472+ func TestDatastorePersist_Range_SkipsExpired (t * testing.T ) {
473+ ctx := context .Background ()
474+ dp , cleanup := createTestStore [string , string ](t , ctx )
475+ defer cleanup ()
476+
477+ // Set entries with different expiry times
478+ past := time .Now ().Add (- 1 * time .Hour )
479+ future := time .Now ().Add (1 * time .Hour )
480+
481+ if err := dp .Set (ctx , "expired-1" , "value1" , past ); err != nil {
482+ t .Fatalf ("Set: %v" , err )
483+ }
484+ if err := dp .Set (ctx , "valid-1" , "value2" , future ); err != nil {
485+ t .Fatalf ("Set: %v" , err )
486+ }
487+ if err := dp .Set (ctx , "valid-2" , "value3" , time.Time {}); err != nil {
488+ t .Fatalf ("Set: %v" , err )
489+ }
490+
491+ // Range should only return valid entries
492+ result := maps .Collect (dp .Range (ctx , "" ))
493+
494+ if len (result ) != 2 {
495+ t .Errorf ("Range() returned %d entries; want 2 (expired entries should be skipped)" , len (result ))
496+ }
497+
498+ if _ , found := result ["expired-1" ]; found {
499+ t .Error ("Range() should skip expired entries" )
500+ }
501+
502+ if val , found := result ["valid-1" ]; ! found || val != "value2" {
503+ t .Error ("Range() should return valid-1" )
504+ }
505+
506+ if val , found := result ["valid-2" ]; ! found || val != "value3" {
507+ t .Error ("Range() should return valid-2" )
508+ }
509+
510+ // Cleanup
511+ _ = dp .Delete (ctx , "valid-1" ) //nolint:errcheck // test cleanup
512+ _ = dp .Delete (ctx , "valid-2" ) //nolint:errcheck // test cleanup
513+ _ = dp .Delete (ctx , "expired-1" ) //nolint:errcheck // test cleanup
514+ }
0 commit comments