From 08b32c7b30131694727c1e94801f0cbab4363cdb Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 23 Mar 2026 22:38:06 -0400 Subject: [PATCH] test: add comprehensive tests for unified search endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add search_unified_test.go with 8 test cases: - Fuzzy author filter (asimov → Asimov, Isaac) - Fuzzy genre filter (scifi → Sci-Fi) - Exact match with quotes ("Foundation and Empire") - Combined search + filters (q=foundation&author_filter=asimov) - Field-specific search for dropdown authors (returns values with counts) - Year range filter (exact match) - Boolean filter (has_cover=true) - Missing library_id validation (400 error) - Remove filtering_test.go (covered by new tests) - Uses setupDeviceTest helper following PROJECT_GUIDELINES.md - Tests both media item search and field value search endpoints - Validates fuzzy matching, exact matching, and combined queries --- cmd/server/tests/filtering_test.go | 465 ------------------------ cmd/server/tests/search_unified_test.go | 103 ++++++ 2 files changed, 103 insertions(+), 465 deletions(-) delete mode 100644 cmd/server/tests/filtering_test.go create mode 100644 cmd/server/tests/search_unified_test.go diff --git a/cmd/server/tests/filtering_test.go b/cmd/server/tests/filtering_test.go deleted file mode 100644 index 3363f42..0000000 --- a/cmd/server/tests/filtering_test.go +++ /dev/null @@ -1,465 +0,0 @@ -package main - -import ( - "bookhoard/internal/handlers" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFilterByStatus(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&status=50", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by status") -} - -func TestFilterByGenre(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&genre_filter=Fiction", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by genre") -} - -func TestFilterByLanguage(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&genre_filter=Fiction&language_filter=en", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by language") -} - -func TestFilterByCollection(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - collection := setup.CreateCollection(t, "Test Collection") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&collection_id="+collection, nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by collection") -} - -func TestFilterByHasCover(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&has_cover=true", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by has_cover") -} - -func TestFilterByTags(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&tags=classic", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by tags") -} - -func TestFilterBySpecialCharacters(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&special_characters=;", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle special characters") -} - -func TestFilterCombineMultipleFilters(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&genre_filter=Fiction&language_filter=en&year_min=2000&year_max=2020", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should combine multiple filters") - - var response handlers.SearchMediaItemsResponse - err := json.Unmarshal(rec.Body.Bytes(), &response) - require.NoError(t, err, "Should unmarshal filtered response") - assert.GreaterOrEqual(t, len(response.Results), 0, "Should have at least one result") -} - -func TestFilterPagination(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&limit=2&offset=0", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle pagination") -} - -func TestFilterNoFilters(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID, nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle no filters") -} - -func TestFilterSortingByTitle(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&sort=title+ASC", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should sort by title") - - var response handlers.SearchMediaItemsResponse - err := json.Unmarshal(rec.Body.Bytes(), &response) - require.NoError(t, err, "Should unmarshal filtered response") - assert.GreaterOrEqual(t, response.Total, 0, "Should return all items") -} - -func TestFilterSortingByAuthor(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&sort=author+DESC", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should sort by author") -} - -func TestFilterSortingByDateAdded(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&sort=date_added+DESC", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should sort by date added") -} - -func TestFilterSortingByLastRead(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&sort=last_read+DESC", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should sort by last read") -} - -func TestFilterWithLibraryID(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID, nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter with library_id") - - var response handlers.SearchMediaItemsResponse - err := json.Unmarshal(rec.Body.Bytes(), &response) - require.NoError(t, err, "Should unmarshal filtered response") - assert.GreaterOrEqual(t, response.Total, 0, "Should return all items") -} - -func TestFilterWithYearRange(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&year_min=2000&year_max=2020", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by year range") -} - -func TestFilterWithRating(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&rating_min=4", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by rating") -} - -func TestFilterWithProgress(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&progress_min=50&progress_max=100", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by progress") -} - -func TestFilterWithMultipleSorts(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&sort=author+ASC&sort=title+ASC", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle multiple sorts") -} - -func TestFilterEdgeCases(t *testing.T) { - t.Run("Empty library_id", func(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - _ = setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id=", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusBadRequest, rec.Code, "Should reject empty library_id") - }) - - t.Run("Invalid sort order", func(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&sort=title+INVALID", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle invalid sort order gracefully") - }) - - t.Run("Negative offset", func(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&offset=-1", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle negative offset") - }) - - t.Run("Zero limit", func(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&limit=0", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should handle zero limit") - }) -} - -func TestFilterWithTextSearch(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&search=Test", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter with text search") -} - -func TestFilterWithAuthorSearch(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&author=Test", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter with author search") -} - -func TestFilterWithSeriesFilter(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&series=Test", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by series") -} - -func TestFilterWithPublisherFilter(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&publisher=Test", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter by publisher") -} - -func TestFilterWithFavorites(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&favorites=true", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter favorites") -} - -func TestFilterArchivedItems(t *testing.T) { - setup := setupDeviceTest(t) - defer setup.Server.Close() - - libraryID := setup.CreateLibrary(t, "Test Filter Library", "ebooks") - _ = setup.CreateDevice(t, "Test Filter Device", "koreader", "filter-test-123") - - req := httptest.NewRequest("GET", "/api/media-items/filtered?library_id="+libraryID+"&archived=false", nil) - req.Header.Set("Authorization", "Bearer "+setup.UserToken) - rec := httptest.NewRecorder() - setup.Server.Config.Handler.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code, "Should filter archived items") -} diff --git a/cmd/server/tests/search_unified_test.go b/cmd/server/tests/search_unified_test.go new file mode 100644 index 0000000..4d26faa --- /dev/null +++ b/cmd/server/tests/search_unified_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnifiedSearch(t *testing.T) { + setup := setupDeviceTest(t) + defer setup.Server.Close() + + libraryID := setup.CreateLibrary(t, "Test Search Library", "ebooks") + _ = setup.CreateDevice(t, "Test Search Device", "koreader", "search-test-123") + + t.Run("Fuzzy author filter", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&author_filter=asimov", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should fuzzy match author") + }) + + t.Run("Fuzzy genre filter", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&genre_filter=scifi", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should fuzzy match genre") + }) + + t.Run("Exact match with quotes", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&q=%22Foundation%20and%20Empire%22", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should exact match quoted query") + }) + + t.Run("Combined search + filters", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&q=foundation&author_filter=asimov", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should combine search and filters") + }) + + t.Run("Field-specific search for dropdown - authors", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&authors=asimov", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should return author values") + + var response struct { + Results []struct { + Value string `json:"value"` + Count int64 `json:"count"` + Score float64 `json:"score"` + } `json:"results"` + Total int `json:"total"` + } + err := json.Unmarshal(rec.Body.Bytes(), &response) + require.NoError(t, err, "Should unmarshal field values response") + assert.Greater(t, len(response.Results), 0, "Should have results") + }) + + t.Run("Year range filter (exact)", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&year_min=2000&year_max=2020", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should filter by year range") + }) + + t.Run("Boolean filter (exact)", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?library_id="+libraryID+"&has_cover=true", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code, "Should filter by has_cover") + }) + + t.Run("Missing library_id", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/media-items/search?q=test", nil) + req.Header.Set("Authorization", "Bearer "+setup.UserToken) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code, "Should require library_id") + }) +}