388 lines
15 KiB
Python
388 lines
15 KiB
Python
"""Integration tests for authentication API endpoints."""
|
|
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock, AsyncMock
|
|
import json
|
|
|
|
|
|
class TestRegistration:
|
|
"""Test user registration endpoints."""
|
|
|
|
@patch("backend.services.email_service.EmailService.send_verification_email")
|
|
def test_register_success(self, mock_send_email, client, test_user_data):
|
|
"""Test successful user registration."""
|
|
# Mock email sending
|
|
mock_send_email.return_value = AsyncMock()
|
|
|
|
# Register new user
|
|
response = client.post("/api/auth/register", json=test_user_data)
|
|
|
|
# Check response
|
|
assert response.status_code == 201
|
|
data = response.json()
|
|
assert data["email"] == test_user_data["email"]
|
|
assert data["is_verified"] is False
|
|
assert "id" in data
|
|
assert "password_hash" not in data
|
|
|
|
def test_register_duplicate_email(self, client, test_user_data):
|
|
"""Test registration with duplicate email."""
|
|
# Register first user
|
|
with patch("backend.services.email_service.EmailService.send_verification_email") as mock_email:
|
|
mock_email.return_value = AsyncMock()
|
|
response = client.post("/api/auth/register", json=test_user_data)
|
|
assert response.status_code == 201
|
|
|
|
# Try to register with same email
|
|
response = client.post("/api/auth/register", json=test_user_data)
|
|
assert response.status_code == 400
|
|
assert "already registered" in response.json()["detail"].lower()
|
|
|
|
def test_register_weak_password(self, client):
|
|
"""Test registration with weak password."""
|
|
data = {
|
|
"email": "test@example.com",
|
|
"password": "weak",
|
|
"confirm_password": "weak"
|
|
}
|
|
response = client.post("/api/auth/register", json=data)
|
|
assert response.status_code == 422
|
|
|
|
def test_register_password_mismatch(self, client):
|
|
"""Test registration with mismatched passwords."""
|
|
data = {
|
|
"email": "test@example.com",
|
|
"password": "SecurePassword123!",
|
|
"confirm_password": "DifferentPassword123!"
|
|
}
|
|
response = client.post("/api/auth/register", json=data)
|
|
assert response.status_code == 400
|
|
assert "do not match" in response.json()["detail"].lower()
|
|
|
|
def test_register_invalid_email(self, client):
|
|
"""Test registration with invalid email format."""
|
|
data = {
|
|
"email": "not-an-email",
|
|
"password": "SecurePassword123!",
|
|
"confirm_password": "SecurePassword123!"
|
|
}
|
|
response = client.post("/api/auth/register", json=data)
|
|
assert response.status_code == 422
|
|
|
|
|
|
class TestLogin:
|
|
"""Test login endpoints."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_user(self, client, test_user_data):
|
|
"""Create a test user before each test."""
|
|
with patch("backend.services.email_service.EmailService.send_verification_email") as mock_email:
|
|
mock_email.return_value = AsyncMock()
|
|
# Set user as verified for login tests
|
|
with patch("backend.models.user.User.is_verified", True):
|
|
response = client.post("/api/auth/register", json=test_user_data)
|
|
assert response.status_code == 201
|
|
self.user_data = test_user_data
|
|
|
|
def test_login_success(self, client):
|
|
"""Test successful login with valid credentials."""
|
|
login_data = {
|
|
"email": self.user_data["email"],
|
|
"password": self.user_data["password"]
|
|
}
|
|
|
|
# Mock user verification status
|
|
with patch("backend.services.auth_service.AuthService.authenticate_user") as mock_auth:
|
|
from backend.models.user import User
|
|
mock_user = User(
|
|
id="test-id",
|
|
email=self.user_data["email"],
|
|
password_hash="hashed",
|
|
is_verified=True,
|
|
is_active=True
|
|
)
|
|
mock_auth.return_value = mock_user
|
|
|
|
response = client.post("/api/auth/login", json=login_data)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "access_token" in data
|
|
assert "refresh_token" in data
|
|
assert data["token_type"] == "bearer"
|
|
assert data["expires_in"] == 900 # 15 minutes
|
|
|
|
def test_login_invalid_password(self, client):
|
|
"""Test login with wrong password."""
|
|
login_data = {
|
|
"email": self.user_data["email"],
|
|
"password": "WrongPassword123!"
|
|
}
|
|
response = client.post("/api/auth/login", json=login_data)
|
|
assert response.status_code == 401
|
|
assert "Invalid email or password" in response.json()["detail"]
|
|
|
|
def test_login_nonexistent_user(self, client):
|
|
"""Test login with non-existent email."""
|
|
login_data = {
|
|
"email": "nonexistent@example.com",
|
|
"password": "SomePassword123!"
|
|
}
|
|
response = client.post("/api/auth/login", json=login_data)
|
|
assert response.status_code == 401
|
|
assert "Invalid email or password" in response.json()["detail"]
|
|
|
|
def test_login_unverified_user(self, client):
|
|
"""Test login with unverified account."""
|
|
# Create unverified user
|
|
unverified_data = {
|
|
"email": "unverified@example.com",
|
|
"password": "SecurePassword123!",
|
|
"confirm_password": "SecurePassword123!"
|
|
}
|
|
|
|
with patch("backend.services.email_service.EmailService.send_verification_email") as mock_email:
|
|
mock_email.return_value = AsyncMock()
|
|
response = client.post("/api/auth/register", json=unverified_data)
|
|
assert response.status_code == 201
|
|
|
|
# Try to login
|
|
login_data = {
|
|
"email": unverified_data["email"],
|
|
"password": unverified_data["password"]
|
|
}
|
|
response = client.post("/api/auth/login", json=login_data)
|
|
assert response.status_code == 403
|
|
assert "not verified" in response.json()["detail"].lower()
|
|
|
|
def test_login_inactive_user(self, client):
|
|
"""Test login with inactive account."""
|
|
login_data = {
|
|
"email": self.user_data["email"],
|
|
"password": self.user_data["password"]
|
|
}
|
|
|
|
# Mock user as inactive
|
|
with patch("backend.services.auth_service.AuthService.authenticate_user") as mock_auth:
|
|
from backend.models.user import User
|
|
mock_user = User(
|
|
id="test-id",
|
|
email=self.user_data["email"],
|
|
password_hash="hashed",
|
|
is_verified=True,
|
|
is_active=False
|
|
)
|
|
mock_auth.return_value = mock_user
|
|
|
|
response = client.post("/api/auth/login", json=login_data)
|
|
assert response.status_code == 403
|
|
assert "deactivated" in response.json()["detail"].lower()
|
|
|
|
|
|
class TestTokenRefresh:
|
|
"""Test token refresh endpoints."""
|
|
|
|
def test_refresh_token_success(self, client):
|
|
"""Test successful token refresh."""
|
|
# Mock token validation
|
|
with patch("backend.services.auth_service.AuthService.refresh_access_token") as mock_refresh:
|
|
mock_refresh.return_value = {
|
|
"access_token": "new_access_token",
|
|
"token_type": "bearer",
|
|
"expires_in": 900
|
|
}
|
|
|
|
response = client.post("/api/auth/refresh", json={
|
|
"refresh_token": "valid_refresh_token"
|
|
})
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["access_token"] == "new_access_token"
|
|
assert "refresh_token" in data
|
|
|
|
def test_refresh_invalid_token(self, client):
|
|
"""Test refresh with invalid token."""
|
|
response = client.post("/api/auth/refresh", json={
|
|
"refresh_token": "invalid_token"
|
|
})
|
|
assert response.status_code == 401
|
|
assert "Invalid refresh token" in response.json()["detail"]
|
|
|
|
def test_refresh_expired_token(self, client):
|
|
"""Test refresh with expired token."""
|
|
with patch("backend.services.auth_service.AuthService.refresh_access_token") as mock_refresh:
|
|
mock_refresh.side_effect = Exception("Token expired")
|
|
|
|
response = client.post("/api/auth/refresh", json={
|
|
"refresh_token": "expired_token"
|
|
})
|
|
assert response.status_code == 401
|
|
|
|
|
|
class TestLogout:
|
|
"""Test logout endpoints."""
|
|
|
|
def test_logout_success(self, client):
|
|
"""Test successful logout."""
|
|
with patch("backend.services.auth_service.AuthService.revoke_refresh_token") as mock_revoke:
|
|
mock_revoke.return_value = True
|
|
|
|
response = client.post("/api/auth/logout", params={
|
|
"refresh_token": "valid_refresh_token"
|
|
})
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["message"] == "Successfully logged out"
|
|
|
|
def test_logout_invalid_token(self, client):
|
|
"""Test logout with invalid token."""
|
|
with patch("backend.services.auth_service.AuthService.revoke_refresh_token") as mock_revoke:
|
|
mock_revoke.return_value = False
|
|
|
|
response = client.post("/api/auth/logout", params={
|
|
"refresh_token": "invalid_token"
|
|
})
|
|
|
|
# Should still return success for security
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestEmailVerification:
|
|
"""Test email verification endpoints."""
|
|
|
|
def test_verify_email_success(self, client):
|
|
"""Test successful email verification."""
|
|
with patch("backend.services.auth_service.AuthService.verify_email") as mock_verify:
|
|
mock_verify.return_value = True
|
|
|
|
response = client.post("/api/auth/verify-email", params={
|
|
"token": "valid_verification_token"
|
|
})
|
|
|
|
assert response.status_code == 200
|
|
assert "verified successfully" in response.json()["message"].lower()
|
|
|
|
def test_verify_email_invalid_token(self, client):
|
|
"""Test verification with invalid token."""
|
|
with patch("backend.services.auth_service.AuthService.verify_email") as mock_verify:
|
|
mock_verify.side_effect = ValueError("Invalid token")
|
|
|
|
response = client.post("/api/auth/verify-email", params={
|
|
"token": "invalid_token"
|
|
})
|
|
|
|
assert response.status_code == 400
|
|
assert "Invalid" in response.json()["detail"]
|
|
|
|
def test_resend_verification_email(self, client):
|
|
"""Test resending verification email."""
|
|
with patch("backend.core.dependencies.get_current_user") as mock_user:
|
|
mock_user.return_value = MagicMock(id="user-id", email="test@example.com")
|
|
|
|
with patch("backend.services.email_service.EmailService.send_verification_email") as mock_send:
|
|
mock_send.return_value = AsyncMock()
|
|
|
|
# Mock authorization header
|
|
response = client.post(
|
|
"/api/auth/resend-verification",
|
|
headers={"Authorization": "Bearer fake_token"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert "sent successfully" in response.json()["message"].lower()
|
|
|
|
|
|
class TestPasswordReset:
|
|
"""Test password reset endpoints."""
|
|
|
|
def test_request_password_reset(self, client):
|
|
"""Test password reset request."""
|
|
with patch("backend.services.auth_service.AuthService.create_password_reset_token") as mock_create:
|
|
mock_create.return_value = "reset_token"
|
|
|
|
with patch("backend.services.email_service.EmailService.send_password_reset_email") as mock_send:
|
|
mock_send.return_value = AsyncMock()
|
|
|
|
response = client.post("/api/auth/reset-password", json={
|
|
"email": "test@example.com"
|
|
})
|
|
|
|
assert response.status_code == 200
|
|
assert "sent" in response.json()["message"].lower()
|
|
|
|
def test_confirm_password_reset(self, client):
|
|
"""Test password reset confirmation."""
|
|
with patch("backend.services.auth_service.AuthService.reset_password") as mock_reset:
|
|
mock_reset.return_value = True
|
|
|
|
response = client.post("/api/auth/reset-password/confirm", json={
|
|
"token": "valid_reset_token",
|
|
"new_password": "NewSecurePassword123!",
|
|
"confirm_password": "NewSecurePassword123!"
|
|
})
|
|
|
|
assert response.status_code == 200
|
|
assert "reset successfully" in response.json()["message"].lower()
|
|
|
|
def test_reset_password_weak(self, client):
|
|
"""Test reset with weak password."""
|
|
response = client.post("/api/auth/reset-password/confirm", json={
|
|
"token": "valid_token",
|
|
"new_password": "weak",
|
|
"confirm_password": "weak"
|
|
})
|
|
assert response.status_code == 422
|
|
|
|
def test_reset_password_mismatch(self, client):
|
|
"""Test reset with mismatched passwords."""
|
|
response = client.post("/api/auth/reset-password/confirm", json={
|
|
"token": "valid_token",
|
|
"new_password": "NewSecurePassword123!",
|
|
"confirm_password": "DifferentPassword123!"
|
|
})
|
|
assert response.status_code == 400
|
|
assert "do not match" in response.json()["detail"].lower()
|
|
|
|
|
|
class TestProtectedEndpoints:
|
|
"""Test protected endpoint authorization."""
|
|
|
|
def test_get_current_user(self, client):
|
|
"""Test getting current user profile."""
|
|
with patch("backend.core.dependencies.get_current_user") as mock_user:
|
|
from backend.models.user import User
|
|
mock_user.return_value = User(
|
|
id="test-id",
|
|
email="test@example.com",
|
|
password_hash="hashed",
|
|
is_verified=True,
|
|
is_active=True
|
|
)
|
|
|
|
response = client.get(
|
|
"/api/auth/me",
|
|
headers={"Authorization": "Bearer valid_token"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["email"] == "test@example.com"
|
|
assert "password_hash" not in data
|
|
|
|
def test_protected_endpoint_no_token(self, client):
|
|
"""Test accessing protected endpoint without token."""
|
|
response = client.get("/api/auth/me")
|
|
assert response.status_code == 401
|
|
assert "Not authenticated" in response.json()["detail"]
|
|
|
|
def test_protected_endpoint_invalid_token(self, client):
|
|
"""Test accessing protected endpoint with invalid token."""
|
|
with patch("backend.core.dependencies.get_current_user") as mock_user:
|
|
mock_user.side_effect = Exception("Invalid token")
|
|
|
|
response = client.get(
|
|
"/api/auth/me",
|
|
headers={"Authorization": "Bearer invalid_token"}
|
|
)
|
|
assert response.status_code == 401 |