Coverage for tests/test_tutorial/test_security/test_tutorial005_an.py: 100%
98 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-08 03:53 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-08 03:53 +0000
1from dirty_equals import IsDict, IsOneOf 1abcde
2from fastapi.testclient import TestClient 1abcde
4from docs_src.security.tutorial005_an import ( 1abcde
5 app,
6 create_access_token,
7 fake_users_db,
8 get_password_hash,
9 verify_password,
10)
12client = TestClient(app) 1abcde
15def get_access_token(username="johndoe", password="secret", scope=None): 1abcde
16 data = {"username": username, "password": password} 1abcde
17 if scope: 1abcde
18 data["scope"] = scope 1abcde
19 response = client.post("/token", data=data) 1abcde
20 content = response.json() 1abcde
21 access_token = content.get("access_token") 1abcde
22 return access_token 1abcde
25def test_login(): 1abcde
26 response = client.post("/token", data={"username": "johndoe", "password": "secret"}) 1abcde
27 assert response.status_code == 200, response.text 1abcde
28 content = response.json() 1abcde
29 assert "access_token" in content 1abcde
30 assert content["token_type"] == "bearer" 1abcde
33def test_login_incorrect_password(): 1abcde
34 response = client.post( 1abcde
35 "/token", data={"username": "johndoe", "password": "incorrect"}
36 )
37 assert response.status_code == 400, response.text 1abcde
38 assert response.json() == {"detail": "Incorrect username or password"} 1abcde
41def test_login_incorrect_username(): 1abcde
42 response = client.post("/token", data={"username": "foo", "password": "secret"}) 1abcde
43 assert response.status_code == 400, response.text 1abcde
44 assert response.json() == {"detail": "Incorrect username or password"} 1abcde
47def test_no_token(): 1abcde
48 response = client.get("/users/me") 1abcde
49 assert response.status_code == 401, response.text 1abcde
50 assert response.json() == {"detail": "Not authenticated"} 1abcde
51 assert response.headers["WWW-Authenticate"] == "Bearer" 1abcde
54def test_token(): 1abcde
55 access_token = get_access_token(scope="me") 1abcde
56 response = client.get( 1abcde
57 "/users/me", headers={"Authorization": f"Bearer {access_token}"}
58 )
59 assert response.status_code == 200, response.text 1abcde
60 assert response.json() == { 1abcde
61 "username": "johndoe",
62 "full_name": "John Doe",
63 "email": "johndoe@example.com",
64 "disabled": False,
65 }
68def test_incorrect_token(): 1abcde
69 response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) 1abcde
70 assert response.status_code == 401, response.text 1abcde
71 assert response.json() == {"detail": "Could not validate credentials"} 1abcde
72 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1abcde
75def test_incorrect_token_type(): 1abcde
76 response = client.get( 1abcde
77 "/users/me", headers={"Authorization": "Notexistent testtoken"}
78 )
79 assert response.status_code == 401, response.text 1abcde
80 assert response.json() == {"detail": "Not authenticated"} 1abcde
81 assert response.headers["WWW-Authenticate"] == "Bearer" 1abcde
84def test_verify_password(): 1abcde
85 assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"]) 1abcde
88def test_get_password_hash(): 1abcde
89 assert get_password_hash("secretalice") 1abcde
92def test_create_access_token(): 1abcde
93 access_token = create_access_token(data={"data": "foo"}) 1abcde
94 assert access_token 1abcde
97def test_token_no_sub(): 1abcde
98 response = client.get( 1abcde
99 "/users/me",
100 headers={
101 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
102 },
103 )
104 assert response.status_code == 401, response.text 1abcde
105 assert response.json() == {"detail": "Could not validate credentials"} 1abcde
106 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1abcde
109def test_token_no_username(): 1abcde
110 response = client.get( 1abcde
111 "/users/me",
112 headers={
113 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
114 },
115 )
116 assert response.status_code == 401, response.text 1abcde
117 assert response.json() == {"detail": "Could not validate credentials"} 1abcde
118 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1abcde
121def test_token_no_scope(): 1abcde
122 access_token = get_access_token() 1abcde
123 response = client.get( 1abcde
124 "/users/me", headers={"Authorization": f"Bearer {access_token}"}
125 )
126 assert response.status_code == 401, response.text 1abcde
127 assert response.json() == {"detail": "Not enough permissions"} 1abcde
128 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1abcde
131def test_token_nonexistent_user(): 1abcde
132 response = client.get( 1abcde
133 "/users/me",
134 headers={
135 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
136 },
137 )
138 assert response.status_code == 401, response.text 1abcde
139 assert response.json() == {"detail": "Could not validate credentials"} 1abcde
140 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1abcde
143def test_token_inactive_user(): 1abcde
144 access_token = get_access_token( 1abcde
145 username="alice", password="secretalice", scope="me"
146 )
147 response = client.get( 1abcde
148 "/users/me", headers={"Authorization": f"Bearer {access_token}"}
149 )
150 assert response.status_code == 400, response.text 1abcde
151 assert response.json() == {"detail": "Inactive user"} 1abcde
154def test_read_items(): 1abcde
155 access_token = get_access_token(scope="me items") 1abcde
156 response = client.get( 1abcde
157 "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
158 )
159 assert response.status_code == 200, response.text 1abcde
160 assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] 1abcde
163def test_read_system_status(): 1abcde
164 access_token = get_access_token() 1abcde
165 response = client.get( 1abcde
166 "/status/", headers={"Authorization": f"Bearer {access_token}"}
167 )
168 assert response.status_code == 200, response.text 1abcde
169 assert response.json() == {"status": "ok"} 1abcde
172def test_read_system_status_no_token(): 1abcde
173 response = client.get("/status/") 1abcde
174 assert response.status_code == 401, response.text 1abcde
175 assert response.json() == {"detail": "Not authenticated"} 1abcde
176 assert response.headers["WWW-Authenticate"] == "Bearer" 1abcde
179def test_openapi_schema(): 1abcde
180 response = client.get("/openapi.json") 1abcde
181 assert response.status_code == 200, response.text 1abcde
182 assert response.json() == { 1abcde
183 "openapi": "3.1.0",
184 "info": {"title": "FastAPI", "version": "0.1.0"},
185 "paths": {
186 "/token": {
187 "post": {
188 "responses": {
189 "200": {
190 "description": "Successful Response",
191 "content": {
192 "application/json": {
193 "schema": {"$ref": "#/components/schemas/Token"}
194 }
195 },
196 },
197 "422": {
198 "description": "Validation Error",
199 "content": {
200 "application/json": {
201 "schema": {
202 "$ref": "#/components/schemas/HTTPValidationError"
203 }
204 }
205 },
206 },
207 },
208 "summary": "Login For Access Token",
209 "operationId": "login_for_access_token_token_post",
210 "requestBody": {
211 "content": {
212 "application/x-www-form-urlencoded": {
213 "schema": {
214 "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
215 }
216 }
217 },
218 "required": True,
219 },
220 }
221 },
222 "/users/me/": {
223 "get": {
224 "responses": {
225 "200": {
226 "description": "Successful Response",
227 "content": {
228 "application/json": {
229 "schema": {"$ref": "#/components/schemas/User"}
230 }
231 },
232 }
233 },
234 "summary": "Read Users Me",
235 "operationId": "read_users_me_users_me__get",
236 "security": [{"OAuth2PasswordBearer": ["me"]}],
237 }
238 },
239 "/users/me/items/": {
240 "get": {
241 "responses": {
242 "200": {
243 "description": "Successful Response",
244 "content": {"application/json": {"schema": {}}},
245 }
246 },
247 "summary": "Read Own Items",
248 "operationId": "read_own_items_users_me_items__get",
249 "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
250 }
251 },
252 "/status/": {
253 "get": {
254 "responses": {
255 "200": {
256 "description": "Successful Response",
257 "content": {"application/json": {"schema": {}}},
258 }
259 },
260 "summary": "Read System Status",
261 "operationId": "read_system_status_status__get",
262 "security": [{"OAuth2PasswordBearer": []}],
263 }
264 },
265 },
266 "components": {
267 "schemas": {
268 "User": {
269 "title": "User",
270 "required": IsOneOf(
271 ["username", "email", "full_name", "disabled"],
272 # TODO: remove when deprecating Pydantic v1
273 ["username"],
274 ),
275 "type": "object",
276 "properties": {
277 "username": {"title": "Username", "type": "string"},
278 "email": IsDict(
279 {
280 "title": "Email",
281 "anyOf": [{"type": "string"}, {"type": "null"}],
282 }
283 )
284 | IsDict(
285 # TODO: remove when deprecating Pydantic v1
286 {"title": "Email", "type": "string"}
287 ),
288 "full_name": IsDict(
289 {
290 "title": "Full Name",
291 "anyOf": [{"type": "string"}, {"type": "null"}],
292 }
293 )
294 | IsDict(
295 # TODO: remove when deprecating Pydantic v1
296 {"title": "Full Name", "type": "string"}
297 ),
298 "disabled": IsDict(
299 {
300 "title": "Disabled",
301 "anyOf": [{"type": "boolean"}, {"type": "null"}],
302 }
303 )
304 | IsDict(
305 # TODO: remove when deprecating Pydantic v1
306 {"title": "Disabled", "type": "boolean"}
307 ),
308 },
309 },
310 "Token": {
311 "title": "Token",
312 "required": ["access_token", "token_type"],
313 "type": "object",
314 "properties": {
315 "access_token": {"title": "Access Token", "type": "string"},
316 "token_type": {"title": "Token Type", "type": "string"},
317 },
318 },
319 "Body_login_for_access_token_token_post": {
320 "title": "Body_login_for_access_token_token_post",
321 "required": ["username", "password"],
322 "type": "object",
323 "properties": {
324 "grant_type": IsDict(
325 {
326 "title": "Grant Type",
327 "anyOf": [
328 {"pattern": "password", "type": "string"},
329 {"type": "null"},
330 ],
331 }
332 )
333 | IsDict(
334 # TODO: remove when deprecating Pydantic v1
335 {
336 "title": "Grant Type",
337 "pattern": "password",
338 "type": "string",
339 }
340 ),
341 "username": {"title": "Username", "type": "string"},
342 "password": {"title": "Password", "type": "string"},
343 "scope": {"title": "Scope", "type": "string", "default": ""},
344 "client_id": IsDict(
345 {
346 "title": "Client Id",
347 "anyOf": [{"type": "string"}, {"type": "null"}],
348 }
349 )
350 | IsDict(
351 # TODO: remove when deprecating Pydantic v1
352 {"title": "Client Id", "type": "string"}
353 ),
354 "client_secret": IsDict(
355 {
356 "title": "Client Secret",
357 "anyOf": [{"type": "string"}, {"type": "null"}],
358 }
359 )
360 | IsDict(
361 # TODO: remove when deprecating Pydantic v1
362 {"title": "Client Secret", "type": "string"}
363 ),
364 },
365 },
366 "ValidationError": {
367 "title": "ValidationError",
368 "required": ["loc", "msg", "type"],
369 "type": "object",
370 "properties": {
371 "loc": {
372 "title": "Location",
373 "type": "array",
374 "items": {
375 "anyOf": [{"type": "string"}, {"type": "integer"}]
376 },
377 },
378 "msg": {"title": "Message", "type": "string"},
379 "type": {"title": "Error Type", "type": "string"},
380 },
381 },
382 "HTTPValidationError": {
383 "title": "HTTPValidationError",
384 "type": "object",
385 "properties": {
386 "detail": {
387 "title": "Detail",
388 "type": "array",
389 "items": {"$ref": "#/components/schemas/ValidationError"},
390 }
391 },
392 },
393 },
394 "securitySchemes": {
395 "OAuth2PasswordBearer": {
396 "type": "oauth2",
397 "flows": {
398 "password": {
399 "scopes": {
400 "me": "Read information about the current user.",
401 "items": "Read items.",
402 },
403 "tokenUrl": "token",
404 }
405 },
406 }
407 },
408 },
409 }