From 49053271ae2d26b6202b814fb2c22032866b9aa8 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Sun, 3 May 2026 21:04:04 +0300 Subject: [PATCH] test: more coverage and fixes --- .../test_annotated_body_depends_merge_body.py | 64 +++++++++++++----- .../test_annotated_body_depends_merge_file.py | 25 ++++--- .../test_annotated_body_depends_merge_form.py | 34 +++++++--- ...ted_body_depends_merge_query_plus_shape.py | 65 +++++++++++++------ 4 files changed, 136 insertions(+), 52 deletions(-) diff --git a/tests/test_annotated_body_depends_merge_body.py b/tests/test_annotated_body_depends_merge_body.py index a9ffce3083..260db47fe4 100644 --- a/tests/test_annotated_body_depends_merge_body.py +++ b/tests/test_annotated_body_depends_merge_body.py @@ -36,10 +36,27 @@ class TestAnnotatedBodyDependsMergeBody: "model_cls", "expected_ref_suffix", "assert_no_query_params", + "payload", ), [ - ("/a", Body(), Depends(FooPayload), FooPayload, "FooPayload", True), - ("/b", Depends(BarPayload), Body(), BarPayload, "BarPayload", False), + pytest.param( + "/a", + Body(), + Depends(FooPayload), + FooPayload, + "FooPayload", + True, + {"kind": "foo", "extra_foo": "hit"}, + ), + pytest.param( + "/b", + Depends(BarPayload), + Body(), + BarPayload, + "BarPayload", + False, + {"kind": "bar", "extra_bar": "hit"}, + ), ], ) def test_openapi_json_body_depends_merge( @@ -50,6 +67,7 @@ class TestAnnotatedBodyDependsMergeBody: model_cls: type[BasePayload], expected_ref_suffix: str, assert_no_query_params: bool, + payload: dict[str, str], ) -> None: app = FastAPI() @@ -69,6 +87,9 @@ class TestAnnotatedBodyDependsMergeBody: ) assert ref.endswith(f"/{expected_ref_suffix}") + ok = client.post(path, json=payload) + assert ok.status_code == status.HTTP_200_OK + def test_runtime_json_validates_concrete_model(self) -> None: app = FastAPI() @@ -79,9 +100,12 @@ class TestAnnotatedBodyDependsMergeBody: return {"extra": data.extra_foo} client = TestClient(app) - r = client.post("/c", json={"kind": "foo", "extra_foo": "x"}) + extra = "ricksanchez" + payload = {"kind": "foo", "extra_foo": extra} + expected_json = {"extra": extra} + r = client.post("/c", json=payload) assert r.status_code == status.HTTP_200_OK - assert r.json() == {"extra": "x"} + assert r.json() == expected_json bad = client.post("/c", json={"kind": "foo"}) # not status.*: Starlette confused HTTP_422_UNPROCESSABLE_CONTENT HTTP_422_UNPROCESSABLE_ENTITY @@ -97,9 +121,12 @@ class TestAnnotatedBodyDependsMergeBody: return {"extra": data.extra_foo} client = TestClient(app) - r = client.post("/depends-empty", json={"kind": "foo", "extra_foo": "z"}) + extra = "foobar" + payload = {"kind": "foo", "extra_foo": extra} + expected_json = {"extra": extra} + r = client.post("/depends-empty", json=payload) assert r.status_code == status.HTTP_200_OK - assert r.json() == {"extra": "z"} + assert r.json() == expected_json def test_merged_body_parameter_signature_default(self) -> None: app = FastAPI() @@ -115,13 +142,15 @@ class TestAnnotatedBodyDependsMergeBody: client = TestClient(app) empty = client.post("/merged-default") assert empty.status_code == status.HTTP_200_OK - assert empty.json() == {"extra": "default"} + expected_default = {"extra": "default"} + assert empty.json() == expected_default - overridden = client.post( - "/merged-default", json={"kind": "foo", "extra_foo": "ov"} - ) + override_extra = "hello world" + override_payload = {"kind": "foo", "extra_foo": override_extra} + expected_override = {"extra": override_extra} + overridden = client.post("/merged-default", json=override_payload) assert overridden.status_code == status.HTTP_200_OK - assert overridden.json() == {"extra": "ov"} + assert overridden.json() == expected_override def test_put_patch_json_body_depends_openapi(self) -> None: app = FastAPI() @@ -149,16 +178,20 @@ class TestAnnotatedBodyDependsMergeBody: ) assert ref.endswith("/FooPayload") - r = client.put("/items/1", json={"kind": "foo", "extra_foo": "a"}) + put_extra = "fizz" + put_payload = {"kind": "foo", "extra_foo": put_extra} + r = client.put("/items/1", json=put_payload) assert r.status_code == status.HTTP_200_OK - r2 = client.patch("/items/1", json={"kind": "foo", "extra_foo": "b"}) + patch_extra = "buzz" + patch_payload = {"kind": "foo", "extra_foo": patch_extra} + r2 = client.patch("/items/1", json=patch_payload) assert r2.status_code == status.HTTP_200_OK def test_rejects_body_with_callable_depends(self) -> None: app = FastAPI() def not_a_model() -> None: - return None + pass # pragma: no cover with pytest.raises(FastAPIError, match="Pydantic model class"): @@ -278,5 +311,4 @@ class TestAnnotatedBodyDependsMergeBody: @app.post("/path/{data}") def route_path( data: Annotated[BasePayload, Body(), Depends(FooPayload)], - ) -> None: - pass # pragma: no cover + ) -> None: ... diff --git a/tests/test_annotated_body_depends_merge_file.py b/tests/test_annotated_body_depends_merge_file.py index 7c6267f1c8..ff58e96985 100644 --- a/tests/test_annotated_body_depends_merge_file.py +++ b/tests/test_annotated_body_depends_merge_file.py @@ -17,7 +17,7 @@ from tests._annotated_body_depends_merge_common import ( class TestAnnotatedBodyDependsMergeFile: @pytest.mark.parametrize( - ("path", "ann1", "ann2", "model_cls", "expected_ref_suffix"), + ("path", "ann1", "ann2", "model_cls", "expected_ref_suffix", "payload"), [ ( "/file-a", @@ -25,6 +25,7 @@ class TestAnnotatedBodyDependsMergeFile: Depends(FooFilePayload), FooFilePayload, "FooFilePayload", + {"kind": "foo", "extra_foo": "hit"}, ), ( "/file-b", @@ -32,6 +33,7 @@ class TestAnnotatedBodyDependsMergeFile: File(), BarFilePayload, "BarFilePayload", + {"kind": "bar", "extra_bar": "hit"}, ), ], ) @@ -42,6 +44,7 @@ class TestAnnotatedBodyDependsMergeFile: ann2: Any, model_cls: type[BasePayload], expected_ref_suffix: str, + payload: dict[str, str], ) -> None: app = FastAPI() @@ -59,6 +62,10 @@ class TestAnnotatedBodyDependsMergeFile: ref = content["multipart/form-data"]["schema"]["$ref"] assert ref.endswith(f"/{expected_ref_suffix}") + blob = ("blob.bin", BytesIO(b"x"), "application/octet-stream") + ok = client.post(path, data=payload, files={"blob": blob}) + assert ok.status_code == status.HTTP_200_OK + def test_runtime_file_validates_concrete_model(self) -> None: app = FastAPI() @@ -69,13 +76,14 @@ class TestAnnotatedBodyDependsMergeFile: return {"extra": data.extra_foo, "fn": data.blob.filename or ""} client = TestClient(app) - r = client.post( - "/file-c", - data={"kind": "foo", "extra_foo": "u"}, - files={"blob": ("up.txt", BytesIO(b"xyz"), "text/plain")}, - ) + extra = "info" + filename = "file.txt" + payload = {"kind": "foo", "extra_foo": extra} + file_field = (filename, BytesIO(b"xyz"), "text/plain") + expected_json = {"extra": extra, "fn": filename} + r = client.post("/file-c", data=payload, files={"blob": file_field}) assert r.status_code == status.HTTP_200_OK - assert r.json() == {"extra": "u", "fn": "up.txt"} + assert r.json() == expected_json bad = client.post( "/file-c", @@ -98,5 +106,4 @@ class TestAnnotatedBodyDependsMergeFile: Form(), Depends(FooPayload), ], - ) -> None: - pass # pragma: no cover + ) -> None: ... diff --git a/tests/test_annotated_body_depends_merge_form.py b/tests/test_annotated_body_depends_merge_form.py index 435019048e..ca6e18205f 100644 --- a/tests/test_annotated_body_depends_merge_form.py +++ b/tests/test_annotated_body_depends_merge_form.py @@ -14,10 +14,24 @@ from tests._annotated_body_depends_merge_common import ( class TestAnnotatedBodyDependsMergeForm: @pytest.mark.parametrize( - ("path", "ann1", "ann2", "model_cls", "expected_ref_suffix"), + ("path", "ann1", "ann2", "model_cls", "expected_ref_suffix", "payload"), [ - ("/form-a", Form(), Depends(FooPayload), FooPayload, "FooPayload"), - ("/form-b", Depends(BarPayload), Form(), BarPayload, "BarPayload"), + ( + "/form-a", + Form(), + Depends(FooPayload), + FooPayload, + "FooPayload", + {"kind": "foo", "extra_foo": "hit"}, + ), + ( + "/form-b", + Depends(BarPayload), + Form(), + BarPayload, + "BarPayload", + {"kind": "bar", "extra_bar": "hit"}, + ), ], ) def test_openapi_form_depends_merge( @@ -27,6 +41,7 @@ class TestAnnotatedBodyDependsMergeForm: ann2: Any, model_cls: type[BasePayload], expected_ref_suffix: str, + payload: dict[str, str], ) -> None: app = FastAPI() @@ -44,6 +59,9 @@ class TestAnnotatedBodyDependsMergeForm: ref = content["application/x-www-form-urlencoded"]["schema"]["$ref"] assert ref.endswith(f"/{expected_ref_suffix}") + ok = client.post(path, data=payload) + assert ok.status_code == status.HTTP_200_OK + def test_runtime_form_validates_concrete_model(self) -> None: app = FastAPI() @@ -54,12 +72,12 @@ class TestAnnotatedBodyDependsMergeForm: return {"extra": data.extra_foo} client = TestClient(app) - r = client.post( - "/form-c", - data={"kind": "foo", "extra_foo": "z"}, - ) + extra = "z" + payload = {"kind": "foo", "extra_foo": extra} + expected_json = {"extra": extra} + r = client.post("/form-c", data=payload) assert r.status_code == status.HTTP_200_OK - assert r.json() == {"extra": "z"} + assert r.json() == expected_json bad = client.post("/form-c", data={"kind": "foo"}) # not status.*: Starlette confused HTTP_422_UNPROCESSABLE_CONTENT HTTP_422_UNPROCESSABLE_ENTITY diff --git a/tests/test_annotated_body_depends_merge_query_plus_shape.py b/tests/test_annotated_body_depends_merge_query_plus_shape.py index 024df417c1..41f69a347d 100644 --- a/tests/test_annotated_body_depends_merge_query_plus_shape.py +++ b/tests/test_annotated_body_depends_merge_query_plus_shape.py @@ -3,7 +3,7 @@ from io import BytesIO from typing import Annotated, Any -from fastapi import Body, Depends, FastAPI, File, Form, Query +from fastapi import Body, Depends, FastAPI, File, Form, Query, status from fastapi.testclient import TestClient from tests._annotated_body_depends_merge_common import ( @@ -44,6 +44,10 @@ class TestQueryPlusMergedShape: ) assert ref.endswith("/FooPayload") + hit_payload = {"kind": "foo", "extra_foo": "x"} + hit = client.post("/mix-json?client_id=hit", json=hit_payload) + assert hit.status_code == status.HTTP_200_OK + def test_runtime_query_plus_json_body(self) -> None: app = FastAPI() @@ -55,12 +59,13 @@ class TestQueryPlusMergedShape: return {"client": client_id, "extra": data.extra_foo} client = TestClient(app) - r = client.post( - "/r-json?client_id=c1", - json={"kind": "foo", "extra_foo": "e"}, - ) - assert r.status_code == 200 - assert r.json() == {"client": "c1", "extra": "e"} + client_id = "c1" + extra = "helloworld" + payload = {"kind": "foo", "extra_foo": extra} + expected_json = {"client": client_id, "extra": extra} + r = client.post(f"/r-json?client_id={client_id}", json=payload) + assert r.status_code == status.HTTP_200_OK + assert r.json() == expected_json def test_openapi_query_plus_form(self) -> None: app = FastAPI() @@ -81,6 +86,10 @@ class TestQueryPlusMergedShape: ref = rb["application/x-www-form-urlencoded"]["schema"]["$ref"] assert ref.endswith("/BarPayload") + hit_payload = {"kind": "bar", "extra_bar": "y"} + hit = client.post("/mix-form", params={"client_id": "hit"}, data=hit_payload) + assert hit.status_code == status.HTTP_200_OK + def test_runtime_query_plus_form(self) -> None: app = FastAPI() @@ -92,13 +101,13 @@ class TestQueryPlusMergedShape: return {"client": client_id, "extra": data.extra_foo} client = TestClient(app) - r = client.post( - "/r-form", - params={"client_id": "c2"}, - data={"kind": "foo", "extra_foo": "f"}, - ) - assert r.status_code == 200 - assert r.json() == {"client": "c2", "extra": "f"} + client_id = "c2" + extra = "foo" + payload = {"kind": "foo", "extra_foo": extra} + expected_json = {"client": client_id, "extra": extra} + r = client.post("/r-form", params={"client_id": client_id}, data=payload) + assert r.status_code == status.HTTP_200_OK + assert r.json() == expected_json def test_openapi_query_plus_file_multipart(self) -> None: app = FastAPI() @@ -119,6 +128,18 @@ class TestQueryPlusMergedShape: ref = rb["multipart/form-data"]["schema"]["$ref"] assert ref.endswith("/FooFilePayload") + hit_form = {"kind": "foo", "extra_foo": "u"} + hit_files = { + "blob": ("up.bin", BytesIO(b"abc"), "application/octet-stream"), + } + hit = client.post( + "/mix-file", + params={"client_id": "hit"}, + data=hit_form, + files=hit_files, + ) + assert hit.status_code == status.HTTP_200_OK + def test_runtime_query_plus_file_multipart(self) -> None: app = FastAPI() @@ -134,11 +155,17 @@ class TestQueryPlusMergedShape: } client = TestClient(app) + client_id = "c3" + extra = "bar" + filename = "up.bin" + payload = {"kind": "bar", "extra_bar": extra} + file_field = (filename, BytesIO(b"abc"), "application/octet-stream") + expected_json = {"client": client_id, "extra": extra, "fn": filename} r = client.post( "/r-file", - params={"client_id": "c3"}, - data={"kind": "bar", "extra_bar": "b"}, - files={"blob": ("up.bin", BytesIO(b"abc"), "application/octet-stream")}, + params={"client_id": client_id}, + data=payload, + files={"blob": file_field}, ) - assert r.status_code == 200 - assert r.json() == {"client": "c3", "extra": "b", "fn": "up.bin"} + assert r.status_code == status.HTTP_200_OK + assert r.json() == expected_json