diff --git a/requirements/base.txt b/requirements/base.txt index e45138b3..94697714 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -54,7 +54,7 @@ click-plugins==1.1.1 # via celery click-repl==0.2.0 # via celery -commonground-api-common==2.10.7 +commonground-api-common==2.11.0 # via open-api-framework cryptography==44.0.1 # via diff --git a/requirements/ci.txt b/requirements/ci.txt index 5f6dfa20..59c8dca1 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -105,7 +105,7 @@ click-repl==0.2.0 # celery codecov==2.1.13 # via -r requirements/test-tools.in -commonground-api-common==2.10.7 +commonground-api-common==2.11.0 # via # -c requirements/base.txt # -r requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index 5c2520ba..17f2fd9c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -129,7 +129,7 @@ codecov==2.1.13 # via # -c requirements/ci.txt # -r requirements/ci.txt -commonground-api-common==2.10.7 +commonground-api-common==2.11.0 # via # -c requirements/ci.txt # -r requirements/ci.txt diff --git a/src/objecttypes/conf/api.py b/src/objecttypes/conf/api.py index e0ba283f..cc9ccb28 100644 --- a/src/objecttypes/conf/api.py +++ b/src/objecttypes/conf/api.py @@ -20,7 +20,7 @@ "DEFAULT_VERSION": "v2", # NOT to be confused with API_VERSION - it's the major version part "ALLOWED_VERSIONS": ("v2",), "VERSION_PARAM": "version", - "EXCEPTION_HANDLER": "objecttypes.utils.views.exception_handler", + "EXCEPTION_HANDLER": "vng_api_common.exception_handling.exception_handler", # test "TEST_REQUEST_DEFAULT_FORMAT": "json", } diff --git a/src/objecttypes/tests/v2/test_validation.py b/src/objecttypes/tests/v2/test_validation.py index 4866cf66..c3b047db 100644 --- a/src/objecttypes/tests/v2/test_validation.py +++ b/src/objecttypes/tests/v2/test_validation.py @@ -2,6 +2,7 @@ from rest_framework import status from rest_framework.test import APITestCase +from vng_api_common.tests import get_validation_errors from objecttypes.core.constants import ObjectVersionStatus from objecttypes.core.tests.factories import ObjectTypeFactory, ObjectVersionFactory @@ -19,8 +20,8 @@ def test_patch_objecttype_with_uuid_fail(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() - self.assertEqual(data["uuid"], ["This field can't be changed"]) + error = get_validation_errors(response, "uuid") + self.assertEqual(error["reason"], "This field can't be changed") def test_delete_objecttype_with_versions_fail(self): object_type = ObjectTypeFactory.create() @@ -31,12 +32,10 @@ def test_delete_objecttype_with_versions_fail(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() + error = get_validation_errors(response, "nonFieldErrors") self.assertEqual( - data["non_field_errors"], - [ - "All related versions should be destroyed before destroying the objecttype" - ], + error["reason"], + "All related versions should be destroyed before destroying the objecttype", ) @@ -54,8 +53,10 @@ def test_create_version_with_incorrect_schema_fail(self): response = self.client.post(url, data) + error = get_validation_errors(response, "jsonSchema") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertTrue("jsonSchema" in response.json()) + self.assertIsNotNone(error) def test_create_version_with_incorrect_objecttype_fail(self): url = reverse("objectversion-list", args=[uuid.uuid4()]) @@ -72,9 +73,8 @@ def test_create_version_with_incorrect_objecttype_fail(self): response = self.client.post(url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()["non_field_errors"], ["Objecttype url is invalid"] - ) + error = get_validation_errors(response, "nonFieldErrors") + self.assertEqual(error["reason"], "Objecttype url is invalid") def test_update_published_version_fail(self): object_type = ObjectTypeFactory.create() @@ -96,10 +96,8 @@ def test_update_published_version_fail(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() - self.assertEqual( - data["non_field_errors"], ["Only draft versions can be changed"] - ) + error = get_validation_errors(response, "nonFieldErrors") + self.assertEqual(error["reason"], "Only draft versions can be changed") def test_delete_puclished_version_fail(self): object_type = ObjectTypeFactory.create() @@ -114,7 +112,5 @@ def test_delete_puclished_version_fail(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() - self.assertEqual( - data["non_field_errors"], ["Only draft versions can be destroyed"] - ) + error = get_validation_errors(response, "nonFieldErrors") + self.assertEqual(error["reason"], "Only draft versions can be destroyed") diff --git a/src/objecttypes/urls.py b/src/objecttypes/urls.py index 517e2916..f167e991 100644 --- a/src/objecttypes/urls.py +++ b/src/objecttypes/urls.py @@ -56,6 +56,7 @@ name="home", ), path("oidc/", include("mozilla_django_oidc.urls")), + path("ref/", include("vng_api_common.urls")), path("api/", include("objecttypes.api.urls")), ] diff --git a/src/objecttypes/utils/tests/test_exception_handler.py b/src/objecttypes/utils/tests/test_exception_handler.py index c933cccb..1d8a35cd 100644 --- a/src/objecttypes/utils/tests/test_exception_handler.py +++ b/src/objecttypes/utils/tests/test_exception_handler.py @@ -5,8 +5,7 @@ from rest_framework.test import APITestCase from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.transport import Transport - -from ..views import exception_handler +from vng_api_common.exception_handling import exception_handler class InMemoryTransport(Transport): diff --git a/src/objecttypes/utils/views.py b/src/objecttypes/utils/views.py index 746f682d..78da619c 100644 --- a/src/objecttypes/utils/views.py +++ b/src/objecttypes/utils/views.py @@ -1,60 +1,13 @@ -from django.utils.translation import gettext_lazy as _ from django.views.generic import RedirectView -import sentry_sdk import structlog from drf_spectacular.views import ( SpectacularJSONAPIView as _SpectacularJSONAPIView, SpectacularYAMLAPIView as _SpectacularYAMLAPIView, ) -from open_api_framework.conf.utils import config -from rest_framework import status -from rest_framework.response import Response -from rest_framework.views import exception_handler as drf_exception_handler logger = structlog.stdlib.get_logger(__name__) -DEFAULT_CODE = "invalid" -DEFAULT_DETAIL = _("Invalid input.") - - -def exception_handler(exc, context): - """ - Transform 5xx errors into DSO-compliant shape. - """ - response = drf_exception_handler(exc, context) - if not response: - if config("DEBUG", default=False): - return None - - data = { - "code": "error", - "title": "Internal Server Error", - "status": status.HTTP_500_INTERNAL_SERVER_ERROR, - "detail": _("A server error has occurred."), - } - event = "api.uncaught_exception" - - # make sure the exception still ends up in Sentry - sentry_sdk.capture_exception(exc) - - response = Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data=data) - logger.exception(event, exc_info=exc) - - return response - - # exception logger event - logger.exception( - "api.handled_exception", - title=getattr(exc, "default_detail", DEFAULT_DETAIL).strip("'"), - code=getattr(exc, "default_code", DEFAULT_CODE), - status=getattr(response, "status_code", status.HTTP_400_BAD_REQUEST), - data=getattr(response, "data", {}), - exc_info=False, - ) - - return response - class AllowAllOriginsMixin: def dispatch(self, request, *args, **kwargs):