Coverage for tests/test_security_oauth2_optional_description.py: 100%
58 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-05 00:03 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-05 00:03 +0000
1from typing import Optional 1abcdef
3import pytest 1abcdef
4from dirty_equals import IsDict 1abcdef
5from fastapi import Depends, FastAPI, Security 1abcdef
6from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict 1abcdef
7from fastapi.testclient import TestClient 1abcdef
8from pydantic import BaseModel 1abcdef
10app = FastAPI() 1abcdef
12reusable_oauth2 = OAuth2( 1abcdef
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)
24class User(BaseModel): 1abcdef
25 username: str 1abcdef
28def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): 1abcdef
29 if oauth_header is None: 1gshitjkulmvnowpqxr
30 return None 1stuvwx
31 user = User(username=oauth_header) 1ghijklmnopqr
32 return user 1ghijklmnopqr
35@app.post("/login") 1abcdef
36def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1abcdef
37 return form_data 1yzABCD
40@app.get("/users/me") 1abcdef
41def read_users_me(current_user: Optional[User] = Depends(get_current_user)): 1abcdef
42 if current_user is None: 1gshitjkulmvnowpqxr
43 return {"msg": "Create an account first"} 1stuvwx
44 return current_user 1ghijklmnopqr
47client = TestClient(app) 1abcdef
50def test_security_oauth2(): 1abcdef
51 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1gikmoq
52 assert response.status_code == 200, response.text 1gikmoq
53 assert response.json() == {"username": "Bearer footokenbar"} 1gikmoq
56def test_security_oauth2_password_other_header(): 1abcdef
57 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1hjlnpr
58 assert response.status_code == 200, response.text 1hjlnpr
59 assert response.json() == {"username": "Other footokenbar"} 1hjlnpr
62def test_security_oauth2_password_bearer_no_header(): 1abcdef
63 response = client.get("/users/me") 1stuvwx
64 assert response.status_code == 200, response.text 1stuvwx
65 assert response.json() == {"msg": "Create an account first"} 1stuvwx
68def test_strict_login_None(): 1abcdef
69 response = client.post("/login", data=None) 1EFGHIJ
70 assert response.status_code == 422 1EFGHIJ
71 assert response.json() == IsDict( 1EFGHIJ
72 {
73 "detail": [
74 {
75 "type": "missing",
76 "loc": ["body", "grant_type"],
77 "msg": "Field required",
78 "input": None,
79 },
80 {
81 "type": "missing",
82 "loc": ["body", "username"],
83 "msg": "Field required",
84 "input": None,
85 },
86 {
87 "type": "missing",
88 "loc": ["body", "password"],
89 "msg": "Field required",
90 "input": None,
91 },
92 ]
93 }
94 ) | IsDict(
95 # TODO: remove when deprecating Pydantic v1
96 {
97 "detail": [
98 {
99 "loc": ["body", "grant_type"],
100 "msg": "field required",
101 "type": "value_error.missing",
102 },
103 {
104 "loc": ["body", "username"],
105 "msg": "field required",
106 "type": "value_error.missing",
107 },
108 {
109 "loc": ["body", "password"],
110 "msg": "field required",
111 "type": "value_error.missing",
112 },
113 ]
114 }
115 )
118def test_strict_login_no_grant_type(): 1abcdef
119 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1KLMNOP
120 assert response.status_code == 422 1KLMNOP
121 assert response.json() == IsDict( 1KLMNOP
122 {
123 "detail": [
124 {
125 "type": "missing",
126 "loc": ["body", "grant_type"],
127 "msg": "Field required",
128 "input": None,
129 }
130 ]
131 }
132 ) | IsDict(
133 # TODO: remove when deprecating Pydantic v1
134 {
135 "detail": [
136 {
137 "loc": ["body", "grant_type"],
138 "msg": "field required",
139 "type": "value_error.missing",
140 }
141 ]
142 }
143 )
146@pytest.mark.parametrize( 1abcdef
147 argnames=["grant_type"],
148 argvalues=[
149 pytest.param("incorrect", id="incorrect value"),
150 pytest.param("passwordblah", id="password with suffix"),
151 pytest.param("blahpassword", id="password with prefix"),
152 ],
153)
154def test_strict_login_incorrect_grant_type(grant_type: str): 1abcdef
155 response = client.post( 1QRSTUV
156 "/login",
157 data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
158 )
159 assert response.status_code == 422 1QRSTUV
160 assert response.json() == IsDict( 1QRSTUV
161 {
162 "detail": [
163 {
164 "type": "string_pattern_mismatch",
165 "loc": ["body", "grant_type"],
166 "msg": "String should match pattern '^password$'",
167 "input": grant_type,
168 "ctx": {"pattern": "^password$"},
169 }
170 ]
171 }
172 ) | IsDict(
173 # TODO: remove when deprecating Pydantic v1
174 {
175 "detail": [
176 {
177 "loc": ["body", "grant_type"],
178 "msg": 'string does not match regex "^password$"',
179 "type": "value_error.str.regex",
180 "ctx": {"pattern": "^password$"},
181 }
182 ]
183 }
184 )
187def test_strict_login_correct_correct_grant_type(): 1abcdef
188 response = client.post( 1yzABCD
189 "/login",
190 data={"username": "johndoe", "password": "secret", "grant_type": "password"},
191 )
192 assert response.status_code == 200, response.text 1yzABCD
193 assert response.json() == { 1yzABCD
194 "grant_type": "password",
195 "username": "johndoe",
196 "password": "secret",
197 "scopes": [],
198 "client_id": None,
199 "client_secret": None,
200 }
203def test_openapi_schema(): 1abcdef
204 response = client.get("/openapi.json") 1WXYZ01
205 assert response.status_code == 200, response.text 1WXYZ01
206 assert response.json() == { 1WXYZ01
207 "openapi": "3.1.0",
208 "info": {"title": "FastAPI", "version": "0.1.0"},
209 "paths": {
210 "/login": {
211 "post": {
212 "responses": {
213 "200": {
214 "description": "Successful Response",
215 "content": {"application/json": {"schema": {}}},
216 },
217 "422": {
218 "description": "Validation Error",
219 "content": {
220 "application/json": {
221 "schema": {
222 "$ref": "#/components/schemas/HTTPValidationError"
223 }
224 }
225 },
226 },
227 },
228 "summary": "Login",
229 "operationId": "login_login_post",
230 "requestBody": {
231 "content": {
232 "application/x-www-form-urlencoded": {
233 "schema": {
234 "$ref": "#/components/schemas/Body_login_login_post"
235 }
236 }
237 },
238 "required": True,
239 },
240 }
241 },
242 "/users/me": {
243 "get": {
244 "responses": {
245 "200": {
246 "description": "Successful Response",
247 "content": {"application/json": {"schema": {}}},
248 }
249 },
250 "summary": "Read Users Me",
251 "operationId": "read_users_me_users_me_get",
252 "security": [{"OAuth2": []}],
253 }
254 },
255 },
256 "components": {
257 "schemas": {
258 "Body_login_login_post": {
259 "title": "Body_login_login_post",
260 "required": ["grant_type", "username", "password"],
261 "type": "object",
262 "properties": {
263 "grant_type": {
264 "title": "Grant Type",
265 "pattern": "^password$",
266 "type": "string",
267 },
268 "username": {"title": "Username", "type": "string"},
269 "password": {"title": "Password", "type": "string"},
270 "scope": {"title": "Scope", "type": "string", "default": ""},
271 "client_id": IsDict(
272 {
273 "title": "Client Id",
274 "anyOf": [{"type": "string"}, {"type": "null"}],
275 }
276 )
277 | IsDict(
278 # TODO: remove when deprecating Pydantic v1
279 {"title": "Client Id", "type": "string"}
280 ),
281 "client_secret": IsDict(
282 {
283 "title": "Client Secret",
284 "anyOf": [{"type": "string"}, {"type": "null"}],
285 }
286 )
287 | IsDict(
288 # TODO: remove when deprecating Pydantic v1
289 {"title": "Client Secret", "type": "string"}
290 ),
291 },
292 },
293 "ValidationError": {
294 "title": "ValidationError",
295 "required": ["loc", "msg", "type"],
296 "type": "object",
297 "properties": {
298 "loc": {
299 "title": "Location",
300 "type": "array",
301 "items": {
302 "anyOf": [{"type": "string"}, {"type": "integer"}]
303 },
304 },
305 "msg": {"title": "Message", "type": "string"},
306 "type": {"title": "Error Type", "type": "string"},
307 },
308 },
309 "HTTPValidationError": {
310 "title": "HTTPValidationError",
311 "type": "object",
312 "properties": {
313 "detail": {
314 "title": "Detail",
315 "type": "array",
316 "items": {"$ref": "#/components/schemas/ValidationError"},
317 }
318 },
319 },
320 },
321 "securitySchemes": {
322 "OAuth2": {
323 "type": "oauth2",
324 "flows": {
325 "password": {
326 "scopes": {
327 "read:users": "Read the users",
328 "write:users": "Create users",
329 },
330 "tokenUrl": "token",
331 }
332 },
333 "description": "OAuth2 security scheme",
334 }
335 },
336 },
337 }