diff --git a/tabbycat/api/exceptions.py b/tabbycat/api/exceptions.py new file mode 100644 index 00000000000..207c7839d03 --- /dev/null +++ b/tabbycat/api/exceptions.py @@ -0,0 +1,24 @@ +import logging + +from django.conf import settings +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import exception_handler as drf_exception_handler + +logger = logging.getLogger(__name__) + + +def api_exception_handler(exc, context): + """Return JSON for unhandled errors instead of letting Django serve the HTML 500 page.""" + response = drf_exception_handler(exc, context) + if response is not None: + return response + + if settings.DEBUG: + return + + logger.exception("Unhandled exception in API request") + + data = {'detail': 'A server error occurred.'} + + return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/tabbycat/api/tests/test_views.py b/tabbycat/api/tests/test_views.py index 538e7ac2896..8f06c2cc04b 100644 --- a/tabbycat/api/tests/test_views.py +++ b/tabbycat/api/tests/test_views.py @@ -1,4 +1,5 @@ import json +from unittest.mock import patch from django.conf import settings from django.test import Client @@ -33,6 +34,17 @@ def test_get_v1_root(self): }) +class APIExceptionHandlerTests(CompletedTournamentTestMixin, APITestCase): + + @patch('api.views.TournamentViewSet.list', side_effect=RuntimeError('deliberate failure')) + def test_unhandled_exception_returns_json(self, _mock): + response = self.client.get(reverse('api-tournament-list')) + self.assertEqual(response.status_code, 500) + self.assertIn('application/json', response['Content-Type']) + payload = json.loads(response.content) + self.assertIn('detail', payload) + + class TournamentViewsetTests(CompletedTournamentTestMixin, APITestCase): def test_get_tournament_list(self): diff --git a/tabbycat/settings/core.py b/tabbycat/settings/core.py index 446c7f8e3b4..f5fcd109a3c 100644 --- a/tabbycat/settings/core.py +++ b/tabbycat/settings/core.py @@ -366,6 +366,7 @@ # ============================================================================== REST_FRAMEWORK = { + 'EXCEPTION_HANDLER': 'api.exceptions.api_exception_handler', 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ],