Skip to content

Commit 259b69a

Browse files
authored
fix(browser-reports): Support the correct media type (#93814)
Browser reports use `application/reports+json` rather than `application/json`.
1 parent b813f6e commit 259b69a

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

src/sentry/issues/endpoints/browser_reporting_collector.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import Any
23

34
from django.http import HttpResponse
45
from django.views.decorators.csrf import csrf_exempt
@@ -14,6 +15,15 @@
1415
logger = logging.getLogger(__name__)
1516

1617

18+
class BrowserReportsJSONParser(JSONParser):
19+
"""
20+
Custom parser for browser Reporting API that handles the application/reports+json content type.
21+
This extends JSONParser since the content is still JSON, just with a different media type.
22+
"""
23+
24+
media_type = "application/reports+json"
25+
26+
1727
@all_silo_endpoint
1828
class BrowserReportingCollectorEndpoint(Endpoint):
1929
"""
@@ -23,18 +33,17 @@ class BrowserReportingCollectorEndpoint(Endpoint):
2333
"""
2434

2535
permission_classes = ()
26-
# TODO: Do we need to specify this parser? Content type will be `application/reports+json`, so
27-
# it might just work automatically.
28-
parser_classes = [JSONParser]
36+
# Support both standard JSON and browser reporting API content types
37+
parser_classes = [BrowserReportsJSONParser, JSONParser]
2938
publish_status = {
3039
"POST": ApiPublishStatus.PRIVATE,
3140
}
3241
owner = ApiOwner.ISSUES
3342

34-
# TODO: It's unclear if either of these decorators is necessary
43+
# CSRF exemption and CORS support required for Browser Reporting API
3544
@csrf_exempt
3645
@allow_cors_options
37-
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
46+
def post(self, request: Request, *args: Any, **kwargs: Any) -> HttpResponse:
3847
if not options.get("issues.browser_reporting.collector_endpoint_enabled"):
3948
return HttpResponse(status=404)
4049

tests/sentry/api/endpoints/test_browser_reporting_collector.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ def test_404s_by_default(self):
3939
def test_logs_request_data_if_option_enabled(
4040
self, mock_logger_info: MagicMock, mock_metrics_incr: MagicMock
4141
):
42-
response = self.client.post(self.url, self.report_data)
42+
response = self.client.post(
43+
self.url, self.report_data, content_type="application/reports+json"
44+
)
4345

4446
assert response.status_code == status.HTTP_200_OK
4547
mock_logger_info.assert_any_call(
@@ -48,3 +50,15 @@ def test_logs_request_data_if_option_enabled(
4850
mock_metrics_incr.assert_any_call(
4951
"browser_reporting.raw_report_received", tags={"type": self.report_data["type"]}
5052
)
53+
54+
@override_options({"issues.browser_reporting.collector_endpoint_enabled": True})
55+
@patch("sentry.issues.endpoints.browser_reporting_collector.metrics.incr")
56+
def test_rejects_invalid_content_type(self, mock_metrics_incr: MagicMock):
57+
"""Test that the endpoint rejects invalid content type and does not call the browser reporting metric"""
58+
response = self.client.post(self.url, self.report_data, content_type="bad/type/json")
59+
60+
assert response.status_code == status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
61+
# Verify that the browser_reporting.raw_report_received metric was not called
62+
# Check that none of the calls were for the browser_reporting.raw_report_received metric
63+
for call in mock_metrics_incr.call_args_list:
64+
assert call[0][0] != "browser_reporting.raw_report_received"

0 commit comments

Comments
 (0)