def test_conversations_list(self): self.conversation1.messages.create(author=self.participant1, content='yay') self.conversation1.messages.create(author=self.participant1, content='second!') conversation2 = ConversationFactory( participants=[self.participant1, self.participant2]) conversation2.messages.create(author=self.participant1, content='yay') self.client.force_login(user=self.participant1) response = self.client.get('/api/conversations/', format='json') response_conversations = response.data['results']['conversations'] self.assertEqual(response.status_code, status.HTTP_200_OK) # is ordered by latest message first self.assertEqual( [conversation['id'] for conversation in response_conversations], [conversation2.id, self.conversation1.id, self.conversation2.id], ) self.assertEqual(response.data['results']['meta'], { 'conversations_marked_at': None, 'threads_marked_at': None, })
def test_conversation_marked_as_seen(self): user, author = [UserFactory() for _ in range(2)] conversation = ConversationFactory(participants=[user, author]) message = ConversationMessage.objects.create(conversation=conversation, content='yay', author=author) participant = conversation.conversationparticipant_set.get(user=user) client = self.connect_as(user) participant.seen_up_to = message participant.save() messages = client.messages_by_topic self.assertEqual(len(messages['status']), 1, messages['status']) self.assertEqual( messages['status'][0]['payload'], { 'unseen_thread_count': 0, 'unseen_conversation_count': 0, 'has_unread_conversations_or_threads': False, 'groups': {}, 'places': {}, })
def test_message_create_requires_author(self): conversation = ConversationFactory() with self.assertRaises(IntegrityError): conversation.messages.create(content='ohno')
def test_message_create(self): user = UserFactory() conversation = ConversationFactory(participants=[user]) conversation.messages.create(author=user, content='yay') self.assertEqual( ConversationMessage.objects.filter(author=user).count(), 1)
def test_join(self): user = UserFactory() conversation = ConversationFactory(participants=[user]) self.assertIn(user, conversation.participants.all())
def test_tags_for_other_conversation(self): conversation = ConversationFactory() tags = conversation_tags(conversation) self.assertEqual(tags, {'type': 'unknown'})
def test_tags_for_private_conversation(self): conversation = ConversationFactory(is_private=True) tags = conversation_tags(conversation) self.assertEqual(tags, {'type': 'private'})
def test_receives_messages(self): self.maxDiff = None op_user = UserFactory() # op: original post author = UserFactory() # this user will reply to op conversation = ConversationFactory(participants=[op_user, author]) thread = conversation.messages.create(author=op_user, content='yay') # login and connect op_client = self.connect_as(op_user) author_client = self.connect_as(author) reply = ConversationMessage.objects.create( conversation=conversation, thread=thread, content='really yay?', author=author, ) op_messages = op_client.messages_by_topic # updated status self.assertEqual(len(op_messages['status']), 1, op_messages['status']) self.assertEqual( op_messages['status'][0]['payload'], { 'unseen_thread_count': 1, 'unseen_conversation_count': 0, 'has_unread_conversations_or_threads': True, 'groups': {}, 'places': {}, }) # user receive message response = op_messages['conversations:message'][0] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast( reply, thread=thread.id, )) # and they should get an updated thread object response = op_messages['conversations:message'][1] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast( thread, thread_meta={ 'is_participant': True, 'muted': False, 'participants': [op_user.id, author.id], 'reply_count': 1, 'seen_up_to': None, 'unread_reply_count': 1 }, thread=thread.id, is_editable=True, # user is author of thread message updated_at=response['payload']['updated_at'], # TODO fix test )) # reply author should get message too response = author_client.messages[0] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast(reply, is_editable=True, thread=thread.id)) # Author receives more recent `update_at` time, # because their `seen_up_to` status is set after sending the message. response = author_client.messages[1] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast( thread, thread=thread.id, thread_meta={ 'is_participant': True, 'muted': False, 'participants': [op_user.id, author.id], 'reply_count': 1, 'seen_up_to': reply.id, 'unread_reply_count': 0, }, updated_at=response['payload']['updated_at'], # TODO fix test ))
def test_receives_messages(self): self.maxDiff = None user = UserFactory() author = UserFactory() conversation = ConversationFactory(participants=[user, author]) thread = conversation.messages.create(author=user, content='yay') # login and connect client = self.connect_as(user) author_client = self.connect_as(author) reply = ConversationMessage.objects.create( conversation=conversation, thread=thread, content='really yay?', author=author, ) # user receive message response = client.messages[0] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast( reply, thread=thread.id, )) # and they should get an updated thread object response = client.messages[1] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast( thread, thread_meta={ 'is_participant': True, 'muted': False, 'participants': [user.id, author.id], 'reply_count': 1, 'seen_up_to': None, 'unread_reply_count': 1 }, thread=thread.id, is_editable=True, # user is author of thread message updated_at=response['payload']['updated_at'], # TODO fix test )) # reply author should get message too response = author_client.messages[0] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast(reply, is_editable=True, thread=thread.id)) # Author receives more recent `update_at` time, # because their `seen_up_to` status is set after sending the message. response = author_client.messages[1] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast( thread, thread=thread.id, thread_meta={ 'is_participant': True, 'muted': False, 'participants': [user.id, author.id], 'reply_count': 1, 'seen_up_to': reply.id, 'unread_reply_count': 0, }, updated_at=response['payload']['updated_at'], # TODO fix test ))
def test_single_participant_per_conversation_and_user(self): user = UserFactory() conversation = ConversationFactory(participants=[user]) with self.assertRaises(IntegrityError): ConversationParticipant.objects.create(conversation=conversation, user=user)
def setUp(self): self.user = UserFactory() self.conversation = ConversationFactory() self.conversation.join(self.user)
class TestEmailReplyAPI(APITestCase): def setUp(self): self.user = UserFactory() self.conversation = ConversationFactory() self.conversation.join(self.user) def make_message(self, reply_token=None): reply_token = reply_token or make_local_part(self.conversation, self.user) relay_message = { 'rcpt_to': '{}@example.com'.format(reply_token), 'content': { 'text': 'message body' }, } return relay_message def send_message(self, relay_message): response = self.client.post( '/api/webhooks/incoming_email/', data=[{ 'msys': { 'relay_message': relay_message } }], HTTP_X_MESSAGESYSTEMS_WEBHOOK_TOKEN='test_key', format='json') return response @override_settings(SPARKPOST_RELAY_SECRET='test_key') def test_receive_incoming_email(self): relay_message = self.make_message() response = self.send_message(relay_message) self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) self.assertEqual(self.conversation.messages.count(), 1) message = ConversationMessage.objects.first() self.assertEqual(message.received_via, 'email') incoming_email = IncomingEmail.objects.first() self.assertEqual(incoming_email.user, self.user) self.assertEqual(incoming_email.payload, relay_message) self.assertEqual(incoming_email.message, message) @override_settings(SPARKPOST_RELAY_SECRET='test_key') def test_receive_incoming_email_with_casefolding(self): relay_message = self.make_message() relay_message['rcpt_to'] = relay_message['rcpt_to'].lower() response = self.send_message(relay_message) self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) self.assertEqual(self.conversation.messages.count(), 1) message = ConversationMessage.objects.first() self.assertEqual(message.received_via, 'email') @override_settings(SPARKPOST_RELAY_SECRET='test_key') def test_handles_legacy_base64_encodings(self): reply_token = signing.dumps([self.conversation.id, self.user.id]).encode('utf8') reply_token_b64 = b64encode(reply_token).decode('utf8') relay_message = self.make_message(reply_token=reply_token_b64) response = self.send_message(relay_message) self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) self.assertEqual(self.conversation.messages.count(), 1) message = ConversationMessage.objects.first() self.assertEqual(message.received_via, 'email') @override_settings(SPARKPOST_RELAY_SECRET='test_key') def test_decode_error_returns_success(self): relay_message = self.make_message() # make invalid reply-to field relay_message['rcpt_to'] = relay_message['rcpt_to'][10:] response = self.send_message(relay_message) self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) self.assertEqual(ConversationMessage.objects.count(), 0) @override_settings(SPARKPOST_RELAY_SECRET='test_key') def test_reject_incoming_email_if_conversation_is_closed(self): mail.outbox = [] self.conversation.is_closed = True self.conversation.save() relay_message = self.make_message() response = self.send_message(relay_message) self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) self.assertEqual(self.conversation.messages.count(), 0) self.assertEqual(IncomingEmail.objects.count(), 0) self.assertEqual(len(mail.outbox), 1) self.assertIn('not accepted', mail.outbox[0].subject) self.assertIn('message body', mail.outbox[0].body)
class TestEmailReplyReceiver(APITestCase): def setUp(self): self.user = UserFactory() self.conversation = ConversationFactory() self.conversation.join(self.user) def make_message(self, reply_token=None, text='message body', html=None): reply_token = reply_token or make_local_part(self.conversation, self.user) return AnymailInboundMessage.construct( to='{}@example.com'.format(reply_token), text=text, html=html, ) def send_message(self, inbound_message): inbound_received( sender=None, event=AnymailInboundEvent( event_type=EventType.INBOUND, message=inbound_message, ), esp_name='', ) def test_receive_incoming_email(self): inbound_message = self.make_message() self.send_message(inbound_message) self.assertEqual(self.conversation.messages.count(), 1) message = ConversationMessage.objects.first() self.assertEqual(message.received_via, 'email') self.assertEqual('message body', message.content) incoming_email = IncomingEmail.objects.first() self.assertEqual(incoming_email.user, self.user) self.assertEqual(incoming_email.payload['text'], inbound_message.text) self.assertEqual(incoming_email.message, message) def test_receive_incoming_email_with_only_html(self): with open( os.path.join(os.path.dirname(__file__), './ms_outlook_2010.html')) as f: html_message = f.read() inbound_message = self.make_message(text=None, html=html_message) self.send_message(inbound_message) self.assertEqual(self.conversation.messages.count(), 1) message = ConversationMessage.objects.first() self.assertEqual(message.received_via, 'email') self.assertIn('Hi. I am fine.', message.content) def test_receive_incoming_email_with_casefolding(self): inbound_message = self.make_message() inbound_message.replace_header('to', inbound_message.to[0].addr_spec.lower()) self.send_message(inbound_message) self.assertEqual(self.conversation.messages.count(), 1) message = ConversationMessage.objects.first() self.assertEqual(message.received_via, 'email') def test_decode_error_is_silent(self): inbound_message = self.make_message() # make invalid reply-to field inbound_message.replace_header('to', inbound_message.to[0].addr_spec[10:]) self.send_message(inbound_message) self.assertEqual(ConversationMessage.objects.count(), 0) def test_reject_incoming_email_if_conversation_is_closed(self): mail.outbox = [] self.conversation.is_closed = True self.conversation.save() inbound_message = self.make_message() self.send_message(inbound_message) self.assertEqual(self.conversation.messages.count(), 0) self.assertEqual(IncomingEmail.objects.count(), 0) self.assertEqual(len(mail.outbox), 1) self.assertIn('not accepted', mail.outbox[0].subject) self.assertIn('message body', mail.outbox[0].body)
def setUp(self): self.user = UserFactory() self.conversation = ConversationFactory(participants=[self.user], is_closed=True)
def test_receives_messages(self): self.maxDiff = None user = UserFactory() author = UserFactory() # join a conversation conversation = ConversationFactory(participants=[user, author]) # login and connect client = self.connect_as(user) author_client = self.connect_as(author) # add a message to the conversation message = ConversationMessage.objects.create(conversation=conversation, content='yay', author=author) # hopefully they receive it! ws_messages = client.messages_by_topic self.assertEqual(len(ws_messages['conversations:conversation']), 1, ws_messages['conversations:conversation']) self.assertEqual(len(ws_messages['conversations:message']), 1, ws_messages['conversations:message']) self.assertEqual(len(ws_messages['status']), 1, ws_messages['status']) self.assertEqual( ws_messages['status'][0]['payload'], { 'unseen_conversation_count': 1, 'unseen_thread_count': 0, 'has_unread_conversations_or_threads': True, 'groups': {}, 'places': {}, }) response = ws_messages['conversations:message'][0] parse_dates(response) self.assertEqual(response, make_conversation_message_broadcast(message)) # and they should get an updated conversation object response = ws_messages['conversations:conversation'][0] parse_dates(response) del response['payload']['participants'] self.assertEqual( response, make_conversation_broadcast( conversation, unread_message_count=1, updated_at=response['payload']['updated_at'], # TODO fix test )) # author should get message & updated conversations object too response = author_client.messages[0] parse_dates(response) self.assertEqual( response, make_conversation_message_broadcast(message, is_editable=True)) # Author receives more recent `update_at` time, # because their `seen_up_to` status is set after sending the message. author_participant = conversation.conversationparticipant_set.get( user=author) response = author_client.messages[1] parse_dates(response) del response['payload']['participants'] self.assertEqual( response, make_conversation_broadcast( conversation, seen_up_to=message.id, updated_at=author_participant.updated_at))
class TestConversationsAPI(APITestCase): def setUp(self): self.participant1 = UserFactory() self.participant2 = UserFactory() self.participant3 = UserFactory() self.not_participant1 = UserFactory() self.not_participant2 = UserFactory() self.not_participant3 = UserFactory() self.conversation1 = ConversationFactory() self.conversation1.sync_users( [self.participant1, self.participant2, self.participant3]) self.conversation1.messages.create(author=self.participant1, content='hello') self.conversation2 = ConversationFactory() self.conversation2.sync_users([self.participant1]) self.conversation2.messages.create(author=self.participant1, content='hello2') self.conversation3 = ConversationFactory() # conversation noone is in def test_conversations_list(self): self.conversation1.messages.create(author=self.participant1, content='yay') self.conversation1.messages.create(author=self.participant1, content='second!') conversation2 = ConversationFactory( participants=[self.participant1, self.participant2]) conversation2.messages.create(author=self.participant1, content='yay') self.client.force_login(user=self.participant1) response = self.client.get('/api/conversations/', format='json') response_conversations = response.data['results']['conversations'] self.assertEqual(response.status_code, status.HTTP_200_OK) # is ordered by latest message first self.assertEqual( [conversation['id'] for conversation in response_conversations], [conversation2.id, self.conversation1.id, self.conversation2.id], ) self.assertEqual(response.data['results']['meta'], { 'marked_at': None, }) 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_list_only_unread_conversations(self): self.conversation1.messages.create(author=self.participant2, content='unread') self.client.force_login(user=self.participant1) response = self.client.get('/api/conversations/?exclude_read=True', format='json') conversations = response.data['results']['conversations'] self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(conversations[0]['id'], self.conversation1.id) self.assertEqual(len(conversations), 1) def test_list_messages(self): self.client.force_login(user=self.participant1) with self.assertNumQueries(5): response = self.client.get('/api/messages/?conversation={}'.format( self.conversation1.id), format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data['results']), 1) self.assertEqual(response.data['results'][0]['content'], 'hello') def test_get_message(self): self.client.force_login(user=self.participant1) message_id = self.conversation1.messages.first().id response = self.client.get('/api/messages/{}/'.format(message_id), format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['id'], message_id) def test_can_get_messages_for_all_conversations(self): self.client.force_login(user=self.participant1) response = self.client.get('/api/messages/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data['results']), 2) self.assertEqual(response.data['results'][0]['content'], 'hello2') self.assertEqual(response.data['results'][1]['content'], 'hello') def test_cannot_get_messages_if_not_in_conversation(self): self.client.force_login(user=self.participant1) response = self.client.get('/api/messages/?conversation={}'.format( self.conversation3.id), format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data) def test_same_error_if_conversation_does_not_exist_as_if_you_are_just_not_in_it( self): self.client.force_login(user=self.participant1) response = self.client.get( '/api/messages/?conversation={}'.format(982398723), format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data) def test_create_message(self): conversation = ConversationFactory(participants=[self.participant1]) self.client.force_login(user=self.participant1) data = {'conversation': conversation.id, 'content': 'a nice message'} response = self.client.post('/api/messages/', data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data) self.assertEqual(response.data['content'], data['content']) self.assertEqual(conversation.messages.first().content, data['content']) self.assertEqual(conversation.messages.first().created_at, parse(response.data['created_at']), response.data) self.assertEqual(conversation.messages.first().id, response.data['id']) self.assertEqual(conversation.messages.first().author.id, response.data['author']) def test_cannot_create_message_without_specifying_conversation(self): self.client.force_login(user=self.participant1) data = {'content': 'a nice message'} response = self.client.post('/api/messages/', data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_cannot_create_message_if_not_in_conversation(self): self.client.force_login(user=self.participant1) data = { 'conversation': self.conversation3.id, 'content': 'a nice message' } response = self.client.post('/api/messages/', data, format='json') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_can_mark_all_as_seen(self): self.client.force_login(user=self.participant1) response = self.client.post('/api/conversations/mark_all_seen/') self.assertEqual(response.status_code, status.HTTP_200_OK) time1 = parse(response.data['marked_at']) self.assertLess(time1, timezone.now()) # time should naturally increase each time we mark response = self.client.post('/api/conversations/mark_all_seen/') time2 = parse(response.data['marked_at']) self.assertLess(time1, time2)
def setUp(self): self.user = UserFactory() self.users = [UserFactory() for _ in range(10)] + [self.user] self.user_not_in_conversation = UserFactory() # filters user who share a conversation, so add all to one ConversationFactory(participants=self.users)