Files
responder/tests/test_text_charset.py
kennethreitz ea5ba0d26b Add standalone Router composition; fix lifespan/dispatch/header correctness; 8.0 behavior changes
The first 8.0 sweep. Flagship: responder.Router records route declarations
without an API instance (Flask-blueprint style) and api.include_router()
replays them with prefix/tags/dependencies/auth composition, nesting, and
conflict guards.

Correctness: lifespan= now composes with on_event (background draining runs
again), view-style default routes dispatch instead of 500ing, CBV HEAD falls
back to on_get, tolerant cookie parsing, case-insensitive resp.headers,
async background tasks, async GraphQL resolvers, Jinja builtins survive
context assignment, identity-based dependency cycle detection, WSGI mounts
wrap once, WebSocket crashes send a 1011 close frame, and dependency
metadata matches actual imports.

Security: Content-Disposition filename escaping in resp.download(); bounded,
lock-guarded MemorySessionBackend with expiry-first eviction.

8.0 behavior changes: no implicit static/ mkdir, url_for raises
RouteNotFoundError and percent-encodes values, redirect() defaults to 307
(permanent=True for 308), charset= on encoded text responses (no more fake
'Encoding' header), and RFC 9512 application/yaml.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 17:05:08 -04:00

113 lines
3.3 KiB
Python

"""Text responses declare charset= in Content-Type and honor resp.encoding."""
def test_text_response_declares_charset(api):
@api.route("/")
def view(req, resp):
resp.text = "hello"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/plain; charset=utf-8"
assert r.content == b"hello"
def test_html_response_declares_charset(api):
@api.route("/")
def view(req, resp):
resp.html = "<h1>hi</h1>"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/html; charset=utf-8"
def test_no_nonstandard_encoding_header(api):
@api.route("/")
def view(req, resp):
resp.text = "hello"
r = api.requests.get("/")
assert "Encoding" not in r.headers
def test_html_honors_resp_encoding(api):
# resp.encoding used to be ignored for text/html: the str body fell
# through to Starlette's UTF-8 encode (mojibake for latin-1).
@api.route("/")
def view(req, resp):
resp.html = "<p>café</p>"
resp.encoding = "latin-1"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/html; charset=latin-1"
assert r.content == "<p>café</p>".encode("latin-1")
assert r.text == "<p>café</p>" # client decodes via the declared charset
def test_text_honors_resp_encoding(api):
@api.route("/")
def view(req, resp):
resp.text = "café"
resp.encoding = "latin-1"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/plain; charset=latin-1"
assert r.content == "café".encode("latin-1")
def test_explicit_charset_in_mimetype_is_not_doubled(api):
@api.route("/")
def view(req, resp):
resp.text = "hello"
resp.mimetype = "text/plain; charset=UTF-8"
r = api.requests.get("/")
assert r.headers["Content-Type"].lower().count("charset=") == 1
def test_bytes_body_with_text_type_gets_no_charset(api):
# A raw-bytes body has an unknown encoding; appending charset=utf-8
# would make clients mojibake e.g. Latin-1 content. 7.x sent the bare
# media type and let clients sniff.
@api.route("/")
def view(req, resp):
resp.content = "<p>café</p>".encode("latin-1")
resp.mimetype = "text/html"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/html"
assert r.content == "<p>café</p>".encode("latin-1")
def test_file_of_html_gets_no_charset(api, tmp_path):
page = tmp_path / "page.html"
page.write_bytes("<p>café</p>".encode("latin-1"))
@api.route("/")
def view(req, resp):
resp.file(page)
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/html"
assert r.content == "<p>café</p>".encode("latin-1")
def test_bytes_body_keeps_user_explicit_charset(api):
@api.route("/")
def view(req, resp):
resp.content = "<p>café</p>".encode("latin-1")
resp.mimetype = "text/html; charset=latin-1"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "text/html; charset=latin-1"
assert r.text == "<p>café</p>"
def test_non_text_content_type_gets_no_charset(api):
@api.route("/")
def view(req, resp):
resp.content = b"\x00\x01"
resp.mimetype = "application/octet-stream"
r = api.requests.get("/")
assert r.headers["Content-Type"] == "application/octet-stream"