def test_role_counter_required_for_new_task(self):
        task = TaskFactory(status=Task.Status.COMPLETE)
        with self.assertRaises(TaskAssignmentError):
            role_counter_required_for_new_task(task)

        project = self.projects['assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEqual(project.tasks.count(), 1)
        # Assign initial task to worker 0
        task = project.tasks.first()
        counter = role_counter_required_for_new_task(task)
        self.assertEqual(counter, 0)

        initial_task = assign_task(self.workers[0].id, task.id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=True):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])

            counter = role_counter_required_for_new_task(initial_task)
            self.assertEqual(counter, 1)

            initial_task = assign_task(self.workers[1].id, task.id)
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[1])
            counter = role_counter_required_for_new_task(initial_task)
            self.assertEqual(counter, 2)
Example #2
0
    def test_staff_command(self, mock_slack, mock_mail):
        task = TaskFactory(status=Task.Status.AWAITING_PROCESSING)
        data = get_mock_slack_data(text='staff {}'.format(task.id),
                                   user_id=self.worker.slack_user_id)
        response = self.request_client.post(self.url, data)
        self.assertEqual(
            load_encoded_json(response.content)['attachments'][0]['text'],
            self.staffbot.staffing_success.format(task.id))

        task = TaskFactory(status=Task.Status.PENDING_REVIEW)
        data = get_mock_slack_data(text='staff {}'.format(task.id),
                                   user_id=self.worker.slack_user_id)

        response = self.request_client.post(self.url, data)
        self.assertEqual(
            load_encoded_json(response.content)['attachments'][0]['text'],
            self.staffbot.staffing_success.format(task.id))
    def test_assign_task(self):
        entry_task = TaskFactory(project=self.projects['base_test_project'],
                                 status=Task.Status.AWAITING_PROCESSING,
                                 step=self.test_step)

        # Assign entry-level task to entry-level worker
        entry_task = assign_task(self.workers[0].id, entry_task.id)
        self.assertTrue(is_worker_assigned_to_task(self.workers[0],
                                                   entry_task))
        self.assertEquals(entry_task.status, Task.Status.PROCESSING)
        self.assertEquals(entry_task.assignments.count(), 1)

        # Attempt to assign task which isn't awaiting a new assignment
        invalid = (Task.Status.PROCESSING, Task.Status.ABORTED,
                   Task.Status.REVIEWING, Task.Status.COMPLETE,
                   Task.Status.POST_REVIEW_PROCESSING)
        for status in invalid:
            invalid_status_task = Task.objects.create(
                project=self.projects['base_test_project'],
                status=status,
                step=self.test_step)

            with self.assertRaises(TaskAssignmentError):
                invalid_status_task = assign_task(self.workers[0].id,
                                                  invalid_status_task.id)

        # Attempt to assign review task to worker already in review hierarchy
        review_task = Task.objects.create(
            project=self.projects['base_test_project'],
            status=Task.Status.PENDING_REVIEW,
            step=self.test_step)
        test_data = {'test_assign': True}
        TaskAssignmentFactory(worker=self.workers[1],
                              task=review_task,
                              status=TaskAssignment.Status.SUBMITTED,
                              in_progress_task_data=test_data,
                              snapshots=empty_snapshots())

        with self.assertRaises(TaskAssignmentError):
            assign_task(self.workers[1].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Attempt to assign review task to worker not certified for task
        with self.assertRaises(WorkerCertificationError):
            assign_task(self.workers[2].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Assign review task to review worker
        self.assertEquals(review_task.assignments.count(), 1)
        review_task = assign_task(self.workers[3].id, review_task.id)
        self.assertEquals(review_task.assignments.count(), 2)
        self.assertEqual(
            current_assignment(review_task).worker, self.workers[3])
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)
        self.assertEquals(review_task.status, Task.Status.REVIEWING)
Example #4
0
 def _task_factory(status, path):
     description_no_kwargs = {'path': path}
     return TaskFactory(
         status=status,
         step=StepFactory(
             slug='stepslug',
             description='the step',
             detailed_description_function=description_no_kwargs),
         project__workflow_version__workflow__description=(
             'the workflow'),
         project__short_description='the coolest project')
Example #5
0
    def test_restaff_command_errors(self):
        """
        Test that the staffing logic errors are raised during
        staff command.
        """
        bot = StaffBot()
        command = 'restaff 999999999999 unknown'
        data = get_mock_slack_data(
            text=command,
            user_id=self.worker.slack_user_id)

        response = bot.dispatch(data)
        self.assertEqual(response.get('text'),
                         command)

        self.assertEqual(response['attachments'][0]['text'],
                         bot.worker_does_not_exist.format('unknown'))

        worker = WorkerFactory(user__username='******')
        data['text'] = 'restaff 999999999999 username'
        response = bot.dispatch(data)
        self.assertEqual(response['attachments'][0]['text'],
                         bot.task_does_not_exist_error.format('999999999999'))

        # making sure it works with slack username as well.
        worker.slack_username = '******'
        worker.save()
        data['text'] = 'restaff 999999999999 slackusername'
        response = bot.dispatch(data)
        self.assertEqual(response['attachments'][0]['text'],
                         bot.task_does_not_exist_error.format('999999999999'))

        data['text'] = 'restaff'
        response = bot.dispatch(data)
        self.assertTrue(bot.default_error_text in response.get('text'))

        task = TaskFactory(status=Task.Status.COMPLETE)
        command = 'restaff {} {}'.format(task.id, worker.user.username)

        data['text'] = command
        response = bot.dispatch(data)
        self.assertEquals(response['attachments'][0]['text'],
                          (bot.task_assignment_does_not_exist_error
                           .format(worker.user.username, task.id)))
Example #6
0
    def test_worker_task_recent_todo_qas(self):
        todo_task_0 = TodoFactory(task=self.task_0)
        todo_task_1 = TodoFactory(task=self.task_1)

        # Zero TodoQAs
        self._verify_worker_task_recent_todo_qas(
            self.task_0, None, True)

        todo_qa_task_0 = TodoQAFactory(todo=todo_task_0, approved=False)

        # Most recent TodoQA is todo_qa_task_0
        self._verify_worker_task_recent_todo_qas(
            self.task_0, todo_qa_task_0, True)

        self._verify_worker_task_recent_todo_qas(
            self.task_1, todo_qa_task_0, True)

        todo_qa_task_1 = TodoQAFactory(todo=todo_task_1, approved=False)

        # If available use the todo qa for the corresponding task.
        self._verify_worker_task_recent_todo_qas(
            self.task_0, todo_qa_task_0, True)

        self._verify_worker_task_recent_todo_qas(
            self.task_1, todo_qa_task_1, True)

        todo_qa_task_0.delete()

        # Most recent TodoQA is todo_qa_task_1
        self._verify_worker_task_recent_todo_qas(
            self.task_0, todo_qa_task_1, True)

        self._verify_worker_task_recent_todo_qas(
            self.task_1, todo_qa_task_1, True)

        # Can't make requests for projects in which you're uninvolved.
        bad_task = TaskFactory()
        todo_bad_task = TodoFactory(task=bad_task)
        todo_qa_bad_task = TodoQAFactory(todo=todo_bad_task, approved=False)
        self._verify_worker_task_recent_todo_qas(
            bad_task, todo_qa_bad_task, False)
Example #7
0
    def test_staff_command_errors(self):
        """
        Test that the staffing logic errors are raised during
        staff command.
        """
        bot = StaffBot()
        data = get_mock_slack_data(text='staff 999999999999',
                                   user_id=self.worker.slack_user_id)

        response = bot.dispatch(data)
        self.assertEqual(response['attachments'][0]['text'],
                         bot.task_does_not_exist_error.format('999999999999'))

        data['text'] = 'staff'
        response = bot.dispatch(data)
        self.assertTrue(bot.default_error_text in response.get('text'))

        task = TaskFactory(status=Task.Status.COMPLETE)
        data['text'] = 'staff {}'.format(task.id)
        response = bot.dispatch(data)
        self.assertEqual(
            response['attachments'][0]['text'],
            bot.task_assignment_error.format(
                task.id, 'Status incompatible with new assignment'))
Example #8
0
    def test_get_detailed_description(self):
        """
        Verify that the detailed description text is valid
        """
        # description functions are optional
        task = TaskFactory()
        self.assertEqual(task.get_detailed_description(), '')

        no_kwargs = {
            'path': ('orchestra.tests.helpers.'
                     'fixtures.get_detailed_description')
        }
        task_no_kwargs = TaskFactory(
            step=StepFactory(slug='stepslug',
                             detailed_description_function=no_kwargs))
        self.assertEqual(task_no_kwargs.get_detailed_description(),
                         'No text given stepslug')

        with_kwargs = {
            'path': ('orchestra.tests.helpers.'
                     'fixtures.get_detailed_description'),
            'kwargs': {
                'text': 'task 2 text',
            }
        }
        task_with_kwargs = TaskFactory(
            step=StepFactory(slug='stepslug',
                             detailed_description_function=with_kwargs))
        self.assertEqual(task_with_kwargs.get_detailed_description(),
                         'task 2 text stepslug')

        extra_kwargs = {'text': 'extra text'}
        self.assertEqual(
            task_with_kwargs.get_detailed_description(
                extra_kwargs=extra_kwargs),
            'extra text stepslug'
        )
Example #9
0
    def test_notify_status_change(self):
        project = self.projects['empty_project']
        internal_name = settings.SLACK_INTERNAL_NOTIFICATION_CHANNEL.strip('#')
        internal_groups = [
            group for group in self.slack.groups.list().body['groups']
            if group['name'] == internal_name
        ]
        internal_group_id = internal_groups[0]['id']
        internal_slack_messages = self.slack.get_messages(internal_group_id)
        experts_slack_messages = self.slack.get_messages(
            project.slack_group_id)

        def _validate_slack_messages(message_stub):
            """
            Check that correct slack message was sent if API key present.
            """
            self.assertIn(message_stub, internal_slack_messages.pop())
            self.assertIn(message_stub, experts_slack_messages.pop())

        task = TaskFactory(project=project,
                           step=self.test_step,
                           status=Task.Status.AWAITING_PROCESSING)

        # Entry-level worker picks up task
        self.assertEqual(task.status, Task.Status.AWAITING_PROCESSING)
        task = assign_task(self.workers[0].id, task.id)
        self.assertTrue(task.is_worker_assigned(self.workers[0]))

        # Notification should be sent to entry-level worker
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[0].user.email)
        self.assertEqual(notification['subject'],
                         "You've been assigned to a new task!")

        _validate_slack_messages('Task has been picked up by a worker.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=True):
            # Entry-level worker submits task
            task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                               self.workers[0])

        self.assertEqual(task.status, Task.Status.PENDING_REVIEW)
        # Notification should be sent to entry-level worker
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[0].user.email)
        self.assertEqual(notification['subject'], 'Your task is under review!')

        _validate_slack_messages('Task is awaiting review.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # Reviewer picks up task
        task = assign_task(self.workers[1].id, task.id)
        self.assertEqual(task.status, Task.Status.REVIEWING)
        # No notification should be sent
        self.assertEqual(len(self.mail.inbox), 0)

        _validate_slack_messages('Task is under review.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # Reviewer rejects task
        task = submit_task(task.id, {}, Iteration.Status.PROVIDED_REVIEW,
                           self.workers[1])
        self.assertEqual(task.status, Task.Status.POST_REVIEW_PROCESSING)
        # Notification should be sent to original worker
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[0].user.email)
        self.assertEqual(notification['subject'],
                         'Your task has been returned')

        _validate_slack_messages('Task was returned by reviewer.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # Entry-level worker resubmits task
        task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                           self.workers[0])
        self.assertEqual(task.status, Task.Status.REVIEWING)
        # Notification should be sent to reviewer
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[1].user.email)
        self.assertEqual(notification['subject'],
                         'A task is ready for re-review!')

        _validate_slack_messages('Task is under review.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # First reviewer accepts task
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=True):
            task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                               self.workers[1])
        self.assertEqual(task.status, Task.Status.PENDING_REVIEW)
        # Notification should be sent to first reviewer
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[1].user.email)
        self.assertEqual(notification['subject'], 'Your task is under review!')

        _validate_slack_messages('Task is awaiting review.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # Second reviewer picks up task
        task = assign_task(self.workers[3].id, task.id)
        self.assertEqual(task.status, Task.Status.REVIEWING)
        # No notification should be sent
        self.assertEqual(len(self.mail.inbox), 0)

        _validate_slack_messages('Task is under review.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # Second reviewer rejects task
        task = submit_task(task.id, {}, Iteration.Status.PROVIDED_REVIEW,
                           self.workers[3])
        self.assertEqual(task.status, Task.Status.POST_REVIEW_PROCESSING)
        # Notification should be sent to first reviewer
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[1].user.email)
        self.assertEqual(notification['subject'],
                         'Your task has been returned')

        _validate_slack_messages('Task was returned by reviewer.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # First reviewer resubmits task
        task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                           self.workers[1])
        self.assertEqual(task.status, Task.Status.REVIEWING)
        # Notification should be sent to second reviewer
        self.assertEqual(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEqual(notification['recipient'], self.workers[3].user.email)
        self.assertEqual(notification['subject'],
                         'A task is ready for re-review!')

        _validate_slack_messages('Task is under review.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # Second reviewer accepts task; task is complete
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                               self.workers[3])
        self.assertEqual(task.status, Task.Status.COMPLETE)

        # Notification should be sent to all workers on task
        self.assertEqual(len(self.mail.inbox), 3)
        recipients = {mail['recipient'] for mail in self.mail.inbox}
        subjects = {mail['subject'] for mail in self.mail.inbox}
        self.assertEqual(recipients,
                         {self.workers[uid].user.email
                          for uid in (0, 1, 3)})
        self.assertEqual(subjects, {'Task complete!'})
        self.mail.clear()

        _validate_slack_messages('Task has been completed.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)

        # End project
        end_project(task.project.id)
        task = Task.objects.get(id=task.id)
        self.assertEqual(task.status, Task.Status.ABORTED)

        # Notification should be sent to all workers on task
        self.assertEqual(len(self.mail.inbox), 3)
        recipients = {mail['recipient'] for mail in self.mail.inbox}
        subjects = {mail['subject'] for mail in self.mail.inbox}
        self.assertEqual(recipients,
                         {self.workers[uid].user.email
                          for uid in (0, 1, 3)})
        self.assertEqual(subjects,
                         {'A task you were working on has been ended'})
        self.mail.clear()

        for task in project.tasks.all():
            _validate_slack_messages('Task has been aborted.')
        self.assertEqual(len(internal_slack_messages), 0)
        self.assertEqual(len(experts_slack_messages), 0)
Example #10
0
    def test_assign_task(self):
        entry_task = TaskFactory(project=self.projects['base_test_project'],
                                 status=Task.Status.AWAITING_PROCESSING,
                                 step=self.test_step)

        # No iterations should be present for task
        self.assertEqual(
            Iteration.objects.filter(assignment__task=entry_task).count(), 0)

        # Assign entry-level task to entry-level worker
        entry_task = assign_task(self.workers[0].id, entry_task.id)
        self.assertTrue(entry_task.is_worker_assigned(self.workers[0]))
        self.assertEqual(entry_task.status, Task.Status.PROCESSING)

        self.assertEqual(entry_task.assignments.count(), 1)
        entry_assignment = entry_task.assignments.first()

        # A single iteration was created for the assignment
        self.assertEqual(entry_assignment.iterations.count(), 1)
        self.assertEqual(
            Iteration.objects.filter(assignment__task=entry_task).count(), 1)
        self.assertEqual(entry_assignment.iterations.first().start_datetime,
                         entry_assignment.start_datetime)

        # Attempt to assign task which isn't awaiting a new assignment
        invalid = (Task.Status.PROCESSING, Task.Status.ABORTED,
                   Task.Status.REVIEWING, Task.Status.COMPLETE,
                   Task.Status.POST_REVIEW_PROCESSING)
        for status in invalid:
            invalid_status_task = Task.objects.create(
                project=self.projects['base_test_project'],
                status=status,
                step=self.test_step)

            with self.assertRaises(TaskAssignmentError):
                invalid_status_task = assign_task(self.workers[0].id,
                                                  invalid_status_task.id)

        # Attempt to assign review task to worker already in review hierarchy
        review_task = Task.objects.create(
            project=self.projects['base_test_project'],
            status=Task.Status.PENDING_REVIEW,
            step=self.test_step)
        test_data = {'test_assign': True}
        TaskAssignmentFactory(worker=self.workers[1],
                              task=review_task,
                              status=TaskAssignment.Status.SUBMITTED,
                              in_progress_task_data=test_data)

        with self.assertRaises(TaskAssignmentError):
            assign_task(self.workers[1].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Attempt to assign review task to worker not certified for task
        with self.assertRaises(WorkerCertificationError):
            assign_task(self.workers[2].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Assign review task to review worker
        self.assertEquals(review_task.assignments.count(), 1)
        review_task = assign_task(self.workers[3].id, review_task.id)
        self.assertEquals(review_task.assignments.count(), 2)

        reviewer_assignment = current_assignment(review_task)
        self.assertEqual(reviewer_assignment.worker, self.workers[3])
        self.assertEqual(reviewer_assignment.in_progress_task_data, test_data)
        self.assertEquals(reviewer_assignment.iterations.count(), 1)
        self.assertEqual(reviewer_assignment.iterations.first().start_datetime,
                         reviewer_assignment.start_datetime)

        self.assertEquals(review_task.status, Task.Status.REVIEWING)
Example #11
0
    def test_assign_task(self):
        entry_task = TaskFactory(
            project=self.projects['base_test_project'],
            status=Task.Status.AWAITING_PROCESSING,
            step=self.test_step)

        # No iterations should be present for task
        self.assertEqual(
            Iteration.objects.filter(assignment__task=entry_task).count(), 0)

        # Assign entry-level task to entry-level worker
        entry_task = assign_task(self.workers[0].id, entry_task.id)
        self.assertTrue(entry_task.is_worker_assigned(self.workers[0]))
        self.assertEqual(entry_task.status, Task.Status.PROCESSING)

        self.assertEqual(entry_task.assignments.count(), 1)
        entry_assignment = entry_task.assignments.first()

        # A single iteration was created for the assignment
        self.assertEqual(entry_assignment.iterations.count(), 1)
        self.assertEqual(
            Iteration.objects.filter(assignment__task=entry_task).count(), 1)
        self.assertEqual(
            entry_assignment.iterations.first().start_datetime,
            entry_assignment.start_datetime)

        # Attempt to assign task which isn't awaiting a new assignment
        invalid = (Task.Status.PROCESSING, Task.Status.ABORTED,
                   Task.Status.REVIEWING, Task.Status.COMPLETE,
                   Task.Status.POST_REVIEW_PROCESSING)
        for status in invalid:
            invalid_status_task = Task.objects.create(
                project=self.projects['base_test_project'],
                status=status,
                step=self.test_step)

            with self.assertRaises(TaskAssignmentError):
                invalid_status_task = assign_task(
                    self.workers[0].id, invalid_status_task.id)

        # Attempt to assign review task to worker already in review hierarchy
        review_task = Task.objects.create(
            project=self.projects['base_test_project'],
            status=Task.Status.PENDING_REVIEW,
            step=self.test_step)
        test_data = {'test_assign': True}
        TaskAssignmentFactory(
            worker=self.workers[1],
            task=review_task,
            status=TaskAssignment.Status.SUBMITTED,
            in_progress_task_data=test_data)

        with self.assertRaises(TaskAssignmentError):
            assign_task(self.workers[1].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Attempt to assign review task to worker not certified for task
        with self.assertRaises(WorkerCertificationError):
            assign_task(self.workers[2].id, review_task.id)
        self.assertEqual(
            current_assignment(review_task).in_progress_task_data, test_data)

        # Assign review task to review worker
        self.assertEquals(review_task.assignments.count(), 1)
        review_task = assign_task(self.workers[3].id, review_task.id)
        self.assertEquals(review_task.assignments.count(), 2)

        reviewer_assignment = current_assignment(review_task)
        self.assertEqual(
            reviewer_assignment.worker, self.workers[3])
        self.assertEqual(
            reviewer_assignment.in_progress_task_data, test_data)
        self.assertEquals(
            reviewer_assignment.iterations.count(), 1)
        self.assertEqual(
            reviewer_assignment.iterations.first().start_datetime,
            reviewer_assignment.start_datetime)

        self.assertEquals(
            review_task.status, Task.Status.REVIEWING)
Example #12
0
 def test_todos_list_create_permissions(self):
     # Can't make requests for projects in which you're uninvolved.
     task = TaskFactory()
     self._verify_todos_list(task.project.id, [], False)
     self._verify_todo_creation(task, False)
    def test_notify_status_change(self):
        project = self.projects['empty_project']
        internal_name = settings.SLACK_INTERNAL_NOTIFICATION_CHANNEL.strip('#')
        internal_groups = [
            group for group in self.slack.groups.list().body['groups']
            if group['name'] == internal_name]
        internal_group_id = internal_groups[0]['id']
        internal_slack_messages = self.slack.get_messages(internal_group_id)
        experts_slack_messages = self.slack.get_messages(
            project.slack_group_id)

        def _validate_slack_messages(message_stub):
            """
            Check that correct slack message was sent if API key present.
            """
            self.assertIn(message_stub, internal_slack_messages.pop())
            self.assertIn(message_stub, experts_slack_messages.pop())

        task = TaskFactory(project=project,
                           step=self.test_step,
                           status=Task.Status.AWAITING_PROCESSING)

        # Entry-level worker picks up task
        self.assertEquals(task.status, Task.Status.AWAITING_PROCESSING)
        task = assign_task(self.workers[0].id, task.id)
        self.assertTrue(task.is_worker_assigned(self.workers[0]))

        # Notification should be sent to entry-level worker
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[0].user.email)
        self.assertEquals(notification['subject'],
                          "You've been assigned to a new task!")

        _validate_slack_messages('Task has been picked up by a worker.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=True):
            # Entry-level worker submits task
            task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                               self.workers[0])

        self.assertEquals(task.status, Task.Status.PENDING_REVIEW)
        # Notification should be sent to entry-level worker
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[0].user.email)
        self.assertEquals(notification['subject'],
                          'Your task is under review!')

        _validate_slack_messages('Task is awaiting review.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # Reviewer picks up task
        task = assign_task(self.workers[1].id, task.id)
        self.assertEquals(task.status, Task.Status.REVIEWING)
        # No notification should be sent
        self.assertEquals(len(self.mail.inbox), 0)

        _validate_slack_messages('Task is under review.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # Reviewer rejects task
        task = submit_task(task.id, {}, Iteration.Status.PROVIDED_REVIEW,
                           self.workers[1])
        self.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING)
        # Notification should be sent to original worker
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[0].user.email)
        self.assertEquals(notification['subject'],
                          'Your task has been returned')

        _validate_slack_messages('Task was returned by reviewer.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # Entry-level worker resubmits task
        task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                           self.workers[0])
        self.assertEquals(task.status, Task.Status.REVIEWING)
        # Notification should be sent to reviewer
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[1].user.email)
        self.assertEquals(notification['subject'],
                          'A task is ready for re-review!')

        _validate_slack_messages('Task is under review.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # First reviewer accepts task
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=True):
            task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                               self.workers[1])
        self.assertEquals(task.status, Task.Status.PENDING_REVIEW)
        # Notification should be sent to first reviewer
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[1].user.email)
        self.assertEquals(notification['subject'],
                          'Your task is under review!')

        _validate_slack_messages('Task is awaiting review.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # Second reviewer picks up task
        task = assign_task(self.workers[3].id, task.id)
        self.assertEquals(task.status, Task.Status.REVIEWING)
        # No notification should be sent
        self.assertEquals(len(self.mail.inbox), 0)

        _validate_slack_messages('Task is under review.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # Second reviewer rejects task
        task = submit_task(task.id, {}, Iteration.Status.PROVIDED_REVIEW,
                           self.workers[3])
        self.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING)
        # Notification should be sent to first reviewer
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[1].user.email)
        self.assertEquals(notification['subject'],
                          'Your task has been returned')

        _validate_slack_messages('Task was returned by reviewer.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # First reviewer resubmits task
        task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                           self.workers[1])
        self.assertEquals(task.status, Task.Status.REVIEWING)
        # Notification should be sent to second reviewer
        self.assertEquals(len(self.mail.inbox), 1)
        notification = self.mail.inbox.pop()
        self.assertEquals(notification['recipient'],
                          self.workers[3].user.email)
        self.assertEquals(notification['subject'],
                          'A task is ready for re-review!')

        _validate_slack_messages('Task is under review.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # Second reviewer accepts task; task is complete
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW,
                               self.workers[3])
        self.assertEquals(task.status, Task.Status.COMPLETE)

        # Notification should be sent to all workers on task
        self.assertEquals(len(self.mail.inbox), 3)
        recipients = {mail['recipient'] for mail in self.mail.inbox}
        subjects = {mail['subject'] for mail in self.mail.inbox}
        self.assertEquals(recipients,
                          {self.workers[uid].user.email for uid in (0, 1, 3)})
        self.assertEquals(subjects, {'Task complete!'})
        self.mail.clear()

        _validate_slack_messages('Task has been completed.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)

        # End project
        end_project(task.project.id)
        task = Task.objects.get(id=task.id)
        self.assertEquals(task.status, Task.Status.ABORTED)

        # Notification should be sent to all workers on task
        self.assertEquals(len(self.mail.inbox), 3)
        recipients = {mail['recipient'] for mail in self.mail.inbox}
        subjects = {mail['subject'] for mail in self.mail.inbox}
        self.assertEquals(recipients,
                          {self.workers[uid].user.email for uid in (0, 1, 3)})
        self.assertEquals(subjects,
                          {'A task you were working on has been ended'})
        self.mail.clear()

        for task in project.tasks.all():
            _validate_slack_messages('Task has been aborted.')
        self.assertEquals(len(internal_slack_messages), 0)
        self.assertEquals(len(experts_slack_messages), 0)