Coverage for tests/test_security_oauth2.py: 100%
51 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-08 03:53 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-08 03:53 +0000
1from dirty_equals import IsDict 1abcde
2from fastapi import Depends, FastAPI, Security 1abcde
3from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict 1abcde
4from fastapi.testclient import TestClient 1abcde
5from pydantic import BaseModel 1abcde
7app = FastAPI() 1abcde
9reusable_oauth2 = OAuth2( 1abcde
10 flows={
11 "password": {
12 "tokenUrl": "token",
13 "scopes": {"read:users": "Read the users", "write:users": "Create users"},
14 }
15 }
16)
19class User(BaseModel): 1abcde
20 username: str 1abcde
23# Here we use string annotations to test them
24def get_current_user(oauth_header: "str" = Security(reusable_oauth2)): 1abcde
25 user = User(username=oauth_header) 1abcde
26 return user 1abcde
29@app.post("/login") 1abcde
30# Here we use string annotations to test them
31def login(form_data: "OAuth2PasswordRequestFormStrict" = Depends()): 1abcde
32 return form_data 1abcde
35@app.get("/users/me") 1abcde
36# Here we use string annotations to test them
37def read_current_user(current_user: "User" = Depends(get_current_user)): 1abcde
38 return current_user 1abcde
41client = TestClient(app) 1abcde
44def test_security_oauth2(): 1abcde
45 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1abcde
46 assert response.status_code == 200, response.text 1abcde
47 assert response.json() == {"username": "Bearer footokenbar"} 1abcde
50def test_security_oauth2_password_other_header(): 1abcde
51 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1abcde
52 assert response.status_code == 200, response.text 1abcde
53 assert response.json() == {"username": "Other footokenbar"} 1abcde
56def test_security_oauth2_password_bearer_no_header(): 1abcde
57 response = client.get("/users/me") 1abcde
58 assert response.status_code == 403, response.text 1abcde
59 assert response.json() == {"detail": "Not authenticated"} 1abcde
62def test_strict_login_no_data(): 1abcde
63 response = client.post("/login") 1abcde
64 assert response.status_code == 422 1abcde
65 assert response.json() == IsDict( 1abcde
66 {
67 "detail": [
68 {
69 "type": "missing",
70 "loc": ["body", "grant_type"],
71 "msg": "Field required",
72 "input": None,
73 },
74 {
75 "type": "missing",
76 "loc": ["body", "username"],
77 "msg": "Field required",
78 "input": None,
79 },
80 {
81 "type": "missing",
82 "loc": ["body", "password"],
83 "msg": "Field required",
84 "input": None,
85 },
86 ]
87 }
88 ) | IsDict(
89 # TODO: remove when deprecating Pydantic v1
90 {
91 "detail": [
92 {
93 "loc": ["body", "grant_type"],
94 "msg": "field required",
95 "type": "value_error.missing",
96 },
97 {
98 "loc": ["body", "username"],
99 "msg": "field required",
100 "type": "value_error.missing",
101 },
102 {
103 "loc": ["body", "password"],
104 "msg": "field required",
105 "type": "value_error.missing",
106 },
107 ]
108 }
109 )
112def test_strict_login_no_grant_type(): 1abcde
113 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1abcde
114 assert response.status_code == 422 1abcde
115 assert response.json() == IsDict( 1abcde
116 {
117 "detail": [
118 {
119 "type": "missing",
120 "loc": ["body", "grant_type"],
121 "msg": "Field required",
122 "input": None,
123 }
124 ]
125 }
126 ) | IsDict(
127 # TODO: remove when deprecating Pydantic v1
128 {
129 "detail": [
130 {
131 "loc": ["body", "grant_type"],
132 "msg": "field required",
133 "type": "value_error.missing",
134 }
135 ]
136 }
137 )
140def test_strict_login_incorrect_grant_type(): 1abcde
141 response = client.post( 1abcde
142 "/login",
143 data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
144 )
145 assert response.status_code == 422 1abcde
146 assert response.json() == IsDict( 1abcde
147 {
148 "detail": [
149 {
150 "type": "string_pattern_mismatch",
151 "loc": ["body", "grant_type"],
152 "msg": "String should match pattern 'password'",
153 "input": "incorrect",
154 "ctx": {"pattern": "password"},
155 }
156 ]
157 }
158 ) | IsDict(
159 # TODO: remove when deprecating Pydantic v1
160 {
161 "detail": [
162 {
163 "loc": ["body", "grant_type"],
164 "msg": 'string does not match regex "password"',
165 "type": "value_error.str.regex",
166 "ctx": {"pattern": "password"},
167 }
168 ]
169 }
170 )
173def test_strict_login_correct_grant_type(): 1abcde
174 response = client.post( 1abcde
175 "/login",
176 data={"username": "johndoe", "password": "secret", "grant_type": "password"},
177 )
178 assert response.status_code == 200 1abcde
179 assert response.json() == { 1abcde
180 "grant_type": "password",
181 "username": "johndoe",
182 "password": "secret",
183 "scopes": [],
184 "client_id": None,
185 "client_secret": None,
186 }
189def test_openapi_schema(): 1abcde
190 response = client.get("/openapi.json") 1abcde
191 assert response.status_code == 200, response.text 1abcde
192 assert response.json() == { 1abcde
193 "openapi": "3.1.0",
194 "info": {"title": "FastAPI", "version": "0.1.0"},
195 "paths": {
196 "/login": {
197 "post": {
198 "responses": {
199 "200": {
200 "description": "Successful Response",
201 "content": {"application/json": {"schema": {}}},
202 },
203 "422": {
204 "description": "Validation Error",
205 "content": {
206 "application/json": {
207 "schema": {
208 "$ref": "#/components/schemas/HTTPValidationError"
209 }
210 }
211 },
212 },
213 },
214 "summary": "Login",
215 "operationId": "login_login_post",
216 "requestBody": {
217 "content": {
218 "application/x-www-form-urlencoded": {
219 "schema": {
220 "$ref": "#/components/schemas/Body_login_login_post"
221 }
222 }
223 },
224 "required": True,
225 },
226 }
227 },
228 "/users/me": {
229 "get": {
230 "responses": {
231 "200": {
232 "description": "Successful Response",
233 "content": {"application/json": {"schema": {}}},
234 }
235 },
236 "summary": "Read Current User",
237 "operationId": "read_current_user_users_me_get",
238 "security": [{"OAuth2": []}],
239 }
240 },
241 },
242 "components": {
243 "schemas": {
244 "Body_login_login_post": {
245 "title": "Body_login_login_post",
246 "required": ["grant_type", "username", "password"],
247 "type": "object",
248 "properties": {
249 "grant_type": {
250 "title": "Grant Type",
251 "pattern": "password",
252 "type": "string",
253 },
254 "username": {"title": "Username", "type": "string"},
255 "password": {"title": "Password", "type": "string"},
256 "scope": {"title": "Scope", "type": "string", "default": ""},
257 "client_id": IsDict(
258 {
259 "title": "Client Id",
260 "anyOf": [{"type": "string"}, {"type": "null"}],
261 }
262 )
263 | IsDict(
264 # TODO: remove when deprecating Pydantic v1
265 {"title": "Client Id", "type": "string"}
266 ),
267 "client_secret": IsDict(
268 {
269 "title": "Client Secret",
270 "anyOf": [{"type": "string"}, {"type": "null"}],
271 }
272 )
273 | IsDict(
274 # TODO: remove when deprecating Pydantic v1
275 {"title": "Client Secret", "type": "string"}
276 ),
277 },
278 },
279 "ValidationError": {
280 "title": "ValidationError",
281 "required": ["loc", "msg", "type"],
282 "type": "object",
283 "properties": {
284 "loc": {
285 "title": "Location",
286 "type": "array",
287 "items": {
288 "anyOf": [{"type": "string"}, {"type": "integer"}]
289 },
290 },
291 "msg": {"title": "Message", "type": "string"},
292 "type": {"title": "Error Type", "type": "string"},
293 },
294 },
295 "HTTPValidationError": {
296 "title": "HTTPValidationError",
297 "type": "object",
298 "properties": {
299 "detail": {
300 "title": "Detail",
301 "type": "array",
302 "items": {"$ref": "#/components/schemas/ValidationError"},
303 }
304 },
305 },
306 },
307 "securitySchemes": {
308 "OAuth2": {
309 "type": "oauth2",
310 "flows": {
311 "password": {
312 "scopes": {
313 "read:users": "Read the users",
314 "write:users": "Create users",
315 },
316 "tokenUrl": "token",
317 }
318 },
319 }
320 },
321 },
322 }