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

80 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-12 18:15 +0000

1import importlib 1abdc

2from unittest.mock import patch 1abdc

3 

4import pytest 1abdc

5from fastapi.testclient import TestClient 1abdc

6from inline_snapshot import snapshot 1abdc

7 

8from ...utils import needs_py310 1abdc

9 

10 

11@pytest.fixture( 1abdc

12 name="client", 

13 params=[ 

14 pytest.param("tutorial001_py310", marks=needs_py310), 

15 ], 

16) 

17def get_client(request: pytest.FixtureRequest): 1abdc

18 mod = importlib.import_module(f"docs_src.body.{request.param}") 1abc

19 

20 client = TestClient(mod.app) 1abc

21 return client 1abc

22 

23 

24def test_body_float(client: TestClient): 1abdc

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

26 assert response.status_code == 200 1hij

27 assert response.json() == { 1hij

28 "name": "Foo", 

29 "price": 50.5, 

30 "description": None, 

31 "tax": None, 

32 } 

33 

34 

35def test_post_with_str_float(client: TestClient): 1abdc

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

37 assert response.status_code == 200 1klm

38 assert response.json() == { 1klm

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): 1abdc

47 response = client.post( 1nop

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

49 ) 

50 assert response.status_code == 200 1nop

51 assert response.json() == { 1nop

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): 1abdc

60 response = client.post( 1qrs

61 "/items/", 

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

63 ) 

64 assert response.status_code == 200 1qrs

65 assert response.json() == { 1qrs

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): 1abdc

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

75 assert response.status_code == 422 1tuv

76 assert response.json() == { 1tuv

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): 1abdc

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

90 assert response.status_code == 422 1wxy

91 assert response.json() == { 1wxy

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): 1abdc

104 response = client.post("/items/", json={}) 1zAB

105 assert response.status_code == 422 1zAB

106 assert response.json() == { 1zAB

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): 1abdc

125 response = client.post("/items/", json=None) 1CDE

126 assert response.status_code == 422 1CDE

127 assert response.json() == { 1CDE

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): 1abdc

140 response = client.post( 1FGH

141 "/items/", 

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

143 content="{some broken json}", 

144 ) 

145 assert response.status_code == 422, response.text 1FGH

146 assert response.json() == { 1FGH

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): 1abdc

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

161 assert response.status_code == 422, response.text 1IJK

162 assert response.json() == { 1IJK

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): 1abdc

175 response = client.post( 1UVW

176 "/items/", 

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

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

179 ) 

180 assert response.status_code == 200, response.text 1UVW

181 

182 

183def test_geo_json(client: TestClient): 1abdc

184 response = client.post( 1XYZ

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 1XYZ

190 

191 

192def test_no_content_type_is_json(client: TestClient): 1abdc

193 response = client.post( 1LMN

194 "/items/", 

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

196 ) 

197 assert response.status_code == 200, response.text 1LMN

198 assert response.json() == { 1LMN

199 "name": "Foo", 

200 "description": None, 

201 "price": 50.5, 

202 "tax": None, 

203 } 

204 

205 

206def test_wrong_headers(client: TestClient): 1abdc

207 data = '{"name": "Foo", "price": 50.5}' 1efg

208 response = client.post( 1efg

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

210 ) 

211 assert response.status_code == 422, response.text 1efg

212 assert response.json() == { 1efg

213 "detail": [ 

214 { 

215 "type": "model_attributes_type", 

216 "loc": ["body"], 

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

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

219 } 

220 ] 

221 } 

222 

223 response = client.post( 1efg

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

225 ) 

226 assert response.status_code == 422, response.text 1efg

227 assert response.json() == { 1efg

228 "detail": [ 

229 { 

230 "type": "model_attributes_type", 

231 "loc": ["body"], 

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

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

234 } 

235 ] 

236 } 

237 

238 response = client.post( 1efg

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

240 ) 

241 assert response.status_code == 422, response.text 1efg

242 assert response.json() == { 1efg

243 "detail": [ 

244 { 

245 "type": "model_attributes_type", 

246 "loc": ["body"], 

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

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

249 } 

250 ] 

251 } 

252 

253 

254def test_other_exceptions(client: TestClient): 1abdc

255 with patch("json.loads", side_effect=Exception): 1OPQ

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

257 assert response.status_code == 400, response.text 1OPQ

258 

259 

260def test_openapi_schema(client: TestClient): 1abdc

261 response = client.get("/openapi.json") 1RST

262 assert response.status_code == 200, response.text 1RST

263 assert response.json() == snapshot( 1RST

264 { 

265 "openapi": "3.1.0", 

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

267 "paths": { 

268 "/items/": { 

269 "post": { 

270 "responses": { 

271 "200": { 

272 "description": "Successful Response", 

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

274 }, 

275 "422": { 

276 "description": "Validation Error", 

277 "content": { 

278 "application/json": { 

279 "schema": { 

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

281 } 

282 } 

283 }, 

284 }, 

285 }, 

286 "summary": "Create Item", 

287 "operationId": "create_item_items__post", 

288 "requestBody": { 

289 "content": { 

290 "application/json": { 

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

292 } 

293 }, 

294 "required": True, 

295 }, 

296 } 

297 } 

298 }, 

299 "components": { 

300 "schemas": { 

301 "Item": { 

302 "title": "Item", 

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

304 "type": "object", 

305 "properties": { 

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

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

308 "description": { 

309 "title": "Description", 

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

311 }, 

312 "tax": { 

313 "title": "Tax", 

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

315 }, 

316 }, 

317 }, 

318 "ValidationError": { 

319 "title": "ValidationError", 

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

321 "type": "object", 

322 "properties": { 

323 "loc": { 

324 "title": "Location", 

325 "type": "array", 

326 "items": { 

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

328 }, 

329 }, 

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

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

332 "input": {"title": "Input"}, 

333 "ctx": {"title": "Context", "type": "object"}, 

334 }, 

335 }, 

336 "HTTPValidationError": { 

337 "title": "HTTPValidationError", 

338 "type": "object", 

339 "properties": { 

340 "detail": { 

341 "title": "Detail", 

342 "type": "array", 

343 "items": { 

344 "$ref": "#/components/schemas/ValidationError" 

345 }, 

346 } 

347 }, 

348 }, 

349 } 

350 }, 

351 } 

352 )