import re import pytest from django.db import IntegrityError from rest_framework.exceptions import NotFound from rest_framework.test import APIRequestFactory from rest_framework.views import APIView from enhanced_drf_jsonapi.exceptions import ( ERROR_ID_HEADER, APIConflictException, HardenedExceptionHandlingMixin, hardened_exception_handler, ) class ExceptionView(HardenedExceptionHandlingMixin, APIView): authentication_classes = [] permission_classes = [] exception = None def get(self, request): raise self.exception def get_response(exception): view = ExceptionView.as_view(exception=exception) request = APIRequestFactory().get( "/failure", HTTP_ACCEPT="application/vnd.api+json", ) response = view(request) response.render() return response def test_expected_api_exception_keeps_its_status_without_error_id(): response = get_response(NotFound("missing")) assert response.status_code == 404 assert ERROR_ID_HEADER not in response assert b"missing" in response.rendered_content def test_deliberate_conflict_is_a_sanitized_409(): response = get_response(APIConflictException()) assert response.status_code == 409 assert ERROR_ID_HEADER not in response assert b"current resource state" in response.rendered_content @pytest.mark.parametrize( "exception", [RuntimeError("database-password=secret"), IntegrityError("private row value")], ) def test_unexpected_exception_is_logged_and_sanitized(exception, caplog): with caplog.at_level("ERROR"): response = get_response(exception) assert response.status_code == 500 assert re.fullmatch(r"[0-9a-f]{32}", response[ERROR_ID_HEADER]) assert b"unexpected server error" in response.rendered_content assert str(exception).encode() not in response.rendered_content assert response[ERROR_ID_HEADER] in caplog.text def test_mixin_selects_the_hardened_handler(): assert ExceptionView().get_exception_handler() is hardened_exception_handler