""" 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)