Skip to content

Commit 1400a42

Browse files
committed
add test of task function itself
1 parent e8683a9 commit 1400a42

File tree

4 files changed

+147
-4
lines changed

4 files changed

+147
-4
lines changed

services/api-server/src/simcore_service_api_server/celery/worker_tasks/functions_tasks.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from ...api.dependencies.authentication import Identity
99
from ...api.dependencies.rabbitmq import get_rabbitmq_rpc_client
1010
from ...api.dependencies.services import (
11-
get_api_client,
1211
get_catalog_service,
1312
get_directorv2_service,
1413
get_function_job_service,
@@ -42,9 +41,9 @@ async def _assemble_function_job_service(*, app: FastAPI, user_identity: Identit
4241
app=app, session_cookies=session_cookie, identity=user_identity
4342
)
4443
web_api_rpc_client = await get_wb_api_rpc_client(app=app)
45-
director2_api = get_api_client(DirectorV2Api)
44+
director2_api = DirectorV2Api.get_instance(app=app)
4645
assert isinstance(director2_api, DirectorV2Api) # nosec
47-
storage_api = get_api_client(StorageApi)
46+
storage_api = StorageApi.get_instance(app=app)
4847
assert isinstance(storage_api, StorageApi) # nosec
4948
catalog_service = get_catalog_service(
5049
rpc_client=rpc_client,

services/api-server/src/simcore_service_api_server/models/api_resources.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from uuid import UUID
55

66
import parse # type: ignore[import-untyped]
7-
from pydantic import AfterValidator, BaseModel, Field, HttpUrl, TypeAdapter
7+
from pydantic import AfterValidator, BaseModel, ConfigDict, Field, HttpUrl, TypeAdapter
88
from pydantic.types import StringConstraints
99

1010
# RESOURCE NAMES https://google.aip.dev/122
@@ -103,6 +103,18 @@ def _url_missing_only_job_id(url: str | None) -> str | None:
103103

104104

105105
class JobLinks(BaseModel):
106+
model_config = ConfigDict(
107+
json_schema_extra={
108+
"examples": [
109+
{
110+
"url_template": "https://api.osparc.io/v0/jobs/{job_id}",
111+
"runner_url_template": "https://runner.osparc.io/dashboard",
112+
"outputs_url_template": "https://api.osparc.io/v0/jobs/{job_id}/outputs",
113+
}
114+
]
115+
}
116+
)
117+
106118
url_template: Annotated[str | None, AfterValidator(_url_missing_only_job_id)]
107119
runner_url_template: str | None
108120
outputs_url_template: Annotated[

services/api-server/tests/unit/api_functions/test_api_routers_functions.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
import httpx
1515
import pytest
1616
import respx
17+
from celery import Task
1718
from faker import Faker
19+
from fastapi import FastAPI
1820
from httpx import AsyncClient
1921
from models_library.api_schemas_long_running_tasks.tasks import TaskGet
2022
from models_library.functions import (
@@ -25,21 +27,30 @@
2527
RegisteredFunctionJob,
2628
RegisteredFunctionJobCollection,
2729
RegisteredProjectFunction,
30+
RegisteredProjectFunctionJob,
2831
)
2932
from models_library.functions_errors import (
3033
FunctionIDNotFoundError,
3134
FunctionReadAccessDeniedError,
3235
)
3336
from models_library.rest_pagination import PageMetaInfoLimitOffset
3437
from models_library.users import UserID
38+
from pydantic import EmailStr
3539
from pytest_mock import MockerFixture, MockType
3640
from pytest_simcore.helpers.httpx_calls_capture_models import HttpApiCallCaptureModel
3741
from servicelib.aiohttp import status
42+
from servicelib.celery.app_server import BaseAppServer
43+
from servicelib.celery.models import TaskID
3844
from servicelib.common_headers import (
3945
X_SIMCORE_PARENT_NODE_ID,
4046
X_SIMCORE_PARENT_PROJECT_UUID,
4147
)
48+
from servicelib.rabbitmq._client_rpc import RabbitMQRPCClient
4249
from simcore_service_api_server._meta import API_VTAG
50+
from simcore_service_api_server.api.dependencies.authentication import Identity
51+
from simcore_service_api_server.celery.worker_tasks import functions_tasks
52+
from simcore_service_api_server.models.api_resources import JobLinks
53+
from simcore_service_api_server.services_rpc.wb_api_server import WbApiRpcClient
4354

4455
_faker = Faker()
4556

@@ -379,6 +390,103 @@ async def async_magic():
379390
)
380391

381392

393+
@pytest.mark.parametrize("capture", ["run_study_function_parent_info.json"])
394+
async def test_run_project_function(
395+
mocker: MockerFixture,
396+
mocked_webserver_rpc_api: dict[str, MockType],
397+
app: FastAPI,
398+
client: AsyncClient,
399+
mock_handler_in_functions_rpc_interface: Callable[[str, Any], None],
400+
mock_registered_project_function: RegisteredProjectFunction,
401+
mock_registered_project_function_job: RegisteredFunctionJob,
402+
auth: httpx.BasicAuth,
403+
user_identity: Identity,
404+
user_email: EmailStr,
405+
job_links: JobLinks,
406+
mocked_webserver_rest_api_base: respx.MockRouter,
407+
mocked_directorv2_rest_api_base: respx.MockRouter,
408+
create_respx_mock_from_capture,
409+
project_tests_dir: Path,
410+
capture: str,
411+
) -> None:
412+
413+
def _get_app_server(celery_app: Any) -> FastAPI:
414+
app_server = mocker.Mock(spec=BaseAppServer)
415+
app_server.app = app
416+
return app_server
417+
418+
mocker.patch.object(functions_tasks, "get_app_server", _get_app_server)
419+
420+
def _get_rabbitmq_rpc_client(app: FastAPI) -> RabbitMQRPCClient:
421+
return mocker.MagicMock(spec=RabbitMQRPCClient)
422+
423+
mocker.patch.object(
424+
functions_tasks, "get_rabbitmq_rpc_client", _get_rabbitmq_rpc_client
425+
)
426+
427+
async def _get_wb_api_rpc_client(app: FastAPI) -> WbApiRpcClient:
428+
wb_api_rpc_client = WbApiRpcClient(
429+
_client=mocker.MagicMock(spec=RabbitMQRPCClient)
430+
)
431+
return wb_api_rpc_client
432+
433+
mocker.patch.object(
434+
functions_tasks, "get_wb_api_rpc_client", _get_wb_api_rpc_client
435+
)
436+
437+
def _default_side_effect(
438+
request: httpx.Request,
439+
path_params: dict[str, Any],
440+
capture: HttpApiCallCaptureModel,
441+
) -> Any:
442+
return capture.response_body
443+
444+
create_respx_mock_from_capture(
445+
respx_mocks=[mocked_webserver_rest_api_base, mocked_directorv2_rest_api_base],
446+
capture_path=project_tests_dir / "mocks" / capture,
447+
side_effects_callbacks=[_default_side_effect] * 50,
448+
)
449+
450+
mock_handler_in_functions_rpc_interface(
451+
"get_function_user_permissions",
452+
FunctionUserAccessRights(
453+
user_id=user_identity.user_id,
454+
execute=True,
455+
read=True,
456+
write=True,
457+
),
458+
)
459+
mock_handler_in_functions_rpc_interface(
460+
"get_function", mock_registered_project_function
461+
)
462+
mock_handler_in_functions_rpc_interface("find_cached_function_jobs", [])
463+
mock_handler_in_functions_rpc_interface(
464+
"register_function_job", mock_registered_project_function_job
465+
)
466+
mock_handler_in_functions_rpc_interface(
467+
"get_functions_user_api_access_rights",
468+
FunctionUserApiAccessRights(
469+
user_id=user_identity.user_id,
470+
execute_functions=True,
471+
write_functions=True,
472+
read_functions=True,
473+
),
474+
)
475+
476+
job = await functions_tasks.run_function(
477+
task=MagicMock(spec=Task),
478+
task_id=TaskID(_faker.uuid4()),
479+
user_identity=user_identity,
480+
function=mock_registered_project_function,
481+
function_inputs={},
482+
pricing_spec=None,
483+
job_links=job_links,
484+
x_simcore_parent_project_uuid=None,
485+
x_simcore_parent_node_id=None,
486+
)
487+
assert isinstance(job, RegisteredProjectFunctionJob)
488+
489+
382490
@pytest.mark.parametrize(
383491
"parent_project_uuid, parent_node_uuid, expected_status_code",
384492
[

services/api-server/tests/unit/conftest.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@
5757
from pytest_simcore.simcore_webserver_projects_rest_api import GET_PROJECT
5858
from requests.auth import HTTPBasicAuth
5959
from respx import MockRouter
60+
from simcore_service_api_server.api.dependencies.authentication import Identity
6061
from simcore_service_api_server.core.application import create_app
6162
from simcore_service_api_server.core.settings import ApplicationSettings
63+
from simcore_service_api_server.models.api_resources import JobLinks
6264
from simcore_service_api_server.repository.api_keys import UserAndProductTuple
6365
from simcore_service_api_server.services_http.solver_job_outputs import ResultsTypes
6466
from simcore_service_api_server.services_rpc.wb_api_server import WbApiRpcClient
@@ -69,6 +71,19 @@ def product_name() -> ProductName:
6971
return "osparc"
7072

7173

74+
@pytest.fixture
75+
def user_identity(
76+
user_id: UserID,
77+
user_email: EmailStr,
78+
product_name: ProductName,
79+
) -> Identity:
80+
return Identity(
81+
user_id=user_id,
82+
product_name=product_name,
83+
email=user_email,
84+
)
85+
86+
7287
@pytest.fixture
7388
def app_environment(
7489
monkeypatch: pytest.MonkeyPatch,
@@ -549,6 +564,15 @@ def project_job_rpc_get() -> ProjectJobRpcGet:
549564
return ProjectJobRpcGet.model_validate(example)
550565

551566

567+
@pytest.fixture
568+
def job_links() -> JobLinks:
569+
extra = JobLinks.model_config.get("json_schema_extra")
570+
assert isinstance(extra, dict)
571+
examples = extra.get("examples")
572+
assert isinstance(examples, list) and len(examples) > 0
573+
return JobLinks.model_validate(examples[0])
574+
575+
552576
@pytest.fixture
553577
def mocked_webserver_rpc_api(
554578
mocked_app_dependencies: None,

0 commit comments

Comments
 (0)