def dnb_company_search_datahub_companies(): """ Creates Data Hub companies for hydrating DNB search results with. """ # Company with no interactions CompanyFactory(duns_number='1234567', id='6083b732-b07a-42d6-ada4-c8082293285b') # Company with two interactions company = CompanyFactory(duns_number='7654321', id='6083b732-b07a-42d6-ada4-c99999999999') interaction_date = datetime.datetime(year=2019, month=8, day=1, hour=16, minute=0, tzinfo=utc) with freeze_time(interaction_date): CompanyInteractionFactory( id='6083b732-b07a-42d6-ada4-222222222222', date=interaction_date, subject='Meeting with Joe Bloggs', company=company, ) older_interaction_date = datetime.datetime(year=2018, month=8, day=1, tzinfo=utc) with freeze_time(older_interaction_date): CompanyInteractionFactory( id='6083b732-b07a-42d6-ada4-111111111111', date=older_interaction_date, subject='Meeting with John Smith', company=company, )
def test_filter_by_one_list_tier_group( self, es_with_collector, ): """ Test that we can filter by one list tier group. """ one_list_tier = OneListTier.objects.all().order_by('?')[0] company_1 = CompanyFactory( name='Global HQ Ltd', one_list_tier=one_list_tier, ) company_2 = CompanyFactory( name='Regional Subsidiary Ltd', global_headquarters=company_1, ) CompanyInteractionFactory( subject='Global HQ chat', company=company_1, ) CompanyInteractionFactory( subject='Regional Subsidiary chat', company=company_2, ) es_with_collector.flush_and_refresh() url = reverse('api-v3:search:interaction') response = self.api_client.post( url, data={ 'company_one_list_group_tier': one_list_tier.id, }, ) assert response.status_code == status.HTTP_200_OK assert response.data['count'] == 2 assert len(response.data['results']) == 2 matched_interaction_subjects = { result['subject'] for result in response.data['results'] } assert matched_interaction_subjects == { 'Global HQ chat', 'Regional Subsidiary chat' } assert all( result['company_one_list_group_tier']['name'] == one_list_tier.name for result in response.data['results'])
def test_filter_by_multiple_one_list_tier_groups( self, es_with_collector, ): """ Test that we can filter by multiple one list tier groups. """ one_list_tiers = list(OneListTier.objects.all().order_by('?')[0:2]) company_1 = CompanyFactory( name='Global HQ Ltd', one_list_tier=one_list_tiers[0], ) company_2 = CompanyFactory( name='Other Company', one_list_tier=one_list_tiers[1], ) CompanyInteractionFactory( subject='Global HQ chat', company=company_1, ) CompanyInteractionFactory( subject='Other Company chat', company=company_2, ) es_with_collector.flush_and_refresh() url = reverse('api-v3:search:interaction') response = self.api_client.post( url, data={ 'company_one_list_group_tier': [tier.id for tier in one_list_tiers], }, ) expected_interaction_subjects = { 'Global HQ chat', 'Other Company chat', } assert response.status_code == status.HTTP_200_OK assert response.data['count'] == 2 assert len(response.data['results']) == 2 matched_interaction_subjects = { result['subject'] for result in response.data['results'] } assert matched_interaction_subjects == expected_interaction_subjects
def test_basic_search_no_permissions(self, setup_es): """Tests model permissions enforcement in basic search for a user with no permissions.""" user = create_test_user(permission_codenames=[], dit_team=TeamFactory()) api_client = self.create_api_client(user=user) InvestmentProjectFactory(created_by=user) CompanyFactory() ContactFactory() EventFactory() CompanyInteractionFactory() OrderFactory() setup_es.indices.refresh() url = reverse('api-v3:search:basic') response = api_client.get( url, data={ 'term': '', 'entity': 'company', }, ) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert response_data['count'] == 0 assert len(response_data['aggregations']) == 0
def test_filtering_by_country_excludes_non_matches(self, es_with_collector): """Test that filtering by country excludes non-matching objects.""" countries = list(Country.objects.order_by('?')[:2]) filter_country = countries[0] other_country = countries[1] # Non-export country interactions should be excluded CompanyInteractionFactory() # Unrelated countries should be excluded CompanyExportCountryHistoryFactory(country=other_country, history_type=HistoryType.INSERT) ExportCountriesInteractionFactory( export_countries__country=other_country) es_with_collector.flush_and_refresh() response = self.api_client.post( export_country_history_search_url, data={ 'country': filter_country.pk, }, ) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert response_data['count'] == 0 assert response_data['results'] == []
def test_permissions(self, es_with_collector, permission, permission_entity, entity): """ Tests model permissions enforcement in basic search. TODO: we should test permissions relevant to a specific search app in the tests for that search app, and remove this test. """ user = create_test_user(permission_codenames=[permission], dit_team=TeamFactory()) api_client = self.create_api_client(user=user) InvestmentProjectFactory(created_by=user) CompanyFactory() ContactFactory() EventFactory() CompanyInteractionFactory() OrderFactory() es_with_collector.flush_and_refresh() url = reverse('api-v3:search:basic') response = api_client.get( url, data={ 'term': '', 'entity': entity, }, ) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert (response_data['count'] == 0) == (permission_entity != entity) assert len(response_data['aggregations']) == 1 assert response_data['aggregations'][0]['entity'] == permission_entity
def test_adding_interaction_updates_company(es_with_signals): """Test that when an interaction is added, the company is synced to ES.""" test_name = 'very_hard_to_find_company' company = CompanyFactory( name=test_name, ) es_with_signals.indices.refresh() doc = es_with_signals.get( index=CompanySearchApp.es_model.get_read_alias(), doc_type=DEFAULT_MAPPING_TYPE, id=company.pk, ) assert doc['_source']['name'] == test_name assert doc['_source']['latest_interaction_date'] is None CompanyInteractionFactory( date=dateutil_parse('2018-04-05T00:00:00Z'), company=company, ) es_with_signals.indices.refresh() updated_doc = es_with_signals.get( index=CompanySearchApp.es_model.get_read_alias(), doc_type=DEFAULT_MAPPING_TYPE, id=company.pk, ) assert updated_doc['_source']['name'] == test_name assert updated_doc['_source']['latest_interaction_date'] == '2018-04-05T00:00:00+00:00'
def test_unarchive_interaction_restricted_user_non_associated_project(self): """ Test that a restricted user cannot un-archive a non-associated interaction. """ project_creator = AdviserFactory() project = InvestmentProjectFactory(created_by=project_creator) # Ensure the requester is created for a different DIT team requester = create_test_user( permission_codenames=[InteractionPermission.change_associated_investmentproject], ) api_client = self.create_api_client(user=requester) interaction = CompanyInteractionFactory( investment_project=project, archived=True, archived_by=project_creator, archived_on=now(), archived_reason='why not', ) url = reverse( 'api-v3:interaction:unarchive-item', kwargs={'pk': interaction.pk}, ) response = api_client.post( url, ) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_realtime_messages_sent( self, monkeypatch, automatic_company_archive_feature_flag, created_on_delta, companies_to_create, expected_message, ): """ Test that appropriate realtime messaging is sent which reflects the archiving actions. """ created_on = timezone.now() - created_on_delta for _ in range(companies_to_create): with freeze_time(created_on): company = CompanyFactory() CompanyInteractionFactory( company=company, date=timezone.now() - relativedelta(years=8, days=1), ) mock_send_realtime_message = mock.Mock() monkeypatch.setattr( 'datahub.company.tasks.company.send_realtime_message', mock_send_realtime_message, ) automatic_company_archive.apply_async(kwargs={'simulate': False}) mock_send_realtime_message.assert_called_once_with(expected_message)
def test_modified_on( self, automatic_company_archive_feature_flag, modified_on_delta, expected_archived, ): """ Test that a company modified_on dates around the 3m boundary are archived or not as expected. """ gt_3m_ago = timezone.now() - relativedelta(months=3, days=1) with freeze_time(gt_3m_ago): company = CompanyFactory() CompanyInteractionFactory( company=company, date=timezone.now() - relativedelta(years=8, days=1), ) with freeze_time(timezone.now() - modified_on_delta): company.save() task_result = automatic_company_archive.apply_async( kwargs={'simulate': False}) assert task_result.successful() archived_company = Company.objects.get(pk=company.id) assert archived_company.archived == expected_archived assert archived_company.modified_on == company.modified_on
def test_cannot_update_read_only_fields(self): """Test updating read-only fields.""" interaction = CompanyInteractionFactory( archived_documents_url_path='old_path', archived=False, archived_by=None, archived_on=None, archived_reason=None, ) url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) # TODO: also test `archived` field once we have made it read-only response = self.api_client.patch( url, data={ 'archived_documents_url_path': 'new_path', 'archived': True, 'archived_by': 123, 'archived_on': date.today(), 'archived_reason': 'test', }, ) assert response.status_code == status.HTTP_200_OK assert response.data['archived_documents_url_path'] == 'old_path' assert response.data['archived'] is False assert response.data['archived_by'] is None assert response.data['archived_on'] is None assert response.data['archived_reason'] is None
def test_unarchive_interaction_non_restricted_user(self, permissions): """ Tests un-archiving an interaction for a non-restricted user. """ requester = create_test_user(permission_codenames=permissions) api_client = self.create_api_client(user=requester) interaction = CompanyInteractionFactory( archived=True, archived_by=requester, archived_reason='just cos', archived_on=now(), ) url = reverse( 'api-v3:interaction:unarchive-item', kwargs={'pk': interaction.pk}, ) response = api_client.post(url) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert response_data['archived'] is False assert response_data['archived_by'] is None assert response_data['archived_reason'] == '' assert response_data['archived_on'] is None
def test_find_active_contact_by_email_address( email, expected_matching_status, match_on_alternative, match_strategy, ): """Test finding a contact by email address for various scenarios.""" for factory_kwargs in EMAIL_MATCHING_CONTACT_TEST_DATA: interaction_count = 0 if factory_kwargs.get('interactions'): interaction_count = factory_kwargs.pop('interactions') created_contact = ContactFactory(**factory_kwargs) for _ in range(interaction_count): CompanyInteractionFactory(contacts=[created_contact]) contact, actual_matching_status = find_active_contact_by_email_address(email, match_strategy) assert actual_matching_status == expected_matching_status if actual_matching_status == ContactMatchingStatus.matched: assert contact actual_email = contact.email_alternative if match_on_alternative else contact.email assert actual_email.lower() == email.lower() else: assert not contact
def test_restricted_user_can_update_associated_investment_project_interaction(self): """ Test that a restricted user can update an interaction for an associated investment project. """ project_creator = AdviserFactory() project = InvestmentProjectFactory(created_by=project_creator) interaction = CompanyInteractionFactory( subject='I am a subject', investment_project=project, ) requester = create_test_user( permission_codenames=[ InteractionPermission.change_associated_investmentproject, ], dit_team=project_creator.dit_team, ) api_client = self.create_api_client(user=requester) url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) response = api_client.patch( url, data={ 'subject': 'I am another subject', }, ) assert response.status_code == status.HTTP_200_OK assert response.data['subject'] == 'I am another subject'
def company_with_multiple_participant_interaction_factory(): """Factory for a company with an interaction that has multiple participants.""" company = CompanyFactory() interaction = CompanyInteractionFactory(company=company, dit_participants=[]) InteractionDITParticipantFactory.create_batch(2, interaction=interaction) return company
def test_interaction_synced_when_dit_participant_added( opensearch_with_signals): """Test that interactions are synced to OpenSearch if their DIT participants change.""" interaction = CompanyInteractionFactory(dit_participants=[]) opensearch_with_signals.indices.refresh() doc = opensearch_with_signals.get( index=InteractionSearchApp.search_model.get_read_alias(), id=interaction.pk, ) assert doc['_source']['dit_participants'] == [] dit_participant = InteractionDITParticipantFactory(interaction=interaction) opensearch_with_signals.indices.refresh() updated_doc = opensearch_with_signals.get( index=InteractionSearchApp.search_model.get_read_alias(), id=interaction.pk, ) actual_dit_participants = updated_doc['_source']['dit_participants'] assert len(actual_dit_participants) == 1 assert actual_dit_participants[0]['adviser']['id'] == str( dit_participant.adviser.pk) assert actual_dit_participants[0]['team']['id'] == str( dit_participant.team.pk)
def test_archive_interaction_restricted_user_associated_project(self): """ Tests archiving an interaction for a restricted user. """ project_creator = AdviserFactory() project = InvestmentProjectFactory(created_by=project_creator) requester = create_test_user( permission_codenames=[InteractionPermission.change_associated_investmentproject], dit_team=project_creator.dit_team, # same dit team as the project creator ) api_client = self.create_api_client(user=requester) interaction = CompanyInteractionFactory(investment_project=project) url = reverse( 'api-v3:interaction:archive-item', kwargs={'pk': interaction.pk}, ) response = api_client.post( url, data={ 'reason': 'archive reason', }, ) assert response.status_code == status.HTTP_200_OK response_data = response.json() assert response_data['archived'] is True assert response_data['archived_by']['id'] == str(requester.pk) assert response_data['archived_reason'] == 'archive reason'
def test_sync_outdated_companies_limit_most_recently_interacted_updated( requests_mock, dnb_response_uk, ): """ Test that running sync_outdated_companies_with_dnb with a limit will update the most recently interacted company. """ requests_mock.post( DNB_SEARCH_URL, json=dnb_response_uk, ) company_most_recent_interaction = CompanyFactory( duns_number='123456789', dnb_modified_on=now() - timedelta(days=1), ) CompanyInteractionFactory(company=company_most_recent_interaction, date=now()) company_least_recent_interaction = CompanyFactory( duns_number='123456788', dnb_modified_on=now() - timedelta(days=1), ) CompanyInteractionFactory( company=company_least_recent_interaction, date=now() - timedelta(days=1), ) task_result = sync_outdated_companies_with_dnb.apply_async(kwargs={ 'fields_to_update': ['global_ultimate_duns_number'], 'dnb_modified_on_before': now() + timedelta(days=1), 'simulate': False, 'limit': 1, }, ) company_least_recent_interaction.refresh_from_db() company_most_recent_interaction.refresh_from_db() assert task_result.successful() # We expect the least recently interacted company to be unmodified assert company_least_recent_interaction.dnb_modified_on == now( ) - timedelta(days=1) # We expect most recently interacted company to be modified assert company_most_recent_interaction.dnb_modified_on == now()
def test_can_replace_some_participants(self): """Test that a subset of existing DIT participants can be replaced.""" interaction = CompanyInteractionFactory(dit_participants=[]) dit_participants = InteractionDITParticipantFactory.create_batch( 3, interaction=interaction, ) # Change the first adviser's team so that we can check that the participant's team is # unchanged after the update. dit_participants[0].adviser.dit_team = TeamFactory() dit_participants[0].adviser.save() new_advisers = [ dit_participants[0].adviser, AdviserFactory(), ] request_data = { 'dit_participants': [ { 'adviser': { 'id': adviser.pk, }, } for adviser in new_advisers ], } url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) response = self.api_client.patch(url, data=request_data) assert response.status_code == status.HTTP_200_OK response_data = response.json() response_data['dit_participants'].sort( key=lambda dit_participant: dit_participant['adviser']['id'], ) expected_advisers_and_teams = [ (new_advisers[0], dit_participants[0].team), (new_advisers[1], new_advisers[1].dit_team), ] expected_advisers_and_teams.sort(key=lambda adviser_and_team: adviser_and_team[0].pk) assert response_data['dit_participants'] == [ { 'adviser': { 'id': str(adviser.pk), 'first_name': adviser.first_name, 'last_name': adviser.last_name, 'name': adviser.name, }, 'team': { 'id': str(team.pk), 'name': team.name, }, } for adviser, team in expected_advisers_and_teams ]
def test_fails_without_permissions(self): """Should return 403""" interaction = CompanyInteractionFactory() user = create_test_user(dit_team=TeamFactory()) api_client = self.create_api_client(user=user) url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) response = api_client.get(url) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_new_interaction_synced(opensearch_with_signals): """Test that new interactions are synced to OpenSearch.""" interaction = CompanyInteractionFactory() opensearch_with_signals.indices.refresh() assert opensearch_with_signals.get( index=InteractionSearchApp.search_model.get_write_index(), id=interaction.pk, )
def test_validation(self, data, errors): """Test validation when an invalid date is provided.""" interaction = CompanyInteractionFactory() url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) response = self.api_client.patch(url, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == errors
def test_new_interaction_synced(es_with_signals): """Test that new interactions are synced to ES.""" interaction = CompanyInteractionFactory() es_with_signals.indices.refresh() assert es_with_signals.get( index=InteractionSearchApp.es_model.get_write_index(), doc_type=InteractionSearchApp.name, id=interaction.pk, )
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_with_multiple_interactions(self, data_flow_api_client): """Test that the correct number of records are returned in the right order""" with freeze_time('2019-01-01 12:30:00'): interaction1 = CompanyInteractionFactory() with freeze_time('2019-01-03 12:00:00'): interaction2 = CompanyInteractionFactory() with freeze_time('2019-01-01 12:00:00'): interaction3 = CompanyInteractionFactory() interaction4 = CompanyInteractionFactory() response = data_flow_api_client.get(self.view_url) assert response.status_code == status.HTTP_200_OK response_results = response.json()['results'] assert len(response_results) == 4 expected_list = sorted( [interaction3, interaction4], key=lambda item: item.pk, ) + [interaction1, interaction2] for index, interaction in enumerate(expected_list): assert interaction.get_absolute_url() == response_results[index]['interaction_link']
def test_filter_by_company_name(self, setup_es, name_term, matched_company_name): """Tests filtering interaction by company name.""" matching_company1 = CompanyFactory( name='whiskers and tabby', trading_names=['Maine Coon', 'Egyptian Mau'], ) matching_company2 = CompanyFactory( name='1a', trading_names=['3a', '4a'], ) non_matching_company = CompanyFactory( name='Pluto and pippo', trading_names=['eniam nooc', 'naitpyge uam'], ) CompanyInteractionFactory(company=matching_company1) CompanyInteractionFactory(company=matching_company2) CompanyInteractionFactory(company=non_matching_company) setup_es.indices.refresh() url = reverse('api-v3:search:interaction') response = self.api_client.post( url, data={ 'original_query': '', 'company_name': name_term, }, ) assert response.status_code == status.HTTP_200_OK match = Interaction.objects.filter( company__name=matched_company_name).first() if match: assert response.data['count'] == 1 assert len(response.data['results']) == 1 assert response.data['results'][0]['id'] == str(match.id) else: assert response.data['count'] == 0 assert len(response.data['results']) == 0
def test_cannot_update_service_answers(self, initial_value, new_value, expected_response): """Test that the service answers field cannot be updated with incorrect data.""" interaction = CompanyInteractionFactory(**initial_value) url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) response = self.api_client.patch( url, data=new_value, ) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == expected_response
def test_null_adviser(api_client): """ Test that we can handle dit_participant.adviser being None """ interaction = CompanyInteractionFactory(dit_participants=[]) InteractionDITParticipantFactory( interaction=interaction, adviser=None, team=TeamFactory(), ) response = hawk.get(api_client, get_url('api-v3:activity-stream:interactions')) assert response.status_code == status.HTTP_200_OK
def test_restricted_user_cannot_get_company_interaction(self): """Test that a restricted user cannot get a company interaction.""" interaction = CompanyInteractionFactory() requester = create_test_user( permission_codenames=[InteractionPermission.view_associated_investmentproject], dit_team=TeamFactory(), ) api_client = self.create_api_client(user=requester) url = reverse('api-v3:interaction:item', kwargs={'pk': interaction.pk}) response = api_client.get(url) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_updated_interaction_synced(opensearch_with_signals): """Test that when an interaction is updated it is synced to OpenSearch.""" interaction = CompanyInteractionFactory() new_subject = 'pluto' interaction.subject = new_subject interaction.save() opensearch_with_signals.indices.refresh() result = opensearch_with_signals.get( index=InteractionSearchApp.search_model.get_write_index(), id=interaction.pk, ) assert result['_source']['subject'] == new_subject