Coverage for tests/test_tutorial/test_query_param_models/test_tutorial002.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-13 13:38 +0000

1import importlib 1abcde

2 

3import pytest 1abcde

4from dirty_equals import IsDict 1abcde

5from fastapi.testclient import TestClient 1abcde

6from inline_snapshot import snapshot 1abcde

7 

8from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 1abcde

9 

10 

11@pytest.fixture( 1abcde

12 name="client", 

13 params=[ 

14 pytest.param("tutorial002", marks=needs_pydanticv2), 

15 pytest.param("tutorial002_py39", marks=[needs_py39, needs_pydanticv2]), 

16 pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), 

17 pytest.param("tutorial002_an", marks=needs_pydanticv2), 

18 pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), 

19 pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), 

20 pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), 

21 pytest.param("tutorial002_pv1_py39", marks=[needs_py39, needs_pydanticv1]), 

22 pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), 

23 pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), 

24 pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), 

25 pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), 

26 ], 

27) 

28def get_client(request: pytest.FixtureRequest): 1abcde

29 mod = importlib.import_module(f"docs_src.query_param_models.{request.param}") 1abcde

30 

31 client = TestClient(mod.app) 1abcde

32 return client 1abcde

33 

34 

35def test_query_param_model(client: TestClient): 1abcde

36 response = client.get( 1fghij

37 "/items/", 

38 params={ 

39 "limit": 10, 

40 "offset": 5, 

41 "order_by": "updated_at", 

42 "tags": ["tag1", "tag2"], 

43 }, 

44 ) 

45 assert response.status_code == 200 1fghij

46 assert response.json() == { 1fghij

47 "limit": 10, 

48 "offset": 5, 

49 "order_by": "updated_at", 

50 "tags": ["tag1", "tag2"], 

51 } 

52 

53 

54def test_query_param_model_defaults(client: TestClient): 1abcde

55 response = client.get("/items/") 1klmno

56 assert response.status_code == 200 1klmno

57 assert response.json() == { 1klmno

58 "limit": 100, 

59 "offset": 0, 

60 "order_by": "created_at", 

61 "tags": [], 

62 } 

63 

64 

65def test_query_param_model_invalid(client: TestClient): 1abcde

66 response = client.get( 1pqrst

67 "/items/", 

68 params={ 

69 "limit": 150, 

70 "offset": -1, 

71 "order_by": "invalid", 

72 }, 

73 ) 

74 assert response.status_code == 422 1pqrst

75 assert response.json() == snapshot( 1pqrst

76 IsDict( 

77 { 

78 "detail": [ 

79 { 

80 "type": "less_than_equal", 

81 "loc": ["query", "limit"], 

82 "msg": "Input should be less than or equal to 100", 

83 "input": "150", 

84 "ctx": {"le": 100}, 

85 }, 

86 { 

87 "type": "greater_than_equal", 

88 "loc": ["query", "offset"], 

89 "msg": "Input should be greater than or equal to 0", 

90 "input": "-1", 

91 "ctx": {"ge": 0}, 

92 }, 

93 { 

94 "type": "literal_error", 

95 "loc": ["query", "order_by"], 

96 "msg": "Input should be 'created_at' or 'updated_at'", 

97 "input": "invalid", 

98 "ctx": {"expected": "'created_at' or 'updated_at'"}, 

99 }, 

100 ] 

101 } 

102 ) 

103 | IsDict( 

104 # TODO: remove when deprecating Pydantic v1 

105 { 

106 "detail": [ 

107 { 

108 "type": "value_error.number.not_le", 

109 "loc": ["query", "limit"], 

110 "msg": "ensure this value is less than or equal to 100", 

111 "ctx": {"limit_value": 100}, 

112 }, 

113 { 

114 "type": "value_error.number.not_ge", 

115 "loc": ["query", "offset"], 

116 "msg": "ensure this value is greater than or equal to 0", 

117 "ctx": {"limit_value": 0}, 

118 }, 

119 { 

120 "type": "value_error.const", 

121 "loc": ["query", "order_by"], 

122 "msg": "unexpected value; permitted: 'created_at', 'updated_at'", 

123 "ctx": { 

124 "given": "invalid", 

125 "permitted": ["created_at", "updated_at"], 

126 }, 

127 }, 

128 ] 

129 } 

130 ) 

131 ) 

