Files
bookhoard/cmd/server/tests/library_test_comprehensive_test.go
john-okeefe ff0517d038 fix(library): sync allowed extensions across service, schema, and tests
Add .epub and .pdf to comics type, add .pdf to manga type, and ensure
avif/tiff/tif extensions are consistently included in manga across all
layers. Update test fixtures to match the canonical extension lists.
2026-05-01 14:31:17 -04:00

572 lines
18 KiB
Go

package main
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
// TestLibraryManagementEndpoints tests library management operations
func TestLibraryManagementEndpoints(t *testing.T) {
libraryID := uuid.New()
t.Run("POST /api/libraries - Create library without admin role", func(t *testing.T) {
payload := map[string]interface{}{
"name": "New Library",
"description": "Test description",
"type": "ebooks",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusCreated)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
t.Run("POST /api/libraries - Create library with invalid type", func(t *testing.T) {
payload := map[string]interface{}{
"name": "New Library",
"description": "Test description",
"type": "invalid-type",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid request"}`))
return
}
libType := req["type"].(string)
if libType != "ebooks" && libType != "comics" && libType != "manga" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid library type"}`))
return
}
w.WriteHeader(http.StatusCreated)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("POST /api/libraries - Create library with missing required fields", func(t *testing.T) {
payload := map[string]interface{}{
"description": "Test description",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid request"}`))
return
}
if _, ok := req["name"]; !ok {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"name is required"}`))
return
}
if _, ok := req["type"]; !ok {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"type is required"}`))
return
}
w.WriteHeader(http.StatusCreated)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("GET /api/libraries/:id - Get library with invalid UUID", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/invalid-uuid", nil)
req.Header.Set("Authorization", "Bearer admin-token")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid library id"}`))
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("GET /api/libraries/:id - Get non-existent library", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/"+uuid.New().String(), nil)
req.Header.Set("Authorization", "Bearer admin-token")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"error":"library not found"}`))
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusNotFound, rr.Code)
})
t.Run("PUT /api/libraries/:id - Update library without admin role", func(t *testing.T) {
payload := map[string]interface{}{
"name": "Updated Library",
"description": "Updated description",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("PUT", "/api/libraries/"+libraryID.String(), bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
t.Run("DELETE /api/libraries/:id - Delete library without admin role", func(t *testing.T) {
req := httptest.NewRequest("DELETE", "/api/libraries/"+libraryID.String(), nil)
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusNoContent)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
t.Run("DELETE /api/libraries/:id - Delete library with invalid UUID", func(t *testing.T) {
req := httptest.NewRequest("DELETE", "/api/libraries/invalid-uuid", nil)
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid library id"}`))
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
}
// TestLibraryFolders tests library folder management
func TestLibraryFolders(t *testing.T) {
libraryID := uuid.New()
t.Run("POST /api/libraries/:id/folders - Add folder without admin role", func(t *testing.T) {
payload := map[string]interface{}{
"folder_path": "/path/to/folder",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries/"+libraryID.String()+"/folders", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusCreated)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
t.Run("POST /api/libraries/:id/folders - Add folder with invalid library ID", func(t *testing.T) {
payload := map[string]interface{}{
"folder_path": "/path/to/folder",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries/invalid-uuid/folders", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid library id"}`))
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("POST /api/libraries/:id/folders - Add folder with missing path", func(t *testing.T) {
payload := map[string]interface{}{}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries/"+libraryID.String()+"/folders", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid request"}`))
return
}
if _, ok := req["folder_path"]; !ok {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"folder_path is required"}`))
return
}
w.WriteHeader(http.StatusCreated)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("GET /api/libraries/:id/folders - Get folders without admin role", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/"+libraryID.String()+"/folders", nil)
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
t.Run("DELETE /api/libraries/:id/folders - Delete folder without admin role", func(t *testing.T) {
payload := map[string]interface{}{
"folder_path": "/path/to/folder",
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("DELETE", "/api/libraries/"+libraryID.String()+"/folders", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusNoContent)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
}
// TestLibraryVisibility tests library visibility controls
func TestLibraryVisibility(t *testing.T) {
libraryID := uuid.New()
userID := uuid.New()
t.Run("POST /api/libraries/visibility - Set visibility without auth", func(t *testing.T) {
payload := map[string]interface{}{
"library_id": libraryID.String(),
"is_visible": true,
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries/visibility", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message":"missing or malformed jwt"}`))
return
}
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
t.Run("POST /api/libraries/visibility - Set visibility with invalid library ID", func(t *testing.T) {
payload := map[string]interface{}{
"library_id": "invalid-uuid",
"is_visible": true,
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries/visibility", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer valid-token")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid request"}`))
return
}
libID := req["library_id"].(string)
if _, err := uuid.Parse(libID); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid library id"}`))
return
}
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("POST /api/libraries/visibility - Set visibility successfully", func(t *testing.T) {
payload := map[string]interface{}{
"library_id": libraryID.String(),
"is_visible": true,
}
jsonData, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/libraries/visibility", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer valid-token")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
visibility := map[string]interface{}{
"id": uuid.New().String(),
"user_id": userID.String(),
"library_id": libraryID.String(),
"is_visible": true,
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(visibility)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
})
t.Run("GET /api/libraries/visible - Get visible libraries without auth", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/visible", nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message":"missing or malformed jwt"}`))
return
}
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
t.Run("GET /api/libraries/visible - Get visible libraries with auth", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/visible", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
libraries := []map[string]interface{}{
{
"id": libraryID.String(),
"name": "Visible Library",
"is_visible": true,
},
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(libraries)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
})
}
// TestLibraryStats tests library statistics
func TestLibraryStats(t *testing.T) {
libraryID := uuid.New()
t.Run("GET /api/libraries/:id/stats - Get stats without admin role", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/"+libraryID.String()+"/stats", nil)
req.Header.Set("Authorization", "Bearer user-token")
req.Header.Set("X-User-Role", "user")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userRole := r.Header.Get("X-User-Role")
if userRole != "admin" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"admin access required"}`))
return
}
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
t.Run("GET /api/libraries/:id/stats - Get stats with invalid library ID", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/invalid-uuid/stats", nil)
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid library id"}`))
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("GET /api/libraries/:id/stats - Get stats successfully", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/"+libraryID.String()+"/stats", nil)
req.Header.Set("Authorization", "Bearer admin-token")
req.Header.Set("X-User-Role", "admin")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
stats := map[string]interface{}{
"media_count": 42,
"total_size": 1024000,
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(stats)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
})
}
// TestLibraryTypes tests library type retrieval
func TestLibraryTypes(t *testing.T) {
t.Run("GET /api/libraries/types - Get all library types", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/libraries/types", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
types := []map[string]interface{}{
{
"id": uuid.New().String(),
"name": "ebooks",
"description": "Ebook files including EPUB, PDF, MOBI, etc.",
"allowed_extensions": []string{".epub", ".pdf", ".mobi"},
},
{
"id": uuid.New().String(),
"name": "comics",
"description": "Comic book archives and image formats",
"allowed_extensions": []string{".cbz", ".cbr", ".cb7", ".cbt", ".epub", ".pdf"},
},
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(types)
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Contains(t, rr.Body.String(), "ebooks")
assert.Contains(t, rr.Body.String(), "comics")
})
}