package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) // TestScannerEndpoints tests ebook scanner operations func TestScannerEndpoints(t *testing.T) { t.Run("POST /api/scanner/scan - Scan without admin role", func(t *testing.T) { payload := map[string]interface{}{ "folder_paths": []string{"/path/to/ebooks"}, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/scanner/scan", 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("POST /api/scanner/watch/start - Start watch mode", func(t *testing.T) { libraryID := uuid.New().String() payload := map[string]interface{}{ "library_id": libraryID, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/scanner/watch/start", 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.StatusOK) response := map[string]interface{}{ "message": "watch mode started for library", "library_id": libraryID, } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response map[string]interface{} json.Unmarshal(rr.Body.Bytes(), &response) assert.Equal(t, "watch mode started for library", response["message"]) assert.Equal(t, libraryID, response["library_id"]) }) t.Run("POST /api/scanner/watch/stop - Stop watch mode", func(t *testing.T) { libraryID := uuid.New().String() payload := map[string]interface{}{ "library_id": libraryID, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/scanner/watch/stop", 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.StatusOK) response := map[string]interface{}{ "message": "watch mode stopped for library", "library_id": libraryID, } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response map[string]interface{} json.Unmarshal(rr.Body.Bytes(), &response) assert.Equal(t, "watch mode stopped for library", response["message"]) assert.Equal(t, libraryID, response["library_id"]) }) t.Run("GET /api/scanner/watch/status - Get watch mode status", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/scanner/watch/status", 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.StatusOK) response := map[string]interface{}{ "watching_libraries": []string{ uuid.New().String(), uuid.New().String(), }, "total_watching": 2, } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response map[string]interface{} json.Unmarshal(rr.Body.Bytes(), &response) assert.Equal(t, float64(2), response["total_watching"]) assert.NotEmpty(t, response["watching_libraries"]) }) t.Run("GET /api/scanner/status/:jobId - Get job status", func(t *testing.T) { jobID := uuid.New().String() req := httptest.NewRequest("GET", "/api/scanner/status/"+jobID, 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.StatusOK) response := map[string]interface{}{ "job_id": jobID, "status": "completed", "error": "", "result": map[string]string{"message": "scan completed"}, "progress": 1.0, } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response map[string]interface{} json.Unmarshal(rr.Body.Bytes(), &response) assert.Equal(t, jobID, response["job_id"]) assert.Equal(t, "completed", response["status"]) }) t.Run("GET /api/scanner/status/:jobId - Job not found", func(t *testing.T) { jobID := uuid.New().String() req := httptest.NewRequest("GET", "/api/scanner/status/"+jobID, 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.StatusNotFound) w.Write([]byte(`{"error":"job not found"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) }) t.Run("POST /api/scanner/scan - Successful scan (background job)", func(t *testing.T) { payload := map[string]interface{}{ "folder_paths": []string{"/valid/path"}, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/scanner/scan", 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.StatusAccepted) response := map[string]interface{}{ "message": "scan job enqueued", "job_id": uuid.New().String(), "status": "pending", } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusAccepted, rr.Code) var response map[string]interface{} json.Unmarshal(rr.Body.Bytes(), &response) assert.Equal(t, "scan job enqueued", response["message"]) assert.NotEmpty(t, response["job_id"]) assert.Equal(t, "pending", response["status"]) }) t.Run("POST /api/scanner/start - Start scanner without admin role", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/scanner/start", 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("POST /api/scanner/start - Start scanner successfully", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/scanner/start", 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.StatusOK) w.Write([]byte(`{"message":"scanner started"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) t.Run("POST /api/scanner/stop - Stop scanner without admin role", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/scanner/stop", 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("POST /api/scanner/stop - Stop scanner successfully", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/scanner/stop", 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.StatusOK) w.Write([]byte(`{"message":"scanner stopped"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) } // TestEdgeCases tests various edge cases and boundary conditions func TestEdgeCases(t *testing.T) { t.Run("Empty request body", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/auth/login", bytes.NewBuffer([]byte(""))) req.Header.Set("Content-Type", "application/json") 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 } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("Malformed JSON", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/auth/login", bytes.NewBuffer([]byte("{invalid json}"))) req.Header.Set("Content-Type", "application/json") 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 } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("Very large payload", func(t *testing.T) { largeString := string(make([]byte, 100000)) payload := map[string]interface{}{ "email": largeString + "@example.com", "username": "user", "password": "password123", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"email too long"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("SQL Injection attempt", func(t *testing.T) { payload := map[string]interface{}{ "email": "test@example.com'; DROP TABLE users; --", "username": "sqlinjection", "password": "password123", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") 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 } email := req["email"].(string) if len(email) > 255 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"email too long"}`)) return } w.WriteHeader(http.StatusCreated) }) handler.ServeHTTP(rr, req) // Should either succeed (if sanitized) or fail with validation error assert.True(t, rr.Code == http.StatusCreated || rr.Code == http.StatusBadRequest) }) t.Run("XSS attempt in fields", func(t *testing.T) { payload := map[string]interface{}{ "email": "test@example.com", "username": "", "password": "password123", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") 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 } username := req["username"].(string) if len(username) > 50 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"username too long"}`)) return } w.WriteHeader(http.StatusCreated) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusCreated, rr.Code) }) t.Run("Rate limiting simulation", func(t *testing.T) { requestCount := 0 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestCount++ if requestCount > 10 { w.WriteHeader(http.StatusTooManyRequests) w.Write([]byte(`{"error":"too many requests"}`)) return } w.WriteHeader(http.StatusOK) }) for i := 0; i < 15; i++ { req := httptest.NewRequest("GET", "/api/libraries/types", nil) rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) if i >= 10 { assert.Equal(t, http.StatusTooManyRequests, rr.Code) } else { assert.Equal(t, http.StatusOK, rr.Code) } } }) } // TestHTMXRequests tests HTMX-specific responses func TestHTMXRequests(t *testing.T) { t.Run("Registration with HTMX header", func(t *testing.T) { payload := map[string]interface{}{ "email": "htmx@example.com", "username": "htmxuser", "password": "password123", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") req.Header.Set("HX-Request", "true") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { isHTMX := r.Header.Get("HX-Request") == "true" w.WriteHeader(http.StatusCreated) if isHTMX { w.Write([]byte(`