Skip to content

Commit 6d27a71

Browse files
committed
acceptance test passes
1 parent 1874890 commit 6d27a71

File tree

3 files changed

+117
-43
lines changed

3 files changed

+117
-43
lines changed

packages/models-library/src/models_library/api_schemas_webserver/users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class UsersGetParams(RequestParameters):
203203
class UsersSearch(InputSchema):
204204
match_: Annotated[
205205
str,
206-
StringConstraints(strip_whitespace=True, min_length=1, max_length=50),
206+
StringConstraints(strip_whitespace=True, min_length=1, max_length=80),
207207
Field(
208208
description="Search string to match with public usernames and emails",
209209
alias="match",

services/web/server/src/simcore_service_webserver/users/_users_repository.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
UsersRepo,
3333
generate_alternative_username,
3434
)
35-
from sqlalchemy import delete
35+
from sqlalchemy import Column, delete
3636
from sqlalchemy.engine.row import Row
3737
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine
3838

@@ -52,6 +52,19 @@ def _parse_as_user(user_id: Any) -> UserID:
5252
raise UserNotFoundError(uid=user_id, user_id=user_id) from err
5353

5454

55+
#
56+
# Privacy settings
57+
#
58+
59+
60+
def _is_private(hide_attribute: Column, caller_id: UserID):
61+
return hide_attribute.is_(True) & (users.c.id != caller_id)
62+
63+
64+
def _is_public(hide_attribute: Column, caller_id: UserID):
65+
return hide_attribute.is_(False) | (users.c.id == caller_id)
66+
67+
5568
def _public_user_cols(caller_id: UserID):
5669
return (
5770
# Fits PublicUser model
@@ -60,21 +73,21 @@ def _public_user_cols(caller_id: UserID):
6073
# privacy settings
6174
sa.case(
6275
(
63-
users.c.privacy_hide_email.is_(True) & (users.c.id != caller_id),
76+
_is_private(users.c.privacy_hide_email, caller_id),
6477
None,
6578
),
6679
else_=users.c.email,
6780
).label("email"),
6881
sa.case(
6982
(
70-
users.c.privacy_hide_fullname.is_(True) & (users.c.id != caller_id),
83+
_is_private(users.c.privacy_hide_fullname, caller_id),
7184
None,
7285
),
7386
else_=users.c.first_name,
7487
).label("first_name"),
7588
sa.case(
7689
(
77-
users.c.privacy_hide_fullname.is_(True) & (users.c.id != caller_id),
90+
_is_private(users.c.privacy_hide_fullname, caller_id),
7891
None,
7992
),
8093
else_=users.c.last_name,
@@ -83,6 +96,11 @@ def _public_user_cols(caller_id: UserID):
8396
)
8497

8598

99+
#
100+
# PUBLIC User
101+
#
102+
103+
86104
async def get_public_user(
87105
engine: AsyncEngine,
88106
connection: AsyncConnection | None = None,
@@ -111,14 +129,16 @@ async def search_public_user(
111129
limit: int,
112130
) -> list:
113131

114-
pattern_ = f"%{search_pattern}%"
115-
is_public_email = users.c.privacy_hide_email.is_(False) | (users.c.id != caller_id)
132+
_pattern = f"%{search_pattern}%"
116133

117134
query = (
118135
sa.select(*_public_user_cols(caller_id=caller_id))
119136
.where(
120-
users.c.name.ilike(pattern_)
121-
| (is_public_email & users.c.users.c.email.ilike(pattern_))
137+
users.c.name.ilike(_pattern)
138+
| (
139+
_is_public(users.c.privacy_hide_email, caller_id)
140+
& users.c.email.ilike(_pattern)
141+
)
122142
)
123143
.limit(limit)
124144
)

services/web/server/tests/unit/with_dbs/03/test_users.py

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import functools
99
import sys
10+
from contextlib import AsyncExitStack
1011
from copy import deepcopy
1112
from http import HTTPStatus
1213
from typing import Any
@@ -32,7 +33,7 @@
3233
random_pre_registration_details,
3334
)
3435
from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict
35-
from pytest_simcore.helpers.webserver_login import UserInfoDict
36+
from pytest_simcore.helpers.webserver_login import NewUser, UserInfoDict
3637
from servicelib.aiohttp import status
3738
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
3839
from simcore_postgres_database.models.users import UserRole, UserStatus
@@ -59,49 +60,102 @@ def app_environment(
5960
)
6061

6162

63+
@pytest.mark.acceptance_test(
64+
"https://github.com/ITISFoundation/osparc-issues/issues/1779"
65+
)
6266
@pytest.mark.parametrize("user_role", [UserRole.USER])
6367
async def test_get_and_search_public_users(
64-
user: UserInfoDict,
6568
logged_user: UserInfoDict,
6669
client: TestClient,
6770
user_role: UserRole,
6871
):
6972
assert client.app
7073
assert user_role.value == logged_user["role"]
71-
other_user = user
72-
73-
assert other_user["id"] != logged_user["id"]
74-
75-
# GET user fro admin
76-
77-
# GET user
78-
url = client.app.router["get_user"].url_for(user_id=f'{other_user["id"]}')
79-
resp = await client.get(f"{url}")
80-
data, _ = await assert_status(resp, status.HTTP_200_OK)
81-
82-
got = UserGet.model_validate(data)
83-
assert got.user_id == other_user["id"]
84-
assert got.user_name == other_user["name"]
8574

86-
# SEARCH user
87-
partial_email = other_user["email"][:-5]
88-
url = client.app.router["search_users"].url_for()
89-
resp = await client.post(f"{url}", json={"match": partial_email})
90-
data, _ = await assert_status(resp, status.HTTP_200_OK)
91-
92-
found = TypeAdapter(list[UserGet]).validate_python(data)
93-
assert found
94-
assert len(found) == 1
95-
assert found[0] == got
75+
async with AsyncExitStack() as stack:
76+
private_user: UserInfoDict = await stack.enter_async_context(
77+
NewUser(
78+
app=client.app,
79+
user_data={
80+
"name": "jamie01",
81+
"first_name": "James",
82+
"last_name": "Bond",
83+
"email": "[email protected]",
84+
"privacy_hide_email": True,
85+
"privacy_hide_fullname": True,
86+
},
87+
)
88+
)
89+
public_user: UserInfoDict = await stack.enter_async_context(
90+
NewUser(
91+
app=client.app,
92+
user_data={
93+
"name": "taylie01",
94+
"first_name": "Taylor",
95+
"last_name": "Swift",
96+
"email": "[email protected]",
97+
"privacy_hide_email": False,
98+
"privacy_hide_fullname": False,
99+
},
100+
)
101+
)
96102

97-
# SEARCH user for admin (from a USER)
98-
url = (
99-
client.app.router["search_users_for_admin"]
100-
.url_for()
101-
.with_query(email=partial_email)
102-
)
103-
resp = await client.get(f"{url}")
104-
await assert_status(resp, status.HTTP_403_FORBIDDEN)
103+
assert private_user["id"] != logged_user["id"]
104+
assert public_user["id"] != logged_user["id"]
105+
106+
# GET user
107+
url = client.app.router["get_user"].url_for(user_id=f'{public_user["id"]}')
108+
resp = await client.get(f"{url}")
109+
data, _ = await assert_status(resp, status.HTTP_200_OK)
110+
111+
# check privacy
112+
got = UserGet.model_validate(data)
113+
assert got.user_id == public_user["id"]
114+
assert got.user_name == public_user["name"]
115+
assert got.first_name == public_user.get("first_name")
116+
assert got.last_name == public_user.get("last_name")
117+
118+
# SEARCH by partial email
119+
partial_email = "@find.m"
120+
assert partial_email in private_user["email"]
121+
assert partial_email in public_user["email"]
122+
123+
url = client.app.router["search_users"].url_for()
124+
resp = await client.post(f"{url}", json={"match": partial_email})
125+
data, _ = await assert_status(resp, status.HTTP_200_OK)
126+
127+
found = TypeAdapter(list[UserGet]).validate_python(data)
128+
assert found
129+
assert len(found) == 1
130+
assert found[0] == got
131+
132+
# SEARCH by partial username
133+
partial_username = "ie01"
134+
assert partial_username in private_user["name"]
135+
assert partial_username in public_user["name"]
136+
137+
url = client.app.router["search_users"].url_for()
138+
resp = await client.post(f"{url}", json={"match": partial_username})
139+
data, _ = await assert_status(resp, status.HTTP_200_OK)
140+
141+
found = TypeAdapter(list[UserGet]).validate_python(data)
142+
assert found
143+
assert len(found) == 2
144+
assert found[1] == got
145+
# check privacy
146+
assert found[0].user_name == private_user["name"]
147+
assert found[0].email is None
148+
assert found[0].first_name is None
149+
assert found[0].last_name is None
150+
151+
# SEARCH user for admin (from a USER)
152+
url = (
153+
client.app.router["search_users_for_admin"]
154+
.url_for()
155+
.with_query(email=partial_email)
156+
)
157+
resp = await client.get(f"{url}")
158+
await assert_status(resp, status.HTTP_403_FORBIDDEN)
105159

106160

107161
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)