From 752ef0993c024f15ac5ced7efbd7446ea84c6d0c Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Sun, 3 May 2026 20:01:53 +0300 Subject: [PATCH] test: add more coverage --- .../test_annotated_body_depends_merge_body.py | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/tests/test_annotated_body_depends_merge_body.py b/tests/test_annotated_body_depends_merge_body.py index 9530cd5aa8..14a48580b1 100644 --- a/tests/test_annotated_body_depends_merge_body.py +++ b/tests/test_annotated_body_depends_merge_body.py @@ -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()