Coverage for tests/test_tutorial/test_body/test_tutorial001_py310.py: 100%

95 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-08 03:53 +0000

1from unittest.mock import patch 1deabc

2 

3import pytest 1deabc

4from dirty_equals import IsDict 1deabc

5from fastapi.testclient import TestClient 1deabc

6 

7from ...utils import needs_py310 1deabc

8 

9 

10@pytest.fixture 1deabc

11def client(): 1deabc

12 from docs_src.body.tutorial001_py310 import app 1abc

13 

14 client = TestClient(app) 1abc

15 return client 1abc

16 

17 

18@needs_py310 1deabc

19def test_body_float(client: TestClient): 1deabc

20 response = client.post("/items/", json={"name": "Foo", "price": 50.5}) 1abc

21 assert response.status_code == 200 1abc

22 assert response.json() == { 1abc

23 "name": "Foo", 

24 "price": 50.5, 

25 "description": None, 

26 "tax": None, 

27 } 

28 

29 

30@needs_py310 1deabc

31def test_post_with_str_float(client: TestClient): 1deabc

32 response = client.post("/items/", json={"name": "Foo", "price": "50.5"}) 1abc

33 assert response.status_code == 200 1abc

34 assert response.json() == { 1abc

35 "name": "Foo", 

36 "price": 50.5, 

37 "description": None, 

38 "tax": None, 

39 } 

40 

41 

42@needs_py310 1deabc

43def test_post_with_str_float_description(client: TestClient): 1deabc

44 response = client.post( 1abc

45 "/items/", json={"name": "Foo", "price": "50.5", "description": "Some Foo"} 

46 ) 

47 assert response.status_code == 200 1abc

48 assert response.json() == { 1abc

49 "name": "Foo", 

50 "price": 50.5, 

51 "description": "Some Foo", 

52 "tax": None, 

53 } 

54 

55 

56@needs_py310 1deabc

57def test_post_with_str_float_description_tax(client: TestClient): 1deabc