132 

133 

134def test_query_param_model_extra(client: TestClient): 1abcde

135 response = client.get( 1uvwxy

136 "/items/", 

137 params={ 

138 "limit": 10, 

139 "offset": 5, 

140 "order_by": "updated_at", 

141 "tags": ["tag1", "tag2"], 

142 "tool": "plumbus", 

143 }, 

144 ) 

145 assert response.status_code == 422 1uvwxy

146 assert response.json() == snapshot( 1uvwxy

147 { 

148 "detail": [ 

149 IsDict( 

150 { 

151 "type": "extra_forbidden", 

152 "loc": ["query", "tool"], 

153 "msg": "Extra inputs are not permitted", 

154 "input": "plumbus", 

155 } 

156 ) 

157 | IsDict( 

158 # TODO: remove when deprecating Pydantic v1 

159 { 

160 "type": "value_error.extra", 

161 "loc": ["query", "tool"], 

162 "msg": "extra fields not permitted", 

163 } 

164 ) 

165 ] 

166 } 

167 ) 

168 

169 

170def test_openapi_schema(client: TestClient): 1abcde

171 response = client.get("/openapi.json") 1zABCD

172 assert response.status_code == 200, response.text 1zABCD

173 assert response.json() == snapshot( 1zABCD

174 { 

175 "openapi": "3.1.0", 

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

177 "paths": { 

178 "/items/": { 

179 "get": { 

180 "summary": "Read Items", 

181 "operationId": "read_items_items__get", 

182 "parameters": [ 

183 { 

184 "name": "limit", 

185 "in": "query", 

186 "required": False, 

187 "schema": { 

188 "type": "integer", 

189 "maximum": 100, 

190 "exclusiveMinimum": 0, 

191 "default": 100, 

192 "title": "Limit", 

193 }, 

194 }, 

195 { 

196 "name": "offset", 

197 "in": "query", 

198 "required": False, 

199 "schema": { 

200 "type": "integer", 

201 "minimum": 0, 

202 "default": 0, 

203 "title": "Offset", 

204 }, 

205 }, 

206 { 

207 "name": "order_by", 

208 "in": "query", 

209 "required": False, 

210 "schema": { 

211 "enum": ["created_at", "updated_at"], 

212 "type": "string", 

213 "default": "created_at", 

214 "title": "Order By", 

215 }, 

216 }, 

217 { 

218 "name": "tags", 

219 "in": "query", 

220 "required": False, 

221 "schema": { 

222 "type": "array", 

223 "items": {"type": "string"}, 

224 "default": [], 

225 "title": "Tags", 

226 }, 

227 }, 

228 ], 

229 "responses": { 

230 "200": { 

231 "description": "Successful Response", 

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

233 }, 

234 "422": { 

235 "description": "Validation Error", 

236 "content": { 

237 "application/json": { 

238 "schema": { 

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

240 } 

241 } 

242 }, 

243 }, 

244 }, 

245 } 

246 } 

247 }, 

248 "components": { 

249 "schemas": { 

250 "HTTPValidationError": { 

251 "properties": { 

252 "detail": { 

253 "items": { 

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

255 }, 

256 "type": "array", 

257 "title": "Detail", 

258 } 

259 }, 

260 "type": "object", 

261 "title": "HTTPValidationError", 

262 }, 

263 "ValidationError": { 

264 "properties": { 

265 "loc": { 

266 "items": { 

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

268 }, 

269 "type": "array", 

270 "title": "Location", 

271 }, 

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

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

274 }, 

275 "type": "object", 

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

277 "title": "ValidationError", 

278 }, 

279 } 

280 }, 

281 } 

282 )