Skip to content

Commit f581b63

Browse files
committed
consolidate test files @sanderegg
1 parent 97f641e commit f581b63

File tree

2 files changed

+326
-343
lines changed

2 files changed

+326
-343
lines changed

services/web/server/tests/unit/with_dbs/01/storage/test_storage.py

Lines changed: 326 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,78 @@
44
# pylint: disable=too-many-arguments
55

66
from collections.abc import Callable
7-
from typing import Any
7+
from pathlib import Path
8+
from typing import Any, Final
89
from urllib.parse import quote
910

1011
import pytest
1112
from aiohttp.test_utils import TestClient
1213
from faker import Faker
1314
from fastapi_pagination.cursor import CursorPage
14-
from models_library.api_schemas_long_running_tasks.tasks import TaskGet
15-
from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet, AsyncJobId
15+
from models_library.api_schemas_long_running_tasks.tasks import (
16+
TaskGet,
17+
TaskResult,
18+
TaskStatus,
19+
)
20+
from models_library.api_schemas_rpc_async_jobs.async_jobs import (
21+
AsyncJobAbort,
22+
AsyncJobGet,
23+
AsyncJobId,
24+
AsyncJobResult,
25+
AsyncJobStatus,
26+
)
27+
from models_library.api_schemas_rpc_async_jobs.exceptions import (
28+
JobAbortedError,
29+
JobError,
30+
JobMissingError,
31+
JobNotDoneError,
32+
JobSchedulerError,
33+
)
34+
from models_library.api_schemas_storage.data_export_async_jobs import (
35+
AccessRightError,
36+
InvalidFileIdentifierError,
37+
)
1638
from models_library.api_schemas_storage.storage_schemas import (
1739
DatasetMetaDataGet,
1840
FileLocation,
1941
FileMetaDataGet,
2042
FileUploadSchema,
2143
PathMetaDataGet,
2244
)
45+
from models_library.api_schemas_webserver._base import OutputSchema
46+
from models_library.api_schemas_webserver.storage import (
47+
DataExportPost,
48+
)
49+
from models_library.generics import Envelope
50+
from models_library.progress_bar import ProgressReport
2351
from models_library.projects_nodes_io import LocationID, StorageFileID
2452
from pydantic import TypeAdapter
2553
from pytest_mock import MockerFixture
2654
from pytest_simcore.helpers.assert_checks import assert_status
55+
from pytest_simcore.helpers.webserver_login import UserInfoDict
2756
from servicelib.aiohttp import status
57+
from servicelib.rabbitmq.rpc_interfaces.async_jobs import async_jobs
2858
from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import (
2959
submit,
3060
)
61+
from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export
3162
from simcore_postgres_database.models.users import UserRole
63+
from yarl import URL
3264

3365
API_VERSION = "v0"
3466

3567

3668
PREFIX = "/" + API_VERSION + "/storage"
3769

70+
_faker = Faker()
71+
_user_roles: Final[list[UserRole]] = [
72+
UserRole.GUEST,
73+
UserRole.USER,
74+
UserRole.TESTER,
75+
UserRole.PRODUCT_OWNER,
76+
UserRole.ADMIN,
77+
]
78+
3879

