Coverage for tests/test_security_oauth2_optional_description.py: 100%

56 statements  

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

1from typing import Optional 1abcde

2 

3from dirty_equals import IsDict 1abcde

4from fastapi import Depends, FastAPI, Security 1abcde

5from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict 1abcde

6from fastapi.testclient import TestClient 1abcde

7from pydantic import BaseModel 1abcde

8 

9app = FastAPI() 1abcde

10 

11reusable_oauth2 = OAuth2( 1abcde

12 flows={ 

13 "password": { 

14 "tokenUrl": "token", 

15 "scopes": {"read:users": "Read the users", "write:users": "Create users"}, 

16 } 

17 }, 

18 description="OAuth2 security scheme", 

19 auto_error=False, 

20) 

21 

22 

23class User(BaseModel): 1abcde

24 username: str 1abcde

25 

26 

27def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): 1abcde

28 if oauth_header is None: 1abcde

29 return None 1abcde

30 user = User(username=oauth_header) 1abcde

31 return user 1abcde

32 

33 

34@app.post("/login") 1abcde

35def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1abcde

36 return form_data 1abcde

37 

38 

39@app.get("/users/me") 1abcde

40def read_users_me(current_user: Optional[User] = Depends(get_current_user)): 1abcde

41 if current_user is None: 1abcde

42 return {"msg": "Create an account first"} 1abcde

43 return current_user 1abcde

44 

45 

46client = TestClient(app) 1abcde

47 

48 

49def test_security_oauth2(): 1abcde

50 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1abcde

51 assert response.status_code == 200, response.text 1abcde

52 assert response.json() == {"username": "Bearer footokenbar"} 1abcde

53 

54 

55def test_security_oauth2_password_other_header(): 1abcde

56 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1abcde

57 assert response.status_code == 200, response.text 1abcde

58 assert response.json() == {"username": "Other footokenbar"} 1abcde

59 

60 

61def test_security_oauth2_password_bearer_no_header(): 1abcde

62 response = client.get("/users/me") 1abcde

63 assert response.status_code == 200, response.text 1abcde

64 assert response.json() == {"msg": "Create an account first"} 1abcde

65 

66 

67def test_strict_login_None(): 1abcde

68 response = client.post("/login", data=None) 1abcde

69 assert response.status_code == 422 1abcde

70 assert response.json() == IsDict( 1abcde

71 { 

72 "detail": [ 

73 { 

74 "type": "missing", 

75 "loc": ["body", "grant_type"], 

76 "msg": "Field required", 

77 "input": None, 

78 }, 

79 { 

80 "type": "missing", 

81 "loc": ["body", "username"], 

82 "msg": "Field required", 

83 "input": None, 

84 }, 

85 { 

86 "type": "missing", 

87 "loc": ["body", "password"], 

88 "msg": "Field required", 

89 "input": None, 

90 }, 

91 ] 

92 } 

93 ) | IsDict( 

94 # TODO: remove when deprecating Pydantic v1 

95 { 

96 "detail": [ 

97 { 

98 "loc": ["body", "grant_type"], 

99 "msg": "field required", 

100 "type": "value_error.missing", 

101 }, 

102 { 

103 "loc": ["body", "username"], 

104 "msg": "field required", 

105 "type": "value_error.missing", 

106 }, 

107 { 

108 "loc": ["body", "password"], 

109 "msg": "field required", 

110 "type": "value_error.missing", 

111 }, 

112 ] 

113 } 

114 ) 

115 

116 

117def test_strict_login_no_grant_type(): 1abcde

118 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1abcde

119 assert response.status_code == 422 1abcde

120 assert response.json() == IsDict( 1abcde

121 { 

122 "detail": [ 

123 { 

124 "type": "missing", 

125 "loc": ["body", "grant_type"], 

126 "msg": "Field required", 

127 "input": None, 

128 } 

129 ] 

130 } 

131 ) | IsDict( 

132 # TODO: remove when deprecating Pydantic v1 

133 { 

134 "detail": [ 

135 { 

136 "loc": ["body", "grant_type"], 

137 "msg": "field required", 

138 "type": "value_error.missing", 

139 } 

140 ] 

141 } 

142 ) 

143 

144 

145def test_strict_login_incorrect_grant_type(): 1abcde

146 response = client.post( 1abcde

147 "/login", 

148 data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, 

149 ) 

150 assert response.status_code == 422 1abcde

