|
|
|
@ -1,8 +1,19 @@ |
|
|
|
from typing import Annotated, Any |
|
|
|
|
|
|
|
import pytest |
|
|
|
from fastapi import Body, Depends, FastAPI, Form, status |
|
|
|
from fastapi import ( |
|
|
|
Body, |
|
|
|
Depends, |
|
|
|
FastAPI, |
|
|
|
File, |
|
|
|
Form, |
|
|
|
Header, |
|
|
|
Query, |
|
|
|
Security, |
|
|
|
status, |
|
|
|
) |
|
|
|
from fastapi.exceptions import FastAPIError |
|
|
|
from fastapi.security import HTTPBearer |
|
|
|
from fastapi.testclient import TestClient |
|
|
|
|
|
|
|
from tests._annotated_body_depends_merge_common import ( |
|
|
|
@ -12,6 +23,8 @@ from tests._annotated_body_depends_merge_common import ( |
|
|
|
openapi_request_body_schema_ref, |
|
|
|
) |
|
|
|
|
|
|
|
_MERGED_BODY_DEFAULT = FooPayload(kind="foo", extra_foo="default") |
|
|
|
|
|
|
|
|
|
|
|
class TestAnnotatedBodyDependsMergeBody: |
|
|
|
@pytest.mark.parametrize( |
|
|
|
@ -72,6 +85,42 @@ class TestAnnotatedBodyDependsMergeBody: |
|
|
|
bad = client.post("/c", json={"kind": "foo"}) |
|
|
|
assert bad.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT |
|
|
|
|
|
|
|
def test_merge_depends_empty_falls_back_to_declared_model(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
@app.post("/depends-empty") |
|
|
|
def route( |
|
|
|
data: Annotated[FooPayload, Body(), Depends()], |
|
|
|
) -> dict[str, str]: |
|
|
|
return {"extra": data.extra_foo} |
|
|
|
|
|
|
|
client = TestClient(app) |
|
|
|
r = client.post("/depends-empty", json={"kind": "foo", "extra_foo": "z"}) |
|
|
|
assert r.status_code == status.HTTP_200_OK |
|
|
|
assert r.json() == {"extra": "z"} |
|
|
|
|
|
|
|
def test_merged_body_parameter_signature_default(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
@app.post("/merged-default") |
|
|
|
def route( |
|
|
|
data: Annotated[ |
|
|
|
BasePayload, Body(), Depends(FooPayload) |
|
|
|
] = _MERGED_BODY_DEFAULT, |
|
|
|
) -> dict[str, str]: |
|
|
|
return {"extra": data.extra_foo} |
|
|
|
|
|
|
|
client = TestClient(app) |
|
|
|
empty = client.post("/merged-default") |
|
|
|
assert empty.status_code == status.HTTP_200_OK |
|
|
|
assert empty.json() == {"extra": "default"} |
|
|
|
|
|
|
|
overridden = client.post( |
|
|
|
"/merged-default", json={"kind": "foo", "extra_foo": "ov"} |
|
|
|
) |
|
|
|
assert overridden.status_code == status.HTTP_200_OK |
|
|
|
assert overridden.json() == {"extra": "ov"} |
|
|
|
|
|
|
|
def test_put_patch_json_body_depends_openapi(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
path = "/items/{item_id}" |
|
|
|
@ -149,6 +198,76 @@ class TestAnnotatedBodyDependsMergeBody: |
|
|
|
) -> None: |
|
|
|
pass # pragma: no cover |
|
|
|
|
|
|
|
@pytest.mark.parametrize( |
|
|
|
"shape_marker", |
|
|
|
[ |
|
|
|
pytest.param(Body(), id="body"), |
|
|
|
pytest.param(Form(), id="form"), |
|
|
|
pytest.param(File(), id="file"), |
|
|
|
], |
|
|
|
) |
|
|
|
def test_rejects_query_with_body_like_in_same_annotated( |
|
|
|
self, shape_marker: Any |
|
|
|
) -> None: |
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
with pytest.raises(FastAPIError, match="Cannot combine `Query`"): |
|
|
|
|
|
|
|
@app.post("/query-shape") |
|
|
|
def route_bad( |
|
|
|
data: Annotated[ |
|
|
|
BasePayload, |
|
|
|
shape_marker, |
|
|
|
Query(), |
|
|
|
Depends(FooPayload), |
|
|
|
], |
|
|
|
) -> None: |
|
|
|
pass # pragma: no cover |
|
|
|
|
|
|
|
def test_rejects_multiple_query_with_depends(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
with pytest.raises(FastAPIError, match="multiple `Query`"): |
|
|
|
|
|
|
|
@app.post("/multi-query") |
|
|
|
def route_multi_query( |
|
|
|
data: Annotated[ |
|
|
|
BasePayload, |
|
|
|
Query(), |
|
|
|
Query(), |
|
|
|
Depends(FooPayload), |
|
|
|
], |
|
|
|
) -> None: |
|
|
|
pass # pragma: no cover |
|
|
|
|
|
|
|
def test_rejects_body_like_with_other_param_in_same_annotated(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
with pytest.raises(FastAPIError, match="other parameter types"): |
|
|
|
|
|
|
|
@app.post("/body-plus-header") |
|
|
|
def route_body_header( |
|
|
|
data: Annotated[ |
|
|
|
BasePayload, |
|
|
|
Body(), |
|
|
|
Header(), |
|
|
|
Depends(FooPayload), |
|
|
|
], |
|
|
|
) -> None: |
|
|
|
pass # pragma: no cover |
|
|
|
|
|
|
|
def test_rejects_security_with_body_like_merge(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
bearer = HTTPBearer(auto_error=False) |
|
|
|
|
|
|
|
with pytest.raises(FastAPIError, match="`Security`"): |
|
|
|
|
|
|
|
@app.post("/body-security") |
|
|
|
def route_body_security( |
|
|
|
data: Annotated[BasePayload, Body(), Security(bearer)], |
|
|
|
) -> None: |
|
|
|
pass # pragma: no cover |
|
|
|
|
|
|
|
def test_rejects_merge_on_path_parameter(self) -> None: |
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
|