|
1 | 1 | import inspect |
2 | 2 | from collections.abc import Callable |
| 3 | +from pathlib import Path |
| 4 | +from typing import Any |
3 | 5 |
|
| 6 | +import httpx |
4 | 7 | import pytest |
| 8 | +import respx |
5 | 9 | from celery import Celery, Task |
6 | 10 | from celery.contrib.testing.worker import TestWorkController |
7 | 11 | from celery_library.task import register_task |
|
20 | 24 | FunctionID, |
21 | 25 | FunctionInputs, |
22 | 26 | FunctionJobID, |
| 27 | + FunctionUserAccessRights, |
| 28 | + FunctionUserApiAccessRights, |
23 | 29 | RegisteredFunction, |
| 30 | + RegisteredFunctionJob, |
24 | 31 | RegisteredProjectFunction, |
25 | 32 | RegisteredProjectFunctionJob, |
26 | 33 | ) |
27 | 34 | 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 |
28 | 38 | from servicelib.celery.models import TaskFilter, TaskID, TaskMetadata, TasksQueue |
29 | 39 | from servicelib.common_headers import ( |
30 | 40 | X_SIMCORE_PARENT_NODE_ID, |
@@ -221,3 +231,101 @@ async def test_celery_error_propagation( |
221 | 231 | await poll_task_until_done(client, auth, f"{task_uuid}") |
222 | 232 |
|
223 | 233 | 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) |
0 commit comments