Coverage for tests / test_tutorial / test_body / test_tutorial001.py: 100%
79 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-04-06 01:24 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-04-06 01:24 +0000
1import importlib 1abcd
2from unittest.mock import patch 1abcd
4import pytest 1abcd
5from fastapi.testclient import TestClient 1abcd
6from inline_snapshot import snapshot 1abcd
8from ...utils import needs_py310 1abcd
11@pytest.fixture( 1abcd
12 name="client",
13 params=[
14 pytest.param("tutorial001_py310", marks=needs_py310),
15 ],
16)
17def get_client(request: pytest.FixtureRequest): 1abcd
18 mod = importlib.import_module(f"docs_src.body.{request.param}") 1ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345
20 client = TestClient(mod.app) 1ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345
21 return client 1ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345
24def test_body_float(client: TestClient): 1abcd
25 response = client.post("/items/", json={"name": "Foo", "price": 50.5}) 16789
26 assert response.status_code == 200 16789
27 assert response.json() == { 16789
28 "name": "Foo",
29 "price": 50.5,
30 "description": None,
31 "tax": None,
32 }
35def test_post_with_str_float(client: TestClient): 1abcd
36 response = client.post("/items/", json={"name": "Foo", "price": "50.5"}) 1!#$%
37 assert response.status_code == 200 1!#$%
38 assert response.json() == { 1!#$%
39 "name": "Foo",
40 "price": 50.5,
41 "description": None,
42 "tax": None,
43 }
46def test_post_with_str_float_description(client: TestClient): 1abcd
47 response = client.post( 1'()*
48 "/items/", json={"name": "Foo", "price": "50.5", "description": "Some Foo"}
49 )
50 assert response.status_code == 200 1'()*
51 assert response.json() == { 1'()*
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): 1abcd
60 response = client.post( 1+,-.
61 "/items/",
62 json={"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
63 )
64 assert response.status_code == 200 1+,-.
65 assert response.json() == { 1+,-.
66 "name": "Foo",
67 "price": 50.5,
68 "description": "Some Foo",
69 "tax": 0.3,
70 }
73def test_post_with_only_name(client: TestClient): 1abcd
74 response = client.post("/items/", json={"name": "Foo"}) 1/:;=
75 assert response.status_code == 422 1/:;=
76 assert response.json() == { 1/:;=
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): 1abcd
89 response = client.post("/items/", json={"name": "Foo", "price": "twenty"}) 1?@[]
90 assert response.status_code == 422 1?@[]
91 assert response.json() == { 1?@[]
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): 1abcd
104 response = client.post("/items/", json={}) 1^_`{
105 assert response.status_code == 422 1^_`{
106 assert response.json() == { 1^_`{
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): 1abcd
125 response = client.post("/items/", json=None) 2| } ~ ab
126 assert response.status_code == 422 2| } ~ ab
127 assert response.json() == { 2| } ~ ab
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): 1abcd
140 response = client.post( 2bbcbdbeb
141 "/items/",
142 headers={"content-type": "application/json"},
143 content="{some broken json}",
144 )
145 assert response.status_code == 422, response.text 2bbcbdbeb
146 assert response.json() == { 2bbcbdbeb
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): 1abcd
160 response = client.post("/items/", data={"name": "Foo", "price": 50.5}) 2fbgbhbib
161 assert response.status_code == 422, response.text 2fbgbhbib
162 assert response.json() == { 2fbgbhbib
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): 1abcd
175 response = client.post( 2rbsbtbub
176 "/items/",
177 content='{"name": "Foo", "price": 50.5}',
178 headers={"Content-Type": "application/json"},
179 )
180 assert response.status_code == 200, response.text 2rbsbtbub
183def test_geo_json(client: TestClient): 1abcd
184 response = client.post( 2vbwbxbyb
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 2vbwbxbyb
192def test_no_content_type_json(client: TestClient): 1abcd
193 response = client.post( 2zbAbBbCb
194 "/items/",
195 content='{"name": "Foo", "price": 50.5}',
196 )
197 assert response.status_code == 422, response.text 2zbAbBbCb
200def test_wrong_headers(client: TestClient): 1abcd
201 data = '{"name": "Foo", "price": 50.5}' 1efgh
202 response = client.post( 1efgh
203 "/items/", content=data, headers={"Content-Type": "text/plain"}
204 )
205 assert response.status_code == 422, response.text 1efgh
206 assert response.json() == { 1efgh
207 "detail": [
208 {
209 "type": "model_attributes_type",
210 "loc": ["body"],
211 "msg": "Input should be a valid dictionary or object to extract fields from",
212 "input": '{"name": "Foo", "price": 50.5}',
213 }
214 ]
215 }
217 response = client.post( 1efgh
218 "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
219 )
220 assert response.status_code == 422, response.text 1efgh
221 assert response.json() == { 1efgh
222 "detail": [
223 {
224 "type": "model_attributes_type",
225 "loc": ["body"],
226 "msg": "Input should be a valid dictionary or object to extract fields from",
227 "input": '{"name": "Foo", "price": 50.5}',
228 }
229 ]
230 }
232 response = client.post( 1efgh
233 "/items/", content=data, headers={"Content-Type": "application/not-really-json"}
234 )
235 assert response.status_code == 422, response.text 1efgh
236 assert response.json() == { 1efgh
237 "detail": [
238 {
239 "type": "model_attributes_type",
240 "loc": ["body"],
241 "msg": "Input should be a valid dictionary or object to extract fields from",
242 "input": '{"name": "Foo", "price": 50.5}',
243 }
244 ]
245 }
248def test_other_exceptions(client: TestClient): 1abcd
249 with patch("json.loads", side_effect=Exception): 2jbkblbmb
250 response = client.post("/items/", json={"test": "test2"}) 2jbkblbmb
251 assert response.status_code == 400, response.text 2jbkblbmb
254def test_openapi_schema(client: TestClient): 1abcd
255 response = client.get("/openapi.json") 2nbobpbqb
256 assert response.status_code == 200, response.text 2nbobpbqb
257 assert response.json() == snapshot( 2nbobpbqb
258 {
259 "openapi": "3.1.0",
260 "info": {"title": "FastAPI", "version": "0.1.0"},
261 "paths": {
262 "/items/": {
263 "post": {
264 "responses": {
265 "200": {
266 "description": "Successful Response",
267 "content": {"application/json": {"schema": {}}},
268 },
269 "422": {
270 "description": "Validation Error",
271 "content": {
272 "application/json": {
273 "schema": {
274 "$ref": "#/components/schemas/HTTPValidationError"
275 }
276 }
277 },
278 },
279 },
280 "summary": "Create Item",
281 "operationId": "create_item_items__post",
282 "requestBody": {
283 "content": {
284 "application/json": {
285 "schema": {"$ref": "#/components/schemas/Item"}
286 }
287 },
288 "required": True,
289 },
290 }
291 }
292 },
293 "components": {
294 "schemas": {
295 "Item": {
296 "title": "Item",
297 "required": ["name", "price"],
298 "type": "object",
299 "properties": {
300 "name": {"title": "Name", "type": "string"},
301 "price": {"title": "Price", "type": "number"},
302 "description": {
303 "title": "Description",
304 "anyOf": [{"type": "string"}, {"type": "null"}],
305 },
306 "tax": {
307 "title": "Tax",
308 "anyOf": [{"type": "number"}, {"type": "null"}],
309 },
310 },
311 },
312 "ValidationError": {
313 "title": "ValidationError",
314 "required": ["loc", "msg", "type"],
315 "type": "object",
316 "properties": {
317 "loc": {
318 "title": "Location",
319 "type": "array",
320 "items": {
321 "anyOf": [{"type": "string"}, {"type": "integer"}]
322 },
323 },
324 "msg": {"title": "Message", "type": "string"},
325 "type": {"title": "Error Type", "type": "string"},
326 "input": {"title": "Input"},
327 "ctx": {"title": "Context", "type": "object"},
328 },
329 },
330 "HTTPValidationError": {
331 "title": "HTTPValidationError",
332 "type": "object",
333 "properties": {
334 "detail": {
335 "title": "Detail",
336 "type": "array",
337 "items": {
338 "$ref": "#/components/schemas/ValidationError"
339 },
340 }
341 },
342 },
343 }
344 },
345 }
346 )