Coverage for tests/test_tutorial/test_body/test_tutorial001.py: 100%
80 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-13 13:38 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-13 13:38 +0000
1import importlib 1abcde
2from unittest.mock import patch 1abcde
4import pytest 1abcde
5from dirty_equals import IsDict 1abcde
6from fastapi.testclient import TestClient 1abcde
8from ...utils import needs_py310 1abcde
11@pytest.fixture( 1abcde
12 name="client",
13 params=[
14 "tutorial001",
15 pytest.param("tutorial001_py310", marks=needs_py310),
16 ],
17)
18def get_client(request: pytest.FixtureRequest): 1abcde
19 mod = importlib.import_module(f"docs_src.body.{request.param}") 1abcde
21 client = TestClient(mod.app) 1abcde
22 return client 1abcde
25def test_body_float(client: TestClient): 1abcde
26 response = client.post("/items/", json={"name": "Foo", "price": 50.5}) 1klmno
27 assert response.status_code == 200 1klmno
28 assert response.json() == { 1klmno
29 "name": "Foo",
30 "price": 50.5,
31 "description": None,
32 "tax": None,
33 }
36def test_post_with_str_float(client: TestClient): 1abcde
37 response = client.post("/items/", json={"name": "Foo", "price": "50.5"}) 1pqrst
38 assert response.status_code == 200 1pqrst
39 assert response.json() == { 1pqrst
40 "name": "Foo",
41 "price": 50.5,
42 "description": None,
43 "tax": None,
44 }
47def test_post_with_str_float_description(client: TestClient): 1abcde
48 response = client.post( 1uvwxy
49 "/items/", json={"name": "Foo", "price": "50.5", "description": "Some Foo"}
50 )
51 assert response.status_code == 200 1uvwxy
52 assert response.json() == { 1uvwxy
53 "name": "Foo",
54 "price": 50.5,
55 "description": "Some Foo",
56 "tax": None,
57 }
60def test_post_with_str_float_description_tax(client: TestClient): 1abcde
61 response = client.post( 1zABCD
62 "/items/",
63 json={"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
64 )
65 assert response.status_code == 200 1zABCD
66 assert response.json() == { 1zABCD
67 "name": "Foo",
68 "price": 50.5,
69 "description": "Some Foo",
70 "tax": 0.3,
71 }
74def test_post_with_only_name(client: TestClient): 1abcde
75 response = client.post("/items/", json={"name": "Foo"}) 1EFGHI
76 assert response.status_code == 422 1EFGHI
77 assert response.json() == IsDict( 1EFGHI
78 {
79 "detail": [
80 {
81 "type": "missing",
82 "loc": ["body", "price"],
83 "msg": "Field required",
84 "input": {"name": "Foo"},
85 }
86 ]
87 }
88 ) | IsDict(
89 # TODO: remove when deprecating Pydantic v1
90 {
91 "detail": [
92 {
93 "loc": ["body", "price"],
94 "msg": "field required",
95 "type": "value_error.missing",
96 }
97 ]
98 }
99 )
102def test_post_with_only_name_price(client: TestClient): 1abcde
103 response = client.post("/items/", json={"name": "Foo", "price": "twenty"}) 1JKLMN
104 assert response.status_code == 422 1JKLMN
105 assert response.json() == IsDict( 1JKLMN
106 {
107 "detail": [
108 {
109 "type": "float_parsing",
110 "loc": ["body", "price"],
111 "msg": "Input should be a valid number, unable to parse string as a number",
112 "input": "twenty",
113 }
114 ]
115 }
116 ) | IsDict(
117 # TODO: remove when deprecating Pydantic v1
118 {
119 "detail": [
120 {
121 "loc": ["body", "price"],
122 "msg": "value is not a valid float",
123 "type": "type_error.float",
124 }
125 ]
126 }
127 )
130def test_post_with_no_data(client: TestClient): 1abcde
131 response = client.post("/items/", json={}) 1OPQRS
132 assert response.status_code == 422 1OPQRS
133 assert response.json() == IsDict( 1OPQRS
134 {
135 "detail": [
136 {
137 "type": "missing",
138 "loc": ["body", "name"],
139 "msg": "Field required",
140 "input": {},
141 },
142 {
143 "type": "missing",
144 "loc": ["body", "price"],
145 "msg": "Field required",
146 "input": {},
147 },
148 ]
149 }
150 ) | IsDict(
151 # TODO: remove when deprecating Pydantic v1
152 {
153 "detail": [
154 {
155 "loc": ["body", "name"],
156 "msg": "field required",
157 "type": "value_error.missing",
158 },
159 {
160 "loc": ["body", "price"],
161 "msg": "field required",
162 "type": "value_error.missing",
163 },
164 ]
165 }
166 )
169def test_post_with_none(client: TestClient): 1abcde
170 response = client.post("/items/", json=None) 1TUVWX
171 assert response.status_code == 422 1TUVWX
172 assert response.json() == IsDict( 1TUVWX
173 {
174 "detail": [
175 {
176 "type": "missing",
177 "loc": ["body"],
178 "msg": "Field required",
179 "input": None,
180 }
181 ]
182 }
183 ) | IsDict(
184 # TODO: remove when deprecating Pydantic v1
185 {
186 "detail": [
187 {
188 "loc": ["body"],
189 "msg": "field required",
190 "type": "value_error.missing",
191 }
192 ]
193 }
194 )
197def test_post_broken_body(client: TestClient): 1abcde
198 response = client.post( 1YZ012
199 "/items/",
200 headers={"content-type": "application/json"},
201 content="{some broken json}",
202 )
203 assert response.status_code == 422, response.text 1YZ012
204 assert response.json() == IsDict( 1YZ012
205 {
206 "detail": [
207 {
208 "type": "json_invalid",
209 "loc": ["body", 1],
210 "msg": "JSON decode error",
211 "input": {},
212 "ctx": {
213 "error": "Expecting property name enclosed in double quotes"
214 },
215 }
216 ]
217 }
218 ) | IsDict(
219 # TODO: remove when deprecating Pydantic v1
220 {
221 "detail": [
222 {
223 "loc": ["body", 1],
224 "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
225 "type": "value_error.jsondecode",
226 "ctx": {
227 "msg": "Expecting property name enclosed in double quotes",
228 "doc": "{some broken json}",
229 "pos": 1,
230 "lineno": 1,
231 "colno": 2,
232 },
233 }
234 ]
235 }
236 )
239def test_post_form_for_json(client: TestClient): 1abcde
240 response = client.post("/items/", data={"name": "Foo", "price": 50.5}) 134567
241 assert response.status_code == 422, response.text 134567
242 assert response.json() == IsDict( 134567
243 {
244 "detail": [
245 {
246 "type": "model_attributes_type",
247 "loc": ["body"],
248 "msg": "Input should be a valid dictionary or object to extract fields from",
249 "input": "name=Foo&price=50.5",
250 }
251 ]
252 }
253 ) | IsDict(
254 # TODO: remove when deprecating Pydantic v1
255 {
256 "detail": [
257 {
258 "loc": ["body"],
259 "msg": "value is not a valid dict",
260 "type": "type_error.dict",
261 }
262 ]
263 }
264 )
267def test_explicit_content_type(client: TestClient): 1abcde
268 response = client.post( 1:;=?@
269 "/items/",
270 content='{"name": "Foo", "price": 50.5}',
271 headers={"Content-Type": "application/json"},
272 )
273 assert response.status_code == 200, response.text 1:;=?@
276def test_geo_json(client: TestClient): 1abcde
277 response = client.post( 1[]^_`
278 "/items/",
279 content='{"name": "Foo", "price": 50.5}',
280 headers={"Content-Type": "application/geo+json"},
281 )
282 assert response.status_code == 200, response.text 1[]^_`
285def test_no_content_type_is_json(client: TestClient): 1abcde
286 response = client.post( 189!#$
287 "/items/",
288 content='{"name": "Foo", "price": 50.5}',
289 )
290 assert response.status_code == 200, response.text 189!#$
291 assert response.json() == { 189!#$
292 "name": "Foo",
293 "description": None,
294 "price": 50.5,
295 "tax": None,
296 }
299def test_wrong_headers(client: TestClient): 1abcde
300 data = '{"name": "Foo", "price": 50.5}' 1fghij
301 response = client.post( 1fghij
302 "/items/", content=data, headers={"Content-Type": "text/plain"}
303 )
304 assert response.status_code == 422, response.text 1fghij
305 assert response.json() == IsDict( 1fghij
306 {
307 "detail": [
308 {
309 "type": "model_attributes_type",
310 "loc": ["body"],
311 "msg": "Input should be a valid dictionary or object to extract fields from",
312 "input": '{"name": "Foo", "price": 50.5}',
313 }
314 ]
315 }
316 ) | IsDict(
317 # TODO: remove when deprecating Pydantic v1
318 {
319 "detail": [
320 {
321 "loc": ["body"],
322 "msg": "value is not a valid dict",
323 "type": "type_error.dict",
324 }
325 ]
326 }
327 )
329 response = client.post( 1fghij
330 "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
331 )
332 assert response.status_code == 422, response.text 1fghij
333 assert response.json() == IsDict( 1fghij
334 {
335 "detail": [
336 {
337 "type": "model_attributes_type",
338 "loc": ["body"],
339 "msg": "Input should be a valid dictionary or object to extract fields from",
340 "input": '{"name": "Foo", "price": 50.5}',
341 }
342 ]
343 }
344 ) | IsDict(
345 # TODO: remove when deprecating Pydantic v1
346 {
347 "detail": [
348 {
349 "loc": ["body"],
350 "msg": "value is not a valid dict",
351 "type": "type_error.dict",
352 }
353 ]
354 }
355 )
356 response = client.post( 1fghij
357 "/items/", content=data, headers={"Content-Type": "application/not-really-json"}
358 )
359 assert response.status_code == 422, response.text 1fghij
360 assert response.json() == IsDict( 1fghij
361 {
362 "detail": [
363 {
364 "type": "model_attributes_type",
365 "loc": ["body"],
366 "msg": "Input should be a valid dictionary or object to extract fields from",
367 "input": '{"name": "Foo", "price": 50.5}',
368 }
369 ]
370 }
371 ) | IsDict(
372 # TODO: remove when deprecating Pydantic v1
373 {
374 "detail": [
375 {
376 "loc": ["body"],
377 "msg": "value is not a valid dict",
378 "type": "type_error.dict",
379 }
380 ]
381 }
382 )
385def test_other_exceptions(client: TestClient): 1abcde
386 with patch("json.loads", side_effect=Exception): 1%'()*
387 response = client.post("/items/", json={"test": "test2"}) 1%'()*
388 assert response.status_code == 400, response.text 1%'()*
391def test_openapi_schema(client: TestClient): 1abcde
392 response = client.get("/openapi.json") 1+,-./
393 assert response.status_code == 200, response.text 1+,-./
394 assert response.json() == { 1+,-./
395 "openapi": "3.1.0",
396 "info": {"title": "FastAPI", "version": "0.1.0"},
397 "paths": {
398 "/items/": {
399 "post": {
400 "responses": {
401 "200": {
402 "description": "Successful Response",
403 "content": {"application/json": {"schema": {}}},
404 },
405 "422": {
406 "description": "Validation Error",
407 "content": {
408 "application/json": {
409 "schema": {
410 "$ref": "#/components/schemas/HTTPValidationError"
411 }
412 }
413 },
414 },
415 },
416 "summary": "Create Item",
417 "operationId": "create_item_items__post",
418 "requestBody": {
419 "content": {
420 "application/json": {
421 "schema": {"$ref": "#/components/schemas/Item"}
422 }
423 },
424 "required": True,
425 },
426 }
427 }
428 },
429 "components": {
430 "schemas": {
431 "Item": {
432 "title": "Item",
433 "required": ["name", "price"],
434 "type": "object",
435 "properties": {
436 "name": {"title": "Name", "type": "string"},
437 "price": {"title": "Price", "type": "number"},
438 "description": IsDict(
439 {
440 "title": "Description",
441 "anyOf": [{"type": "string"}, {"type": "null"}],
442 }
443 )
444 | IsDict(
445 # TODO: remove when deprecating Pydantic v1
446 {"title": "Description", "type": "string"}
447 ),
448 "tax": IsDict(
449 {
450 "title": "Tax",
451 "anyOf": [{"type": "number"}, {"type": "null"}],
452 }
453 )
454 | IsDict(
455 # TODO: remove when deprecating Pydantic v1
456 {"title": "Tax", "type": "number"}
457 ),
458 },
459 },
460 "ValidationError": {
461 "title": "ValidationError",
462 "required": ["loc", "msg", "type"],
463 "type": "object",
464 "properties": {
465 "loc": {
466 "title": "Location",
467 "type": "array",
468 "items": {
469 "anyOf": [{"type": "string"}, {"type": "integer"}]
470 },
471 },
472 "msg": {"title": "Message", "type": "string"},
473 "type": {"title": "Error Type", "type": "string"},
474 },
475 },
476 "HTTPValidationError": {
477 "title": "HTTPValidationError",
478 "type": "object",
479 "properties": {
480 "detail": {
481 "title": "Detail",
482 "type": "array",
483 "items": {"$ref": "#/components/schemas/ValidationError"},
484 }
485 },
486 },
487 }
488 },
489 }