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
« 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
8app = FastAPI() 1adbc
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)
21class User(BaseModel): 1adbc
22 username: str 1abc
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
32@app.post("/login") 1adbc
33def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): 1adbc
34 return form_data 1qrst
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
44client = TestClient(app) 1adbc
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
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
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
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 }
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 }
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 }
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 }
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 )