99from collections .abc import AsyncGenerator
1010from http import HTTPStatus
1111from typing import Any
12+ from unittest .mock import AsyncMock
1213
1314import pytest
1415import simcore_service_webserver .login ._auth_service
2324)
2425from models_library .products import ProductName
2526from models_library .rest_pagination import Page
27+ from pytest_mock import MockerFixture
2628from pytest_simcore .aioresponses_mocker import AioResponsesMock
2729from pytest_simcore .helpers .assert_checks import assert_status
2830from pytest_simcore .helpers .faker_factories import (
@@ -55,6 +57,35 @@ def app_environment(
5557 )
5658
5759
60+ @pytest .fixture
61+ def mock_email_session (mocker : MockerFixture ) -> AsyncMock :
62+ """Mock the email session and capture sent messages"""
63+ # Create a mock email session
64+ mock_session = AsyncMock ()
65+
66+ # List to store sent messages
67+ sent_messages = []
68+
69+ async def mock_send_message (msg ):
70+ """Mock send_message method to capture messages"""
71+ sent_messages .append (msg )
72+
73+ mock_session .send_message = mock_send_message
74+ mock_session .sent_messages = sent_messages
75+
76+ # Mock the context manager behavior
77+ mock_session .__aenter__ = AsyncMock (return_value = mock_session )
78+ mock_session .__aexit__ = AsyncMock (return_value = None )
79+
80+ # Use mocker to patch the create_email_session function
81+ mocker .patch (
82+ "simcore_service_webserver.users._accounts_service.create_email_session" ,
83+ return_value = mock_session ,
84+ )
85+
86+ return mock_session
87+
88+
5889@pytest .mark .parametrize (
5990 "user_role,expected" ,
6091 [
@@ -388,6 +419,7 @@ async def test_reject_user_account(
388419 faker : Faker ,
389420 product_name : ProductName ,
390421 pre_registration_details_db_cleanup : None ,
422+ mock_email_session : AsyncMock ,
391423):
392424 assert client .app
393425
@@ -424,7 +456,20 @@ async def test_reject_user_account(
424456 )
425457 await assert_status (resp , status .HTTP_204_NO_CONTENT )
426458
427- # 4. Verify the user is no longer in PENDING status
459+ # 4. Verify rejection email was sent
460+ # Wait a bit for fire-and-forget task to complete
461+ import asyncio
462+
463+ await asyncio .sleep (0.1 )
464+
465+ assert len (mock_email_session .sent_messages ) == 1
466+ rejection_msg = mock_email_session .sent_messages [0 ]
467+
468+ # Verify email recipients and content
469+ assert rejection_msg ["To" ] == pre_registered_email
470+ assert "rejected" in rejection_msg ["Subject" ].lower ()
471+
472+ # 5. Verify the user is no longer in PENDING status
428473 url = client .app .router ["list_users_accounts" ].url_for ()
429474 resp = await client .get (
430475 f"{ url } ?review_status=PENDING" , headers = {X_PRODUCT_NAME_HEADER : product_name }
@@ -433,7 +478,7 @@ async def test_reject_user_account(
433478 pending_emails = [user ["email" ] for user in pending_data ]
434479 assert pre_registered_email not in pending_emails
435480
436- # 5 . Verify the user is now in REJECTED status
481+ # 6 . Verify the user is now in REJECTED status
437482 # First get user details to check status
438483 resp = await client .get (
439484 "/v0/admin/user-accounts:search" ,
@@ -449,7 +494,7 @@ async def test_reject_user_account(
449494 assert user_data ["accountRequestReviewedBy" ] == logged_user ["id" ]
450495 assert user_data ["accountRequestReviewedAt" ] is not None
451496
452- # 6 . Verify that a rejected user cannot be approved
497+ # 7 . Verify that a rejected user cannot be approved
453498 url = client .app .router ["approve_user_account" ].url_for ()
454499 resp = await client .post (
455500 f"{ url } " ,
@@ -474,6 +519,7 @@ async def test_approve_user_account_with_full_invitation_details(
474519 product_name : ProductName ,
475520 pre_registration_details_db_cleanup : None ,
476521 mock_invitations_service_http_api : AioResponsesMock ,
522+ mock_email_session : AsyncMock ,
477523):
478524 """Test approving user account with complete invitation details (trial days + credits)"""
479525 assert client .app
@@ -510,7 +556,20 @@ async def test_approve_user_account_with_full_invitation_details(
510556 )
511557 await assert_status (resp , status .HTTP_204_NO_CONTENT )
512558
513- # 3. Verify the user account status and invitation data in extras
559+ # 3. Verify approval email was sent
560+ # Wait a bit for fire-and-forget task to complete
561+ import asyncio
562+
563+ await asyncio .sleep (0.1 )
564+
565+ assert len (mock_email_session .sent_messages ) == 1
566+ approval_msg = mock_email_session .sent_messages [0 ]
567+
568+ # Verify email recipients and content
569+ assert approval_msg ["To" ] == test_email
570+ assert "approved" in approval_msg ["Subject" ].lower ()
571+
572+ # 4. Verify the user account status and invitation data in extras
514573 resp = await client .get (
515574 "/v0/admin/user-accounts:search" ,
516575 params = {"email" : test_email },
@@ -524,7 +583,7 @@ async def test_approve_user_account_with_full_invitation_details(
524583 assert user_data ["accountRequestReviewedBy" ] == logged_user ["id" ]
525584 assert user_data ["accountRequestReviewedAt" ] is not None
526585
527- # 4 . Verify invitation data is stored in extras
586+ # 5 . Verify invitation data is stored in extras
528587 assert "invitation" in user_data ["extras" ]
529588 invitation_data = user_data ["extras" ]["invitation" ]
530589 assert invitation_data ["guest" ] == test_email
0 commit comments