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