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

1import importlib 1abcd

2from unittest.mock import patch 1abcd

3 

4import pytest 1abcd

5from fastapi.testclient import TestClient 1abcd

6from inline_snapshot import snapshot 1abcd

7 

8from ...utils import needs_py310 1abcd

9 

10 

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

19 

20 client = TestClient(mod.app) 1ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345

21 return client 1ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345

22 

23 

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 } 

33 

34 

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 } 

44 

45 

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 } 

57 

58 

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 } 

71 

72 

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 } 

86 

87 

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 } 

101 

102 

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 } 

122 

123 

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 } 

137 

138 

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 } 

157 

158 

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 } 

172 

173 

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

181 

182 

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

190 

191 

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

198 

199 

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 } 

216 

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 } 

231 

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 } 

246 

247 

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

252 

253 

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 )