diff --git a/backend/router/routes_test.go b/backend/router/routes_test.go index df7e203..1fce55a 100644 --- a/backend/router/routes_test.go +++ b/backend/router/routes_test.go @@ -498,3 +498,464 @@ func TestPasswordResetRoutes_CORS(t *testing.T) { assert.NotEqual(t, http.StatusInternalServerError, w.Code) }) } + +// ============================================================================= +// Register Route Tests +// ============================================================================= + +func TestRegisterRoute(t *testing.T) { + router := setupTestRouter() + + t.Run("POST /register - Success", func(t *testing.T) { + database.SetupTestDB() + err := user.MigrateStructure() + require.NoError(t, err, "Failed to migrate user structure") + + randomSuffix := rand.Intn(100000) + userData := map[string]interface{}{ + "username": fmt.Sprintf("testuser_%d", randomSuffix), + "password": "testpassword123", + "email": fmt.Sprintf("test_%d@example.com", randomSuffix), + } + jsonData, _ := json.Marshal(userData) + + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusCreated, w.Code) + + var response map[string]interface{} + err = json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response["message"], "User registered successfully") + + // Verify user was created in database + var createdUser user.User + err = createdUser.First(userData["username"].(string)) + assert.NoError(t, err, "User should be created in database") + assert.Equal(t, userData["username"], createdUser.Username) + assert.Equal(t, userData["email"], createdUser.Email) + // Verify password was hashed + assert.NotEqual(t, userData["password"], createdUser.PasswordHash) + }) + + t.Run("POST /register - Missing Required Fields", func(t *testing.T) { + // Note: The current implementation allows creation of users with missing fields + // The database structure allows empty strings for non-primary key fields + database.SetupTestDB() + err := user.MigrateStructure() + require.NoError(t, err, "Failed to migrate user structure") + + testCases := []struct { + name string + data map[string]interface{} + expectedStatus int + }{ + { + name: "Missing username - should return error", + data: map[string]interface{}{ + "password": "testpassword123", + "email": fmt.Sprintf("missing_username_%d@example.com", rand.Intn(100000)), + }, + expectedStatus: http.StatusBadRequest, // Now returns error for empty username + }, + { + name: "Missing password - should return error", + data: map[string]interface{}{ + "username": fmt.Sprintf("testuser_nopass_%d", rand.Intn(100000)), + "email": fmt.Sprintf("testnopass_%d@example.com", rand.Intn(100000)), + }, + expectedStatus: http.StatusBadRequest, // Now returns error for empty password + }, + { + name: "Missing email - should return error", + data: map[string]interface{}{ + "username": fmt.Sprintf("testuser_noemail_%d", rand.Intn(100000)), + "password": "testpassword123", + }, + expectedStatus: http.StatusBadRequest, // Now returns error for empty email + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jsonData, _ := json.Marshal(tc.data) + + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, tc.expectedStatus, w.Code) + + var response map[string]interface{} + err = json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + + if tc.expectedStatus == http.StatusCreated { + assert.Contains(t, response["message"], "User registered successfully") + } else { + assert.Contains(t, response, "error") + } + }) + } + }) + + t.Run("POST /register - Duplicate Username", func(t *testing.T) { + database.SetupTestDB() + err := user.MigrateStructure() + require.NoError(t, err, "Failed to migrate user structure") + + // Create first user + randomSuffix := rand.Intn(100000) + userData1 := map[string]interface{}{ + "username": fmt.Sprintf("duplicate_user_%d", randomSuffix), + "password": "testpassword123", + "email": fmt.Sprintf("first_%d@example.com", randomSuffix), + } + jsonData1, _ := json.Marshal(userData1) + + req1, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData1)) + req1.Header.Set("Content-Type", "application/json") + + w1 := httptest.NewRecorder() + router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusCreated, w1.Code) + + // Try to create second user with same username + userData2 := map[string]interface{}{ + "username": userData1["username"], // Same username + "password": "differentpassword", + "email": fmt.Sprintf("second_%d@example.com", randomSuffix), + } + jsonData2, _ := json.Marshal(userData2) + + req2, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData2)) + req2.Header.Set("Content-Type", "application/json") + + w2 := httptest.NewRecorder() + router.ServeHTTP(w2, req2) + + assert.Equal(t, http.StatusInternalServerError, w2.Code) + + var response map[string]interface{} + err = json.Unmarshal(w2.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + assert.Contains(t, response["error"], "Failed to create user") + }) + + t.Run("POST /register - Duplicate Email", func(t *testing.T) { + database.SetupTestDB() + err := user.MigrateStructure() + require.NoError(t, err, "Failed to migrate user structure") + + // Create first user + randomSuffix := rand.Intn(100000) + userData1 := map[string]interface{}{ + "username": fmt.Sprintf("first_user_%d", randomSuffix), + "password": "testpassword123", + "email": fmt.Sprintf("duplicate_%d@example.com", randomSuffix), + } + jsonData1, _ := json.Marshal(userData1) + + req1, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData1)) + req1.Header.Set("Content-Type", "application/json") + + w1 := httptest.NewRecorder() + router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusCreated, w1.Code) + + // Try to create second user with same email + userData2 := map[string]interface{}{ + "username": fmt.Sprintf("second_user_%d", randomSuffix), + "password": "differentpassword", + "email": userData1["email"], // Same email + } + jsonData2, _ := json.Marshal(userData2) + + req2, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData2)) + req2.Header.Set("Content-Type", "application/json") + + w2 := httptest.NewRecorder() + router.ServeHTTP(w2, req2) + + assert.Equal(t, http.StatusInternalServerError, w2.Code) + + var response map[string]interface{} + err = json.Unmarshal(w2.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + assert.Contains(t, response["error"], "Failed to create user") + }) + + t.Run("POST /register - Invalid JSON", func(t *testing.T) { + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer([]byte("invalid json"))) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + }) +} + +// ============================================================================= +// Login Route Tests +// ============================================================================= + +func TestLoginRoute(t *testing.T) { + router := setupTestRouter() + + t.Run("POST /login - Success", func(t *testing.T) { + // Setup user first + testUser := setupTestUserForRouter(t) + + loginData := map[string]interface{}{ + "username": testUser.Username, + "password": "testpassword123", // Original password before hashing + } + jsonData, _ := json.Marshal(loginData) + + req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Equal(t, "Login successful", response["message"]) + assert.Contains(t, response, "token") + assert.NotEmpty(t, response["token"]) + + // Verify token format (should contain user ID) + token := response["token"].(string) + assert.Contains(t, token, ".") + assert.Contains(t, token, ":") + }) + + t.Run("POST /login - Invalid Username", func(t *testing.T) { + setupTestUserForRouter(t) // Setup database + + loginData := map[string]interface{}{ + "username": "nonexistentuser", + "password": "testpassword123", + } + jsonData, _ := json.Marshal(loginData) + + req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + assert.Contains(t, response["error"], "Invalid username") + }) + + t.Run("POST /login - Invalid Password", func(t *testing.T) { + testUser := setupTestUserForRouter(t) + + loginData := map[string]interface{}{ + "username": testUser.Username, + "password": "wrongpassword", + } + jsonData, _ := json.Marshal(loginData) + + req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + assert.Contains(t, response["error"], "Invalid username") + }) + + t.Run("POST /login - Missing Required Fields", func(t *testing.T) { + // Note: The database may contain users with empty usernames from test data + // We need to test with usernames that definitely don't exist to get proper errors + setupTestUserForRouter(t) // Setup database + + testCases := []struct { + name string + data map[string]interface{} + expectedStatus int + }{ + { + name: "Missing username - should be unauthorized", + data: map[string]interface{}{ + "password": "testpassword123", + }, + expectedStatus: http.StatusUnauthorized, // No username provided + }, + { + name: "Missing password - should be unauthorized", + data: map[string]interface{}{ + "username": fmt.Sprintf("nonexistent_%d", rand.Intn(100000)), + }, + expectedStatus: http.StatusUnauthorized, // No password provided + }, + { + name: "Nonexistent username - should be unauthorized", + data: map[string]interface{}{ + "username": fmt.Sprintf("definitely_nonexistent_%d", rand.Intn(100000)), + "password": "testpassword123", + }, + expectedStatus: http.StatusUnauthorized, // Username doesn't exist + }, + { + name: "Empty password with nonexistent user - should be unauthorized", + data: map[string]interface{}{ + "username": fmt.Sprintf("another_nonexistent_%d", rand.Intn(100000)), + "password": "", + }, + expectedStatus: http.StatusUnauthorized, // Password mismatch + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jsonData, _ := json.Marshal(tc.data) + + req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, tc.expectedStatus, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + }) + } + }) + + t.Run("POST /login - Invalid JSON", func(t *testing.T) { + req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer([]byte("invalid json"))) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + }) + + t.Run("POST /login - Empty Request Body", func(t *testing.T) { + req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer([]byte(""))) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + }) +} + +// ============================================================================= +// Integration Tests for Register and Login Flow +// ============================================================================= + +func TestRegisterLoginFlow(t *testing.T) { + router := setupTestRouter() + + t.Run("Complete Register and Login Flow", func(t *testing.T) { + database.SetupTestDB() + err := user.MigrateStructure() + require.NoError(t, err, "Failed to migrate user structure") + + randomSuffix := rand.Intn(100000) + username := fmt.Sprintf("flowtest_user_%d", randomSuffix) + password := "testpassword123" + email := fmt.Sprintf("flowtest_%d@example.com", randomSuffix) + + // Step 1: Register user + registerData := map[string]interface{}{ + "username": username, + "password": password, + "email": email, + } + jsonData, _ := json.Marshal(registerData) + + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusCreated, w.Code) + + // Step 2: Login with registered credentials + loginData := map[string]interface{}{ + "username": username, + "password": password, + } + jsonData, _ = json.Marshal(loginData) + + req, _ = http.NewRequest("POST", "/login", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w = httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var response map[string]interface{} + err = json.Unmarshal(w.Body.Bytes(), &response) + require.NoError(t, err) + assert.Equal(t, "Login successful", response["message"]) + assert.Contains(t, response, "token") + assert.NotEmpty(t, response["token"]) + + // Step 3: Verify login fails with wrong password + wrongLoginData := map[string]interface{}{ + "username": username, + "password": "wrongpassword", + } + jsonData, _ = json.Marshal(wrongLoginData) + + req, _ = http.NewRequest("POST", "/login", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + w = httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} diff --git a/backend/user/handlers.go b/backend/user/handlers.go index ed7a911..025aa2f 100644 --- a/backend/user/handlers.go +++ b/backend/user/handlers.go @@ -32,6 +32,27 @@ func RegisterUser(c *gin.Context) { return } + // Validate that username is not empty + if user.Username == "" { + logger.Error("Registrierung fehlgeschlagen - Benutzername ist leer") + respondWithError(c, http.StatusBadRequest, "Username cannot be empty") + return + } + + // Validate that email is not empty + if user.Email == "" { + logger.Error("Registrierung fehlgeschlagen - E-Mail ist leer") + respondWithError(c, http.StatusBadRequest, "Email cannot be empty") + return + } + + // Validate that password is not empty + if user.PasswordHash == "" { + logger.Error("Registrierung fehlgeschlagen - Passwort ist leer") + respondWithError(c, http.StatusBadRequest, "Password cannot be empty") + return + } + logger.Debug("Registriere Benutzer: %s", user.Username) //fmt.Printf("User input: '%s'", user.PasswordHash) //hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.PasswordHash), bcrypt.DefaultCost)