annator-command-center / tests /api /test_auth_2fa_routes.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 7)
cc036ff verified
Raw
History Blame Contribute Delete
13.8 kB
"""
Auth 2FA Routes API Tests
Tests for two-factor authentication endpoints including:
- Getting 2FA status
- Setting up 2FA
- Enabling 2FA
- Disabling 2FA
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import Mock, patch, AsyncMock
from sqlalchemy.orm import Session
from main_api_app import app
from core.auth import get_current_user
from core.models import User
class TestAuth2FARoutes:
"""Test 2FA authentication API endpoints."""
@pytest.fixture
def mock_user(self):
"""Create mock user."""
user = Mock(spec=User)
user.id = "user-123"
user.email = "user@example.com"
user.two_factor_enabled = False
user.two_factor_secret = None
user.two_factor_backup_codes = None
return user
@pytest.fixture
def client(self, mock_user):
"""Create test client with auth override."""
app.dependency_overrides[get_current_user] = lambda: mock_user
try:
yield TestClient(app)
finally:
app.dependency_overrides.clear()
def test_get_2fa_status_disabled(self, client, mock_user):
"""Test getting 2FA status when disabled."""
mock_user.two_factor_enabled = False
response = client.get("/api/auth/2fa/status")
assert response.status_code == 200
data = response.json()
assert "enabled" in data
assert data["enabled"] is False
def test_get_2fa_status_enabled(self, client, mock_user):
"""Test getting 2FA status when enabled."""
mock_user.two_factor_enabled = True
response = client.get("/api/auth/2fa/status")
assert response.status_code == 200
data = response.json()
assert "enabled" in data
assert data["enabled"] is True
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_setup_2fa_success(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test successful 2FA setup."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_pyotp.random_base32.return_value = "test-secret-123"
mock_totp = Mock()
mock_totp.provisioning_uri.return_value = "otpauth://totp/test"
mock_pyotp.totp.TOTP.return_value = mock_totp
response = client.post("/api/auth/2fa/setup")
assert response.status_code == 200
data = response.json()
assert "secret" in data
assert "otpauth_url" in data
assert data["secret"] == "test-secret-123"
def test_setup_2fa_already_enabled(self, client, mock_user):
"""Test setup when 2FA is already enabled."""
mock_user.two_factor_enabled = True
response = client.post("/api/auth/2fa/setup")
assert response.status_code == 409 # Conflict
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_enable_2fa_success(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test successfully enabling 2FA."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = False
mock_user.two_factor_secret = "existing-secret"
mock_totp = Mock()
mock_totp.verify.return_value = True
mock_pyotp.TOTP.return_value = mock_totp
mock_audit.log_event = Mock()
response = client.post(
"/api/auth/2fa/enable",
json={"code": "123456"}
)
assert response.status_code == 200
data = response.json()
assert "backup_codes" in data or "success" in data
assert mock_user.two_factor_enabled is True
def test_enable_2fa_already_enabled(self, client, mock_user):
"""Test enable when 2FA is already enabled."""
mock_user.two_factor_enabled = True
response = client.post(
"/api/auth/2fa/enable",
json={"code": "123456"}
)
assert response.status_code == 409 # Conflict
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.get_db')
def test_enable_2fa_no_setup(self, mock_get_db, mock_pyotp, client, mock_user):
"""Test enable without prior setup."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = False
mock_user.two_factor_secret = None
response = client.post(
"/api/auth/2fa/enable",
json={"code": "123456"}
)
assert response.status_code in [400, 422] # Validation error
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_enable_2fa_invalid_code(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test enable with invalid verification code."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = False
mock_user.two_factor_secret = "test-secret"
mock_totp = Mock()
mock_totp.verify.return_value = False
mock_pyotp.TOTP.return_value = mock_totp
response = client.post(
"/api/auth/2fa/enable",
json={"code": "000000"}
)
assert response.status_code in [400, 422] # Validation error
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_disable_2fa_success(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test successfully disabling 2FA."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = True
mock_user.two_factor_secret = "test-secret"
mock_totp = Mock()
mock_totp.verify.return_value = True
mock_pyotp.TOTP.return_value = mock_totp
mock_audit.log_event = Mock()
response = client.post(
"/api/auth/2fa/disable",
json={"code": "123456"}
)
assert response.status_code == 200
assert mock_user.two_factor_enabled is False
assert mock_user.two_factor_secret is None
def test_disable_2fa_not_enabled(self, client, mock_user):
"""Test disable when 2FA is not enabled."""
mock_user.two_factor_enabled = False
response = client.post(
"/api/auth/2fa/disable",
json={"code": "123456"}
)
assert response.status_code in [400, 422] # Validation error
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_disable_2fa_invalid_code(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test disable with invalid verification code."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = True
mock_user.two_factor_secret = "test-secret"
mock_totp = Mock()
mock_totp.verify.return_value = False
mock_pyotp.TOTP.return_value = mock_totp
response = client.post(
"/api/auth/2fa/disable",
json={"code": "000000"}
)
assert response.status_code in [400, 422] # Validation error
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_2fa_endpoints_require_auth(self, mock_get_db, mock_audit, mock_pyotp):
"""Test that 2FA endpoints require authentication."""
# Create client without auth override
client = TestClient(app)
# Test status endpoint
response = client.get("/api/auth/2fa/status")
assert response.status_code == 401
# Test setup endpoint
response = client.post("/api/auth/2fa/setup")
assert response.status_code == 401
# Test enable endpoint
response = client.post("/api/auth/2fa/enable", json={"code": "123456"})
assert response.status_code == 401
# Test disable endpoint
response = client.post("/api/auth/2fa/disable", json={"code": "123456"})
assert response.status_code == 401
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_2fa_workflow_full_cycle(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test full 2FA enable/disable cycle."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_pyotp.random_base32.return_value = "secret-123"
mock_totp = Mock()
mock_totp.provisioning_uri.return_value = "otpauth://test"
mock_totp.verify.return_value = True
mock_pyotp.totp.TOTP.return_value = mock_totp
mock_audit.log_event = Mock()
# Setup 2FA
response = client.post("/api/auth/2fa/setup")
assert response.status_code == 200
assert mock_user.two_factor_secret == "secret-123"
# Enable 2FA
response = client.post(
"/api/auth/2fa/enable",
json={"code": "123456"}
)
assert response.status_code == 200
assert mock_user.two_factor_enabled is True
# Disable 2FA
response = client.post(
"/api/auth/2fa/disable",
json={"code": "123456"}
)
assert response.status_code == 200
assert mock_user.two_factor_enabled is False
assert mock_user.two_factor_secret is None
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_enable_creates_backup_codes(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test that enabling 2FA creates backup codes."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = False
mock_user.two_factor_secret = "secret"
mock_totp = Mock()
mock_totp.verify.return_value = True
mock_pyotp.TOTP.return_value = mock_totp
mock_audit.log_event = Mock()
response = client.post(
"/api/auth/2fa/enable",
json={"code": "123456"}
)
assert response.status_code == 200
assert mock_user.two_factor_backup_codes is not None
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_enable_logs_audit_event(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test that enabling 2FA logs audit event."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = False
mock_user.two_factor_secret = "secret"
mock_totp = Mock()
mock_totp.verify.return_value = True
mock_pyotp.TOTP.return_value = mock_totp
mock_audit.log_event = Mock()
response = client.post(
"/api/auth/2fa/enable",
json={"code": "123456"}
)
assert response.status_code == 200
mock_audit.log_event.assert_called_once()
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_disable_logs_audit_event(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test that disabling 2FA logs audit event."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
mock_user.two_factor_enabled = True
mock_user.two_factor_secret = "secret"
mock_totp = Mock()
mock_totp.verify.return_value = True
mock_pyotp.TOTP.return_value = mock_totp
mock_audit.log_event = Mock()
response = client.post(
"/api/auth/2fa/disable",
json={"code": "123456"}
)
assert response.status_code == 200
mock_audit.log_event.assert_called_once()
@patch('api.auth_2fa_routes.pyotp')
@patch('api.auth_2fa_routes.audit_service')
@patch('api.auth_2fa_routes.get_db')
def test_2fa_endpoints_return_json(self, mock_get_db, mock_audit, mock_pyotp, client, mock_user):
"""Test that 2FA endpoints return JSON."""
mock_db = Mock(spec=Session)
mock_get_db.return_value = mock_db
# Test status endpoint
response = client.get("/api/auth/2fa/status")
assert response.headers["content-type"].startswith("application/json")
# Setup mocks for POST endpoints
mock_pyotp.random_base32.return_value = "secret"
mock_totp = Mock()
mock_totp.provisioning_uri.return_value = "otpauth://test"
mock_totp.verify.return_value = True
mock_pyotp.totp.TOTP.return_value = mock_totp
# Test setup endpoint
response = client.post("/api/auth/2fa/setup")
assert response.headers["content-type"].startswith("application/json")
# Test enable endpoint
mock_user.two_factor_enabled = False
mock_user.two_factor_secret = "secret"
response = client.post("/api/auth/2fa/enable", json={"code": "123456"})
assert response.headers["content-type"].startswith("application/json")
# Test disable endpoint
mock_user.two_factor_enabled = True
response = client.post("/api/auth/2fa/disable", json={"code": "123456"})
assert response.headers["content-type"].startswith("application/json")