Coverage for tests / test_annotated.py: 100%

60 statements  

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

1from typing import Annotated 1abcd

2 

3import pytest 1abcd

4from fastapi import APIRouter, FastAPI, Query 1abcd

5from fastapi.testclient import TestClient 1abcd

6from inline_snapshot import snapshot 1abcd

7 

8app = FastAPI() 1abcd

9 

10 

11@app.get("/default") 1abcd

12async def default(foo: Annotated[str, Query()] = "foo"): 1abcd

13 return {"foo": foo} 1klm

14 

15 

16@app.get("/required") 1abcd

17async def required(foo: Annotated[str, Query(min_length=1)]): 1abcd

18 return {"foo": foo} 1klm

19 

20 

21@app.get("/multiple") 1abcd

22async def multiple(foo: Annotated[str, object(), Query(min_length=1)]): 1abcd

23 return {"foo": foo} 1klm

24 

25 

26@app.get("/unrelated") 1abcd

27async def unrelated(foo: Annotated[str, object()]): 1abcd

28 return {"foo": foo} 1klm

29 

30 

31client = TestClient(app) 1abcd

32 

33foo_is_missing = { 1abcd

34 "detail": [ 

35 { 

36 "loc": ["query", "foo"], 

37 "msg": "Field required", 

38 "type": "missing", 

39 "input": None, 

40 } 

41 ] 

42} 

43foo_is_short = { 1abcd

44 "detail": [ 

45 { 

46 "ctx": {"min_length": 1}, 

47 "loc": ["query", "foo"], 

48 "msg": "String should have at least 1 character", 

49 "type": "string_too_short", 

50 "input": "", 

51 } 

52 ] 

53} 

54 

55 

56@pytest.mark.parametrize( 1abcd

57 "path,expected_status,expected_response", 

58 [ 

59 ("/default", 200, {"foo": "foo"}), 

60 ("/default?foo=bar", 200, {"foo": "bar"}), 

61 ("/required?foo=bar", 200, {"foo": "bar"}), 

62 ("/required", 422, foo_is_missing), 

63 ("/required?foo=", 422, foo_is_short), 

64 ("/multiple?foo=bar", 200, {"foo": "bar"}), 

65 ("/multiple", 422, foo_is_missing), 

66 ("/multiple?foo=", 422, foo_is_short), 

67 ("/unrelated?foo=bar", 200, {"foo": "bar"}), 

68 ("/unrelated", 422, foo_is_missing), 

69 ], 

70) 

71def test_get(path, expected_status, expected_response): 1abcd

72 response = client.get(path) 1klm

73 assert response.status_code == expected_status 1klm

74 assert response.json() == expected_response 1klm

75 

76 

77def test_multiple_path(): 1abcd

78 app = FastAPI() 1efg

79 

80 @app.get("/test1") 1efg

81 @app.get("/test2") 1efg

82 async def test(var: Annotated[str, Query()] = "bar"): 1efg

83 return {"foo": var} 1efg

84 

85 client = TestClient(app) 1efg

86 response = client.get("/test1") 1efg

87 assert response.status_code == 200 1efg

88 assert response.json() == {"foo": "bar"} 1efg

89 

90 response = client.get("/test1", params={"var": "baz"}) 1efg

91 assert response.status_code == 200 1efg

92 assert response.json() == {"foo": "baz"} 1efg

93 

94 response = client.get("/test2") 1efg

95 assert response.status_code == 200 1efg

96 assert response.json() == {"foo": "bar"} 1efg

97 

98 response = client.get("/test2", params={"var": "baz"}) 1efg

99 assert response.status_code == 200 1efg

100 assert response.json() == {"foo": "baz"} 1efg

101 

102 

103def test_nested_router(): 1abcd

104 app = FastAPI() 1hij

105 

106 router = APIRouter(prefix="/nested") 1hij

107 

108 @router.get("/test") 1hij

109 async def test(var: Annotated[str, Query()] = "bar"): 1hij

110 return {"foo": var} 1hij

111 

