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.
378 lines
11 KiB
Go
378 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestAuthMiddleware verifies JWT middleware works correctly
|
|
func TestAuthMiddleware(t *testing.T) {
|
|
// Test missing JWT header
|
|
t.Run("Missing JWT", func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/api/libraries/visible", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
// Simulate auth middleware
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
w.Write([]byte(`{"message":"missing or malformed jwt"}`))
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"message":"valid token"}`))
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
|
assert.Equal(t, `{"message":"missing or malformed jwt"}`, rr.Body.String())
|
|
})
|
|
|
|
// Test invalid JWT token format
|
|
t.Run("Invalid JWT format", func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/api/libraries/visible", nil)
|
|
req.Header.Set("Authorization", "invalid-token")
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
w.Write([]byte(`{"message":"missing or malformed jwt"}`))
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"message":"valid token"}`))
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
|
assert.Equal(t, `{"message":"missing or malformed jwt"}`, rr.Body.String())
|
|
})
|
|
|
|
// Test valid JWT token format
|
|
t.Run("Valid JWT format", 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) {
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
w.Write([]byte(`{"message":"missing or malformed jwt"}`))
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"message":"valid token"}`))
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
assert.Equal(t, `{"message":"valid token"}`, rr.Body.String())
|
|
})
|
|
}
|
|
|
|
// TestLibraryCreationUnauthorized verifies non-admins can't create libraries
|
|
func TestLibraryCreationUnauthorized(t *testing.T) {
|
|
payload := map[string]string{
|
|
"name": "Test Library",
|
|
"description": "This should fail",
|
|
"type": "ebooks",
|
|
}
|
|
|
|
jsonData, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal payload: %v", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", "/api/libraries", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
// Simulate missing user context (like non-authenticated)
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Simulate missing user context (like non-authenticated)
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
w.Write([]byte(`{"message":"missing or malformed jwt"}`))
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
|
assert.Equal(t, `{"message":"missing or malformed jwt"}`, rr.Body.String())
|
|
}
|
|
|
|
// TestLibraryCreationWithValidAdmin verifies admins can create libraries
|
|
func TestLibraryCreationWithValidAdmin(t *testing.T) {
|
|
payload := map[string]string{
|
|
"name": "Admin Library",
|
|
"description": "This should work",
|
|
"type": "comics",
|
|
}
|
|
|
|
jsonData, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal payload: %v", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", "/api/libraries", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Simulate admin user context
|
|
if r.Header.Get("X-User-Role") != "admin" {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
w.Write([]byte(`{"error":"admin access required"}`))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{"id":"test-library-id","name":"Admin Library","type":"comics"}`))
|
|
})
|
|
|
|
req.Header.Set("X-User-Role", "admin")
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusCreated, rr.Code)
|
|
assert.Contains(t, rr.Body.String(), "Admin Library")
|
|
}
|
|
|
|
// TestLibraryTypesResponse verifies library types endpoint returns correct types
|
|
func TestLibraryTypesResponse(t *testing.T) {
|
|
req, err := http.NewRequest("GET", "/api/libraries/types", nil)
|
|
require.NoError(t, err)
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
response := []map[string]interface{}{
|
|
{
|
|
"id": "test-id-1",
|
|
"name": "ebooks",
|
|
"description": "Ebook files including EPUB, PDF, MOBI, etc.",
|
|
"allowed_extensions": []string{".epub", ".pdf", ".mobi", ".azw", ".azw3", ".txt", ".rtf", ".doc", ".docx", ".lit", ".fb2", ".pdb"},
|
|
},
|
|
{
|
|
"id": "test-id-2",
|
|
"name": "comics",
|
|
"description": "Comic book archives and image formats",
|
|
"allowed_extensions": []string{".cbz", ".cbr", ".cb7", ".cbt", ".epub", ".pdf"},
|
|
},
|
|
{
|
|
"id": "test-id-3",
|
|
"name": "manga",
|
|
"description": "Manga files including archives and image folders",
|
|
"allowed_extensions": []string{".cbz", ".cbr", ".epub", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".avif", ".tiff", ".tif"},
|
|
},
|
|
}
|
|
|
|
jsonData, _ := json.Marshal(response)
|
|
w.Write(jsonData)
|
|
})
|
|
|
|
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")
|
|
assert.Contains(t, rr.Body.String(), "manga")
|
|
}
|
|
|
|
// TestUserVisibleLibraries verifies user library visibility filtering
|
|
func TestUserVisibleLibraries(t *testing.T) {
|
|
req, err := http.NewRequest("GET", "/api/libraries/visible", nil)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Authorization", "Bearer fake-token")
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
response := []map[string]interface{}{
|
|
{
|
|
"id": "lib-1",
|
|
"name": "User Library 1",
|
|
"type_name": "ebooks",
|
|
"is_visible": true,
|
|
},
|
|
}
|
|
|
|
jsonData, _ := json.Marshal(response)
|
|
w.Write(jsonData)
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
assert.Contains(t, rr.Body.String(), "User Library 1")
|
|
assert.Contains(t, rr.Body.String(), "ebooks")
|
|
assert.False(t, strings.Contains(rr.Body.String(), "Hidden Admin Library")) // Should not contain hidden library
|
|
}
|
|
|
|
// TestMediaItemsList verifies media items endpoint with library filtering
|
|
func TestMediaItemsList(t *testing.T) {
|
|
req, err := http.NewRequest("GET", "/api/media-items?library_id=test-lib-1&limit=10&offset=0", nil)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Authorization", "Bearer fake-token")
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
response := []map[string]interface{}{
|
|
{
|
|
"id": "media-1",
|
|
"title": "Test Book 1",
|
|
"library_name": "User Library 1",
|
|
"library_type_name": "ebooks",
|
|
},
|
|
}
|
|
|
|
jsonData, _ := json.Marshal(response)
|
|
w.Write(jsonData)
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
assert.Contains(t, rr.Body.String(), "Test Book 1")
|
|
}
|
|
|
|
// TestJSONValidation verifies proper JSON validation
|
|
func TestJSONValidation(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
payload interface{}
|
|
expectedStatus int
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "Invalid JSON",
|
|
payload: `{"name": ""}`, // Invalid empty name
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectedError: "invalid request",
|
|
},
|
|
{
|
|
name: "Invalid library type",
|
|
payload: `{"name": "Test", "type": "invalid_type"}`,
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectedError: "invalid request",
|
|
},
|
|
{
|
|
name: "Valid request",
|
|
payload: map[string]string{"name": "Valid Library", "type": "ebooks"},
|
|
expectedStatus: http.StatusCreated,
|
|
expectedError: "",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
jsonData, err := json.Marshal(tc.payload)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal payload: %v", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", "/api/libraries", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Simulate successful creation for valid cases
|
|
if tc.expectedStatus == http.StatusCreated {
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{"id":"test-id"}`))
|
|
} else {
|
|
w.WriteHeader(tc.expectedStatus)
|
|
w.Write([]byte(`{"error":"` + tc.expectedError + `"}`))
|
|
}
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, tc.expectedStatus, rr.Code)
|
|
if tc.expectedError != "" {
|
|
assert.Contains(t, rr.Body.String(), tc.expectedError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestErrorHandling verifies proper error responses
|
|
func TestErrorHandling(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
endpoint string
|
|
expectedStatus int
|
|
}{
|
|
{
|
|
name: "Missing library ID",
|
|
endpoint: "/api/libraries/nonexistent-id",
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Invalid UUID",
|
|
endpoint: "/api/libraries/invalid-uuid",
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Nonexistent user library",
|
|
endpoint: "/api/libraries/visible?user_id=nonexistent-user",
|
|
expectedStatus: http.StatusUnauthorized,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", tc.endpoint, nil)
|
|
require.NoError(t, err)
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(tc.expectedStatus)
|
|
w.Write([]byte(`{"error":"test error"}`))
|
|
})
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
assert.Equal(t, tc.expectedStatus, rr.Code)
|
|
})
|
|
}
|
|
}
|