def setup_followup_history(): """ There are 4 users 1,2,3,4 but 4 doesn't have a social profile There are 2 chats: * chat 1 has participants 1,2,3,4 and followups from 2 outside of time range and 3,4 within time range * chat 2 has participants 1,2 and followups from 2 inside of time range Therefore * user 1 will get notifications from 3,4 for chat 1 and from 2 for chat 2 * user 2 will get notifications from 3,4 for chat 1 * user 3 will get notifications from 4 for chat 1 <-- note: users don't receive notifications for their own updates * user 4 would get notifications from 3 for chat 1 BUT has no social profile, so no way to contact them :return: """ user1 = UserFactory() soc_prof1 = SocialProfileFactory(user=user1) user2 = UserFactory() soc_prof2 = SocialProfileFactory(user=user2) user3 = UserFactory() soc_prof3 = SocialProfileFactory(user=user3) user4 = UserFactory() # NOTE! soc_prof4 is intentionally omitted because sometimes a User doesn't have a SocialProfile penny_chat1 = PennyChatFactory() Participant.objects.create(penny_chat=penny_chat1, user=user1, role=Participant.ORGANIZER) Participant.objects.create(penny_chat=penny_chat1, user=user2, role=Participant.ATTENDEE) Participant.objects.create(penny_chat=penny_chat1, user=user3, role=Participant.ATTENDEE) Participant.objects.create(penny_chat=penny_chat1, user=user4, role=Participant.ATTENDEE) FollowUpFactory(penny_chat=penny_chat1, user=user2, date=before_range_date) FollowUpFactory(penny_chat=penny_chat1, user=user3, date=within_range_date) FollowUpFactory(penny_chat=penny_chat1, user=user4, date=within_range_date) penny_chat2 = PennyChatFactory() Participant.objects.create(penny_chat=penny_chat2, user=user2, role=Participant.ORGANIZER) Participant.objects.create(penny_chat=penny_chat2, user=user1, role=Participant.ATTENDEE) FollowUpFactory(penny_chat=penny_chat2, user=user2, date=within_range_date) return { 'penny_chats': [penny_chat1, penny_chat2], 'users': [user1, user2, user3, user4], 'social_profiles': [soc_prof1, soc_prof2, soc_prof3] }
def test_make_matches(mocker): slack_team_id = 'sl123' slack_client = mocker.Mock() conversation = {'channel': {'id': 'FAKE123'}} slack_client.conversations_open.return_value = conversation profile1 = SocialProfileFactory(slack_team_id=slack_team_id) profile2 = SocialProfileFactory(slack_team_id=slack_team_id) profile3 = SocialProfileFactory(slack_team_id=slack_team_id) channel = TopicChannel.objects.create( channel_id='one', slack_team_id=slack_team_id, name='uno', ) with mocker.patch('matchmaking.common.get_slack_client', return_value=slack_client): make_matches(slack_team_id, [profile1.email, profile2.email, profile3.email], channel.name) expected_blocks = [ { "type": "section", "text": { "type": "mrkdwn", "text": "Yahoo, you've been matched for a conversation about <#one>!\n\nWork together to find a time to meet and chat. Once you do, click the button below to schedule a Penny Chat.", # noqa } }, { "type": "actions", "elements": [{ "type": "button", "action_id": "penny_chat_schedule_match", "text": { "type": "plain_text", "text": "Schedule Chat :calendar:", "emoji": True, }, "value": "FAKE123", "style": "primary", }] } ] slack_client.chat_postMessage.assert_called_once_with( channel='FAKE123', blocks=expected_blocks) match = Match.objects.get(conversation_id='FAKE123', topic_channel=channel) assert profile1 in match.profiles.all() assert profile2 in match.profiles.all() assert profile3 in match.profiles.all()
def test_schedule_match_penny_chat(mocker): profile_1 = SocialProfileFactory() profile_2 = SocialProfileFactory() topic = TopicChannel.objects.create(slack_team_id=profile_1.slack_team_id, channel_id='FAKE_CHANNEL', name='testing') match = Match.objects.create(topic_channel=topic, conversation_id='FAKE_CONVERSATION') match.profiles.add(profile_1, profile_2) event = { 'user': { 'id': profile_1.slack_id, 'team_id': profile_1.slack_team_id }, 'trigger_id': 'fake_trigger', 'actions': [{ 'action_id': 'penny_chat_schedule_match', 'value': match.conversation_id }], } slack_client = mocker.Mock() response = mocker.Mock(data={'view': {'id': '12345'}}) slack_client.configure_mock(**{'views_open.return_value': response}) # The Actual Tests with mocker.patch( 'bot.processors.pennychat.get_or_create_social_profile_from_slack_id', return_value=profile_1): PennyChatBotModule(slack_client).schedule_match(event) match.refresh_from_db() penny_chat = match.penny_chat assert penny_chat is not None invite = PennyChatSlackInvitation.objects.get(penny_chat=penny_chat) assert invite.invitees == profile_2.slack_id assert invite.title == f'{profile_1.real_name} + {profile_2.real_name} Discuss {match.topic_channel.name}' assert invite.organizer_slack_id == profile_1.slack_id
def test_penny_chat_reminders_blocks(mocker): invite = PennyChatSlackInvitationFactory() organizer = UserFactory() organizer_profile = SocialProfileFactory() Participant.objects.create(user=organizer, penny_chat=invite, role=Participant.ORGANIZER) for i in range(12): user = UserFactory() Participant.objects.create(user=user, penny_chat=invite, role=Participant.ATTENDEE) with mocker.patch( 'bot.tasks.pennychat.get_or_create_social_profile_from_slack_id', return_value=organizer_profile): reminder_blocks = _penny_chat_details_blocks(invite, 'remind') # Check that the correct number of blocks were created assert len(reminder_blocks[4]['elements']) == 10 assert reminder_blocks[4]['elements'][-1]['text'] == '& 4 more attending'
def test_get_unfulfilled_match_requests(): now = datetime.now().astimezone(timezone.utc) since_date = now - timedelta(weeks=10) with freeze_time(now - timedelta(weeks=52), tz_offset=0): old_match_request_with_no_match = MatchRequestFactory( profile=SocialProfileFactory()) with freeze_time(now - timedelta(weeks=5), tz_offset=0): match_request_with_successful_match = MatchRequestFactory( profile=SocialProfileFactory()) MatchFactory( profiles=(match_request_with_successful_match.profile, SocialProfileFactory()), # penny_chat is automatically supplied by MatchFactory date=now - timedelta(weeks=4), ) match_request_with_unsuccessful_match = MatchRequestFactory( profile=SocialProfileFactory()) MatchFactory( profiles=(match_request_with_unsuccessful_match.profile, SocialProfileFactory()), penny_chat=None, date=now - timedelta(weeks=4), ) match_request_with_no_match = MatchRequestFactory( profile=SocialProfileFactory()) match_requests_profile_ids = set([ mr['profile_id'] for mr in MatchMaker._get_unfulfilled_match_requests(since_date) ]) assert old_match_request_with_no_match.profile_id not in match_requests_profile_ids, \ 'this profile made a match request too long ago and should not be considered' assert match_request_with_successful_match.profile_id not in match_requests_profile_ids, \ 'this profile has had a successful match since their last match request and should not be considered' assert match_request_with_unsuccessful_match.profile_id in match_requests_profile_ids, \ 'this profile has had an unsuccessful match since their last match request (no chat) and should be considered' assert match_request_with_no_match.profile_id in match_requests_profile_ids, \ 'this profile has not had a match since their last match request and should be considered'
def test_gather_data(): now = datetime.now().astimezone(timezone.utc) prof1, prof2, prof3, prof4, prof5, prof6, prof_too_old = [ SocialProfileFactory(email=f'prof{i+1}@email.com') for i in range(7) ] prof_too_old.email = '*****@*****.**' prof_too_old.save() topic_channel_science = TopicChannelFactory(name='science') topic_channel_art = TopicChannelFactory(name='art') with freeze_time(now - timedelta(weeks=52), tz_offset=0): MatchRequestFactory(profile=prof_too_old) with freeze_time(now - timedelta(weeks=1), tz_offset=0): MatchRequestFactory( profile=prof1, topic_channel=topic_channel_art, ) MatchRequestFactory( profile=prof1, topic_channel=topic_channel_science, ) MatchRequestFactory(profile=prof2, topic_channel=topic_channel_art) MatchRequestFactory(profile=prof3, topic_channel=topic_channel_science) MatchRequestFactory(profile=prof4, topic_channel=topic_channel_science) with freeze_time(now - timedelta(weeks=3), tz_offset=0): MatchFactory( topic_channel=topic_channel_art, profiles=(prof1, prof2, prof3), ) MatchFactory( topic_channel=topic_channel_science, profiles=(prof4, prof5), ) MatchFactory( topic_channel=topic_channel_science, profiles=(prof6, prof_too_old), ) match_maker = MatchMaker(match_request_since_date=now - timedelta(weeks=2)) match_maker._gather_data() # _recent_match_by_profile_pair, _recent_match_by_profile_pair, and _recent_match_by_profile_pair_and_topic # are used to look up information useful in creating a score assert 'prof_too_old' not in set( chain(*set(match_maker._recent_match_by_profile_pair.keys()))) assert set(match_maker._recent_match_by_profile_pair.keys()) == { key('*****@*****.**', '*****@*****.**'), # note that the meeting with 3 people got turned into 3 pairs here key('*****@*****.**', '*****@*****.**'), key('*****@*****.**', '*****@*****.**'), key('*****@*****.**', '*****@*****.**'), } assert match_maker._recent_match_by_profile_pair[key( '*****@*****.**', '*****@*****.**')]['num_attending'] == 3 assert set(match_maker._recent_match_by_profile_topic) == { key('*****@*****.**', 'art'), key('*****@*****.**', 'art'), key('*****@*****.**', 'art'), key('*****@*****.**', 'science'), key('*****@*****.**', 'science'), } assert set(match_maker._recent_match_by_profile_pair_and_topic.keys()) == { key('*****@*****.**', '*****@*****.**', 'art'), key('*****@*****.**', '*****@*****.**', 'art'), key('*****@*****.**', '*****@*****.**', 'art'), key('*****@*****.**', '*****@*****.**', 'science'), } # _match_requests_profile_to_topic, _match_requests_topic_to_profile, and _possible_matches # are used largely as filters to quickly find topics for people, people for topics, and people that share a topic # respectively assert match_maker._match_requests_profile_to_topic == { '*****@*****.**': {'science', 'art'}, '*****@*****.**': {'art'}, '*****@*****.**': {'science'}, '*****@*****.**': {'science'}, } assert match_maker._match_requests_topic_to_profile == { 'art': {'*****@*****.**', '*****@*****.**'}, 'science': {'*****@*****.**', '*****@*****.**', '*****@*****.**'}, } assert match_maker._possible_matches == { '*****@*****.**': {'*****@*****.**', '*****@*****.**', '*****@*****.**'}, '*****@*****.**': {'*****@*****.**'}, '*****@*****.**': {'*****@*****.**', '*****@*****.**'}, '*****@*****.**': {'*****@*****.**', '*****@*****.**'}, }
def test_review_match_requests(): profile1 = SocialProfileFactory() profile2 = SocialProfileFactory() profile3 = SocialProfileFactory() chan1 = TopicChannel.objects.create( channel_id='one', slack_team_id='sl123', name='uno', ) chan2 = TopicChannel.objects.create( channel_id='two', slack_team_id='sl123', name='dos', ) # recent with freeze_time(timezone.now() - timedelta(days=3), tz_offset=0): MatchRequest.objects.create( topic_channel=chan1, profile=profile1, ) MatchRequest.objects.create( topic_channel=chan2, profile=profile1, ) MatchRequest.objects.create( topic_channel=chan1, profile=profile2, ) # old - this one better not show up with freeze_time(timezone.now() - timedelta(days=30), tz_offset=0): MatchRequest.objects.create( topic_channel=chan1, profile=profile3, ) actual = get_recent_matches(since_days_ago=7) expected = f"""PROFILE TO TOPICS {profile1.email} ({profile1.real_name}) requested a match for: topic {chan1.name} in channel {chan1.channel_id} topic {chan2.name} in channel {chan2.channel_id} {profile2.email} ({profile2.real_name}) requested a match for: topic {chan1.name} in channel {chan1.channel_id} TOPIC TO PROFILES Channel {chan1.channel_id} with topic {chan1.name} has interested people: topic {profile1.email} ({profile1.real_name}) topic {profile2.email} ({profile2.real_name}) Channel two with topic dos has interested people: topic {profile1.email} ({profile1.real_name}) """ # noqa assert actual == expected # noqa