Coverage for tests / test_tutorial / test_body / test_tutorial001.py: 100%
80 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1import importlib 1abdc
2from unittest.mock import patch 1abdc
4import pytest 1abdc
5from fastapi.testclient import TestClient 1abdc
6from inline_snapshot import snapshot 1abdc
8from ...utils import needs_py310 1abdc
11@pytest.fixture( 1abdc
12 name="client",
13 params=[
14 pytest.param("tutorial001_py310", marks=needs_py310),
15 ],
16)
17def get_client(request: pytest.FixtureRequest): 1abdc
18 mod = importlib.import_module(f"docs_src.body.{request.param}") 1abc
20 client = TestClient(mod.app) 1abc
21 return client 1abc
24def test_body_float(client: TestClient): 1abdc
25 response = client.post("/items/", json={"name": "Foo", "price": 50.5}) 1hij
26 assert response.status_code == 200 1hij
27 assert response.json() == { 1hij
28 "name": "Foo",
29 "price": 50.5,
30 "description": None,
31 "tax": None,
32 }
35def test_post_with_str_float(client: TestClient): 1abdc
36 response = client.post("/items/", json={"name": "Foo", "price": "50.5"}) 1klm
37 assert response.status_code == 200 1klm
38 assert response.json() == { 1klm
39 "name": "Foo",
40 "price": 50.5,
41 "description": None,
42 "tax": None,
43 }
46def test_post_with_str_float_description(client: TestClient): 1abdc
47 response = client.post( 1nop
48 "/items/", json={"name": "Foo", "price": "50.5", "description": "Some Foo"}
49 )
50 assert response.status_code == 200 1nop
51 assert response.json() == { 1nop
52 "name": "Foo",
53 "price": 50.5,
54 "description": "Some Foo",
55 "tax": None,
56 }
59def test_post_with_str_float_description_tax(client: TestClient): 1abdc
60 response = client.post( 1qrs
61 "/items/",
62 json={"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
63 )
64 assert response.status_code == 200 1qrs
65 assert response.json() == { 1qrs
66 "name": "Foo",
67 "price": 50.5,
68 "description": "Some Foo",
69 "tax": 0.3,
70 }
73def test_post_with_only_name(client: TestClient): 1abdc
74 response = client.post("/items/", json={"name": "Foo"}) 1tuv
75 assert response.status_code == 422 1tuv
76 assert response.json() == { 1tuv
77 "detail": [
78 {
79 "type": "missing",
80 "loc": ["body", "price"],
81 "msg": "Field required",
82 "input": {"name": "Foo"},
83 }
84 ]
85 }
88def test_post_with_only_name_price(client: TestClient): 1abdc
89 response = client.post("/items/", json={"name": "Foo", "price": "twenty"}) 1wxy
90 assert response.status_code == 422 1wxy
91 assert response.json() == { 1wxy
92 "detail": [
93 {
94 "type": "float_parsing",
95 "loc": ["body", "price"],
96 "msg": "Input should be a valid number, unable to parse string as a number",
97 "input": "twenty",
98 }
99 ]
100 }
103def test_post_with_no_data(client: TestClient): 1abdc
104 response = client.post("/items/", json={}) 1zAB
105 assert response.status_code == 422 1zAB
106 assert response.json() == { 1zAB
107 "detail": [
108 {
109 "type": "missing",
110 "loc": ["body", "name"],
111 "msg": "Field required",
112 "input": {},
113 },
114 {
115 "type": "missing",
116 "loc": ["body", "price"],
117 "msg": "Field required",
118 "input": {},
119 },
120 ]
121 }
124def test_post_with_none(client: TestClient): 1abdc
125 response = client.post("/items/", json=None) 1CDE
126 assert response.status_code == 422 1CDE
127 assert response.json() == { 1CDE
128 "detail": [
129 {
130 "type": "missing",
131 "loc": ["body"],
132 "msg": "Field required",
133 "input": None,
134 }
135 ]
136 }
139def test_post_broken_body(client: TestClient): 1abdc
140 response = client.post( 1FGH
141 "/items/",
142 headers={"content-type": "application/json"},
143 content="{some broken json}",
144 )
145 assert response.status_code == 422, response.text 1FGH
146 assert response.json() == { 1FGH
147 "detail": [
148 {
149 "type": "json_invalid",
150 "loc": ["body", 1],
151 "msg": "JSON decode error",
152 "input": {},
153 "ctx": {"error": "Expecting property name enclosed in double quotes"},
154 }
155 ]
156 }
159def test_post_form_for_json(client: TestClient): 1abdc
160 response = client.post("/items/", data={"name": "Foo", "price": 50.5}) 1IJK
161 assert response.status_code == 422, response.text 1IJK
162 assert response.json() == { 1IJK
163 "detail": [
164 {
165 "type": "model_attributes_type",
166 "loc": ["body"],
167 "msg": "Input should be a valid dictionary or object to extract fields from",
168 "input": "name=Foo&price=50.5",
169 }
170 ]
171 }
174def test_explicit_content_type(client: TestClient): 1abdc
175 response = client.post( 1UVW
176 "/items/",
177 content='{"name": "Foo", "price": 50.5}',
178 headers={"Content-Type": "application/json"},
179 )
180 assert response.status_code == 200, response.text 1UVW
183def test_geo_json(client: TestClient): 1abdc
184 response = client.post( 1XYZ
185 "/items/",
186 content='{"name": "Foo", "price": 50.5}',
187 headers={"Content-Type": "application/geo+json"},
188 )
189 assert response.status_code == 200, response.text 1XYZ
192def test_no_content_type_is_json(client: TestClient): 1abdc
193 response = client.post( 1LMN
194 "/items/",
195 content='{"name": "Foo", "price": 50.5}',
196 )
197 assert response.status_code == 200, response.text 1LMN
198 assert response.json() == { 1LMN
199 "name": "Foo",
200 "description": None,
201 "price": 50.5,
202 "tax": None,
203 }
206def test_wrong_headers(client: TestClient): 1abdc
207 data = '{"name": "Foo", "price": 50.5}' 1efg
208 response = client.post( 1efg
209 "/items/", content=data, headers={"Content-Type": "text/plain"}
210 )
211 assert response.status_code == 422, response.text 1efg
212 assert response.json() == { 1efg
213 "detail": [
214 {
215 "type": "model_attributes_type",
216 "loc": ["body"],
217 "msg": "Input should be a valid dictionary or object to extract fields from",
218 "input": '{"name": "Foo", "price": 50.5}',
219 }
220 ]
221 }
223 response = client.post( 1efg
224 "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
225 )
226 assert response.status_code == 422, response.text 1efg
227 assert response.json() == { 1efg
228 "detail": [
229 {
230 "type": "model_attributes_type",
231 "loc": ["body"],
232 "msg": "Input should be a valid dictionary or object to extract fields from",
233 "input": '{"name": "Foo", "price": 50.5}',
234 }
235 ]
236 }
238 response = client.post( 1efg
239 "/items/", content=data, headers={"Content-Type": "application/not-really-json"}
240 )
241 assert response.status_code == 422, response.text 1efg
242 assert response.json() == { 1efg
243 "detail": [
244 {
245 "type": "model_attributes_type",
246 "loc": ["body"],
247 "msg": "Input should be a valid dictionary or object to extract fields from",
248 "input": '{"name": "Foo", "price": 50.5}',
249 }
250 ]
251 }
254def test_other_exceptions(client: TestClient): 1abdc
255 with patch("json.loads", side_effect=Exception): 1OPQ
256 response = client.post("/items/", json={"test": "test2"}) 1OPQ
257 assert response.status_code == 400, response.text 1OPQ
260def test_openapi_schema(client: TestClient): 1abdc
261 response = client.get("/openapi.json") 1RST
262 assert response.status_code == 200, response.text 1RST
263 assert response.json() == snapshot( 1RST
264 {
265 "openapi": "3.1.0",
266 "info": {"title": "FastAPI", "version": "0.1.0"},
267 "paths": {
268 "/items/": {
269 "post": {
270 "responses": {
271 "200": {
272 "description": "Successful Response",
273 "content": {"application/json": {"schema": {}}},
274 },
275 "422": {
276 "description": "Validation Error",
277 "content": {
278 "application/json": {
279 "schema": {
280 "$ref": "#/components/schemas/HTTPValidationError"
281 }
282 }
283 },
284 },
285 },
286 "summary": "Create Item",
287 "operationId": "create_item_items__post",
288 "requestBody": {
289 "content": {
290 "application/json": {
291 "schema": {"$ref": "#/components/schemas/Item"}
292 }
293 },
294 "required": True,
295 },
296 }
297 }
298 },
299 "components": {
300 "schemas": {
301 "Item": {
302 "title": "Item",
303 "required": ["name", "price"],
304 "type": "object",
305 "properties": {
306 "name": {"title": "Name", "type": "string"},
307 "price": {"title": "Price", "type": "number"},
308 "description": {
309 "title": "Description",
310 "anyOf": [{"type": "string"}, {"type": "null"}],
311 },
312 "tax": {
313 "title": "Tax",
314 "anyOf": [{"type": "number"}, {"type": "null"}],
315 },
316 },
317 },
318 "ValidationError": {
319 "title": "ValidationError",
320 "required": ["loc", "msg", "type"],
321 "type": "object",
322 "properties": {
323 "loc": {
324 "title": "Location",
325 "type": "array",
326 "items": {
327 "anyOf": [{"type": "string"}, {"type": "integer"}]
328 },
329 },
330 "msg": {"title": "Message", "type": "string"},
331 "type": {"title": "Error Type", "type": "string"},
332 "input": {"title": "Input"},
333 "ctx": {"title": "Context", "type": "object"},
334 },
335 },
336 "HTTPValidationError": {
337 "title": "HTTPValidationError",
338 "type": "object",
339 "properties": {
340 "detail": {
341 "title": "Detail",
342 "type": "array",
343 "items": {
344 "$ref": "#/components/schemas/ValidationError"
345 },
346 }
347 },
348 },
349 }
350 },
351 }
352 )