From 83c04f5f563d59f94e49d09cc535b8dcf84a84cf Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Wed, 11 Dec 2024 14:58:07 +0100 Subject: [PATCH 1/8] redirect get_project_inactivity --- .../dynamic_services.py | 2 + .../dynamic_scheduler/services.py | 23 ++++++++++- .../api/rpc/_services.py | 15 ++++++- .../services/director_v2/_public_client.py | 17 +++++++- .../services/director_v2/_thin_client.py | 11 ++++++ .../services/scheduler_interface.py | 22 ++++++++++- .../unit/api_rpc/test_api_rpc__services.py | 39 ++++++++++++++++++- .../director_v2/_core_dynamic_services.py | 18 --------- .../director_v2/api.py | 2 - .../dynamic_scheduler/api.py | 19 ++++++++- .../projects/projects_api.py | 5 +-- 11 files changed, 143 insertions(+), 30 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services.py b/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services.py index 151611271a4..d26acac0490 100644 --- a/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services.py +++ b/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services.py @@ -79,3 +79,5 @@ class DynamicServiceCreate(ServiceDetails): class GetProjectInactivityResponse(BaseModel): is_inactive: bool + + model_config = ConfigDict(json_schema_extra={"example": {"is_inactive": "false"}}) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py index d941f889bd7..78325688f14 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py @@ -1,7 +1,10 @@ import logging from typing import Final -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.api_schemas_dynamic_scheduler import DYNAMIC_SCHEDULER_RPC_NAMESPACE from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, @@ -93,3 +96,21 @@ async def stop_dynamic_service( timeout_s=timeout_s, ) assert result is None # nosec + + +@log_decorator(_logger, level=logging.DEBUG) +async def get_project_inactivity( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + project_id: ProjectID, + max_inactivity_seconds: NonNegativeInt, +) -> GetProjectInactivityResponse: + result = await rabbitmq_rpc_client.request( + DYNAMIC_SCHEDULER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_project_inactivity"), + project_id=project_id, + max_inactivity_seconds=max_inactivity_seconds, + timeout_s=_RPC_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, GetProjectInactivityResponse) # nosec + return result diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py index 8cd90ddb8f0..173fb5b563a 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py @@ -1,5 +1,8 @@ from fastapi import FastAPI -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, DynamicServiceStop, @@ -8,6 +11,7 @@ from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID from models_library.users import UserID +from pydantic import NonNegativeInt from servicelib.rabbitmq import RPCRouter from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler.errors import ( ServiceWaitingForManualInterventionError, @@ -56,3 +60,12 @@ async def stop_dynamic_service( return await scheduler_interface.stop_dynamic_service( app, dynamic_service_stop=dynamic_service_stop ) + + +@router.expose() +async def get_project_inactivity( + app: FastAPI, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt +) -> GetProjectInactivityResponse: + return await scheduler_interface.get_project_inactivity( + app, project_id=project_id, max_inactivity_seconds=max_inactivity_seconds + ) diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py index 4b49618d6df..2a9586e9c4a 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py @@ -2,7 +2,10 @@ from typing import Any from fastapi import FastAPI, status -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, ) @@ -10,7 +13,7 @@ from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID from models_library.users import UserID -from pydantic import TypeAdapter +from pydantic import NonNegativeInt, TypeAdapter from servicelib.fastapi.app_state import SingletonInAppStateMixin from servicelib.fastapi.http_client import AttachLifespanMixin, HasClientSetupInterface from servicelib.fastapi.http_client_thin import UnexpectedStatusError @@ -108,6 +111,16 @@ async def list_tracked_dynamic_services( ) return TypeAdapter(list[DynamicServiceGet]).validate_python(response.json()) + async def get_project_inactivity( + self, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt + ) -> GetProjectInactivityResponse: + response = await self.thin_client.get_projects_inactivity( + project_id=project_id, max_inactivity_seconds=max_inactivity_seconds + ) + return TypeAdapter(GetProjectInactivityResponse).validate_python( + response.json() + ) + def setup_director_v2(app: FastAPI) -> None: public_client = DirectorV2Client(app) diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py index bfb64e8839a..666b6f9da5e 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py @@ -12,6 +12,7 @@ from models_library.projects_nodes_io import NodeID from models_library.services_resources import ServiceResourcesDictHelpers from models_library.users import UserID +from pydantic import NonNegativeInt from servicelib.common_headers import ( X_DYNAMIC_SIDECAR_REQUEST_DNS, X_DYNAMIC_SIDECAR_REQUEST_SCHEME, @@ -124,3 +125,13 @@ async def get_dynamic_services( "/dynamic_services", params=as_dict_exclude_unset(user_id=user_id, project_id=project_id), ) + + @retry_on_errors() + @expect_status(status.HTTP_200_OK) + async def get_projects_inactivity( + self, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt + ) -> Response: + return await self.client.get( + f"/dynamic_services/projects/{project_id}/inactivity", + params={"max_inactivity_seconds": max_inactivity_seconds}, + ) diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py index 6f655b544e2..8eaf74906b8 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py @@ -1,5 +1,8 @@ from fastapi import FastAPI -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, DynamicServiceStop, @@ -8,6 +11,7 @@ from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID from models_library.users import UserID +from pydantic import NonNegativeInt from ..core.settings import ApplicationSettings from .director_v2 import DirectorV2Client @@ -73,3 +77,19 @@ async def stop_dynamic_service( ) await set_request_as_stopped(app, dynamic_service_stop) + + +async def get_project_inactivity( + app: FastAPI, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt +) -> GetProjectInactivityResponse: + settings: ApplicationSettings = app.state.settings + if settings.DYNAMIC_SCHEDULER_USE_INTERNAL_SCHEDULER: + raise NotImplementedError + + director_v2_client = DirectorV2Client.get_from_app_state(app) + response: GetProjectInactivityResponse = ( + await director_v2_client.get_project_inactivity( + project_id=project_id, max_inactivity_seconds=max_inactivity_seconds + ) + ) + return response diff --git a/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py b/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py index 7c1665065ae..d84de2c3bb8 100644 --- a/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py +++ b/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py @@ -9,7 +9,10 @@ from faker import Faker from fastapi import FastAPI, status from fastapi.encoders import jsonable_encoder -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, DynamicServiceStop, @@ -490,3 +493,37 @@ async def test_stop_dynamic_service_serializes_generic_errors( ), timeout_s=5, ) + + +@pytest.fixture +def inactivity_response() -> GetProjectInactivityResponse: + return TypeAdapter(GetProjectInactivityResponse).validate_python( + GetProjectInactivityResponse.model_json_schema()["example"] + ) + + +@pytest.fixture +def mock_director_v2_get_project_inactivity( + project_id: ProjectID, inactivity_response: GetProjectInactivityResponse +) -> Iterator[None]: + with respx.mock( + base_url="http://director-v2:8000/v2", + assert_all_called=False, + assert_all_mocked=True, # IMPORTANT: KEEP always True! + ) as mock: + mock.get(f"/dynamic_services/projects/{project_id}/inactivity").respond( + status.HTTP_200_OK, text=inactivity_response.model_dump_json() + ) + yield None + + +async def test_get_project_inactivity( + mock_director_v2_get_project_inactivity: None, + rpc_client: RabbitMQRPCClient, + project_id: ProjectID, + inactivity_response: GetProjectInactivityResponse, +): + result = await services.get_project_inactivity( + rpc_client, project_id=project_id, max_inactivity_seconds=5 + ) + assert result == inactivity_response diff --git a/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py b/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py index bd4c03f31fe..7d7054e05a6 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py @@ -9,7 +9,6 @@ from aiohttp import web from models_library.projects import ProjectID from models_library.services import ServicePortKey -from pydantic import NonNegativeInt from servicelib.logging_utils import log_decorator from yarl import URL @@ -94,20 +93,3 @@ async def update_dynamic_service_networks_in_project( await request_director_v2( app, "PATCH", backend_url, expected_status=web.HTTPNoContent ) - - -@log_decorator(logger=_log) -async def get_project_inactivity( - app: web.Application, - project_id: ProjectID, - max_inactivity_seconds: NonNegativeInt, -) -> DataType: - settings: DirectorV2Settings = get_plugin_settings(app) - backend_url = ( - URL(settings.base_url) / f"dynamic_services/projects/{project_id}/inactivity" - ).update_query(max_inactivity_seconds=max_inactivity_seconds) - result = await request_director_v2( - app, "GET", backend_url, expected_status=web.HTTPOk - ) - assert isinstance(result, dict) # nosec - return result diff --git a/services/web/server/src/simcore_service_webserver/director_v2/api.py b/services/web/server/src/simcore_service_webserver/director_v2/api.py index f56de16b543..c2f4dcd5a98 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/api.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/api.py @@ -17,7 +17,6 @@ stop_pipeline, ) from ._core_dynamic_services import ( - get_project_inactivity, request_retrieve_dyn_service, restart_dynamic_service, retrieve, @@ -34,7 +33,6 @@ "DirectorServiceError", "get_batch_tasks_outputs", "get_computation_task", - "get_project_inactivity", "get_project_run_policy", "is_healthy", "is_pipeline_running", diff --git a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py index ef8f2b1f703..ee6f770e2a8 100644 --- a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py +++ b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py @@ -3,7 +3,10 @@ from functools import partial from aiohttp import web -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, DynamicServiceStop, @@ -19,6 +22,7 @@ from models_library.projects_nodes_io import NodeID from models_library.rabbitmq_messages import ProgressRabbitMessageProject, ProgressType from models_library.users import UserID +from pydantic import NonNegativeInt from pydantic.types import PositiveInt from servicelib.progress_bar import ProgressBarData from servicelib.rabbitmq import RabbitMQClient, RPCServerError @@ -148,3 +152,16 @@ async def stop_dynamic_services_in_project( ] await logged_gather(*services_to_stop) + + +async def get_project_inactivity( + app: web.Application, + *, + project_id: ProjectID, + max_inactivity_seconds: NonNegativeInt, +) -> GetProjectInactivityResponse: + return await services.get_project_inactivity( + get_rabbitmq_rpc_client(app), + project_id=project_id, + max_inactivity_seconds=max_inactivity_seconds, + ) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index c0d3c8af835..43fdef52363 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -1869,13 +1869,12 @@ async def get_project_inactivity( app: web.Application, project_id: ProjectID ) -> GetProjectInactivityResponse: project_settings: ProjectsSettings = get_plugin_settings(app) - project_inactivity = await director_v2_api.get_project_inactivity( + return await dynamic_scheduler_api.get_project_inactivity( app, - project_id, + project_id=project_id, # NOTE: project is considered inactive if all services exposing an /inactivity # endpoint were inactive since at least PROJECTS_INACTIVITY_INTERVAL max_inactivity_seconds=int( project_settings.PROJECTS_INACTIVITY_INTERVAL.total_seconds() ), ) - return GetProjectInactivityResponse.model_validate(project_inactivity) From ed2ae7574ffe3907c14cb80b8e5e5b28636a6cfe Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Wed, 11 Dec 2024 14:59:15 +0100 Subject: [PATCH 2/8] fixed specs --- .../server/src/simcore_service_webserver/api/v0/openapi.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index d27cbd9537e..ae962e8eda8 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -9952,6 +9952,8 @@ components: required: - is_inactive title: GetProjectInactivityResponse + example: + is_inactive: 'false' GetWalletAutoRecharge: properties: enabled: @@ -10636,7 +10638,7 @@ components: read: true write: false description: Open to all users - gid: '0' + gid: 1 label: All me: accessRights: From c175ca031d84d644d3b32b09f250aeabea38bba5 Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Wed, 11 Dec 2024 15:12:22 +0100 Subject: [PATCH 3/8] update specs --- services/director-v2/openapi.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/director-v2/openapi.json b/services/director-v2/openapi.json index 63418baabe5..acb78a4d987 100644 --- a/services/director-v2/openapi.json +++ b/services/director-v2/openapi.json @@ -2057,7 +2057,10 @@ "required": [ "is_inactive" ], - "title": "GetProjectInactivityResponse" + "title": "GetProjectInactivityResponse", + "example": { + "is_inactive": "false" + } }, "HTTPValidationError": { "properties": { From 6c32749f4d44f19584a5f939d7839a6bb3b6eb6c Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Thu, 12 Dec 2024 09:20:07 +0100 Subject: [PATCH 4/8] fixed broken test --- .../02/test_projects_crud_handlers.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py index 95a2671739b..971dcda8a20 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py @@ -4,7 +4,6 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -import re import uuid as uuidlib from collections.abc import Awaitable, Callable, Iterator from http import HTTPStatus @@ -16,9 +15,13 @@ from aiohttp.test_utils import TestClient from aioresponses import aioresponses from faker import Faker +from models_library.api_schemas_directorv2.dynamic_services import ( + GetProjectInactivityResponse, +) from models_library.products import ProductName from models_library.projects_state import ProjectState from pydantic import TypeAdapter +from pytest_mock import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.webserver_login import UserInfoDict from pytest_simcore.helpers.webserver_parametrizations import ( @@ -651,18 +654,10 @@ async def test_new_template_from_project( @pytest.fixture -def mock_director_v2_inactivity( - aioresponses_mocker: aioresponses, is_inactive: bool -) -> None: - aioresponses_mocker.clear() - get_services_pattern = re.compile( - r"^http://[a-z\-_]*director-v2:[0-9]+/v2/dynamic_services/projects/.*/inactivity.*$" - ) - aioresponses_mocker.get( - get_services_pattern, - status=status.HTTP_200_OK, - repeat=True, - payload={"is_inactive": is_inactive}, +def mock_dynamic_scheduler_inactivity(mocker: MockerFixture, is_inactive: bool) -> None: + mocker.patch( + "simcore_service_webserver.projects.projects_api.dynamic_scheduler_api.get_project_inactivity", + return_value=GetProjectInactivityResponse(is_inactive=is_inactive), ) @@ -675,7 +670,7 @@ def mock_director_v2_inactivity( ) @pytest.mark.parametrize("is_inactive", [True, False]) async def test_get_project_inactivity( - mock_director_v2_inactivity: None, + mock_dynamic_scheduler_inactivity: None, logged_user: UserInfoDict, client: TestClient, faker: Faker, From efa102b1fa861a59cf69cfd61db5b7abb0545def Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Wed, 18 Dec 2024 09:16:23 +0100 Subject: [PATCH 5/8] removed interface --- .../director_v2/_core_dynamic_services.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py diff --git a/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py b/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py deleted file mode 100644 index fe0d6d6a546..00000000000 --- a/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py +++ /dev/null @@ -1,7 +0,0 @@ -""" Operations on dynamic-services - -- This interface HIDES request/responses/exceptions to the director-v2 API service - -""" - -# NOTE: this is now empty From 7281f23efaa4fa852c07ac33aa20f8899acacff6 Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Wed, 18 Dec 2024 10:36:05 +0100 Subject: [PATCH 6/8] revert cahnge --- .../services/scheduler_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py index 8c1df0e45ff..ff279fb75c9 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py @@ -103,7 +103,6 @@ async def restart_user_services(app: FastAPI, *, node_id: NodeID) -> None: raise NotImplementedError director_v2_client = DirectorV2Client.get_from_app_state(app) - await director_v2_client.restart_user_services(node_id=node_id) From 5c36635641097f36660cb2f1a736a4816ab3eca0 Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Wed, 18 Dec 2024 12:15:26 +0100 Subject: [PATCH 7/8] unblocking hanging tests --- .../server/tests/unit/with_dbs/02/conftest.py | 17 +++++++++++++++-- .../with_dbs/02/test_projects_crud_handlers.py | 17 +---------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/02/conftest.py b/services/web/server/tests/unit/with_dbs/02/conftest.py index 148d6315d8d..ba6ffa2e327 100644 --- a/services/web/server/tests/unit/with_dbs/02/conftest.py +++ b/services/web/server/tests/unit/with_dbs/02/conftest.py @@ -17,7 +17,10 @@ from aiohttp.test_utils import TestClient from aioresponses import aioresponses from faker import Faker -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet +from models_library.api_schemas_directorv2.dynamic_services import ( + DynamicServiceGet, + GetProjectInactivityResponse, +) from models_library.projects_nodes import Node, NodeID from models_library.projects_state import ProjectState from models_library.services_resources import ( @@ -264,9 +267,19 @@ async def _assert_it( return _assert_it +@pytest.fixture +def disable_dynamic_scheduler_inactivity(mocker: MockerFixture) -> None: + mocker.patch( + "simcore_service_webserver.projects.projects_api.dynamic_scheduler_api.get_project_inactivity", + return_value=GetProjectInactivityResponse(is_inactive=True), + ) + + @pytest.fixture def app_environment( - app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch + app_environment: EnvVarsDict, + monkeypatch: pytest.MonkeyPatch, + disable_dynamic_scheduler_inactivity: None, ) -> EnvVarsDict: envs_plugins = setenvs_from_dict( monkeypatch, diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py index d494d98f446..3747723ecb6 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py @@ -15,13 +15,9 @@ from aiohttp.test_utils import TestClient from aioresponses import aioresponses from faker import Faker -from models_library.api_schemas_directorv2.dynamic_services import ( - GetProjectInactivityResponse, -) from models_library.products import ProductName from models_library.projects_state import ProjectState from pydantic import TypeAdapter -from pytest_mock import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.webserver_login import UserInfoDict from pytest_simcore.helpers.webserver_parametrizations import ( @@ -658,14 +654,6 @@ async def test_new_template_from_project( TypeAdapter(uuidlib.UUID).validate_python(node_name) -@pytest.fixture -def mock_dynamic_scheduler_inactivity(mocker: MockerFixture, is_inactive: bool) -> None: - mocker.patch( - "simcore_service_webserver.projects.projects_api.dynamic_scheduler_api.get_project_inactivity", - return_value=GetProjectInactivityResponse(is_inactive=is_inactive), - ) - - @pytest.mark.parametrize( "user_role,expected", [ @@ -673,15 +661,12 @@ def mock_dynamic_scheduler_inactivity(mocker: MockerFixture, is_inactive: bool) *((role, status.HTTP_200_OK) for role in UserRole if role > UserRole.ANONYMOUS), ], ) -@pytest.mark.parametrize("is_inactive", [True, False]) async def test_get_project_inactivity( - mock_dynamic_scheduler_inactivity: None, logged_user: UserInfoDict, client: TestClient, faker: Faker, user_role: UserRole, expected: HTTPStatus, - is_inactive: bool, ): mock_project_id = faker.uuid4() @@ -697,4 +682,4 @@ async def test_get_project_inactivity( assert data assert error is None - assert data["is_inactive"] is is_inactive + assert data["is_inactive"] is True From 99b369566a490e585ac4563cd633346ef62d6224 Mon Sep 17 00:00:00 2001 From: Andrei Neagu Date: Thu, 19 Dec 2024 09:50:53 +0100 Subject: [PATCH 8/8] restore fixtures --- .../server/tests/unit/with_dbs/02/conftest.py | 17 ++------------ .../02/test_projects_crud_handlers.py | 22 ++++++++++++++++++- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/02/conftest.py b/services/web/server/tests/unit/with_dbs/02/conftest.py index ba6ffa2e327..148d6315d8d 100644 --- a/services/web/server/tests/unit/with_dbs/02/conftest.py +++ b/services/web/server/tests/unit/with_dbs/02/conftest.py @@ -17,10 +17,7 @@ from aiohttp.test_utils import TestClient from aioresponses import aioresponses from faker import Faker -from models_library.api_schemas_directorv2.dynamic_services import ( - DynamicServiceGet, - GetProjectInactivityResponse, -) +from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet from models_library.projects_nodes import Node, NodeID from models_library.projects_state import ProjectState from models_library.services_resources import ( @@ -267,19 +264,9 @@ async def _assert_it( return _assert_it -@pytest.fixture -def disable_dynamic_scheduler_inactivity(mocker: MockerFixture) -> None: - mocker.patch( - "simcore_service_webserver.projects.projects_api.dynamic_scheduler_api.get_project_inactivity", - return_value=GetProjectInactivityResponse(is_inactive=True), - ) - - @pytest.fixture def app_environment( - app_environment: EnvVarsDict, - monkeypatch: pytest.MonkeyPatch, - disable_dynamic_scheduler_inactivity: None, + app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch ) -> EnvVarsDict: envs_plugins = setenvs_from_dict( monkeypatch, diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py index 3747723ecb6..b15a9fb9e45 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers.py @@ -4,6 +4,7 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable +import re import uuid as uuidlib from collections.abc import Awaitable, Callable, Iterator from http import HTTPStatus @@ -654,6 +655,22 @@ async def test_new_template_from_project( TypeAdapter(uuidlib.UUID).validate_python(node_name) +@pytest.fixture +def mock_director_v2_inactivity( + aioresponses_mocker: aioresponses, is_inactive: bool +) -> None: + aioresponses_mocker.clear() + get_services_pattern = re.compile( + r"^http://[a-z\-_]*director-v2:[0-9]+/v2/dynamic_services/projects/.*/inactivity.*$" + ) + aioresponses_mocker.get( + get_services_pattern, + status=status.HTTP_200_OK, + repeat=True, + payload={"is_inactive": is_inactive}, + ) + + @pytest.mark.parametrize( "user_role,expected", [ @@ -661,12 +678,15 @@ async def test_new_template_from_project( *((role, status.HTTP_200_OK) for role in UserRole if role > UserRole.ANONYMOUS), ], ) +@pytest.mark.parametrize("is_inactive", [True, False]) async def test_get_project_inactivity( + mock_director_v2_inactivity: None, logged_user: UserInfoDict, client: TestClient, faker: Faker, user_role: UserRole, expected: HTTPStatus, + is_inactive: bool, ): mock_project_id = faker.uuid4() @@ -682,4 +702,4 @@ async def test_get_project_inactivity( assert data assert error is None - assert data["is_inactive"] is True + assert data["is_inactive"] is is_inactive