Files
sdb2-backend/tests/core/test_api_token_dependencies.py
JSC bccfcafe0e
Some checks failed
Backend CI / lint (push) Failing after 10s
Backend CI / test (push) Failing after 1m37s
feat: Update CORS origins to allow Chrome extensions and improve logging in migration tool
2025-09-19 16:41:11 +02:00

235 lines
8.1 KiB
Python

"""Tests for API token authentication dependencies."""
from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock
import pytest
from fastapi import HTTPException
from app.core.dependencies import get_current_user_api_token, get_current_user_flexible
from app.models.user import User
from app.services.auth import AuthService
# Constants
HTTP_401_UNAUTHORIZED = 401
class TestApiTokenDependencies:
"""Test API token authentication dependencies."""
@pytest.fixture
def mock_auth_service(self) -> AsyncMock:
"""Create a mock auth service."""
return AsyncMock(spec=AuthService)
@pytest.fixture
def test_user(self) -> User:
"""Create a test user."""
return User(
id=1,
email="test@example.com",
name="Test User",
role="user",
is_active=True,
plan_id=1,
credits=100,
api_token="test_api_token_123",
api_token_expires_at=datetime.now(UTC) + timedelta(days=30),
)
@pytest.mark.asyncio
async def test_get_current_user_api_token_success(
self,
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test successful API token authentication."""
mock_auth_service.get_user_by_api_token.return_value = test_user
api_token_header = "test_api_token_123"
result = await get_current_user_api_token(mock_auth_service, api_token_header)
assert result == test_user
mock_auth_service.get_user_by_api_token.assert_called_once_with(
"test_api_token_123",
)
@pytest.mark.asyncio
async def test_get_current_user_api_token_no_header(
self,
mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication without API-TOKEN header."""
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, None)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API-TOKEN header required" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_empty_token(
self,
mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with empty token."""
api_token_header = " "
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API token required" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_whitespace_token(
self,
mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with whitespace-only token."""
api_token_header = " "
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API token required" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_invalid_token(
self,
mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with invalid token."""
mock_auth_service.get_user_by_api_token.return_value = None
api_token_header = "invalid_token"
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Invalid API token" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_expired_token(
self,
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test API token authentication with expired token."""
# Set expired token
test_user.api_token_expires_at = datetime.now(UTC) - timedelta(days=1)
mock_auth_service.get_user_by_api_token.return_value = test_user
api_token_header = "expired_token"
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API token has expired" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_inactive_user(
self,
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test API token authentication with inactive user."""
test_user.is_active = False
mock_auth_service.get_user_by_api_token.return_value = test_user
api_token_header = "test_token"
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Account is deactivated" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_service_exception(
self,
mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with service exception."""
mock_auth_service.get_user_by_api_token.side_effect = Exception(
"Database error",
)
api_token_header = "test_token"
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Could not validate API token" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_flexible_uses_api_token(
self,
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test flexible authentication uses API token when available."""
mock_auth_service.get_user_by_api_token.return_value = test_user
api_token_header = "test_api_token_123"
access_token = "jwt_token"
result = await get_current_user_flexible(
mock_auth_service,
access_token,
api_token_header,
)
assert result == test_user
mock_auth_service.get_user_by_api_token.assert_called_once_with(
"test_api_token_123",
)
@pytest.mark.asyncio
async def test_get_current_user_flexible_falls_back_to_jwt(
self,
mock_auth_service: AsyncMock,
) -> None:
"""Test flexible authentication falls back to JWT when no API token."""
# Mock the get_current_user function (normally imported)
with pytest.raises(Exception, match=r"Database error|Could not validate"):
# This will fail because we can't easily mock the get_current_user import
# In a real test, you'd mock the import or use dependency injection
await get_current_user_flexible(mock_auth_service, "jwt_token", None)
@pytest.mark.asyncio
async def test_api_token_no_expiry_never_expires(
self,
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test API token with no expiry date never expires."""
test_user.api_token_expires_at = None
mock_auth_service.get_user_by_api_token.return_value = test_user
api_token_header = "test_token"
result = await get_current_user_api_token(mock_auth_service, api_token_header)
assert result == test_user
@pytest.mark.asyncio
async def test_api_token_with_whitespace(
self,
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test API token with leading/trailing whitespace is handled correctly."""
mock_auth_service.get_user_by_api_token.return_value = test_user
api_token_header = " test_token "
result = await get_current_user_api_token(mock_auth_service, api_token_header)
assert result == test_user
mock_auth_service.get_user_by_api_token.assert_called_once_with("test_token")