Coverage for tests/test_tutorial/test_security/test_tutorial005.py: 100%
120 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
1import importlib 1abcdef
2from types import ModuleType 1abcdef
4import pytest 1abcdef
5from dirty_equals import IsDict, IsOneOf 1abcdef
6from fastapi.testclient import TestClient 1abcdef
8from ...utils import needs_py39, needs_py310 1abcdef
11@pytest.fixture( 1abcdef
12 name="mod",
13 params=[
14 "tutorial005",
15 pytest.param("tutorial005_py310", marks=needs_py310),
16 "tutorial005_an",
17 pytest.param("tutorial005_py39", marks=needs_py39),
18 pytest.param("tutorial005_an_py39", marks=needs_py39),
19 pytest.param("tutorial005_an_py310", marks=needs_py310),
20 ],
21)
22def get_mod(request: pytest.FixtureRequest): 1abcdef
23 mod = importlib.import_module(f"docs_src.security.{request.param}") 1abcdef
25 return mod 1abcdef
28def get_access_token( 1abcdef
29 *, username="johndoe", password="secret", scope=None, client: TestClient
30):
31 data = {"username": username, "password": password} 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD
32 if scope: 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD
33 data["scope"] = scope 1ghiklmopqstuwxyABC
34 response = client.post("/token", data=data) 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD
35 content = response.json() 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD
36 access_token = content.get("access_token") 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD
37 return access_token 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD
40def test_login(mod: ModuleType): 1abcdef
41 client = TestClient(mod.app) 1KLMNOP
42 response = client.post("/token", data={"username": "johndoe", "password": "secret"}) 1KLMNOP
43 assert response.status_code == 200, response.text 1KLMNOP
44 content = response.json() 1KLMNOP
45 assert "access_token" in content 1KLMNOP
46 assert content["token_type"] == "bearer" 1KLMNOP
49def test_login_incorrect_password(mod: ModuleType): 1abcdef
50 client = TestClient(mod.app) 2` { | } ~ ab
51 response = client.post( 2` { | } ~ ab
52 "/token", data={"username": "johndoe", "password": "incorrect"}
53 )
54 assert response.status_code == 400, response.text 2` { | } ~ ab
55 assert response.json() == {"detail": "Incorrect username or password"} 2` { | } ~ ab
58def test_login_incorrect_username(mod: ModuleType): 1abcdef
59 client = TestClient(mod.app) 2bbcbdbebfbgb
60 response = client.post("/token", data={"username": "foo", "password": "secret"}) 2bbcbdbebfbgb
61 assert response.status_code == 400, response.text 2bbcbdbebfbgb
62 assert response.json() == {"detail": "Incorrect username or password"} 2bbcbdbebfbgb
65def test_no_token(mod: ModuleType): 1abcdef
66 client = TestClient(mod.app) 1QRSTUV
67 response = client.get("/users/me") 1QRSTUV
68 assert response.status_code == 401, response.text 1QRSTUV
69 assert response.json() == {"detail": "Not authenticated"} 1QRSTUV
70 assert response.headers["WWW-Authenticate"] == "Bearer" 1QRSTUV
73def test_token(mod: ModuleType): 1abcdef
74 client = TestClient(mod.app) 1hlptxB
75 access_token = get_access_token(scope="me", client=client) 1hlptxB
76 response = client.get( 1hlptxB
77 "/users/me", headers={"Authorization": f"Bearer {access_token}"}
78 )
79 assert response.status_code == 200, response.text 1hlptxB
80 assert response.json() == { 1hlptxB
81 "username": "johndoe",
82 "full_name": "John Doe",
83 "email": "johndoe@example.com",
84 "disabled": False,
85 }
88def test_incorrect_token(mod: ModuleType): 1abcdef
89 client = TestClient(mod.app) 1WXYZ01
90 response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) 1WXYZ01
91 assert response.status_code == 401, response.text 1WXYZ01
92 assert response.json() == {"detail": "Could not validate credentials"} 1WXYZ01
93 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1WXYZ01
96def test_incorrect_token_type(mod: ModuleType): 1abcdef
97 client = TestClient(mod.app) 1234567
98 response = client.get( 1234567
99 "/users/me", headers={"Authorization": "Notexistent testtoken"}
100 )
101 assert response.status_code == 401, response.text 1234567
102 assert response.json() == {"detail": "Not authenticated"} 1234567
103 assert response.headers["WWW-Authenticate"] == "Bearer" 1234567
106def test_verify_password(mod: ModuleType): 1abcdef
107 assert mod.verify_password( 2tbubvbwbxbyb
108 "secret", mod.fake_users_db["johndoe"]["hashed_password"]
109 )
112def test_get_password_hash(mod: ModuleType): 1abcdef
113 assert mod.get_password_hash("secretalice") 2zbAbBbCbDbEb
116def test_create_access_token(mod: ModuleType): 1abcdef
117 access_token = mod.create_access_token(data={"data": "foo"}) 2nbobpbqbrbsb
118 assert access_token 2nbobpbqbrbsb
121def test_token_no_sub(mod: ModuleType): 1abcdef
122 client = TestClient(mod.app) 189!#$%
124 response = client.get( 189!#$%
125 "/users/me",
126 headers={
127 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
128 },
129 )
130 assert response.status_code == 401, response.text 189!#$%
131 assert response.json() == {"detail": "Could not validate credentials"} 189!#$%
132 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 189!#$%
135def test_token_no_username(mod: ModuleType): 1abcdef
136 client = TestClient(mod.app) 1'()*+,
138 response = client.get( 1'()*+,
139 "/users/me",
140 headers={
141 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
142 },
143 )
144 assert response.status_code == 401, response.text 1'()*+,
145 assert response.json() == {"detail": "Could not validate credentials"} 1'()*+,
146 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1'()*+,
149def test_token_no_scope(mod: ModuleType): 1abcdef
150 client = TestClient(mod.app) 1jnrvzD
152 access_token = get_access_token(client=client) 1jnrvzD
153 response = client.get( 1jnrvzD
154 "/users/me", headers={"Authorization": f"Bearer {access_token}"}
155 )
156 assert response.status_code == 401, response.text 1jnrvzD
157 assert response.json() == {"detail": "Not enough permissions"} 1jnrvzD
158 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1jnrvzD
161def test_token_nonexistent_user(mod: ModuleType): 1abcdef
162 client = TestClient(mod.app) 1-./:;=
164 response = client.get( 1-./:;=
165 "/users/me",
166 headers={
167 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
168 },
169 )
170 assert response.status_code == 401, response.text 1-./:;=
171 assert response.json() == {"detail": "Could not validate credentials"} 1-./:;=
172 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1-./:;=
175def test_token_inactive_user(mod: ModuleType): 1abcdef
176 client = TestClient(mod.app) 1imquyC
178 access_token = get_access_token( 1imquyC
179 username="alice", password="secretalice", scope="me", client=client
180 )
181 response = client.get( 1imquyC
182 "/users/me", headers={"Authorization": f"Bearer {access_token}"}
183 )
184 assert response.status_code == 400, response.text 1imquyC
185 assert response.json() == {"detail": "Inactive user"} 1imquyC
188def test_read_items(mod: ModuleType): 1abcdef
189 client = TestClient(mod.app) 1gkoswA
190 access_token = get_access_token(scope="me items", client=client) 1gkoswA
191 response = client.get( 1gkoswA
192 "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
193 )
194 assert response.status_code == 200, response.text 1gkoswA
195 assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] 1gkoswA
198def test_read_system_status(mod: ModuleType): 1abcdef
199 client = TestClient(mod.app) 1EFGHIJ
200 access_token = get_access_token(client=client) 1EFGHIJ
201 response = client.get( 1EFGHIJ
202 "/status/", headers={"Authorization": f"Bearer {access_token}"}
203 )
204 assert response.status_code == 200, response.text 1EFGHIJ
205 assert response.json() == {"status": "ok"} 1EFGHIJ
208def test_read_system_status_no_token(mod: ModuleType): 1abcdef
209 client = TestClient(mod.app) 1?@[]^_
210 response = client.get("/status/") 1?@[]^_
211 assert response.status_code == 401, response.text 1?@[]^_
212 assert response.json() == {"detail": "Not authenticated"} 1?@[]^_
213 assert response.headers["WWW-Authenticate"] == "Bearer" 1?@[]^_
216def test_openapi_schema(mod: ModuleType): 1abcdef
217 client = TestClient(mod.app) 2hbibjbkblbmb
218 response = client.get("/openapi.json") 2hbibjbkblbmb
219 assert response.status_code == 200, response.text 2hbibjbkblbmb
220 assert response.json() == { 2hbibjbkblbmb
221 "openapi": "3.1.0",
222 "info": {"title": "FastAPI", "version": "0.1.0"},
223 "paths": {
224 "/token": {
225 "post": {
226 "responses": {
227 "200": {
228 "description": "Successful Response",
229 "content": {
230 "application/json": {
231 "schema": {"$ref": "#/components/schemas/Token"}
232 }
233 },
234 },
235 "422": {
236 "description": "Validation Error",
237 "content": {
238 "application/json": {
239 "schema": {
240 "$ref": "#/components/schemas/HTTPValidationError"
241 }
242 }
243 },
244 },
245 },
246 "summary": "Login For Access Token",
247 "operationId": "login_for_access_token_token_post",
248 "requestBody": {
249 "content": {
250 "application/x-www-form-urlencoded": {
251 "schema": {
252 "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
253 }
254 }
255 },
256 "required": True,
257 },
258 }
259 },
260 "/users/me/": {
261 "get": {
262 "responses": {
263 "200": {
264 "description": "Successful Response",
265 "content": {
266 "application/json": {
267 "schema": {"$ref": "#/components/schemas/User"}
268 }
269 },
270 }
271 },
272 "summary": "Read Users Me",
273 "operationId": "read_users_me_users_me__get",
274 "security": [{"OAuth2PasswordBearer": ["me"]}],
275 }
276 },
277 "/users/me/items/": {
278 "get": {
279 "responses": {
280 "200": {
281 "description": "Successful Response",
282 "content": {"application/json": {"schema": {}}},
283 }
284 },
285 "summary": "Read Own Items",
286 "operationId": "read_own_items_users_me_items__get",
287 "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
288 }
289 },
290 "/status/": {
291 "get": {
292 "responses": {
293 "200": {
294 "description": "Successful Response",
295 "content": {"application/json": {"schema": {}}},
296 }
297 },
298 "summary": "Read System Status",
299 "operationId": "read_system_status_status__get",
300 "security": [{"OAuth2PasswordBearer": []}],
301 }
302 },
303 },
304 "components": {
305 "schemas": {
306 "User": {
307 "title": "User",
308 "required": IsOneOf(
309 ["username", "email", "full_name", "disabled"],
310 # TODO: remove when deprecating Pydantic v1
311 ["username"],
312 ),
313 "type": "object",
314 "properties": {
315 "username": {"title": "Username", "type": "string"},
316 "email": IsDict(
317 {
318 "title": "Email",
319 "anyOf": [{"type": "string"}, {"type": "null"}],
320 }
321 )
322 | IsDict(
323 # TODO: remove when deprecating Pydantic v1
324 {"title": "Email", "type": "string"}
325 ),
326 "full_name": IsDict(
327 {
328 "title": "Full Name",
329 "anyOf": [{"type": "string"}, {"type": "null"}],
330 }
331 )
332 | IsDict(
333 # TODO: remove when deprecating Pydantic v1
334 {"title": "Full Name", "type": "string"}
335 ),
336 "disabled": IsDict(
337 {
338 "title": "Disabled",
339 "anyOf": [{"type": "boolean"}, {"type": "null"}],
340 }
341 )
342 | IsDict(
343 # TODO: remove when deprecating Pydantic v1
344 {"title": "Disabled", "type": "boolean"}
345 ),
346 },
347 },
348 "Token": {
349 "title": "Token",
350 "required": ["access_token", "token_type"],
351 "type": "object",
352 "properties": {
353 "access_token": {"title": "Access Token", "type": "string"},
354 "token_type": {"title": "Token Type", "type": "string"},
355 },
356 },
357 "Body_login_for_access_token_token_post": {
358 "title": "Body_login_for_access_token_token_post",
359 "required": ["username", "password"],
360 "type": "object",
361 "properties": {
362 "grant_type": IsDict(
363 {
364 "title": "Grant Type",
365 "anyOf": [
366 {"pattern": "^password$", "type": "string"},
367 {"type": "null"},
368 ],
369 }
370 )
371 | IsDict(
372 # TODO: remove when deprecating Pydantic v1
373 {
374 "title": "Grant Type",
375 "pattern": "^password$",
376 "type": "string",
377 }
378 ),
379 "username": {"title": "Username", "type": "string"},
380 "password": {"title": "Password", "type": "string"},
381 "scope": {"title": "Scope", "type": "string", "default": ""},
382 "client_id": IsDict(
383 {
384 "title": "Client Id",
385 "anyOf": [{"type": "string"}, {"type": "null"}],
386 }
387 )
388 | IsDict(
389 # TODO: remove when deprecating Pydantic v1
390 {"title": "Client Id", "type": "string"}
391 ),
392 "client_secret": IsDict(
393 {
394 "title": "Client Secret",
395 "anyOf": [{"type": "string"}, {"type": "null"}],
396 }
397 )
398 | IsDict(
399 # TODO: remove when deprecating Pydantic v1
400 {"title": "Client Secret", "type": "string"}
401 ),
402 },
403 },
404 "ValidationError": {
405 "title": "ValidationError",
406 "required": ["loc", "msg", "type"],
407 "type": "object",
408 "properties": {
409 "loc": {
410 "title": "Location",
411 "type": "array",
412 "items": {
413 "anyOf": [{"type": "string"}, {"type": "integer"}]
414 },
415 },
416 "msg": {"title": "Message", "type": "string"},
417 "type": {"title": "Error Type", "type": "string"},
418 },
419 },
420 "HTTPValidationError": {
421 "title": "HTTPValidationError",
422 "type": "object",
423 "properties": {
424 "detail": {
425 "title": "Detail",
426 "type": "array",
427 "items": {"$ref": "#/components/schemas/ValidationError"},
428 }
429 },
430 },
431 },
432 "securitySchemes": {
433 "OAuth2PasswordBearer": {
434 "type": "oauth2",
435 "flows": {
436 "password": {
437 "scopes": {
438 "me": "Read information about the current user.",
439 "items": "Read items.",
440 },
441 "tokenUrl": "token",
442 }
443 },
444 }
445 },
446 },
447 }