class TestApplicationConversationModel(APITestCase): def setUp(self): self.applicant = VerifiedUserFactory() self.member = VerifiedUserFactory() self.group = GroupFactory(members=[self.member]) self.application = ApplicationFactory(group=self.group, user=self.applicant) self.conversation = Conversation.objects.get_for_target(self.application) def test_member_leaves_group(self): GroupMembership.objects.filter(user=self.member, group=self.group).delete() self.assertNotIn( self.member, self.conversation.participants.all(), ) def test_user_erased(self): self.applicant.refresh_from_db() # otherwise user.photo.name is None self.applicant.erase() self.application.refresh_from_db() self.assertEqual(self.application.status, ApplicationStatus.WITHDRAWN.value) def test_deleting_application_deletes_conversation(self): Application.objects.filter(user=self.applicant, group=self.group).delete() self.assertIsNone(Conversation.objects.get_for_target(self.application)) def test_sets_group(self): self.assertEqual(self.conversation.group, self.group) def test_withdraw_pending_application_when_user_joins_group(self): self.group.add_member(self.applicant) self.application.refresh_from_db() self.assertEqual(self.application.status, ApplicationStatus.WITHDRAWN.value)
class GroupConversationReceiverPushTests(TransactionTestCase): 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)
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('karrot.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('karrot.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 TestApplicationReceivers(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 TestPlaceModel(TestCase): def setUp(self): self.group = GroupFactory() def test_create_fails_if_name_too_long(self): with self.assertRaises(DataError): Place.objects.create(name='a' * 81, group=self.group) def test_create_place_with_same_name_fails(self): Place.objects.create(name='abcdef', group=self.group) with self.assertRaises(IntegrityError): Place.objects.create(name='abcdef', group=self.group) def test_create_place_with_same_name_in_different_groups_works(self): Place.objects.create(name='abcdef', group=self.group) Place.objects.create(name='abcdef', group=GroupFactory()) def test_get_active_status(self): s = Place.objects.create(name='my place', group=self.group) self.assertFalse(s.is_active()) s.status = 'active' s.save() self.assertTrue(s.is_active()) s.status = 'declined' s.save() self.assertFalse(s.is_active()) def test_removes_subscription_when_leaving_group(self): place = Place.objects.create(name='abcdef', group=self.group) user = UserFactory() self.group.add_member(user) place.placesubscription_set.create(user=user) self.group.remove_member(user) self.assertFalse(place.placesubscription_set.filter(user=user).exists()) conversation = place.conversation self.assertFalse(conversation.conversationparticipant_set.filter(user=user).exists())
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.trust_threshold_for_newcomer(), 1, ) def test_ramp_up_threshold(self): self.create_group_with_members(5) self.assertEqual( self.group.trust_threshold_for_newcomer(), 2, ) def test_max_threshold(self): self.create_group_with_members(6) self.assertEqual( self.group.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.trust_threshold_for_newcomer(), 1, )
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_mute(self): participant = ConversationParticipant.objects.get( conversation=self.conversation, user=self.user) self.assertFalse(participant.muted) self.client.force_login(user=self.user) data = {'notifications': ConversationNotificationStatus.MUTED.value} response = self.client.patch('/api/conversations/{}/'.format( self.conversation.id), data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['notifications'], ConversationNotificationStatus.MUTED.value) participant.refresh_from_db() self.assertTrue(participant.muted) def test_unmute(self): participant = ConversationParticipant.objects.get( conversation=self.conversation, user=self.user) participant.muted = True participant.save() self.assertTrue(participant.muted) self.client.force_login(user=self.user) data = {'notifications': ConversationNotificationStatus.ALL.value} response = self.client.patch('/api/conversations/{}/'.format( self.conversation.id), data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['notifications'], ConversationNotificationStatus.ALL.value) participant.refresh_from_db() self.assertFalse(participant.muted) 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.group.add_member(bounce_user) for _ in range(5): 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_not_exclude_bounced_addresses_if_too_old(self): bounce_user = VerifiedUserFactory() self.group.add_member(bounce_user) some_months_ago = timezone.now() - relativedelta(months=4) for _ in range(5): EmailEvent.objects.create(created_at=some_months_ago, 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), 1) def test_exclude_unverified_addresses(self): user = UserFactory() # not verified self.group.add_member(user) mail.outbox = [] ConversationMessage.objects.create(author=self.user, conversation=self.conversation, content='asdf') self.assertEqual(len(mail.outbox), 0)
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(self): member_client = self.connect_as(self.member) joining_client = self.connect_as(self.user) nonmember_client = self.connect_as(UserFactory()) self.group.add_member(self.user) response = member_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 = member_client.messages_by_topic.get( 'groups:group_preview')[0] self.assertIn(self.user.id, response['payload']['members']) self.assertNotIn('memberships', response['payload']) response = joining_client.messages_by_topic.get( 'groups:group_detail')[0] self.assertIn(self.user.id, response['payload']['members']) response = joining_client.messages_by_topic.get( 'groups:group_preview')[0] self.assertIn(self.user.id, response['payload']['members']) self.assertNotIn('groups:group_detail', nonmember_client.messages_by_topic.keys()) response = nonmember_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_leave_as_leaving_user(self): # Clean up notifications from group setup, to prevent notification_deleted messages Notification.objects.all().delete() client = self.connect_as(self.member) self.group.remove_member(self.member) response = client.messages_by_topic.get('groups:group_preview')[0] self.assertNotIn(self.user.id, response['payload']['members']) self.assertNotIn('memberships', response['payload']) self.assertEqual([m['topic'] for m in client.messages], [ 'history:history', 'conversations:leave', 'conversations:conversation', 'status', 'groups:group_preview', ]) status_messages = client.messages_by_topic['status'] self.assertEqual(len(status_messages), 1, status_messages) self.assertEqual( status_messages[0]['payload'], { 'unseen_conversation_count': 0, 'unseen_thread_count': 0, 'has_unread_conversations_or_threads': False, 'groups': { self.group.id: { 'unread_wall_message_count': 0 } }, 'places': {}, }) def test_receive_group_roles_update(self): membership = self.group.add_member(self.user) client = self.connect_as(self.member) membership.add_roles([roles.GROUP_EDITOR]) membership.save() response = client.messages_by_topic.get('groups:group_detail')[0] self.assertIn( roles.GROUP_EDITOR, response['payload']['memberships'][self.user.id]['roles']) self.assertEqual([m['topic'] for m in client.messages], [ 'notifications:notification', 'status', 'groups:group_detail', ])
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_mute(self): participant = ConversationParticipant.objects.get( conversation=self.conversation, user=self.user) self.assertFalse(participant.muted) self.client.force_login(user=self.user) data = {'notifications': ConversationNotificationStatus.MUTED.value} response = self.client.patch('/api/conversations/{}/'.format( self.conversation.id), data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['notifications'], ConversationNotificationStatus.MUTED.value) participant.refresh_from_db() self.assertTrue(participant.muted) def test_unmute(self): participant = ConversationParticipant.objects.get( conversation=self.conversation, user=self.user) participant.muted = True participant.save() self.assertTrue(participant.muted) self.client.force_login(user=self.user) data = {'notifications': ConversationNotificationStatus.ALL.value} response = self.client.patch('/api/conversations/{}/'.format( self.conversation.id), data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['notifications'], ConversationNotificationStatus.ALL.value) participant.refresh_from_db() self.assertFalse(participant.muted) def test_send_email_notifications(self): users = [VerifiedUserFactory() for _ in range(3)] [self.group.add_member(u) for u in users] mail.outbox = [] with execute_scheduled_tasks_immediately(): 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_unverified_addresses(self): user = UserFactory() # not verified self.group.add_member(user) mail.outbox = [] ConversationMessage.objects.create(author=self.user, conversation=self.conversation, content='asdf') self.assertEqual(len(mail.outbox), 0)
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_per_person(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), 5) 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([email.to[0] for email in emails]), sorted([member.email for member in expected_members])) self.assertNotIn(self.user_without_notifications.email, emails[0].to) def test_creates_one_email_per_person_with_different_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), 15) 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), 5) 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([email.to[0] for email in emails]), 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) place = PlaceFactory(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(place=place, max_collectors=1, collectors=[user], is_disabled=True) from_date, to_date = karrot.groups.emails.calculate_group_summary_dates( self.group) data = karrot.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) place = PlaceFactory(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(place=place) PickupDateFactory(place=place, 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(place=place) # a fulfilled pickup PickupDateFactory(place=place, max_collectors=1, collectors=[user]) from_date, to_date = karrot.groups.emails.calculate_group_summary_dates( self.group) data = karrot.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)