Coverage for tests / test_security_oauth2_optional.py: 100%
58 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1from typing import Optional 1abcd
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
10app = FastAPI() 1abcd
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 auto_error=False,
20)
23class User(BaseModel): 1abcd
24 username: str 1abcd
27def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): 1abcd
28 if oauth_header is None: 1ekfglhimj
29 return None 1klm
30 user = User(username=oauth_header) 1efghij
31 return user 1efghij
34@app.post("/login") 1abcd
35def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1abcd
36 return form_data 1nop
39@app.get("/users/me") 1abcd
40def read_users_me(current_user: Optional[User] = Depends(get_current_user)): 1abcd
41 if current_user is None: 1ekfglhimj
42 return {"msg": "Create an account first"} 1klm
43 return current_user 1efghij
46client = TestClient(app) 1abcd
49def test_security_oauth2(): 1abcd
50 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1egi
51 assert response.status_code == 200, response.text 1egi
52 assert response.json() == {"username": "Bearer footokenbar"} 1egi
55def test_security_oauth2_password_other_header(): 1abcd
56 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1fhj
57 assert response.status_code == 200, response.text 1fhj
58 assert response.json() == {"username": "Other footokenbar"} 1fhj
61def test_security_oauth2_password_bearer_no_header(): 1abcd
62 response = client.get("/users/me") 1klm
63 assert response.status_code == 200, response.text 1klm
64 assert response.json() == {"msg": "Create an account first"} 1klm
67def test_strict_login_no_data(): 1abcd
68 response = client.post("/login") 1qrs
69 assert response.status_code == 422 1qrs
70 assert response.json() == { 1qrs
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 }
94def test_strict_login_no_grant_type(): 1abcd
95 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1tuv
96 assert response.status_code == 422 1tuv
97 assert response.json() == { 1tuv
98 "detail": [
99 {
100 "type": "missing",
101 "loc": ["body", "grant_type"],
102 "msg": "Field required",
103 "input": None,
104 }
105 ]
106 }
109@pytest.mark.parametrize( 1abcd
110 argnames=["grant_type"],
111 argvalues=[
112 pytest.param("incorrect", id="incorrect value"),
113 pytest.param("passwordblah", id="password with suffix"),
114 pytest.param("blahpassword", id="password with prefix"),
115 ],
116)
117def test_strict_login_incorrect_grant_type(grant_type: str): 1abcd
118 response = client.post( 1wxy
119 "/login",
120 data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
121 )
122 assert response.status_code == 422 1wxy
123 assert response.json() == { 1wxy
124 "detail": [
125 {
126 "type": "string_pattern_mismatch",
127 "loc": ["body", "grant_type"],
128 "msg": "String should match pattern '^password$'",
129 "input": grant_type,
130 "ctx": {"pattern": "^password$"},
131 }
132 ]
133 }
136def test_strict_login_correct_data(): 1abcd
137 response = client.post( 1nop
138 "/login",
139 data={"username": "johndoe", "password": "secret", "grant_type": "password"},
140 )
141 assert response.status_code == 200 1nop
142 assert response.json() == { 1nop
143 "grant_type": "password",
144 "username": "johndoe",
145 "password": "secret",
146 "scopes": [],
147 "client_id": None,
148 "client_secret": None,
149 }
152def test_openapi_schema(): 1abcd
153 response = client.get("/openapi.json") 1zAB
154 assert response.status_code == 200, response.text 1zAB
155 assert response.json() == snapshot( 1zAB
156 {
157 "openapi": "3.1.0",
158 "info": {"title": "FastAPI", "version": "0.1.0"},
159 "paths": {
160 "/login": {
161 "post": {
162 "responses": {
163 "200": {
164 "description": "Successful Response",
165 "content": {"application/json": {"schema": {}}},
166 },
167 "422": {
168 "description": "Validation Error",
169 "content": {
170 "application/json": {
171 "schema": {
172 "$ref": "#/components/schemas/HTTPValidationError"
173 }
174 }
175 },
176 },
177 },
178 "summary": "Login",
179 "operationId": "login_login_post",
180 "requestBody": {
181 "content": {
182 "application/x-www-form-urlencoded": {
183 "schema": {
184 "$ref": "#/components/schemas/Body_login_login_post"
185 }
186 }
187 },
188 "required": True,
189 },
190 }
191 },
192 "/users/me": {
193 "get": {
194 "responses": {
195 "200": {
196 "description": "Successful Response",
197 "content": {"application/json": {"schema": {}}},
198 }
199 },
200 "summary": "Read Users Me",
201 "operationId": "read_users_me_users_me_get",
202 "security": [{"OAuth2": []}],
203 }
204 },
205 },
206 "components": {
207 "schemas": {
208 "Body_login_login_post": {
209 "title": "Body_login_login_post",
210 "required": ["grant_type", "username", "password"],
211 "type": "object",
212 "properties": {
213 "grant_type": {
214 "title": "Grant Type",
215 "pattern": "^password$",
216 "type": "string",
217 },
218 "username": {"title": "Username", "type": "string"},
219 "password": {"title": "Password", "type": "string"},
220 "scope": {
221 "title": "Scope",
222 "type": "string",
223 "default": "",
224 },
225 "client_id": {
226 "title": "Client Id",
227 "anyOf": [{"type": "string"}, {"type": "null"}],
228 },
229 "client_secret": {
230 "title": "Client Secret",
231 "anyOf": [{"type": "string"}, {"type": "null"}],
232 },
233 },
234 },
235 "ValidationError": {
236 "title": "ValidationError",
237 "required": ["loc", "msg", "type"],
238 "type": "object",
239 "properties": {
240 "loc": {
241 "title": "Location",
242 "type": "array",
243 "items": {
244 "anyOf": [{"type": "string"}, {"type": "integer"}]
245 },
246 },
247 "msg": {"title": "Message", "type": "string"},
248 "type": {"title": "Error Type", "type": "string"},
249 "input": {"title": "Input"},
250 "ctx": {"title": "Context", "type": "object"},
251 },
252 },
253 "HTTPValidationError": {
254 "title": "HTTPValidationError",
255 "type": "object",
256 "properties": {
257 "detail": {
258 "title": "Detail",
259 "type": "array",
260 "items": {
261 "$ref": "#/components/schemas/ValidationError"
262 },
263 }
264 },
265 },
266 },
267 "securitySchemes": {
268 "OAuth2": {
269 "type": "oauth2",
270 "flows": {
271 "password": {
272 "scopes": {
273 "read:users": "Read the users",
274 "write:users": "Create users",
275 },
276 "tokenUrl": "token",
277 }
278 },
279 }
280 },
281 },
282 }
283 )