Files
responder/tests/test_file_conditional.py
kennethreitz fde93a7658 v6.4.0: typed path parameters, sharper path-param OpenAPI, If-Range
- Typed path params on plain segments: /users/{id} with id: int (or UUID, …)
  now validates/coerces the path value and 422s on bad input (works for HTTP
  views, CBV methods, and WebSocket handlers).
- Path-param OpenAPI reflects a bare handler annotation, Path(...) alias/
  constraints/description, and the {id:uuid} convertor (string/format: uuid).
  Parameters are emitted per-operation (incl. per-CBV-method).
- If-Range support in resp.file()/stream_file()/download(): a stale resumable
  request falls back to the full body instead of a wrong partial.
- Type-only fixes for the new code: _if_range_matches None-guard; parameters
  annotation in the path-adapter builder.

Backwards-compatible. 580 tests pass; ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 02:08:50 -04:00

117 lines
3.3 KiB
Python

"""v5.4: stat-based ETag + Last-Modified for served files."""
from starlette.testclient import TestClient
import responder
def _app(tmp_path):
fp = tmp_path / "data.txt"
fp.write_text("hello world, this is a file")
api = responder.API(allowed_hosts=[";"], secret_key="x" * 32, session_https_only=False)
@api.route("/f")
def f(req, resp):
resp.file(str(fp))
@api.route("/s")
def s(req, resp):
resp.stream_file(str(fp))
@api.route("/d")
def d(req, resp):
resp.download(str(fp))
@api.route("/noc")
def noc(req, resp):
resp.file(str(fp), conditional=False)
return TestClient(api, base_url="http://;")
def test_file_sends_etag_and_last_modified(tmp_path):
r = _app(tmp_path).get("/f")
assert r.status_code == 200
assert r.headers["etag"].startswith('W/"')
assert "last-modified" in r.headers
def test_if_none_match_returns_304(tmp_path):
client = _app(tmp_path)
etag = client.get("/f").headers["etag"]
r = client.get("/f", headers={"If-None-Match": etag})
assert r.status_code == 304
assert r.content == b""
def test_if_modified_since_returns_304(tmp_path):
client = _app(tmp_path)
last_modified = client.get("/f").headers["last-modified"]
r = client.get("/f", headers={"If-Modified-Since": last_modified})
assert r.status_code == 304
def test_stream_and_download_are_conditional(tmp_path):
client = _app(tmp_path)
for path in ("/s", "/d"):
etag = client.get(path).headers["etag"]
assert client.get(path, headers={"If-None-Match": etag}).status_code == 304
def test_range_still_served(tmp_path):
r = _app(tmp_path).get("/f", headers={"Range": "bytes=0-4"})
assert r.status_code == 206
assert r.content == b"hello"
def test_if_range_last_modified_serves_partial(tmp_path):
client = _app(tmp_path)
last_modified = client.get("/f").headers["last-modified"]
r = client.get("/f", headers={"Range": "bytes=0-4", "If-Range": last_modified})
assert r.status_code == 206
assert r.content == b"hello"
def test_if_range_stale_last_modified_falls_back_to_full(tmp_path):
r = _app(tmp_path).get(
"/f",
headers={
"Range": "bytes=0-4",
"If-Range": "Thu, 01 Jan 1970 00:00:00 GMT",
},
)
assert r.status_code == 200
assert r.content == b"hello world, this is a file"
def test_if_range_strong_etag_is_honored(tmp_path):
fp = tmp_path / "data.txt"
fp.write_text("hello world, this is a file")
api = responder.API(allowed_hosts=[";"], secret_key="x" * 32, session_https_only=False)
@api.route("/f")
def f(req, resp):
resp.etag = '"v1"'
resp.file(str(fp))
client = TestClient(api, base_url="http://;")
r = client.get("/f", headers={"Range": "bytes=0-4", "If-Range": '"v1"'})
assert r.status_code == 206
assert r.content == b"hello"
def test_if_range_applies_to_downloads(tmp_path):
client = _app(tmp_path)
last_modified = client.get("/d").headers["last-modified"]
r = client.get("/d", headers={"Range": "bytes=0-4", "If-Range": last_modified})
assert r.status_code == 206
assert r.content == b"hello"
def test_conditional_opt_out(tmp_path):
r = _app(tmp_path).get("/noc")
assert "etag" not in r.headers
assert r.content == b"hello world, this is a file"