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

1from unittest.mock import patch 1abcde

2 

3import pytest 1abcde

4from dirty_equals import IsDict 1abcde

5from fastapi.testclient import TestClient 1abcde

6 

7 

8@pytest.fixture 1abcde

9def client(): 1abcde

10 from docs_src.body.tutorial001 import app 1abcde

11 

12 client = TestClient(app) 1abcde

13 return client 1abcde

14 

15 

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 } 

25 

26 

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 } 

36 

37 

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 } 

49 

50 

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 } 

63 

64 

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 ) 

91 

92 

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 ) 

119 

120 

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 ) 

158 

159 

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 ) 

186 

187 

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 ) 

228 

229 

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 ) 

256 

257 

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

265 

266 

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

274 

275 

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 } 

288 

289 

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 ) 

319 

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 ) 

374 

375 

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

380 

381 

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 }