def _company_factory(num_interactions, num_contacts, num_orders): """Factory for a company that has companies, interactions and OMIS orders.""" company = CompanyFactory() ContactFactory.create_batch(num_contacts, company=company) CompanyInteractionFactory.create_batch(num_interactions, company=company) OrderFactory.create_batch(num_orders, company=company) return company
def _company_factory( num_interactions=0, num_contacts=0, num_investment_projects=0, num_orders=0, num_referrals=0, num_company_list_items=0, num_pipeline_items=0, ): """ Factory for a company that has companies, interactions, investment projects and OMIS orders. """ company = CompanyFactory() ContactFactory.create_batch(num_contacts, company=company) CompanyInteractionFactory.create_batch(num_interactions, company=company) CompanyReferralFactory.create_batch(num_referrals, company=company, contact=None) OrderFactory.create_batch(num_orders, company=company) CompanyListItemFactory.create_batch(num_company_list_items, company=company) PipelineItemFactory.create_batch(num_pipeline_items, company=company) fields_iter = cycle(INVESTMENT_PROJECT_COMPANY_FIELDS) fields = islice(fields_iter, 0, num_investment_projects) InvestmentProjectFactory.create_batch( num_investment_projects, **{field: company for field in fields}, ) return company
def test_all(self): """Test getting all contacts""" ContactFactory.create_batch(5) url = reverse('api-v3:contact:list') response = self.api_client.get(url) assert response.status_code == status.HTTP_200_OK assert response.data['count'] == 5
def unrelated_objects(): """ Create some objects not related to a known company. This is used in tests below to make sure objects unrelated to the company being merged do not affect the counts of objects that will be affected by the merge. """ ContactFactory.create_batch(2) CompanyInteractionFactory.create_batch(2) OrderFactory.create_batch(2) InvestmentProjectFactory.create_batch(2)
def test_email_filter(self, opensearch_with_collector, contacts, filter_, expected): """Tests the email filter""" ContactFactory.create_batch(len(contacts), email=factory.Iterator(contacts)) opensearch_with_collector.flush_and_refresh() response = self.api_client.post( reverse('api-v3:search:contact'), data=dict(email=filter_), ) assert response.status_code == status.HTTP_200_OK assert {res['email'] for res in response.data['results']} == expected
def _company_factory( num_interactions=0, num_contacts=0, num_orders=0, num_referrals=0, num_company_list_items=0, ): """Factory for a company that has companies, interactions and OMIS orders.""" company = CompanyFactory() ContactFactory.create_batch(num_contacts, company=company) CompanyInteractionFactory.create_batch(num_interactions, company=company) CompanyReferralFactory.create_batch(num_referrals, company=company, contact=None) OrderFactory.create_batch(num_orders, company=company) CompanyListItemFactory.create_batch(num_company_list_items, company=company) return company
def test_all_without_view_document_permission(self): """Test getting all contacts without view document permission.""" ContactFactory.create_batch( 5, archived_documents_url_path='https://some-docs') user = create_test_user(permission_codenames=('view_contact', ), ) api_client = self.create_api_client(user=user) url = reverse('api-v3:contact:list') response = api_client.get(url) assert response.status_code == status.HTTP_200_OK assert response.data['count'] == 5 assert all('archived_documents_url_path' not in contact for contact in response.data['results'])
def make_matched_rows(num_records): """Make multiple interaction CSV rows that should pass contact matching.""" adviser = AdviserFactory( first_name='Adviser for', last_name='Matched interaction', ) service = random_service() communication_channel = random_communication_channel() contacts = ContactFactory.create_batch( num_records, email=factory.Sequence(lambda i: f'unique{i}@matched.uk'), ) return [ { 'theme': Interaction.THEMES.export, 'kind': Interaction.KINDS.interaction, 'date': '01/01/2018', 'adviser_1': adviser.name, 'contact_email': contact.email, 'service': service.name, 'communication_channel': communication_channel.name, } for contact in contacts ]
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
def test_contact_dbmodels_to_es_documents(es): """Tests conversion of db models to Elasticsearch documents.""" contacts = ContactFactory.create_batch(2) result = ESContact.db_objects_to_es_documents(contacts) assert len(list(result)) == len(contacts)
def test_contact_dbmodels_to_documents(opensearch): """Tests conversion of db models to OpenSearch documents.""" contacts = ContactFactory.create_batch(2) result = SearchContact.db_objects_to_documents(contacts) assert len(list(result)) == len(contacts)
def test_filter_by_company(self): """Test getting contacts by company id""" company1 = CompanyFactory() company2 = CompanyFactory() ContactFactory.create_batch(3, company=company1) contacts = ContactFactory.create_batch(2, company=company2) url = reverse('api-v3:contact:list') response = self.api_client.get(url, data={'company_id': company2.id}) assert response.status_code == status.HTTP_200_OK assert response.data['count'] == 2 expected_contacts = {str(contact.id) for contact in contacts} assert {contact['id'] for contact in response.data['results']} == expected_contacts
def _company_factory(num_interactions, num_contacts, num_investment_projects, num_orders): """ Factory for a company that has companies, interactions, investment projects and OMIS orders. """ company = CompanyFactory() ContactFactory.create_batch(num_contacts, company=company) CompanyInteractionFactory.create_batch(num_interactions, company=company) OrderFactory.create_batch(num_orders, company=company) fields_iter = cycle(INVESTMENT_PROJECT_COMPANY_FIELDS) fields = islice(fields_iter, 0, num_investment_projects) InvestmentProjectFactory.create_batch( num_investment_projects, **{field: company for field in fields}, ) return company
def test_company_sector_descends_filter( self, hierarchical_sectors, opensearch_with_collector, sector_level, ): """Test the company_sector_descends filter.""" num_sectors = len(hierarchical_sectors) sectors_ids = [sector.pk for sector in hierarchical_sectors] companies = CompanyFactory.create_batch( num_sectors, sector_id=factory.Iterator(sectors_ids), ) contacts = ContactFactory.create_batch( 3, company=factory.Iterator(companies), ) other_companies = CompanyFactory.create_batch( 3, sector=factory.LazyFunction(lambda: random_obj_for_queryset( SectorModel.objects.exclude(pk__in=sectors_ids), )), ) ContactFactory.create_batch( 3, company=factory.Iterator(other_companies), ) opensearch_with_collector.flush_and_refresh() url = reverse('api-v3:search:contact') body = { 'company_sector_descends': hierarchical_sectors[sector_level].pk, } response = self.api_client.post(url, body) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert response_data['count'] == num_sectors - sector_level actual_ids = { uuid.UUID(contact['id']) for contact in response_data['results'] } expected_ids = {contact.pk for contact in contacts[sector_level:]} assert actual_ids == expected_ids
def test_get_contact_names(self, num_contacts, expected_display_value): """Test that contact names are formatted as expected.""" interaction = CompanyInteractionFactory( contacts=ContactFactory.create_batch(num_contacts), ) interaction_admin = InteractionAdmin(Interaction, site) first_contact = interaction.contacts.order_by('pk').first() formatted_expected_display_value = expected_display_value.format( first_contact_name=first_contact.name if first_contact else '', ) assert interaction_admin.get_contact_names( interaction) == formatted_expected_display_value
def test_intelligent_homepage_limit(self, setup_es): """Test the limit param.""" CompanyInteractionFactory.create_batch(15, dit_adviser=self.user) ContactFactory.create_batch(15, created_by=self.user) setup_es.indices.refresh() url = reverse('dashboard:intelligent-homepage') response = self.api_client.get( url, data={ 'limit': 10, }, ) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert len(response_data['contacts']) == 10 assert len(response_data['interactions']) == 10
def test_search_contact_by_archived(self, setup_es, setup_data, archived): """Tests filtering by archived.""" ContactFactory.create_batch(5, archived=True) setup_es.indices.refresh() url = reverse('api-v3:search:contact') response = self.api_client.post( url, data={ 'archived': archived, }, ) assert response.status_code == status.HTTP_200_OK assert response.data['count'] > 0 assert all(result['archived'] == archived for result in response.data['results'])
def test_run(s3_stubber, caplog): """Test that the command updates the specified records (ignoring ones with errors).""" caplog.set_level('ERROR') original_datetime = datetime(2017, 1, 1, tzinfo=timezone.utc) with freeze_time(original_datetime): accepts_dit_email_marketing_values = [True, False, True] contacts = ContactFactory.create_batch( 3, accepts_dit_email_marketing=factory.Iterator( accepts_dit_email_marketing_values), ) bucket = 'test_bucket' object_key = 'test_key' csv_content = f"""id,accepts_dit_email_marketing 00000000-0000-0000-0000-000000000000,true {contacts[0].pk},false {contacts[1].pk},false {contacts[2].pk},blah """ s3_stubber.add_response( 'get_object', { 'Body': BytesIO(csv_content.encode(encoding='utf-8')), }, expected_params={ 'Bucket': bucket, 'Key': object_key, }, ) with freeze_time('2018-11-11 00:00:00'): call_command('update_contact_accepts_dit_email_marketing', bucket, object_key) for contact in contacts: contact.refresh_from_db() assert 'Contact matching query does not exist' in caplog.text assert 'Must be a valid boolean' in caplog.text assert len(caplog.records) == 2 assert [contact.accepts_dit_email_marketing for contact in contacts] == [ False, False, True, ] assert all(contact.modified_on == original_datetime for contact in contacts)
def test_interaction_permission(self, setup_es): """Test that the interaction view permission is enforced.""" requester = create_test_user(permission_codenames=('view_contact', ), ) CompanyInteractionFactory.create_batch(5, dit_adviser=requester) ContactFactory.create_batch(5, created_by=requester) setup_es.indices.refresh() api_client = self.create_api_client(user=requester) url = reverse('dashboard:intelligent-homepage') response = api_client.get( url, data={ 'limit': 10, }, ) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert len(response_data['contacts']) == 5 assert response_data['interactions'] == []
def test_simulate(s3_stubber, caplog): """Test that the command simulates updates if --simulate is passed in.""" caplog.set_level('ERROR') original_datetime = datetime(2017, 1, 1, tzinfo=timezone.utc) with freeze_time(original_datetime): before_accepts_dit_email_marketing_values = [True, False] contacts = ContactFactory.create_batch( 2, accepts_dit_email_marketing=factory.Iterator( before_accepts_dit_email_marketing_values, ), ) bucket = 'test_bucket' object_key = 'test_key' csv_content = f"""id,accepts_dit_email_marketing 00000000-0000-0000-0000-000000000000,true {contacts[0].pk},false {contacts[1].pk},false """ s3_stubber.add_response( 'get_object', { 'Body': BytesIO(csv_content.encode(encoding='utf-8')), }, expected_params={ 'Bucket': bucket, 'Key': object_key, }, ) with freeze_time('2018-11-11 00:00:00'): call_command( 'update_contact_accepts_dit_email_marketing', bucket, object_key, simulate=True, ) for contact in contacts: contact.refresh_from_db() assert 'Contact matching query does not exist' in caplog.text assert len(caplog.records) == 1 after_accepts_dit_email_marketing_values = [ contact.accepts_dit_email_marketing for contact in contacts ] assert after_accepts_dit_email_marketing_values == before_accepts_dit_email_marketing_values
def test_filter_by_created_on_exists(self, setup_es, created_on_exists): """Tests filtering contact by created_on exists.""" ContactFactory.create_batch(3) no_created_on = ContactFactory.create_batch(3) for contact in no_created_on: contact.created_on = None contact.save() setup_es.indices.refresh() url = reverse('api-v3:search:contact') request_data = { 'created_on_exists': created_on_exists, } response = self.api_client.post(url, request_data) assert response.status_code == status.HTTP_200_OK response_data = response.json() results = response_data['results'] assert response_data['count'] == 3 assert all((not result['created_on'] is None) == created_on_exists for result in results)
def make_multiple_matches_rows(num_records): """Make multiple interaction CSV rows that should have multiple contact matches.""" adviser = AdviserFactory( first_name='Adviser for', last_name='Multi-matched interaction', ) service = random_service() communication_channel = random_communication_channel() contact_email = '*****@*****.**' ContactFactory.create_batch(2, email=contact_email) return [ { 'theme': Interaction.THEMES.export, 'kind': Interaction.KINDS.interaction, 'date': '01/01/2018', 'adviser_1': adviser.name, 'contact_email': contact_email, 'service': service.name, 'communication_channel': communication_channel.name, } for _ in range(num_records) ]
def test_cannot_add_more_contacts_to_event_service_delivery(self): """Test that an event service delivery cannot be updated to have multiple contacts.""" service_delivery = EventServiceDeliveryFactory() new_contacts = ContactFactory.create_batch(2, company=service_delivery.company) url = reverse('api-v3:interaction:item', kwargs={'pk': service_delivery.pk}) request_data = { 'contacts': [{'id': contact.pk} for contact in new_contacts], } response = self.api_client.patch(url, data=request_data) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == { 'contacts': ['Only one contact can be provided for event service deliveries.'], }
def test_opts_out_contacts(self, encoding): """ Test that accepts_dit_email_marketing is updated for the contacts specified in the CSV file. """ filename = 'filea.csv' emails = [ 'test1@datahub', 'test1@datahub', 'test2@datahub', 'test2@datahub', 'test3@datahub', 'test4@datahub', ] marketing_status = [True, True, True, False, True, True] creation_time = datetime(2011, 2, 1, 14, 0, 10, tzinfo=utc) with freeze_time(creation_time): contacts = ContactFactory.create_batch( len(emails), email=factory.Iterator(emails), accepts_dit_email_marketing=factory.Iterator(marketing_status), modified_by=None, ) file = io.BytesIO("""email\r test1@datahub\r TEST2@datahub\r test6@datahub\r """.encode(encoding=encoding)) file.name = filename url = reverse( admin_urlname(Contact._meta, 'load-email-marketing-opt-outs'), ) post_time = datetime(2014, 5, 3, 19, 0, 16, tzinfo=utc) with freeze_time(post_time): response = self.client.post( url, follow=True, data={ 'email_list': file, }, ) assert response.status_code == status.HTTP_200_OK assert len(response.redirect_chain) == 1 change_list_url = reverse(admin_urlname(Contact._meta, 'changelist')) assert response.redirect_chain[0][0] == change_list_url for contact in contacts: contact.refresh_from_db() assert [contact.accepts_dit_email_marketing for contact in contacts] == [ False, False, False, False, True, True, ] assert [contact.modified_on for contact in contacts] == [ post_time, post_time, post_time, creation_time, creation_time, creation_time, ] assert [contact.modified_by for contact in contacts] == [ self.user, self.user, self.user, None, None, None, ] messages = list(response.context['messages']) assert len(messages) == 2 assert messages[0].level == django_messages.SUCCESS assert messages[0].message == ( '3 contacts opted out of marketing emails and 1 contacts already opted out' ) assert messages[1].level == django_messages.WARNING assert messages[1].message == '1 email addresses did not match a contact'
def test_export( self, es_with_collector, request_sortby, orm_ordering, ): """Test export of contact search results.""" ArchivedContactFactory() ContactWithOwnAddressFactory() ContactFactory() # These are to test date of and team of latest interaction a bit more thoroughly CompanyInteractionFactory.create_batch(2) CompanyInteractionFactory(contacts=ContactFactory.create_batch(2)) interaction_with_multiple_teams = CompanyInteractionFactory() InteractionDITParticipantFactory.create_batch( 2, interaction=interaction_with_multiple_teams, ) es_with_collector.flush_and_refresh() data = {} if request_sortby: data['sortby'] = request_sortby url = reverse('api-v3:search:contact-export') with freeze_time('2018-01-01 11:12:13'): response = self.api_client.post(url, data=data) assert response.status_code == status.HTTP_200_OK assert parse_header(response.get('Content-Type')) == ('text/csv', { 'charset': 'utf-8' }) assert parse_header(response.get('Content-Disposition')) == ( 'attachment', { 'filename': 'Data Hub - Contacts - 2018-01-01-11-12-13.csv' }, ) sorted_contacts = Contact.objects.annotate( computed_address_country_name=Coalesce( 'address_country__name', 'company__address_country__name', ), ).order_by( orm_ordering, 'pk', ) reader = DictReader(StringIO(response.getvalue().decode('utf-8-sig'))) assert reader.fieldnames == list( SearchContactExportAPIView.field_titles.values()) # E123 is ignored as there are seemingly unresolvable indentation errors in the dict below expected_row_data = [ # noqa: E123 { 'Name': contact.name, 'Job title': contact.job_title, 'Date created': contact.created_on, 'Archived': contact.archived, 'Link': f'{settings.DATAHUB_FRONTEND_URL_PREFIXES["contact"]}/{contact.pk}', 'Company': get_attr_or_none(contact, 'company.name'), 'Company sector': get_attr_or_none(contact, 'company.sector.name'), 'Company link': f'{settings.DATAHUB_FRONTEND_URL_PREFIXES["company"]}/{contact.company.pk}', 'Company UK region': get_attr_or_none(contact, 'company.uk_region.name'), 'Country': contact.company.address_country.name if contact.address_same_as_company else contact.address_country.name, 'Postcode': contact.company.address_postcode if contact.address_same_as_company else contact.address_postcode, 'Phone number': ' '.join( (contact.telephone_countrycode, contact.telephone_number)), 'Email address': contact.email, 'Accepts DIT email marketing': contact.accepts_dit_email_marketing, 'Date of latest interaction': max(contact.interactions.all(), key=attrgetter('date')).date if contact.interactions.all() else None, 'Teams of latest interaction': _format_interaction_team_names( max(contact.interactions.all(), key=attrgetter('date')), ) if contact.interactions.exists() else None, 'Created by team': get_attr_or_none(contact, 'created_by.dit_team.name'), } for contact in sorted_contacts ] actual_row_data = [dict(row) for row in reader] assert actual_row_data == format_csv_data(expected_row_data)
def company_with_contacts_factory(): """Factory for a company with contacts.""" company = CompanyFactory() ContactFactory.create_batch(3, company=company) return company
def test_export( self, opensearch_with_collector, request_sortby, orm_ordering, requests_mock, accepts_dit_email_marketing, ): """Test export of contact search results.""" ArchivedContactFactory() ContactWithOwnAddressFactory() ContactFactory() ContactWithOwnAreaFactory() # These are to test date of and team of latest interaction a bit more thoroughly CompanyInteractionFactory.create_batch(2) CompanyInteractionFactory(contacts=ContactFactory.create_batch(2)) interaction_with_multiple_teams = CompanyInteractionFactory() InteractionDITParticipantFactory.create_batch( 2, interaction=interaction_with_multiple_teams, ) opensearch_with_collector.flush_and_refresh() data = {} if request_sortby: data['sortby'] = request_sortby url = reverse('api-v3:search:contact-export') with freeze_time('2018-01-01 11:12:13'): response = self.api_client.post(url, data=data) assert response.status_code == status.HTTP_200_OK assert parse_header(response.get('Content-Type')) == ('text/csv', { 'charset': 'utf-8' }) assert parse_header(response.get('Content-Disposition')) == ( 'attachment', { 'filename': 'Data Hub - Contacts - 2018-01-01-11-12-13.csv' }, ) sorted_contacts = Contact.objects.annotate( computed_address_country_name=Coalesce( 'address_country__name', 'company__address_country__name', ), ).order_by( orm_ordering, 'pk', ) matcher = requests_mock.get( f'{settings.CONSENT_SERVICE_BASE_URL}' f'{CONSENT_SERVICE_PERSON_PATH_LOOKUP}', text=generate_hawk_response({ 'results': [{ 'email': contact.email, 'consents': [ CONSENT_SERVICE_EMAIL_CONSENT_TYPE, ] if accepts_dit_email_marketing else [], } for contact in sorted_contacts], }), status_code=status.HTTP_200_OK, ) reader = DictReader(StringIO(response.getvalue().decode('utf-8-sig'))) assert reader.fieldnames == list( SearchContactExportAPIView.field_titles.values()) expected_row_data = format_csv_data([{ 'Name': contact.name, 'Job title': contact.job_title, 'Date created': contact.created_on, 'Archived': contact.archived, 'Link': f'{settings.DATAHUB_FRONTEND_URL_PREFIXES["contact"]}/{contact.pk}', 'Company': get_attr_or_none(contact, 'company.name'), 'Company sector': get_attr_or_none(contact, 'company.sector.name'), 'Company link': f'{settings.DATAHUB_FRONTEND_URL_PREFIXES["company"]}/{contact.company.pk}', 'Company UK region': get_attr_or_none(contact, 'company.uk_region.name'), 'Area': (contact.company.address_area and contact.company.address_area.name) if contact.address_same_as_company else (contact.address_area and contact.address_area.name), 'Country': contact.company.address_country.name if contact.address_same_as_company else contact.address_country.name, 'Postcode': contact.company.address_postcode if contact.address_same_as_company else contact.address_postcode, 'Phone number': contact.full_telephone_number, 'Email address': contact.email, 'Accepts DIT email marketing': accepts_dit_email_marketing, 'Date of latest interaction': max(contact.interactions.all(), key=attrgetter('date')).date if contact.interactions.all() else None, 'Teams of latest interaction': _format_interaction_team_names( max(contact.interactions.all(), key=attrgetter('date')), ) if contact.interactions.exists() else None, 'Created by team': get_attr_or_none(contact, 'created_by.dit_team.name'), } for contact in sorted_contacts]) actual_row_data = [dict(row) for row in reader] assert len(actual_row_data) == len(expected_row_data) for index, row in enumerate(actual_row_data): assert row == expected_row_data[index] assert matcher.call_count == 1 assert matcher.last_request.query == urllib.parse.urlencode( {'email': [c.email for c in sorted_contacts]}, doseq=True, )