58 response = client.post( 1abc

59 "/items/", 

60 json={"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3}, 

61 ) 

62 assert response.status_code == 200 1abc

63 assert response.json() == { 1abc

64 "name": "Foo", 

65 "price": 50.5, 

66 "description": "Some Foo", 

67 "tax": 0.3, 

68 } 

69 

70 

71@needs_py310 1deabc

72def test_post_with_only_name(client: TestClient): 1deabc

73 response = client.post("/items/", json={"name": "Foo"}) 1abc

74 assert response.status_code == 422 1abc

75 assert response.json() == IsDict( 1abc

76 { 

77 "detail": [ 

78 { 

79 "type": "missing", 

80 "loc": ["body", "price"], 

81 "msg": "Field required", 

82 "input": {"name": "Foo"}, 

83 } 

84 ] 

85 } 

86 ) | IsDict( 

87 # TODO: remove when deprecating Pydantic v1 

88 { 

89 "detail": [ 

90 { 

91 "loc": ["body", "price"], 

92 "msg": "field required", 

93 "type": "value_error.missing", 

94 } 

95 ] 

96 } 

97 ) 

98 

99 

100@needs_py310 1deabc

101def test_post_with_only_name_price(client: TestClient): 1deabc

102 response = client.post("/items/", json={"name": "Foo", "price": "twenty"}) 1abc

103 assert response.status_code == 422 1abc

104 assert response.json() == IsDict( 1abc

105 { 

106 "detail": [ 

107 { 

108 "type": "float_parsing", 

109 "loc": ["body", "price"], 

110 "msg": "Input should be a valid number, unable to parse string as a number", 

111 "input": "twenty", 

112 } 

113 ] 

114 } 

115 ) | IsDict( 

116 # TODO: remove when deprecating Pydantic v1 

117 { 

118 "detail": [ 

119 { 

120 "loc": ["body", "price"], 

121 "msg": "value is not a valid float", 

122 "type": "type_error.float", 

123 } 

124 ] 

125 } 

126 ) 

127 

128 

129@needs_py310 1deabc

130def test_post_with_no_data(client: TestClient): 1deabc

131 response = client.post("/items/", json={}) 1abc

132 assert response.status_code == 422 1abc

133 assert response.json() == IsDict( 1abc

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 ) 

167 

168 

169@needs_py310 1deabc

170def test_post_with_none(client: TestClient): 1deabc

171 response = client.post("/items/", json=None) 1abc

172 assert response.status_code == 422 1abc

173 assert response.json() == IsDict( 1abc

174 { 

175 "detail": [ 

176 { 

177 "type": "missing", 

178 "loc": ["body"], 

179 "msg": "Field required", 

180 "input": None, 

181 } 

182 ] 

183 } 

184 ) | IsDict( 

185 # TODO: remove when deprecating Pydantic v1 

186 { 

187 "detail": [ 

188 { 

189 "loc": ["body"], 

190 "msg": "field required", 

191 "type": "value_error.missing", 

192 } 

193 ] 

194 } 

195 ) 

196 

197 

198@needs_py310 1deabc

199def test_post_broken_body(client: TestClient): 1deabc

200 response = client.post( 1abc

201 "/items/", 

202 headers={"content-type": "application/json"}, 

203 content="{some broken json}", 

204 ) 

205 assert response.status_code == 422, response.text 1abc

206 assert response.json() == IsDict( 1abc

207 { 

208 "detail": [ 

209 { 

210 "type": "json_invalid", 

211 "loc": ["body", 1], 

212 "msg": "JSON decode error", 

213 "input": {}, 

214 "ctx": { 

215 "error": "Expecting property name enclosed in double quotes" 

216 }, 

217 } 

218 ] 

219 } 

220 ) | IsDict( 

221 # TODO: remove when deprecating Pydantic v1 

222 { 

223 "detail": [ 

224 { 

225 "loc": ["body", 1], 

226 "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)", 

227 "type": "value_error.jsondecode", 

228 "ctx": { 

229 "msg": "Expecting property name enclosed in double quotes", 

230 "doc": "{some broken json}", 

231 "pos": 1, 

232 "lineno": 1, 

233 "colno": 2, 

234 }, 

235 } 

236 ] 

237 } 

238 ) 

239 

240 

241@needs_py310 1deabc

242def test_post_form_for_json(client: TestClient): 1deabc

243 response = client.post("/items/", data={"name": "Foo", "price": 50.5}) 1abc

244 assert response.status_code == 422, response.text 1abc

245 assert response.json() == IsDict( 1abc

246 { 

247 "detail": [ 

248 { 

249 "type": "model_attributes_type", 

250 "loc": ["body"], 

251 "msg": "Input should be a valid dictionary or object to extract fields from", 

252 "input": "name=Foo&price=50.5", 

253 } 

254 ] 

255 } 

256 ) | IsDict( 

257 # TODO: remove when deprecating Pydantic v1 

258 { 

259 "detail": [ 

260 { 

261 "loc": ["body"], 

262 "msg": "value is not a valid dict", 

263 "type": "type_error.dict", 

264 } 

265 ] 

266 } 

267 ) 

268 

269 

270@needs_py310 1deabc

271def test_explicit_content_type(client: TestClient): 1deabc

272 response = client.post( 1abc

273 "/items/", 

274 content='{"name": "Foo", "price": 50.5}', 

275 headers={"Content-Type": "application/json"}, 

276 ) 

277 assert response.status_code == 200, response.text 1abc

278 

279 

280@needs_py310 1deabc

281def test_geo_json(client: TestClient): 1deabc

282 response = client.post( 1abc

283 "/items/", 

284 content='{"name": "Foo", "price": 50.5}', 

285 headers={"Content-Type": "application/geo+json"}, 

286 ) 

287 assert response.status_code == 200, response.text 1abc

288 

289 

290@needs_py310 1deabc

291def test_no_content_type_is_json(client: TestClient): 1deabc

292 response = client.post( 1abc

293 "/items/", 

294 content='{"name": "Foo", "price": 50.5}', 

295 ) 

296 assert response.status_code == 200, response.text 1abc

297 assert response.json() == { 1abc

298 "name": "Foo", 

299 "description": None, 

300 "price": 50.5, 

301 "tax": None, 

302 } 

303 

304 

305@needs_py310 1deabc

306def test_wrong_headers(client: TestClient): 1deabc

307 data = '{"name": "Foo", "price": 50.5}' 1abc

308 response = client.post( 1abc

309 "/items/", content=data, headers={"Content-Type": "text/plain"} 

310 ) 

311 assert response.status_code == 422, response.text 1abc

312 assert response.json() == IsDict( 1abc

313 { 

314 "detail": [ 

315 { 

316 "type": "model_attributes_type", 

317 "loc": ["body"], 

318 "msg": "Input should be a valid dictionary or object to extract fields from", 

319 "input": '{"name": "Foo", "price": 50.5}', 

320 } 

321 ] 

322 } 

323 ) | IsDict( 

324 # TODO: remove when deprecating Pydantic v1 

325 { 

326 "detail": [ 

327 { 

328 "loc": ["body"], 

329 "msg": "value is not a valid dict", 

330 "type": "type_error.dict", 

331 } 

332 ] 

333 } 

334 ) 

335 

336 response = client.post( 1abc

337 "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"} 

338 ) 

339 assert response.status_code == 422, response.text 1abc

