diff --git a/services/api-server/openapi.json b/services/api-server/openapi.json index 4b7da8245dab..f35f4723d923 100644 --- a/services/api-server/openapi.json +++ b/services/api-server/openapi.json @@ -7604,6 +7604,42 @@ "format": "uuid", "title": "Function Id" } + }, + { + "name": "x-simcore-parent-project-uuid", + "in": "header", + "required": true, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "const": "null", + "type": "string" + } + ], + "title": "X-Simcore-Parent-Project-Uuid" + } + }, + { + "name": "x-simcore-parent-node-id", + "in": "header", + "required": true, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "const": "null", + "type": "string" + } + ], + "title": "X-Simcore-Parent-Node-Id" + } } ], "requestBody": { @@ -7700,6 +7736,42 @@ "format": "uuid", "title": "Function Id" } + }, + { + "name": "x-simcore-parent-project-uuid", + "in": "header", + "required": true, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "const": "null", + "type": "string" + } + ], + "title": "X-Simcore-Parent-Project-Uuid" + } + }, + { + "name": "x-simcore-parent-node-id", + "in": "header", + "required": true, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "const": "null", + "type": "string" + } + ], + "title": "X-Simcore-Parent-Node-Id" + } } ], "requestBody": { diff --git a/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py b/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py index fd5f2af40290..bbfeefc4efc0 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py @@ -1,8 +1,9 @@ +# pylint: disable=too-many-positional-arguments from collections.abc import Callable -from typing import Annotated, Final +from typing import Annotated, Final, Literal import jsonschema -from fastapi import APIRouter, Depends, Request, status +from fastapi import APIRouter, Depends, Header, Request, status from fastapi_pagination.api import create_page from jsonschema import ValidationError from models_library.api_schemas_api_server.functions import ( @@ -29,6 +30,8 @@ UnsupportedFunctionClassError, ) from models_library.products import ProductName +from models_library.projects import ProjectID +from models_library.projects_nodes_io import NodeID from models_library.projects_state import RunningState from models_library.users import UserID from servicelib.fastapi.dependencies import get_reverse_url_mapper @@ -372,8 +375,21 @@ async def run_function( # noqa: PLR0913 product_name: Annotated[str, Depends(get_product_name)], solver_service: Annotated[SolverService, Depends(get_solver_service)], job_service: Annotated[JobService, Depends(get_job_service)], + x_simcore_parent_project_uuid: Annotated[ProjectID | Literal["null"], Header()], + x_simcore_parent_node_id: Annotated[NodeID | Literal["null"], Header()], ) -> RegisteredFunctionJob: + parent_project_uuid = ( + x_simcore_parent_project_uuid + if isinstance(x_simcore_parent_project_uuid, ProjectID) + else None + ) + parent_node_id = ( + x_simcore_parent_node_id + if isinstance(x_simcore_parent_node_id, NodeID) + else None + ) + # Make sure the user is allowed to execute any function # (read/write right is checked in the other endpoint called in this method) user_api_access_rights = await wb_api_rpc.get_functions_user_api_access_rights( @@ -443,8 +459,8 @@ async def run_function( # noqa: PLR0913 webserver_api=webserver_api, wb_api_rpc=wb_api_rpc, url_for=url_for, - x_simcore_parent_project_uuid=None, - x_simcore_parent_node_id=None, + x_simcore_parent_project_uuid=parent_project_uuid, + x_simcore_parent_node_id=parent_node_id, user_id=user_id, product_name=product_name, ) @@ -478,8 +494,8 @@ async def run_function( # noqa: PLR0913 solver_service=solver_service, job_service=job_service, url_for=url_for, - x_simcore_parent_project_uuid=None, - x_simcore_parent_node_id=None, + x_simcore_parent_project_uuid=parent_project_uuid, + x_simcore_parent_node_id=parent_node_id, ) await solvers_jobs.start_job( request=request, @@ -558,6 +574,8 @@ async def map_function( # noqa: PLR0913 product_name: Annotated[str, Depends(get_product_name)], solver_service: Annotated[SolverService, Depends(get_solver_service)], job_service: Annotated[JobService, Depends(get_job_service)], + x_simcore_parent_project_uuid: Annotated[ProjectID | Literal["null"], Header()], + x_simcore_parent_node_id: Annotated[NodeID | Literal["null"], Header()], ) -> RegisteredFunctionJobCollection: function_jobs = [] function_jobs = [ @@ -573,6 +591,8 @@ async def map_function( # noqa: PLR0913 request=request, solver_service=solver_service, job_service=job_service, + x_simcore_parent_project_uuid=x_simcore_parent_project_uuid, + x_simcore_parent_node_id=x_simcore_parent_node_id, ) for function_inputs in function_inputs_list ] diff --git a/services/api-server/tests/mocks/run_study_function_parent_info.json b/services/api-server/tests/mocks/run_study_function_parent_info.json new file mode 100644 index 000000000000..b9b9f8dc326c --- /dev/null +++ b/services/api-server/tests/mocks/run_study_function_parent_info.json @@ -0,0 +1,2368 @@ +[ + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:05:50.043464+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.10b07f64-0b19-4f2a-924b-67d9bcadcef8/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:08:08.825919+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b0f371b4-5247-4efc-94fb-eb3338765f73/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:09:08.065507+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.c7256296-53c7-408d-ad2b-53f2e2e17b77/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:11:47.314542+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.55547ef2-50e2-4320-ae1b-ba4fb0c68feb/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:15:55.792723+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.b20cfc08-44cb-4a7c-92a5-112e2560ba61/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:20:31.694095+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.68d96dd3-5ee2-42c8-add2-b0e32196a773/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8", + "task_name": "POST /v0/projects?from_study=e3e70682-c209-4cac-a29f-6fbed82c07cd&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3De3e70682-c209-4cac-a29f-6fbed82c07cd%26hidden%3Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:23:55.125558+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253De3e70682-c209-4cac-a29f-6fbed82c07cd%2526hidden%253Dtrue.54dfa19d-3324-44cf-876b-e30c92f238b8/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": null, + "error": { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "support_id": null, + "status": 404, + "errors": [ + { + "code": "HTTPNotFound", + "message": "Not Found", + "resource": null, + "field": null + } + ], + "logs": [ + { + "message": "Project e3e70682-c209-4cac-a29f-6fbed82c07cd not found", + "level": "ERROR", + "logger": "user" + } + ] + } + }, + "status_code": 404 + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24", + "task_name": "POST /v0/projects?from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:29:34.382817+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.40882c4a-e222-4e04-af43-3a8e45b2df24/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "uuid": "71ec33ca-485a-11f0-bc8c-0242ac140019", + "name": "sleeper (Copy)", + "description": "", + "thumbnail": "", + "type": "STANDARD", + "templateType": null, + "workbench": { + "1cd13000-dd5d-5ebd-964f-7affa26606a9": { + "key": "simcore/services/comp/itis/sleeper", + "version": "2.2.1", + "label": "sleeper", + "progress": 0.0, + "inputs": { + "input_2": 2, + "input_3": false, + "input_4": 0, + "input_5": 0 + }, + "inputsRequired": [], + "inputNodes": [], + "state": { + "modified": true, + "dependencies": [], + "currentStatus": "NOT_STARTED", + "progress": null + } + } + }, + "prjOwner": "bisgaard@itis.swiss", + "accessRights": { + "4": { + "read": true, + "write": true, + "delete": true + } + }, + "creationDate": "2025-06-13T13:29:34.407Z", + "lastChangeDate": "2025-06-13T13:29:34.407Z", + "state": { + "locked": { + "value": false, + "status": "CLOSED" + }, + "state": { + "value": "NOT_STARTED" + } + }, + "trashedAt": null, + "trashedBy": null, + "tags": [], + "classifiers": [], + "quality": { + "enabled": true, + "tsr_target": { + "r01": { + "level": 4, + "references": "" + }, + "r02": { + "level": 4, + "references": "" + }, + "r03": { + "level": 4, + "references": "" + }, + "r04": { + "level": 4, + "references": "" + }, + "r05": { + "level": 4, + "references": "" + }, + "r06": { + "level": 4, + "references": "" + }, + "r07": { + "level": 4, + "references": "" + }, + "r08": { + "level": 4, + "references": "" + }, + "r09": { + "level": 4, + "references": "" + }, + "r10": { + "level": 4, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + }, + "tsr_current": { + "r01": { + "level": 0, + "references": "" + }, + "r02": { + "level": 0, + "references": "" + }, + "r03": { + "level": 0, + "references": "" + }, + "r04": { + "level": 0, + "references": "" + }, + "r05": { + "level": 0, + "references": "" + }, + "r06": { + "level": 0, + "references": "" + }, + "r07": { + "level": 0, + "references": "" + }, + "r08": { + "level": 0, + "references": "" + }, + "r09": { + "level": 0, + "references": "" + }, + "r10": { + "level": 0, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + } + }, + "ui": { + "workbench": { + "1cd13000-dd5d-5ebd-964f-7affa26606a9": { + "position": { + "x": 250, + "y": 100 + } + } + }, + "slideshow": {}, + "currentNodeId": "b00f9b90-4857-11f0-bc8c-0242ac140019", + "mode": "pipeline" + }, + "dev": {}, + "workspaceId": null, + "folderId": null + } + }, + "status_code": 201 + }, + { + "name": "PATCH /projects/71ec33ca-485a-11f0-bc8c-0242ac140019", + "description": "", + "method": "PATCH", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "request_payload": { + "name": "studies/b8dd6dc4-4857-11f0-bc8c-0242ac140019/jobs/71ec33ca-485a-11f0-bc8c-0242ac140019" + }, + "status_code": 204 + }, + { + "name": "GET /projects/71ec33ca-485a-11f0-bc8c-0242ac140019/inputs", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}/inputs", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "response_body": { + "data": {} + } + }, + { + "name": "POST /computations/71ec33ca-485a-11f0-bc8c-0242ac140019:start", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/computations/{project_id}:start", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "request_payload": {}, + "response_body": { + "data": { + "pipeline_id": "71ec33ca-485a-11f0-bc8c-0242ac140019" + } + }, + "status_code": 201 + }, + { + "name": "GET /v2/computations/71ec33ca-485a-11f0-bc8c-0242ac140019", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v2/computations/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "query": "user_id=1", + "response_body": { + "id": "71ec33ca-485a-11f0-bc8c-0242ac140019", + "state": "STARTED", + "result": null, + "pipeline_details": { + "adjacency_list": { + "1cd13000-dd5d-5ebd-964f-7affa26606a9": [] + }, + "progress": 0.05, + "node_states": { + "1cd13000-dd5d-5ebd-964f-7affa26606a9": { + "modified": true, + "dependencies": [], + "currentStatus": "STARTED", + "progress": 0.05 + } + } + }, + "iteration": 1, + "started": "2025-06-13T13:30:18.988214Z", + "stopped": null, + "submitted": "2025-06-13T13:30:18.806995Z", + "url": "http://webserver:8080:30003/v2/computations/71ec33ca-485a-11f0-bc8c-0242ac140019?user_id=1", + "stop_url": "http://webserver:8080:30003/v2/computations/71ec33ca-485a-11f0-bc8c-0242ac140019:stop?user_id=1" + } + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.14b1e487-728d-4b99-badc-6993ec76f269", + "task_name": "POST /v0/projects?from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.14b1e487-728d-4b99-badc-6993ec76f269", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.14b1e487-728d-4b99-badc-6993ec76f269/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.14b1e487-728d-4b99-badc-6993ec76f269" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.14b1e487-728d-4b99-badc-6993ec76f269", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.14b1e487-728d-4b99-badc-6993ec76f269", + "message": "Collecting files of 'sleeper'...", + "percent": 1.0 + }, + "done": false, + "started": "2025-06-13T13:31:09.141214+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.14b1e487-728d-4b99-badc-6993ec76f269", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.14b1e487-728d-4b99-badc-6993ec76f269", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:31:09.141214+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.14b1e487-728d-4b99-badc-6993ec76f269/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "uuid": "aa679b18-485a-11f0-89d1-0242ac14050c", + "name": "sleeper (Copy)", + "description": "", + "thumbnail": "", + "type": "STANDARD", + "templateType": null, + "workbench": { + "ed78079d-66fa-54fb-aca4-8f37ec436383": { + "key": "simcore/services/comp/itis/sleeper", + "version": "2.2.1", + "label": "sleeper", + "progress": 0.0, + "inputs": { + "input_2": 2, + "input_3": false, + "input_4": 0, + "input_5": 0 + }, + "inputsRequired": [], + "inputNodes": [], + "state": { + "modified": true, + "dependencies": [], + "currentStatus": "NOT_STARTED", + "progress": null + } + } + }, + "prjOwner": "bisgaard@itis.swiss", + "accessRights": { + "4": { + "read": true, + "write": true, + "delete": true + } + }, + "creationDate": "2025-06-13T13:31:09.169Z", + "lastChangeDate": "2025-06-13T13:31:09.169Z", + "state": { + "locked": { + "value": false, + "status": "CLOSED" + }, + "state": { + "value": "NOT_STARTED" + } + }, + "trashedAt": null, + "trashedBy": null, + "tags": [], + "classifiers": [], + "quality": { + "enabled": true, + "tsr_target": { + "r01": { + "level": 4, + "references": "" + }, + "r02": { + "level": 4, + "references": "" + }, + "r03": { + "level": 4, + "references": "" + }, + "r04": { + "level": 4, + "references": "" + }, + "r05": { + "level": 4, + "references": "" + }, + "r06": { + "level": 4, + "references": "" + }, + "r07": { + "level": 4, + "references": "" + }, + "r08": { + "level": 4, + "references": "" + }, + "r09": { + "level": 4, + "references": "" + }, + "r10": { + "level": 4, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + }, + "tsr_current": { + "r01": { + "level": 0, + "references": "" + }, + "r02": { + "level": 0, + "references": "" + }, + "r03": { + "level": 0, + "references": "" + }, + "r04": { + "level": 0, + "references": "" + }, + "r05": { + "level": 0, + "references": "" + }, + "r06": { + "level": 0, + "references": "" + }, + "r07": { + "level": 0, + "references": "" + }, + "r08": { + "level": 0, + "references": "" + }, + "r09": { + "level": 0, + "references": "" + }, + "r10": { + "level": 0, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + } + }, + "ui": { + "workbench": { + "ed78079d-66fa-54fb-aca4-8f37ec436383": { + "position": { + "x": 250, + "y": 100 + } + } + }, + "slideshow": {}, + "currentNodeId": "b00f9b90-4857-11f0-bc8c-0242ac140019", + "mode": "pipeline" + }, + "dev": {}, + "workspaceId": null, + "folderId": null + } + }, + "status_code": 201 + }, + { + "name": "PATCH /projects/aa679b18-485a-11f0-89d1-0242ac14050c", + "description": "", + "method": "PATCH", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "request_payload": { + "name": "studies/b8dd6dc4-4857-11f0-bc8c-0242ac140019/jobs/aa679b18-485a-11f0-89d1-0242ac14050c" + }, + "status_code": 204 + }, + { + "name": "GET /projects/aa679b18-485a-11f0-89d1-0242ac14050c/inputs", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}/inputs", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "response_body": { + "data": {} + } + }, + { + "name": "POST /computations/aa679b18-485a-11f0-89d1-0242ac14050c:start", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/computations/{project_id}:start", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "request_payload": {}, + "response_body": { + "data": { + "pipeline_id": "aa679b18-485a-11f0-89d1-0242ac14050c" + } + }, + "status_code": 201 + }, + { + "name": "GET /v2/computations/aa679b18-485a-11f0-89d1-0242ac14050c", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v2/computations/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "query": "user_id=1", + "response_body": { + "id": "aa679b18-485a-11f0-89d1-0242ac14050c", + "state": "STARTED", + "result": null, + "pipeline_details": { + "adjacency_list": { + "ed78079d-66fa-54fb-aca4-8f37ec436383": [] + }, + "progress": 0.05, + "node_states": { + "ed78079d-66fa-54fb-aca4-8f37ec436383": { + "modified": true, + "dependencies": [], + "currentStatus": "STARTED", + "progress": 0.05 + } + } + }, + "iteration": 1, + "started": "2025-06-13T13:31:17.841816Z", + "stopped": null, + "submitted": "2025-06-13T13:31:17.672682Z", + "url": "http://webserver:8080:30003/v2/computations/aa679b18-485a-11f0-89d1-0242ac14050c?user_id=1", + "stop_url": "http://webserver:8080:30003/v2/computations/aa679b18-485a-11f0-89d1-0242ac14050c:stop?user_id=1" + } + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca", + "task_name": "POST /v0/projects?from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:34:33.232952+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.4280a56d-58e7-4f2c-aa15-f35a137e60ca/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "uuid": "240cb4c6-485b-11f0-89d1-0242ac14050c", + "name": "sleeper (Copy)", + "description": "", + "thumbnail": "", + "type": "STANDARD", + "templateType": null, + "workbench": { + "982c5607-783c-5295-9a4f-26c8e3c21211": { + "key": "simcore/services/comp/itis/sleeper", + "version": "2.2.1", + "label": "sleeper", + "progress": 0.0, + "inputs": { + "input_2": 2, + "input_3": false, + "input_4": 0, + "input_5": 0 + }, + "inputsRequired": [], + "inputNodes": [], + "state": { + "modified": true, + "dependencies": [], + "currentStatus": "NOT_STARTED", + "progress": null + } + } + }, + "prjOwner": "bisgaard@itis.swiss", + "accessRights": { + "4": { + "read": true, + "write": true, + "delete": true + } + }, + "creationDate": "2025-06-13T13:34:33.254Z", + "lastChangeDate": "2025-06-13T13:34:33.254Z", + "state": { + "locked": { + "value": false, + "status": "CLOSED" + }, + "state": { + "value": "NOT_STARTED" + } + }, + "trashedAt": null, + "trashedBy": null, + "tags": [], + "classifiers": [], + "quality": { + "enabled": true, + "tsr_target": { + "r01": { + "level": 4, + "references": "" + }, + "r02": { + "level": 4, + "references": "" + }, + "r03": { + "level": 4, + "references": "" + }, + "r04": { + "level": 4, + "references": "" + }, + "r05": { + "level": 4, + "references": "" + }, + "r06": { + "level": 4, + "references": "" + }, + "r07": { + "level": 4, + "references": "" + }, + "r08": { + "level": 4, + "references": "" + }, + "r09": { + "level": 4, + "references": "" + }, + "r10": { + "level": 4, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + }, + "tsr_current": { + "r01": { + "level": 0, + "references": "" + }, + "r02": { + "level": 0, + "references": "" + }, + "r03": { + "level": 0, + "references": "" + }, + "r04": { + "level": 0, + "references": "" + }, + "r05": { + "level": 0, + "references": "" + }, + "r06": { + "level": 0, + "references": "" + }, + "r07": { + "level": 0, + "references": "" + }, + "r08": { + "level": 0, + "references": "" + }, + "r09": { + "level": 0, + "references": "" + }, + "r10": { + "level": 0, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + } + }, + "ui": { + "workbench": { + "982c5607-783c-5295-9a4f-26c8e3c21211": { + "position": { + "x": 250, + "y": 100 + } + } + }, + "slideshow": {}, + "currentNodeId": "b00f9b90-4857-11f0-bc8c-0242ac140019", + "mode": "pipeline" + }, + "dev": {}, + "workspaceId": null, + "folderId": null + } + }, + "status_code": 201 + }, + { + "name": "PATCH /projects/240cb4c6-485b-11f0-89d1-0242ac14050c", + "description": "", + "method": "PATCH", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "request_payload": { + "name": "studies/b8dd6dc4-4857-11f0-bc8c-0242ac140019/jobs/240cb4c6-485b-11f0-89d1-0242ac14050c" + }, + "status_code": 204 + }, + { + "name": "GET /projects/240cb4c6-485b-11f0-89d1-0242ac14050c/inputs", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}/inputs", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "response_body": { + "data": {} + } + }, + { + "name": "POST /computations/240cb4c6-485b-11f0-89d1-0242ac14050c:start", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/computations/{project_id}:start", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "request_payload": {}, + "response_body": { + "data": { + "pipeline_id": "240cb4c6-485b-11f0-89d1-0242ac14050c" + } + }, + "status_code": 201 + }, + { + "name": "GET /v2/computations/240cb4c6-485b-11f0-89d1-0242ac14050c", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v2/computations/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "query": "user_id=1", + "response_body": { + "id": "240cb4c6-485b-11f0-89d1-0242ac14050c", + "state": "STARTED", + "result": null, + "pipeline_details": { + "adjacency_list": { + "982c5607-783c-5295-9a4f-26c8e3c21211": [] + }, + "progress": 0.05, + "node_states": { + "982c5607-783c-5295-9a4f-26c8e3c21211": { + "modified": true, + "dependencies": [], + "currentStatus": "STARTED", + "progress": 0.05 + } + } + }, + "iteration": 1, + "started": "2025-06-13T13:34:40.872636Z", + "stopped": null, + "submitted": "2025-06-13T13:34:40.690820Z", + "url": "http://webserver:8080:30003/v2/computations/240cb4c6-485b-11f0-89d1-0242ac14050c?user_id=1", + "stop_url": "http://webserver:8080:30003/v2/computations/240cb4c6-485b-11f0-89d1-0242ac14050c:stop?user_id=1" + } + }, + { + "name": "POST /projects", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/projects", + "path_parameters": [] + }, + "query": "from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "response_body": { + "data": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b", + "task_name": "POST /v0/projects?from_study=b8dd6dc4-4857-11f0-bc8c-0242ac140019&hidden=true", + "status_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b", + "result_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b/result", + "abort_href": "http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b" + } + }, + "status_code": 202 + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "task_progress": { + "task_id": "POST%20%2Fv0%2Fprojects%3Ffrom_study%3Db8dd6dc4-4857-11f0-bc8c-0242ac140019%26hidden%3Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b", + "message": "finished", + "percent": 1.0 + }, + "done": true, + "started": "2025-06-13T13:48:21.407753+00:00" + }, + "error": null + } + }, + { + "name": "GET http://webserver:8080/v0/tasks-legacy/POST%2520%252Fv0%252Fprojects%253Ffrom_study%253Db8dd6dc4-4857-11f0-bc8c-0242ac140019%2526hidden%253Dtrue.e43e3bf5-5910-42ad-8abe-636f7aa87d7b/result", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/tasks-legacy/{task_id}/result", + "path_parameters": [ + { + "in": "path", + "name": "task_id", + "required": true, + "schema": { + "title": "Task Id", + "type": "str" + }, + "response_value": "tasks-legacy" + } + ] + }, + "response_body": { + "data": { + "uuid": "11ae9478-485d-11f0-bc8c-0242ac140019", + "name": "sleeper (Copy)", + "description": "", + "thumbnail": "", + "type": "STANDARD", + "templateType": null, + "workbench": { + "4a7d1258-dcfa-55ab-ab53-38d0fa99431a": { + "key": "simcore/services/comp/itis/sleeper", + "version": "2.2.1", + "label": "sleeper", + "progress": 0.0, + "inputs": { + "input_2": 2, + "input_3": false, + "input_4": 0, + "input_5": 0 + }, + "inputsRequired": [], + "inputNodes": [], + "state": { + "modified": true, + "dependencies": [], + "currentStatus": "NOT_STARTED", + "progress": null + } + } + }, + "prjOwner": "bisgaard@itis.swiss", + "accessRights": { + "4": { + "read": true, + "write": true, + "delete": true + } + }, + "creationDate": "2025-06-13T13:48:21.431Z", + "lastChangeDate": "2025-06-13T13:48:21.431Z", + "state": { + "locked": { + "value": false, + "status": "CLOSED" + }, + "state": { + "value": "NOT_STARTED" + } + }, + "trashedAt": null, + "trashedBy": null, + "tags": [], + "classifiers": [], + "quality": { + "enabled": true, + "tsr_target": { + "r01": { + "level": 4, + "references": "" + }, + "r02": { + "level": 4, + "references": "" + }, + "r03": { + "level": 4, + "references": "" + }, + "r04": { + "level": 4, + "references": "" + }, + "r05": { + "level": 4, + "references": "" + }, + "r06": { + "level": 4, + "references": "" + }, + "r07": { + "level": 4, + "references": "" + }, + "r08": { + "level": 4, + "references": "" + }, + "r09": { + "level": 4, + "references": "" + }, + "r10": { + "level": 4, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + }, + "tsr_current": { + "r01": { + "level": 0, + "references": "" + }, + "r02": { + "level": 0, + "references": "" + }, + "r03": { + "level": 0, + "references": "" + }, + "r04": { + "level": 0, + "references": "" + }, + "r05": { + "level": 0, + "references": "" + }, + "r06": { + "level": 0, + "references": "" + }, + "r07": { + "level": 0, + "references": "" + }, + "r08": { + "level": 0, + "references": "" + }, + "r09": { + "level": 0, + "references": "" + }, + "r10": { + "level": 0, + "references": "" + }, + "r03b": { + "references": "" + }, + "r03c": { + "references": "" + }, + "r07b": { + "references": "" + }, + "r07c": { + "references": "" + }, + "r07d": { + "references": "" + }, + "r07e": { + "references": "" + }, + "r08b": { + "references": "" + }, + "r10b": { + "references": "" + } + } + }, + "ui": { + "workbench": { + "4a7d1258-dcfa-55ab-ab53-38d0fa99431a": { + "position": { + "x": 250, + "y": 100 + } + } + }, + "slideshow": {}, + "currentNodeId": "b00f9b90-4857-11f0-bc8c-0242ac140019", + "mode": "pipeline" + }, + "dev": {}, + "workspaceId": null, + "folderId": null + } + }, + "status_code": 201 + }, + { + "name": "PATCH /projects/11ae9478-485d-11f0-bc8c-0242ac140019", + "description": "", + "method": "PATCH", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "request_payload": { + "name": "studies/b8dd6dc4-4857-11f0-bc8c-0242ac140019/jobs/11ae9478-485d-11f0-bc8c-0242ac140019" + }, + "status_code": 204 + }, + { + "name": "GET /projects/11ae9478-485d-11f0-bc8c-0242ac140019/inputs", + "description": "", + "method": "GET", + "host": "webserver", + "path": { + "path": "/v0/projects/{project_id}/inputs", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "projects" + } + ] + }, + "response_body": { + "data": {} + } + }, + { + "name": "POST /computations/11ae9478-485d-11f0-bc8c-0242ac140019:start", + "description": "", + "method": "POST", + "host": "webserver", + "path": { + "path": "/v0/computations/{project_id}:start", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "request_payload": {}, + "response_body": { + "data": { + "pipeline_id": "11ae9478-485d-11f0-bc8c-0242ac140019" + } + }, + "status_code": 201 + }, + { + "name": "GET /v2/computations/11ae9478-485d-11f0-bc8c-0242ac140019", + "description": "", + "method": "GET", + "host": "director-v2", + "path": { + "path": "/v2/computations/{project_id}", + "path_parameters": [ + { + "in": "path", + "name": "project_id", + "required": true, + "schema": { + "title": "Project Id", + "type": "str", + "format": "uuid" + }, + "response_value": "computations" + } + ] + }, + "query": "user_id=1", + "response_body": { + "id": "11ae9478-485d-11f0-bc8c-0242ac140019", + "state": "STARTED", + "result": null, + "pipeline_details": { + "adjacency_list": { + "4a7d1258-dcfa-55ab-ab53-38d0fa99431a": [] + }, + "progress": 0.05, + "node_states": { + "4a7d1258-dcfa-55ab-ab53-38d0fa99431a": { + "modified": true, + "dependencies": [], + "currentStatus": "STARTED", + "progress": 0.05 + } + } + }, + "iteration": 1, + "started": "2025-06-13T13:48:28.876507Z", + "stopped": null, + "submitted": "2025-06-13T13:48:28.689344Z", + "url": "http://director-v2:8000/v2/computations/11ae9478-485d-11f0-bc8c-0242ac140019?user_id=1", + "stop_url": "http://director-v2:8000/v2/computations/11ae9478-485d-11f0-bc8c-0242ac140019:stop?user_id=1" + } + } +] diff --git a/services/api-server/tests/unit/api_functions/conftest.py b/services/api-server/tests/unit/api_functions/conftest.py index 891fdc533f47..1df264f0e397 100644 --- a/services/api-server/tests/unit/api_functions/conftest.py +++ b/services/api-server/tests/unit/api_functions/conftest.py @@ -26,7 +26,10 @@ RegisteredProjectFunction, RegisteredProjectFunctionJob, ) -from models_library.functions import RegisteredFunctionJobCollection +from models_library.functions import ( + RegisteredFunctionJobCollection, + RegisteredSolverFunction, +) from models_library.functions_errors import FunctionIDNotFoundError from models_library.projects import ProjectID from pytest_mock import MockerFixture @@ -119,7 +122,7 @@ def mock_function( @pytest.fixture -def mock_registered_function(mock_function: Function) -> RegisteredFunction: +def mock_registered_project_function(mock_function: Function) -> RegisteredFunction: return RegisteredProjectFunction( **{ **mock_function.dict(), @@ -130,9 +133,33 @@ def mock_registered_function(mock_function: Function) -> RegisteredFunction: @pytest.fixture -def mock_function_job(mock_registered_function: RegisteredFunction) -> FunctionJob: +def mock_registered_solver_function( + mock_function: Function, + sample_input_schema: JSONFunctionInputSchema, + sample_output_schema: JSONFunctionOutputSchema, +) -> RegisteredFunction: + return RegisteredSolverFunction( + **{ + "title": "test_function", + "function_class": FunctionClass.SOLVER, + "description": "A test function", + "input_schema": sample_input_schema, + "output_schema": sample_output_schema, + "default_inputs": None, + "uid": str(uuid4()), + "created_at": datetime.datetime.now(datetime.UTC), + "solver_key": "simcore/services/comp/ans-model", + "solver_version": "1.0.1", + } + ) + + +@pytest.fixture +def mock_function_job( + mock_registered_project_function: RegisteredFunction, +) -> FunctionJob: mock_function_job = { - "function_uid": mock_registered_function.uid, + "function_uid": mock_registered_project_function.uid, "title": "Test Function Job", "description": "A test function job", "inputs": {"key": "value"}, diff --git a/services/api-server/tests/unit/api_functions/test_api_routers_functions.py b/services/api-server/tests/unit/api_functions/test_api_routers_functions.py index b2f0ace7e0bb..98acc73352d1 100644 --- a/services/api-server/tests/unit/api_functions/test_api_routers_functions.py +++ b/services/api-server/tests/unit/api_functions/test_api_routers_functions.py @@ -1,15 +1,20 @@ # pylint: disable=unused-argument +# pylint: disable=too-many-arguments +# pylint: disable=too-many-positional-arguments # pylint: disable=redefined-outer-name import datetime from collections.abc import Callable +from functools import partial +from pathlib import Path from typing import Any from unittest.mock import MagicMock -from uuid import uuid4 +from uuid import UUID, uuid4 import httpx import pytest import respx +from faker import Faker from httpx import AsyncClient from models_library.api_schemas_webserver.functions import ( FunctionJobCollection, @@ -22,6 +27,7 @@ from models_library.functions import ( FunctionUserAccessRights, FunctionUserApiAccessRights, + RegisteredFunctionJob, ) from models_library.functions_errors import ( FunctionIDNotFoundError, @@ -30,19 +36,26 @@ from models_library.rest_pagination import PageMetaInfoLimitOffset from models_library.users import UserID from pytest_mock import MockType +from pytest_simcore.helpers.httpx_calls_capture_models import HttpApiCallCaptureModel from servicelib.aiohttp import status +from servicelib.common_headers import ( + X_SIMCORE_PARENT_NODE_ID, + X_SIMCORE_PARENT_PROJECT_UUID, +) from simcore_service_api_server._meta import API_VTAG +_faker = Faker() + async def test_register_function( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], mock_function: ProjectFunction, auth: httpx.BasicAuth, - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, ) -> None: mock_handler_in_functions_rpc_interface( - "register_function", mock_registered_function + "register_function", mock_registered_project_function ) response = await client.post( f"{API_VTAG}/functions", json=mock_function.model_dump(mode="json"), auth=auth @@ -51,7 +64,7 @@ async def test_register_function( data = response.json() returned_function = RegisteredProjectFunction.model_validate(data) assert returned_function.uid is not None - assert returned_function == mock_registered_function + assert returned_function == mock_registered_project_function async def test_register_function_invalid( @@ -77,16 +90,18 @@ async def test_register_function_invalid( async def test_get_function( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: function_id = str(uuid4()) - mock_handler_in_functions_rpc_interface("get_function", mock_registered_function) + mock_handler_in_functions_rpc_interface( + "get_function", mock_registered_project_function + ) response = await client.get(f"{API_VTAG}/functions/{function_id}", auth=auth) assert response.status_code == status.HTTP_200_OK returned_function = RegisteredProjectFunction.model_validate(response.json()) - assert returned_function == mock_registered_function + assert returned_function == mock_registered_project_function async def test_get_function_not_found( @@ -114,7 +129,7 @@ async def test_get_function_read_access_denied( mock_handler_in_functions_rpc_interface: Callable[ [str, Any, Exception | None], None ], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: unauthorized_user_id = "unauthorized user" @@ -122,29 +137,30 @@ async def test_get_function_read_access_denied( "get_function", None, FunctionReadAccessDeniedError( - function_id=mock_registered_function.uid, user_id=unauthorized_user_id + function_id=mock_registered_project_function.uid, + user_id=unauthorized_user_id, ), ) response = await client.get( - f"{API_VTAG}/functions/{mock_registered_function.uid}", auth=auth + f"{API_VTAG}/functions/{mock_registered_project_function.uid}", auth=auth ) assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()["errors"][0] == ( - f"Function {mock_registered_function.uid} read access denied for user {unauthorized_user_id}" + f"Function {mock_registered_project_function.uid} read access denied for user {unauthorized_user_id}" ) async def test_list_functions( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: mock_handler_in_functions_rpc_interface( "list_functions", ( - [mock_registered_function for _ in range(5)], + [mock_registered_project_function for _ in range(5)], PageMetaInfoLimitOffset(total=5, count=5, limit=10, offset=0), ), ) @@ -155,13 +171,13 @@ async def test_list_functions( assert response.status_code == status.HTTP_200_OK data = response.json()["items"] assert len(data) == 5 - assert data[0]["title"] == mock_registered_function.title + assert data[0]["title"] == mock_registered_project_function.title async def test_update_function_title( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: @@ -169,7 +185,7 @@ async def test_update_function_title( "update_function_title", RegisteredProjectFunction( **{ - **mock_registered_function.model_dump(), + **mock_registered_project_function.model_dump(), "title": "updated_example_function", } ), @@ -178,7 +194,7 @@ async def test_update_function_title( # Update the function title updated_title = {"title": "updated_example_function"} response = await client.patch( - f"{API_VTAG}/functions/{mock_registered_function.uid}/title", + f"{API_VTAG}/functions/{mock_registered_project_function.uid}/title", params=updated_title, auth=auth, ) @@ -190,14 +206,14 @@ async def test_update_function_title( async def test_update_function_description( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: mock_handler_in_functions_rpc_interface( "update_function_description", RegisteredProjectFunction( **{ - **mock_registered_function.model_dump(), + **mock_registered_project_function.model_dump(), "description": "updated_example_function", } ), @@ -206,7 +222,7 @@ async def test_update_function_description( # Update the function description updated_description = {"description": "updated_example_function"} response = await client.patch( - f"{API_VTAG}/functions/{mock_registered_function.uid}/description", + f"{API_VTAG}/functions/{mock_registered_project_function.uid}/description", params=updated_description, auth=auth, ) @@ -218,56 +234,66 @@ async def test_update_function_description( async def test_get_function_input_schema( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: - mock_handler_in_functions_rpc_interface("get_function", mock_registered_function) + mock_handler_in_functions_rpc_interface( + "get_function", mock_registered_project_function + ) response = await client.get( - f"{API_VTAG}/functions/{mock_registered_function.uid}/input_schema", auth=auth + f"{API_VTAG}/functions/{mock_registered_project_function.uid}/input_schema", + auth=auth, ) assert response.status_code == status.HTTP_200_OK data = response.json() assert ( - data["schema_content"] == mock_registered_function.input_schema.schema_content + data["schema_content"] + == mock_registered_project_function.input_schema.schema_content ) async def test_get_function_output_schema( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: - mock_handler_in_functions_rpc_interface("get_function", mock_registered_function) + mock_handler_in_functions_rpc_interface( + "get_function", mock_registered_project_function + ) response = await client.get( - f"{API_VTAG}/functions/{mock_registered_function.uid}/output_schema", auth=auth + f"{API_VTAG}/functions/{mock_registered_project_function.uid}/output_schema", + auth=auth, ) assert response.status_code == status.HTTP_200_OK data = response.json() assert ( - data["schema_content"] == mock_registered_function.output_schema.schema_content + data["schema_content"] + == mock_registered_project_function.output_schema.schema_content ) async def test_validate_function_inputs( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: - mock_handler_in_functions_rpc_interface("get_function", mock_registered_function) + mock_handler_in_functions_rpc_interface( + "get_function", mock_registered_project_function + ) # Validate inputs validate_payload = {"input1": 10} response = await client.post( - f"{API_VTAG}/functions/{mock_registered_function.uid}:validate_inputs", + f"{API_VTAG}/functions/{mock_registered_project_function.uid}:validate_inputs", json=validate_payload, auth=auth, ) @@ -279,14 +305,14 @@ async def test_validate_function_inputs( async def test_delete_function( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: mock_handler_in_functions_rpc_interface("delete_function", None) # Delete the function response = await client.delete( - f"{API_VTAG}/functions/{mock_registered_function.uid}", auth=auth + f"{API_VTAG}/functions/{mock_registered_project_function.uid}", auth=auth ) assert response.status_code == status.HTTP_200_OK @@ -369,7 +395,7 @@ async def test_list_function_jobs_with_function_filter( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], mock_registered_function_job: RegisteredProjectFunctionJob, - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: @@ -383,7 +409,7 @@ async def test_list_function_jobs_with_function_filter( # Now, list function jobs with a filter response = await client.get( - f"{API_VTAG}/functions/{mock_registered_function.uid}/jobs", auth=auth + f"{API_VTAG}/functions/{mock_registered_project_function.uid}/jobs", auth=auth ) assert response.status_code == status.HTTP_200_OK @@ -562,7 +588,7 @@ async def test_list_function_job_collections_with_function_filter( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], mock_registered_function_job_collection: RegisteredFunctionJobCollection, - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, ) -> None: @@ -575,7 +601,7 @@ async def test_list_function_job_collections_with_function_filter( ) response = await client.get( - f"{API_VTAG}/function_job_collections?function_id={mock_registered_function.uid}&limit=2&offset=1", + f"{API_VTAG}/function_job_collections?function_id={mock_registered_project_function.uid}&limit=2&offset=1", auth=auth, ) assert response.status_code == status.HTTP_200_OK @@ -598,7 +624,7 @@ async def test_list_function_job_collections_with_function_filter( async def test_run_map_function_not_allowed( client: AsyncClient, mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], - mock_registered_function: RegisteredProjectFunction, + mock_registered_project_function: RegisteredProjectFunction, auth: httpx.BasicAuth, user_id: UserID, mocked_webserver_rest_api_base: respx.MockRouter, @@ -635,17 +661,221 @@ async def async_magic(): MagicMock.__await__ = lambda _: async_magic().__await__() response = await client.post( - f"{API_VTAG}/functions/{mock_registered_function.uid}:{funcapi_endpoint}", + f"{API_VTAG}/functions/{mock_registered_project_function.uid}:{funcapi_endpoint}", json=endpoint_inputs, auth=auth, + headers={ + X_SIMCORE_PARENT_PROJECT_UUID: "null", + X_SIMCORE_PARENT_NODE_ID: "null", + }, ) if user_has_execute_right: assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()["errors"][0] == ( - f"Function {mock_registered_function.uid} execute access denied for user {user_id}" + f"Function {mock_registered_project_function.uid} execute access denied for user {user_id}" ) else: assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()["errors"][0] == ( f"User {user_id} does not have the permission to execute functions" ) + + +@pytest.mark.parametrize( + "parent_project_uuid, parent_node_uuid, expected_status_code", + [ + (None, None, status.HTTP_422_UNPROCESSABLE_ENTITY), + (f"{_faker.uuid4()}", None, status.HTTP_422_UNPROCESSABLE_ENTITY), + (None, f"{_faker.uuid4()}", status.HTTP_422_UNPROCESSABLE_ENTITY), + (f"{_faker.uuid4()}", f"{_faker.uuid4()}", status.HTTP_200_OK), + ("null", "null", status.HTTP_200_OK), + ], +) +@pytest.mark.parametrize("capture", ["run_study_function_parent_info.json"]) +async def test_run_project_function_parent_info( + client: AsyncClient, + mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], + mock_registered_project_function: RegisteredProjectFunction, + mock_registered_function_job: RegisteredFunctionJob, + auth: httpx.BasicAuth, + user_id: UserID, + mocked_webserver_rest_api_base: respx.MockRouter, + mocked_directorv2_rest_api_base: respx.MockRouter, + mocked_webserver_rpc_api: dict[str, MockType], + create_respx_mock_from_capture, + project_tests_dir: Path, + parent_project_uuid: str | None, + parent_node_uuid: str | None, + expected_status_code: int, + capture: str, +) -> None: + + def _default_side_effect( + request: httpx.Request, + path_params: dict[str, Any], + capture: HttpApiCallCaptureModel, + ) -> Any: + if request.method == "POST" and request.url.path.endswith("/projects"): + if parent_project_uuid and parent_project_uuid != "null": + _parent_uuid = request.headers.get(X_SIMCORE_PARENT_PROJECT_UUID) + assert _parent_uuid is not None + assert parent_project_uuid == _parent_uuid + if parent_node_uuid and parent_node_uuid != "null": + _parent_node_uuid = request.headers.get(X_SIMCORE_PARENT_NODE_ID) + assert _parent_node_uuid is not None + assert parent_node_uuid == _parent_node_uuid + return capture.response_body + + create_respx_mock_from_capture( + respx_mocks=[mocked_webserver_rest_api_base, mocked_directorv2_rest_api_base], + capture_path=project_tests_dir / "mocks" / capture, + side_effects_callbacks=[_default_side_effect] * 50, + ) + + mock_handler_in_functions_rpc_interface( + "get_function_user_permissions", + FunctionUserAccessRights( + user_id=user_id, + execute=True, + read=True, + write=True, + ), + ) + mock_handler_in_functions_rpc_interface( + "get_function", mock_registered_project_function + ) + mock_handler_in_functions_rpc_interface("find_cached_function_jobs", []) + mock_handler_in_functions_rpc_interface( + "register_function_job", mock_registered_function_job + ) + mock_handler_in_functions_rpc_interface( + "get_functions_user_api_access_rights", + FunctionUserApiAccessRights( + user_id=user_id, + execute_functions=True, + write_functions=True, + read_functions=True, + ), + ) + + headers = dict() + if parent_project_uuid: + headers[X_SIMCORE_PARENT_PROJECT_UUID] = parent_project_uuid + if parent_node_uuid: + headers[X_SIMCORE_PARENT_NODE_ID] = parent_node_uuid + + response = await client.post( + f"{API_VTAG}/functions/{mock_registered_project_function.uid}:run", + json={}, + auth=auth, + headers=headers, + ) + assert response.status_code == expected_status_code + + +@pytest.mark.parametrize( + "parent_project_uuid, parent_node_uuid, expected_status_code", + [ + (None, None, status.HTTP_422_UNPROCESSABLE_ENTITY), + (f"{_faker.uuid4()}", None, status.HTTP_422_UNPROCESSABLE_ENTITY), + (None, f"{_faker.uuid4()}", status.HTTP_422_UNPROCESSABLE_ENTITY), + (f"{_faker.uuid4()}", f"{_faker.uuid4()}", status.HTTP_200_OK), + ("null", "null", status.HTTP_200_OK), + ], +) +@pytest.mark.parametrize("capture", ["run_study_function_parent_info.json"]) +async def test_map_function_parent_info( + client: AsyncClient, + mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], + mock_registered_project_function: RegisteredProjectFunction, + mock_registered_function_job: RegisteredFunctionJob, + auth: httpx.BasicAuth, + user_id: UserID, + mocked_webserver_rest_api_base: respx.MockRouter, + mocked_directorv2_rest_api_base: respx.MockRouter, + mocked_webserver_rpc_api: dict[str, MockType], + create_respx_mock_from_capture, + project_tests_dir: Path, + parent_project_uuid: str | None, + parent_node_uuid: str | None, + expected_status_code: int, + capture: str, +) -> None: + + side_effect_checks = dict() + + def _default_side_effect( + side_effect_checks: dict, + request: httpx.Request, + path_params: dict[str, Any], + capture: HttpApiCallCaptureModel, + ) -> Any: + if request.method == "POST" and request.url.path.endswith("/projects"): + side_effect_checks["headers_checked"] = True + if parent_project_uuid and parent_project_uuid != "null": + _parent_uuid = request.headers.get(X_SIMCORE_PARENT_PROJECT_UUID) + assert _parent_uuid is not None + assert parent_project_uuid == _parent_uuid + if parent_node_uuid and parent_node_uuid != "null": + _parent_node_uuid = request.headers.get(X_SIMCORE_PARENT_NODE_ID) + assert _parent_node_uuid is not None + assert parent_node_uuid == _parent_node_uuid + return capture.response_body + + create_respx_mock_from_capture( + respx_mocks=[mocked_webserver_rest_api_base, mocked_directorv2_rest_api_base], + capture_path=project_tests_dir / "mocks" / capture, + side_effects_callbacks=[partial(_default_side_effect, side_effect_checks)] * 50, + ) + + mock_handler_in_functions_rpc_interface( + "get_function_user_permissions", + FunctionUserAccessRights( + user_id=user_id, + execute=True, + read=True, + write=True, + ), + ) + mock_handler_in_functions_rpc_interface( + "get_function", mock_registered_project_function + ) + mock_handler_in_functions_rpc_interface("find_cached_function_jobs", []) + mock_handler_in_functions_rpc_interface( + "register_function_job", mock_registered_function_job + ) + mock_handler_in_functions_rpc_interface( + "get_functions_user_api_access_rights", + FunctionUserApiAccessRights( + user_id=user_id, + execute_functions=True, + write_functions=True, + read_functions=True, + ), + ) + mock_handler_in_functions_rpc_interface( + "register_function_job_collection", + RegisteredFunctionJobCollection( + uid=UUID(_faker.uuid4()), + title="Test Collection", + description="A test function job collection", + job_ids=[], + created_at=datetime.datetime.now(datetime.UTC), + ), + ) + + headers = dict() + if parent_project_uuid: + headers[X_SIMCORE_PARENT_PROJECT_UUID] = parent_project_uuid + if parent_node_uuid: + headers[X_SIMCORE_PARENT_NODE_ID] = parent_node_uuid + + response = await client.post( + f"{API_VTAG}/functions/{mock_registered_project_function.uid}:map", + json=[{}, {}], + auth=auth, + headers=headers, + ) + if expected_status_code == status.HTTP_200_OK: + assert side_effect_checks["headers_checked"] == True + assert response.status_code == expected_status_code