mirror of
https://github.com/kennethreitz/responder.git
synced 2026-07-21 18:39:29 +00:00
9735659d4b
Correctness fixes: - Type-driven OpenAPI now hoists nested-model $defs into top-level components and rewrites $ref, so nested models resolve (Swagger/ReDoc/ codegen). Output is also down-converted to the declared dialect: under 3.0.x, Optional fields become `nullable` and `examples` is singularized. - Query/Header/Cookie/Path markers now enforce their constraints (min_length/gt/... via Annotated+Field) and 422 on violation; a typo'd kwarg raises TypeError instead of silently making the param required; description/deprecated now reach the spec. - enable_hsts now sends a real Strict-Transport-Security header (new responder.middleware.HSTSMiddleware), not just the HTTP->HTTPS redirect; docstrings and tour.rst corrected. Additive ergonomics: - HTTP verb decorators (@api.get/post/put/patch/delete) + @api.websocket_route, on API and route groups; same-path routes allowed when methods are disjoint. - resp.delete_cookie() and resp.vary(); opt-in API(auto_vary=True) emits Vary: Accept on negotiated responses (off by default). All backwards-compatible. 473 tests pass; ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
1.8 KiB
Python
70 lines
1.8 KiB
Python
"""v5.1: Query/Header/Cookie/Path markers honor constraints and reject typos."""
|
|
|
|
import pytest
|
|
import yaml
|
|
from starlette.testclient import TestClient
|
|
|
|
import responder
|
|
from responder import Query
|
|
|
|
|
|
def _api():
|
|
return responder.API(
|
|
title="T", version="1", openapi="3.0.2",
|
|
allowed_hosts=[";"], session_https_only=False,
|
|
)
|
|
|
|
|
|
def _client(api):
|
|
return TestClient(api, base_url="http://;")
|
|
|
|
|
|
def test_unknown_kwarg_is_rejected():
|
|
with pytest.raises(TypeError, match="unexpected keyword argument"):
|
|
Query("x", dafault=5)
|
|
|
|
|
|
def test_string_constraint_enforced():
|
|
api = _api()
|
|
|
|
@api.route("/s")
|
|
def search(req, resp, *, q: str = Query("hello", min_length=3)):
|
|
resp.media = {"q": q}
|
|
|
|
client = _client(api)
|
|
assert client.get("/s?q=abcd").status_code == 200
|
|
assert client.get("/s?q=ab").status_code == 422
|
|
|
|
|
|
def test_numeric_constraint_enforced():
|
|
api = _api()
|
|
|
|
@api.route("/n")
|
|
def num(req, resp, *, n: int = Query(1, ge=1, le=10)):
|
|
resp.media = {"n": n}
|
|
|
|
client = _client(api)
|
|
assert client.get("/n?n=5").status_code == 200
|
|
assert client.get("/n?n=0").status_code == 422
|
|
assert client.get("/n?n=11").status_code == 422
|
|
|
|
|
|
def test_constraints_and_metadata_in_openapi():
|
|
api = _api()
|
|
|
|
@api.route("/s")
|
|
def search(
|
|
req,
|
|
resp,
|
|
*,
|
|
q: str = Query("x", min_length=3, description="search term", deprecated=True),
|
|
):
|
|
resp.media = {"q": q}
|
|
|
|
item = yaml.safe_load(_client(api).get("/schema.yml").content)["paths"]["/s"]
|
|
params = item.get("parameters", []) + item["get"].get("parameters", [])
|
|
q = next(p for p in params if p["name"] == "q")
|
|
assert q["schema"]["minLength"] == 3
|
|
assert q["description"] == "search term"
|
|
assert q["deprecated"] is True
|