from collections.abc import Iterator from typing import Any import httpx import pytest from fansifter_common.auth.exceptions import NotAuthenticated, PermissionDenied from fansifter_common.exceptions import FansifterError from fastapi import FastAPI, HTTPException from pytest_mock import MockerFixture from starlette import status from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.testclient import TestClient from dmp.adapters.fivetran.exceptions import FivetranClientError from dmp.adapters.meta.exceptions import FacebookClientError def raise_exception() -> None: pass def exc_route() -> Any: raise_exception() @pytest.fixture def test_client(app: FastAPI) -> Iterator[TestClient]: with TestClient(app, raise_server_exceptions=False) as client: yield client @pytest.fixture(autouse=True) def setup_error_routes(app: FastAPI) -> Iterator[None]: app.debug = False app.add_api_route("/exc", endpoint=exc_route) yield app.debug = True class CustomError(FansifterError): code = "custom_error" message = "Custom error" additional_properties = ( ("param_one", str), ("param_two", int), ) def __init__(self, param_one: str, param_two: int) -> None: super().__init__() self.param_one = param_one self.param_two = param_two @pytest.mark.parametrize( "exc, status_code, content", [ ( HTTPException(status_code=400, detail="Bad request"), status.HTTP_400_BAD_REQUEST, { "code": "bad_request", "message": "Bad request", }, ), ( StarletteHTTPException(status_code=400, detail="Bad request"), status.HTTP_400_BAD_REQUEST, { "code": "bad_request", "message": "Bad request", }, ), ( FansifterError(), status.HTTP_500_INTERNAL_SERVER_ERROR, {"code": "fansifter_error", "message": "Fansifter error"}, ), ( FansifterError(message="Some error"), status.HTTP_500_INTERNAL_SERVER_ERROR, {"code": "fansifter_error", "message": "Some error"}, ), ( CustomError(param_one="one", param_two=2), status.HTTP_500_INTERNAL_SERVER_ERROR, { "code": "custom_error", "message": "Custom error", "paramOne": "one", "paramTwo": 2, }, ), ( NotAuthenticated, status.HTTP_401_UNAUTHORIZED, { "code": "authorization_error", "message": "Unauthorized", }, ), ( NotAuthenticated("missing header"), status.HTTP_401_UNAUTHORIZED, { "code": "authorization_error", "message": "missing header", }, ), ( PermissionDenied("not allowed"), status.HTTP_403_FORBIDDEN, { "code": "permission_denied", "message": "not allowed", }, ), ( FacebookClientError, status.HTTP_500_INTERNAL_SERVER_ERROR, { "code": "facebook_client_error", "message": "Facebook client error", "fb_error": {}, }, ), ( FacebookClientError("Some error", request=None, response=None), status.HTTP_500_INTERNAL_SERVER_ERROR, { "code": "facebook_client_error", "message": "Some error", "fb_error": {}, }, ), ( FacebookClientError( request=None, response=httpx.Response( status_code=400, json={"error": {"message": "Some error", "code": 200}}, ), ), status.HTTP_400_BAD_REQUEST, { "code": "facebook_client_error", "message": "Facebook client error", "fb_error": {"message": "Some error", "code": 200}, }, ), ( FivetranClientError("Some error", request=None, response=None), status.HTTP_500_INTERNAL_SERVER_ERROR, { "code": "fivetran_client_error", "message": "Some error", }, ), ( FivetranClientError( request=None, response=httpx.Response( status_code=400, json={"message": "Some error", "code": 200}, ), ), status.HTTP_400_BAD_REQUEST, { "code": "fivetran_client_error", "message": "Fivetran client error", }, ), ], ) def test_error_handler( exc: Exception | type[Exception], status_code: int, content: Any, test_client: TestClient, mocker: MockerFixture, ) -> None: mocker.patch(f"{__name__}.raise_exception", side_effect=exc) response = test_client.get("/exc") assert response.status_code == status_code assert response.json() == content