"""Test API routes""" import pytest class TestAuthRoutes: """Test authentication routes""" @pytest.mark.auth def test_register_success(self, client): """Test successful user registration""" response = client.post( "/api/auth/register", json={ "email": "newuser@example.com", "password": "password123", "confirm_password": "password123", "username": "newuser", "first_name": "New", "last_name": "User", }, ) assert response.status_code == 201 data = response.get_json() assert data["user"]["email"] == "newuser@example.com" assert data["user"]["username"] == "newuser" assert "password" not in data["user"] assert "password_hash" not in data["user"] @pytest.mark.auth def test_register_password_mismatch(self, client): """Test registration with mismatched passwords""" response = client.post( "/api/auth/register", json={ "email": "mismatch@example.com", "password": "password123", "confirm_password": "differentpassword", "username": "mismatch", }, ) assert response.status_code == 400 data = response.get_json() assert "do not match" in data["error"].lower() @pytest.mark.auth def test_register_missing_confirm_password(self, client): """Test registration without confirm_password""" response = client.post( "/api/auth/register", json={"email": "noconfirm@example.com", "password": "password123"} ) assert response.status_code == 400 data = response.get_json() assert "confirm_password" in data["error"].lower() @pytest.mark.auth def test_register_missing_fields(self, client): """Test registration with missing required fields""" response = client.post( "/api/auth/register", json={"email": "newuser@example.com"} ) assert response.status_code == 400 data = response.get_json() assert "error" in data @pytest.mark.auth def test_register_duplicate_email(self, client, regular_user): """Test registration with duplicate email""" response = client.post( "/api/auth/register", json={ "email": regular_user.email, "password": "password123", "confirm_password": "password123", }, ) assert response.status_code == 400 data = response.get_json() assert "already exists" in data["error"].lower() @pytest.mark.auth def test_login_success(self, client, regular_user): """Test successful login""" response = client.post( "/api/auth/login", json={"email": regular_user.email, "password": "password123"}, ) assert response.status_code == 200 data = response.get_json() assert "access_token" in data assert "refresh_token" in data assert data["user"]["email"] == regular_user.email @pytest.mark.auth @pytest.mark.parametrize( "email,password,expected_status", [ ("wrong@example.com", "password123", 401), ("user@example.com", "wrongpassword", 401), (None, "password123", 400), ("user@example.com", None, 400), ], ) def test_login_validation( self, client, regular_user, email, password, expected_status ): """Test login with various invalid inputs""" login_data = {} if email is not None: login_data["email"] = email if password is not None: login_data["password"] = password response = client.post("/api/auth/login", json=login_data) assert response.status_code == expected_status @pytest.mark.auth def test_login_inactive_user(self, client, inactive_user): """Test login with inactive user""" response = client.post( "/api/auth/login", json={"email": inactive_user.email, "password": "password123"}, ) assert response.status_code == 401 data = response.get_json() assert "locked" in data["error"].lower() @pytest.mark.auth def test_get_current_user(self, client, auth_headers, regular_user): """Test getting current user""" response = client.get("/api/users/me", headers=auth_headers) assert response.status_code == 200 data = response.get_json() assert data["email"] == regular_user.email @pytest.mark.auth def test_get_current_user_unauthorized(self, client): """Test getting current user without authentication""" response = client.get("/api/users/me") assert response.status_code == 401 @pytest.mark.auth def test_change_password_success(self, client, auth_headers): """Test successful password change""" response = client.put( "/api/users/me/password", headers=auth_headers, json={ "current_password": "password123", "new_password": "newpassword456", "confirm_password": "newpassword456", }, ) assert response.status_code == 200 data = response.get_json() assert "Password updated" in data["message"] @pytest.mark.auth def test_change_password_wrong_current(self, client, auth_headers): """Test password change with wrong current password""" response = client.put( "/api/users/me/password", headers=auth_headers, json={ "current_password": "wrongpassword", "new_password": "newpassword456", "confirm_password": "newpassword456", }, ) assert response.status_code == 400 data = response.get_json() assert "incorrect" in data["error"].lower() @pytest.mark.auth def test_change_password_mismatch(self, client, auth_headers): """Test password change with mismatched new passwords""" response = client.put( "/api/users/me/password", headers=auth_headers, json={ "current_password": "password123", "new_password": "newpassword456", "confirm_password": "different456", }, ) assert response.status_code == 400 data = response.get_json() assert "do not match" in data["error"].lower() @pytest.mark.auth def test_get_login_history(self, client, auth_headers, regular_user): """Test getting login history""" # Login first to create history client.post( "/api/auth/login", json={"email": regular_user.email, "password": "password123"}, ) response = client.get( "/api/users/me/login-history", headers=auth_headers ) assert response.status_code == 200 data = response.get_json() assert isinstance(data, list) @pytest.mark.auth def test_login_lockout_after_five_attempts(self, client, regular_user): """Test account lockout after 5 failed attempts (default timeout strategy)""" # First 4 attempts should return generic invalid error for i in range(4): response = client.post( "/api/auth/login", json={ "email": regular_user.email, "password": "wrongpassword", }, ) assert response.status_code == 401 data = response.get_json() assert "Invalid" in data["error"] # 5th attempt should trigger lockout response = client.post( "/api/auth/login", json={ "email": regular_user.email, "password": "wrongpassword", }, ) assert response.status_code == 401 data = response.get_json() assert "locked" in data["error"].lower() @pytest.mark.auth def test_login_lockout_increments_counter(self, client, regular_user): """Test that failed_login_attempts increments correctly""" from app import db # First attempt client.post( "/api/auth/login", json={ "email": regular_user.email, "password": "wrongpassword", }, ) db.session.refresh(regular_user) assert regular_user.failed_login_attempts == 1 # Second attempt client.post( "/api/auth/login", json={ "email": regular_user.email, "password": "wrongpassword", }, ) db.session.refresh(regular_user) assert regular_user.failed_login_attempts == 2 @pytest.mark.auth def test_login_resets_counter_on_success(self, client, regular_user): """Test that successful login resets failed_login_attempts""" from app import db # 3 failed attempts for _ in range(3): client.post( "/api/auth/login", json={ "email": regular_user.email, "password": "wrongpassword", }, ) db.session.refresh(regular_user) assert regular_user.failed_login_attempts == 3 # Successful login resets counter response = client.post( "/api/auth/login", json={ "email": regular_user.email, "password": "password123", }, ) assert response.status_code == 200 db.session.refresh(regular_user) assert regular_user.failed_login_attempts == 0 @pytest.mark.auth def test_change_password_unauthorized(self, client): """Test password change without authentication""" response = client.put( "/api/users/me/password", json={ "current_password": "password123", "new_password": "newpassword456", "confirm_password": "newpassword456", }, ) assert response.status_code == 401 @pytest.mark.auth def test_get_login_history_unauthorized(self, client): """Test login history without authentication""" response = client.get("/api/users/me/login-history") assert response.status_code == 401 @pytest.mark.auth def test_get_login_history_success(self, client, auth_headers): """Test getting login history""" response = client.get( "/api/users/me/login-history", headers=auth_headers ) assert response.status_code == 200 data = response.get_json() assert isinstance(data, list) @pytest.mark.auth def test_login_history_recorded(self, client, regular_user): """Test that login history is recorded""" # Log in client.post( "/api/auth/login", json={"email": regular_user.email, "password": "password123"}, ) # Check history from app import db from app.models import LoginHistory entries = LoginHistory.query.filter_by(user_id=regular_user.id).all() assert len(entries) >= 1