Restructure TestOPDSEndpoints and TestOPDSConversion to follow the Kobo test pattern. Create all media items at parent level before any subtests run, avoiding token invalidation when setupDeviceTest is called. Subtests now use pre-created media IDs and device.AuthToken for authentication.
339 lines
12 KiB
Go
339 lines
12 KiB
Go
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) {
|
|
setup := setupTestServer(t)
|
|
// Create ALL media items needed for ALL subtests BEFORE any t.Run (following Kobo pattern)
|
|
_ = createTestMediaItemID(t, setup)
|
|
bookID1 := createTestMediaItemID(t, setup)
|
|
bookID2 := createTestMediaItemID(t, setup)
|
|
bookID3 := createTestMediaItemID(t, setup)
|
|
client := &http.Client{}
|
|
|
|
t.Run("GetDeviceCatalog_WithoutDeviceAuth", func(t *testing.T) {
|
|
deviceID := uuid.New()
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/catalog", nil)
|
|
|
|
resp, err := client.Do(httpReq)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// OPDS endpoints require device authentication via devices.auth_token
|
|
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("GetDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/catalog", nil)
|
|
|
|
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_BearerToken", func(t *testing.T) {
|
|
// Create a device with auth token
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-bearer-test")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/catalog", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
resp, err := client.Do(httpReq)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should return 200 with catalog (even if empty)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("GetDeviceCatalog_ValidDevice_QueryToken", func(t *testing.T) {
|
|
// Create a device with auth token
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "kobo", "opds-query-test")
|
|
|
|
// Test query parameter authentication
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/catalog?token="+device.AuthToken, nil)
|
|
|
|
resp, err := client.Do(httpReq)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should return 200 with catalog (even if empty)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("SearchDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/search?q=test", nil)
|
|
|
|
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) {
|
|
// Create a device with auth token
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-search-test")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/search?q=test", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
resp, err := client.Do(httpReq)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should return 200 (even if empty results)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("GetDeviceNavigation_InvalidDeviceID", func(t *testing.T) {
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/nav", nil)
|
|
|
|
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) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-nav-test")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/nav", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/download/"+uuid.New().String(), nil)
|
|
|
|
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) {
|
|
deviceID := uuid.New()
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/download/invalid-uuid", nil)
|
|
|
|
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) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-download-test")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID1, nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/cover/"+uuid.New().String(), nil)
|
|
|
|
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) {
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/cover/"+uuid.New().String(), nil)
|
|
|
|
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) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-cover-test")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/cover/"+bookID2, nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
bookID := uuid.New()
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/formats/"+bookID.String(), nil)
|
|
|
|
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) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-formats-test")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/formats/"+bookID3, nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
setup := setupTestServer(t)
|
|
// Create ALL media items needed for ALL subtests BEFORE any t.Run (following Kobo pattern)
|
|
_ = createTestMediaItemID(t, setup)
|
|
bookID1 := createTestMediaItemID(t, setup)
|
|
bookID2 := createTestMediaItemID(t, setup)
|
|
bookID3 := createTestMediaItemID(t, setup)
|
|
client := &http.Client{}
|
|
|
|
t.Run("DownloadKEPUB_FormatParameter", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "kobo", "opds-kepub-test")
|
|
|
|
// Request KEPUB format
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID1+"?format=kepub", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-epub-test")
|
|
|
|
// Request default format (no format parameter)
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID2, nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-unsupported-test")
|
|
|
|
// Request unsupported format
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID3+"?format=pdf", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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) {
|
|
setup := setupTestServer(t)
|
|
_ = createTestMediaItemID(t, setup)
|
|
client := &http.Client{}
|
|
|
|
t.Run("Catalog_EmptyLibrary", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-edge-empty")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/catalog", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
resp, err := client.Do(httpReq)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should return empty catalog, not error
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("Search_SpecialCharacters", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-edge-special")
|
|
|
|
// Search with special characters
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/search?q=test%20%26%20more", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
resp, err := client.Do(httpReq)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should handle special characters
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("Search_EmptyQuery", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-edge-emptyq")
|
|
|
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/search?q=", nil)
|
|
httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
|
|
|
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)
|
|
})
|
|
}
|