示例#1
0
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)
示例#3
0
    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)
示例#4
0
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)
示例#5
0
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)
示例#6
0
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,
            },
        }])
示例#7
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_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'])
示例#8
0
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,
        )
示例#9
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_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)
示例#10
0
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,
                }
            }])
示例#11
0
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)
示例#12
0
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)