def test_success_subsidiary(self, data_flow_api_client): """Test that for a company and it's subsidiary two companies are returned""" company = SubsidiaryFactory() response = data_flow_api_client.get(self.view_url) assert response.status_code == status.HTTP_200_OK response_results = response.json()['results'] result = next( (x for x in response_results if x['id'] == str(company.id))) assert result == get_expected_data_from_company(company)
class TestAssignRegionalCompanyAccountManagerView(APITestMixin): """ Tests for the self-assign company account manager view. (Implemented in CompanyViewSet.assign_regional_account_manager().) """ @staticmethod def _get_url(company): return reverse('api-v4:company:assign-regional-account-manager', kwargs={'pk': company.pk}) def test_returns_401_if_unauthenticated(self, api_client): """Test that a 401 is returned if no credentials are provided.""" company = CompanyFactory() url = self._get_url(company) response = api_client.post(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED @pytest.mark.parametrize( 'permission_codenames', ( (), (CompanyPermission.change_company, ), (CompanyPermission.change_regional_account_manager, ), ), ) def test_returns_403_if_without_permission(self, permission_codenames): """ Test that a 403 is returned if the user does not have all of the required permissions. """ company = CompanyFactory() user = create_test_user(permission_codenames=permission_codenames, dit_team=None) api_client = self.create_api_client(user=user) url = self._get_url(company) response = api_client.post(url) assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json() == { 'detail': 'You do not have permission to perform this action.' } @pytest.mark.parametrize( 'company_factory', ( pytest.param( lambda: CompanyFactory(one_list_account_owner=None, one_list_tier=None), id='no-existing-account-manager', ), pytest.param( lambda: CompanyFactory( one_list_account_owner=AdviserFactory(), one_list_tier_id=OneListTierID. tier_d_international_trade_advisers.value, ), id='existing-international-trade-adviser-account-manager', ), ), ) def test_assigns_account_manager(self, company_factory, international_trade_adviser): """ Test that an account manager can be assigned to: - a company not on the One List - a company on the One List tier 'Tier D - International Trade Adviser Accounts' """ company = company_factory() api_client = self.create_api_client(user=international_trade_adviser) url = self._get_url(company) regional_account_manager = AdviserFactory() response = api_client.post( url, {'regional_account_manager': regional_account_manager.id}) assert response.status_code == status.HTTP_204_NO_CONTENT company.refresh_from_db() assert company.one_list_account_owner == regional_account_manager assert company.one_list_tier_id == OneListTierID.tier_d_international_trade_advisers.value assert company.modified_by == international_trade_adviser @pytest.mark.parametrize( 'company_factory,expected_errors,regional_account_manager_fn', ( pytest.param( lambda: SubsidiaryFactory( global_headquarters__one_list_tier=random_obj_for_model( OneListTier), global_headquarters__one_list_account_owner=AdviserFactory( ), ), { api_settings.NON_FIELD_ERRORS_KEY: [ "A lead adviser can't be set on a subsidiary of a One List company.", ], }, lambda: AdviserFactory().pk, id='subsidiary-of-one-list-company', ), pytest.param( lambda: CompanyFactory( one_list_tier=_random_non_ita_one_list_tier(), one_list_account_owner=AdviserFactory(), ), { api_settings.NON_FIELD_ERRORS_KEY: [ "A lead adviser can't be set for companies on this One List tier.", ], }, lambda: AdviserFactory().pk, id='already-on-another-one-list-tier', ), pytest.param( lambda: CompanyFactory(), { 'regional_account_manager': [ 'This field may not be null.', ], }, lambda: None, ), ), ) def test_validation( self, company_factory, expected_errors, regional_account_manager_fn, international_trade_adviser, ): """ Test that an account manager can't be assigned: - to a company on a One List tier other than 'Tier D - International Trade Adviser Accounts' - to a company on that is a subsidiary of any One List company - without providing a new account manager """ company = company_factory() api_client = self.create_api_client(user=international_trade_adviser) url = self._get_url(company) response = api_client.post( url, {'regional_account_manager': regional_account_manager_fn()}, ) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == expected_errors
class TestUpdateOneListTierAndGlobalAccountManager(APITestMixin): """ Tests for the update company One List tier and global account manager view. (Implemented in CompanyViewSet.update_one_list_tier_and_global_account_manager().) """ @staticmethod def _get_url(company): return reverse( 'api-v4:company:assign-one-list-tier-and-global-account-manager', kwargs={'pk': company.pk}, ) def test_returns_401_if_unauthenticated(self, api_client): """Test that a 401 is returned if no credentials are provided.""" company = CompanyFactory() url = self._get_url(company) response = api_client.post(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED @pytest.mark.parametrize( 'permission_codenames', ( (), (CompanyPermission.change_company, ), (CompanyPermission.change_one_list_tier_and_global_account_manager, ), ), ) def test_returns_403_if_without_permission(self, permission_codenames): """ Test that a 403 is returned if the user does not have all of the required permissions. """ company = CompanyFactory() user = create_test_user(permission_codenames=permission_codenames, dit_team=None) api_client = self.create_api_client(user=user) url = self._get_url(company) response = api_client.post(url) assert response.status_code == status.HTTP_403_FORBIDDEN @pytest.mark.parametrize( 'company_factory', ( pytest.param( lambda: CompanyFactory(one_list_account_owner=None, one_list_tier=None), id='no-existing-global-account-manager', ), pytest.param( lambda: CompanyFactory( one_list_account_owner=AdviserFactory(), one_list_tier=random_non_ita_one_list_tier(), ), id='existing-global-account-manager', ), ), ) @pytest.mark.django_db def test_assigns_one_list_tier_and_global_account_manager( self, company_factory, one_list_editor, ): """ Test that a One List tier and global account manager can be assigned to: - a company not on the One List - a company on random One List tier except 'Tier D - International Trade Adviser Accounts' """ company = company_factory() api_client = self.create_api_client(user=one_list_editor) url = self._get_url(company) new_one_list_tier = random_non_ita_one_list_tier() global_account_manager = AdviserFactory() response = api_client.post( url, { 'one_list_tier': new_one_list_tier.id, 'global_account_manager': global_account_manager.id, }, ) assert response.status_code == status.HTTP_204_NO_CONTENT company.refresh_from_db() assert company.one_list_account_owner == global_account_manager assert company.one_list_tier_id == new_one_list_tier.pk assert company.modified_by == one_list_editor # Check that object version is stored correctly versions = Version.objects.get_for_object(company) assert versions.count() == 1 assert versions[0].field_dict[ 'one_list_tier_id'] == new_one_list_tier.id assert versions[0].field_dict[ 'one_list_account_owner_id'] == global_account_manager.id @pytest.mark.parametrize( 'company_factory,adviser_id_fn,new_one_list_tier_id_fn,expected_errors', ( pytest.param( lambda: CompanyFactory( one_list_tier=random_obj_for_model(OneListTier), one_list_account_owner=AdviserFactory(), ), lambda: None, lambda: None, { 'global_account_manager': [ 'This field may not be null.', ], 'one_list_tier': [ 'This field may not be null.', ], }, id='required', ), pytest.param( lambda: SubsidiaryFactory( global_headquarters__one_list_tier=random_obj_for_model( OneListTier), global_headquarters__one_list_account_owner=AdviserFactory( ), ), lambda: AdviserFactory().pk, lambda: random_non_ita_one_list_tier().pk, { api_settings.NON_FIELD_ERRORS_KEY: [ 'A subsidiary cannot be on One List.', ], }, id='subsidiary-of-one-list-company', ), pytest.param( lambda: CompanyFactory( one_list_tier_id=OneListTierID. tier_d_international_trade_advisers.value, one_list_account_owner=AdviserFactory(), ), lambda: AdviserFactory().pk, lambda: random_non_ita_one_list_tier().pk, { api_settings.NON_FIELD_ERRORS_KEY: [ 'A company on this One List tier can only be changed by ITA.', ], }, id='company-one-list-details-can-only-be-changed-by-ita', ), pytest.param( lambda: CompanyFactory( one_list_tier=random_non_ita_one_list_tier(), one_list_account_owner=AdviserFactory(), ), lambda: AdviserFactory().pk, lambda: OneListTierID.tier_d_international_trade_advisers. value, { 'one_list_tier': [ 'A company can only have this One List tier assigned by ITA.', ], }, id='company-can-only-have-this-one-list-tier-assigned-by-ita', ), ), ) @pytest.mark.django_db def test_validation( self, company_factory, adviser_id_fn, new_one_list_tier_id_fn, expected_errors, one_list_editor, ): """ Test that a One List tier and account manager can't be assigned to: - a company on a One List tier 'Tier D - International Trade Adviser Accounts' - a company on that is a subsidiary of any One List company """ company = company_factory() api_client = self.create_api_client(user=one_list_editor) url = self._get_url(company) response = api_client.post( url, { 'one_list_tier': new_one_list_tier_id_fn(), 'global_account_manager': adviser_id_fn(), }, ) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == expected_errors
class TestConfirmMergeViewPost(AdminTestMixin): """Tests form submission in the 'Confirm merge' view.""" @pytest.mark.parametrize( 'factory_relation_kwarg,creates_contacts', ( ('num_company_list_items', False), ('num_contacts', True), ('num_interactions', True), ('num_investment_projects', False), ('num_orders', True), ('num_referrals', False), ), ) @pytest.mark.parametrize('num_related_objects', (0, 1, 3)) def test_merge_succeeds( self, factory_relation_kwarg, creates_contacts, num_related_objects, ): """ Test that the merge succeeds and the source company is marked as a duplicate when the source company has various amounts of contacts, interactions, investment projects and orders. """ creation_time = datetime(2010, 12, 1, 15, 0, 10, tzinfo=utc) with freeze_time(creation_time): source_company = _company_factory( **{factory_relation_kwarg: num_related_objects}, ) target_company = CompanyFactory() source_interactions = list(source_company.interactions.all()) source_contacts = list(source_company.contacts.all()) source_orders = list(source_company.orders.all()) source_referrals = list(source_company.referrals.all()) source_company_list_items = list( source_company.company_list_items.all()) source_investment_projects_by_field = { investment_project_field: list( InvestmentProject.objects.filter( **{ investment_project_field: source_company, }), ) for investment_project_field in INVESTMENT_PROJECT_COMPANY_FIELDS } # Note that the interaction and order factories also create contacts # source_num_interactions + source_num_contacts + source_num_orders assert len(source_contacts) == (num_related_objects if creates_contacts else 0) confirm_merge_url = _make_confirm_merge_url(source_company, target_company) merge_time = datetime(2011, 2, 1, 14, 0, 10, tzinfo=utc) with freeze_time(merge_time): response = self.client.post(confirm_merge_url, follow=True) assert response.status_code == status.HTTP_200_OK assert len(response.redirect_chain) == 1 assert response.redirect_chain[0][0] == _get_changelist_url() messages = list(response.context['messages']) assert len(messages) == 1 assert messages[0].level == django_messages.SUCCESS merge_entries = [] if len(source_interactions) > 0: interaction_noun = _get_verbose_name(len(source_interactions), Interaction) merge_entries.append( f'{len(source_interactions)} {interaction_noun}', ) if len(source_contacts) > 0: contact_noun = _get_verbose_name(len(source_contacts), Contact) merge_entries.append(f'{len(source_contacts)} {contact_noun}', ) for field, investment_projects in source_investment_projects_by_field.items( ): num_investment_projects = len(investment_projects) if num_investment_projects > 0: project_noun = _get_verbose_name(num_investment_projects, InvestmentProject) description = FIELD_TO_DESCRIPTION_MAPPING.get(field) merge_entries.append( f'{num_investment_projects} {project_noun}{description}', ) if len(source_orders) > 0: order_noun = _get_verbose_name(len(source_orders), Order) merge_entries.append(f'{len(source_orders)} {order_noun}', ) if len(source_referrals) > 0: referral_noun = _get_verbose_name(len(source_referrals), CompanyReferral) merge_entries.append(f'{len(source_referrals)} {referral_noun}', ) if len(source_company_list_items) > 0: company_list_item_noun = _get_verbose_name( len(source_company_list_items), CompanyListItem, ) merge_entries.append( f'{len(source_company_list_items)} {company_list_item_noun}', ) merge_entries = ', '.join(merge_entries) match = re.match( r'^Merge complete – (?P<merge_entries>.*)' r' moved from' r' <a href="(?P<source_company_url>.*)" target="_blank">(?P<source_company>.*)</a>' r' to' r' <a href="(?P<target_company_url>.*)" target="_blank">(?P<target_company>.*)</a>' r'\.$', messages[0].message, ) assert match assert match.groupdict() == { 'merge_entries': merge_entries, 'source_company_url': escape(source_company.get_absolute_url()), 'source_company': escape(str(source_company)), 'target_company_url': escape(target_company.get_absolute_url()), 'target_company': escape(str(target_company)), } source_non_project_related_objects = [ *source_company_list_items, *source_contacts, *source_interactions, *source_orders, *source_referrals, ] for obj in chain( source_non_project_related_objects, chain.from_iterable( source_investment_projects_by_field.values()), ): obj.refresh_from_db() assert all(obj.company == target_company for obj in source_non_project_related_objects) assert all(obj.modified_on == creation_time for obj in source_non_project_related_objects) for field, investment_projects in source_investment_projects_by_field.items( ): assert all( getattr(obj, field) == target_company for obj in investment_projects) assert all(obj.modified_on == creation_time for obj in investment_projects) source_company.refresh_from_db() assert source_company.archived assert source_company.archived_by == self.user assert source_company.archived_on == merge_time assert source_company.archived_reason == ( f'This record is no longer in use and its data has been transferred ' f'to {target_company} for the following reason: Duplicate record.') assert source_company.modified_by == self.user assert source_company.modified_on == merge_time assert source_company.transfer_reason == Company.TransferReason.DUPLICATE assert source_company.transferred_by == self.user assert source_company.transferred_on == merge_time assert source_company.transferred_to == target_company def test_successful_merge_creates_revision(self): """Test that a revision is created following a successful merge.""" source_company = CompanyFactory() target_company = CompanyFactory() source_contacts = ContactFactory.create_batch(2, company=source_company) confirm_merge_url = _make_confirm_merge_url(source_company, target_company) frozen_time = datetime(2011, 2, 1, 14, 0, 10, tzinfo=utc) with freeze_time(frozen_time): response = self.client.post(confirm_merge_url, follow=True) assert response.status_code == status.HTTP_200_OK assert len(response.redirect_chain) == 1 assert response.redirect_chain[0][0] == _get_changelist_url() source_company_versions = Version.objects.get_for_object( source_company) assert source_company_versions.count() == 1 reversion = source_company_versions[0].revision assert reversion.date_created == frozen_time assert reversion.get_comment() == REVERSION_REVISION_COMMENT assert reversion.user == self.user contact_0_versions = Version.objects.get_for_object(source_contacts[0]) assert contact_0_versions.count() == 1 assert contact_0_versions[0].revision == reversion contact_1_versions = Version.objects.get_for_object(source_contacts[1]) assert contact_1_versions.count() == 1 assert contact_1_versions[0].revision == reversion @pytest.mark.parametrize( 'source_company_factory,target_company_factory', ( ( CompanyFactory, ArchivedCompanyFactory, ), ( SubsidiaryFactory, CompanyFactory, ), ( lambda: SubsidiaryFactory().global_headquarters, CompanyFactory, ), ), ) def test_merge_fails(self, source_company_factory, target_company_factory): """ Test that the merge fails when the source company cannot be merged into the target company. """ source_company = source_company_factory() target_company = target_company_factory() source_interactions = list(source_company.interactions.all()) source_contacts = list(source_company.contacts.all()) confirm_merge_url = _make_confirm_merge_url(source_company, target_company) response = self.client.post(confirm_merge_url) assert response.status_code == status.HTTP_200_OK messages = list(response.context['messages']) assert len(messages) == 1 assert messages[0].level == django_messages.ERROR assert messages[0].message == ( f'Merging failed – merging {source_company} into {target_company} is not allowed.' ) for obj in chain(source_interactions, source_contacts): obj.refresh_from_db() assert all(obj.company == source_company for obj in source_interactions) assert all(obj.company == source_company for obj in source_contacts) source_company.refresh_from_db() assert not source_company.archived assert source_company.transfer_reason == '' assert not source_company.transferred_by assert not source_company.transferred_on assert not source_company.transferred_to
class TestSelectPrimaryCompanyViewGet(AdminTestMixin): """Tests form rendering in the 'Select primary company' view.""" @pytest.mark.parametrize( 'data', ( {}, { 'company_1': '12345', 'company_2': '64567', }, { 'company_1': '', 'company_2': '', }, { 'company_1': '12345', }, { 'company_1': lambda: str(CompanyFactory().pk), 'company_2': '64567', }, { 'company_1': '13495', 'company_2': lambda: str(CompanyFactory().pk), }, ), ) def test_returns_400_if_invalid_companies_passed(self, data): """ Test that a 400 is returned when invalid values are passed for company_1 or company_2. This could only happen if the query string was manipulated, or one of the referenced companies was deleted. """ for key, value in data.items(): if callable(value): data[key] = value() select_primary_route_name = admin_urlname( Company._meta, 'merge-select-primary-company') select_primary_url = reverse(select_primary_route_name) response = self.client.get(select_primary_url, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.parametrize( 'swap', (True, False), ) @pytest.mark.parametrize( 'company_1_factory,company_2_factory', ( ( ArchivedCompanyFactory, CompanyFactory, ), ( CompanyFactory, SubsidiaryFactory, ), ( CompanyFactory, lambda: SubsidiaryFactory().global_headquarters, ), ), ids=[ 'archived-company', 'subsidiary', 'global-headquarters', ], ) def test_radio_button_correctly_enabled_or_disabled( self, company_1_factory, company_2_factory, swap, ): """ Tests that the radio button to select a company is disabled if it is archived, or the other company has an OMIS order or other related object (other than an interaction, contact or investment project). """ company_1 = (company_2_factory if swap else company_1_factory)() company_2 = (company_1_factory if swap else company_2_factory)() company_1_disabled = not swap company_2_disabled = swap select_primary_route_name = admin_urlname( Company._meta, 'merge-select-primary-company') select_primary_url = reverse(select_primary_route_name) response = self.client.get( select_primary_url, data={ 'company_1': str(company_1.pk), 'company_2': str(company_2.pk), }, ) assert response.status_code == status.HTTP_200_OK expected_radio_1_html = _get_radio_html(1, company_1_disabled) assert _html_count_occurrences(expected_radio_1_html, response.rendered_content) == 1 expected_radio_2_html = _get_radio_html(2, company_2_disabled) assert _html_count_occurrences(expected_radio_2_html, response.rendered_content) == 1
class TestSelectPrimaryCompanyViewPost(AdminTestMixin): """Tests form submission in the 'Select primary company' view.""" @pytest.mark.parametrize('selected_company', ('1', '2')) def test_proceeds_if_company_chosen(self, selected_company): """Test that if a valid selection is made, the user is redirected to the change list.""" company_1 = CompanyFactory() company_2 = CompanyFactory() select_primary_route_name = admin_urlname( Company._meta, 'merge-select-primary-company') select_primary_query_args = { 'company_1': str(company_1.pk), 'company_2': str(company_2.pk), } select_primary_url = reverse_with_query_string( select_primary_route_name, select_primary_query_args, ) response = self.client.post( select_primary_url, follow=True, data={ 'selected_company': selected_company, }, ) assert response.status_code == status.HTTP_200_OK assert len(response.redirect_chain) == 1 confirm_merge_route_name = admin_urlname(Company._meta, 'merge-confirm') confirm_merge_query_args = { 'source_company': (company_1 if selected_company != '1' else company_2).pk, 'target_company': (company_1 if selected_company == '1' else company_2).pk, } confirm_merge_url = reverse_with_query_string( confirm_merge_route_name, confirm_merge_query_args, ) assert response.redirect_chain[0][0] == confirm_merge_url @pytest.mark.parametrize('swap', (False, True)) @pytest.mark.parametrize( 'company_1_factory,company_2_factory,expected_error', ( ( ArchivedCompanyFactory, CompanyFactory, 'The company selected is archived.', ), ( CompanyFactory, SubsidiaryFactory, 'The other company has related records which can’t be moved to the selected ' 'company.', ), ( CompanyFactory, lambda: SubsidiaryFactory().global_headquarters, 'The other company has related records which can’t be moved to the selected ' 'company.', ), ), ) def test_error_displayed_if_invalid_selection_made( self, swap, company_1_factory, company_2_factory, expected_error, ): """Tests that if an invalid selection is submitted, an error is returned.""" company_1 = (company_2_factory if swap else company_1_factory)() company_2 = (company_1_factory if swap else company_2_factory)() selected_company = 2 if swap else 1 select_primary_route_name = admin_urlname( Company._meta, 'merge-select-primary-company') select_primary_query_args = { 'company_1': str(company_1.pk), 'company_2': str(company_2.pk), } select_primary_url = reverse_with_query_string( select_primary_route_name, select_primary_query_args, ) response = self.client.post( select_primary_url, data={ 'company_1': str(company_1.pk), 'company_2': str(company_2.pk), 'selected_company': selected_company, }, ) assert response.status_code == status.HTTP_200_OK form = response.context['form'] assert form.errors == { NON_FIELD_ERRORS: [expected_error], }