From dcae958c2080f42346267fd76049c5644e603cb9 Mon Sep 17 00:00:00 2001 From: Giancarlo Romeo Date: Tue, 17 Dec 2024 13:25:10 +0100 Subject: [PATCH 1/2] Sync nodes when patching --- .../projects/projects_api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 80195446249..639328a9d41 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 @@ -1047,13 +1047,17 @@ async def patch_project_node( if _node_patch_exclude_unset.get("label"): await dynamic_scheduler_api.update_projects_networks(app, project_id=project_id) - # 5. Updates project states for user, if inputs have been changed - if "inputs" in _node_patch_exclude_unset: + # 5. Updates project states for user, if inputs/outputs have been changed + if {"inputs", "outputs"} & _node_patch_exclude_unset.keys(): updated_project = await add_project_states_for_user( - user_id=user_id, project=updated_project, is_template=False, app=app + user_id=user_id, project=updated_project, is_template=False, app=app ) + for node_uuid in updated_project["workbench"]: + await notify_project_node_update( + app, updated_project, node_uuid, errors=None + ) + return - # 6. Notify project node update await notify_project_node_update(app, updated_project, node_id, errors=None) From 98afd8fc054f527e26a4ccc13b86e3cbc2aee5b5 Mon Sep 17 00:00:00 2001 From: Giancarlo Romeo Date: Wed, 18 Dec 2024 15:36:10 +0100 Subject: [PATCH 2/2] add tests --- .../02/test_projects_nodes_handlers__patch.py | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handlers__patch.py b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handlers__patch.py index 5f836e2b747..f9af27e9398 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handlers__patch.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handlers__patch.py @@ -53,6 +53,13 @@ def mock_catalog_rpc_check_for_service(mocker: MockerFixture): ) +@pytest.fixture +def mocked_notify_project_node_update(mocker: MockerFixture): + return mocker.patch( + "simcore_service_webserver.projects.projects_api.notify_project_node_update", + ) + + @pytest.mark.parametrize( "user_role,expected", [ @@ -195,7 +202,7 @@ async def test_patch_project_node( _tested_node = data["workbench"][node_id] assert _tested_node["label"] == "testing-string" - assert _tested_node["progress"] == None + assert _tested_node["progress"] is None assert _tested_node["key"] == _patch_key["key"] assert _tested_node["version"] == _patch_version["version"] assert _tested_node["inputs"] == _patch_inputs["inputs"] @@ -205,6 +212,83 @@ async def test_patch_project_node( assert _tested_node["outputs"] == _patch_outputs["outputs"] +@pytest.mark.parametrize( + "user_role,expected", [(UserRole.USER, status.HTTP_204_NO_CONTENT)] +) +async def test_patch_project_node_notifies( + mocker: MockerFixture, + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + expected: HTTPStatus, + mock_catalog_api_get_services_for_user_in_product, + mock_project_uses_available_services, + mock_catalog_rpc_check_for_service, + mocked_notify_project_node_update, +): + + node_id = next(iter(user_project["workbench"])) + assert client.app + base_url = client.app.router["patch_project_node"].url_for( + project_id=user_project["uuid"], node_id=node_id + ) + + # inputs + _patch_inputs = { + "key": "simcore/services/dynamic/patch-service-key", + } + resp = await client.patch( + f"{base_url}", + data=json.dumps(_patch_inputs), + ) + await assert_status(resp, expected) + assert mocked_notify_project_node_update.call_count == 1 + args = mocked_notify_project_node_update.await_args_list + assert args[0][0][1]["workbench"][node_id]["key"] == _patch_inputs["key"] + assert f"{args[0][0][2]}" == node_id + + +@pytest.mark.parametrize( + "user_role,expected", [(UserRole.USER, status.HTTP_204_NO_CONTENT)] +) +async def test_patch_project_node_inputs_notifies( + mocker: MockerFixture, + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + expected: HTTPStatus, + mock_catalog_api_get_services_for_user_in_product, + mock_project_uses_available_services, + mocked_notify_project_node_update, +): + node_id = next(iter(user_project["workbench"])) + assert client.app + base_url = client.app.router["patch_project_node"].url_for( + project_id=user_project["uuid"], node_id=node_id + ) + + # inputs + _patch_inputs = { + "inputs": { + "input_1": { + "nodeUuid": "c374e5ba-fc42-5c40-ae74-df7ef337f597", + "output": "out_1", + }, + } + } + resp = await client.patch( + f"{base_url}", + data=json.dumps(_patch_inputs), + ) + await assert_status(resp, expected) + assert mocked_notify_project_node_update.call_count > 1 + # 1 message per node updated + assert [ + call_args[0][2] + for call_args in mocked_notify_project_node_update.await_args_list + ] == list(user_project["workbench"].keys()) + + @pytest.mark.parametrize( "user_role,expected", [(UserRole.USER, status.HTTP_204_NO_CONTENT)] )