Coverage for tests/test_security_oauth2_optional.py: 100%
56 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 typing import Optional 1abcde
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
9app = FastAPI() 1abcde
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 auto_error=False,
19)
22class User(BaseModel): 1abcde
23 username: str 1abcde
26def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): 1abcde
27 if oauth_header is None: 1abcde
28 return None 1abcde
29 user = User(username=oauth_header) 1abcde
30 return user 1abcde
33@app.post("/login") 1abcde
34def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1abcde
35 return form_data 1abcde
38@app.get("/users/me") 1abcde
39def read_users_me(current_user: Optional[User] = Depends(get_current_user)): 1abcde
40 if current_user is None: 1abcde
41 return {"msg": "Create an account first"} 1abcde
42 return current_user 1abcde
45client = TestClient(app) 1abcde
48def test_security_oauth2(): 1abcde
49 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1abcde
50 assert response.status_code == 200, response.text 1abcde
51 assert response.json() == {"username": "Bearer footokenbar"} 1abcde
54def test_security_oauth2_password_other_header(): 1abcde
55 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1abcde
56 assert response.status_code == 200, response.text 1abcde
57 assert response.json() == {"username": "Other footokenbar"} 1abcde
60def test_security_oauth2_password_bearer_no_header(): 1abcde
61 response = client.get("/users/me") 1abcde
62 assert response.status_code == 200, response.text 1abcde
63 assert response.json() == {"msg": "Create an account first"} 1abcde
66def test_strict_login_no_data(): 1abcde
67 response = client.post("/login") 1abcde
68 assert response.status_code == 422 1abcde
69 assert response.json() == IsDict( 1abcde
70 {
71 "detail": [
72 {
73 "type": "missing",
74 "loc": ["body", "grant_type"],
75 "msg": "Field required",
76 "input": None,
77 },
78 {
79 "type": "missing",
80 "loc": ["body", "username"],
81 "msg": "Field required",
82 "input": None,
83 },
84 {
85 "type": "missing",
86 "loc": ["body", "password"],
87 "msg": "Field required",
88 "input": None,
89 },
90 ]
91 }
92 ) | IsDict(
93 # TODO: remove when deprecating Pydantic v1
94 {
95 "detail": [
96 {
97 "loc": ["body", "grant_type"],
98 "msg": "field required",
99 "type": "value_error.missing",
100 },
101 {
102 "loc": ["body", "username"],
103 "msg": "field required",
104 "type": "value_error.missing",
105 },
106 {
107 "loc": ["body", "password"],
108 "msg": "field required",
109 "type": "value_error.missing",
110 },
111 ]
112 }
113 )
116def test_strict_login_no_grant_type(): 1abcde
117 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1abcde
118 assert response.status_code == 422 1abcde
119 assert response.json() == IsDict( 1abcde
120 {
121 "detail": [
122 {
123 "type": "missing",
124 "loc": ["body", "grant_type"],
125 "msg": "Field required",
126 "input": None,
127 }
128 ]
129 }
130 ) | IsDict(
131 # TODO: remove when deprecating Pydantic v1
132 {
133 "detail": [
134 {
135 "loc": ["body", "grant_type"],
136 "msg": "field required",
137 "type": "value_error.missing",
138 }
139 ]
140 }
141 )
144def test_strict_login_incorrect_grant_type(): 1abcde
145 response = client.post( 1abcde
146 "/login",
147 data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
148 )
149 assert response.status_code == 422 1abcde
150 assert response.json() == IsDict( 1abcde
151 {
152 "detail": [
153 {
154 "type": "string_pattern_mismatch",
155 "loc": ["body", "grant_type"],
156 "msg": "String should match pattern 'password'",
157 "input": "incorrect",
158 "ctx": {"pattern": "password"},
159 }
160 ]
161 }
162 ) | IsDict(
163 # TODO: remove when deprecating Pydantic v1
164 {
165 "detail": [
166 {
167 "loc": ["body", "grant_type"],
168 "msg": 'string does not match regex "password"',
169 "type": "value_error.str.regex",
170 "ctx": {"pattern": "password"},
171 }
172 ]
173 }
174 )
177def test_strict_login_correct_data(): 1abcde
178 response = client.post( 1abcde
179 "/login",
180 data={"username": "johndoe", "password": "secret", "grant_type": "password"},
181 )
182 assert response.status_code == 200 1abcde
183 assert response.json() == { 1abcde
184 "grant_type": "password",
185 "username": "johndoe",
186 "password": "secret",
187 "scopes": [],
188 "client_id": None,
189 "client_secret": None,
190 }
193def test_openapi_schema(): 1abcde
194 response = client.get("/openapi.json") 1abcde
195 assert response.status_code == 200, response.text 1abcde
196 assert response.json() == { 1abcde
197 "openapi": "3.1.0",
198 "info": {"title": "FastAPI", "version": "0.1.0"},
199 "paths": {
200 "/login": {
201 "post": {
202 "responses": {
203 "200": {
204 "description": "Successful Response",
205 "content": {"application/json": {"schema": {}}},
206 },
207 "422": {
208 "description": "Validation Error",
209 "content": {
210 "application/json": {
211 "schema": {
212 "$ref": "#/components/schemas/HTTPValidationError"
213 }
214 }
215 },
216 },
217 },
218 "summary": "Login",
219 "operationId": "login_login_post",
220 "requestBody": {
221 "content": {
222 "application/x-www-form-urlencoded": {
223 "schema": {
224 "$ref": "#/components/schemas/Body_login_login_post"
225 }
226 }
227 },
228 "required": True,
229 },
230 }
231 },
232 "/users/me": {
233 "get": {
234 "responses": {
235 "200": {
236 "description": "Successful Response",
237 "content": {"application/json": {"schema": {}}},
238 }
239 },
240 "summary": "Read Users Me",
241 "operationId": "read_users_me_users_me_get",
242 "security": [{"OAuth2": []}],
243 }
244 },
245 },
246 "components": {
247 "schemas": {
248 "Body_login_login_post": {
249 "title": "Body_login_login_post",
250 "required": ["grant_type", "username", "password"],
251 "type": "object",
252 "properties": {
253 "grant_type": {
254 "title": "Grant Type",
255 "pattern": "password",
256 "type": "string",
257 },
258 "username": {"title": "Username", "type": "string"},
259 "password": {"title": "Password", "type": "string"},
260 "scope": {"title": "Scope", "type": "string", "default": ""},
261 "client_id": IsDict(
262 {
263 "title": "Client Id",
264 "anyOf": [{"type": "string"}, {"type": "null"}],
265 }
266 )
267 | IsDict(
268 # TODO: remove when deprecating Pydantic v1
269 {"title": "Client Id", "type": "string"}
270 ),
271 "client_secret": IsDict(
272 {
273 "title": "Client Secret",
274 "anyOf": [{"type": "string"}, {"type": "null"}],
275 }
276 )
277 | IsDict(
278 # TODO: remove when deprecating Pydantic v1
279 {"title": "Client Secret", "type": "string"}
280 ),
281 },
282 },
283 "ValidationError": {
284 "title": "ValidationError",
285 "required": ["loc", "msg", "type"],
286 "type": "object",
287 "properties": {
288 "loc": {
289 "title": "Location",
290 "type": "array",
291 "items": {
292 "anyOf": [{"type": "string"}, {"type": "integer"}]
293 },
294 },
295 "msg": {"title": "Message", "type": "string"},
296 "type": {"title": "Error Type", "type": "string"},
297 },
298 },
299 "HTTPValidationError": {
300 "title": "HTTPValidationError",
301 "type": "object",
302 "properties": {
303 "detail": {
304 "title": "Detail",
305 "type": "array",
306 "items": {"$ref": "#/components/schemas/ValidationError"},
307 }
308 },
309 },
310 },
311 "securitySchemes": {
312 "OAuth2": {
313 "type": "oauth2",
314 "flows": {
315 "password": {
316 "scopes": {
317 "read:users": "Read the users",
318 "write:users": "Create users",
319 },
320 "tokenUrl": "token",
321 }
322 },
323 }
324 },
325 },
326 }