package handlers import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" ) // TestLibraryMatchUnmatchedItems_NilPool verifies that the unmatched-items // endpoint returns a clear error when the database pool is not configured. func TestLibraryMatchUnmatchedItems_NilPool(t *testing.T) { h := &LibraryHandler{} r := chi.NewRouter() r.Get("/libraries/unmatched-items", h.HandleListUnmatchedItems) req := httptest.NewRequest(http.MethodGet, "/libraries/unmatched-items", nil) rec := httptest.NewRecorder() r.ServeHTTP(rec, req) if rec.Code != http.StatusInternalServerError { t.Fatalf("expected 500 for nil pool, got %d: %s", rec.Code, rec.Body.String()) } var resp errorResponse if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { t.Fatalf("decoding error response: %v", err) } if resp.Error != "internal_error" { t.Errorf("expected error code 'internal_error', got %q", resp.Error) } } // TestLibraryMatchUnmatchedItems_ResponseShape verifies that the // unmatchedItemResponse type has the expected JSON field tags for the // admin maintenance page. This is a compile-time structure test. func TestLibraryMatchUnmatchedItems_ResponseShape(t *testing.T) { item := unmatchedItemResponse{ ContentID: "abc-123", Title: "Test Movie", Year: 2024, ContentType: "movie", LibraryID: 1, LibraryName: "Movies", Status: "unmatched", } data, err := json.Marshal(item) if err != nil { t.Fatalf("marshal: %v", err) } var m map[string]any if err := json.Unmarshal(data, &m); err != nil { t.Fatalf("unmarshal: %v", err) } expectedKeys := []string{ "content_id", "title", "year", "content_type", "library_id", "library_name", "status", } for _, key := range expectedKeys { if _, ok := m[key]; !ok { t.Errorf("expected JSON key %q in response, got keys: %v", key, keys(m)) } } } // TestLibraryMatchStaleIDs_NilRepo verifies that the stale-IDs endpoint // returns an empty array when the repository is nil, confirming backward // compatibility after the new match endpoints are introduced. func TestLibraryMatchStaleIDs_NilRepo(t *testing.T) { h := &LibraryHandler{} // StaleIDRepo is nil r := chi.NewRouter() r.Get("/libraries/stale-ids", h.HandleListStaleIDs) req := httptest.NewRequest(http.MethodGet, "/libraries/stale-ids", nil) rec := httptest.NewRecorder() r.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200 for nil StaleIDRepo, got %d: %s", rec.Code, rec.Body.String()) } var resp []staleMediaIDResponse if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { t.Fatalf("decoding response: %v", err) } if len(resp) != 0 { t.Errorf("expected empty stale IDs list, got %d entries", len(resp)) } } // TestLibraryMatchStaleIDs_ResponseShape verifies the stale media ID response // JSON structure still contains the expected fields. func TestLibraryMatchStaleIDs_ResponseShape(t *testing.T) { item := staleMediaIDResponse{ ContentID: "content-1", LibraryID: 2, LibraryName: "TV Shows", Title: "Test Show", Year: 2023, ContentType: "series", Provider: "tvdb", ProviderID: "12345", FirstSeenAt: "2023-01-01T00:00:00Z", LastSeenAt: "2023-06-01T00:00:00Z", } data, err := json.Marshal(item) if err != nil { t.Fatalf("marshal: %v", err) } var m map[string]any if err := json.Unmarshal(data, &m); err != nil { t.Fatalf("unmarshal: %v", err) } expectedKeys := []string{ "content_id", "library_id", "library_name", "title", "year", "content_type", "provider", "provider_id", "first_seen_at", "last_seen_at", } for _, key := range expectedKeys { if _, ok := m[key]; !ok { t.Errorf("expected JSON key %q in response, got keys: %v", key, keys(m)) } } } // TestLibraryMatchRematch_DeprecatedStillRoutes verifies that the deprecated // HandleRematchStaleID handler method still exists and can be registered as a // route alongside the new match endpoints. This is a compile-time routing test. func TestLibraryMatchRematch_DeprecatedStillRoutes(t *testing.T) { h := &LibraryHandler{} r := chi.NewRouter() // Register both old and new endpoints to prove they coexist. r.Post("/libraries/stale-ids/{contentID}/rematch", h.HandleRematchStaleID) r.Get("/libraries/unmatched-items", h.HandleListUnmatchedItems) r.Get("/libraries/stale-ids", h.HandleListStaleIDs) // Verify the routes are registered by checking that a walk finds them. found := map[string]bool{} chi.Walk(r, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error { found[method+" "+route] = true return nil }) expectedRoutes := []string{ "POST /libraries/stale-ids/{contentID}/rematch", "GET /libraries/unmatched-items", "GET /libraries/stale-ids", } for _, route := range expectedRoutes { if !found[route] { t.Errorf("expected route %q to be registered, registered routes: %v", route, found) } } } func keys(m map[string]any) []string { out := make([]string, 0, len(m)) for k := range m { out = append(out, k) } return out }