def test_thread_returned_with_thread_id_returns_404(self): """retrieves thread using id that doesn't exist""" with self.app.app_context(): with current_app.test_request_context(): with self.assertRaises(NotFound): Retriever.retrieve_thread('anotherThreadId', self.user_respondent)
def test_msg_returned_with_msg_id_returns_404(self): """retrieves message using id that doesn't exist""" message_id = "1" with self.app.app_context(): with current_app.test_request_context(): with self.assertRaises(NotFound): Retriever.retrieve_message(message_id, self.user_internal)
def test_latest_message_from_each_thread_chosen_desc(self): """checks the message chosen for each thread is the latest message within that thread""" for _ in range(5): self.create_thread(no_of_messages=3) with self.app.app_context(): with current_app.test_request_context(): args = get_args(limit=MESSAGE_QUERY_LIMIT) response = Retriever.retrieve_thread_list( self.user_internal, args) date = [] thread_ids = [] msg_ids = [] for message in response.items: serialized_msg = message.serialize(self.user_internal) if 'sent_date' in serialized_msg: date.append(serialized_msg['sent_date']) elif 'modified_date' in serialized_msg: date.append(serialized_msg['modified_date']) thread_ids.append(serialized_msg['thread_id']) msg_ids.append(serialized_msg['msg_id']) self.assertEqual(len(msg_ids), 5) args = get_args(page=1, limit=MESSAGE_QUERY_LIMIT) for x in range(0, len(thread_ids)): thread = Retriever.retrieve_thread(thread_ids[x], self.user_internal) self.assertEqual(date[x], str(thread.all()[0].events[0].date_time)) self.assertEqual(msg_ids[x], thread.all()[0].events[0].msg_id)
def test_thread_count_by_survey_my_conversations_on(self): """checks that the returned thread count is the same for every internal user even if they are not part of the conversations""" request_args = MessageArgs(page=0, limit=100, business_id=None, cc=None, label=None, desc=None, ce=None, surveys=[self.BRES_SURVEY], is_closed=False, my_conversations=True, new_respondent_conversations=False, all_conversation_types=False, unread_conversations=False) for _ in range(5): self.create_thread(no_of_messages=2) with self.app.app_context(): with current_app.test_request_context(): thread_count_internal = Retriever.thread_count_by_survey( request_args, User(self.default_internal_actor, role='internal')) thread_count_second_internal = Retriever.thread_count_by_survey( request_args, User(self.second_internal_actor, role='internal')) self.assertEqual(thread_count_internal, 5) self.assertEqual(thread_count_second_internal, 0)
def get(): """Get count of all conversations for a specific internal user Typically for a specific survey, supports filtering by case, collection exercise, business party id etc :returns: if all_conversation_types is set 'true' returns json representing all 4 counts else returns the count for the single type combination requested. """ logger.info("Getting count of threads for user", user_uuid=g.user.user_uuid) message_args = get_options(request.args) if message_args.all_conversation_types: logger.info("Getting counts for all conversation states for user", user_uuid=g.user.user_uuid) return jsonify(totals=Retriever. thread_count_by_survey_and_conversation_states( message_args, g.user)) if message_args.unread_conversations: logger.info("Getting counts of unread conversations", user_uuid=g.user.user_uuid) return jsonify(total=Retriever.unread_message_count(g.user)) return jsonify( total=Retriever.thread_count_by_survey(message_args, g.user))
def test_all_messages_in_conversation_marked_unread(self): # create a thread with two messages conversation_id = self.create_conversation_with_respondent_as_unread( user=self.user_respondent, message_count=2) with self.app.app_context(): conversation = Retriever.retrieve_thread(conversation_id, self.user_respondent) for msg in conversation.all(): # as there's two ways that a message is unread, first check the `read at` time isn't set self.assertIsNone(msg.read_at) # now collect all the message labels labels = [] for status in msg.statuses: labels.append(status.label) # and check the unread is present self.assertTrue("UNREAD" in labels) # now mark the first message as read and check the whole conversation is now read Modifier.mark_message_as_read( conversation[0].serialize(self.user_respondent), self.user_respondent) con = Retriever.retrieve_thread(conversation_id, self.user_respondent) for msg in con.all(): # message `read at` should now be set self.assertIsNotNone(msg.read_at) # collect the labels again labels = [] for status in msg.statuses: labels.append(status.label) # and there should be no unread self.assertFalse("UNREAD" in labels)
def test_open_conversation(self): """Test re-opening conversation works""" conversation_id = self.add_conversation(is_closed=True) with self.app.app_context(): # msg_id is the same as thread id for a conversation of 1 metadata = Retriever.retrieve_conversation_metadata( conversation_id) Modifier.open_conversation(metadata, self.user_internal) metadata = Retriever.retrieve_conversation_metadata( conversation_id) self.assertFalse(metadata.is_closed) self.assertIsNone(metadata.closed_by) self.assertIsNone(metadata.closed_by_uuid) self.assertIsNone(metadata.closed_at)
def test_get_nonexistant_conversation_returns_none(self): """retrieves message using id that doesn't exist""" random_uuid = str(uuid.uuid4()) with self.app.app_context(): with current_app.test_request_context(): result = Retriever.retrieve_conversation_metadata(random_uuid) self.assertIsNone(result)
def test_db_connection_test_fails(self): """runs check_db_connection function after dropping database""" with self.app.app_context(): database.db.drop_all() with current_app.test_request_context(): response = Retriever.check_db_connection() self.assertEqual(response.status_code, 500)
def test_all_msg_returned_for_thread_id(self): """retrieves messages for thread_id from database""" thread_id = self.create_thread(no_of_messages=6) with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_thread(thread_id, self.user_respondent) self.assertEqual(len(response.all()), 6)
def test_msg_returned_with_msg_id_true(self): """retrieves message using id""" self.create_thread(no_of_messages=2) with self.engine.connect() as con: query = con.execute( 'SELECT msg_id FROM securemessage.secure_message LIMIT 1') msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_message( msg_id, self.user_internal) self.assertEqual(response['msg_id'], msg_id)
def test_read_date_is_set(self): """testing message read_date is set when unread label is removed""" thread_id = self.populate_database(1, mark_as_read=False) with self.app.app_context(): thread = Retriever.retrieve_thread(thread_id, self.user_respondent).all() serialised_message = Retriever.retrieve_message( thread[0].msg_id, self.user_internal) Modifier.mark_message_as_read(serialised_message, self.user_internal) serialised_message = Retriever.retrieve_message( thread[0].msg_id, self.user_internal) db_message = SecureMessage.query.filter( SecureMessage.msg_id == serialised_message['msg_id']).one() self.assertIsNotNone(serialised_message['read_date']) self.assertTrue(isinstance(db_message.read_at, datetime.datetime)) # Test that timestamp on read message is less than 3 seconds old to prove it # was only just created delta = datetime.datetime.utcnow() - db_message.read_at self.assertTrue(delta.total_seconds() < 3)
def test_close_conversation(self): """Test close conversation works""" conversation_id = self.populate_database(1) with self.app.app_context(): internal_user_service.use_mock_service() # msg_id is the same as thread id for a conversation of 1 metadata = Retriever.retrieve_conversation_metadata( conversation_id) Modifier.close_conversation(metadata, self.user_internal) metadata = Retriever.retrieve_conversation_metadata( conversation_id) self.assertTrue(metadata.is_closed) self.assertEqual(metadata.closed_by, "Selphie Tilmitt") self.assertEqual(metadata.closed_by_uuid, "ce12b958-2a5f-44f4-a6da-861e59070a31") self.assertTrue(isinstance(metadata.closed_at, datetime.datetime)) # Test that timestamp on read message is less than 3 seconds old to prove it # was only just created delta = datetime.datetime.utcnow() - metadata.closed_at self.assertTrue(delta.total_seconds() < 3)
def test_read_date_is_not_reset(self): """testing message read_date is not reset when unread label is removed again""" self.populate_database(1) with self.engine.connect() as con: query = con.execute( 'SELECT msg_id FROM securemessage.secure_message LIMIT 1') msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): message = Retriever.retrieve_message(msg_id, self.user_internal) Modifier.mark_message_as_read(message, self.user_internal) message = Retriever.retrieve_message(msg_id, self.user_internal) read_date_set = message['read_date'] Modifier.add_unread(message, self.user_internal) message = Retriever.retrieve_message(msg_id, self.user_internal) Modifier.mark_message_as_read(message, self.user_internal) message = Retriever.retrieve_message(msg_id, self.user_internal) self.assertEqual(message['read_date'], read_date_set)
def test_correct_labels_returned_external(self): """retrieves message using id and checks the labels are correct""" self.create_thread() with self.engine.connect() as con: query = con.execute( 'SELECT msg_id FROM securemessage.secure_message LIMIT 1') msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_message( msg_id, self.user_respondent) labels = ['SENT'] self.assertCountEqual(response['labels'], labels)
def test_sent_date_returned_for_message(self): """retrieves message using id and checks the sent date returned""" self.create_thread() with self.engine.connect() as con: query = con.execute( 'SELECT msg_id FROM securemessage.secure_message LIMIT 1') msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_message( msg_id, self.user_internal) self.assertTrue('modified_date' not in response) self.assertTrue(response['sent_date'] != "N/A")
def test_respondent_can_only_see_their_messages(self): """tests that a respondent can only see their messages i.e. they should not see any messages sent to another respondent""" first_respondent_thread_id = self.create_thread( no_of_messages=1, external_actor=self.default_external_actor, internal_actor=self.default_internal_actor) second_respondent_thread_id = self.create_thread( no_of_messages=1, external_actor=self.second_external_actor, internal_actor=self.default_internal_actor) with self.app.app_context(): with current_app.test_request_context(): args = get_args() first_respondent_thread_list = Retriever.retrieve_thread_list( self.user_respondent, args) self.assertEqual(first_respondent_thread_list.total, 1) second_respondent_thread_list = Retriever.retrieve_thread_list( self.second_user_respondent, args) self.assertEqual(second_respondent_thread_list.total, 1) internal_thread_list = Retriever.retrieve_thread_list( self.user_internal, args) self.assertEqual(internal_thread_list.total, 2) # first respondent can retrieve the message they sent first_respondent_thread = Retriever.retrieve_thread( first_respondent_thread_id, self.user_respondent) self.assertIsNotNone(first_respondent_thread) # second respondent can retrieve the message they sent second_respondent_thread = Retriever.retrieve_thread( second_respondent_thread_id, self.second_user_respondent) self.assertIsNotNone(second_respondent_thread) # first respondent shouldn't be able to retrieve second respondent's message with self.assertRaises(Forbidden): Retriever.retrieve_thread(first_respondent_thread_id, self.second_user_respondent) # second respondent shouldn't be able to retrieve first respondent's message with self.assertRaises(Forbidden): Retriever.retrieve_thread(second_respondent_thread_id, self.user_respondent)
def get(thread_id): """Get messages by thread id""" logger.info("Getting messages from thread", thread_id=thread_id, user_uuid=g.user.user_uuid) conversation = Retriever.retrieve_thread(thread_id, g.user) conversation_metadata = Retriever.retrieve_conversation_metadata( thread_id) logger.info("Successfully retrieved messages from thread", thread_id=thread_id, user_uuid=g.user.user_uuid) messages = [] for message in conversation.all(): msg = message.serialize(g.user, body_summary=False) messages.append(msg) if conversation_metadata and conversation_metadata.is_closed: return jsonify({ "messages": add_users_and_business_details(messages), "is_closed": conversation_metadata.is_closed, "closed_by": conversation_metadata.closed_by, "closed_by_uuid": conversation_metadata.closed_by_uuid, "closed_at": conversation_metadata.closed_at.isoformat() }) return jsonify({ "messages": add_users_and_business_details(messages), "is_closed": False })
def test_correct_to_and_from_returned_internal_user(self): """retrieves message using id and checks the to and from urns are correct""" self.create_thread(internal_actor=self.user_internal.user_uuid) with self.engine.connect() as con: query = con.execute( 'SELECT msg_id FROM securemessage.secure_message LIMIT 1') msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_message( msg_id, self.user_respondent) self.assertEqual(response['msg_to'][0], self.user_internal.user_uuid) self.assertEqual(response['msg_from'], self.user_respondent.user_uuid)
def test_read_date_returned_for_message(self): """retrieves message using id and checks the read date returned""" self.create_thread(no_of_messages=2) with self.engine.connect() as con: query_sql = "SELECT securemessage.secure_message.msg_id FROM securemessage.secure_message " \ "JOIN securemessage.events ON securemessage.secure_message.msg_id = securemessage.events.msg_id " \ "WHERE securemessage.events.event = '" + EventsApi.READ.value + "' LIMIT 1" query = con.execute(query_sql) msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_message( msg_id, self.user_internal) self.assertTrue('modified_date' not in response) self.assertTrue(response['read_date'] != "N/A")
def test_thread_returned_in_desc_order(self): """check thread returned in correct order""" thread_id = self.create_thread(no_of_messages=6) with self.app.app_context(): with current_app.test_request_context(): response = Retriever.retrieve_thread(thread_id, self.user_respondent) self.assertEqual(len(response.all()), 6) sent = [ str(message.events[0].date_time) for message in response.all() ] desc_date = sorted(sent, reverse=True) self.assertEqual(len(sent), 6) self.assertListEqual(desc_date, sent)
def get(): """Get thread list""" logger.info("Getting list of threads for user", user_uuid=g.user.user_uuid) message_args = get_options(request.args) ThreadList._validate_request(message_args, g.user) result = Retriever.retrieve_thread_list(g.user, message_args) logger.info("Successfully retrieved threads for user", user_uuid=g.user.user_uuid) messages, links = process_paginated_list(result, request.host_url, g.user, message_args, THREAD_LIST_ENDPOINT) if messages: messages = add_users_and_business_details(messages) return jsonify({"messages": messages, "_links": links})
def test_two_unread_labels_are_added_to_message(self): """testing duplicate message labels are not added to the database""" self.populate_database(1) with self.engine.connect() as con: query = con.execute( 'SELECT msg_id FROM securemessage.secure_message LIMIT 1') msg_id = query.first()[0] with self.app.app_context(): with current_app.test_request_context(): message = Retriever.retrieve_message(msg_id, self.user_internal) Modifier.add_unread(message, self.user_internal) Modifier.add_unread(message, self.user_internal) with self.engine.connect() as con: query = f"SELECT count(label) FROM securemessage.status WHERE msg_id = '{msg_id}' AND label = 'UNREAD'" query_x = con.execute(query) unread_label_total = [] for row in query_x: unread_label_total.append(row[0]) self.assertTrue(unread_label_total[0] == 1)
def _validate_post_data(post_data): if 'msg_id' in post_data: raise BadRequest(description="Message can not include msg_id") message = MessageSchema().load(post_data) if post_data.get('thread_id'): if post_data['from_internal'] and not party.get_users_details(post_data['msg_to']): # If an internal person is sending a message to a respondent, we need to check that they exist. # If they don't exist (because they've been deleted) then we raise a NotFound exception as the # respondent can't be found in the system. raise NotFound(description="Respondent not found") conversation_metadata = Retriever.retrieve_conversation_metadata(post_data.get('thread_id')) # Ideally, we'd return a 404 if there isn't a record in the conversation table. But until we # ensure there is a record in here for every thread_id in the secure_message table, we just have to # assume that it's fine if it's empty. if conversation_metadata and conversation_metadata.is_closed: raise BadRequest(description="Cannot reply to a closed conversation") return message
def put(message_id): """Update message by status""" request_data = request.get_json() action, label = MessageModifyById._validate_request(request_data) message = Retriever.retrieve_message(message_id, g.user) if label == Labels.UNREAD.value: resp = MessageModifyById._try_modify_unread(action, message, g.user) else: resp = MessageModifyById._modify_label(action, message, g.user, label) if resp: res = jsonify({'status': 'ok'}) res.status_code = 200 else: res = jsonify({'status': 'error'}) res.status_code = 400 logger.error('Error updating message', msg_id=message_id, status_code=res.status_code) return res
def test_thread_list_returned_in_descending_order_respondent(self): """retrieves threads from database in desc sent_date order for respondent""" for _ in range(5): self.create_thread(no_of_messages=2) with self.app.app_context(): with current_app.test_request_context(): args = get_args(limit=MESSAGE_QUERY_LIMIT) response = Retriever.retrieve_thread_list( self.user_respondent, args) date = [] for message in response.items: serialized_msg = message.serialize(self.user_respondent) if 'sent_date' in serialized_msg: date.append(serialized_msg['sent_date']) elif 'modified_date' in serialized_msg: date.append(serialized_msg['modified_date']) desc_date = sorted(date, reverse=True) self.assertEqual(len(date), 5) self.assertListEqual(desc_date, date)
def patch(thread_id): """Modify conversation metadata""" bound_logger = logger.bind(thread_id=thread_id, user_uuid=g.user.user_uuid) bound_logger.info("Validating request") if not g.user.is_internal: bound_logger.info("Thread modification is forbidden") abort(403) if request.headers.get('Content-Type', '').lower() != 'application/json': bound_logger.info( 'Request must set accept content type "application/json" in header.' ) raise BadRequest( description= 'Request must set accept content type "application/json" in header.' ) bound_logger.info("Retrieving metadata for thread") request_data = request.get_json() metadata = Retriever.retrieve_conversation_metadata(thread_id) if metadata is None: abort(404) ThreadById._validate_request(request_data, metadata) bound_logger.info("Attempting to modify metadata for thread") if request_data.get('is_closed'): bound_logger.info("About to close conversation") Modifier.close_conversation(metadata, g.user) else: bound_logger.info("About to re-open conversation") Modifier.open_conversation(metadata, g.user) bound_logger.info("Thread metadata update successful") bound_logger.unbind('thread_id', 'user_uuid') return '', 204
def get(): return Retriever.check_db_connection()