From 9646f84b7998874c6cec73edacdc0e42d41fa093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 23 Oct 2024 11:18:03 +0200 Subject: [PATCH] Only send new alias events on user creation (#2285) * Only send new alias events on user creation (cherry picked from commit ab8f998dd40dc83c8f8a528a156ba50eae376aaf) * Trigger a sync when a new partner user is created * Improve tests * Move it to the partner_utils --------- Co-authored-by: Carlos Quintana <74399022+cquintana92@users.noreply.github.com> --- app/account_linking.py | 1 + app/partner_user_utils.py | 9 +++- app/proton/proton_callback_handler.py | 14 +----- tests/proton/test_proton_callback_handler.py | 49 +++++++++++++++++--- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/app/account_linking.py b/app/account_linking.py index ef7c0bd6..02160e64 100644 --- a/app/account_linking.py +++ b/app/account_linking.py @@ -112,6 +112,7 @@ def ensure_partner_user_exists_for_user( partner_email=link_request.email, external_user_id=link_request.external_user_id, ) + Session.commit() LOG.i( f"Created new partner_user for partner:{partner.id} user:{sl_user.id} external_user_id:{link_request.external_user_id}. PartnerUser.id is {res.id}" diff --git a/app/partner_user_utils.py b/app/partner_user_utils.py index c25f0b0c..ba665f07 100644 --- a/app/partner_user_utils.py +++ b/app/partner_user_utils.py @@ -1,8 +1,10 @@ from typing import Optional +import arrow from arrow import Arrow -from app.models import PartnerUser, PartnerSubscription, User +from app import config +from app.models import PartnerUser, PartnerSubscription, User, Job from app.user_audit_log_utils import emit_user_audit_log, UserAuditLogAction @@ -15,6 +17,11 @@ def create_partner_user( partner_email=partner_email, external_user_id=external_user_id, ) + Job.create( + name=config.JOB_SEND_ALIAS_CREATION_EVENTS, + payload={"user_id": user.id}, + run_at=arrow.now(), + ) emit_user_audit_log( user=user, action=UserAuditLogAction.LinkAccount, diff --git a/app/proton/proton_callback_handler.py b/app/proton/proton_callback_handler.py index f726d487..53c80763 100644 --- a/app/proton/proton_callback_handler.py +++ b/app/proton/proton_callback_handler.py @@ -2,11 +2,9 @@ from dataclasses import dataclass from enum import Enum from flask import url_for from typing import Optional -import arrow -from app import config from app.errors import LinkException -from app.models import User, Partner, Job +from app.models import User, Partner from app.proton.proton_client import ProtonClient, ProtonUser from app.account_linking import ( process_login_case, @@ -43,21 +41,12 @@ class ProtonCallbackHandler: def __init__(self, proton_client: ProtonClient): self.proton_client = proton_client - def _initial_alias_sync(self, user: User): - Job.create( - name=config.JOB_SEND_ALIAS_CREATION_EVENTS, - payload={"user_id": user.id}, - run_at=arrow.now(), - commit=True, - ) - def handle_login(self, partner: Partner) -> ProtonCallbackResult: try: user = self.__get_partner_user() if user is None: return generate_account_not_allowed_to_log_in() res = process_login_case(user, partner) - self._initial_alias_sync(res.user) return ProtonCallbackResult( redirect_to_login=False, flash_message=None, @@ -86,7 +75,6 @@ class ProtonCallbackHandler: if user is None: return generate_account_not_allowed_to_log_in() res = process_link_case(user, current_user, partner) - self._initial_alias_sync(res.user) return ProtonCallbackResult( redirect_to_login=False, flash_message="Account successfully linked", diff --git a/tests/proton/test_proton_callback_handler.py b/tests/proton/test_proton_callback_handler.py index 9916f993..adf87119 100644 --- a/tests/proton/test_proton_callback_handler.py +++ b/tests/proton/test_proton_callback_handler.py @@ -25,15 +25,17 @@ class MockProtonClient(ProtonClient): return self.user -def check_initial_sync_job(user: User): +def check_initial_sync_job(user: User, expected: bool): + found = False for job in Job.yield_per_query(10).filter_by( name=config.JOB_SEND_ALIAS_CREATION_EVENTS, state=JobState.ready.value, ): if job.payload.get("user_id") == user.id: + found = True Job.delete(job.id) - return - assert False + break + assert expected == found def test_proton_callback_handler_unexistant_sl_user(): @@ -69,10 +71,9 @@ def test_proton_callback_handler_unexistant_sl_user(): ) assert partner_user is not None assert partner_user.external_user_id == external_id - check_initial_sync_job(res.user) -def test_proton_callback_handler_existant_sl_user(): +def test_proton_callback_handler_existing_sl_user(): email = random_email() sl_user = User.create(email, commit=True) @@ -98,7 +99,43 @@ def test_proton_callback_handler_existant_sl_user(): sa = PartnerUser.get_by(user_id=sl_user.id, partner_id=get_proton_partner().id) assert sa is not None assert sa.partner_email == user.email - check_initial_sync_job(res.user) + check_initial_sync_job(res.user, True) + + +def test_proton_callback_handler_linked_sl_user(): + email = random_email() + external_id = random_string() + sl_user = User.create(email, commit=True) + PartnerUser.create( + user_id=sl_user.id, + partner_id=get_proton_partner().id, + external_user_id=external_id, + partner_email=email, + commit=True, + ) + + user = UserInformation( + email=email, + name=random_string(), + id=external_id, + plan=SLPlan(type=SLPlanType.Premium, expiration=Arrow.utcnow().shift(hours=2)), + ) + handler = ProtonCallbackHandler(MockProtonClient(user=user)) + res = handler.handle_login(get_proton_partner()) + + assert res.user is not None + assert res.user.id == sl_user.id + # Ensure the user is not marked as created from partner + assert User.FLAG_CREATED_FROM_PARTNER != ( + res.user.flags & User.FLAG_CREATED_FROM_PARTNER + ) + assert res.user.notification is True + assert res.user.trial_end is not None + + sa = PartnerUser.get_by(user_id=sl_user.id, partner_id=get_proton_partner().id) + assert sa is not None + assert sa.partner_email == user.email + check_initial_sync_job(res.user, False) def test_proton_callback_handler_none_user_login():