340 assert response.json() == IsDict( 1abc

341 { 

342 "detail": [ 

343 { 

344 "type": "model_attributes_type", 

345 "loc": ["body"], 

346 "msg": "Input should be a valid dictionary or object to extract fields from", 

347 "input": '{"name": "Foo", "price": 50.5}', 

348 } 

349 ] 

350 } 

351 ) | IsDict( 

352 # TODO: remove when deprecating Pydantic v1 

353 { 

354 "detail": [ 

355 { 

356 "loc": ["body"], 

357 "msg": "value is not a valid dict", 

358 "type": "type_error.dict", 

359 } 

360 ] 

361 } 

362 ) 

363 response = client.post( 1abc

364 "/items/", content=data, headers={"Content-Type": "application/not-really-json"} 

365 ) 

366 assert response.status_code == 422, response.text 1abc

367 assert response.json() == IsDict( 1abc

368 { 

369 "detail": [ 

370 { 

371 "type": "model_attributes_type", 

372 "loc": ["body"], 

373 "msg": "Input should be a valid dictionary or object to extract fields from", 

374 "input": '{"name": "Foo", "price": 50.5}', 

375 } 

376 ] 

377 } 

378 ) | IsDict( 

379 # TODO: remove when deprecating Pydantic v1 

380 { 

381 "detail": [ 

382 { 

383 "loc": ["body"], 

384 "msg": "value is not a valid dict", 

385 "type": "type_error.dict", 

386 } 

387 ] 

388 } 

389 ) 

390 

391 

392@needs_py310 1deabc

393def test_other_exceptions(client: TestClient): 1deabc

394 with patch("json.loads", side_effect=Exception): 1abc

395 response = client.post("/items/", json={"test": "test2"}) 1abc

396 assert response.status_code == 400, response.text 1abc

397 

398 

399@needs_py310 1deabc

400def test_openapi_schema(client: TestClient): 1deabc

401 response = client.get("/openapi.json") 1abc

402 assert response.status_code == 200, response.text 1abc

403 assert response.json() == { 1abc

404 "openapi": "3.1.0", 

405 "info": {"title": "FastAPI", "version": "0.1.0"}, 

406 "paths": { 

407 "/items/": { 

408 "post": { 

409 "responses": { 

410 "200": { 

411 "description": "Successful Response", 

412 "content": {"application/json": {"schema": {}}}, 

413 }, 

414 "422": { 

415 "description": "Validation Error", 

416 "content": { 

417 "application/json": { 

418 "schema": { 

419 "$ref": "#/components/schemas/HTTPValidationError" 

420 } 

421 } 

422 }, 

423 }, 

424 }, 

425 "summary": "Create Item", 

426 "operationId": "create_item_items__post", 

427 "requestBody": { 

428 "content": { 

429 "application/json": { 

430 "schema": {"$ref": "#/components/schemas/Item"} 

431 } 

432 }, 

433 "required": True, 

434 }, 

435 } 

436 } 

437 }, 

438 "components": { 

439 "schemas": { 

440 "Item": { 

441 "title": "Item", 

442 "required": ["name", "price"], 

443 "type": "object", 

444 "properties": { 

445 "name": {"title": "Name", "type": "string"}, 

446 "price": {"title": "Price", "type": "number"}, 

447 "description": IsDict( 

448 { 

449 "title": "Description", 

450 "anyOf": [{"type": "string"}, {"type": "null"}], 

451 } 

452 ) 

453 | IsDict( 

454 # TODO: remove when deprecating Pydantic v1 

455 {"title": "Description", "type": "string"} 

456 ), 

457 "tax": IsDict( 

458 { 

459 "title": "Tax", 

460 "anyOf": [{"type": "number"}, {"type": "null"}], 

461 } 

462 ) 

463 | IsDict( 

464 # TODO: remove when deprecating Pydantic v1 

465 {"title": "Tax", "type": "number"} 

466 ), 

467 }, 

468 }, 

469 "ValidationError": { 

470 "title": "ValidationError", 

471 "required": ["loc", "msg", "type"], 

472 "type": "object", 

473 "properties": { 

474 "loc": { 

475 "title": "Location", 

476 "type": "array", 

477 "items": { 

478 "anyOf": [{"type": "string"}, {"type": "integer"}] 

479 }, 

480 }, 

481 "msg": {"title": "Message", "type": "string"}, 

482 "type": {"title": "Error Type", "type": "string"}, 

483 }, 

484 }, 

485 "HTTPValidationError": { 

486 "title": "HTTPValidationError", 

487 "type": "object", 

488 "properties": { 

489 "detail": { 

490 "title": "Detail", 

491 "type": "array", 

492 "items": {"$ref": "#/components/schemas/ValidationError"}, 

493 } 

494 }, 

495 }, 

496 } 

497 }, 

498 }