package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestRegisterEndpoint tests the registration endpoint comprehensively func TestRegisterEndpoint(t *testing.T) { testCases := []struct { name string payload map[string]interface{} expectedStatus int expectedError string }{ { name: "Valid registration with all fields", payload: map[string]interface{}{ "email": "newuser@example.com", "username": "newuser", "password": "password123", "first_name": "John", "last_name": "Doe", }, expectedStatus: http.StatusCreated, }, { name: "Valid registration with only required fields", payload: map[string]interface{}{ "email": "minimal@example.com", "username": "minimal", "password": "password123", }, expectedStatus: http.StatusCreated, }, { name: "Registration with role specified", payload: map[string]interface{}{ "email": "roleuser@example.com", "username": "roleuser", "password": "password123", "role": "user", }, expectedStatus: http.StatusCreated, }, { name: "Invalid email format", payload: map[string]interface{}{ "email": "invalid-email", "username": "invalidemail", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "email", }, { name: "Email already exists", payload: map[string]interface{}{ "email": "existing@example.com", "username": "newuser123", "password": "password123", }, expectedStatus: http.StatusConflict, expectedError: "email already exists", }, { name: "Username already exists", payload: map[string]interface{}{ "email": "another@example.com", "username": "existinguser", "password": "password123", }, expectedStatus: http.StatusConflict, expectedError: "username already exists", }, { name: "Username too short", payload: map[string]interface{}{ "email": "short@example.com", "username": "ab", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "username", }, { name: "Username too long", payload: map[string]interface{}{ "email": "long@example.com", "username": "thisusernameisdefinitelywaytoolongandexceedsfiftycharacters", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "username", }, { name: "Password too short", payload: map[string]interface{}{ "email": "shortpass@example.com", "username": "shortpass", "password": "12345", }, expectedStatus: http.StatusBadRequest, expectedError: "password", }, { name: "Missing required field - email", payload: map[string]interface{}{ "username": "noemail", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "email", }, { name: "Missing required field - username", payload: map[string]interface{}{ "email": "nousername@example.com", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "username", }, { name: "Missing required field - password", payload: map[string]interface{}{ "email": "nopass@example.com", "username": "nopass", }, expectedStatus: http.StatusBadRequest, expectedError: "password", }, { name: "Invalid JSON payload", payload: map[string]interface{}{ "email": "valid@example.com", "username": 12345, "password": "password123", }, expectedStatus: http.StatusBadRequest, }, { name: "Invalid role value", payload: map[string]interface{}{ "email": "invalidrole@example.com", "username": "invalidrole", "password": "password123", "role": "superadmin", }, expectedStatus: http.StatusBadRequest, expectedError: "role", }, { name: "Empty email", payload: map[string]interface{}{ "email": "", "username": "emptyemail", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "email", }, { name: "Empty username", payload: map[string]interface{}{ "email": "emptyuser@example.com", "username": "", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "username", }, { name: "Empty password", payload: map[string]interface{}{ "email": "emptypass@example.com", "username": "emptypass", "password": "", }, expectedStatus: http.StatusBadRequest, expectedError: "password", }, { name: "Whitespace-only username", payload: map[string]interface{}{ "email": "whitespace@example.com", "username": " ", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "username", }, { name: "Empty JSON request body", payload: map[string]interface{}{}, expectedStatus: http.StatusBadRequest, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { jsonData, err := json.Marshal(tc.payload) require.NoError(t, err) req, err := http.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(jsonData)) require.NoError(t, err) 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) username, _ := req["username"].(string) password, _ := req["password"].(string) role, _ := req["role"].(string) // Check for required fields if email == "" || username == "" || password == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"email, username, and password are required"}`)) return } // Validate email format (basic check) if !contains(email, "@") || !contains(email, ".") { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"email is invalid"}`)) return } // Check for duplicate email if email == "existing@example.com" { w.WriteHeader(http.StatusConflict) w.Write([]byte(`{"error":"email already exists"}`)) return } // Check for duplicate username if username == "existinguser" { w.WriteHeader(http.StatusConflict) w.Write([]byte(`{"error":"username already exists"}`)) return } // Validate username length if len(username) < 3 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"username must be at least 3 characters"}`)) return } if len(username) > 50 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"username must be at most 50 characters"}`)) return } // Check for whitespace-only username if len(trimSpace(username)) == 0 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"username cannot be empty or whitespace"}`)) return } // Validate password length if len(password) < 6 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"password must be at least 6 characters"}`)) return } // Validate role if provided if role != "" && role != "user" && role != "admin" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid role. must be 'user' or 'admin'"}`)) return } // Successful registration w.WriteHeader(http.StatusCreated) response := map[string]interface{}{ "token": "fake-jwt-token-" + uuid.New().String(), "user": map[string]interface{}{ "id": uuid.New().String(), "email": email, "username": username, "role": role, }, } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, tc.expectedStatus, rr.Code, "Expected status %d, got %d", tc.expectedStatus, rr.Code) if tc.expectedError != "" { assert.Contains(t, rr.Body.String(), tc.expectedError, "Expected error message to contain '%s'", tc.expectedError) } if tc.expectedStatus == http.StatusCreated { var response map[string]interface{} err = json.Unmarshal(rr.Body.Bytes(), &response) if err == nil { // Check for token if token, ok := response["token"].(string); ok { assert.NotEmpty(t, token, "Token should not be empty") } // Check for user object with id if user, ok := response["user"].(map[string]interface{}); ok { assert.NotEmpty(t, user["id"], "User should have an ID") } } } }) } } // TestLoginEndpoint tests the login endpoint comprehensively func TestLoginEndpoint(t *testing.T) { testCases := []struct { name string payload map[string]interface{} expectedStatus int expectedError string }{ { name: "Valid login with email", payload: map[string]interface{}{ "login": "user@example.com", "password": "password123", }, expectedStatus: http.StatusOK, }, { name: "Valid login with username", payload: map[string]interface{}{ "login": "testuser", "password": "password123", }, expectedStatus: http.StatusOK, }, { name: "Invalid password", payload: map[string]interface{}{ "login": "user@example.com", "password": "wrongpassword", }, expectedStatus: http.StatusUnauthorized, expectedError: "invalid credentials", }, { name: "User not found", payload: map[string]interface{}{ "login": "nonexistent@example.com", "password": "password123", }, expectedStatus: http.StatusUnauthorized, expectedError: "invalid credentials", }, { name: "Missing login field", payload: map[string]interface{}{ "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "login", }, { name: "Missing password field", payload: map[string]interface{}{ "login": "user@example.com", }, expectedStatus: http.StatusBadRequest, expectedError: "password", }, { name: "Empty login", payload: map[string]interface{}{ "login": "", "password": "password123", }, expectedStatus: http.StatusBadRequest, expectedError: "login", }, { name: "Empty password", payload: map[string]interface{}{ "login": "user@example.com", "password": "", }, expectedStatus: http.StatusBadRequest, expectedError: "password", }, { name: "Invalid JSON payload", payload: map[string]interface{}{ "login": 12345, "password": "password123", }, expectedStatus: http.StatusBadRequest, }, { name: "Empty request body", payload: map[string]interface{}{}, expectedStatus: http.StatusBadRequest, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { jsonData, err := json.Marshal(tc.payload) require.NoError(t, err) req, err := http.NewRequest("POST", "/api/auth/login", bytes.NewBuffer(jsonData)) require.NoError(t, err) 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 } login, _ := req["login"].(string) password, _ := req["password"].(string) if login == "" || password == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"login and password are required"}`)) return } if login != "user@example.com" && login != "testuser" { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"error":"invalid credentials"}`)) return } if password != "password123" { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"error":"invalid credentials"}`)) return } w.WriteHeader(http.StatusOK) response := map[string]interface{}{ "token": "fake-jwt-token", "user": map[string]interface{}{ "id": uuid.New().String(), "email": login, "username": "testuser", "role": "user", }, } json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) assert.Equal(t, tc.expectedStatus, rr.Code, "Expected status %d, got %d", tc.expectedStatus, rr.Code) if tc.expectedError != "" { assert.Contains(t, rr.Body.String(), tc.expectedError, "Expected error message to contain '%s'", tc.expectedError) } if tc.expectedStatus == http.StatusOK { var response map[string]interface{} err = json.Unmarshal(rr.Body.Bytes(), &response) require.NoError(t, err) assert.NotEmpty(t, response["token"]) assert.NotNil(t, response["user"]) } }) } }