Coverage for tests / test_security_oauth2.py: 100%
54 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
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)
20class User(BaseModel): 1abcd
21 username: str 1abcd
24# Here we use string annotations to test them
25def get_current_user(oauth_header: "str" = Security(reusable_oauth2)): 1abcd
26 user = User(username=oauth_header) 1efghij
27 return user 1efghij
30@app.post("/login") 1abcd
31# Here we use string annotations to test them
32def login(form_data: "OAuth2PasswordRequestFormStrict" = Depends()): 1abcd
33 return form_data 1klm
36@app.get("/users/me") 1abcd
37# Here we use string annotations to test them
38def read_current_user(current_user: "User" = Depends(get_current_user)): 1abcd
39 return current_user 1efghij
42client = TestClient(app) 1abcd
45def test_security_oauth2(): 1abcd
46 response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) 1egi
47 assert response.status_code == 200, response.text 1egi
48 assert response.json() == {"username": "Bearer footokenbar"} 1egi
51def test_security_oauth2_password_other_header(): 1abcd
52 response = client.get("/users/me", headers={"Authorization": "Other footokenbar"}) 1fhj
53 assert response.status_code == 200, response.text 1fhj
54 assert response.json() == {"username": "Other footokenbar"} 1fhj
57def test_security_oauth2_password_bearer_no_header(): 1abcd
58 response = client.get("/users/me") 1nop
59 assert response.status_code == 401, response.text 1nop
60 assert response.json() == {"detail": "Not authenticated"} 1nop
61 assert response.headers["WWW-Authenticate"] == "Bearer" 1nop
64def test_strict_login_no_data(): 1abcd
65 response = client.post("/login") 1qrs
66 assert response.status_code == 422 1qrs
67 assert response.json() == { 1qrs
68 "detail": [
69 {
70 "type": "missing",
71 "loc": ["body", "grant_type"],
72 "msg": "Field required",
73 "input": None,
74 },
75 {
76 "type": "missing",
77 "loc": ["body", "username"],
78 "msg": "Field required",
79 "input": None,
80 },
81 {
82 "type": "missing",
83 "loc": ["body", "password"],
84 "msg": "Field required",
85 "input": None,
86 },
87 ]
88 }
91def test_strict_login_no_grant_type(): 1abcd
92 response = client.post("/login", data={"username": "johndoe", "password": "secret"}) 1tuv
93 assert response.status_code == 422 1tuv
94 assert response.json() == { 1tuv
95 "detail": [
96 {
97 "type": "missing",
98 "loc": ["body", "grant_type"],
99 "msg": "Field required",
100 "input": None,
101 }
102 ]
103 }
106@pytest.mark.parametrize( 1abcd
107 argnames=["grant_type"],
108 argvalues=[
109 pytest.param("incorrect", id="incorrect value"),
110 pytest.param("passwordblah", id="password with suffix"),
111 pytest.param("blahpassword", id="password with prefix"),
112 ],
113)
114def test_strict_login_incorrect_grant_type(grant_type: str): 1abcd
115 response = client.post( 1wxy
116 "/login",
117 data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
118 )
119 assert response.status_code == 422 1wxy
120 assert response.json() == { 1wxy
121 "detail": [
122 {
123 "type": "string_pattern_mismatch",
124 "loc": ["body", "grant_type"],
125 "msg": "String should match pattern '^password$'",
126 "input": grant_type,
127 "ctx": {"pattern": "^password$"},
128 }
129 ]
130 }
133def test_strict_login_correct_grant_type(): 1abcd
134 response = client.post( 1klm
135 "/login",
136 data={"username": "johndoe", "password": "secret", "grant_type": "password"},
137 )
138 assert response.status_code == 200 1klm
139 assert response.json() == { 1klm
140 "grant_type": "password",
141 "username": "johndoe",
142 "password": "secret",
143 "scopes": [],
144 "client_id": None,
145 "client_secret": None,
146 }
149def test_openapi_schema(): 1abcd
150 response = client.get("/openapi.json") 1zAB
151 assert response.status_code == 200, response.text 1zAB
152 assert response.json() == snapshot( 1zAB
153 {
154 "openapi": "3.1.0",
155 "info": {"title": "FastAPI", "version": "0.1.0"},
156 "paths": {
157 "/login": {
158 "post": {
159 "responses": {
160 "200": {
161 "description": "Successful Response",
162 "content": {"application/json": {"schema": {}}},
163 },
164 "422": {
165 "description": "Validation Error",
166 "content": {
167 "application/json": {
168 "schema": {
169 "$ref": "#/components/schemas/HTTPValidationError"
170 }
171 }
172 },
173 },
174 },
175 "summary": "Login",
176 "operationId": "login_login_post",
177 "requestBody": {
178 "content": {
179 "application/x-www-form-urlencoded": {
180 "schema": {
181 "$ref": "#/components/schemas/Body_login_login_post"
182 }
183 }
184 },
185 "required": True,
186 },
187 }
188 },
189 "/users/me": {
190 "get": {
191 "responses": {
192 "200": {
193 "description": "Successful Response",
194 "content": {"application/json": {"schema": {}}},
195 }
196 },
197 "summary": "Read Current User",
198 "operationId": "read_current_user_users_me_get",
199 "security": [{"OAuth2": []}],
200 }
201 },
202 },
203 "components": {
204 "schemas": {
205 "Body_login_login_post": {
206 "title": "Body_login_login_post",
207 "required": ["grant_type", "username", "password"],
208 "type": "object",
209 "properties": {
210 "grant_type": {
211 "title": "Grant Type",
212 "pattern": "^password$",
213 "type": "string",
214 },
215 "username": {"title": "Username", "type": "string"},
216 "password": {"title": "Password", "type": "string"},
217 "scope": {
218 "title": "Scope",
219 "type": "string",
220 "default": "",
221 },
222 "client_id": {
223 "title": "Client Id",
224 "anyOf": [{"type": "string"}, {"type": "null"}],
225 },
226 "client_secret": {
227 "title": "Client Secret",
228 "anyOf": [{"type": "string"}, {"type": "null"}],
229 },
230 },
231 },
232 "ValidationError": {
233 "title": "ValidationError",
234 "required": ["loc", "msg", "type"],
235 "type": "object",
236 "properties": {
237 "loc": {
238 "title": "Location",
239 "type": "array",
240 "items": {
241 "anyOf": [{"type": "string"}, {"type": "integer"}]
242 },
243 },
244 "msg": {"title": "Message", "type": "string"},
245 "type": {"title": "Error Type", "type": "string"},
246 "input": {"title": "Input"},
247 "ctx": {"title": "Context", "type": "object"},
248 },
249 },
250 "HTTPValidationError": {
251 "title": "HTTPValidationError",
252 "type": "object",
253 "properties": {
254 "detail": {
255 "title": "Detail",
256 "type": "array",
257 "items": {
258 "$ref": "#/components/schemas/ValidationError"
259 },
260 }
261 },
262 },
263 },
264 "securitySchemes": {
265 "OAuth2": {
266 "type": "oauth2",
267 "flows": {
268 "password": {
269 "scopes": {
270 "read:users": "Read the users",
271 "write:users": "Create users",
272 },
273 "tokenUrl": "token",
274 }
275 },
276 }
277 },
278 },
279 }
280 )