def test_mark_issue_conversation_as_closed(self): long_time_ago = timezone.now() - relativedelta(days=30) # not cancelled -> should stay open issue = IssueFactory() conversation = issue.conversation conversation.messages.create(content='hello', author=issue.created_by, created_at=long_time_ago) # cancelled but recently commented on -> should stay open with freeze_time(long_time_ago, tick=True): issue_ended_recently = IssueFactory() issue_ended_recently.cancel() conversation_ended_recently = issue_ended_recently.conversation conversation_ended_recently.messages.create( content='hello', author=issue_ended_recently.created_by, created_at=timezone.now() ) # cancelled and not commented on -> should be closed with freeze_time(long_time_ago, tick=True): issue_ended = IssueFactory() issue_ended.cancel() conversation_ended = issue_ended.conversation conversation_ended.messages.create(content='hello', author=issue_ended.created_by, created_at=long_time_ago) conversations = Conversation.objects.filter(target_type__model='issue') self.assertEqual(conversations.count(), 3) self.assertEqual(conversations.filter(is_closed=False).count(), 3) mark_conversations_as_closed() self.assertEqual(conversations.filter(is_closed=False).count(), 2) self.assertEqual(conversations.filter(is_closed=True).first(), conversation_ended)
def test_conflict_resolution_notifications(self): user1, user2, user3 = UserFactory(), UserFactory(), UserFactory() group = GroupFactory(members=[user1, user2, user3]) Notification.objects.all().delete() issue = IssueFactory(group=group, created_by=user1, affected_user=user2) notifications = Notification.objects.order_by('type') self.assertEqual(notifications.count(), 2) self.assertEqual( notifications[1].type, NotificationType.CONFLICT_RESOLUTION_CREATED_ABOUT_YOU.value) self.assertEqual(notifications[1].user, user2) self.assertEqual(notifications[1].context, { 'issue': issue.id, 'group': group.id, 'user': user2.id }) self.assertEqual(notifications[0].type, NotificationType.CONFLICT_RESOLUTION_CREATED.value) self.assertEqual(notifications[0].user, user3) # keep discussing Notification.objects.all().delete() voting = issue.latest_voting() vote_for_further_discussion(voting=voting, user=user1) with fast_forward_to_voting_expiration(voting): process_expired_votings() notifications = Notification.objects.order_by('type') self.assertEqual(notifications.count(), 3) self.assertEqual(notifications[0].type, NotificationType.CONFLICT_RESOLUTION_CONTINUED.value) self.assertEqual(notifications[1].type, NotificationType.CONFLICT_RESOLUTION_CONTINUED.value) self.assertEqual( notifications[2].type, NotificationType.CONFLICT_RESOLUTION_CONTINUED_ABOUT_YOU.value) # remove user Notification.objects.all().delete() voting = issue.latest_voting() vote_for_remove_user(voting=voting, user=user1) with fast_forward_to_voting_expiration(voting): process_expired_votings() notifications = Notification.objects.order_by('type') self.assertEqual(notifications.count(), 3) self.assertEqual([n.type for n in notifications], [ NotificationType.CONFLICT_RESOLUTION_DECIDED.value, NotificationType.CONFLICT_RESOLUTION_DECIDED.value, NotificationType.CONFLICT_RESOLUTION_YOU_WERE_REMOVED.value, ])
def setUp(self): self.member = VerifiedUserFactory() self.affected_member = VerifiedUserFactory() self.group = GroupFactory(members=[self.member, self.affected_member]) self.issue = IssueFactory(group=self.group, created_by=self.member, affected_user=self.affected_member) # add notification type to send out emails for membership in self.group.groupmembership_set.all(): membership.add_notification_types( [GroupNotificationType.CONFLICT_RESOLUTION]) membership.save()
def test_vote(self): member = VerifiedUserFactory() member2 = VerifiedUserFactory() group = GroupFactory(members=[member, member2]) issue = IssueFactory(group=group, affected_user=member2, created_by=member) client = self.connect_as(member) vote_for_further_discussion(voting=issue.latest_voting(), user=member) messages = client.messages_by_topic self.assertEqual(len(client.messages), 1) self.assertEqual(len(messages['issues:issue']), 1)
def test_list_conversations_with_related_data_efficiently(self): user = UserFactory() group = GroupFactory(members=[user]) place = PlaceFactory(group=group) pickup = PickupDateFactory(place=place) application = ApplicationFactory(user=UserFactory(), group=group) issue = IssueFactory(group=group) conversations = [ t.conversation for t in (group, pickup, application, issue) ] [c.sync_users([user]) for c in conversations] [c.messages.create(content='hey', author=user) for c in conversations] ConversationMeta.objects.get_or_create(user=user) self.client.force_login(user=user) with self.assertNumQueries(13): response = self.client.get('/api/conversations/', {'group': group.id}, format='json') results = response.data['results'] self.assertEqual(len(results['conversations']), len(conversations)) self.assertEqual(results['pickups'][0]['id'], pickup.id) self.assertEqual(results['applications'][0]['id'], application.id) self.assertEqual(results['issues'][0]['id'], issue.id)
def test_issue_message_title(self): issue = IssueFactory() author = issue.group.members.first() conversation = issue.conversation message = conversation.messages.create(author=author, content='bla') title = get_message_title(message, 'en') self.assertIn('☹️', title)
def test_create_voting_ends_soon_notifications(self): creator, affected_user, voter = UserFactory(), UserFactory(), UserFactory() group = GroupFactory(members=[creator, affected_user, voter]) issue = IssueFactory(group=group, created_by=creator, affected_user=affected_user) voting = issue.latest_voting() # let's vote with user "voter" vote_for_further_discussion(voting=voting, user=voter) Notification.objects.all().delete() with fast_forward_just_before_voting_expiration(voting): create_voting_ends_soon_notifications() # can call it a second time without duplicating notifications create_voting_ends_soon_notifications() notifications = Notification.objects.filter(type=NotificationType.VOTING_ENDS_SOON.value) # user "voter" is not being notified self.assertEqual( sorted([n.user_id for n in notifications]), sorted([issue.affected_user_id, issue.created_by_id]) )
def setUp(self): self.user = VerifiedUserFactory() self.more_users = [VerifiedUserFactory() for _ in range(2)] self.group = GroupFactory(members=[self.user, *self.more_users]) for membership in self.group.groupmembership_set.all(): membership.add_notification_types([GroupNotificationType.CONFLICT_RESOLUTION]) membership.save() self.issue = IssueFactory(group=self.group, created_by=self.user) self.conversation = self.issue.conversation mail.outbox = []
def test_issue_created(self): member = VerifiedUserFactory() member2 = VerifiedUserFactory() group = GroupFactory(members=[member, member2]) client = self.connect_as(member) IssueFactory(group=group, affected_user=member2, created_by=member) messages = client.messages_by_topic self.assertIn('issues:issue', messages) self.assertIn('conversations:conversation', messages) self.assertEqual(len(messages['issues:issue']), 1)
def test_mark_empty_as_closed(self): long_time_ago = timezone.now() - relativedelta(days=30) # no messages and cancelled some time ago -> should be closed with freeze_time(long_time_ago, tick=True): issue_ended_long_ago = IssueFactory() issue_ended_long_ago.cancel() # no messages and cancelled recently -> should stay open issue_ended_recently = IssueFactory() issue_ended_recently.cancel() mark_conversations_as_closed() conversations = Conversation.objects.filter(target_type__model='issue') self.assertEqual(conversations.filter(is_closed=True).first(), issue_ended_long_ago.conversation) self.assertEqual(conversations.filter(is_closed=False).first(), issue_ended_recently.conversation)
def test_get_conversation_status_efficiently(self): user = UserFactory() group = GroupFactory(members=[user]) place = PlaceFactory(group=group) activity = ActivityFactory(place=place) application = ApplicationFactory(user=UserFactory(), group=group) issue = IssueFactory(group=group) offer = OfferFactory(group=group) conversations = [ t.conversation for t in (group, activity, application, issue, offer) ] another_user = UserFactory() [c.sync_users([user, another_user]) for c in conversations] [ c.messages.create(content='hey', author=another_user) for c in conversations ] with self.assertNumQueries(2): unread_conversations(user)
class IssueModelTests(TestCase): def setUp(self): self.member = VerifiedUserFactory() self.affected_member = VerifiedUserFactory() self.group = GroupFactory(members=[self.member, self.affected_member]) self.issue = IssueFactory(group=self.group, created_by=self.member, affected_user=self.affected_member) # add notification type to send out emails for membership in self.group.groupmembership_set.all(): membership.add_notification_types( [GroupNotificationType.CONFLICT_RESOLUTION]) membership.save() def get_voting(self): return self.issue.votings.first() def vote_on(self, option_type, user=None): for option in self.get_voting().options.all(): option.votes.create(user=user or self.member, score=5 if option.type == option_type else 0) def fast_forward_to_voting_expiration(self): time_when_voting_expires = self.get_voting( ).expires_at + relativedelta(hours=1) return freeze_time(time_when_voting_expires, tick=True) def process_votings(self): with self.fast_forward_to_voting_expiration(): process_expired_votings() def create_editor(self): user = VerifiedUserFactory() self.group.groupmembership_set.create(user=user, roles=[GROUP_EDITOR]) return user def test_removes_user(self): self.vote_on(OptionTypes.REMOVE_USER.value) History.objects.all().delete() self.process_votings() with self.fast_forward_to_voting_expiration(): self.issue.refresh_from_db() self.assertTrue(self.issue.is_decided()) self.assertFalse(self.group.is_member(self.affected_member)) self.assertEqual(self.issue.votings.count(), 1) self.assertTrue(self.get_voting().is_expired()) self.assertEqual(History.objects.count(), 1) self.assertEqual(History.objects.first().typus, HistoryTypus.MEMBER_REMOVED) def test_further_discussion(self): self.vote_on(OptionTypes.FURTHER_DISCUSSION.value) mail.outbox = [] self.process_votings() with self.fast_forward_to_voting_expiration(): self.issue.refresh_from_db() self.assertFalse(self.issue.is_decided()) self.assertTrue(self.group.is_member(self.affected_member)) self.assertEqual(self.issue.votings.count(), 2) self.assertEqual([ v.is_expired() for v in self.issue.votings.order_by('created_at') ], [True, False]) # check if emails have been sent self.assertEqual(len(mail.outbox), 2) email_to_affected_user = next( email for email in mail.outbox if email.to[0] == self.affected_member.email) email_to_editor = next(email for email in mail.outbox if email.to[0] == self.member.email) self.assertIn('with you', email_to_affected_user.subject) self.assertIn('with {}'.format(self.affected_member.display_name), email_to_editor.subject) def test_no_change(self): self.vote_on(OptionTypes.NO_CHANGE.value) self.process_votings() with self.fast_forward_to_voting_expiration(): self.issue.refresh_from_db() self.assertTrue(self.issue.is_decided()) self.assertTrue(self.group.is_member(self.affected_member)) self.assertEqual(self.issue.votings.count(), 1) self.assertTrue(self.get_voting().is_expired()) def test_tie_results_in_further_discussion(self): self.vote_on(OptionTypes.NO_CHANGE.value, user=self.member) voter = self.create_editor() self.vote_on(OptionTypes.REMOVE_USER.value, user=voter) self.process_votings() with self.fast_forward_to_voting_expiration(): self.assertEqual(self.get_voting().accepted_option.type, OptionTypes.FURTHER_DISCUSSION.value) def test_no_vote_results_in_cancelled_issue(self): self.process_votings() self.issue.refresh_from_db() self.assertTrue(self.issue.is_cancelled()) def test_voluntary_user_removal_results_in_cancelled_issue(self): self.group.groupmembership_set.filter( user=self.affected_member).delete() self.issue.refresh_from_db() self.assertTrue(self.issue.is_cancelled()) def test_new_members_are_not_in_existing_issue_conversations(self): # create a new member and a new editor self.group.groupmembership_set.create(user=VerifiedUserFactory(), roles=[roles.GROUP_EDITOR]) self.group.groupmembership_set.create(user=VerifiedUserFactory()) # ...they shouldn't become part of existing issue conversations expected_ids = sorted([self.member.id, self.affected_member.id]) conversation_participant_ids = sorted( self.issue.conversation.participants.values_list('id', flat=True)) self.assertEqual(conversation_participant_ids, expected_ids) def test_remove_participant_if_they_leave_group(self): self.assertTrue( self.issue.conversation.participants.filter( id=self.member.id).exists()) self.group.groupmembership_set.filter(user=self.member).delete() self.assertFalse( self.issue.conversation.participants.filter( id=self.member.id).exists())
def create_issue(self, **kwargs): return IssueFactory(group=self.group, created_by=self.member, **kwargs)
def random_issue(): return IssueFactory(group=random_group(), created_by=random_user(), affected_user=random_user())