Coverage for tests / test_security_oauth2_optional.py: 100%

57 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-04-06 01:24 +0000

1import pytest 1adbc

2from fastapi import Depends, FastAPI, Security 1adbc

3from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict 1adbc

4from fastapi.testclient import TestClient 1adbc

5from inline_snapshot import snapshot 1adbc

6from pydantic import BaseModel 1adbc

7 

8app = FastAPI() 1adbc

9 

10reusable_oauth2 = OAuth2( 1adbc

11 flows={ 

12 "password": { 

13 "tokenUrl": "token", 

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

15 } 

16 }, 

17 auto_error=False, 

18) 

19 

20 

21class User(BaseModel): 1adbc

22 username: str 1abc

23 

24 

25def get_current_user(oauth_header: str | None = Security(reusable_oauth2)): 1adbc

26 if oauth_header is None: 1mefghnijokl

27 return None 1mpno

28 user = User(username=oauth_header) 1efghijkl

29 return user 1efghijkl

30 

31 

32@app.post("/login") 1adbc

33def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1adbc

34 return form_data 1qrst

35 

36 

37@app.get("/users/me") 1adbc

38def read_users_me(current_user: User | None = Depends(get_current_user)): 1adbc

39 if current_user is None: 1mefghnijokl

40 return {"msg": "Create an account first"} 1mpno

41 return current_user 1efghijkl

42 

43 

44client = TestClient(app) 1adbc

45 

46 

47def test_security_oauth2(): 1adbc

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

49 assert response.status_code == 200, response.text 1fhjl

50 assert response.json() == {"username": "Bearer footokenbar"} 1fhjl

51 

52 

53def test_security_oauth2_password_other_header(): 1adbc

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

55 assert response.status_code == 200, response.text 1egik

56 assert response.json() == {"username": "Other footokenbar"} 1egik

57 

58 

59def test_security_oauth2_password_bearer_no_header(): 1adbc

60 response = client.get("/users/me") 1mpno

61 assert response.status_code == 200, response.text 1mpno

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

63 

64 

65def test_strict_login_no_data(): 1adbc

66 response = client.post("/login") 1uvwx

67 assert response.status_code == 422 1uvwx

68 assert response.json() == { 1uvwx

69 "detail": [ 

70 { 

71 "type": "missing", 

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

73 "msg": "Field required", 

74 "input": None, 

75 }, 

76 { 

77 "type": "missing", 

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

79 "msg": "Field required", 

80 "input": None, 

81 }, 

82 { 

83 "type": "missing", 

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

85 "msg": "Field required", 

86 "input": None, 

87 }, 

88 ] 

89 } 

90 

91 

92def test_strict_login_no_grant_type(): 1adbc

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

94 assert response.status_code == 422 1yzAB

95 assert response.json() == { 1yzAB

96 "detail": [ 

97 { 

98 "type": "missing", 

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

100 "msg": "Field required", 

101 "input": None, 

102 } 

103 ] 

104 } 

105 

106 

107@pytest.mark.parametrize( 1adbc

108 argnames=["grant_type"], 

109 argvalues=[ 

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

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

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

113 ], 

114) 

115def test_strict_login_incorrect_grant_type(grant_type: str): 1adbc

116 response = client.post( 1CDEFGHIJKL

117 "/login", 

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

119 ) 

120 assert response.status_code == 422 1CDEFGHIJKL

121 assert response.json() == { 1CDEFGHIJKL

122 "detail": [ 

123 { 

124 "type": "string_pattern_mismatch", 

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

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

127 "input": grant_type, 

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

129 } 

130 ] 

131 } 

132 

133 

134def test_strict_login_correct_data(): 1adbc

135 response = client.post( 1qrst

136 "/login", 

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

138 ) 

139 assert response.status_code == 200 1qrst

140 assert response.json() == { 1qrst

141 "grant_type": "password", 

142 "username": "johndoe", 

143 "password": "secret", 

144 "scopes": [], 

145 "client_id": None, 

146 "client_secret": None, 

147 } 

148 

149 

150def test_openapi_schema(): 1adbc

151 response = client.get("/openapi.json") 1MNOP

152 assert response.status_code == 200, response.text 1MNOP

153 assert response.json() == snapshot( 1MNOP

154 { 

155 "openapi": "3.1.0", 

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

157 "paths": { 

158 "/login": { 

159 "post": { 

160 "responses": { 

161 "200": { 

162 "description": "Successful Response", 

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

164 }, 

165 "422": { 

166 "description": "Validation Error", 

167 "content": { 

168 "application/json": { 

169 "schema": { 

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

171 } 

172 } 

173 }, 

174 }, 

175 }, 

176 "summary": "Login", 

177 "operationId": "login_login_post", 

178 "requestBody": { 

179 "content": { 

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

181 "schema": { 

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

183 } 

184 } 

185 }, 

186 "required": True, 

187 }, 

188 } 

189 }, 

190 "/users/me": { 

191 "get": { 

192 "responses": { 

193 "200": { 

194 "description": "Successful Response", 

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

196 } 

197 }, 

198 "summary": "Read Users Me", 

199 "operationId": "read_users_me_users_me_get", 

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

201 } 

202 }, 

203 }, 

204 "components": { 

205 "schemas": { 

206 "Body_login_login_post": { 

207 "title": "Body_login_login_post", 

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

209 "type": "object", 

210 "properties": { 

211 "grant_type": { 

212 "title": "Grant Type", 

213 "pattern": "^password$", 

214 "type": "string", 

215 }, 

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

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

218 "scope": { 

219 "title": "Scope", 

220 "type": "string", 

221 "default": "", 

222 }, 

223 "client_id": { 

224 "title": "Client Id", 

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

226 }, 

227 "client_secret": { 

228 "title": "Client Secret", 

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

230 }, 

231 }, 

232 }, 

233 "ValidationError": { 

234 "title": "ValidationError", 

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

236 "type": "object", 

237 "properties": { 

238 "loc": { 

239 "title": "Location", 

240 "type": "array", 

241 "items": { 

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

243 }, 

244 }, 

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

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

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

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

249 }, 

250 }, 

251 "HTTPValidationError": { 

252 "title": "HTTPValidationError", 

253 "type": "object", 

254 "properties": { 

255 "detail": { 

256 "title": "Detail", 

257 "type": "array", 

258 "items": { 

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

260 }, 

261 } 

262 }, 

263 }, 

264 }, 

265 "securitySchemes": { 

266 "OAuth2": { 

267 "type": "oauth2", 

268 "flows": { 

269 "password": { 

270 "scopes": { 

271 "read:users": "Read the users", 

272 "write:users": "Create users", 

273 }, 

274 "tokenUrl": "token", 

275 } 

276 }, 

277 } 

278 }, 

279 }, 

280 } 

281 )