Skip to content

Commit 53ced29

Browse files
committed
add test for new parameters
1 parent ddc3e74 commit 53ced29

File tree

4 files changed

+169
-4
lines changed

4 files changed

+169
-4
lines changed

services/api-server/openapi.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4494,6 +4494,15 @@
44944494
}
44954495
}
44964496
],
4497+
"requestBody": {
4498+
"content": {
4499+
"application/json": {
4500+
"schema": {
4501+
"$ref": "#/components/schemas/Body_clone_study_v0_studies__study_id__clone_post"
4502+
}
4503+
}
4504+
}
4505+
},
44974506
"responses": {
44984507
"201": {
44994508
"description": "Successful Response",
@@ -7762,6 +7771,41 @@
77627771
],
77637772
"title": "Body_abort_multipart_upload_v0_files__file_id__abort_post"
77647773
},
7774+
"Body_clone_study_v0_studies__study_id__clone_post": {
7775+
"properties": {
7776+
"hidden": {
7777+
"type": "boolean",
7778+
"title": "Hidden",
7779+
"default": false
7780+
},
7781+
"title": {
7782+
"anyOf": [
7783+
{
7784+
"type": "string"
7785+
},
7786+
{
7787+
"type": "null"
7788+
}
7789+
],
7790+
"title": "Title",
7791+
"empty": true
7792+
},
7793+
"description": {
7794+
"anyOf": [
7795+
{
7796+
"type": "string"
7797+
},
7798+
{
7799+
"type": "null"
7800+
}
7801+
],
7802+
"title": "Description",
7803+
"empty": true
7804+
}
7805+
},
7806+
"type": "object",
7807+
"title": "Body_clone_study_v0_studies__study_id__clone_post"
7808+
},
77657809
"Body_complete_multipart_upload_v0_files__file_id__complete_post": {
77667810
"properties": {
77677811
"client_file": {

services/api-server/src/simcore_service_api_server/api/routes/studies.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import logging
22
from typing import Annotated, Final
33

4-
from fastapi import APIRouter, Depends, Header, status
4+
from fastapi import APIRouter, Body, Depends, Header, Query, status
55
from fastapi_pagination.api import create_page
6-
from models_library.api_schemas_webserver.projects import ProjectGet
6+
from models_library.api_schemas_webserver.projects import ProjectGet, ProjectPatch
7+
from models_library.basic_types import LongTruncatedStr, ShortTruncatedStr
78
from models_library.projects import ProjectID
89
from models_library.projects_nodes_io import NodeID
910

@@ -94,13 +95,24 @@ async def clone_study(
9495
webserver_api: Annotated[AuthSession, Depends(get_webserver_session)],
9596
x_simcore_parent_project_uuid: Annotated[ProjectID | None, Header()] = None,
9697
x_simcore_parent_node_id: Annotated[NodeID | None, Header()] = None,
98+
hidden: Annotated[bool, Query()] = False,
99+
title: Annotated[ShortTruncatedStr | None, Body(empty=True)] = None,
100+
description: Annotated[LongTruncatedStr | None, Body(empty=True)] = None,
97101
):
98102
project: ProjectGet = await webserver_api.clone_project(
99103
project_id=study_id,
100-
hidden=False,
104+
hidden=hidden,
101105
parent_project_uuid=x_simcore_parent_project_uuid,
102106
parent_node_id=x_simcore_parent_node_id,
103107
)
108+
if title or description:
109+
patch_params = ProjectPatch(
110+
name=title,
111+
description=description,
112+
)
113+
await webserver_api.patch_project(
114+
project_id=study_id, patch_params=patch_params
115+
)
104116
return _create_study_from_project(project)
105117

106118

services/api-server/tests/unit/api_studies/test_api_routes_studies.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# pylint: disable=unused-variable
44

55

6+
import json
67
from collections.abc import Callable
78
from pathlib import Path
89
from typing import Any, TypedDict
@@ -188,15 +189,102 @@ def clone_project_side_effect(request: httpx.Request):
188189
_headers[X_SIMCORE_PARENT_PROJECT_UUID] = f"{parent_project_id}"
189190
if parent_node_id is not None:
190191
_headers[X_SIMCORE_PARENT_NODE_ID] = f"{parent_node_id}"
192+
191193
resp = await client.post(
192-
f"/{API_VTAG}/studies/{study_id}:clone", headers=_headers, auth=auth
194+
f"/{API_VTAG}/studies/{study_id}:clone", headers=_headers, auth=auth, json=body
193195
)
194196

195197
assert mocked_webserver_rest_api_base["create_projects"].called
196198

197199
assert resp.status_code == status.HTTP_201_CREATED
198200

199201

202+
# string length limits: https://github.com/ITISFoundation/osparc-simcore/blob/master/packages/models-library/src/models_library/api_schemas_webserver/projects.py#L242
203+
@pytest.mark.parametrize("hidden", [True, False, None])
204+
@pytest.mark.parametrize(
205+
"title, description, expected_status_code",
206+
[
207+
(
208+
_faker.text(max_nb_chars=600),
209+
_faker.text(max_nb_chars=65536),
210+
status.HTTP_201_CREATED,
211+
),
212+
("a" * 999, "b" * 99999, status.HTTP_201_CREATED),
213+
(None, None, status.HTTP_201_CREATED),
214+
],
215+
)
216+
async def test_clone_study_with_title(
217+
client: httpx.AsyncClient,
218+
auth: httpx.BasicAuth,
219+
study_id: StudyID,
220+
mocked_webserver_rest_api_base: MockRouter,
221+
patch_webserver_long_running_project_tasks: Callable[[MockRouter], MockRouter],
222+
mock_webserver_patch_project: Callable[
223+
[
224+
MockRouter,
225+
],
226+
MockRouter,
227+
],
228+
hidden: bool | None,
229+
title: str | None,
230+
description: str | None,
231+
expected_status_code: int,
232+
):
233+
# Mocks /projects
234+
patch_webserver_long_running_project_tasks(mocked_webserver_rest_api_base)
235+
mock_webserver_patch_project(mocked_webserver_rest_api_base)
236+
237+
create_callback = mocked_webserver_rest_api_base["create_projects"].side_effect
238+
assert create_callback is not None
239+
patch_callback = mocked_webserver_rest_api_base["project_patch"].side_effect
240+
assert patch_callback is not None
241+
242+
def clone_project_side_effect(request: httpx.Request):
243+
if hidden is not None:
244+
_hidden = request.url.params.get("hidden")
245+
assert _hidden == str(hidden).lower()
246+
return create_callback(request)
247+
248+
def patch_project_side_effect(request: httpx.Request, *args, **kwargs):
249+
body = json.loads(request.content.decode("utf-8"))
250+
if title is not None:
251+
_name = body.get("name")
252+
assert _name is not None and _name in title
253+
if description is not None:
254+
_description = body.get("description")
255+
assert _description is not None and _description in description
256+
return patch_callback(request, *args, **kwargs)
257+
258+
mocked_webserver_rest_api_base["create_projects"].side_effect = (
259+
clone_project_side_effect
260+
)
261+
mocked_webserver_rest_api_base["project_patch"].side_effect = (
262+
patch_project_side_effect
263+
)
264+
265+
query = dict()
266+
if hidden is not None:
267+
query["hidden"] = str(hidden).lower()
268+
269+
body = dict()
270+
if hidden is not None:
271+
body["hidden"] = hidden
272+
if title is not None:
273+
body["title"] = title
274+
if description is not None:
275+
body["description"] = description
276+
277+
resp = await client.post(
278+
f"/{API_VTAG}/studies/{study_id}:clone", auth=auth, json=body, params=query
279+
)
280+
281+
assert mocked_webserver_rest_api_base["create_projects"].called
282+
if title or description:
283+
assert mocked_webserver_rest_api_base["project_patch"].called
284+
285+
assert resp.status_code == expected_status_code
286+
287+
200288
async def test_clone_study_not_found(
201289
client: httpx.AsyncClient,
202290
auth: httpx.BasicAuth,

services/api-server/tests/unit/conftest.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,27 @@ def _mock(webserver_mock_router: MockRouter) -> MockRouter:
715715
return _mock
716716

717717

718+
@pytest.fixture
719+
def mock_webserver_patch_project(
720+
app: FastAPI, faker: Faker, services_mocks_enabled: bool
721+
) -> Callable[[MockRouter], MockRouter]:
722+
settings: ApplicationSettings = app.state.settings
723+
assert settings.API_SERVER_WEBSERVER is not None
724+
725+
def _mock(webserver_mock_router: MockRouter) -> MockRouter:
726+
def _patch_project(request: httpx.Request, *args, **kwargs):
727+
return httpx.Response(status.HTTP_200_OK)
728+
729+
if services_mocks_enabled:
730+
webserver_mock_router.patch(
731+
path__regex=r"/projects/(?P<project_id>[\w-]+)$",
732+
name="project_patch",
733+
).mock(side_effect=_patch_project)
734+
return webserver_mock_router
735+
736+
return _mock
737+
738+
718739
@pytest.fixture
719740
def openapi_dev_specs(project_slug_dir: Path) -> dict[str, Any]:
720741
openapi_file = (project_slug_dir / "openapi-dev.json").resolve()

0 commit comments

Comments
 (0)