269 lines
9.3 KiB
Python
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)
|
|
|