package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) // TestUserProfileEndpoints tests user profile management endpoints func TestUserProfileEndpoints(t *testing.T) { userID := uuid.New() t.Run("GET /api/auth/profile - Get profile without auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/auth/profile", nil) rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" || !containsPrefix(authHeader, "Bearer ") { 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/auth/profile - Get profile with valid auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/auth/profile", nil) req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { profile := map[string]interface{}{ "id": userID.String(), "email": "user@example.com", "username": "testuser", "role": "user", } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(profile) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) t.Run("PUT /api/auth/profile - Update profile with valid data", func(t *testing.T) { payload := map[string]interface{}{ "first_name": "Updated", "last_name": "Name", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/profile", 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) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message":"profile updated"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) } // TestUserUpdateEndpoints tests user field update endpoints func TestUserUpdateEndpoints(t *testing.T) { t.Run("PUT /api/auth/email - Update email to existing email", func(t *testing.T) { payload := map[string]interface{}{ "email": "existing@example.com", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/email", 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 } email := req["email"].(string) if email == "existing@example.com" { w.WriteHeader(http.StatusConflict) w.Write([]byte(`{"error":"email already taken"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusConflict, rr.Code) }) t.Run("PUT /api/auth/email - Update email with invalid format", func(t *testing.T) { payload := map[string]interface{}{ "email": "invalid-email", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/email", 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 } email, _ := req["email"].(string) if !contains(email, "@") || !contains(email, ".") { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"email is invalid"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("PUT /api/auth/email - Update email with empty value", func(t *testing.T) { payload := map[string]interface{}{ "email": "", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/email", 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) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"email is required"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("PUT /api/auth/username - Update username to existing username", func(t *testing.T) { payload := map[string]interface{}{ "username": "existinguser", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/username", 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 } username := req["username"].(string) if username == "existinguser" { w.WriteHeader(http.StatusConflict) w.Write([]byte(`{"error":"username already taken"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusConflict, rr.Code) }) t.Run("PUT /api/auth/username - Update username with invalid length", func(t *testing.T) { testCases := []struct { name string username string }{ {"Username too short", "ab"}, {"Username too long", "thisusernameiswaytoolongandshouldfailvalidation"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { payload := map[string]interface{}{ "username": tc.username, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/username", 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) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"username must be between 3 and 50 characters"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) } }) t.Run("PUT /api/auth/password - Update password with wrong current password", func(t *testing.T) { payload := map[string]interface{}{ "current_password": "wrongpassword", "new_password": "newpassword123", "confirm_password": "newpassword123", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/password", 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) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"error":"current password is incorrect"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusUnauthorized, rr.Code) }) t.Run("PUT /api/auth/password - Update password with mismatched passwords", func(t *testing.T) { payload := map[string]interface{}{ "current_password": "correctpassword", "new_password": "newpassword123", "confirm_password": "differentpassword", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/password", 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) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"new passwords do not match"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("PUT /api/auth/password - Update password with too short new password", func(t *testing.T) { payload := map[string]interface{}{ "current_password": "correctpassword", "new_password": "short", "confirm_password": "short", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/password", 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) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"password must be at least 6 characters"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("PUT /api/auth/theme - Update theme", func(t *testing.T) { payload := map[string]interface{}{ "theme": "tokyo-night", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/theme", 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) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message":"theme updated successfully"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) t.Run("PUT /api/auth/theme - Update theme with empty value", func(t *testing.T) { payload := map[string]interface{}{ "theme": "", } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/auth/theme", 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) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"theme is required"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) } // TestAccountDeletion tests account deletion scenarios func TestAccountDeletion(t *testing.T) { userID := uuid.New() t.Run("DELETE /api/auth/account - Delete without auth", func(t *testing.T) { req := httptest.NewRequest("DELETE", "/api/auth/account", 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("DELETE /api/auth/account - Delete as last admin", func(t *testing.T) { req := httptest.NewRequest("DELETE", "/api/auth/account", nil) req.Header.Set("Authorization", "Bearer admin-token") req.Header.Set("X-User-Role", "admin") req.Header.Set("X-Admin-Count", "1") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { adminCount := r.Header.Get("X-Admin-Count") if adminCount == "1" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"cannot delete the last admin account"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) t.Run("DELETE /api/auth/account - Delete successfully", func(t *testing.T) { req := httptest.NewRequest("DELETE", "/api/auth/account", nil) req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message":"account deleted successfully"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) t.Run("DELETE /api/auth/account - Admin delete another user", func(t *testing.T) { req := httptest.NewRequest("DELETE", "/api/auth/account?user_id="+userID.String(), 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) { 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) w.Write([]byte(`{"message":"user account deleted successfully"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) t.Run("DELETE /api/auth/account - Non-admin tries to delete another user", func(t *testing.T) { req := httptest.NewRequest("DELETE", "/api/auth/account?user_id="+userID.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.StatusOK) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) }) } // TestAdminOnlyEndpoints tests admin-only endpoints func TestAdminOnlyEndpoints(t *testing.T) { t.Run("GET /api/auth/users - List users without admin role", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/auth/users", 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/auth/users - List users with admin role", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/auth/users", 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) { users := []map[string]interface{}{ { "id": uuid.New().String(), "email": "user@example.com", "username": "testuser", "role": "user", }, } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(users) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) } // TestScanSettings tests scan settings endpoints func TestScanSettings(t *testing.T) { t.Run("GET /api/library/scan-settings - Get settings without auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/library/scan-settings", 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("PUT /api/library/scan-settings - Update with invalid frequency", func(t *testing.T) { testCases := []struct { name string scanFrequencyMinutes int }{ {"Frequency too low (14 minutes)", 14}, {"Frequency too high (1441 minutes)", 1441}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { payload := map[string]interface{}{ "scan_frequency_minutes": tc.scanFrequencyMinutes, "auto_scan_enabled": true, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/library/scan-settings", 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) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"scan_frequency_minutes must be between 15 and 1440"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) }) } }) t.Run("PUT /api/library/scan-settings - Update with valid frequency", func(t *testing.T) { payload := map[string]interface{}{ "scan_frequency_minutes": 60, "auto_scan_enabled": true, } jsonData, _ := json.Marshal(payload) req := httptest.NewRequest("PUT", "/api/library/scan-settings", 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) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message":"scan settings updated successfully"}`)) }) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }) }