Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distributed error reporting: Endpoint /api/errorreports/report to store frontend error #12261

1 change: 1 addition & 0 deletions kolibri/core/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
re_path(r"^discovery/", include("kolibri.core.discovery.api_urls")),
re_path(r"^notifications/", include("kolibri.core.analytics.api_urls")),
re_path(r"^public/", include("kolibri.core.public.api_urls")),
re_path(r"^errorreports/", include("kolibri.core.errorreports.api_urls")),
]
42 changes: 42 additions & 0 deletions kolibri/core/errorreports/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import logging

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from .constants import FRONTEND
from .models import ErrorReports
from .serializers import ErrorReportSerializer

logger = logging.getLogger(__name__)


@api_view(["POST"])
def report(request):
"""
test with:
curl -X POST http://localhost:8000/api/errorreports/report/ -H "Content-Type: application/json" -d '{
"error_message": "An example error occurred",
"traceback": "Traceback (most recent call last): ..."
}'
"""
serializer = ErrorReportSerializer(data=request.data)
if serializer.is_valid():
data = serializer.validated_data
try:
error = ErrorReports.insert_or_update_error(
error_from=FRONTEND,
error_message=data["error_message"],
traceback=data["traceback"],
)
return Response(
{"error_id": error.id if error else None}, status=status.HTTP_200_OK
)
except (AttributeError, Exception) as e:
logger.error("Error while saving error report: {}".format(e))
return Response(
{"error": "Error while saving error report"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
5 changes: 5 additions & 0 deletions kolibri/core/errorreports/api_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.urls import re_path

from .api import report

urlpatterns = [re_path(r"^report", report, name="report")]
6 changes: 6 additions & 0 deletions kolibri/core/errorreports/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework import serializers


class ErrorReportSerializer(serializers.Serializer):
error_message = serializers.CharField(max_length=255)
traceback = serializers.CharField()
83 changes: 83 additions & 0 deletions kolibri/core/errorreports/test/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from unittest.mock import patch

from django.db.utils import IntegrityError
from django.test import TestCase
from django.urls import reverse
from rest_framework.status import HTTP_200_OK
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR
from rest_framework.test import APIClient


class FrontendReportTestCase(TestCase):
databases = "__all__"

def setUp(self):
self.client = APIClient()

def test_frontend_report(self):
url = reverse("kolibri:core:report")
data = {
"error_message": "Something went wrong",
"traceback": "Traceback information",
}
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, HTTP_200_OK)

def test_frontend_report_invalid_data(self):
url = reverse("kolibri:core:report")
data = {
"error_message": "",
"traceback": "Traceback information",
}
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)

@patch(
"kolibri.core.errorreports.models.ErrorReports.insert_or_update_error",
side_effect=AttributeError("Mocked AttributeError"),
)
def test_frontend_report_server_error_attribute_error(
self, mock_insert_or_update_error
):
url = reverse("kolibri:core:report")
data = {
"error_message": "Something went wrong",
"traceback": "Traceback information",
}
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, HTTP_500_INTERNAL_SERVER_ERROR)
self.assertIn("error", response.data)

@patch(
"kolibri.core.errorreports.models.ErrorReports.insert_or_update_error",
side_effect=Exception("Mocked exception"),
)
def test_frontend_report_server_error_general_exception(
self, mock_insert_or_update_error
):
url = reverse("kolibri:core:report")
data = {
"error_message": "Something went wrong",
"traceback": "Traceback information",
}
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, HTTP_500_INTERNAL_SERVER_ERROR)
self.assertIn("error", response.data)

@patch(
"kolibri.core.errorreports.models.ErrorReports.insert_or_update_error",
side_effect=IntegrityError("Mocked exception integrity error"),
)
def test_frontend_report_server_error_any_other_exception(
self, mock_insert_or_update_error
):
# this is to check that anything other than AttributeError or Exception can be caught
url = reverse("kolibri:core:report")
data = {
"error_message": "Something went wrong",
"traceback": "Traceback information",
}
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, HTTP_500_INTERNAL_SERVER_ERROR)
self.assertIn("error", response.data)