package main import ( "bytes" "encoding/json" "io" "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") client := &http.Client{} // Add folder to library (required before adding media items) folderReq := map[string]interface{}{ "folder_path": "/app/uploads", } folderBody, _ := json.Marshal(folderReq) folderHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries/"+libraryID+"/folders", bytes.NewBuffer(folderBody)) folderHTTP.Header.Set("Content-Type", "application/json") folderHTTP.Header.Set("Authorization", "Bearer "+setup.UserToken) folderResp, err := client.Do(folderHTTP) require.NoError(t, err) _ = folderResp.Body.Close() require.Equal(t, http.StatusCreated, folderResp.StatusCode) // Helper to create book with fields createBook := func(title, author, genre string, year int, hasCover bool) { bookReq := map[string]interface{}{ "library_id": libraryID, "title": title, "author": author, "genre": genre, "copyright_year": year, "file_path": "/tmp/test.epub", "file_size": 1024, "mime_type": "application/epub+zip", } if hasCover { bookReq["cover_image_path"] = "/tmp/cover.jpg" } body, _ := json.Marshal(bookReq) req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+setup.UserToken) resp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) require.Equal(t, http.StatusCreated, resp.StatusCode) } // Create test books with various fields for filtering createBook("Foundation and Empire", "asimov", "scifi", 1951, true) createBook("The Martian", "weir", "scifi", 2010, true) createBook("I, Robot", "asimov", "fiction", 1950, false) t.Run("Fuzzy author filter", 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("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 searches all libraries", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/media-items/search?q=zzzznonexistent", nil) req.Header.Set("Authorization", "Bearer "+setup.UserToken) rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) // library_id is optional - searches all libraries when omitted // Returns 404 when no results match assert.Equal(t, http.StatusNotFound, rec.Code, "Should return 404 when no results match") }) }