|
6 | 6 | import logging |
7 | 7 | import random |
8 | 8 | from collections.abc import Iterator |
| 9 | +from pathlib import Path |
9 | 10 | from threading import Thread |
10 | | -from typing import Any |
11 | | -from urllib.parse import quote |
| 11 | +from typing import Annotated |
12 | 12 |
|
13 | 13 | import pytest |
14 | 14 | import uvicorn |
15 | | -from aiohttp.test_utils import TestClient |
16 | 15 | from faker import Faker |
17 | | -from fastapi import APIRouter, FastAPI, Request |
| 16 | +from fastapi import APIRouter, Depends, FastAPI, Request, status |
| 17 | +from fastapi_pagination import add_pagination, create_page |
| 18 | +from fastapi_pagination.cursor import CursorPage, CursorParams |
18 | 19 | from models_library.api_schemas_storage.storage_schemas import ( |
19 | 20 | DatasetMetaDataGet, |
20 | 21 | FileLocation, |
|
24 | 25 | FileUploadCompletionBody, |
25 | 26 | FileUploadSchema, |
26 | 27 | LinkType, |
| 28 | + PathMetaDataGet, |
27 | 29 | ) |
28 | 30 | from models_library.generics import Envelope |
29 | 31 | from models_library.projects import ProjectID |
30 | 32 | from models_library.projects_nodes_io import LocationID, StorageFileID |
31 | 33 | from models_library.users import UserID |
32 | 34 | from pydantic import AnyUrl, TypeAdapter |
33 | | -from pytest_simcore.helpers.assert_checks import assert_status |
34 | 35 | from pytest_simcore.helpers.logging_tools import log_context |
35 | | -from servicelib.aiohttp import status |
| 36 | +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict |
36 | 37 | from servicelib.utils import unused_port |
37 | | -from simcore_postgres_database.models.users import UserRole |
38 | 38 | from yarl import URL |
39 | 39 |
|
40 | | -API_VERSION = "v0" |
41 | | - |
42 | 40 |
|
43 | 41 | @pytest.fixture(scope="session") |
44 | 42 | def storage_vtag() -> str: |
45 | 43 | return "v9" |
46 | 44 |
|
47 | 45 |
|
48 | 46 | @pytest.fixture(scope="module") |
49 | | -def fake_storage_app(storage_vtag: str) -> FastAPI: |
| 47 | +def fake_storage_app(storage_vtag: str) -> FastAPI: # noqa: C901 |
50 | 48 | app = FastAPI(debug=True) |
| 49 | + add_pagination(app) |
| 50 | + |
51 | 51 | router = APIRouter( |
52 | 52 | prefix=f"/{storage_vtag}", |
53 | 53 | ) |
@@ -75,6 +75,37 @@ async def _list_storage_locations(user_id: UserID, request: Request): |
75 | 75 | ] |
76 | 76 | ) |
77 | 77 |
|
| 78 | + @router.get( |
| 79 | + "/locations/{location_id}/paths", |
| 80 | + response_model=CursorPage[PathMetaDataGet], |
| 81 | + ) |
| 82 | + async def _list_paths( |
| 83 | + page_params: Annotated[CursorParams, Depends()], |
| 84 | + # dsm: Annotated[BaseDataManager, Depends(get_data_manager)], |
| 85 | + user_id: UserID, |
| 86 | + file_filter: Path | None = None, |
| 87 | + ): |
| 88 | + assert user_id |
| 89 | + assert "json_schema_extra" in PathMetaDataGet.model_config |
| 90 | + assert isinstance(PathMetaDataGet.model_config["json_schema_extra"], dict) |
| 91 | + assert isinstance( |
| 92 | + PathMetaDataGet.model_config["json_schema_extra"]["examples"], list |
| 93 | + ) |
| 94 | + |
| 95 | + example_index = len(file_filter.parts) if file_filter else 0 |
| 96 | + assert example_index < len( |
| 97 | + PathMetaDataGet.model_config["json_schema_extra"]["examples"] |
| 98 | + ), "fake server unable to server this example" |
| 99 | + chosen_example = PathMetaDataGet.model_config["json_schema_extra"]["examples"][ |
| 100 | + example_index |
| 101 | + ] |
| 102 | + |
| 103 | + return create_page( |
| 104 | + random.randint(3, 15) * [PathMetaDataGet.model_validate(chosen_example)], |
| 105 | + params=page_params, |
| 106 | + next_=None, |
| 107 | + ) |
| 108 | + |
78 | 109 | @router.get( |
79 | 110 | "/locations/{location_id}/files/metadata", |
80 | 111 | response_model=Envelope[list[FileMetaDataGet]], |
@@ -296,238 +327,18 @@ def app_environment( |
296 | 327 | monkeypatch: pytest.MonkeyPatch, |
297 | 328 | ) -> dict[str, str]: |
298 | 329 | # NOTE: overrides app_environment |
299 | | - monkeypatch.setenv("STORAGE_PORT", f"{fake_storage_server.port}") |
300 | | - monkeypatch.setenv("STORAGE_VTAG", storage_vtag) |
301 | | - monkeypatch.setenv("WEBSERVER_GARBAGE_COLLECTOR", "null") |
302 | | - return app_environment | {"WEBSERVER_GARBAGE_COLLECTOR": "null"} |
303 | | - |
304 | | - |
305 | | -# -------------------------------------------------------------------------- |
306 | | -PREFIX = "/" + API_VERSION + "/storage" |
307 | 330 |
|
308 | | - |
309 | | -@pytest.mark.parametrize( |
310 | | - "user_role,expected", |
311 | | - [ |
312 | | - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
313 | | - (UserRole.GUEST, status.HTTP_200_OK), |
314 | | - (UserRole.USER, status.HTTP_200_OK), |
315 | | - (UserRole.TESTER, status.HTTP_200_OK), |
316 | | - ], |
317 | | -) |
318 | | -async def test_list_storage_locations( |
319 | | - client: TestClient, |
320 | | - logged_user: dict[str, Any], |
321 | | - expected: int, |
322 | | -): |
323 | | - url = "/v0/storage/locations" |
324 | | - assert url.startswith(PREFIX) |
325 | | - |
326 | | - resp = await client.get(url, params={"user_id": logged_user["id"]}) |
327 | | - data, error = await assert_status(resp, expected) |
328 | | - |
329 | | - if not error: |
330 | | - assert "json_schema_extra" in FileLocation.model_config |
331 | | - assert isinstance(FileLocation.model_config["json_schema_extra"], dict) |
332 | | - assert isinstance( |
333 | | - FileLocation.model_config["json_schema_extra"]["examples"], list |
334 | | - ) |
335 | | - assert len(data) == len( |
336 | | - FileLocation.model_config["json_schema_extra"]["examples"] |
337 | | - ) |
338 | | - assert data == FileLocation.model_config["json_schema_extra"]["examples"] |
339 | | - |
340 | | - |
341 | | -@pytest.mark.parametrize( |
342 | | - "user_role,expected", |
343 | | - [ |
344 | | - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
345 | | - (UserRole.GUEST, status.HTTP_200_OK), |
346 | | - (UserRole.USER, status.HTTP_200_OK), |
347 | | - (UserRole.TESTER, status.HTTP_200_OK), |
348 | | - ], |
349 | | -) |
350 | | -async def test_list_datasets_metadata( |
351 | | - client: TestClient, |
352 | | - logged_user: dict[str, Any], |
353 | | - expected: int, |
354 | | -): |
355 | | - url = "/v0/storage/locations/0/datasets" |
356 | | - assert url.startswith(PREFIX) |
357 | | - assert client.app |
358 | | - _url = client.app.router["list_datasets_metadata"].url_for(location_id="0") |
359 | | - |
360 | | - assert url == str(_url) |
361 | | - |
362 | | - resp = await client.get(url, params={"user_id": logged_user["id"]}) |
363 | | - data, error = await assert_status(resp, expected) |
364 | | - |
365 | | - if not error: |
366 | | - assert "json_schema_extra" in DatasetMetaDataGet.model_config |
367 | | - assert isinstance(DatasetMetaDataGet.model_config["json_schema_extra"], dict) |
368 | | - assert isinstance( |
369 | | - DatasetMetaDataGet.model_config["json_schema_extra"]["examples"], list |
370 | | - ) |
371 | | - |
372 | | - assert len(data) == len( |
373 | | - DatasetMetaDataGet.model_config["json_schema_extra"]["examples"] |
374 | | - ) |
375 | | - assert data == DatasetMetaDataGet.model_config["json_schema_extra"]["examples"] |
376 | | - |
377 | | - |
378 | | -@pytest.mark.parametrize( |
379 | | - "user_role,expected", |
380 | | - [ |
381 | | - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
382 | | - (UserRole.GUEST, status.HTTP_200_OK), |
383 | | - (UserRole.USER, status.HTTP_200_OK), |
384 | | - (UserRole.TESTER, status.HTTP_200_OK), |
385 | | - ], |
386 | | -) |
387 | | -async def test_list_dataset_files_metadata( |
388 | | - client: TestClient, |
389 | | - logged_user: dict[str, Any], |
390 | | - expected: int, |
391 | | -): |
392 | | - url = "/v0/storage/locations/0/datasets/N:asdfsdf/metadata" |
393 | | - assert url.startswith(PREFIX) |
394 | | - assert client.app |
395 | | - _url = client.app.router["list_dataset_files_metadata"].url_for( |
396 | | - location_id="0", dataset_id="N:asdfsdf" |
| 331 | + return app_environment | setenvs_from_dict( |
| 332 | + monkeypatch, |
| 333 | + { |
| 334 | + "STORAGE_PORT": f"{fake_storage_server.port}", |
| 335 | + "STORAGE_VTAG": storage_vtag, |
| 336 | + "WEBSERVER_DB_LISTENER": "0", |
| 337 | + "WEBSERVER_GARBAGE_COLLECTOR": "null", |
| 338 | + }, |
397 | 339 | ) |
398 | 340 |
|
399 | | - assert url == str(_url) |
400 | | - |
401 | | - resp = await client.get(url, params={"user_id": logged_user["id"]}) |
402 | | - data, error = await assert_status(resp, expected) |
403 | | - |
404 | | - if not error: |
405 | | - assert "json_schema_extra" in FileMetaDataGet.model_config |
406 | | - assert isinstance(FileMetaDataGet.model_config["json_schema_extra"], dict) |
407 | | - assert isinstance( |
408 | | - FileMetaDataGet.model_config["json_schema_extra"]["examples"], list |
409 | | - ) |
410 | | - assert len(data) == len( |
411 | | - FileMetaDataGet.model_config["json_schema_extra"]["examples"] |
412 | | - ) |
413 | | - assert data == [ |
414 | | - FileMetaDataGet.model_validate(e).model_dump(mode="json") |
415 | | - for e in FileMetaDataGet.model_config["json_schema_extra"]["examples"] |
416 | | - ] |
417 | | - |
418 | | - |
419 | | -@pytest.mark.parametrize( |
420 | | - "user_role,expected", |
421 | | - [ |
422 | | - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
423 | | - (UserRole.GUEST, status.HTTP_200_OK), |
424 | | - (UserRole.USER, status.HTTP_200_OK), |
425 | | - (UserRole.TESTER, status.HTTP_200_OK), |
426 | | - ], |
427 | | -) |
428 | | -async def test_storage_file_meta( |
429 | | - client: TestClient, |
430 | | - logged_user: dict[str, Any], |
431 | | - expected: int, |
432 | | - faker: Faker, |
433 | | -): |
434 | | - # tests redirect of path with quotes in path |
435 | | - file_id = f"{faker.uuid4()}/{faker.uuid4()}/a/b/c/d/e/dat" |
436 | | - quoted_file_id = quote(file_id, safe="") |
437 | | - url = f"/v0/storage/locations/0/files/{quoted_file_id}/metadata" |
438 | | - |
439 | | - assert url.startswith(PREFIX) |
440 | | - |
441 | | - resp = await client.get(url, params={"user_id": logged_user["id"]}) |
442 | | - data, error = await assert_status(resp, expected) |
443 | | - |
444 | | - if not error: |
445 | | - assert "json_schema_extra" in FileMetaDataGet.model_config |
446 | | - assert isinstance(FileMetaDataGet.model_config["json_schema_extra"], dict) |
447 | | - assert isinstance( |
448 | | - FileMetaDataGet.model_config["json_schema_extra"]["examples"], list |
449 | | - ) |
450 | | - |
451 | | - assert data |
452 | | - model = FileMetaDataGet.model_validate(data) |
453 | | - assert model |
454 | | - |
455 | | - |
456 | | -@pytest.mark.parametrize( |
457 | | - "user_role,expected", |
458 | | - [ |
459 | | - (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
460 | | - (UserRole.GUEST, status.HTTP_200_OK), |
461 | | - (UserRole.USER, status.HTTP_200_OK), |
462 | | - (UserRole.TESTER, status.HTTP_200_OK), |
463 | | - ], |
464 | | -) |
465 | | -async def test_storage_list_filter( |
466 | | - client: TestClient, |
467 | | - logged_user: dict[str, Any], |
468 | | - expected: int, |
469 | | -): |
470 | | - # tests composition of 2 queries |
471 | | - file_id = "a/b/c/d/e/dat" |
472 | | - url = "/v0/storage/locations/0/files/metadata?uuid_filter={}".format( |
473 | | - quote(file_id, safe="") |
474 | | - ) |
475 | | - |
476 | | - assert url.startswith(PREFIX) |
477 | | - |
478 | | - resp = await client.get(url, params={"user_id": logged_user["id"]}) |
479 | | - data, error = await assert_status(resp, expected) |
480 | | - |
481 | | - if not error: |
482 | | - assert "json_schema_extra" in FileMetaDataGet.model_config |
483 | | - assert isinstance(FileMetaDataGet.model_config["json_schema_extra"], dict) |
484 | | - assert isinstance( |
485 | | - FileMetaDataGet.model_config["json_schema_extra"]["examples"], list |
486 | | - ) |
487 | | - |
488 | | - assert len(data) == 2 |
489 | | - for item in data: |
490 | | - model = FileMetaDataGet.model_validate(item) |
491 | | - assert model |
492 | | - |
493 | 341 |
|
494 | 342 | @pytest.fixture |
495 | | -def file_id(faker: Faker) -> StorageFileID: |
496 | | - return TypeAdapter(StorageFileID).validate_python( |
497 | | - f"{faker.uuid4()}/{faker.uuid4()}/{faker.file_name()} with spaces.dat" |
498 | | - ) |
499 | | - |
500 | | - |
501 | | -@pytest.mark.parametrize( |
502 | | - "user_role,expected", |
503 | | - [ |
504 | | - # (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
505 | | - # (UserRole.GUEST, status.HTTP_200_OK), |
506 | | - (UserRole.USER, status.HTTP_200_OK), |
507 | | - # (UserRole.TESTER, status.HTTP_200_OK), |
508 | | - ], |
509 | | -) |
510 | | -async def test_upload_file( |
511 | | - client: TestClient, |
512 | | - logged_user: dict[str, Any], |
513 | | - expected: int, |
514 | | - file_id: StorageFileID, |
515 | | -): |
516 | | - url = f"/v0/storage/locations/0/files/{quote(file_id, safe='')}" |
517 | | - |
518 | | - assert url.startswith(PREFIX) |
519 | | - |
520 | | - resp = await client.put(url, params={"user_id": logged_user["id"]}) |
521 | | - data, error = await assert_status(resp, expected) |
522 | | - assert not error |
523 | | - assert data |
524 | | - file_upload_schema = FileUploadSchema.model_validate(data) |
525 | | - |
526 | | - # let's abort |
527 | | - resp = await client.post( |
528 | | - f"{file_upload_schema.links.abort_upload.path}", |
529 | | - params={"user_id": logged_user["id"]}, |
530 | | - ) |
531 | | - data, error = await assert_status(resp, status.HTTP_204_NO_CONTENT) |
532 | | - assert not error |
533 | | - assert not data |
| 343 | +def location_id(faker: Faker) -> LocationID: |
| 344 | + return TypeAdapter(LocationID).validate_python(faker.pyint(min_value=0)) |
0 commit comments