Skip to content

Commit 14beb76

Browse files
committed
fixes all tests
1 parent 542d787 commit 14beb76

File tree

5 files changed

+108
-51
lines changed

5 files changed

+108
-51
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ class UsersForAdminListFilter(Filters):
249249
#
250250
review_status: Literal["PENDING", "REVIEWED"] | None = None
251251

252+
model_config = ConfigDict(extra="forbid")
253+
252254

253255
class UsersForAdminListQueryParams(UsersForAdminListFilter, PageQueryParameters): ...
254256

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from .exceptions import (
4444
AlreadyPreRegisteredError,
4545
MissingGroupExtraPropertiesForProductError,
46+
PendingPreRegistrationNotFoundError,
4647
UserNameDuplicateError,
4748
UserNotFoundError,
4849
)
@@ -51,6 +52,10 @@
5152

5253

5354
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
55+
PendingPreRegistrationNotFoundError: HttpErrorInfo(
56+
status.HTTP_400_BAD_REQUEST,
57+
PendingPreRegistrationNotFoundError.msg_template,
58+
),
5459
UserNotFoundError: HttpErrorInfo(
5560
status.HTTP_404_NOT_FOUND,
5661
"This user cannot be found. Either it is not registered or has enabled privacy settings.",

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from .exceptions import (
3232
AlreadyPreRegisteredError,
3333
MissingGroupExtraPropertiesForProductError,
34+
PendingPreRegistrationNotFoundError,
3435
)
3536

3637
_logger = logging.getLogger(__name__)
@@ -474,7 +475,7 @@ async def approve_user_account(
474475
int: The ID of the approved pre-registration record
475476
476477
Raises:
477-
ValueError: If no pre-registration is found for the email/product
478+
PendingPreRegistrationNotFoundError: If no pre-registration is found for the email/product
478479
"""
479480
engine = get_asyncpg_engine(app)
480481

@@ -487,8 +488,9 @@ async def approve_user_account(
487488
)
488489

489490
if not pre_registrations:
490-
msg = f"No pending pre-registration found for email {pre_registration_email} in product {product_name}"
491-
raise ValueError(msg)
491+
raise PendingPreRegistrationNotFoundError(
492+
email=pre_registration_email, product_name=product_name
493+
)
492494

493495
# There should be only one registration matching these criteria
494496
pre_registration = pre_registrations[0]
@@ -524,7 +526,7 @@ async def reject_user_account(
524526
int: The ID of the rejected pre-registration record
525527
526528
Raises:
527-
ValueError: If no pre-registration is found for the email/product
529+
PendingPreRegistrationNotFoundError: If no pre-registration is found for the email/product
528530
"""
529531
engine = get_asyncpg_engine(app)
530532

@@ -537,8 +539,9 @@ async def reject_user_account(
537539
)
538540

539541
if not pre_registrations:
540-
msg = f"No pending pre-registration found for email {pre_registration_email} in product {product_name}"
541-
raise ValueError(msg)
542+
raise PendingPreRegistrationNotFoundError(
543+
email=pre_registration_email, product_name=product_name
544+
)
542545

543546
# There should be only one registration matching these criteria
544547
pre_registration = pre_registrations[0]

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,14 @@ class BillingDetailsNotFoundError(UsersBaseError):
6464
class MissingGroupExtraPropertiesForProductError(UsersBaseError):
6565
msg_template = "Missing group_extra_property for product_name={product_name}"
6666
tip = "Add a new row in group_extra_property table and assign it to this product"
67+
68+
69+
class PendingPreRegistrationNotFoundError(UsersBaseError):
70+
msg_template = (
71+
"No pending pre-registration found for email {email} in product {product_name}"
72+
)
73+
74+
def __init__(self, *, email: str, product_name: str, **ctx: Any):
75+
super().__init__(**ctx)
76+
self.email = email
77+
self.product_name = product_name

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

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# pylint: disable=unused-variable
77

88

9+
from collections.abc import AsyncGenerator
910
from http import HTTPStatus
1011
from typing import Any
1112

@@ -14,24 +15,37 @@
1415
import simcore_service_webserver.users
1516
import simcore_service_webserver.users._users_repository
1617
import simcore_service_webserver.users._users_service
18+
import sqlalchemy as sa
1719
from aiohttp.test_utils import TestClient
20+
from common_library.pydantic_fields_extension import is_nullable
1821
from common_library.users_enums import UserRole, UserStatus
1922
from faker import Faker
2023
from models_library.api_schemas_webserver.auth import AccountRequestInfo
2124
from models_library.api_schemas_webserver.users import (
2225
UserForAdminGet,
2326
)
2427
from models_library.products import ProductName
28+
from models_library.rest_pagination import Page
2529
from pytest_simcore.helpers.assert_checks import assert_status
2630
from pytest_simcore.helpers.faker_factories import (
2731
DEFAULT_TEST_PASSWORD,
2832
)
2933
from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict
34+
from pytest_simcore.helpers.typing_env import EnvVarsDict
3035
from pytest_simcore.helpers.webserver_login import (
3136
UserInfoDict,
3237
)
3338
from servicelib.aiohttp import status
3439
from servicelib.rest_constants import X_PRODUCT_NAME_HEADER
40+
from simcore_postgres_database.models.users_details import (
41+
users_pre_registration_details,
42+
)
43+
from simcore_service_webserver.db.plugin import get_asyncpg_engine
44+
45+
# pylint: disable=redefined-outer-name
46+
# pylint: disable=unused-argument
47+
# pylint: disable=unused-variable
48+
# pylint: disable=too-many-arguments
3549

3650

3751
@pytest.fixture
@@ -101,6 +115,22 @@ def account_request_form(faker: Faker) -> dict[str, Any]:
101115
return form
102116

103117

118+
@pytest.fixture
119+
async def pre_registration_details_db_cleanup(
120+
client: TestClient,
121+
) -> AsyncGenerator[None, None]:
122+
"""Fixture to clean up all pre-registration details after test"""
123+
124+
assert client.app
125+
126+
yield
127+
128+
# Tear down - clean up the pre-registration details table
129+
async with get_asyncpg_engine(client.app).connect() as conn:
130+
await conn.execute(sa.delete(users_pre_registration_details))
131+
await conn.commit()
132+
133+
104134
@pytest.mark.acceptance_test(
105135
"pre-registration in https://github.com/ITISFoundation/osparc-simcore/issues/5138"
106136
)
@@ -114,6 +144,7 @@ async def test_search_and_pre_registration(
114144
client: TestClient,
115145
logged_user: UserInfoDict,
116146
account_request_form: dict[str, Any],
147+
pre_registration_details_db_cleanup: None,
117148
):
118149
assert client.app
119150

@@ -125,17 +156,14 @@ async def test_search_and_pre_registration(
125156

126157
found, _ = await assert_status(resp, status.HTTP_200_OK)
127158
assert len(found) == 1
128-
got = UserForAdminGet(
129-
institution=None,
130-
address=None,
131-
city=None,
132-
state=None,
133-
postal_code=None,
134-
country=None,
135-
pre_registration_id=None,
136-
invited_by=None,
137-
**found[0],
138-
)
159+
160+
nullable_fields = {
161+
name: None
162+
for name, field in UserForAdminGet.model_fields.items()
163+
if is_nullable(field)
164+
}
165+
166+
got = UserForAdminGet.model_validate({**nullable_fields, **found[0]})
139167
expected = {
140168
"first_name": logged_user.get("first_name"),
141169
"last_name": logged_user.get("last_name"),
@@ -206,6 +234,7 @@ async def test_list_users_for_admin(
206234
account_request_form: dict[str, Any],
207235
faker: Faker,
208236
product_name: ProductName,
237+
pre_registration_details_db_cleanup: None,
209238
):
210239
assert client.app
211240

@@ -230,14 +259,18 @@ async def test_list_users_for_admin(
230259
resp = await client.get(
231260
f"{url}?review_status=PENDING", headers={X_PRODUCT_NAME_HEADER: product_name}
232261
)
233-
data, _ = await assert_status(resp, status.HTTP_200_OK)
262+
assert resp.status == status.HTTP_200_OK
263+
response_json = await resp.json()
264+
265+
# Parse response into Page[UserForAdminGet] model
266+
page_model = Page[UserForAdminGet].model_validate(response_json)
234267

235268
# Access the items field from the paginated response
236-
pending_emails = [
237-
user["email"]
238-
for user in data
239-
if user.get("account_request_status") == "PENDING"
269+
pending_users = [
270+
user for user in page_model.data if user.account_request_status == "PENDING"
240271
]
272+
pending_emails = [user.email for user in pending_users]
273+
241274
for pre_user in pre_registered_users:
242275
assert pre_user["email"] in pending_emails
243276

@@ -267,26 +300,26 @@ async def test_list_users_for_admin(
267300
resp = await client.get(
268301
f"{url}?review_status=PENDING", headers={X_PRODUCT_NAME_HEADER: product_name}
269302
)
270-
pending_data, _ = await assert_status(resp, status.HTTP_200_OK)
303+
assert resp.status == status.HTTP_200_OK
304+
response_json = await resp.json()
305+
pending_page = Page[UserForAdminGet].model_validate(response_json)
271306

272307
# The registered user should no longer be in pending status
273-
pending_emails = [user["email"] for user in pending_data]
308+
pending_emails = [user.email for user in pending_page.data]
274309
assert registered_email not in pending_emails
275310
assert len(pending_emails) >= len(pre_registered_users) - 1
276311

277312
# b. Check REVIEWED users (should include the registered user)
278313
resp = await client.get(
279314
f"{url}?review_status=REVIEWED", headers={X_PRODUCT_NAME_HEADER: product_name}
280315
)
281-
approved_data, _ = await assert_status(resp, status.HTTP_200_OK)
316+
assert resp.status == status.HTTP_200_OK
317+
response_json = await resp.json()
318+
reviewed_page = Page[UserForAdminGet].model_validate(response_json)
282319

283320
# Find the registered user in the reviewed users
284321
active_user = next(
285-
(
286-
UserForAdminGet(**item)
287-
for item in approved_data
288-
if item["email"] == registered_email
289-
),
322+
(user for user in reviewed_page.data if user.email == registered_email),
290323
None,
291324
)
292325
assert active_user is not None
@@ -301,12 +334,13 @@ async def test_list_users_for_admin(
301334
headers={X_PRODUCT_NAME_HEADER: product_name},
302335
)
303336
assert resp.status == status.HTTP_200_OK
304-
page1_payload = await resp.json()
337+
response_json = await resp.json()
338+
page1 = Page[UserForAdminGet].model_validate(response_json)
305339

306-
assert len(page1_payload["items"]) == 2
307-
assert page1_payload["meta"]["limit"] == 2
308-
assert page1_payload["meta"]["offset"] == 0
309-
assert page1_payload["meta"]["total"] >= len(pre_registered_users)
340+
assert len(page1.data) == 2
341+
assert page1.meta.limit == 2
342+
assert page1.meta.offset == 0
343+
assert page1.meta.total >= len(pre_registered_users)
310344

311345
# b. Second page (limit 2)
312346
resp = await client.get(
@@ -315,15 +349,16 @@ async def test_list_users_for_admin(
315349
headers={X_PRODUCT_NAME_HEADER: product_name},
316350
)
317351
assert resp.status == status.HTTP_200_OK
318-
page2_payload = await resp.json()
352+
response_json = await resp.json()
353+
page2 = Page[UserForAdminGet].model_validate(response_json)
319354

320-
assert len(page2_payload["items"]) == 2
321-
assert page2_payload["meta"]["limit"] == 2
322-
assert page2_payload["meta"]["offset"] == 2
355+
assert len(page2.data) == 2
356+
assert page2.meta.limit == 2
357+
assert page2.meta.offset == 2
323358

324359
# Ensure page 1 and page 2 contain different items
325-
page1_emails = [item["email"] for item in page1_payload["data"]]
326-
page2_emails = [item["email"] for item in page2_payload["data"]]
360+
page1_emails = [user.email for user in page1.data]
361+
page2_emails = [user.email for user in page2.data]
327362
assert not set(page1_emails).intersection(page2_emails)
328363

329364
# 5. Combine status filter with pagination
@@ -332,12 +367,12 @@ async def test_list_users_for_admin(
332367
params={"review_status": "PENDING", "limit": 2, "offset": 0},
333368
headers={X_PRODUCT_NAME_HEADER: product_name},
334369
)
335-
filtered_page_payload, _ = await assert_status(resp, status.HTTP_200_OK)
336-
filtered_page_data = filtered_page_payload["data"]
370+
assert resp.status == status.HTTP_200_OK
371+
response_json = await resp.json()
372+
filtered_page = Page[UserForAdminGet].model_validate(response_json)
337373

338-
assert len(filtered_page_data) <= 2
339-
for item in filtered_page_data:
340-
user = UserForAdminGet(**item)
374+
assert len(filtered_page.data) <= 2
375+
for user in filtered_page.data:
341376
assert user.registered is False # Pending users are not registered
342377
assert user.account_request_status == "PENDING"
343378

@@ -354,6 +389,7 @@ async def test_reject_user_account(
354389
account_request_form: dict[str, Any],
355390
faker: Faker,
356391
product_name: ProductName,
392+
pre_registration_details_db_cleanup: None,
357393
):
358394
assert client.app
359395

@@ -374,7 +410,7 @@ async def test_reject_user_account(
374410
# 2. Verify the user is in PENDING status
375411
url = client.app.router["list_users_for_admin"].url_for()
376412
resp = await client.get(
377-
f"{url}?status=PENDING", headers={X_PRODUCT_NAME_HEADER: product_name}
413+
f"{url}?review_status=PENDING", headers={X_PRODUCT_NAME_HEADER: product_name}
378414
)
379415
data, _ = await assert_status(resp, status.HTTP_200_OK)
380416

@@ -393,7 +429,7 @@ async def test_reject_user_account(
393429
# 4. Verify the user is no longer in PENDING status
394430
url = client.app.router["list_users_for_admin"].url_for()
395431
resp = await client.get(
396-
f"{url}?status=PENDING", headers={X_PRODUCT_NAME_HEADER: product_name}
432+
f"{url}?review_status=PENDING", headers={X_PRODUCT_NAME_HEADER: product_name}
397433
)
398434
pending_data, _ = await assert_status(resp, status.HTTP_200_OK)
399435
pending_emails = [user["email"] for user in pending_data]
@@ -411,9 +447,9 @@ async def test_reject_user_account(
411447

412448
# Check that account_request_status is REJECTED
413449
user_data = found[0]
414-
assert user_data["account_request_status"] == "REJECTED"
415-
assert user_data["account_request_reviewed_by"] == logged_user["id"]
416-
assert user_data["account_request_reviewed_at"] is not None
450+
assert user_data["accountRequestStatus"] == "REJECTED"
451+
assert user_data["accountRequestReviewedBy"] == logged_user["id"]
452+
assert user_data["accountRequestReviewedAt"] is not None
417453

418454
# 6. Verify that a rejected user cannot be approved
419455
url = client.app.router["approve_user_account"].url_for()

0 commit comments

Comments
 (0)