Coverage for tests / test_security_oauth2_optional_description.py: 100%

58 statements  

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

1from typing import Optional 1abcd

2 

3import pytest 1abcd

4from fastapi import Depends, FastAPI, Security 1abcd

5from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict 1abcd

6from fastapi.testclient import TestClient 1abcd

7from inline_snapshot import snapshot 1abcd

8from pydantic import BaseModel 1abcd

9 

10app = FastAPI() 1abcd

11 

12reusable_oauth2 = OAuth2( 1abcd

13 flows={ 

14 "password": { 

15 "tokenUrl": "token", 

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

17 } 

18 }, 

19 description="OAuth2 security scheme", 

20 auto_error=False, 

21) 

22 

23 

24class User(BaseModel): 1abcd

25 username: str 1abcd

26 

27 

28def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): 1abcd

29 if oauth_header is None: 1ekfglhimj

30 return None 1klm

31 user = User(username=oauth_header) 1efghij

32 return user 1efghij

33 

34 

35@app.post("/login") 1abcd

36def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1abcd

37 return form_data 1nop

38 

39 

40@app.get("/users/me") 1abcd

41def read_users_me(current_user: Optional[User] = Depends(get_current_user)): 1abcd

42 if current_user is None: 1ekfglhimj

43 return {"msg": "Create an account first"} 1klm

44 return current_user 1efghij

45 

46 

47client = TestClient(app) 1abcd

48 

49 

50def test_security_oauth2(): 1abcd

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

52 assert response.status_code == 200, response.text 1egi

53 assert response.json() == {"username": "Bearer footokenbar"} 1egi

54 

55 

56def test_security_oauth2_password_other_header(): 1abcd

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

58 assert response.status_code == 200, response.text 1fhj

59 assert response.json() == {"username": "Other footokenbar"} 1fhj

60 

61 

62def test_security_oauth2_password_bearer_no_header(): 1abcd

63 response = client.get("/users/me") 1klm

64 assert response.status_code == 200, response.text 1klm

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

66 

67 

68def test_strict_login_None(): 1abcd

69 response = client.post("/login", data=None) 1qrs

70 assert response.status_code == 422 1qrs

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

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 

94 

95def test_strict_login_no_grant_type(): 1abcd

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

97 assert response.status_code == 422 1tuv

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

99 "detail": [ 

100 { 

101 "type": "missing", 

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

103 "msg": "Field required", 

104 "input": None, 

105 } 

106 ] 

107 } 

108 

109 

110@pytest.mark.parametrize( 1abcd

111 argnames=["grant_type"], 

112 argvalues=[ 

113 pytest.param("incorrect", id="incorrect value"), 

114 pytest.param("passwordblah", id="password with suffix"), 

115 pytest.param("blahpassword", id="password with prefix"), 

116 ], 

117) 

118def test_strict_login_incorrect_grant_type(grant_type: str): 1abcd

119 response = client.post( 1wxy

120 "/login", 

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

122 ) 

123 assert response.status_code == 422 1wxy

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

125 "detail": [ 

126 { 

127 "type": "string_pattern_mismatch", 

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

129 "msg": "String should match pattern '^password$'", 

130 "input": grant_type, 

131 "ctx": {"pattern": "^password$"}, 

132 } 

133 ] 

134 } 

135 

136 

137def test_strict_login_correct_correct_grant_type(): 1abcd

138 response = client.post( 1nop

139 "/login", 

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

141 ) 

142 assert response.status_code == 200, response.text 1nop

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

144 "grant_type": "password", 

145 "username": "johndoe", 

146 "password": "secret", 

147 "scopes": [], 

148 "client_id": None, 

149 "client_secret": None, 

150 } 

151 

152 

153def test_openapi_schema(): 1abcd

154 response = client.get("/openapi.json") 1zAB

155 assert response.status_code == 200, response.text 1zAB

156 assert response.json() == snapshot( 1zAB

157 { 

158 "openapi": "3.1.0", 

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

160 "paths": { 

161 "/login": { 

162 "post": { 

163 "responses": { 

164 "200": { 

165 "description": "Successful Response", 

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

167 }, 

168 "422": { 

169 "description": "Validation Error", 

170 "content": { 

171 "application/json": { 

172 "schema": { 

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

174 } 

175 } 

176 }, 

177 }, 

178 }, 

179 "summary": "Login", 

180 "operationId": "login_login_post", 

181 "requestBody": { 

182 "content": { 

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

184 "schema": { 

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

186 } 

187 } 

188 }, 

189 "required": True, 

190 }, 

191 } 

192 }, 

193 "/users/me": { 

194 "get": { 

195 "responses": { 

196 "200": { 

197 "description": "Successful Response", 

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

199 } 

200 }, 

201 "summary": "Read Users Me", 

202 "operationId": "read_users_me_users_me_get", 

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

204 } 

205 }, 

206 }, 

207 "components": { 

208 "schemas": { 

209 "Body_login_login_post": { 

210 "title": "Body_login_login_post", 

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

212 "type": "object", 

213 "properties": { 

214 "grant_type": { 

215 "title": "Grant Type", 

216 "pattern": "^password$", 

217 "type": "string", 

218 }, 

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

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

221 "scope": { 

222 "title": "Scope", 

223 "type": "string", 

224 "default": "", 

225 }, 

226 "client_id": { 

227 "title": "Client Id", 

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

229 }, 

230 "client_secret": { 

231 "title": "Client Secret", 

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

233 }, 

234 }, 

235 }, 

236 "ValidationError": { 

237 "title": "ValidationError", 

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

239 "type": "object", 

240 "properties": { 

241 "loc": { 

242 "title": "Location", 

243 "type": "array", 

244 "items": { 

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

246 }, 

247 }, 

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

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

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

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

252 }, 

253 }, 

254 "HTTPValidationError": { 

255 "title": "HTTPValidationError", 

256 "type": "object", 

257 "properties": { 

258 "detail": { 

259 "title": "Detail", 

260 "type": "array", 

261 "items": { 

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

263 }, 

264 } 

265 }, 

266 }, 

267 }, 

268 "securitySchemes": { 

269 "OAuth2": { 

270 "type": "oauth2", 

271 "flows": { 

272 "password": { 

273 "scopes": { 

274 "read:users": "Read the users", 

275 "write:users": "Create users", 

276 }, 

277 "tokenUrl": "token", 

278 } 

279 }, 

280 "description": "OAuth2 security scheme", 

281 } 

282 }, 

283 }, 

284 } 

285 )