PDF-Assit_RAG / backend /tests /test_exception_handling.py
Param20h's picture
deploy: pure backend API with keywords fix
7c46845 unverified
Raw
History Blame Contribute Delete
6.29 kB
import asyncio
import json
from unittest.mock import Mock
import pytest
from fastapi import Request
from fastapi.exceptions import RequestValidationError
from slowapi.errors import RateLimitExceeded
from app.exceptions import (
AppException,
ConflictException,
ExternalServiceException,
ForbiddenException,
NotFoundException,
RateLimitException,
UnauthorizedException,
UnsafePromptException,
ValidationException,
)
from app.main import (
app_exception_handler,
rate_limit_handler,
unhandled_exception_handler,
validation_exception_handler,
)
def _mock_request(request_id="test-123"):
request = Mock()
request.state = Mock(request_id=request_id)
return request
# ──────────────────────────────────────────────
# Exception class instantiation (uncovered lines)
# ──────────────────────────────────────────────
def test_app_exception_defaults():
exc = AppException("CODE", "msg", 500)
assert exc.code == "CODE"
assert exc.message == "msg"
assert exc.status_code == 500
assert exc.details == {}
def test_app_exception_with_details():
exc = AppException("CODE", "msg", 400, {"key": "val"})
assert exc.details == {"key": "val"}
def test_not_found_with_identifier():
exc = NotFoundException("document", "42")
assert exc.code == "DOCUMENT_NOT_FOUND"
assert "42" in exc.message
assert exc.status_code == 404
assert exc.details == {"document": "42"}
def test_not_found_without_identifier():
exc = NotFoundException("user")
assert exc.code == "USER_NOT_FOUND"
assert exc.status_code == 404
assert exc.details == {}
def test_unauthorized_default():
exc = UnauthorizedException()
assert exc.code == "UNAUTHORIZED"
assert exc.status_code == 401
def test_forbidden_default():
exc = ForbiddenException()
assert exc.code == "FORBIDDEN"
assert exc.status_code == 403
def test_conflict():
exc = ConflictException("Username taken")
assert exc.code == "CONFLICT"
assert exc.status_code == 409
assert exc.message == "Username taken"
def test_conflict_with_details():
exc = ConflictException("Conflict", {"field": "username"})
assert exc.details == {"field": "username"}
def test_validation():
exc = ValidationException("bad input", {"field": "email"})
assert exc.code == "VALIDATION_ERROR"
assert exc.status_code == 400
assert exc.details == {"field": "email"}
def test_rate_limit():
exc = RateLimitException()
assert exc.code == "RATE_LIMIT_EXCEEDED"
assert exc.status_code == 429
assert "Rate limit" in exc.message
def test_external_service():
exc = ExternalServiceException("openai", "key expired")
assert exc.code == "OPENAI_ERROR"
assert exc.status_code == 502
assert exc.details == {"service": "openai"}
def test_external_service_default_message():
exc = ExternalServiceException("stripe")
assert "stripe" in exc.message
def test_unsafe_prompt():
exc = UnsafePromptException()
assert exc.code == "UNSAFE_PROMPT"
assert exc.status_code == 400
assert "prohibited" in exc.message
def test_unsafe_prompt_custom_message():
exc = UnsafePromptException("Custom block message")
assert exc.message == "Custom block message"
# ──────────────────────────────────────────────
# Exception handler response format (uncovered lines)
# ──────────────────────────────────────────────
def test_app_exception_handler():
request = _mock_request()
exc = AppException("NOT_FOUND", "nope", 404, {"id": "1"})
response = asyncio.run(app_exception_handler(request, exc))
assert response.status_code == 404
body = json.loads(response.body)
assert body == {
"error": {
"code": "NOT_FOUND",
"message": "nope",
"details": {"id": "1"},
"request_id": "test-123",
}
}
def test_app_exception_handler_no_request_id():
request = Mock()
request.state = Mock(spec_set=[])
response = asyncio.run(
app_exception_handler(request, AppException("E", "e", 500))
)
body = json.loads(response.body)
assert body["error"]["request_id"] is None
def test_rate_limit_handler():
request = _mock_request()
exc = RateLimitExceeded()
response = asyncio.run(rate_limit_handler(request, exc))
assert response.status_code == 429
body = json.loads(response.body)
assert body["error"]["code"] == "RATE_LIMIT_EXCEEDED"
assert body["error"]["request_id"] == "test-123"
def test_validation_exception_handler():
request = _mock_request()
exc = RequestValidationError(
errors=[
{
"loc": ("body", "email"),
"msg": "field required",
"type": "value_error",
}
]
)
response = asyncio.run(validation_exception_handler(request, exc))
assert response.status_code == 422
body = json.loads(response.body)
assert body["error"]["code"] == "VALIDATION_ERROR"
assert body["error"]["details"]["errors"][0]["field"] == "body -> email"
def test_unhandled_exception_handler(monkeypatch):
monkeypatch.setattr("app.main.settings.DEBUG", False)
request = _mock_request()
exc = ValueError("unexpected")
response = asyncio.run(unhandled_exception_handler(request, exc))
assert response.status_code == 500
body = json.loads(response.body)
assert body["error"]["code"] == "INTERNAL_ERROR"
assert body["error"]["message"] == "An unexpected error occurred"
def test_unhandled_exception_handler_debug_raises(monkeypatch):
monkeypatch.setattr("app.main.settings.DEBUG", True)
request = _mock_request()
exc = ValueError("unexpected")
try:
raise exc
except ValueError:
with pytest.raises(ValueError, match="unexpected"):
asyncio.run(unhandled_exception_handler(request, exc))