datasette/tests/test_internals_response.py
Claude 8b159144a5
Add Response.error() for JSON errors in the standard format
Response.error(messages, status=400) builds a JSON error response in
Datasette's standard error format, alongside Response.json/html/text.
messages can be a single string or a list. All internal error response
construction now uses it - the private views.base._error() helper is
gone and the verbose Response.json(error_body(...), status=...) sites
are converted. error_body() remains for the cases that merge the error
keys into a larger payload (the JSON renderer, handle_exception and the
permission debug payload builders).

Since Response is public plugin API, plugins that build JSON endpoints
now have an obvious way to return errors in the canonical shape.
Documented in the internals documentation, including the guidance to
raise Forbidden/NotFound/BadRequest/DatasetteError instead when the
error should content-negotiate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ
2026-07-06 23:48:49 +00:00

78 lines
2.2 KiB
Python

from datasette.utils.asgi import Response
import json
import pytest
def test_response_html():
response = Response.html("Hello from HTML")
assert 200 == response.status
assert "Hello from HTML" == response.body
assert "text/html; charset=utf-8" == response.content_type
def test_response_text():
response = Response.text("Hello from text")
assert 200 == response.status
assert "Hello from text" == response.body
assert "text/plain; charset=utf-8" == response.content_type
def test_response_json():
response = Response.json({"this_is": "json"})
assert 200 == response.status
assert '{"this_is": "json"}' == response.body
assert "application/json; charset=utf-8" == response.content_type
def test_response_redirect():
response = Response.redirect("/foo")
assert 302 == response.status
assert "/foo" == response.headers["Location"]
@pytest.mark.asyncio
async def test_response_set_cookie():
events = []
async def send(event):
events.append(event)
response = Response.redirect("/foo")
response.set_cookie("foo", "bar", max_age=10, httponly=True)
await response.asgi_send(send)
assert [
{
"type": "http.response.start",
"status": 302,
"headers": [
[b"Location", b"/foo"],
[b"content-type", b"text/plain"],
[b"set-cookie", b"foo=bar; HttpOnly; Max-Age=10; Path=/; SameSite=lax"],
],
},
{"type": "http.response.body", "body": b""},
] == events
def test_response_error_single_message():
response = Response.error("Method not allowed", 405)
assert response.status == 405
assert response.content_type == "application/json; charset=utf-8"
assert json.loads(response.body) == {
"ok": False,
"error": "Method not allowed",
"errors": ["Method not allowed"],
"status": 405,
}
def test_response_error_message_list_and_default_status():
response = Response.error(["First problem", "Second problem"])
assert response.status == 400
assert json.loads(response.body) == {
"ok": False,
"error": "First problem; Second problem",
"errors": ["First problem", "Second problem"],
"status": 400,
}