Big-Link-Man/tests/unit/test_api_endpoints.py

269 lines
9.3 KiB
Python

"""
Unit tests for API endpoints
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import Mock, patch
from src.api.main import app, get_auth_service, get_current_user
from src.database.models import User
from src.auth.service import AuthService
@pytest.fixture
def client():
"""Create a test client for the API"""
return TestClient(app)
@pytest.fixture
def mock_user():
"""Create a mock user for testing"""
user = Mock(spec=User)
user.id = 1
user.username = "testuser"
user.role = "User"
user.is_admin.return_value = False
return user
@pytest.fixture
def mock_admin_user():
"""Create a mock admin user for testing"""
user = Mock(spec=User)
user.id = 2
user.username = "admin"
user.role = "Admin"
user.is_admin.return_value = True
return user
class TestHealthEndpoint:
"""Tests for the health check endpoint"""
def test_health_check_returns_200(self, client):
"""Test that health check endpoint returns 200 OK"""
response = client.get("/health")
assert response.status_code == 200
def test_health_check_returns_correct_structure(self, client):
"""Test that health check returns expected response structure"""
response = client.get("/health")
data = response.json()
assert "status" in data
assert "message" in data
assert data["status"] == "healthy"
assert data["message"] == "API is running"
def test_health_check_no_authentication_required(self, client):
"""Test that health check does not require authentication"""
# Should work without credentials
response = client.get("/health")
assert response.status_code == 200
class TestAuthenticatedEndpoints:
"""Tests for authenticated endpoints"""
def test_me_endpoint_without_auth_returns_401(self, client):
"""Test that /api/v1/me returns 401 without authentication"""
response = client.get("/api/v1/me")
assert response.status_code == 401
def test_me_endpoint_with_invalid_credentials_returns_401(self, client, mock_user):
"""Test that /api/v1/me returns 401 with invalid credentials"""
from src.api.main import app, get_auth_service
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = None
app.dependency_overrides[get_auth_service] = lambda: mock_service
try:
response = client.get(
"/api/v1/me",
auth=("invaliduser", "wrongpassword")
)
assert response.status_code == 401
finally:
app.dependency_overrides.clear()
def test_me_endpoint_with_valid_credentials_returns_user_info(self, client, mock_user):
"""Test that /api/v1/me returns user info with valid credentials"""
from src.api.main import app, get_auth_service
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = mock_user
app.dependency_overrides[get_auth_service] = lambda: mock_service
try:
response = client.get(
"/api/v1/me",
auth=("testuser", "password")
)
assert response.status_code == 200
data = response.json()
assert data["id"] == 1
assert data["username"] == "testuser"
assert data["role"] == "User"
finally:
app.dependency_overrides.clear()
class TestAdminEndpoints:
"""Tests for admin-only endpoints"""
def test_admin_status_without_auth_returns_401(self, client):
"""Test that admin endpoint returns 401 without authentication"""
response = client.get("/api/v1/admin/status")
assert response.status_code == 401
def test_admin_status_with_non_admin_user_returns_403(self, client, mock_user):
"""Test that admin endpoint returns 403 for non-admin users"""
from src.api.main import app, get_auth_service
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = mock_user
app.dependency_overrides[get_auth_service] = lambda: mock_service
try:
response = client.get(
"/api/v1/admin/status",
auth=("testuser", "password")
)
assert response.status_code == 403
data = response.json()
assert "detail" in data
assert "Admin privileges required" in data["detail"]
finally:
app.dependency_overrides.clear()
def test_admin_status_with_admin_user_returns_200(self, client, mock_admin_user):
"""Test that admin endpoint returns 200 for admin users"""
from src.api.main import app, get_auth_service
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = mock_admin_user
app.dependency_overrides[get_auth_service] = lambda: mock_service
try:
response = client.get(
"/api/v1/admin/status",
auth=("admin", "password")
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "admin_access_granted"
assert data["admin"] is True
assert "admin" in data["message"].lower()
finally:
app.dependency_overrides.clear()
class TestAuthenticationDependencies:
"""Tests for authentication dependency functions"""
def test_get_current_user_with_valid_credentials(self, mock_user):
"""Test get_current_user dependency with valid credentials"""
from fastapi.security import HTTPBasicCredentials
from src.api.main import get_current_user
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = mock_user
credentials = HTTPBasicCredentials(username="testuser", password="password")
result = get_current_user(credentials, mock_service)
assert result == mock_user
mock_service.authenticate_user.assert_called_once_with(
username="testuser",
password="password"
)
def test_get_current_user_with_invalid_credentials_raises_401(self):
"""Test get_current_user dependency raises 401 with invalid credentials"""
from fastapi.security import HTTPBasicCredentials
from fastapi import HTTPException
from src.api.main import get_current_user
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = None
credentials = HTTPBasicCredentials(username="invalid", password="wrong")
with pytest.raises(HTTPException) as exc_info:
get_current_user(credentials, mock_service)
assert exc_info.value.status_code == 401
assert "Invalid username or password" in exc_info.value.detail
def test_get_current_admin_user_with_admin(self, mock_admin_user):
"""Test get_current_admin_user dependency with admin user"""
from src.api.main import get_current_admin_user
result = get_current_admin_user(mock_admin_user)
assert result == mock_admin_user
def test_get_current_admin_user_with_non_admin_raises_403(self, mock_user):
"""Test get_current_admin_user dependency raises 403 for non-admin"""
from fastapi import HTTPException
from src.api.main import get_current_admin_user
with pytest.raises(HTTPException) as exc_info:
get_current_admin_user(mock_user)
assert exc_info.value.status_code == 403
assert "Admin privileges required" in exc_info.value.detail
class TestAPIResponseSchemas:
"""Tests for API response schemas"""
def test_health_response_schema(self, client):
"""Test that health endpoint follows schema"""
response = client.get("/health")
data = response.json()
# Verify schema compliance
assert isinstance(data["status"], str)
assert isinstance(data["message"], str)
def test_user_response_schema(self, client, mock_user):
"""Test that user endpoint follows schema"""
from src.api.main import app, get_auth_service
mock_service = Mock(spec=AuthService)
mock_service.authenticate_user.return_value = mock_user
app.dependency_overrides[get_auth_service] = lambda: mock_service
try:
response = client.get(
"/api/v1/me",
auth=("testuser", "password")
)
data = response.json()
# Verify schema compliance
assert isinstance(data["id"], int)
assert isinstance(data["username"], str)
assert isinstance(data["role"], str)
finally:
app.dependency_overrides.clear()
def test_error_response_schema(self, client):
"""Test that error responses follow schema"""
response = client.get("/api/v1/me")
data = response.json()
# Verify error schema compliance
assert "detail" in data
assert isinstance(data["detail"], str)