diff --git a/swh/web/api/apiresponse.py b/swh/web/api/apiresponse.py --- a/swh/web/api/apiresponse.py +++ b/swh/web/api/apiresponse.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2019 The Software Heritage developers +# Copyright (C) 2017-2021 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -136,10 +136,12 @@ # get request status code doc_data["status_code"] = options.get("status", 200) + accepted_media_type = getattr(request, "accepted_media_type", "application/json") + # when requesting HTML, typically when browsing the API through its # documented views, we need to enrich the input data with documentation # and render the apidoc HTML template - if request.accepted_media_type == "text/html": + if accepted_media_type == "text/html": doc_data["response_data"] = data if data is not None: doc_data["response_data"] = json.dumps( @@ -166,7 +168,7 @@ response = Response( data, headers=headers, - content_type=request.accepted_media_type, + content_type=accepted_media_type, status=doc_data["status_code"], ) @@ -209,7 +211,7 @@ "reason": str(exception), } - if request.accepted_media_type == "text/html": + if getattr(request, "accepted_media_type", None) == "text/html": error_data["reason"] = escape(error_data["reason"]) if get_config()["debug"]: diff --git a/swh/web/api/views/graph.py b/swh/web/api/views/graph.py --- a/swh/web/api/views/graph.py +++ b/swh/web/api/views/graph.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 The Software Heritage developers +# Copyright (C) 2020-2021 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -11,6 +11,7 @@ from django.http.response import StreamingHttpResponse from rest_framework.decorators import renderer_classes +from rest_framework.renderers import JSONRenderer from rest_framework.request import Request from rest_framework.response import Response @@ -123,7 +124,7 @@ @api_route(r"/graph/(?P.+)/", "api-1-graph") -@renderer_classes([PlainTextRenderer]) +@renderer_classes([JSONRenderer, PlainTextRenderer]) def api_graph_proxy( request: Request, graph_query: str ) -> Union[Response, StreamingHttpResponse]: @@ -142,7 +143,7 @@ # graph stats and counter endpoint responses are not streamed if response.headers.get("Transfer-Encoding") != "chunked": return Response( - response.text, + response.json(), status=response.status_code, content_type=response.headers["Content-Type"], ) diff --git a/swh/web/tests/api/views/test_graph.py b/swh/web/tests/api/views/test_graph.py --- a/swh/web/tests/api/views/test_graph.py +++ b/swh/web/tests/api/views/test_graph.py @@ -4,7 +4,6 @@ # See top-level LICENSE file for more information import hashlib -import json import textwrap from django.http.response import StreamingHttpResponse @@ -126,7 +125,7 @@ resp = check_http_get_response(api_client, url, status_code=200) assert resp.content_type == "application/json" - assert resp.content == json.dumps(_response_json).encode() + assert resp.data == _response_json def test_graph_ndjson_response(api_client, keycloak_oidc, requests_mock): @@ -254,4 +253,18 @@ resp = check_http_get_response(api_client, url, status_code=200) assert resp.content_type == "application/json" - assert resp.content == json.dumps(_response_json).encode() + assert resp.data == _response_json + + +def test_graph_response_invalid_accept_header(api_client): + url = reverse( + "api-1-graph", + url_args={"graph_query": "stats"}, + query_params={"resolve_origins": "true"}, + ) + + resp = api_client.get(url, HTTP_ACCEPT="text/html") + assert resp.status_code == 406 + assert resp.content_type == "application/json" + assert resp.data["exception"] == "NotAcceptable" + assert resp.data["reason"] == "Could not satisfy the request Accept header."