112 app.include_router(router) 1hij

113 

114 client = TestClient(app) 1hij

115 

116 response = client.get("/nested/test") 1hij

117 assert response.status_code == 200 1hij

118 assert response.json() == {"foo": "bar"} 1hij

119 

120 

121def test_openapi_schema(): 1abcd

122 response = client.get("/openapi.json") 1nop

123 assert response.status_code == 200 1nop

124 assert response.json() == snapshot( 1nop

125 { 

126 "openapi": "3.1.0", 

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

128 "paths": { 

129 "/default": { 

130 "get": { 

131 "summary": "Default", 

132 "operationId": "default_default_get", 

133 "parameters": [ 

134 { 

135 "required": False, 

136 "schema": { 

137 "title": "Foo", 

138 "type": "string", 

139 "default": "foo", 

140 }, 

141 "name": "foo", 

142 "in": "query", 

143 } 

144 ], 

145 "responses": { 

146 "200": { 

147 "description": "Successful Response", 

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

149 }, 

150 "422": { 

151 "description": "Validation Error", 

152 "content": { 

153 "application/json": { 

154 "schema": { 

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

156 } 

157 } 

158 }, 

159 }, 

160 }, 

161 } 

162 }, 

163 "/required": { 

164 "get": { 

165 "summary": "Required", 

166 "operationId": "required_required_get", 

167 "parameters": [ 

168 { 

169 "required": True, 

170 "schema": { 

171 "title": "Foo", 

172 "minLength": 1, 

173 "type": "string", 

174 }, 

175 "name": "foo", 

176 "in": "query", 

177 } 

178 ], 

179 "responses": { 

180 "200": { 

181 "description": "Successful Response", 

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

183 }, 

184 "422": { 

185 "description": "Validation Error", 

186 "content": { 

187 "application/json": { 

188 "schema": { 

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

190 } 

191 } 

192 }, 

193 }, 

194 }, 

195 } 

196 }, 

197 "/multiple": { 

198 "get": { 

199 "summary": "Multiple", 

200 "operationId": "multiple_multiple_get", 

201 "parameters": [ 

202 { 

203 "required": True, 

204 "schema": { 

205 "title": "Foo", 

206 "minLength": 1, 

207 "type": "string", 

208 }, 

209 "name": "foo", 

210 "in": "query", 

211 } 

212 ], 

213 "responses": { 

214 "200": { 

215 "description": "Successful Response", 

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

217 }, 

218 "422": { 

219 "description": "Validation Error", 

220 "content": { 

221 "application/json": { 

222 "schema": { 

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

224 } 

225 } 

226 }, 

227 }, 

228 }, 

229 } 

230 }, 

231 "/unrelated": { 

232 "get": { 

233 "summary": "Unrelated", 

234 "operationId": "unrelated_unrelated_get", 

235 "parameters": [ 

236 { 

237 "required": True, 

238 "schema": {"title": "Foo", "type": "string"}, 

239 "name": "foo", 

240 "in": "query", 

241 } 

242 ], 

243 "responses": { 

244 "200": { 

245 "description": "Successful Response", 

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

247 }, 

248 "422": { 

249 "description": "Validation Error", 

250 "content": { 

251 "application/json": { 

252 "schema": { 

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

254 } 

255 } 

256 }, 

257 }, 

258 }, 

259 } 

260 }, 

261 }, 

262 "components": { 

263 "schemas": { 

264 "HTTPValidationError": { 

265 "title": "HTTPValidationError", 

266 "type": "object", 

267 "properties": { 

268 "detail": { 

269 "title": "Detail", 

270 "type": "array", 

271 "items": { 

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

273 }, 

274 } 

275 }, 

276 }, 

277 "ValidationError": { 

278 "title": "ValidationError", 

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

280 "type": "object", 

281 "properties": { 

282 "loc": { 

283 "title": "Location", 

284 "type": "array", 

285 "items": { 

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

287 }, 

288 }, 

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

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

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

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

293 }, 

294 }, 

295 } 

296 }, 

297 } 

298 )