The pagination tests were incorrectly parsing the API response. The API
returns data wrapped in a {"data": [...]} structure, but the tests were
expecting a direct array. This caused tests to fail silently when
json.Decode couldn't match the response structure.
Changed response parsing to correctly extract the "data" field before
asserting on array length.
520 lines
15 KiB
Go
520 lines
15 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// createTestLibrary creates a test library and returns its ID
|
|
func createTestLibrary(t *testing.T, ts *httptest.Server, token, name string) string {
|
|
t.Helper()
|
|
|
|
payload := map[string]interface{}{
|
|
"name": name,
|
|
"description": "Test library for ISBN tests",
|
|
"type": "ebooks",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", ts.URL+"/api/libraries", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
require.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
|
|
var result map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&result)
|
|
|
|
return result["id"].(string)
|
|
}
|
|
|
|
// addFolderToLibrary adds a folder to a test library
|
|
func addFolderToLibrary(t *testing.T, ts *httptest.Server, token, libraryID, folderPath string) {
|
|
t.Helper()
|
|
|
|
payload := map[string]interface{}{
|
|
"folder_path": folderPath,
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", ts.URL+"/api/libraries/"+libraryID+"/folders", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
require.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
}
|
|
|
|
// TestMediaItemISBNNormalization tests ISBN normalization with media-items endpoint
|
|
func TestMediaItemISBNNormalization(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
// Create an ebook library first
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
libID := createTestLibrary(t, setup.Server, token, "test-isbn-lib")
|
|
|
|
// Add a folder to the library (required before adding media items)
|
|
addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads")
|
|
|
|
// Test ISBN normalization cases
|
|
testCases := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "ISBN-13 with hyphens (978-0-12345-678-9)",
|
|
input: "978-0-12345-678-9",
|
|
expected: "9780123456789",
|
|
},
|
|
{
|
|
name: "ISBN-13 with single hyphen (978-0123456789)",
|
|
input: "978-0123456789",
|
|
expected: "9780123456789",
|
|
},
|
|
{
|
|
name: "ISBN-13 without hyphens",
|
|
input: "9780123456789",
|
|
expected: "9780123456789",
|
|
},
|
|
{
|
|
name: "ISBN-13 with spaces",
|
|
input: "978 0123456789",
|
|
expected: "9780123456789",
|
|
},
|
|
{
|
|
name: "ISBN-13 with mixed hyphens and spaces",
|
|
input: "978-0 1234-56789",
|
|
expected: "9780123456789",
|
|
},
|
|
{
|
|
name: "ISBN-10 with hyphens",
|
|
input: "0-12345-678-9",
|
|
expected: "9780123456786",
|
|
},
|
|
{
|
|
name: "ISBN-10 without hyphens",
|
|
input: "0123456789",
|
|
expected: "9780123456786",
|
|
},
|
|
{
|
|
name: "ISBN-10 with X",
|
|
input: "0-12345-678-X",
|
|
expected: "9780123456786",
|
|
},
|
|
{
|
|
name: "ISBN-10 converts to ISBN-13",
|
|
input: "0-306-40615-2",
|
|
expected: "9780306406157",
|
|
},
|
|
{
|
|
name: "ISBN-13 with trailing hyphen",
|
|
input: "978030640615-7-",
|
|
expected: "9780306406157",
|
|
},
|
|
{
|
|
name: "ISBN-10 with trailing hyphen",
|
|
input: "0306406152-",
|
|
expected: "9780306406157",
|
|
},
|
|
{
|
|
name: "only hyphens converts to empty string",
|
|
input: "---",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "only spaces converts to empty string",
|
|
input: " ",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "ISBN-10 with X converts to ISBN-13",
|
|
input: "0-596-00965-X",
|
|
expected: "9780596009656",
|
|
},
|
|
{
|
|
name: "ISBN-13 with leading/trailing hyphens",
|
|
input: "-978-0-306-40615-7-",
|
|
expected: "9780306406157",
|
|
},
|
|
{
|
|
name: "ISBN-13 preserves case",
|
|
input: "978-0-306-40615-7",
|
|
expected: "9780306406157",
|
|
},
|
|
{
|
|
name: "ISBN-13 lowercase preserved",
|
|
input: "978-0-306-40615-7",
|
|
expected: "9780306406157",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
payload := map[string]interface{}{
|
|
"title": fmt.Sprintf("Test Book %s", tc.name),
|
|
"isbn": tc.input,
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Check if this is an invalid ISBN case that should return 422
|
|
if tc.expected == "" && (tc.input == "---" || tc.input == " ") {
|
|
assert.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
|
|
} else {
|
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
}
|
|
|
|
var response map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&response)
|
|
|
|
// For valid ISBN responses, verify normalization worked correctly
|
|
if resp.StatusCode == http.StatusCreated {
|
|
assert.Equal(t, tc.expected, response["isbn"])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMediaItemISBNEdgeCases tests ISBN edge cases
|
|
func TestMediaItemISBNEdgeCases(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
libID := createTestLibrary(t, setup.Server, token, "test-isbn-edge-lib")
|
|
addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads")
|
|
|
|
t.Run("Empty ISBN should be accepted", func(t *testing.T) {
|
|
payload := map[string]interface{}{
|
|
"title": "Book without ISBN",
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("ISBN with multiple hyphens", func(t *testing.T) {
|
|
payload := map[string]interface{}{
|
|
"title": "Multi-Hyphen ISBN",
|
|
"isbn": "978-0-306-40615-7",
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
var response map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&response)
|
|
|
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
assert.Equal(t, "9780306406157", response["isbn"])
|
|
})
|
|
|
|
t.Run("ISBN with trailing hyphen", func(t *testing.T) {
|
|
payload := map[string]interface{}{
|
|
"title": "Trailing Hyphen ISBN",
|
|
"isbn": "978-0-596-00965-2",
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
var response map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&response)
|
|
|
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
assert.Equal(t, "9780596009652", response["isbn"])
|
|
})
|
|
}
|
|
|
|
// TestMediaItemsPagination tests pagination with media-items endpoint
|
|
func TestMediaItemsPagination(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
libID := createTestLibrary(t, setup.Server, token, "test-pagination-lib")
|
|
addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads")
|
|
|
|
// Create some test media items
|
|
for i := 1; i <= 5; i++ {
|
|
payload := map[string]interface{}{
|
|
"title": fmt.Sprintf("Book %d", i),
|
|
"isbn": fmt.Sprintf("978030640615%d", i),
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
resp.Body.Close()
|
|
}
|
|
|
|
// Small delay to allow database to commit before pagination queries
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
t.Run("Valid pagination parameters", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=2&offset=0", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var response map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&response)
|
|
data := response["data"].([]interface{})
|
|
|
|
// Should get 2 items
|
|
assert.Equal(t, 2, len(data))
|
|
})
|
|
|
|
t.Run("Pagination with offset", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=2&offset=2", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var response map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&response)
|
|
data := response["data"].([]interface{})
|
|
|
|
// Should get 2 items starting from offset 2
|
|
assert.Equal(t, 2, len(data))
|
|
})
|
|
|
|
t.Run("Negative limit should fail", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=-10&offset=0", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should return 400 Bad Request or handle it gracefully
|
|
assert.NotEqual(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("Negative offset should fail", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=10&offset=-5", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should return 400 Bad Request or handle it gracefully
|
|
assert.NotEqual(t, http.StatusOK, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("Limit exceeds maximum", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=10000&offset=0", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should be capped at maximum or return error
|
|
// The application uses maxPaginationLimit = 1000
|
|
assert.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
|
|
})
|
|
}
|
|
|
|
// TestMediaItemLibraryRequirement tests that media items require a library
|
|
func TestMediaItemLibraryRequirement(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
|
|
t.Run("Create media-item without library should fail gracefully", func(t *testing.T) {
|
|
payload := map[string]interface{}{
|
|
"title": "Orphan Book",
|
|
"isbn": "9780123456789",
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Should fail - library_id is required
|
|
assert.NotEqual(t, http.StatusCreated, resp.StatusCode)
|
|
})
|
|
|
|
t.Run("Create media-item with existing library should succeed", func(t *testing.T) {
|
|
libID := createTestLibrary(t, setup.Server, token, "test-req-lib")
|
|
addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads")
|
|
|
|
payload := map[string]interface{}{
|
|
"title": "Valid Book",
|
|
"isbn": "978-0-306-40615-7",
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
|
|
var response map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&response)
|
|
|
|
// VerifyISBN was normalized
|
|
assert.Equal(t, "9780306406157", response["isbn"])
|
|
assert.Equal(t, libID, response["library_id"])
|
|
})
|
|
}
|
|
|
|
// TestUpdateMediaItemISBN tests updating media-item ISBN
|
|
func TestUpdateMediaItemISBN(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
libID := createTestLibrary(t, setup.Server, token, "test-update-lib")
|
|
addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads")
|
|
|
|
// First create a media item
|
|
createPayload := map[string]interface{}{
|
|
"title": "Original Title",
|
|
"isbn": "978-0-596-00965-2",
|
|
"library_id": libID,
|
|
"file_path": "/test/path.epub",
|
|
"file_size": 1024,
|
|
"mime_type": "application/epub+zip",
|
|
}
|
|
|
|
body, _ := json.Marshal(createPayload)
|
|
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 "+token)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
var createResponse map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&createResponse)
|
|
resp.Body.Close()
|
|
|
|
mediaItemID := createResponse["id"].(string)
|
|
|
|
// Now update with new ISBN
|
|
t.Run("Update with invalid ISBN rejects", func(t *testing.T) {
|
|
updatePayload := map[string]interface{}{
|
|
"title": "Updated Title",
|
|
"isbn": "978-9876543210-9",
|
|
}
|
|
|
|
body, _ := json.Marshal(updatePayload)
|
|
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/media-items/"+mediaItemID, bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
|
|
})
|
|
}
|