class GroupConversationReceiverPushTests(TestCase): def setUp(self): self.group = GroupFactory() self.user = UserFactory() self.author = UserFactory() self.group.add_member(self.user) self.group.add_member(self.author) self.token = faker.uuid4() self.content = faker.text() self.conversation = self.group.conversation # add a push subscriber self.subscription = PushSubscription.objects.create( user=self.user, token=self.token, platform=PushSubscriptionPlatform.ANDROID.value, ) def test_sends_to_push_subscribers(self, notify_subscribers): # add a message to the conversation ConversationMessage.objects.create(conversation=self.conversation, content=self.content, author=self.author) self.assertEqual(notify_subscribers.call_count, 2) kwargs = notify_subscribers.call_args_list[0][1] self.assertEqual(list(kwargs['subscriptions']), [self.subscription]) self.assertEqual(kwargs['fcm_options']['message_title'], self.group.name + ' / ' + self.author.display_name) self.assertEqual(kwargs['fcm_options']['message_body'], self.content)
class GroupConversationReceiverPushTests(ChannelTestCase): def setUp(self): self.group = GroupFactory() self.user = UserFactory() self.author = UserFactory() self.group.add_member(self.user) self.group.add_member(self.author) self.token = faker.uuid4() self.content = faker.text() self.conversation = self.group.conversation # add a push subscriber PushSubscription.objects.create(user=self.user, token=self.token, platform=PushSubscriptionPlatform.ANDROID) def test_sends_to_push_subscribers(self, m): def check_json_data(request): data = json.loads(request.body.decode('utf-8')) self.assertEqual(data['notification']['title'], self.group.name + ' / ' + self.author.display_name) self.assertEqual(data['notification']['body'], self.content) self.assertEqual(data['to'], self.token) return True m.post(FCMApi.FCM_END_POINT, json={}, additional_matcher=check_json_data) # add a message to the conversation ConversationMessage.objects.create(conversation=self.conversation, content=self.content, author=self.author)
def test_creates_new_member_notification(self): member1 = UserFactory() member2 = UserFactory() group = GroupFactory(members=[member1, member2]) Notification.objects.all().delete() user = UserFactory() group.add_member(user, added_by=member1) notifications = Notification.objects.filter(type=NotificationType.NEW_MEMBER.value) # member1 doesn't get a notification, as they added the user self.assertEqual(notifications.count(), 1, notifications) self.assertEqual(notifications[0].user, member2) self.assertEqual(notifications[0].context['user'], user.id)
class TestSendStatistics(TestCase): def setUp(self): self.user = UserFactory() self.member = UserFactory() self.group = GroupFactory(members=[self.member]) @patch('foodsaving.groups.stats.write_points') def test_send_group_join_stats(self, write_mock): self.group.add_member(self.user) self.assertTrue(write_mock.called) @patch('foodsaving.groups.stats.write_points') def test_non_send_group_join_stats_on_update(self, write_mock): membership = GroupMembership.objects.get(group=self.group, user=self.member) membership.inactive_at = None membership.save() self.assertFalse(write_mock.called)
class TestGroupApplicationReceivers(APITestCase): def setUp(self): self.new_member = UserFactory() self.existing_member = UserFactory() self.group = GroupFactory(members=[self.existing_member], application_questions='') def test_group_add_member_marks_existing_messages_as_read(self): self.group.conversation.messages.create(author=self.existing_member, content='foo') second_message = self.group.conversation.messages.create( author=self.existing_member, content='bar') self.group.add_member(self.new_member) new_participant = ConversationParticipant.objects.get( user=self.new_member, conversation=self.group.conversation) self.assertTrue(new_participant.seen_up_to == second_message)
class TestSummaryEmailTask(TestCase): def setUp(self): self.group = GroupFactory() @patch('foodsaving.groups.stats.write_points') def test_collects_stats(self, write_points): a_few_days_ago = timezone.now() - relativedelta(days=4) store = StoreFactory(group=self.group) message_count = 10 pickups_missed_count = 5 feedback_count = 4 new_user_count = 3 pickups_done_count = 8 new_users = [VerifiedUserFactory() for _ in range(new_user_count)] user = new_users[0] with freeze_time(a_few_days_ago, tick=True): [self.group.add_member(u) for u in new_users] # a couple of messages [self.group.conversation.messages.create(author=user, content='hello') for _ in range(message_count)] # missed pickups [PickupDateFactory(store=store) for _ in range(pickups_missed_count)] # fullfilled pickups pickups = [ PickupDateFactory(store=store, max_collectors=1, collectors=[user]) for _ in range(pickups_done_count) ] # pickup feedback [FeedbackFactory(about=pickup, given_by=user) for pickup in pickups[:feedback_count]] write_points.reset_mock() send_summary_emails() write_points.assert_called_with([{ 'measurement': 'karrot.email.group_summary', 'tags': { 'group': str(self.group.id) }, 'fields': { 'value': 1, 'new_user_count': new_user_count, 'email_recipient_count': new_user_count, 'feedback_count': feedback_count, 'pickups_missed_count': pickups_missed_count, 'message_count': message_count, 'pickups_done_count': pickups_done_count, }, }])
class GroupMembershipReceiverTests(WSTestCase): def setUp(self): super().setUp() self.member = UserFactory() self.user = UserFactory() self.group = GroupFactory(members=[self.member]) def test_receive_group_join_as_member(self): self.client = self.connect_as(self.member) self.group.add_member(self.user) response = self.client.messages_by_topic.get('groups:group_detail')[0] self.assertIn(self.user.id, response['payload']['members']) self.assertIn(self.user.id, response['payload']['memberships'].keys()) response = self.client.messages_by_topic.get('groups:group_preview')[0] self.assertIn(self.user.id, response['payload']['members']) self.assertNotIn('memberships', response['payload']) def test_receive_group_join_as_joining_user(self): self.client = self.connect_as(self.user) self.group.add_member(self.user) response = self.client.messages_by_topic.get('groups:group_detail')[0] self.assertIn(self.user.id, response['payload']['members']) self.assertIn(self.user.id, response['payload']['memberships'].keys()) response = self.client.messages_by_topic.get('groups:group_preview')[0] self.assertIn(self.user.id, response['payload']['members']) self.assertNotIn('memberships', response['payload']) def test_receive_group_join_as_nonmember(self): self.client = self.connect_as(self.user) join_user = UserFactory() self.group.add_member(join_user) self.assertNotIn('groups:group_detail', self.client.messages_by_topic.keys()) response = self.client.messages_by_topic.get('groups:group_preview')[0] self.assertIn(join_user.id, response['payload']['members']) self.assertNotIn('memberships', response['payload']) def test_receive_group_leave_as_leaving_user(self): self.client = self.connect_as(self.member) self.group.remove_member(self.member) response = self.client.messages_by_topic.get('groups:group_preview')[0] self.assertNotIn(self.user.id, response['payload']['members']) self.assertNotIn('memberships', response['payload'])
class TestTrustThreshold(TestCase): def create_group_with_members(self, member_count): self.members = [UserFactory() for _ in range(member_count)] self.group = GroupFactory(members=self.members) # trust threshold calculation ignores recently joined users, so we need to create users before that two_days_ago = timezone.now() - relativedelta(days=2) GroupMembership.objects.filter(group=self.group).update( created_at=two_days_ago) def test_min_threshold(self): self.create_group_with_members(1) self.assertEqual( self.group.get_trust_threshold_for_newcomer(), 1, ) def test_ramp_up_threshold(self): self.create_group_with_members(5) self.assertEqual( self.group.get_trust_threshold_for_newcomer(), 2, ) def test_max_threshold(self): self.create_group_with_members(6) self.assertEqual( self.group.get_trust_threshold_for_newcomer(), 3, ) def test_ignores_recently_joined_users(self): self.create_group_with_members(1) [self.group.add_member(UserFactory()) for _ in range(5)] self.assertEqual( self.group.get_trust_threshold_for_newcomer(), 1, )
class TestGroupSummaryEmails(APITestCase): def setUp(self): self.group = GroupFactory() self.user_without_notifications = VerifiedUserFactory(language='en') self.group.add_member(self.user_without_notifications) m = GroupMembership.objects.get(group=self.group, user=self.user_without_notifications) m.notification_types = [] m.save() # it should ignore unverified and inactive users so adding a random number # of them here should not change anything unverified_users = [ UserFactory(language='en') for _ in list(range(randint(2, 5))) ] for user in unverified_users: self.group.add_member(user) inactive_users = [ VerifiedUserFactory(language='en') for _ in list(range(randint(2, 5))) ] for user in inactive_users: membership = self.group.add_member(user) membership.inactive_at = timezone.now() membership.save() def test_creates_one_email_for_one_language(self): n = 5 for i in list(range(n)): self.group.add_member(VerifiedUserFactory(language='en')) from_date, to_date = group_emails.calculate_group_summary_dates( self.group) context = group_emails.prepare_group_summary_data( self.group, from_date, to_date) emails = group_emails.prepare_group_summary_emails(self.group, context) self.assertEqual(len(emails), 1) expected_members = self.group.members.filter( groupmembership__in=GroupMembership.objects.active( ).with_notification_type(GroupNotificationType.WEEKLY_SUMMARY) ).exclude(groupmembership__user__in=get_user_model().objects. unverified_or_ignored()) self.assertEqual(sorted(emails[0].to), sorted([member.email for member in expected_members])) self.assertNotIn(self.user_without_notifications.email, emails[0].to) def test_creates_three_emails_for_three_languages(self): n = 5 for _ in list(range(n)): self.group.add_member(VerifiedUserFactory(language='en')) for _ in list(range(n)): self.group.add_member(VerifiedUserFactory(language='de')) for _ in list(range(n)): self.group.add_member(VerifiedUserFactory(language='fr')) from_date, to_date = group_emails.calculate_group_summary_dates( self.group) context = group_emails.prepare_group_summary_data( self.group, from_date, to_date) emails = group_emails.prepare_group_summary_emails(self.group, context) self.assertEqual(len(emails), 3) to = [] for email in emails: to.extend(email.to) expected_members = self.group.members.filter( groupmembership__in=GroupMembership.objects.active( ).with_notification_type(GroupNotificationType.WEEKLY_SUMMARY) ).exclude(groupmembership__user__in=get_user_model().objects. unverified_or_ignored()) self.assertEqual(sorted(to), sorted([member.email for member in expected_members])) self.assertNotIn(self.user_without_notifications.email, to) def test_creates_emails_unknown_locale(self): n = 5 for _ in list(range(n)): self.group.add_member(VerifiedUserFactory(language='dummy')) from_date, to_date = group_emails.calculate_group_summary_dates( self.group) context = group_emails.prepare_group_summary_data( self.group, from_date, to_date) emails = group_emails.prepare_group_summary_emails(self.group, context) self.assertEqual(len(emails), 1) expected_members = self.group.members.filter( groupmembership__in=GroupMembership.objects.active( ).with_notification_type(GroupNotificationType.WEEKLY_SUMMARY) ).exclude(groupmembership__user__in=get_user_model().objects. unverified_or_ignored()) self.assertEqual(sorted(emails[0].to), sorted([member.email for member in expected_members])) self.assertNotIn(self.user_without_notifications.email, emails[0].to) def test_ignores_deleted_pickups(self): a_few_days_ago = timezone.now() - relativedelta(days=4) store = StoreFactory(group=self.group) user = VerifiedUserFactory(mail_verified=True) self.group.add_member(user) with freeze_time(a_few_days_ago, tick=True): # fulfilled, but deleted PickupDateFactory(store=store, max_collectors=1, collectors=[user], is_disabled=True) from_date, to_date = foodsaving.groups.emails.calculate_group_summary_dates( self.group) data = foodsaving.groups.emails.prepare_group_summary_data( self.group, from_date, to_date) self.assertEqual(data['pickups_done_count'], 0) def test_group_summary_data(self): a_couple_of_weeks_ago = timezone.now() - relativedelta(weeks=3) a_few_days_ago = timezone.now() - relativedelta(days=4) store = StoreFactory(group=self.group) old_user = VerifiedUserFactory(mail_verified=True) user = VerifiedUserFactory(mail_verified=True) # should not be included in summary email with freeze_time(a_couple_of_weeks_ago, tick=True): self.group.add_member(old_user) self.group.conversation.messages.create(author=old_user, content='old message') PickupDateFactory(store=store) PickupDateFactory(store=store, max_collectors=1, collectors=[old_user]) # should be included in summary email with freeze_time(a_few_days_ago, tick=True): self.group.add_member(user) # a couple of messages self.group.conversation.messages.create(author=user, content='hello') self.group.conversation.messages.create(author=user, content='whats up') # a missed pickup PickupDateFactory(store=store) # a fulfilled pickup PickupDateFactory(store=store, max_collectors=1, collectors=[user]) from_date, to_date = foodsaving.groups.emails.calculate_group_summary_dates( self.group) data = foodsaving.groups.emails.prepare_group_summary_data( self.group, from_date, to_date) self.assertEqual(data['pickups_done_count'], 1) self.assertEqual(data['pickups_missed_count'], 1) self.assertEqual(len(data['new_users']), 1) self.assertEqual(len(data['messages']), 2)
class TestPickupNotificationTask(APITestCase): def setUp(self): self.user = VerifiedUserFactory() self.other_user = VerifiedUserFactory() self.non_verified_user = UserFactory() self.group = GroupFactory( members=[self.user, self.other_user, self.non_verified_user]) self.store = StoreFactory(group=self.group) self.declined_store = StoreFactory(group=self.group, status=StoreStatus.DECLINED.value) # unsubscribe other_user from notifications GroupMembership.objects.filter( group=self.group, user=self.other_user).update(notification_types=[]) # add some random inactive users, to make sure we don't send to them inactive_users = [ VerifiedUserFactory(language='en') for _ in list(range(randint(2, 5))) ] for user in inactive_users: membership = self.group.add_member(user) membership.inactive_at = timezone.now() membership.save() mail.outbox = [] def create_empty_pickup(self, delta, store=None): if store is None: store = self.store return PickupDate.objects.create( store=store, date=timezone.localtime() + delta, max_collectors=1, ) def create_not_full_pickup(self, delta, store=None): if store is None: store = self.store pickup = PickupDate.objects.create( store=store, date=timezone.localtime() + delta, max_collectors=2, ) pickup.collectors.add(self.other_user) pickup.save() return pickup def create_user_pickup(self, delta, store=None, **kwargs): if store is None: store = self.store pickup = PickupDate.objects.create( store=store, date=timezone.localtime() + delta, **kwargs, ) pickup.collectors.add(self.user) pickup.save() return pickup def create_deleted_pickup(self, delta, store=None): if store is None: store = self.store return PickupDate.objects.create( store=store, date=timezone.localtime() + delta, max_collectors=1, deleted=True, ) def test_user_pickups(self): with group_timezone_at(self.group, hour=20): user_pickup_tonight = self.create_user_pickup( relativedelta(minutes=50), max_collectors=1) user_pickup_tomorrow = self.create_user_pickup( relativedelta(hours=8), max_collectors=1) entries = fetch_pickup_notification_data_for_group(self.group) self.assertEqual(list(entries[0]['tonight_user']), [user_pickup_tonight]) self.assertEqual(list(entries[0]['tomorrow_user']), [user_pickup_tomorrow]) def test_empty_pickups(self): with group_timezone_at(self.group, hour=20): empty_pickup_tonight = self.create_empty_pickup( relativedelta(minutes=50)) empty_pickup_tomorrow = self.create_empty_pickup( relativedelta(hours=8)) entries = fetch_pickup_notification_data_for_group(self.group) self.assertEqual(list(entries[0]['tonight_empty']), [empty_pickup_tonight]) self.assertEqual(list(entries[0]['tomorrow_empty']), [empty_pickup_tomorrow]) def test_not_full_pickups(self): with group_timezone_at(self.group, hour=20): not_full_pickup_tonight = self.create_not_full_pickup( relativedelta(minutes=50)) not_full_pickup_tomorrow = self.create_not_full_pickup( relativedelta(hours=8)) entries = fetch_pickup_notification_data_for_group(self.group) self.assertEqual(list(entries[0]['tonight_not_full']), [not_full_pickup_tonight]) self.assertEqual(list(entries[0]['tomorrow_not_full']), [not_full_pickup_tomorrow]) def test_do_not_include_not_full_if_user_is_collector(self): with group_timezone_at(self.group, hour=20): self.create_user_pickup(relativedelta(minutes=50), max_collectors=2) self.create_user_pickup(relativedelta(hours=8), max_collectors=2) entries = fetch_pickup_notification_data_for_group(self.group) self.assertEqual(list(entries[0]['tonight_not_full']), []) self.assertEqual(list(entries[0]['tomorrow_not_full']), []) def test_send_notification_email(self): with group_timezone_at(self.group, hour=20): self.create_empty_pickup(delta=relativedelta(minutes=10)) daily_pickup_notifications() self.assertEqual(len(mail.outbox), 1) self.assertIn(store_url(self.store), mail.outbox[0].body) def test_does_not_send_if_no_pickups(self): with group_timezone_at(self.group, hour=20): daily_pickup_notifications() self.assertEqual(len(mail.outbox), 0) def test_does_not_send_at_other_times(self): with group_timezone_at(self.group, hour=21): self.create_empty_pickup(delta=relativedelta(minutes=10)) daily_pickup_notifications() self.assertEqual(len(mail.outbox), 0) def test_ignores_not_active_stores(self): with group_timezone_at(self.group, hour=20): self.create_empty_pickup(delta=relativedelta(minutes=10), store=self.declined_store) daily_pickup_notifications() self.assertEqual(len(mail.outbox), 0) def test_ignores_deleted_pickups(self): with group_timezone_at(self.group, hour=20): self.create_deleted_pickup(delta=relativedelta(minutes=10)) daily_pickup_notifications() self.assertEqual(len(mail.outbox), 0) @patch('foodsaving.pickups.stats.write_points') def test_writes_stats(self, write_points): write_points() with group_timezone_at(self.group, hour=20): tonight = relativedelta(minutes=10) tomorrow = relativedelta(hours=10) [self.create_user_pickup(tonight) for _ in range(2)] [self.create_empty_pickup(tonight) for _ in range(3)] [self.create_not_full_pickup(tonight) for _ in range(4)] [self.create_user_pickup(tomorrow) for _ in range(5)] [self.create_empty_pickup(tomorrow) for _ in range(6)] [self.create_not_full_pickup(tomorrow) for _ in range(7)] daily_pickup_notifications() write_points.assert_called_with([{ 'measurement': 'karrot.email.pickup_notification', 'tags': { 'group': str(self.group.id), }, 'fields': { 'value': 1, 'tonight_user': 2, 'tonight_empty': 3, 'tonight_not_full': 4, 'tomorrow_user': 5, 'tomorrow_empty': 6, 'tomorrow_not_full': 7, } }])
class TestConversationNotificationTask(TestCase): def setUp(self): self.user = VerifiedUserFactory() self.author = VerifiedUserFactory() self.group = GroupFactory(members=[self.author, self.user]) mail.outbox = [] with suppressed_notifications(): self.message = self.group.conversation.messages.create( author=self.author, content='initial message') def test_only_notifies_active_group_members(self): self.group.add_member(UserFactory()) inactive_user = VerifiedUserFactory() self.group.add_member(inactive_user) self.group.groupmembership_set.filter(user=inactive_user).update( inactive_at=timezone.now()) mail.outbox = [] self.group.conversation.messages.create(author=self.author, content='foo') self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, [self.user.email]) def test_notify_about_unseen_message(self): self.group.conversation.conversationparticipant_set.filter( user=self.user).update(seen_up_to=self.message) self.group.conversation.messages.create(author=self.author, content='this should be sent') self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to[0], self.user.email) self.assertIn('this should be sent', mail.outbox[0].body) self.assertNotIn('initial message', mail.outbox[0].body) def test_exclude_seen_message(self): with suppressed_notifications(): another_message = self.group.conversation.messages.create( author=self.author, content='foo') self.group.conversation.conversationparticipant_set.filter( user=self.user).update(seen_up_to=another_message) tasks.notify_participants(another_message) self.assertEqual(len(mail.outbox), 0) def test_exclude_thread_replies_from_conversation_notification(self): with suppressed_notifications(): self.group.conversation.messages.create( author=self.user, thread=self.message, content='first thread reply') self.group.conversation.messages.create(author=self.author, content='conversation') self.assertNotIn('first thread reply', mail.outbox[0].body) def test_does_notification_batching_in_threads(self): with suppressed_notifications(): self.group.conversation.messages.create( author=self.user, thread=self.message, content='first thread reply') recent_message = self.group.conversation.messages.create( author=self.user, thread=self.message, content='second thread reply') self.assertEqual(len(mail.outbox), 1) self.assertIn('first thread reply', mail.outbox[0].body) self.assertIn('second thread reply', mail.outbox[0].body) self.assertEqual(mail.outbox[0].to[0], self.author.email) participant = ConversationThreadParticipant.objects.get( thread=self.message, user=self.author) self.assertEqual(participant.notified_up_to.id, recent_message.id) def test_exclude_seen_message_in_thread(self): with suppressed_notifications(): another_message = self.group.conversation.messages.create( author=self.user, thread=self.message, content='first thread reply') ConversationThreadParticipant.objects.filter( thread=self.message, user=self.author).update(seen_up_to=another_message) self.assertEqual(len(mail.outbox), 0) def test_exclude_already_notified_in_thread(self): self.group.conversation.messages.create(author=self.user, thread=self.message, content='first thread reply') mail.outbox = [] self.group.conversation.messages.create(author=self.user, thread=self.message, content='second thread reply') self.assertEqual(len(mail.outbox), 1) self.assertIn('second thread reply', mail.outbox[0].body) self.assertNotIn('first thread reply', mail.outbox[0].body)
class TestConversationsEmailNotificationsAPI(APITestCase): def setUp(self): self.user = VerifiedUserFactory() self.group = GroupFactory(members=[self.user]) self.conversation = self.group.conversation self.participant = ConversationParticipant.objects.get(conversation=self.conversation, user=self.user) def test_disable_email_notifications(self): participant = ConversationParticipant.objects.get(conversation=self.conversation, user=self.user) self.assertTrue(participant.email_notifications) self.client.force_login(user=self.user) data = {'email_notifications': False} response = self.client.post( '/api/conversations/{}/email_notifications/'.format(self.conversation.id), data, format='json' ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['email_notifications'], False) participant.refresh_from_db() self.assertFalse(participant.email_notifications) def test_enable_email_notifications(self): participant = ConversationParticipant.objects.get(conversation=self.conversation, user=self.user) participant.email_notifications = False participant.save() self.assertFalse(participant.email_notifications) self.client.force_login(user=self.user) data = {'email_notifications': True} response = self.client.post( '/api/conversations/{}/email_notifications/'.format(self.conversation.id), data, format='json' ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['email_notifications'], True) participant.refresh_from_db() self.assertTrue(participant.email_notifications) def test_send_email_notifications(self): users = [VerifiedUserFactory() for _ in range(3)] [self.group.add_member(u) for u in users] mail.outbox = [] ConversationMessage.objects.create(author=self.user, conversation=self.conversation, content='asdf') actual_recipients = set(m.to[0] for m in mail.outbox) expected_recipients = set(u.email for u in users) self.assertEqual(actual_recipients, expected_recipients) self.assertEqual(len(mail.outbox), 3) def test_exclude_bounced_addresses(self): bounce_user = VerifiedUserFactory() self.conversation.join(bounce_user) EmailEvent.objects.create(address=bounce_user.email, event='bounce', payload={}) mail.outbox = [] ConversationMessage.objects.create(author=self.user, conversation=self.conversation, content='asdf') self.assertEqual(len(mail.outbox), 0) def test_exclude_unverified_addresses(self): user = UserFactory() self.conversation.join(user) mail.outbox = [] ConversationMessage.objects.create(author=self.user, conversation=self.conversation, content='asdf') self.assertEqual(len(mail.outbox), 0)