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