151 assert response.json() == IsDict( 1abcde

152 { 

153 "detail": [ 

154 { 

155 "type": "string_pattern_mismatch", 

156 "loc": ["body", "grant_type"], 

157 "msg": "String should match pattern 'password'", 

158 "input": "incorrect", 

159 "ctx": {"pattern": "password"}, 

160 } 

161 ] 

162 } 

163 ) | IsDict( 

164 # TODO: remove when deprecating Pydantic v1 

165 { 

166 "detail": [ 

167 { 

168 "loc": ["body", "grant_type"], 

169 "msg": 'string does not match regex "password"', 

170 "type": "value_error.str.regex", 

171 "ctx": {"pattern": "password"}, 

172 } 

173 ] 

174 } 

175 ) 

176 

177 

178def test_strict_login_correct_correct_grant_type(): 1abcde

179 response = client.post( 1abcde

180 "/login", 

181 data={"username": "johndoe", "password": "secret", "grant_type": "password"}, 

182 ) 

183 assert response.status_code == 200, response.text 1abcde

184 assert response.json() == { 1abcde

185 "grant_type": "password", 

186 "username": "johndoe", 

187 "password": "secret", 

188 "scopes": [], 

189 "client_id": None, 

190 "client_secret": None, 

191 } 

192 

193 

194def test_openapi_schema(): 1abcde

195 response = client.get("/openapi.json") 1abcde

196 assert response.status_code == 200, response.text 1abcde

197 assert response.json() == { 1abcde

198 "openapi": "3.1.0", 

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

200 "paths": { 

201 "/login": { 

202 "post": { 

203 "responses": { 

204 "200": { 

205 "description": "Successful Response", 

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

207 }, 

208 "422": { 

209 "description": "Validation Error", 

210 "content": { 

211 "application/json": { 

212 "schema": { 

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

214 } 

215 } 

216 }, 

217 }, 

218 }, 

219 "summary": "Login", 

220 "operationId": "login_login_post", 

221 "requestBody": { 

222 "content": { 

223 "application/x-www-form-urlencoded": { 

224 "schema": { 

225 "$ref": "#/components/schemas/Body_login_login_post" 

226 } 

227 } 

228 }, 

229 "required": True, 

230 }, 

231 } 

232 }, 

233 "/users/me": { 

234 "get": { 

235 "responses": { 

236 "200": { 

237 "description": "Successful Response", 

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

239 } 

240 }, 

241 "summary": "Read Users Me", 

242 "operationId": "read_users_me_users_me_get", 

243 "security": [{"OAuth2": []}], 

244 } 

245 }, 

246 }, 

247 "components": { 

248 "schemas": { 

249 "Body_login_login_post": { 

250 "title": "Body_login_login_post", 

251 "required": ["grant_type", "username", "password"], 

252 "type": "object", 

253 "properties": { 

254 "grant_type": { 

255 "title": "Grant Type", 

256 "pattern": "password", 

257 "type": "string", 

258 }, 

259 "username": {"title": "Username", "type": "string"}, 

260 "password": {"title": "Password", "type": "string"}, 

261 "scope": {"title": "Scope", "type": "string", "default": ""}, 

262 "client_id": IsDict( 

263 { 

264 "title": "Client Id", 

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

266 } 

267 ) 

268 | IsDict( 

269 # TODO: remove when deprecating Pydantic v1 

270 {"title": "Client Id", "type": "string"} 

271 ), 

272 "client_secret": IsDict( 

273 { 

274 "title": "Client Secret", 

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

276 } 

277 ) 

278 | IsDict( 

279 # TODO: remove when deprecating Pydantic v1 

280 {"title": "Client Secret", "type": "string"} 

281 ), 

282 }, 

283 }, 

284 "ValidationError": { 

285 "title": "ValidationError", 

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

287 "type": "object", 

288 "properties": { 

289 "loc": { 

290 "title": "Location", 

291 "type": "array", 

292 "items": { 

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

294 }, 

295 }, 

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

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

298 }, 

299 }, 

300 "HTTPValidationError": { 

301 "title": "HTTPValidationError", 

302 "type": "object", 

303 "properties": { 

304 "detail": { 

305 "title": "Detail", 

306 "type": "array", 

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

308 } 

309 }, 

310 }, 

311 }, 

312 "securitySchemes": { 

313 "OAuth2": { 

314 "type": "oauth2", 

315 "flows": { 

316 "password": { 

317 "scopes": { 

318 "read:users": "Read the users", 

319 "write:users": "Create users", 

320 }, 

321 "tokenUrl": "token", 

322 } 

323 }, 

324 "description": "OAuth2 security scheme", 

325 } 

326 }, 

327 }, 

328 }