from datetime import timedelta import flask from atlas_um.audit.processors import EventBridgeAsyncProcessor from atlas_um.consts import SystemEvents, AccountStateSource from atlas_um.dna_accounts.services import ( UpdateDNAAccountService, UpdateDNAAccountClaimsService, DisableDNAAccountClaimsService, CreateDNAAccountService, SuspendDNAAccountService, SyncDNAAccountWithUSM, SendDNAInvitationService, SyncInternalUserDomainsWithUSM, SendDNAAccessExpirationNotificationService, SyncDNAAccountWithAuth0Service, SyncDNAAccountExternalStates, ) from atlas_um.extensions import usm from atlas_um.helpers.either import Left, Right from atlas_um.pgdb import DNAAccount, InternalUserDomain, pgdb from atlas_um.pgdb.associations import dna_account_claim_table from atlas_um.pgdb.dna_account import ( DNAAccountStatuses, DNAAccountExternalState, ) from atlas_um.settings import Settings from tests.atlas_um import factories from tests.atlas_um.factories import DNAAccountFactory from tests.atlas_um.factories.internal_user_domain_factory import ( InternalUserDomainFactory, ) class TestUpdateDNAAccountService: def test_account_update_usm_exists( self, pgdb_session, faker, mocker, boto3_client_mock ): account = factories.DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() old_values = { "given_name": account.given_name, "family_name": account.family_name, "email": account.email, } new_values = { "business_unit": factories.BusinessUnitFactory.build(), "job_category": factories.JobCategoryFactory.build(), "personnel_type": factories.PersonnelTypeFactory.build(), "given_name": faker.pystr(), "family_name": faker.pystr(), "email": faker.ascii_email(), "is_vip": True, "is_sony_employee": True, "expiration_date": (account.expiration_date or faker.future_date()) + timedelta(days=2), "supervisor_email": faker.email(), "supervisor_name": faker.pystr(), "job_title": faker.pystr(), "location": faker.pystr(), "preferred_username": faker.pystr(), "tags": factories.TagFactory.build_batch( faker.pyint(min_value=1, max_value=5) ), } for k, v in new_values.items(): assert getattr(account, k) != v, "Invalid new value for: {k}" mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) UpdateDNAAccountService.execute(account, **new_values) account = DNAAccount.query.filter(DNAAccount.id == account.id).first() for k, v in new_values.items(): if k in old_values: continue assert getattr(account, k) == v, "Invalid updated value for: {k}" for k, v in old_values.items(): assert getattr(account, k) == v, "Invalid updated value for: {k}" assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.updating_dna_account, dna_account_id=account.sub ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] def test_account_update_no_usm( self, pgdb_session, faker, mocker, boto3_client_mock ): account = factories.DNAAccountFactory.build( usm_account=None, email="test@example.com", invitation_is_sent=True ) pgdb_session.add(account) pgdb_session.commit() new_values = { "business_unit": factories.BusinessUnitFactory.build(), "job_category": factories.JobCategoryFactory.build(), "personnel_type": factories.PersonnelTypeFactory.build(), "given_name": faker.pystr(), "family_name": faker.pystr(), "email": faker.ascii_email(), "is_vip": True, "is_sony_employee": True, "expiration_date": account.expiration_date + timedelta(days=faker.pyint(min_value=1)), "supervisor_email": faker.email(), "supervisor_name": faker.pystr(), "job_title": faker.pystr(), "location": faker.pystr(), "preferred_username": faker.pystr(), "tags": factories.TagFactory.build_batch( faker.pyint(min_value=1, max_value=5) ), } for k, v in new_values.items(): assert getattr(account, k) != v, "Invalid new value for: {k}" mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) UpdateDNAAccountService.execute(account, **new_values) account = DNAAccount.query.filter(DNAAccount.id == account.id).first() for k, v in new_values.items(): assert getattr(account, k) == v, "Invalid updated value for: {k}" assert not account.invitation_is_sent assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.updating_dna_account, dna_account_id=account.sub ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] class TestUpdateDNAAccountClaimsService: def test_update_new(self, pgdb_session, mocker, boto3_client_mock): account = factories.DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() claim_name = factories.ClaimNameFactory.build( claim_values_serializer="BooleanAnd" ) claim_value = factories.ClaimValueFactory.build( claim_name=claim_name, components={"value1": 1} ) pgdb_session.add(claim_value) pgdb_session.commit() mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) assert len(account.claim_values) == 0 UpdateDNAAccountClaimsService.execute( account, [claim_value], claim_name.resource_group ) assert account.claim_values == [claim_value] assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.updating_dna_account_claims, dna_account_id=account.sub, ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] def test_update_disabled(self, pgdb_session, mocker, boto3_client_mock): account = factories.DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() claim_name = factories.ClaimNameFactory.build( claim_values_serializer="BooleanAnd" ) claim_value = factories.ClaimValueFactory.build( claim_name=claim_name, components={"value1": 1}, ) account.claim_values = [claim_value] pgdb_session.add(claim_value) pgdb_session.commit() pgdb.session.execute( pgdb.update(dna_account_claim_table).values(is_disabled=True) ) assert len(account.claim_values) == 1 mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) UpdateDNAAccountClaimsService.execute( account, [claim_value], claim_name.resource_group ) assert account.claim_values == [claim_value] assert ( pgdb.session.execute( pgdb.select( pgdb.func.count(dna_account_claim_table.c.id) ).where( dna_account_claim_table.c.is_disabled == False # noqa ) ).scalar() == 1 ) assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.updating_dna_account_claims, dna_account_id=account.sub, ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] def test_update_keep_existing_values( self, pgdb_session, mocker, boto3_client_mock ): account = factories.DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() claim_name1 = factories.ClaimNameFactory.build( claim_values_serializer="BooleanAnd" ) claim_value1 = factories.ClaimValueFactory.build( claim_name=claim_name1, components={"value1": 1}, ) claim_name2 = factories.ClaimNameFactory.build( claim_values_serializer="BooleanAnd" ) claim_value2 = factories.ClaimValueFactory.build( claim_name=claim_name2, components={"value2": 1}, ) account.claim_values = [claim_value1, claim_value2] pgdb_session.add(claim_value1, claim_value2) pgdb_session.commit() mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) assert len(account.claim_values) == 2 UpdateDNAAccountClaimsService.execute( account, [claim_value2], claim_name2.resource_group ) assert set(account.claim_values) == {claim_value1, claim_value2} assert ( pgdb.session.execute( pgdb.select( pgdb.func.count(dna_account_claim_table.c.id) ).where( dna_account_claim_table.c.is_disabled == False # noqa ) ).scalar() == 2 ) assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.updating_dna_account_claims, dna_account_id=account.sub, ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] def test_set_token_length(self, pgdb_session, mocker, boto3_client_mock): account = factories.DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() claim_name = factories.ClaimNameFactory.build( claim_values_serializer="BooleanAnd" ) claim_value = factories.ClaimValueFactory.build( claim_name=claim_name, components={"value1": 1} ) pgdb_session.add(claim_value) pgdb_session.commit() assert account.token_length == 0 mocker.patch( "atlas_um.tokens.bearer_tokens.DNABearerToken.token_length", new_callable=mocker.PropertyMock, ).return_value = 100 UpdateDNAAccountClaimsService.execute( account, [claim_value], claim_name.resource_group ) assert account.token_length == 100 class TestDisableDNAAccountClaimsService: def test_disable(self, pgdb_session, mocker, boto3_client_mock): account = factories.DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() claim_name = factories.ClaimNameFactory.build( claim_values_serializer="BooleanAnd" ) claim_value = factories.ClaimValueFactory.build( claim_name=claim_name, components={"value1": 1}, ) account.claim_values = [claim_value] pgdb_session.add(claim_value) pgdb_session.commit() assert len(account.claim_values) == 1 mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) DisableDNAAccountClaimsService.execute( account, [claim_value], claim_name.resource_group ) assert ( pgdb.session.execute( pgdb.select( pgdb.func.count(dna_account_claim_table.c.id) ).where( dna_account_claim_table.c.is_disabled == True # noqa ) ).scalar() == 1 ) assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.disabling_dna_account_claims, dna_account_id=account.sub, ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( { "result": Right(account), "args": [claim_name.resource_group], } ), } ] ) ] class TestSuspendDNAAccountService: def test_suspend(self, pgdb_session, mocker, boto3_client_mock): account = DNAAccountFactory.build() pgdb_session.add(account) pgdb_session.commit() assert account.status == DNAAccountStatuses.ACTIVE mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) SuspendDNAAccountService.execute(account) assert account.status == DNAAccountStatuses.SUSPENDED assert ( pgdb_session.query(DNAAccount.status, DNAAccount) .where(DNAAccount.id == account.id) .scalar() == DNAAccountStatuses.SUSPENDED ) assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.suspending_dna_account, dna_account_id=account.sub ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.suspending_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] class TestCreateDNAAccountService: def test_create(self, pgdb_session, faker, mocker, boto3_client_mock): business_unit = factories.BusinessUnitFactory.build() job_category = factories.JobCategoryFactory.build() personnel_type = factories.PersonnelTypeFactory.build() pgdb_session.add(business_unit) pgdb_session.add(job_category) pgdb_session.add(personnel_type) pgdb_session.commit() data = { "given_name": faker.first_name(), "family_name": faker.last_name(), "email": faker.email(), "is_sony_employee": True, "is_vip": True, "job_category": job_category, "business_unit": business_unit, "personnel_type": personnel_type, "expiration_date": faker.future_date(), "supervisor_email": faker.email(), "supervisor_name": faker.name(), "job_title": faker.pystr(), "location": faker.pystr(), "preferred_username": faker.pystr(), "tags": factories.TagFactory.build_batch( faker.pyint(min_value=1, max_value=5) ), } mocked_user_events = mocker.patch( "atlas_um.analytics.collectors.Collector.log" ) result = CreateDNAAccountService.execute(**data) assert result.is_right account = result.value assert account.given_name == data["given_name"] assert account.family_name == data["family_name"] assert account.email == data["email"] assert account.is_sony_employee == data["is_sony_employee"] assert account.is_vip == data["is_vip"] assert account.job_category == data["job_category"] assert account.business_unit == data["business_unit"] assert account.personnel_type == data["personnel_type"] assert account.expiration_date == data["expiration_date"] assert account.supervisor_email == data["supervisor_email"] assert account.supervisor_name == data["supervisor_name"] assert account.job_title == data["job_title"] assert account.location == data["location"] assert account.preferred_username == data["preferred_username"] assert account.tags == data["tags"] assert mocked_user_events.call_args_list == [ mocker.call( SystemEvents.creating_dna_account, dna_account_id=account.sub ) ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.creating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(account)} ), } ] ) ] class TestSyncDNAAccountWithUSM: def test_sync_internal( self, pgdb_session, faker, mocker, internal_user_domain_factory, ): internal_user_domain = internal_user_domain_factory.build( domain="sonytest.com" ) pgdb_session.add(internal_user_domain) pgdb_session.commit() email = f"test@{internal_user_domain.domain}" dna_account = DNAAccountFactory.build(email=email) usm_data = { "givenName": faker.first_name(), "surname": faker.last_name(), "country": faker.pystr(), "jobTitle": faker.pystr(), } mocker.patch( "atlas_um.usm.client.USMClient.get_user" ).return_value = Right(usm_data) SyncDNAAccountWithUSM.execute(dna_account=dna_account) assert dna_account.given_name == usm_data["givenName"] assert dna_account.family_name == usm_data["surname"] assert dna_account.location == usm_data["country"] assert dna_account.job_title == usm_data["jobTitle"] def test_sync_external( self, pgdb_session, faker, mocker, internal_user_domain_factory ): internal_user_domain = internal_user_domain_factory.build( domain="sonytest.com" ) pgdb_session.add(internal_user_domain) external_user_domain = "external.com" email = f"test@{external_user_domain}" dna_account = DNAAccountFactory.build(email=email) pgdb_session.add(dna_account) pgdb_session.commit() usm_data = { "firstName": faker.first_name(), "lastName": faker.last_name(), "country": faker.pystr(), "jobTitle": faker.pystr(), } mocker.patch( "atlas_um.usm.client.USMClient.lookup_user" ).return_value = Right(usm_data) SyncDNAAccountWithUSM.execute(dna_account=dna_account) assert internal_user_domain.domain != external_user_domain assert dna_account.given_name == usm_data["firstName"] assert dna_account.family_name == usm_data["lastName"] assert dna_account.location == usm_data["country"] assert dna_account.job_title == usm_data["jobTitle"] class TestSendDNAInvitationService: def test_external(self, pgdb_session, faker, mocker, boto3_client_mock): password_reset_link = faker.url() dna_account = DNAAccountFactory.build() admin_dna_account = DNAAccountFactory.build() pgdb_session.add(dna_account) pgdb_session.add(admin_dna_account) pgdb_session.commit() assert dna_account.is_external mocked_save_user = mocker.patch( "atlas_um.usm.client.USMClient.save_user" ) mocked_save_user.return_value = Right(None) mocked_reset_password = mocker.patch( "atlas_um.usm.client.USMClient.reset_password" ) mocked_reset_password.return_value = Right(password_reset_link) mocked_send_email = mocker.patch( "core_notifications_client.CoreNotificationsClient.send_email" ) expected_template = flask.render_template( "notifications/dna_invitation.html", account=dna_account, link=password_reset_link, ) SendDNAInvitationService.execute(dna_account, admin_dna_account) assert mocked_save_user.call_args_list == [ mocker.call( dna_account.email, dna_account.given_name, dna_account.family_name, admin_dna_account.email, ) ] assert mocked_reset_password.call_args_list == [ mocker.call(dna_account.email, admin_dna_account.email) ] assert mocked_send_email.call_args_list == [ mocker.call( "Sony Music Product Design & Engineering Invitation", dna_account.email, expected_template, ), ] bus_conf = EventBridgeAsyncProcessor.EVENTS_SETTINGS.get( SystemEvents.updating_dna_account ) assert boto3_client_mock.put_events.call_args_list == [ mocker.call( Entries=[ { "Source": Settings.EVENT_BUS_SOURCE, "EventBusName": Settings.EVENT_BUS_NAME, "DetailType": bus_conf.detail_type, "Detail": bus_conf.detail_schema().dumps( {"result": Right(dna_account)} ), } ] ) ] class TestSyncInternalUserDomainsWithUSM: def test_sync_internal_domains_with_usm( self, pgdb_session, faker, mocker, internal_user_domain_factory, ): # imagine that we have some domains in our DB domain_test_1 = internal_user_domain_factory.build(domain="test_1.com") domain_test_2 = internal_user_domain_factory.build(domain="test_2.com") pgdb_session.add(domain_test_1) pgdb_session.add(domain_test_2) pgdb_session.commit() assert pgdb_session.query(InternalUserDomain).count() == 2 domains_from_usm = [ "test_2.com", "test_3.com", "test_4.com", ] mocker.patch( "atlas_um.usm.client.USMClient.get_internal_domains" ).return_value = Right(domains_from_usm) SyncInternalUserDomainsWithUSM.execute() # check that count of domains from USM the equal count of domains in DB count_domains_in_db = pgdb_session.query(InternalUserDomain).count() assert count_domains_in_db == len(domains_from_usm) # check that all domains saved in DB for domain_from_usm in domains_from_usm: assert InternalUserDomain.query.filter_by( domain=domain_from_usm ).first() class TestSendDNAAccessExpirationNotificationService: def test_send_notification( self, pgdb_session, mocker, ): dna_account = DNAAccountFactory.build() pgdb_session.add(dna_account) pgdb_session.commit() mocked_send_email = mocker.patch( "core_notifications_client.CoreNotificationsClient.send_email" ) expected_template = flask.render_template( "notifications/account_expiration.html", account=dna_account, ) SendDNAAccessExpirationNotificationService.execute(dna_account) assert mocked_send_email.call_args_list == [ mocker.call( "Sony Music Product Design & Engineering", dna_account.email, expected_template, ), ] class TestSyncDNAAccountWithAuth0Service: def test_sync(self, pgdb_session, mocker, faker): sub = faker.pystr() account = DNAAccountFactory.build(sub=sub) pgdb_session.add(account) pgdb_session.commit() mocker.patch("atlas_um.analytics.collectors.Collector.log") mocked_auth0_user = mocker.MagicMock(user_id=faker.pystr()) mocker.patch( "atlas_um.dna_accounts.services." "auth0.get_user_by_email", return_value=mocked_auth0_user, ) SyncDNAAccountWithAuth0Service.execute(account) assert account.sub != sub class TestSyncDNAAccountExternalStates: def test_execution_new_active_state( self, app, mocker, faker, pgdb_session ): dna_account = DNAAccountFactory.build() pgdb_session.add(dna_account) pgdb_session.commit() usm_data = { "givenName": faker.first_name(), "surname": faker.last_name(), "country": faker.pystr(), "jobTitle": faker.pystr(), } mocker.patch.object(usm, "get_user").return_value = Right(usm_data) result = SyncDNAAccountExternalStates.execute(dna_account=dna_account) assert result.is_right assert ( result.value == DNAAccountExternalState.query.filter( DNAAccountExternalState.dna_account_id == dna_account.id, DNAAccountExternalState.is_active == True, # noqa ).all() ) assert result.value[0].is_active def test_execution_updated_not_active_state( self, app, mocker, faker, pgdb_session ): dna_account = DNAAccountFactory.build() state = DNAAccountExternalState() state.dna_account = dna_account state.source = AccountStateSource.USM.value state.is_active = True pgdb_session.add(dna_account) pgdb_session.add(state) pgdb_session.commit() usm_data = {"success": False, "message": faker.pystr()} mocker.patch.object(usm, "get_user").return_value = Left(usm_data) mocker.patch.object(usm, "lookup_user").return_value = Left(usm_data) result = SyncDNAAccountExternalStates.execute(dna_account=dna_account) assert result.is_right assert ( result.value == DNAAccountExternalState.query.filter( DNAAccountExternalState.dna_account_id == dna_account.id, DNAAccountExternalState.is_active == False, # noqa ).all() ) assert not result.value[0].is_active def test_execution_updated_not_active_state_internal_domain( self, app, mocker, faker, pgdb_session ): internal_domain = InternalUserDomainFactory.build() pgdb_session.add(internal_domain) dna_account = DNAAccountFactory.build( email=f"test@{internal_domain.domain}" ) pgdb_session.add(dna_account) pgdb_session.commit() usm_data = {"success": False, "message": faker.pystr()} mocker.patch.object(usm, "get_user").return_value = Left(usm_data) mocker.patch.object(usm, "lookup_user").return_value = Left(usm_data) result = SyncDNAAccountExternalStates.execute(dna_account=dna_account) assert result.is_right assert ( result.value == DNAAccountExternalState.query.filter( DNAAccountExternalState.dna_account_id == dna_account.id, DNAAccountExternalState.is_active == False, # noqa ).all() ) assert not result.value[0].is_active def test_execution_unknown_state(self, app, mocker, faker, pgdb_session): dna_account = DNAAccountFactory.build() pgdb_session.add(dna_account) pgdb_session.commit() usm_data = {"success": False, "message": faker.pystr()} mocker.patch.object(usm, "get_user").return_value = Left(usm_data) mocker.patch.object(usm, "lookup_user").return_value = Left(usm_data) result = SyncDNAAccountExternalStates.execute(dna_account=dna_account) assert result.is_right assert result.value == [] assert ( DNAAccountExternalState.query.filter( DNAAccountExternalState.dna_account_id == dna_account.id, DNAAccountExternalState.is_active == False, # noqa ).count() == 0 )