3980
@pytest.mark.parametrize(
4081
"user_role,expected",
@@ -317,3 +358,285 @@ async def test_upload_file(
317358
data, error = await assert_status(resp, status.HTTP_204_NO_CONTENT)
318359
assert not error
319360
assert not data
361+
362+
363+
@pytest.fixture
364+
def create_storage_rpc_client_mock(
365+
mocker: MockerFixture,
366+
) -> Callable[[str, str, Any], None]:
367+
def _(module: str, method: str, result_or_exception: Any):
368+
def side_effect(*args, **kwargs):
369+
if isinstance(result_or_exception, Exception):
370+
raise result_or_exception
371+
372+
return result_or_exception
373+
374+
for fct in (f"{module}.{method}",):
375+
mocker.patch(fct, side_effect=side_effect)
376+
377+
return _
378+
379+
380+
@pytest.mark.parametrize("user_role", _user_roles)
381+
@pytest.mark.parametrize(
382+
"backend_result_or_exception, expected_status",
383+
[
384+
(AsyncJobGet(job_id=AsyncJobId(f"{_faker.uuid4()}")), status.HTTP_202_ACCEPTED),
385+
(
386+
InvalidFileIdentifierError(file_id=Path("/my/file")),
387+
status.HTTP_404_NOT_FOUND,
388+
),
389+
(
390+
AccessRightError(
391+
user_id=_faker.pyint(min_value=0), file_id=Path("/my/file")
392+
),
393+
status.HTTP_403_FORBIDDEN,
394+
),
395+
(JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR),
396+
],
397+
ids=lambda x: type(x).__name__,
398+
)
399+
async def test_data_export(
400+
user_role: UserRole,
401+
logged_user: UserInfoDict,
402+
client: TestClient,
403+
create_storage_rpc_client_mock: Callable[[str, str, Any], None],
404+
faker: Faker,
405+
backend_result_or_exception: Any,
406+
expected_status: int,
407+
):
408+
create_storage_rpc_client_mock(
409+
"simcore_service_webserver.storage._rest",
410+
start_data_export.__name__,
411+
backend_result_or_exception,
412+
)
413+
414+
_body = DataExportPost(
415+
paths=[f"{faker.uuid4()}/{faker.uuid4()}/{faker.file_name()}"]
416+
)
417+
response = await client.post(
418+
f"/{API_VERSION}/storage/locations/0/export-data", data=_body.model_dump_json()
419+
)
420+
assert response.status == expected_status
421+
if response.status == status.HTTP_202_ACCEPTED:
422+
Envelope[TaskGet].model_validate(await response.json())
423+
424+
425+
@pytest.mark.parametrize("user_role", _user_roles)
426+
@pytest.mark.parametrize(
427+
"backend_result_or_exception, expected_status",
428+
[
429+
(
430+
AsyncJobStatus(
431+
job_id=AsyncJobId(f"{_faker.uuid4()}"),
432+
progress=ProgressReport(actual_value=0.5, total=1.0),
433+
done=False,
434+
),
435+
status.HTTP_200_OK,
436+
),
437+
(JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR),
438+
(JobMissingError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND),
439+
],
440+
ids=lambda x: type(x).__name__,
441+
)
442+
async def test_get_async_jobs_status(
443+
user_role: UserRole,
444+
logged_user: UserInfoDict,
445+
client: TestClient,
446+
create_storage_rpc_client_mock: Callable[[str, str, Any], None],
447+
backend_result_or_exception: Any,
448+
expected_status: int,
449+
):
450+
_job_id = AsyncJobId(_faker.uuid4())
451+
create_storage_rpc_client_mock(
452+
"simcore_service_webserver.tasks._rest",
453+
f"async_jobs.{async_jobs.status.__name__}",
454+
backend_result_or_exception,
455+
)
456+
457+
response = await client.get(f"/{API_VERSION}/tasks/{_job_id}")
458+
assert response.status == expected_status
459+
if response.status == status.HTTP_200_OK:
460+
response_body_data = (
461+
Envelope[TaskStatus].model_validate(await response.json()).data
462+
)
463+
assert response_body_data is not None
464+
465+
466+
@pytest.mark.parametrize("user_role", _user_roles)
467+
@pytest.mark.parametrize(
468+
"backend_result_or_exception, expected_status",
469+
[
470+
(
471+
AsyncJobAbort(result=True, job_id=AsyncJobId(_faker.uuid4())),
472+
status.HTTP_204_NO_CONTENT,
473+
),
474+
(JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR),
475+
(JobMissingError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND),
476+
],
477+
ids=lambda x: type(x).__name__,
478+
)
479+
async def test_abort_async_jobs(
480+
user_role: UserRole,
481+
logged_user: UserInfoDict,
482+
client: TestClient,
483+
create_storage_rpc_client_mock: Callable[[str, str, Any], None],
484+
faker: Faker,
485+
backend_result_or_exception: Any,
486+
expected_status: int,
487+
):
488+
_job_id = AsyncJobId(faker.uuid4())
489+
create_storage_rpc_client_mock(
490+
"simcore_service_webserver.tasks._rest",
491+
f"async_jobs.{async_jobs.cancel.__name__}",
492+
backend_result_or_exception,
493+
)
494+
495+
response = await client.delete(f"/{API_VERSION}/tasks/{_job_id}")
496+
assert response.status == expected_status
497+
498+
499+
@pytest.mark.parametrize("user_role", _user_roles)
500+
@pytest.mark.parametrize(
501+
"backend_result_or_exception, expected_status",
502+
[
503+
(JobNotDoneError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND),
504+
(AsyncJobResult(result=None), status.HTTP_200_OK),
505+
(JobError(job_id=_faker.uuid4()), status.HTTP_500_INTERNAL_SERVER_ERROR),
506+
(JobAbortedError(job_id=_faker.uuid4()), status.HTTP_410_GONE),
507+
(JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR),
508+
(JobMissingError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND),
509+
],
510+
ids=lambda x: type(x).__name__,
511+
)
512+
async def test_get_async_job_result(
513+
user_role: UserRole,
514+
logged_user: UserInfoDict,
515+
client: TestClient,
516+
create_storage_rpc_client_mock: Callable[[str, str, Any], None],
517+
faker: Faker,
518+
backend_result_or_exception: Any,
519+
expected_status: int,
520+
):
521+
_job_id = AsyncJobId(faker.uuid4())
522+
create_storage_rpc_client_mock(
523+
"simcore_service_webserver.tasks._rest",
524+
f"async_jobs.{async_jobs.result.__name__}",
525+
backend_result_or_exception,
526+
)
527+
528+
response = await client.get(f"/{API_VERSION}/tasks/{_job_id}/result")
529+
assert response.status == expected_status
530+
531+
532+
@pytest.mark.parametrize("user_role", _user_roles)
533+
@pytest.mark.parametrize(
534+
"backend_result_or_exception, expected_status",
535+
[
536+
(
537+
[
538+
AsyncJobGet(
539+
job_id=AsyncJobId(_faker.uuid4()),
540+
)
541+
],
542+
status.HTTP_200_OK,
543+
),
544+
(JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR),
545+
],
546+
ids=lambda x: type(x).__name__,
547+
)
548+
async def test_get_user_async_jobs(
549+
user_role: UserRole,
550+
logged_user: UserInfoDict,
551+
client: TestClient,
552+
create_storage_rpc_client_mock: Callable[[str, str, Any], None],
553+
backend_result_or_exception: Any,
554+
expected_status: int,
555+
):
556+
create_storage_rpc_client_mock(
557+
"simcore_service_webserver.tasks._rest",
558+
f"async_jobs.{async_jobs.list_jobs.__name__}",
559+
backend_result_or_exception,
560+
)
561+
562+
response = await client.get(f"/{API_VERSION}/tasks")
563+
assert response.status == expected_status
564+
if response.status == status.HTTP_200_OK:
565+
Envelope[list[TaskGet]].model_validate(await response.json())
566+
567+
568+
@pytest.mark.parametrize("user_role", _user_roles)
569+
@pytest.mark.parametrize(
570+
"http_method, href, backend_method, backend_object, return_status, return_schema",
571+
[
572+
(
573+
"GET",
574+
"status_href",
575+
async_jobs.status.__name__,
576+
AsyncJobStatus(
577+
job_id=AsyncJobId(_faker.uuid4()),
578+
progress=ProgressReport(actual_value=0.5, total=1.0),
579+
done=False,
580+
),
581+
status.HTTP_200_OK,
582+
TaskStatus,
583+
),
584+
(
585+
"DELETE",
586+
"abort_href",
587+
async_jobs.cancel.__name__,
588+
AsyncJobAbort(result=True, job_id=AsyncJobId(_faker.uuid4())),
589+
status.HTTP_204_NO_CONTENT,
590+
None,
591+
),
592+
(
593+
"GET",
594+
"result_href",
595+
async_jobs.result.__name__,
596+
AsyncJobResult(result=None),
597+
status.HTTP_200_OK,
598+
TaskResult,
599+
),
600+
],
601+
)
602+
async def test_get_async_job_links(
603+
user_role: UserRole,
604+
logged_user: UserInfoDict,
605+
client: TestClient,
606+
create_storage_rpc_client_mock: Callable[[str, str, Any], None],
607+
faker: Faker,
608+
http_method: str,
609+
href: str,
610+
backend_method: str,
611+
backend_object: Any,
612+
return_status: int,
613+
return_schema: OutputSchema | None,
614+
):
615+
create_storage_rpc_client_mock(
616+
"simcore_service_webserver.storage._rest",
617+
start_data_export.__name__,
618+
AsyncJobGet(job_id=AsyncJobId(f"{_faker.uuid4()}")),
619+
)
620+
621+
_body = DataExportPost(
622+
paths=[f"{faker.uuid4()}/{faker.uuid4()}/{faker.file_name()}"]
623+
)
624+
response = await client.post(
625+
f"/{API_VERSION}/storage/locations/0/export-data", data=_body.model_dump_json()
626+
)
627+
assert response.status == status.HTTP_202_ACCEPTED
628+
response_body_data = Envelope[TaskGet].model_validate(await response.json()).data
629+
assert response_body_data is not None
630+
631+
# Call the different links and check the correct model and return status
632+
create_storage_rpc_client_mock(
633+
"simcore_service_webserver.tasks._rest",
634+
f"async_jobs.{backend_method}",
635+
backend_object,
636+
)
637+
response = await client.request(
638+
http_method, URL(getattr(response_body_data, href)).path
639+
)
640+
assert response.status == return_status
641+
if return_schema:
642+
Envelope[return_schema].model_validate(await response.json())

0 commit comments

Comments
 (0)