youtube-summarizer/backend/tests/integration/test_auth_api.py

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