"""Tests for error handler middleware. Tests exception handling and error responses. """ from unittest.mock import patch from httpx import ASGITransport, AsyncClient from app.domain.exceptions import ( AlreadyExistsException, DomainException, ForbiddenException, NotFoundException, ValidationException, ) from app.main import app_factory class TestDomainExceptionHandlers: """Test suite for domain exception handlers.""" async def test_validation_exception(self) -> None: """Test ValidationException returns 400.""" with patch( "app.application.use_cases.get_post.GetPostUseCase.by_id", side_effect=ValidationException("Invalid input"), ): app = app_factory() transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/posts/12345678-1234-1234-1234-123456789abc") assert response.status_code == 400 data = response.json() assert data["error"] == "ValidationException" assert data["message"] == "Invalid input" assert "timestamp" in data assert "path" in data async def test_forbidden_exception(self) -> None: """Test ForbiddenException returns 403.""" with patch( "app.application.use_cases.get_post.GetPostUseCase.by_id", side_effect=ForbiddenException("Access denied"), ): app = app_factory() transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/posts/12345678-1234-1234-1234-123456789abc") assert response.status_code == 403 data = response.json() assert data["error"] == "ForbiddenException" assert data["message"] == "Access denied" async def test_not_found_exception(self) -> None: """Test NotFoundException returns 404.""" with patch( "app.application.use_cases.get_post.GetPostUseCase.by_id", side_effect=NotFoundException("Post not found"), ): app = app_factory() transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/posts/12345678-1234-1234-1234-123456789abc") assert response.status_code == 404 data = response.json() assert data["error"] == "NotFoundException" assert data["message"] == "Post not found" async def test_already_exists_exception(self) -> None: """Test AlreadyExistsException returns 409.""" with patch( "app.application.use_cases.get_post.GetPostUseCase.by_id", side_effect=AlreadyExistsException("Post already exists"), ): app = app_factory() transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/posts/12345678-1234-1234-1234-123456789abc") assert response.status_code == 409 data = response.json() assert data["error"] == "AlreadyExistsException" assert data["message"] == "Post already exists" async def test_generic_domain_exception(self) -> None: """Test generic DomainException returns 500.""" with patch( "app.application.use_cases.get_post.GetPostUseCase.by_id", side_effect=DomainException("Generic error"), ): app = app_factory() transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/posts/12345678-1234-1234-1234-123456789abc") assert response.status_code == 500 data = response.json() assert data["error"] == "DomainException" assert data["message"] == "Generic error" class TestHTTPExceptionHandler: """Test suite for HTTP exception handling.""" async def test_http_exception_structure(self) -> None: """Test HTTP exception response structure.""" # Test that exception handler is registered and produces correct format import json from dataclasses import dataclass, field from starlette.exceptions import HTTPException from app.infrastructure.middleware.error_handler import http_exception_handler # Create mock request @dataclass class MockURL: path: str = "/test" @dataclass class MockRequest: url: MockURL = field(default_factory=MockURL) exc = HTTPException(status_code=404, detail="Not found") response = await http_exception_handler(MockRequest(), exc) # type: ignore[arg-type] assert response.status_code == 404 body_bytes: bytes = response.body # type: ignore[assignment] data: dict[str, object] = json.loads(body_bytes.decode("utf-8")) assert data["error"] == "HTTPException" assert "message" in data class TestGenericExceptionHandler: """Test suite for generic exception handling.""" async def test_generic_exception_handler_function(self) -> None: """Test generic exception handler function directly.""" import json from dataclasses import dataclass, field from app.infrastructure.middleware.error_handler import ( generic_exception_handler, ) # Create mock request @dataclass class MockURL: path: str = "/test" @dataclass class MockRequest: url: MockURL = field(default_factory=MockURL) exc = RuntimeError("Internal error") response = await generic_exception_handler(MockRequest(), exc) # type: ignore[arg-type] assert response.status_code == 500 body_bytes: bytes = response.body # type: ignore[assignment] data: dict[str, object] = json.loads(body_bytes.decode("utf-8")) assert data["error"] == "InternalServerError" assert data["message"] == "An unexpected error occurred" assert "timestamp" in data assert "path" in data class TestGetStatusCode: """Test suite for get_status_code function.""" def test_validation_exception_status(self) -> None: """Test ValidationException maps to 400.""" from app.infrastructure.middleware.error_handler import get_status_code exc = ValidationException("Invalid") assert get_status_code(exc) == 400 def test_forbidden_exception_status(self) -> None: """Test ForbiddenException maps to 403.""" from app.infrastructure.middleware.error_handler import get_status_code exc = ForbiddenException("Forbidden") assert get_status_code(exc) == 403 def test_not_found_exception_status(self) -> None: """Test NotFoundException maps to 404.""" from app.infrastructure.middleware.error_handler import get_status_code exc = NotFoundException("Not found") assert get_status_code(exc) == 404 def test_already_exists_exception_status(self) -> None: """Test AlreadyExistsException maps to 409.""" from app.infrastructure.middleware.error_handler import get_status_code exc = AlreadyExistsException("Already exists") assert get_status_code(exc) == 409 def test_generic_exception_status(self) -> None: """Test generic DomainException maps to 500.""" from app.infrastructure.middleware.error_handler import get_status_code exc = DomainException("Generic") assert get_status_code(exc) == 500