Skip to content

Commit dc092c2

Browse files
adding unit tests
1 parent f9c7c0b commit dc092c2

File tree

6 files changed

+310
-9
lines changed

6 files changed

+310
-9
lines changed

services/web/server/src/simcore_service_webserver/folders/_folders_db.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ async def update(
317317
trashed_at: datetime | None | UnSet = _unset,
318318
trashed_explicitly: bool | UnSet = _unset,
319319
workspace_id: WorkspaceID | None | UnSet = _unset,
320+
user_id: UserID | None | UnSet = _unset,
320321
) -> FolderDB:
321322
"""
322323
Batch/single patch of folder/s
@@ -328,6 +329,7 @@ async def update(
328329
trashed_at=trashed_at,
329330
trashed_explicitly=trashed_explicitly,
330331
workspace_id=workspace_id,
332+
user_id=user_id,
331333
)
332334

333335
query = (

services/web/server/src/simcore_service_webserver/folders/_workspaces_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ async def move_folder_into_workspace(
8989
folders_id_or_ids=set(folder_ids),
9090
product_name=product_name,
9191
workspace_id=workspace_id, # <-- Updating workspace_id
92+
user_id=user_id if workspace_id is None else None, # <-- Updating user_id
9293
)
9394

9495
# 6. Update source folder parent folder ID with NULL (it will appear in the root directory)
@@ -114,7 +115,7 @@ async def move_folder_into_workspace(
114115
app,
115116
connection=None,
116117
folders_id_or_ids=set(folder_ids),
117-
user_id=user_id if workspace_id is None else None,
118+
user_id=user_id if workspace_id is None else None, # <-- Updating user_id
118119
)
119120

120121
# 9. Remove all project permissions, leave only the user who moved the project

services/web/server/src/simcore_service_webserver/folders/plugin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from servicelib.aiohttp.application_keys import APP_SETTINGS_KEY
88
from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup
99

10-
from . import _folders_handlers, _trash_handlers
10+
from . import _folders_handlers, _trash_handlers, _workspaces_handlers
1111

1212
_logger = logging.getLogger(__name__)
1313

@@ -25,3 +25,4 @@ def setup_folders(app: web.Application):
2525
# routes
2626
app.router.add_routes(_folders_handlers.routes)
2727
app.router.add_routes(_trash_handlers.routes)
28+
app.router.add_routes(_workspaces_handlers.routes)

services/web/server/src/simcore_service_webserver/projects/_folders_db.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def update_project_to_folder(
123123
connection: AsyncConnection | None = None,
124124
*,
125125
folders_id_or_ids: FolderID | set[FolderID],
126-
filter_by_user_id: UserID | None | UnSet = _unset,
126+
# filter_by_user_id: UserID | None | UnSet = _unset,
127127
# updatable columns
128128
user_id: UserID | None | UnSet = _unset,
129129
) -> None:
@@ -146,8 +146,8 @@ async def update_project_to_folder(
146146
# single-update
147147
query = query.where(projects_to_folders.c.folder_id == folders_id_or_ids)
148148

149-
if not isinstance(filter_by_user_id, UnSet):
150-
query = query.where(projects_to_folders.c.user_id == filter_by_user_id)
149+
# if not isinstance(filter_by_user_id, UnSet):
150+
# query = query.where(projects_to_folders.c.user_id == filter_by_user_id)
151151

152152
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
153153
await conn.stream(query)
@@ -166,7 +166,9 @@ async def delete_all_project_to_folder_by_project_ids_not_in_folder_ids(
166166
if isinstance(project_id_or_ids, set):
167167
# batch-delete
168168
query = query.where(
169-
projects_to_folders.c.project_uuid.in_(list(project_id_or_ids))
169+
projects_to_folders.c.project_uuid.in_(
170+
[f"{project_id}" for project_id in project_id_or_ids]
171+
)
170172
)
171173
else:
172174
# single-delete
@@ -175,9 +177,7 @@ async def delete_all_project_to_folder_by_project_ids_not_in_folder_ids(
175177
)
176178

177179
query = query.where(
178-
projects_to_folders.c.folder_id.not_in( # <-- NOT IN!
179-
[f"{folder_id}" for folder_id in not_in_folder_ids]
180-
)
180+
projects_to_folders.c.folder_id.not_in(not_in_folder_ids) # <-- NOT IN!
181181
)
182182

183183
async with transaction_context(get_asyncpg_engine(app), connection) as conn:

services/web/server/tests/unit/with_dbs/04/workspaces/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77
import sqlalchemy as sa
8+
from simcore_postgres_database.models.projects import projects
89
from simcore_postgres_database.models.workspaces import workspaces
910

1011

@@ -13,3 +14,4 @@ def workspaces_clean_db(postgres_db: sa.engine.Engine) -> Iterator[None]:
1314
with postgres_db.connect() as con:
1415
yield
1516
con.execute(workspaces.delete())
17+
con.execute(projects.delete())
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
# pylint: disable=too-many-statements
6+
7+
8+
from copy import deepcopy
9+
from http import HTTPStatus
10+
from http.client import NO_CONTENT
11+
12+
import pytest
13+
from aiohttp.test_utils import TestClient
14+
from pytest_mock import MockerFixture
15+
from pytest_simcore.helpers.assert_checks import assert_status
16+
from pytest_simcore.helpers.webserver_login import UserInfoDict
17+
from pytest_simcore.helpers.webserver_projects import create_project
18+
from servicelib.aiohttp import status
19+
from simcore_service_webserver.db.models import UserRole
20+
from simcore_service_webserver.projects.models import ProjectDict
21+
22+
23+
@pytest.fixture
24+
def mock_catalog_api_get_services_for_user_in_product(mocker: MockerFixture):
25+
mocker.patch(
26+
"simcore_service_webserver.projects._crud_api_read.get_services_for_user_in_product",
27+
spec=True,
28+
return_value=[],
29+
)
30+
mocker.patch(
31+
"simcore_service_webserver.projects._crud_handlers.get_services_for_user_in_product",
32+
spec=True,
33+
return_value=[],
34+
)
35+
mocker.patch(
36+
"simcore_service_webserver.projects._crud_handlers.project_uses_available_services",
37+
spec=True,
38+
return_value=True,
39+
)
40+
41+
42+
# @pytest.mark.parametrize(*standard_role_response(), ids=str)
43+
# async def test_moving_between_workspaces_user_role_permissions(
44+
# client: TestClient,
45+
# logged_user: UserInfoDict,
46+
# user_project: ProjectDict,
47+
# expected: ExpectedResponse,
48+
# mock_catalog_api_get_services_for_user_in_product: MockerFixture,
49+
# fake_project: ProjectDict,
50+
# workspaces_clean_db: None,
51+
# ):
52+
# # Move project from workspace to your private workspace
53+
# base_url = client.app.router["replace_folder_workspace"].url_for(
54+
# folder_id="1", workspace_id="null"
55+
# )
56+
# resp = await client.put(f"{base_url}")
57+
# await assert_status(resp, expected.no_content)
58+
59+
60+
## Usecases to test:
61+
# 1. Private workspace -> Shared workspace
62+
# 2. Shared workspace -> Shared workspace
63+
# 3. Shared workspace -> Private workspace
64+
65+
66+
async def _setup_test(
67+
client: TestClient,
68+
logged_user: UserInfoDict,
69+
fake_project: ProjectDict,
70+
) -> str:
71+
assert client.app
72+
73+
### Project creation
74+
75+
# Create 2 projects
76+
project_data = deepcopy(fake_project)
77+
first_project = await create_project(
78+
client.app,
79+
project_data,
80+
user_id=logged_user["id"],
81+
product_name="osparc",
82+
)
83+
second_project = await create_project(
84+
client.app,
85+
project_data,
86+
user_id=logged_user["id"],
87+
product_name="osparc",
88+
)
89+
90+
### Folder creation
91+
92+
# Create folder
93+
url = client.app.router["create_folder"].url_for()
94+
resp = await client.post(
95+
f"{url}",
96+
json={
97+
"name": "Original user folder",
98+
},
99+
)
100+
first_folder, _ = await assert_status(resp, status.HTTP_201_CREATED)
101+
102+
# Create sub folder of previous folder
103+
url = client.app.router["create_folder"].url_for()
104+
resp = await client.post(
105+
f"{url}",
106+
json={
107+
"name": "Second user folder",
108+
"parentFolderId": f"{first_folder['folderId']}",
109+
},
110+
)
111+
second_folder, _ = await assert_status(resp, status.HTTP_201_CREATED)
112+
113+
# Create sub sub folder of previous sub folder
114+
url = client.app.router["create_folder"].url_for()
115+
resp = await client.post(
116+
f"{url}",
117+
json={
118+
"name": "Third user folder",
119+
"parentFolderId": f"{second_folder['folderId']}",
120+
},
121+
)
122+
third_folder, _ = await assert_status(resp, status.HTTP_201_CREATED)
123+
124+
### Move projects to subfolder
125+
# add first project to the folder
126+
url = client.app.router["replace_project_folder"].url_for(
127+
folder_id=f"{second_folder['folderId']}", project_id=f"{first_project['uuid']}"
128+
)
129+
resp = await client.put(f"{url}")
130+
await assert_status(resp, status.HTTP_204_NO_CONTENT)
131+
# add second project to the folder
132+
url = client.app.router["replace_project_folder"].url_for(
133+
folder_id=f"{second_folder['folderId']}", project_id=f"{second_project['uuid']}"
134+
)
135+
resp = await client.put(f"{url}")
136+
await assert_status(resp, status.HTTP_204_NO_CONTENT)
137+
138+
## Double check whether everything is setup OK
139+
url = (
140+
client.app.router["list_projects"]
141+
.url_for()
142+
.with_query({"folder_id": f"{second_folder['folderId']}"})
143+
)
144+
resp = await client.get(f"{url}")
145+
data, _ = await assert_status(resp, status.HTTP_200_OK)
146+
assert len(data) == 2
147+
148+
url = (
149+
client.app.router["list_projects"]
150+
.url_for()
151+
.with_query({"folder_id": f"{first_folder['folderId']}"})
152+
)
153+
resp = await client.get(f"{url}")
154+
data, _ = await assert_status(resp, status.HTTP_200_OK)
155+
assert len(data) == 0
156+
157+
url = client.app.router["list_projects"].url_for().with_query({"folder_id": "null"})
158+
resp = await client.get(f"{url}")
159+
data, _ = await assert_status(resp, status.HTTP_200_OK)
160+
assert len(data) == 0
161+
162+
url = client.app.router["list_folders"].url_for().with_query({"folder_id": "null"})
163+
resp = await client.get(f"{url}")
164+
data, _ = await assert_status(resp, status.HTTP_200_OK)
165+
assert len(data) == 1
166+
167+
url = (
168+
client.app.router["list_folders"]
169+
.url_for()
170+
.with_query({"folder_id": f"{first_folder['folderId']}"})
171+
)
172+
resp = await client.get(f"{url}")
173+
data, _ = await assert_status(resp, status.HTTP_200_OK)
174+
assert len(data) == 1
175+
176+
return f"{second_folder['folderId']}"
177+
178+
179+
async def _move_folder_to_workspace_and_assert(
180+
client: TestClient, folder_id: str, workspace_id: str
181+
):
182+
assert client.app
183+
184+
# MOVE
185+
base_url = client.app.router["replace_folder_workspace"].url_for(
186+
folder_id=folder_id,
187+
workspace_id=workspace_id,
188+
)
189+
resp = await client.put(f"{base_url}")
190+
await assert_status(resp, NO_CONTENT)
191+
192+
# ASSERT
193+
url = (
194+
client.app.router["list_projects"]
195+
.url_for()
196+
.with_query(
197+
{
198+
"folder_id": folder_id,
199+
"workspace_id": workspace_id,
200+
}
201+
)
202+
)
203+
resp = await client.get(f"{url}")
204+
data, _ = await assert_status(resp, status.HTTP_200_OK)
205+
assert len(data) == 2
206+
207+
url = (
208+
client.app.router["list_folders"]
209+
.url_for()
210+
.with_query(
211+
{
212+
"folder_id": folder_id,
213+
"workspace_id": workspace_id,
214+
}
215+
)
216+
)
217+
resp = await client.get(f"{url}")
218+
data, _ = await assert_status(resp, status.HTTP_200_OK)
219+
assert len(data) == 1
220+
221+
222+
@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)])
223+
async def test_moving_between_private_and_shared_workspaces(
224+
client: TestClient,
225+
logged_user: UserInfoDict,
226+
# user_project: ProjectDict,
227+
expected: HTTPStatus,
228+
mock_catalog_api_get_services_for_user_in_product: MockerFixture,
229+
fake_project: ProjectDict,
230+
workspaces_clean_db: None,
231+
):
232+
assert client.app
233+
234+
moving_folder_id = await _setup_test(client, logged_user, fake_project)
235+
236+
# We will test these scenarios of moving folders:
237+
# 1. Private workspace -> Shared workspace
238+
# 2. Shared workspace A -> Shared workspace B
239+
# 3. Shared workspace A -> Shared workspace A (Corner case - This endpoint is not used like this)
240+
# 4. Shared workspace -> Private workspace
241+
# 5. Private workspace -> Private workspace (Corner case - This endpoint is not used like this)
242+
243+
# create a new workspace
244+
url = client.app.router["create_workspace"].url_for()
245+
resp = await client.post(
246+
url.path,
247+
json={
248+
"name": "A",
249+
"description": "A",
250+
"thumbnail": None,
251+
},
252+
)
253+
added_workspace, _ = await assert_status(resp, status.HTTP_201_CREATED)
254+
255+
# 1. Private workspace -> Shared workspace A
256+
await _move_folder_to_workspace_and_assert(
257+
client,
258+
folder_id=moving_folder_id,
259+
workspace_id=f"{added_workspace['workspaceId']}",
260+
)
261+
262+
# create a new workspace
263+
url = client.app.router["create_workspace"].url_for()
264+
resp = await client.post(
265+
url.path,
266+
json={
267+
"name": "B",
268+
"description": "B",
269+
"thumbnail": None,
270+
},
271+
)
272+
second_workspace, _ = await assert_status(resp, status.HTTP_201_CREATED)
273+
# 2. Shared workspace A -> Shared workspace B
274+
await _move_folder_to_workspace_and_assert(
275+
client,
276+
folder_id=moving_folder_id,
277+
workspace_id=f"{second_workspace['workspaceId']}",
278+
)
279+
280+
# 3. (Corner case) Shared workspace A -> Shared workspace A
281+
await _move_folder_to_workspace_and_assert(
282+
client,
283+
folder_id=moving_folder_id,
284+
workspace_id=f"{second_workspace['workspaceId']}",
285+
)
286+
287+
# 4. Shared workspace -> Private workspace
288+
await _move_folder_to_workspace_and_assert(
289+
client, folder_id=moving_folder_id, workspace_id="null"
290+
)
291+
292+
# 5. (Corner case) Private workspace -> Private workspace
293+
await _move_folder_to_workspace_and_assert(
294+
client, folder_id=moving_folder_id, workspace_id="null"
295+
)

0 commit comments

Comments
 (0)