66# pylint: disable=unused-variable
77
88
9+ from collections .abc import AsyncGenerator
910from http import HTTPStatus
1011from typing import Any
1112
1415import simcore_service_webserver .users
1516import simcore_service_webserver .users ._users_repository
1617import simcore_service_webserver .users ._users_service
18+ import sqlalchemy as sa
1719from aiohttp .test_utils import TestClient
20+ from common_library .pydantic_fields_extension import is_nullable
1821from common_library .users_enums import UserRole , UserStatus
1922from faker import Faker
2023from models_library .api_schemas_webserver .auth import AccountRequestInfo
2124from models_library .api_schemas_webserver .users import (
2225 UserForAdminGet ,
2326)
2427from models_library .products import ProductName
28+ from models_library .rest_pagination import Page
2529from pytest_simcore .helpers .assert_checks import assert_status
2630from pytest_simcore .helpers .faker_factories import (
2731 DEFAULT_TEST_PASSWORD ,
2832)
2933from pytest_simcore .helpers .monkeypatch_envs import EnvVarsDict , setenvs_from_dict
34+ from pytest_simcore .helpers .typing_env import EnvVarsDict
3035from pytest_simcore .helpers .webserver_login import (
3136 UserInfoDict ,
3237)
3338from servicelib .aiohttp import status
3439from 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