package cache import ( "slices" "testing" ) func newTestStorage(t *testing.T) *BoltStorage { t.Helper() storage, err := NewBoltStorage(false) if err != nil { t.Fatalf("NewBoltStorage failed: %v", err) } t.Cleanup(func() { storage.Close() }) return storage } // testDID generates a unique DID for each test to ensure test isolation. func testDID(t *testing.T, suffix string) string { return "did:plc:test/" + t.Name() + "/" + suffix } func TestReadOnlyMode(t *testing.T) { tests := []struct { name string readOnly bool shouldWrite bool expectError bool }{ { name: "read-write mode allows writes", readOnly: false, shouldWrite: true, expectError: false, }, { name: "read-only mode prevents writes", readOnly: true, shouldWrite: true, expectError: true, }, { name: "read-only mode allows iteration", readOnly: true, shouldWrite: false, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storage, err := NewBoltStorage(tt.readOnly) if err != nil { t.Fatalf("NewBoltStorage failed: %v", err) } t.Cleanup(func() { storage.Close() }) did := "did:plc:testro" + t.Name() records := map[string][]byte{ "key1": []byte(`{"trackName":"track1"}`), } if tt.shouldWrite { err = storage.SaveRecords(did, records) if tt.expectError && err == nil { t.Error("expected error when writing to read-only storage") } else if !tt.expectError && err != nil { t.Errorf("unexpected error when writing: %v", err) } } else { // Just test that iteration works on read-only storage var count int for range storage.IterateUnpublished(did, false) { count++ } if count != 0 { t.Errorf("expected 0 records, got %d", count) } } }) } } func TestSaveIterateRoundtrip(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "roundtrip") records := map[string][]byte{ "key1": []byte(`{"trackName":"track1"}`), "key2": []byte(`{"trackName":"track2"}`), } if err := storage.SaveRecords(did, records); err != nil { t.Fatalf("SaveRecords failed: %v", err) } count := 0 for range storage.IterateUnpublished(did, false) { count++ } if count != 2 { t.Errorf("expected 2 records, got %d", count) } } func TestMarkPublished(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "mark") records := map[string][]byte{ "key1": []byte(`{"trackName":"track1"}`), "key2": []byte(`{"trackName":"track2"}`), } storage.SaveRecords(did, records) if err := storage.MarkPublished(did, "key1"); err != nil { t.Fatalf("MarkPublished failed: %v", err) } count := 0 for range storage.IterateUnpublished(did, false) { count++ } if count != 1 { t.Errorf("expected 1 unpublished record, got %d", count) } } func TestClear(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "clear") storage.SaveRecords(did, map[string][]byte{"key1": []byte(`{}`)}) if !storage.IsValid(did) { t.Error("cache should be valid") } storage.Clear(did) if storage.IsValid(did) { t.Error("cache should be invalid") } } func TestIterateUnpublishedReverse(t *testing.T) { tests := []struct { name string did string records map[string][]byte reverse bool expectedKeys []string }{ { name: "basic reverse iteration", records: map[string][]byte{"aaa": []byte(`{"trackName":"a"}`), "bbb": []byte(`{"trackName":"b"}`), "ccc": []byte(`{"trackName":"c"}`)}, reverse: true, expectedKeys: []string{"ccc", "bbb", "aaa"}, }, { name: "forward iteration", records: map[string][]byte{"aaa": []byte(`{"trackName":"a"}`), "bbb": []byte(`{"trackName":"b"}`), "ccc": []byte(`{"trackName":"c"}`)}, reverse: false, expectedKeys: []string{"aaa", "bbb", "ccc"}, }, { name: "single record reverse", records: map[string][]byte{"only": []byte(`{"trackName":"one"}`)}, reverse: true, expectedKeys: []string{"only"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "unpublished") if err := storage.SaveRecords(did, tt.records); err != nil { t.Fatalf("SaveRecords failed: %v", err) } var keys []string for key, rec := range storage.IterateUnpublished(did, tt.reverse) { keys = append(keys, key) _ = rec } if !slices.Equal(keys, tt.expectedKeys) { t.Errorf("expected keys %v, got %v", tt.expectedKeys, keys) } }) } } func TestIteratePublishedReverse(t *testing.T) { tests := []struct { name string records map[string][]byte published []string reverse bool expectedKeys []string }{ { name: "basic published reverse", records: map[string][]byte{"aaa": []byte(`{"trackName":"a"}`), "bbb": []byte(`{"trackName":"b"}`), "ccc": []byte(`{"trackName":"c"}`)}, published: []string{"aaa", "ccc"}, reverse: true, expectedKeys: []string{"ccc", "aaa"}, }, { name: "forward published", records: map[string][]byte{"aaa": []byte(`{"trackName":"a"}`), "bbb": []byte(`{"trackName":"b"}`), "ccc": []byte(`{"trackName":"c"}`)}, published: []string{"aaa", "ccc"}, reverse: false, expectedKeys: []string{"aaa", "ccc"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "published") if err := storage.SaveRecords(did, tt.records); err != nil { t.Fatalf("SaveRecords failed: %v", err) } if err := storage.MarkPublished(did, tt.published...); err != nil { t.Fatalf("MarkPublished failed: %v", err) } var keys []string for key, rec := range storage.IteratePublished(did, tt.reverse) { keys = append(keys, key) _ = rec } if !slices.Equal(keys, tt.expectedKeys) { t.Errorf("expected keys %v, got %v", tt.expectedKeys, keys) } }) } } func TestIterateReverseEmptyBucket(t *testing.T) { tests := []struct { name string reverse bool expectedLen int }{ { name: "empty unpublished reverse", reverse: true, expectedLen: 0, }, { name: "empty published reverse", reverse: true, expectedLen: 0, }, { name: "empty unpublished forward", reverse: false, expectedLen: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "empty") count := 0 for range storage.IterateUnpublished(did, tt.reverse) { count++ } if count != tt.expectedLen { t.Errorf("expected %d records, got %d", tt.expectedLen, count) } }) } } func TestIterateReverseWithEarlyExit(t *testing.T) { tests := []struct { name string records map[string][]byte reverse bool breakAfter int expectedKeys []string }{ { name: "exit after first record reverse", records: map[string][]byte{"aaa": []byte(`{"trackName":"a"}`), "bbb": []byte(`{"trackName":"b"}`), "ccc": []byte(`{"trackName":"c"}`)}, reverse: true, breakAfter: 1, expectedKeys: []string{"ccc"}, }, { name: "exit after two records reverse", records: map[string][]byte{"aaa": []byte(`{"trackName":"a"}`), "bbb": []byte(`{"trackName":"b"}`), "ccc": []byte(`{"trackName":"c"}`)}, reverse: true, breakAfter: 2, expectedKeys: []string{"ccc", "bbb"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "exit") if err := storage.SaveRecords(did, tt.records); err != nil { t.Fatalf("SaveRecords failed: %v", err) } var keys []string for key, rec := range storage.IterateUnpublished(did, tt.reverse) { keys = append(keys, key) _ = rec if len(keys) >= tt.breakAfter { break } } if !slices.Equal(keys, tt.expectedKeys) { t.Errorf("expected keys %v, got %v", tt.expectedKeys, keys) } }) } } func TestIterateFailed(t *testing.T) { tests := []struct { name string setupStorage func(*BoltStorage, string) error wantCount int wantKeys []string }{ { name: "returns failed records", setupStorage: func(s *BoltStorage, did string) error { records := map[string][]byte{ "key1": []byte(`{"trackName":"a"}`), "key2": []byte(`{"trackName":"b"}`), "key3": []byte(`{"trackName":"c"}`), } if err := s.SaveRecords(did, records); err != nil { return err } return s.MarkFailed(did, []string{"key1", "key3"}, "timeout error") }, wantCount: 2, wantKeys: []string{"key1", "key3"}, }, { name: "handles empty failed set", setupStorage: func(s *BoltStorage, did string) error { records := map[string][]byte{ "key1": []byte(`{"trackName":"a"}`), } return s.SaveRecords(did, records) }, wantCount: 0, wantKeys: nil, }, { name: "handles non-existent DID", setupStorage: func(s *BoltStorage, did string) error { return nil }, wantCount: 0, wantKeys: nil, }, { name: "returns error messages", setupStorage: func(s *BoltStorage, did string) error { records := map[string][]byte{ "key1": []byte(`{"trackName":"a"}`), } if err := s.SaveRecords(did, records); err != nil { return err } return s.MarkFailed(did, []string{"key1"}, "custom error message") }, wantCount: 1, wantKeys: []string{"key1"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "failed") if err := tt.setupStorage(storage, did); err != nil { t.Fatalf("setupStorage failed: %v", err) } var count int var keys []string iterateFailed := storage.IterateFailed(did) iterateFailed(func(key string, rec []byte, errMsg string) bool { count++ keys = append(keys, key) return true }) if count != tt.wantCount { t.Errorf("IterateFailed() returned %d records, want %d", count, tt.wantCount) } if len(keys) != len(tt.wantKeys) { t.Errorf("IterateFailed() returned %d keys, want %d", len(keys), len(tt.wantKeys)) return } for i, key := range keys { if key != tt.wantKeys[i] { t.Errorf("IterateFailed() key[%d] = %s, want %s", i, key, tt.wantKeys[i]) } } }) } } func TestIterateFailedWithEarlyExit(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "failed-exit") records := map[string][]byte{ "key1": []byte(`{"trackName":"a"}`), "key2": []byte(`{"trackName":"b"}`), "key3": []byte(`{"trackName":"c"}`), } if err := storage.SaveRecords(did, records); err != nil { t.Fatalf("SaveRecords failed: %v", err) } if err := storage.MarkFailed(did, []string{"key1", "key2", "key3"}, "error"); err != nil { t.Fatalf("MarkFailed failed: %v", err) } // Exit after 2 records var count int iterateFailed := storage.IterateFailed(did) iterateFailed(func(key string, rec []byte, errMsg string) bool { count++ return count < 2 // Exit after 2 records }) if count != 2 { t.Errorf("IterateFailed() returned %d records with early exit, want 2", count) } } func TestIterateFailedPreservesRecordData(t *testing.T) { storage := newTestStorage(t) did := testDID(t, "failed-data") records := map[string][]byte{ "key1": []byte(`{"trackName":"Test Track","artist":"Test Artist"}`), } if err := storage.SaveRecords(did, records); err != nil { t.Fatalf("SaveRecords failed: %v", err) } if err := storage.MarkFailed(did, []string{"key1"}, "network error"); err != nil { t.Fatalf("MarkFailed failed: %v", err) } var gotRecord []byte var gotErrMsg string iterateFailed := storage.IterateFailed(did) iterateFailed(func(key string, rec []byte, errMsg string) bool { if key == "key1" { gotRecord = rec gotErrMsg = errMsg } return true }) expectedRecord := []byte(`{"trackName":"Test Track","artist":"Test Artist"}`) if string(gotRecord) != string(expectedRecord) { t.Errorf("IterateFailed() record = %s, want %s", string(gotRecord), string(expectedRecord)) } if gotErrMsg != "network error" { t.Errorf("IterateFailed() errMsg = %s, want 'network error'", gotErrMsg) } }