Skip to content

Commit 6a60b04

Browse files
committed
✨ users: Enhance user pre-registration with optional linking to existing users
1 parent 6f8d5a5 commit 6a60b04

File tree

3 files changed

+109
-7
lines changed

3 files changed

+109
-7
lines changed

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

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -678,19 +678,54 @@ async def create_user_pre_registration(
678678
connection: AsyncConnection | None = None,
679679
*,
680680
email: str,
681-
created_by: UserID,
681+
created_by: UserID | None = None,
682682
product_name: ProductName,
683+
link_to_existing_user: bool = True,
683684
**other_values,
684685
) -> int:
686+
"""Creates a user pre-registration entry.
687+
688+
Args:
689+
engine: Database engine
690+
connection: Optional existing connection
691+
email: Email address for the pre-registration
692+
created_by: ID of the user creating the pre-registration (None for anonymous)
693+
product_name: Product name the user is requesting access to
694+
link_to_existing_user: Whether to link the pre-registration to an existing user with the same email
695+
**other_values: Additional values to insert in the pre-registration entry
696+
697+
Returns:
698+
ID of the created pre-registration
699+
"""
685700
async with transaction_context(engine, connection) as conn:
701+
# If link_to_existing_user is True, try to find a matching user
702+
user_id = None
703+
if link_to_existing_user:
704+
result = await conn.execute(
705+
sa.select(users.c.id).where(users.c.email == email)
706+
)
707+
user = result.one_or_none()
708+
if user:
709+
user_id = user.id
710+
711+
# Insert the pre-registration record
712+
values = {
713+
"pre_email": email,
714+
"product_name": product_name,
715+
**other_values,
716+
}
717+
718+
# Only add created_by if not None
719+
if created_by is not None:
720+
values["created_by"] = created_by
721+
722+
# Add user_id if found
723+
if user_id is not None:
724+
values["user_id"] = user_id
725+
686726
result = await conn.execute(
687727
sa.insert(users_pre_registration_details)
688-
.values(
689-
created_by=created_by,
690-
pre_email=email,
691-
product_name=product_name,
692-
**other_values,
693-
)
728+
.values(**values)
694729
.returning(users_pre_registration_details.c.id)
695730
)
696731
pre_registration_id: int = result.scalar_one()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from contextlib import suppress
23

34
from aiohttp import web

services/web/server/tests/unit/with_dbs/03/users/test_users_repository.py

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

66
from typing import Any
77

8+
import pytest
89
import sqlalchemy as sa
910
from aiohttp import web
1011
from common_library.users_enums import AccountRequestStatus
@@ -285,3 +286,68 @@ async def test_list_user_pre_registrations(
285286
)
286287
)
287288
await conn.commit()
289+
290+
291+
@pytest.mark.parametrize(
292+
"link_to_existing_user,expected_linked", [(True, True), (False, False)]
293+
)
294+
async def test_create_pre_registration_with_existing_user_linking(
295+
app: web.Application,
296+
product_name: ProductName,
297+
product_owner_user: dict[str, Any],
298+
link_to_existing_user: bool,
299+
expected_linked: bool,
300+
):
301+
"""Test that creating a pre-registration for an existing user correctly handles auto-linking."""
302+
# Arrange
303+
asyncpg_engine = get_asyncpg_engine(app)
304+
existing_user_id = product_owner_user["id"]
305+
existing_user_email = product_owner_user["email"]
306+
307+
# Act - Create pre-registration with the same email as product_owner_user
308+
pre_registration_id = await _users_repository.create_user_pre_registration(
309+
asyncpg_engine,
310+
email=existing_user_email, # Same email as existing user
311+
created_by=existing_user_id,
312+
product_name=product_name,
313+
link_to_existing_user=link_to_existing_user, # Parameter to test
314+
pre_first_name="Link-Test",
315+
pre_last_name="User",
316+
institution=f"{'Auto-linked' if link_to_existing_user else 'No-link'} Institution",
317+
)
318+
319+
# Assert - Verify through list_user_pre_registrations
320+
registrations, count = await _users_repository.list_user_pre_registrations(
321+
asyncpg_engine,
322+
filter_by_pre_email=existing_user_email,
323+
filter_by_product_name=product_name,
324+
)
325+
326+
# Verify count and that we found our registration
327+
assert count == 1
328+
assert len(registrations) == 1
329+
330+
# Get the registration
331+
reg = registrations[0]
332+
333+
# Verify linking behavior based on parameter
334+
assert reg["id"] == pre_registration_id
335+
assert reg["pre_email"] == existing_user_email
336+
337+
# When True, user_id should be set to the existing user ID
338+
# When False, user_id should be None
339+
if expected_linked:
340+
assert (
341+
reg["user_id"] == existing_user_id
342+
), "Should be linked to the existing user"
343+
else:
344+
assert reg["user_id"] is None, "Should NOT be linked to any user"
345+
346+
# Clean up
347+
async with asyncpg_engine.connect() as conn:
348+
await conn.execute(
349+
sa.delete(users_pre_registration_details).where(
350+
users_pre_registration_details.c.id == pre_registration_id
351+
)
352+
)
353+
await conn.commit()

0 commit comments

Comments
 (0)