Skip to content

Commit c1a67d5

Browse files
committed
add test for full round trip of running function
1 parent 75a06d5 commit c1a67d5

File tree

4 files changed

+108
-91
lines changed

4 files changed

+108
-91
lines changed

services/api-server/tests/unit/celery/test_functions.py renamed to services/api-server/tests/unit/api_functions/celery/test_functions.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import inspect
22
from collections.abc import Callable
3+
from pathlib import Path
4+
from typing import Any
35

6+
import httpx
47
import pytest
8+
import respx
59
from celery import Celery, Task
610
from celery.contrib.testing.worker import TestWorkController
711
from celery_library.task import register_task
@@ -20,11 +24,17 @@
2024
FunctionID,
2125
FunctionInputs,
2226
FunctionJobID,
27+
FunctionUserAccessRights,
28+
FunctionUserApiAccessRights,
2329
RegisteredFunction,
30+
RegisteredFunctionJob,
2431
RegisteredProjectFunction,
2532
RegisteredProjectFunctionJob,
2633
)
2734
from models_library.projects import ProjectID
35+
from models_library.users import UserID
36+
from pytest_mock import MockType
37+
from pytest_simcore.helpers.httpx_calls_capture_models import HttpApiCallCaptureModel
2838
from servicelib.celery.models import TaskFilter, TaskID, TaskMetadata, TasksQueue
2939
from servicelib.common_headers import (
3040
X_SIMCORE_PARENT_NODE_ID,
@@ -221,3 +231,101 @@ async def test_celery_error_propagation(
221231
await poll_task_until_done(client, auth, f"{task_uuid}")
222232

223233
assert exc_info.value.response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
234+
235+
236+
@pytest.mark.parametrize(
237+
"parent_project_uuid, parent_node_uuid, expected_status_code",
238+
[
239+
(None, None, status.HTTP_422_UNPROCESSABLE_ENTITY),
240+
(f"{_faker.uuid4()}", None, status.HTTP_422_UNPROCESSABLE_ENTITY),
241+
(None, f"{_faker.uuid4()}", status.HTTP_422_UNPROCESSABLE_ENTITY),
242+
(f"{_faker.uuid4()}", f"{_faker.uuid4()}", status.HTTP_200_OK),
243+
("null", "null", status.HTTP_200_OK),
244+
],
245+
)
246+
@pytest.mark.parametrize("capture", ["run_study_function_parent_info.json"])
247+
@pytest.mark.parametrize("mocked_app_dependencies", [None])
248+
async def test_run_project_function_parent_info(
249+
app: FastAPI,
250+
with_api_server_celery_worker: TestWorkController,
251+
client: AsyncClient,
252+
mock_handler_in_functions_rpc_interface: Callable[[str, Any], None],
253+
mock_registered_project_function: RegisteredProjectFunction,
254+
mock_registered_project_function_job: RegisteredFunctionJob,
255+
auth: httpx.BasicAuth,
256+
user_id: UserID,
257+
mocked_webserver_rest_api_base: respx.MockRouter,
258+
mocked_directorv2_rest_api_base: respx.MockRouter,
259+
mocked_webserver_rpc_api: dict[str, MockType],
260+
create_respx_mock_from_capture,
261+
project_tests_dir: Path,
262+
parent_project_uuid: str | None,
263+
parent_node_uuid: str | None,
264+
expected_status_code: int,
265+
capture: str,
266+
) -> None:
267+
def _default_side_effect(
268+
request: httpx.Request,
269+
path_params: dict[str, Any],
270+
capture: HttpApiCallCaptureModel,
271+
) -> Any:
272+
if request.method == "POST" and request.url.path.endswith("/projects"):
273+
if parent_project_uuid and parent_project_uuid != "null":
274+
_parent_uuid = request.headers.get(X_SIMCORE_PARENT_PROJECT_UUID)
275+
assert _parent_uuid is not None
276+
assert parent_project_uuid == _parent_uuid
277+
if parent_node_uuid and parent_node_uuid != "null":
278+
_parent_node_uuid = request.headers.get(X_SIMCORE_PARENT_NODE_ID)
279+
assert _parent_node_uuid is not None
280+
assert parent_node_uuid == _parent_node_uuid
281+
return capture.response_body
282+
283+
create_respx_mock_from_capture(
284+
respx_mocks=[mocked_webserver_rest_api_base, mocked_directorv2_rest_api_base],
285+
capture_path=project_tests_dir / "mocks" / capture,
286+
side_effects_callbacks=[_default_side_effect] * 50,
287+
)
288+
289+
mock_handler_in_functions_rpc_interface(
290+
"get_function_user_permissions",
291+
FunctionUserAccessRights(
292+
user_id=user_id,
293+
execute=True,
294+
read=True,
295+
write=True,
296+
),
297+
)
298+
mock_handler_in_functions_rpc_interface(
299+
"get_function", mock_registered_project_function
300+
)
301+
mock_handler_in_functions_rpc_interface("find_cached_function_jobs", [])
302+
mock_handler_in_functions_rpc_interface(
303+
"register_function_job", mock_registered_project_function_job
304+
)
305+
mock_handler_in_functions_rpc_interface(
306+
"get_functions_user_api_access_rights",
307+
FunctionUserApiAccessRights(
308+
user_id=user_id,
309+
execute_functions=True,
310+
write_functions=True,
311+
read_functions=True,
312+
),
313+
)
314+
315+
headers = {}
316+
if parent_project_uuid:
317+
headers[X_SIMCORE_PARENT_PROJECT_UUID] = parent_project_uuid
318+
if parent_node_uuid:
319+
headers[X_SIMCORE_PARENT_NODE_ID] = parent_node_uuid
320+
321+
response = await client.post(
322+
f"{API_VTAG}/functions/{mock_registered_project_function.uid}:run",
323+
json={},
324+
auth=auth,
325+
headers=headers,
326+
)
327+
assert response.status_code == expected_status_code
328+
if response.status_code == status.HTTP_200_OK:
329+
task = TaskGet.model_validate(response.json())
330+
result = await poll_task_until_done(client, auth, task.task_id)
331+
RegisteredProjectFunctionJob.model_validate(result.result)

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

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -487,97 +487,6 @@ def _default_side_effect(
487487
assert isinstance(job, RegisteredProjectFunctionJob)
488488

489489

490-
@pytest.mark.parametrize(
491-
"parent_project_uuid, parent_node_uuid, expected_status_code",
492-
[
493-
(None, None, status.HTTP_422_UNPROCESSABLE_ENTITY),
494-
(f"{_faker.uuid4()}", None, status.HTTP_422_UNPROCESSABLE_ENTITY),
495-
(None, f"{_faker.uuid4()}", status.HTTP_422_UNPROCESSABLE_ENTITY),
496-
(f"{_faker.uuid4()}", f"{_faker.uuid4()}", status.HTTP_200_OK),
497-
("null", "null", status.HTTP_200_OK),
498-
],
499-
)
500-
@pytest.mark.parametrize("capture", ["run_study_function_parent_info.json"])
501-
async def test_run_project_function_parent_info(
502-
client: AsyncClient,
503-
mock_handler_in_functions_rpc_interface: Callable[[str, Any], None],
504-
mock_registered_project_function: RegisteredProjectFunction,
505-
mock_registered_project_function_job: RegisteredFunctionJob,
506-
auth: httpx.BasicAuth,
507-
user_id: UserID,
508-
mocked_webserver_rest_api_base: respx.MockRouter,
509-
mocked_directorv2_rest_api_base: respx.MockRouter,
510-
mocked_webserver_rpc_api: dict[str, MockType],
511-
create_respx_mock_from_capture,
512-
project_tests_dir: Path,
513-
parent_project_uuid: str | None,
514-
parent_node_uuid: str | None,
515-
expected_status_code: int,
516-
capture: str,
517-
) -> None:
518-
def _default_side_effect(
519-
request: httpx.Request,
520-
path_params: dict[str, Any],
521-
capture: HttpApiCallCaptureModel,
522-
) -> Any:
523-
if request.method == "POST" and request.url.path.endswith("/projects"):
524-
if parent_project_uuid and parent_project_uuid != "null":
525-
_parent_uuid = request.headers.get(X_SIMCORE_PARENT_PROJECT_UUID)
526-
assert _parent_uuid is not None
527-
assert parent_project_uuid == _parent_uuid
528-
if parent_node_uuid and parent_node_uuid != "null":
529-
_parent_node_uuid = request.headers.get(X_SIMCORE_PARENT_NODE_ID)
530-
assert _parent_node_uuid is not None
531-
assert parent_node_uuid == _parent_node_uuid
532-
return capture.response_body
533-
534-
create_respx_mock_from_capture(
535-
respx_mocks=[mocked_webserver_rest_api_base, mocked_directorv2_rest_api_base],
536-
capture_path=project_tests_dir / "mocks" / capture,
537-
side_effects_callbacks=[_default_side_effect] * 50,
538-
)
539-
540-
mock_handler_in_functions_rpc_interface(
541-
"get_function_user_permissions",
542-
FunctionUserAccessRights(
543-
user_id=user_id,
544-
execute=True,
545-
read=True,
546-
write=True,
547-
),
548-
)
549-
mock_handler_in_functions_rpc_interface(
550-
"get_function", mock_registered_project_function
551-
)
552-
mock_handler_in_functions_rpc_interface("find_cached_function_jobs", [])
553-
mock_handler_in_functions_rpc_interface(
554-
"register_function_job", mock_registered_project_function_job
555-
)
556-
mock_handler_in_functions_rpc_interface(
557-
"get_functions_user_api_access_rights",
558-
FunctionUserApiAccessRights(
559-
user_id=user_id,
560-
execute_functions=True,
561-
write_functions=True,
562-
read_functions=True,
563-
),
564-
)
565-
566-
headers = {}
567-
if parent_project_uuid:
568-
headers[X_SIMCORE_PARENT_PROJECT_UUID] = parent_project_uuid
569-
if parent_node_uuid:
570-
headers[X_SIMCORE_PARENT_NODE_ID] = parent_node_uuid
571-
572-
response = await client.post(
573-
f"{API_VTAG}/functions/{mock_registered_project_function.uid}:run",
574-
json={},
575-
auth=auth,
576-
headers=headers,
577-
)
578-
assert response.status_code == expected_status_code
579-
580-
581490
@pytest.mark.parametrize(
582491
"parent_project_uuid, parent_node_uuid, expected_status_code",
583492
[

0 commit comments

Comments
 (0)