package main import ( "net/http" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestOPDSEndpoints tests OPDS (Open Publication Distribution System) endpoints func TestOPDSEndpoints(t *testing.T) { t.Run("GetDeviceCatalog_WithoutDeviceAuth", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/catalog", nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // OPDS endpoints may or may not require auth - depends on implementation // Accept either 200 (public) or 401 (requires auth) assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized) }) t.Run("GetDeviceCatalog_InvalidDeviceID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/invalid-uuid/catalog", nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should return 400 for invalid UUID assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("GetDeviceCatalog_ValidDevice", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) // Note: Device registration requires different endpoint // For now, test with a valid UUID format deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/catalog", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should return either 200 (OK with empty catalog) or 404 (device not found) assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound) }) t.Run("SearchDeviceCatalog_InvalidDeviceID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/invalid-uuid/search?query=test", nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("SearchDeviceCatalog_ValidDevice", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/search?query=test", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should return 200 or 404 assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound) }) t.Run("GetDeviceNavigation_InvalidDeviceID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/invalid-uuid/nav", nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("GetDeviceNavigation_ValidDevice", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/nav", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should return navigation or 404 assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound) }) t.Run("DownloadBook_InvalidDeviceID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() bookID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/invalid-uuid/download/"+bookID.String(), nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("DownloadBook_InvalidBookID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/download/invalid-uuid", nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("DownloadBook_ValidIDs", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() bookID := createTestEbookID(t, ts, token) httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/download/"+bookID, nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // May return 404 if device/book not linked, or 500 for file not found // Should not return 400 (invalid IDs) assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("GetCoverImage_InvalidDeviceID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() bookID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/invalid-uuid/cover/"+bookID.String(), nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("GetCoverImage_InvalidBookID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/cover/invalid-uuid", nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("GetCoverImage_ValidIDs", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() bookID := createTestEbookID(t, ts, token) httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/cover/"+bookID, nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // May return 404 if no cover, but not 400 assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("ListFormats_InvalidDeviceID", func(t *testing.T) { ts, _, _, _ := setupTestServer(t) defer ts.Close() bookID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/invalid-uuid/formats/"+bookID.String(), nil) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("ListFormats_ValidDeviceID", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() bookID := createTestEbookID(t, ts, token) httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/formats/"+bookID, nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should return formats list or 404 assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound) }) } // TestOPDSConversion tests on-the-fly conversion for downloads func TestOPDSConversion(t *testing.T) { t.Run("DownloadKEPUB_FormatParameter", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() bookID := createTestEbookID(t, ts, token) // Request KEPUB format httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/download/"+bookID+"?format=kepub", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should attempt conversion (may fail if file doesn't exist) // Important: Should not return 400 for invalid IDs assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("DownloadEPUB_DefaultFormat", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() bookID := createTestEbookID(t, ts, token) // Request default format (no format parameter) httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/download/"+bookID, nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should attempt to download original format assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode) }) t.Run("Download_UnsupportedFormat", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() bookID := createTestEbookID(t, ts, token) // Request unsupported format httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/download/"+bookID+"?format=pdf", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should handle gracefully (either 400 for unsupported format or 404/500) assert.True(t, resp.StatusCode >= 400 && resp.StatusCode < 600) }) } // TestOPDSEdgeCases tests edge cases for OPDS endpoints func TestOPDSEdgeCases(t *testing.T) { t.Run("Catalog_EmptyLibrary", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/catalog", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should return empty catalog, not error assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound) }) t.Run("Search_SpecialCharacters", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() // Search with special characters httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/search?query=test%20%26%20more", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should handle special characters assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound) }) t.Run("Search_EmptyQuery", func(t *testing.T) { ts, db, _, _ := setupTestServer(t) defer ts.Close() token := loginTestUser(t, ts, db) deviceID := uuid.New() httpReq, _ := http.NewRequest("GET", ts.URL+"/opds/devices/"+deviceID.String()+"/search?query=", nil) httpReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(httpReq) require.NoError(t, err) defer resp.Body.Close() // Should handle empty query assert.True(t, resp.StatusCode >= 200 && resp.StatusCode < 500) }) }