Пример #1
0
def create_subsequent_tasks_api(request):
    project_id = load_encoded_json(request.body)['project_id']
    try:
        project = Project.objects.get(id=project_id)
    except Project.DoesNotExist:
        raise BadRequest('Project not found for the given id.')
    create_subsequent_tasks(project)
Пример #2
0
def create_project_with_tasks(workflow_slug,
                              description,
                              priority,
                              task_class,
                              project_data,
                              review_document_url,
                              workflow_version_slug=None):

    # Allow backwards compatibility with calls that pass in a version slug in
    # the 'workflow_slug' variable.
    # TODO(dhaas): be less backward-compatible?
    if workflow_version_slug is None:
        try:
            workflow_version = WorkflowVersion.objects.get(slug=workflow_slug)
        except WorkflowVersion.MultipleObjectsReturned:
            raise ValueError('No workflow slug passed, and version slug {} is '
                             'not unique.'.format(workflow_slug))
    else:
        workflow_version = WorkflowVersion.objects.get(
            slug=workflow_version_slug,
            workflow__slug=workflow_slug)

    project = Project.objects.create(workflow_version=workflow_version,
                                     short_description=description,
                                     priority=priority,
                                     project_data=project_data,
                                     task_class=task_class,
                                     review_document_url=review_document_url)

    create_project_slack_group(project)
    create_project_google_folder(project)

    create_subsequent_tasks(project)
    return project
Пример #3
0
    def test_preassign_workers(self, mock_mail, mock_slack):
        request_cause = StaffBotRequest.RequestCause.TASK_POLICY.value
        staffing_request_count = StaffingRequestInquiry.objects.filter(
            request__request_cause=request_cause).count()
        project = self.projects['staffbot_assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        address_staffing_requests()
        self.assertEqual(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])

        # Mock mail should be called if we autostaff
        self.assertTrue(mock_mail.called)
        self.assertTrue(mock_slack.called)
        # Assert we created new StaffingRequestInquirys because of autostaff
        new_staffing_request_count = StaffingRequestInquiry.objects.filter(
            request__request_cause=request_cause).count()
        self.assertTrue(staffing_request_count < new_staffing_request_count)
        self.assertEqual(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 0 not certified for related tasks, so should not have been
        # auto-assigned
        self.assertEqual(related_task.assignments.count(), 0)
        self.assertEqual(related_task.status, Task.Status.AWAITING_PROCESSING)
Пример #4
0
    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.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        task = project.tasks.first()
        counter = role_counter_required_for_new_task(task)
        self.assertEquals(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.assertEquals(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.assertEquals(counter, 2)
Пример #5
0
    def test_malformed_creation_policy(self):
        project = self.projects['creation_policy']
        workflow_version = project.workflow_version
        first_step = self.workflow_steps[
            workflow_version.slug]['creation_policy_step_0']

        # Create an invalid machine step with an assignment policy
        malformed_step = StepFactory(
            workflow_version=workflow_version,
            slug='machine_step',
            is_human=False,
            creation_policy={},
        )
        malformed_step.creation_depends_on.add(first_step)

        # Create first task in project
        create_subsequent_tasks(project)
        self.assertEqual(project.tasks.count(), 1)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        # Cannot have an invalid blob for the creation_policy
        with self.assertRaises(CreationPolicyError):
            create_subsequent_tasks(project)
Пример #6
0
    def setUp(self):
        super().setUp()
        setup_models(self)

        machine_workflow_version = (
            self.workflow_versions['machine_workflow_version'])
        self.step = machine_workflow_version.steps.get(slug='machine_step')
        self.assertIsNotNone(self.step)
        self.step.execution_function = {
            'path': 'orchestra.tests.helpers.workflow.machine_task_function',
        }
        self.step.save()
        patcher = patch(
            'orchestra.tests.helpers.workflow.machine_task_function',
            return_value={'data': ''})
        self.machine_function_mock = patcher.start()
        self.addCleanup(patcher.stop)

        self.project = ProjectFactory(
            workflow_version=machine_workflow_version)
        create_subsequent_tasks(self.project)
        self.task = self.project.tasks.first()
        self.assertIsNotNone(self.task)

        # Since create_subsequent_tasks will automatically run the machine task
        # we need to reset the task each time it's called
        self._reset_task()
Пример #7
0
    def test_malformed_creation_policy(self):
        project = self.projects['creation_policy']
        workflow_version = project.workflow_version
        first_step = self.workflow_steps[
            workflow_version.slug]['creation_policy_step_0']

        # Create an invalid machine step with an assignment policy
        malformed_step = StepFactory(
            workflow_version=workflow_version,
            slug='machine_step',
            is_human=False,
            creation_policy={},
        )
        malformed_step.creation_depends_on.add(first_step)

        # Create first task in project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        # Cannot have an invalid blob for the creation_policy
        with self.assertRaises(CreationPolicyError):
            create_subsequent_tasks(project)
Пример #8
0
def create_subsequent_tasks_api(request):
    project_id = load_encoded_json(request.body)['project_id']
    try:
        project = Project.objects.get(id=project_id)
    except Project.DoesNotExist:
        raise BadRequest('Project not found for the given id.')
    create_subsequent_tasks(project)
Пример #9
0
    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.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        task = project.tasks.first()
        counter = role_counter_required_for_new_task(task)
        self.assertEquals(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.assertEquals(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.assertEquals(counter, 2)
Пример #10
0
 def test_todolist_templates_to_apply(self):
     project = self.projects['assignment_policy']
     mock = MagicMock(return_value=True)
     with patch('orchestra.utils.task_lifecycle.add_todolist_template',
                new=mock):
         # Create first task in test project
         create_subsequent_tasks(project)
         assert mock.called_once
         assert mock.call_args[0][0] == 'project-checklist'
Пример #11
0
    def test_task_form_init(self):
        """
        Test task form initialization for new, human and machine tasks
        """
        # Create new task form
        # (Test form init with no task instance)
        TaskForm()

        project = Project.objects.get(workflow_slug='test_workflow_2')
        self.assertEquals(Task.objects.filter(project=project).count(),
                          0)
        create_subsequent_tasks(project)

        # Human task was created but not assigned
        # (Test form init with empty assignment history)
        self.assertEquals(Task.objects.filter(project=project).count(),
                          1)
        human_task = Task.objects.filter(project=project).first()
        form = TaskForm(instance=human_task)
        self.assertEquals(form.fields['currently_assigned_to'].initial,
                          None)

        # Human task assigned to entry_level worker
        # (Test form init with a single entry-level worker)
        human_task = assign_task(self.workers[0].id, human_task.id)
        form = TaskForm(instance=human_task)
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=True):
            human_task = submit_task(human_task.id, {},
                                     TaskAssignment.SnapshotType.SUBMIT,
                                     self.workers[0], 0)
        self.assertEquals(form.fields['currently_assigned_to'].initial,
                          self.workers[0].id)

        # Human task under review
        # (Test form init with both an entry-level worker and reviewer)
        human_task = assign_task(self.workers[1].id, human_task.id)
        form = TaskForm(instance=human_task)
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            human_task = submit_task(human_task.id, {},
                                     TaskAssignment.SnapshotType.ACCEPT,
                                     self.workers[1], 0)
        self.assertEquals(form.fields['currently_assigned_to'].initial,
                          self.workers[1].id)

        # Machine task was created
        # (Test form init with a machine task)
        self.assertEquals(Task.objects.filter(project=project).count(),
                          2)
        machine_task = (Task.objects.filter(project=project)
                                    .exclude(id=human_task.id).first())
        form = TaskForm(instance=machine_task)
        self.assertEquals(form.fields['currently_assigned_to'].initial,
                          None)
Пример #12
0
    def test_complete_all_tasks_slack_annoucement(self, mock_slack_archive):
        project = self.projects['single_human_step']
        create_subsequent_tasks(project)
        task = Task.objects.get(project=project)
        assign_task(self.workers[1].id, task.id)
        self.assertEqual(project.status, Project.Status.ACTIVE)
        self.assertFalse(mock_slack_archive.called)

        complete_and_skip_task(task.id)
        create_subsequent_tasks(project)
        project.refresh_from_db()
        self.assertEqual(project.status, Project.Status.COMPLETED)
        self.assertTrue(mock_slack_archive.called)
Пример #13
0
def create_project_with_tasks(workflow_slug, workflow_version_slug,
                              description, priority, task_class, project_data):

    workflow_version = WorkflowVersion.objects.get(
        slug=workflow_version_slug, workflow__slug=workflow_slug)

    project = Project.objects.create(workflow_version=workflow_version,
                                     short_description=description,
                                     priority=priority,
                                     project_data=project_data,
                                     task_class=task_class)

    create_project_google_folder(project)
    create_project_slack_group(project)
    create_subsequent_tasks(project)
    return project
Пример #14
0
    def test_preassign_workers(self):
        project = self.projects['assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       TaskAssignment.SnapshotType.SUBMIT,
                                       self.workers[0], 0)
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 0 not certified for related tasks, so should not have been
        # auto-assigned
        self.assertEquals(related_task.assignments.count(), 0)
        self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING)

        # Reset project
        project.tasks.all().delete()

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 4
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       TaskAssignment.SnapshotType.SUBMIT,
                                       self.workers[4], 0)
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 4 is certified for related task and should have been assigned
        self.assertEquals(related_task.assignments.count(), 1)
        self.assertEquals(related_task.status, Task.Status.PROCESSING)
        self.assertTrue(
            is_worker_assigned_to_task(self.workers[4], related_task))

        # Reset project
        project.tasks.all().delete()
Пример #15
0
    def test_preassign_workers(self):
        project = self.projects['assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       TaskAssignment.SnapshotType.SUBMIT,
                                       self.workers[0], 0)
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 0 not certified for related tasks, so should not have been
        # auto-assigned
        self.assertEquals(related_task.assignments.count(), 0)
        self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING)

        # Reset project
        project.tasks.all().delete()

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 4
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       TaskAssignment.SnapshotType.SUBMIT,
                                       self.workers[4], 0)
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 4 is certified for related task and should have been assigned
        self.assertEquals(related_task.assignments.count(), 1)
        self.assertEquals(related_task.status, Task.Status.PROCESSING)
        self.assertTrue(is_worker_assigned_to_task(self.workers[4],
                                                   related_task))

        # Reset project
        project.tasks.all().delete()
Пример #16
0
    def test_schedule_machine_tasks(self, mock_schedule):
        project = self.projects['test_human_and_machine']

        # Create first task in project
        create_subsequent_tasks(project)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        create_subsequent_tasks(project)
        self.assertEqual(mock_schedule.call_count, 1)
        self.assertEqual(mock_schedule.call_args[0][0], project)
        steps = list(project.workflow_version.steps.filter(is_human=False))
        self.assertEqual(mock_schedule.call_args[0][1], steps)
Пример #17
0
    def test_schedule_machine_tasks(self, mock_schedule):
        project = self.projects['test_human_and_machine']

        # Create first task in project
        create_subsequent_tasks(project)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        create_subsequent_tasks(project)
        self.assertEqual(mock_schedule.call_count, 1)
        self.assertEqual(mock_schedule.call_args[0][0], project)
        steps = list(project.workflow_version.steps.filter(is_human=False))
        self.assertEqual(mock_schedule.call_args[0][1], steps)
Пример #18
0
    def test_schedule_machine_tasks_failed(self, mock_schedule,
                                           mock_preassign):
        project = self.projects['test_human_and_machine']

        # Create first task in project
        create_subsequent_tasks(project)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        mock_preassign.side_effect = Exception
        with self.assertRaises(Exception):
            create_subsequent_tasks(project)
        mock_schedule.assert_not_called()
Пример #19
0
    def test_schedule_machine_tasks_failed(self, mock_schedule,
                                           mock_preassign):
        project = self.projects['test_human_and_machine']

        # Create first task in project
        create_subsequent_tasks(project)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        mock_preassign.side_effect = Exception
        with self.assertRaises(Exception):
            create_subsequent_tasks(project)
        mock_schedule.assert_not_called()
Пример #20
0
def execute(project_id, step_slug):
    project = Project.objects.get(id=project_id)
    step = Step.objects.get(slug=step_slug,
                            workflow_version=project.workflow_version)
    task = Task.objects.get(project=project,
                            step=step)

    # Run machine function
    if step.is_human:
        raise MachineExecutionError('Step worker type is not machine')

    if task.status == Task.Status.COMPLETE:
        raise MachineExecutionError('Task assignment already completed')

    # Machine tasks are only assigned to one worker/machine,
    # so they should only have one task assignment,
    # and should never be submitted for review.
    count = task.assignments.count()
    if count > 1:
        raise MachineExecutionError('At most 1 assignment per machine task')
    elif count == 1:
        task_assignment = task.assignments.first()
        if task_assignment.status == TaskAssignment.Status.SUBMITTED:
            raise MachineExecutionError('Task assignment completed '
                                        'but task is not!')
    else:
        task_assignment = (
            TaskAssignment.objects
            .create(task=task,
                    status=TaskAssignment.Status.PROCESSING,
                    in_progress_task_data={},
                    snapshots={}))

    prerequisites = previously_completed_task_data(task)

    function_module = import_module(step.execution_function['module'])
    function = getattr(function_module, step.execution_function['name'])
    task_data = function(project.project_data, prerequisites)
    task_assignment.status = TaskAssignment.Status.SUBMITTED
    task_assignment.in_progress_task_data = task_data
    task_assignment.save()
    task.status = Task.Status.COMPLETE
    task.save()

    create_subsequent_tasks(project)
Пример #21
0
    def test_always_create_policy(self):
        project = self.projects['creation_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEqual(project.tasks.count(), 1)

        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should not be created, it never is
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])

        self.assertEqual(project.tasks.count(), 1)
Пример #22
0
    def test_unarchive_slack_channel_api(self, mock_archive, mock_unarchive):
        project = self.projects['single_human_step']
        create_subsequent_tasks(project)
        task = Task.objects.get(project=project)
        assign_task(self.workers[1].id, task.id)
        complete_and_skip_task(task.id)
        create_subsequent_tasks(project)
        self.assertTrue(mock_archive.called)

        response = self.api_client.post(reverse(
            ('orchestra:orchestra:project_management'
             ':unarchive_slack_channel')),
                                        json.dumps({
                                            'project_id': project.id,
                                        }),
                                        content_type='application/json')
        self.assertEqual(response.status_code, 200)
        self.assertTrue(mock_unarchive.called)
Пример #23
0
    def test_task_creation(self):
        """
        Test human and machine task creation
        """
        Task.objects.filter(status=Task.Status.AWAITING_PROCESSING).delete()

        project = self.projects['test_human_and_machine']
        self.assertEquals(Task.objects.filter(project=project).count(),
                          0)
        create_subsequent_tasks(project)

        # Human Task was created
        self.assertEquals(Task.objects.filter(project=project).count(),
                          1)

        response = (self.clients[0].get(
            '/orchestra/api/interface/new_task_assignment/entry_level/'))
        self.assertEquals(response.status_code, 200)

        human_step = self.workflow_steps['test_workflow_2']['step4']
        task = Task.objects.get(step=human_step, project=project)
        data = {'submit_key1': 'submit_val1'}

        # user 0 submits a task
        response = self._submit_assignment(self.clients[0], task.id, data=data)
        self.assertEquals(response.status_code, 200)

        # Machine Task was created
        self.assertEquals(Task.objects.filter(project=project).count(),
                          2)
        machine_step = self.workflow_steps['test_workflow_2']['simple_machine']
        machine_task_assignment = (
            TaskAssignment.objects
            .filter(task__step=machine_step,
                    task__project=project)[0])

        self.assertTrue(machine_task_assignment.status,
                        TaskAssignment.Status.SUBMITTED)

        self.assertTrue(machine_task_assignment.in_progress_task_data,
                        {'simple': 'json'})

        self.assertTrue(machine_task_assignment.task.status,
                        Task.Status.COMPLETE)
Пример #24
0
def create_project_with_tasks(workflow_slug,
                              description,
                              priority,
                              task_class,
                              project_data,
                              review_document_url):

    project = Project.objects.create(workflow_slug=workflow_slug,
                                     short_description=description,
                                     priority=priority,
                                     project_data=project_data,
                                     task_class=task_class,
                                     review_document_url=review_document_url)

    create_project_slack_group(project)
    create_project_google_folder(project)

    create_subsequent_tasks(project)
    return project
Пример #25
0
    def test_completion_ends_project_false(self, mock_slack_archive):
        project = self.projects['test_human_and_machine']
        create_subsequent_tasks(project)

        task = project.tasks.first()
        assign_task(self.workers[1].id, task.id)
        self.assertEqual(project.status, Project.Status.ACTIVE)
        self.assertFalse(mock_slack_archive.called)
        task.status = Task.Status.COMPLETE
        task.save()

        create_subsequent_tasks(project)
        project.refresh_from_db()
        incomplete_tasks = (Task.objects.filter(project=project).exclude(
            Q(status=Task.Status.COMPLETE)
            | Q(status=Task.Status.ABORTED))).count()
        self.assertTrue(incomplete_tasks > 0)
        self.assertEqual(project.status, Project.Status.ACTIVE)
        self.assertFalse(mock_slack_archive.called)
Пример #26
0
def execute(project_id, step_slug):
    project = Project.objects.get(id=project_id)
    workflow = get_workflow_by_slug(project.workflow_slug)
    step = workflow.get_step(step_slug)
    task = Task.objects.get(project=project,
                            step_slug=step_slug)

    # Run machine function
    if step.worker_type != Step.WorkerType.MACHINE:
        raise MachineExecutionError('Step worker type is not machine')

    if task.status == Task.Status.COMPLETE:
        raise MachineExecutionError('Task assignment already completed')

    # Machine tasks are only assigned to one worker/machine,
    # so they should only have one task assignment,
    # and should never be submitted for review.
    count = task.assignments.count()
    if count > 1:
        raise MachineExecutionError('At most 1 assignment per machine task')
    elif count == 1:
        task_assignment = task.assignments.first()
        if task_assignment.status == TaskAssignment.Status.SUBMITTED:
            raise MachineExecutionError('Task assignment completed '
                                        'but task is not!')
    else:
        task_assignment = (
            TaskAssignment.objects
            .create(task=task,
                    status=TaskAssignment.Status.PROCESSING,
                    in_progress_task_data={},
                    snapshots={}))

    prerequisites = previously_completed_task_data(task)

    task_data = step.function(project.project_data, prerequisites)
    task_assignment.status = TaskAssignment.Status.SUBMITTED
    task_assignment.in_progress_task_data = task_data
    task_assignment.save()
    task.status = Task.Status.COMPLETE
    task.save()

    create_subsequent_tasks(project)
Пример #27
0
    def test_complete_and_skip_task_api(self):
        # This task, when submitted, will be moved into reviewing
        # state because it's already been assigned.
        task = self.tasks['project_management_task']
        response = self.api_client.post(
            reverse(
                'orchestra:orchestra:project_management:'
                'complete_and_skip_task'),
            json.dumps({
                'task_id': task.id,
            }),
            content_type='application/json')
        self.assertEqual(response.status_code, 200)
        task.refresh_from_db()
        self.assertEqual(task.status, Task.Status.REVIEWING)
        assignment_statuses = [
            TaskAssignment.Status.SUBMITTED, TaskAssignment.Status.SUBMITTED,
            TaskAssignment.Status.PROCESSING]
        for index, assignment in enumerate(
                task.assignments.all().order_by('assignment_counter')):
            self.assertEqual(
                assignment.status, assignment_statuses[index])
        # Check that dependent tasks have already been created
        num_tasks = task.project.tasks.count()
        create_subsequent_tasks(task.project)
        self.assertEqual(task.project.tasks.count(), num_tasks)

        # This task, when submitted, will be moved into a completed
        # state because it has no assignments.
        task = self.tasks['awaiting_processing']
        response = self.api_client.post(
            reverse(
                'orchestra:orchestra:project_management:'
                'complete_and_skip_task'),
            json.dumps({
                'task_id': task.id,
            }),
            content_type='application/json')
        self.assertEqual(response.status_code, 200)
        task.refresh_from_db()
        self.assertEqual(task.status, Task.Status.COMPLETE)
Пример #28
0
    def test_complete_and_skip_task_api(self):
        task = self.tasks['project_management_task']
        response = self.api_client.post(reverse(
            'orchestra:orchestra:project_management:'
            'complete_and_skip_task'),
                                        json.dumps({
                                            'task_id': task.id,
                                        }),
                                        content_type='application/json')
        self.assertEquals(response.status_code, 200)
        task.refresh_from_db()
        self.assertEquals(task.status, Task.Status.COMPLETE)
        for assignment in task.assignments.all():
            self.assertEquals(assignment.status,
                              TaskAssignment.Status.SUBMITTED)

        # Check that dependent tasks have already been created
        # TODO(jrbotros): Create a `get_dependent_tasks` function
        num_tasks = task.project.tasks.count()
        create_subsequent_tasks(task.project)
        self.assertEquals(task.project.tasks.count(), num_tasks)
Пример #29
0
def create_project_with_tasks(workflow_slug,
                              workflow_version_slug,
                              description,
                              priority,
                              task_class,
                              project_data):

    workflow_version = WorkflowVersion.objects.get(
        slug=workflow_version_slug,
        workflow__slug=workflow_slug)

    project = Project.objects.create(workflow_version=workflow_version,
                                     short_description=description,
                                     priority=priority,
                                     project_data=project_data,
                                     task_class=task_class)

    create_project_slack_group(project)
    create_project_google_folder(project)

    create_subsequent_tasks(project)
    return project
Пример #30
0
def execute(project_id, step_slug):
    project = Project.objects.get(id=project_id)
    step = Step.objects.get(slug=step_slug, workflow_version=project.workflow_version)
    task = Task.objects.get(project=project, step=step)

    # Run machine function
    if step.is_human:
        raise MachineExecutionError("Step worker type is not machine")

    if task.status == Task.Status.COMPLETE:
        raise MachineExecutionError("Task assignment already completed")

    # Machine tasks are only assigned to one worker/machine,
    # so they should only have one task assignment,
    # and should never be submitted for review.
    count = task.assignments.count()
    if count > 1:
        raise MachineExecutionError("At most 1 assignment per machine task")
    elif count == 1:
        task_assignment = task.assignments.first()
        if task_assignment.status == TaskAssignment.Status.SUBMITTED:
            raise MachineExecutionError("Task assignment completed " "but task is not!")
    else:
        task_assignment = TaskAssignment.objects.create(
            task=task, status=TaskAssignment.Status.PROCESSING, in_progress_task_data={}, snapshots={}
        )

    prerequisites = previously_completed_task_data(task)

    function_module = import_module(step.execution_function["module"])
    function = getattr(function_module, step.execution_function["name"])
    task_data = function(project.project_data, prerequisites)
    task_assignment.status = TaskAssignment.Status.SUBMITTED
    task_assignment.in_progress_task_data = task_data
    task_assignment.save()
    task.status = Task.Status.COMPLETE
    task.save()

    create_subsequent_tasks(project)
    def test_complete_and_skip_task_api(self):
        task = self.tasks['project_management_task']
        response = self.api_client.post(
            reverse(
                'orchestra:orchestra:project_management:'
                'complete_and_skip_task'),
            json.dumps({
                'task_id': task.id,
            }),
            content_type='application/json')
        self.assertEquals(response.status_code, 200)
        task.refresh_from_db()
        self.assertEquals(task.status, Task.Status.COMPLETE)
        for assignment in task.assignments.all():
            self.assertEquals(
                assignment.status, TaskAssignment.Status.SUBMITTED)

        # Check that dependent tasks have already been created
        # TODO(jrbotros): Create a `get_dependent_tasks` function
        num_tasks = task.project.tasks.count()
        create_subsequent_tasks(task.project)
        self.assertEquals(task.project.tasks.count(), num_tasks)
Пример #32
0
    def test_task_creation(self):
        """
        Test human and machine task creation
        """
        Task.objects.filter(status=Task.Status.AWAITING_PROCESSING).delete()

        project = self.projects['test_human_and_machine']
        self.assertEqual(Task.objects.filter(project=project).count(), 0)
        create_subsequent_tasks(project)

        # Human Task was created
        self.assertEqual(Task.objects.filter(project=project).count(), 1)

        response = (self.clients[0].get(
            '/orchestra/api/interface/new_task_assignment/entry_level/'))
        self.assertEqual(response.status_code, 200)

        human_step = self.workflow_steps['test_workflow_2']['step4']
        task = Task.objects.get(step=human_step, project=project)
        data = {'submit_key1': 'submit_val1'}

        # user 0 submits a task
        response = self._submit_assignment(self.clients[0], task.id, data=data)
        self.assertEqual(response.status_code, 200)

        # Machine Task was created
        self.assertEqual(Task.objects.filter(project=project).count(), 2)
        machine_step = self.workflow_steps['test_workflow_2']['simple_machine']
        machine_task_assignment = (TaskAssignment.objects.filter(
            task__step=machine_step, task__project=project)[0])

        self.assertTrue(machine_task_assignment.status,
                        TaskAssignment.Status.SUBMITTED)

        self.assertTrue(machine_task_assignment.in_progress_task_data,
                        {'simple': 'json'})

        self.assertTrue(machine_task_assignment.task.status,
                        Task.Status.COMPLETE)
Пример #33
0
    def test_malformed_assignment_policy(self):
        project = self.projects['assignment_policy']
        workflow_version = project.workflow_version
        first_step = self.workflow_steps[workflow_version.slug]['step_0']

        # Create an invalid machine step with an assignment policy
        malformed_step = StepFactory(
            workflow_version=workflow_version,
            slug='machine_step',
            is_human=False,
            assignment_policy={
                'policy_function': {
                    'entry_level': {
                        'path': ('orchestra.assignment_policies.'
                                 'previously_completed_steps'),
                        'kwargs': {
                            'related_steps': ['step_0']
                        },
                    }
                }
            },
            creation_policy=get_default_creation_policy(),
        )
        malformed_step.creation_depends_on.add(first_step)

        # Create first task in project
        create_subsequent_tasks(project)
        self.assertEqual(project.tasks.count(), 1)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        # Cannot preassign machine task
        with self.assertRaises(AssignmentPolicyError):
            create_subsequent_tasks(project)

        # Reset project
        project.tasks.all().delete()

        # Machine should not be member of assignment policy
        first_step.assignment_policy = {
            'policy_function': {
                'entry_level': {
                    'path': ('orchestra.assignment_policies.'
                             'previously_completed_steps'),
                    'kwargs': {
                        'related_steps': ['machine_step']
                    },
                },
            }
        }
        first_step.save()
        with self.assertRaises(AssignmentPolicyError):
            create_subsequent_tasks(project)
Пример #34
0
    def test_malformed_assignment_policy(self):
        project = self.projects['assignment_policy']
        workflow_version = project.workflow_version
        first_step = self.workflow_steps[workflow_version.slug]['step_0']

        # Create an invalid machine step with an assignment policy
        malformed_step = StepFactory(
            workflow_version=workflow_version,
            slug='machine_step',
            is_human=False,
            assignment_policy={
                'policy_function': {
                    'entry_level': {
                        'path': ('orchestra.assignment_policies.'
                                 'previously_completed_steps'),
                        'kwargs': {
                            'related_steps': ['step_0']
                        },
                    }
                }
            },
            creation_policy=get_default_creation_policy(),
        )
        malformed_step.creation_depends_on.add(first_step)

        # Create first task in project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        # Cannot preassign machine task
        with self.assertRaises(AssignmentPolicyError):
            create_subsequent_tasks(project)

        # Reset project
        project.tasks.all().delete()

        # Machine should not be member of assignment policy
        first_step.assignment_policy = {
            'policy_function': {
                'entry_level': {
                    'path': ('orchestra.assignment_policies.'
                             'previously_completed_steps'),
                    'kwargs': {
                        'related_steps': ['machine_step']
                    },
                },
            }
        }
        first_step.save()
        with self.assertRaises(AssignmentPolicyError):
            create_subsequent_tasks(project)
    def test_preassign_workers(self, mock_mail, mock_slack):
        request_cause = StaffBotRequest.RequestCause.AUTOSTAFF.value
        staffing_request_count = StaffingRequestInquiry.objects.filter(
            request__request_cause=request_cause).count()
        project = self.projects['staffbot_assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        send_staffing_requests()
        self.assertEqual(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])

        # Mock mail should be called if we autostaff
        self.assertTrue(mock_mail.called)
        self.assertTrue(mock_slack.called)
        # Assert we created new StaffingRequestInquirys because of autostaff
        new_staffing_request_count = StaffingRequestInquiry.objects.filter(
            request__request_cause=request_cause).count()
        self.assertTrue(staffing_request_count < new_staffing_request_count)
        self.assertEqual(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 0 not certified for related tasks, so should not have been
        # auto-assigned
        self.assertEqual(related_task.assignments.count(), 0)
        self.assertEqual(related_task.status, Task.Status.AWAITING_PROCESSING)

        # Reset project
        project.tasks.all().delete()
Пример #36
0
    def test_malformed_assignment_policy(self):
        project = self.projects['assignment_policy']

        # Machine should not have an assignment policy
        workflow = get_workflow_by_slug('assignment_policy_workflow')
        machine_step = Step(
            slug='machine_step',
            worker_type=Step.WorkerType.MACHINE,
            assignment_policy={'policy': 'previously_completed_steps',
                               'steps': ['step_0']},
            creation_depends_on=[workflow.get_step('step_0')],
            function=lambda *args: None,
        )
        workflow.add_step(machine_step)

        # Create first task in project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)

        # Assign initial task to worker 0 and mark as complete
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        initial_task.status = Task.Status.COMPLETE
        initial_task.save()

        # Cannot preassign machine task
        with self.assertRaises(AssignmentPolicyError):
            create_subsequent_tasks(project)

        # Reset project
        project.tasks.all().delete()

        # Machine should not be member of assignment policy
        (workflow.get_step('step_0')
            .assignment_policy) = {'policy': 'previously_completed_steps',
                                   'steps': ['machine_step']}
        with self.assertRaises(AssignmentPolicyError):
            create_subsequent_tasks(project)

        # Reset workflow and project
        (workflow.get_step('step_0')
            .assignment_policy) = {'policy': 'anyone_certified'}
        del workflow.steps['machine_step']
        project.tasks.all().delete()
Пример #37
0
    def test_create_and_handle_sanity_checks(self, mock_slack):
        # Initialize relevant project.
        create_subsequent_tasks(self.projects['sanitybot'])
        # Initialize irrelevant project.
        create_subsequent_tasks(self.projects['test_human_and_machine'])

        # No sanity checks exist, and Slack hasn't received a message.
        sanity_checks = SanityCheck.objects.all()
        self.assertEqual(sanity_checks.count(), 0)
        self.assertEqual(mock_slack.call_count, 0)

        create_and_handle_sanity_checks()
        # Of the four sanity checks for this project, three were
        # triggered by orchestra.tests.helpers.workflow.check_project.
        sanity_checks = SanityCheck.objects.all()
        self.assertEqual(
            dict(Counter(sanity_checks.values_list('check_slug', flat=True))),
            {
                'frequently_repeating_check': 1,
                'infrequently_repeating_check': 1,
                'onetime_check': 1
            })
        for sanity_check in sanity_checks:
            self.assertEqual(
                sanity_check.project.workflow_version.slug,
                'sanitybot_workflow')
        # Three slack messages for three sanity checks.
        self.assertEqual(mock_slack.call_count, 3)

        # Too little time has passed, so we expect no new sanity checks.
        create_and_handle_sanity_checks()
        # Still only three sanity check exists for the relevant project.
        sanity_checks = SanityCheck.objects.all()
        self.assertEqual(sanity_checks.count(), 3)
        # Slack didn't get called again.
        self.assertEqual(mock_slack.call_count, 3)

        # Mark the sanity checks as having happened two days ago. Only
        # the `frequently_repeating_check` should trigger.
        for sanity_check in sanity_checks:
            sanity_check.created_at = (
                sanity_check.created_at - timedelta(days=2))
            sanity_check.save()
        create_and_handle_sanity_checks()
        # Look for the three old sanity checks and a new
        # `frequently_repeating_check`.
        sanity_checks = SanityCheck.objects.all()
        self.assertEqual(
            dict(Counter(sanity_checks.values_list('check_slug', flat=True))),
            {
                'frequently_repeating_check': 2,
                'infrequently_repeating_check': 1,
                'onetime_check': 1
            })
        for sanity_check in sanity_checks:
            self.assertEqual(
                sanity_check.project.workflow_version.slug,
                'sanitybot_workflow')
        # Slack got called another time.
        self.assertEqual(mock_slack.call_count, 4)

        create_and_handle_sanity_checks()
        # Still only four sanity checks exists for the relevant project.
        sanity_checks = SanityCheck.objects.all()
        self.assertEqual(sanity_checks.count(), 4)
        # Slack didn't get called again.
        self.assertEqual(mock_slack.call_count, 4)
Пример #38
0
def execute(project_id, step_slug):
    project = Project.objects.get(id=project_id)
    step = Step.objects.get(slug=step_slug,
                            workflow_version=project.workflow_version)
    task = Task.objects.get(project=project,
                            step=step)

    # Run machine function
    if step.is_human:
        raise MachineExecutionError('Step worker type is not machine')

    if task.status == Task.Status.COMPLETE:
        raise MachineExecutionError('Task assignment already completed')

    # Machine tasks are only assigned to one worker/machine,
    # so they should only have one task assignment,
    # and should never be submitted for review.

    with transaction.atomic():
        # Uniqueness constraint on assignnment_counter and task prevents
        # concurrent creation of more than one assignment
        task_assignment, created = TaskAssignment.objects.get_or_create(
            assignment_counter=0,
            task=task,
            defaults={
                'status': TaskAssignment.Status.PROCESSING,
                'in_progress_task_data': {}})
        if created:
            task.status = Task.Status.PROCESSING
            task.save()

            Iteration.objects.create(
                assignment=task_assignment,
                start_datetime=task_assignment.start_datetime)
        else:
            # Task assignment already exists
            if task_assignment.status == TaskAssignment.Status.FAILED:
                # Pick up failed task for reprocessing
                task_assignment.status = TaskAssignment.Status.PROCESSING
                task_assignment.save()
            else:
                # Task assignment already processing
                raise MachineExecutionError(
                    'Task already picked up by another machine')

    prerequisites = get_previously_completed_task_data(task.step, task.project)

    function = locate(step.execution_function['path'])
    kwargs = step.execution_function.get('kwargs', {})
    try:
        project_data = project.project_data
        project_data['project_id'] = project_id
        task_data = function(project_data, prerequisites, **kwargs)
    except:
        task_assignment.status = TaskAssignment.Status.FAILED
        logger.exception('Machine task has failed')
        task_assignment.save()
        return
    task_assignment.status = TaskAssignment.Status.SUBMITTED
    task_assignment.in_progress_task_data = task_data
    task_assignment.save()

    if task.project.status == Project.Status.ABORTED:
        # If a long-running task's project was aborted while running, we ensure
        # the aborted state on the task.
        task.status = Task.Status.ABORTED
        task.save()
    else:
        task.status = Task.Status.COMPLETE
        task.save()

        iteration = get_latest_iteration(task_assignment)
        iteration.status = Iteration.Status.REQUESTED_REVIEW
        iteration.submitted_data = task_data
        iteration.end_datetime = timezone.now()
        iteration.save()

        create_subsequent_tasks(project)
Пример #39
0
def execute(project_id, step_slug):
    project = Project.objects.get(id=project_id)
    step = Step.objects.get(slug=step_slug,
                            workflow_version=project.workflow_version)
    task = Task.objects.get(project=project,
                            step=step)

    # Run machine function
    if step.is_human:
        raise MachineExecutionError('Step worker type is not machine')

    if task.status == Task.Status.COMPLETE:
        raise MachineExecutionError('Task assignment already completed')

    # Machine tasks are only assigned to one worker/machine,
    # so they should only have one task assignment,
    # and should never be submitted for review.

    with transaction.atomic():
        # Uniqueness constraint on assignnment_counter and task prevents
        # concurrent creation of more than one assignment
        task_assignment, created = TaskAssignment.objects.get_or_create(
            assignment_counter=0,
            task=task,
            defaults={
                'status': TaskAssignment.Status.PROCESSING,
                'in_progress_task_data': {}})
        if created:
            task.status = Task.Status.PROCESSING
            task.save()

            Iteration.objects.create(
                assignment=task_assignment,
                start_datetime=task_assignment.start_datetime)
        else:
            # Task assignment already exists
            if task_assignment.status == TaskAssignment.Status.FAILED:
                # Pick up failed task for reprocessing
                task_assignment.status = TaskAssignment.Status.PROCESSING
                task_assignment.save()
            else:
                # Task assignment already processing
                raise MachineExecutionError(
                    'Task already picked up by another machine')

    prerequisites = previously_completed_task_data(task)

    function = locate(step.execution_function['path'])
    kwargs = step.execution_function.get('kwargs', {})
    try:
        project_data = project.project_data
        project_data['project_id'] = project_id
        task_data = function(project_data, prerequisites, **kwargs)
    except:
        task_assignment.status = TaskAssignment.Status.FAILED
        logger.exception('Machine task has failed')
        task_assignment.save()
        return
    task_assignment.status = TaskAssignment.Status.SUBMITTED
    task_assignment.in_progress_task_data = task_data
    task_assignment.save()

    if task.project.status == Project.Status.ABORTED:
        # If a long-running task's project was aborted while running, we ensure
        # the aborted state on the task.
        task.status = Task.Status.ABORTED
        task.save()
    else:
        task.status = Task.Status.COMPLETE
        task.save()

        iteration = get_latest_iteration(task_assignment)
        iteration.status = Iteration.Status.REQUESTED_REVIEW
        iteration.submitted_data = task_data
        iteration.end_datetime = timezone.now()
        iteration.save()

        create_subsequent_tasks(project)
Пример #40
0
    def test_preassign_workers(self):
        project = self.projects['assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 0 not certified for related tasks, so should not have been
        # auto-assigned
        self.assertEquals(related_task.assignments.count(), 0)
        self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING)

        # Reset project
        project.tasks.all().delete()

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; verify we use the reviewer assignment policy
        mock_preassign_workers = MagicMock(return_value=initial_task)
        patch_path = 'orchestra.utils.task_lifecycle._preassign_workers'
        with patch(patch_path, new=mock_preassign_workers):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])
            mock_preassign_workers.assert_called_once_with(
                initial_task, AssignmentPolicyType.REVIEWER)

        # Reset project
        project.tasks.all().delete()

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 4
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[4])
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 4 is certified for related task and should have been assigned
        self.assertEquals(related_task.assignments.count(), 1)
        self.assertEquals(related_task.status, Task.Status.PROCESSING)
        self.assertTrue(
            related_task.is_worker_assigned(self.workers[4]))
Пример #41
0
    def test_preassign_workers(self):
        project = self.projects['assignment_policy']

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 0 not certified for related tasks, so should not have been
        # auto-assigned
        self.assertEquals(related_task.assignments.count(), 0)
        self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING)

        # Reset project
        project.tasks.all().delete()

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 0
        initial_task = assign_task(self.workers[0].id,
                                   project.tasks.first().id)
        # Submit task; verify we use the reviewer assignment policy
        mock_preassign_workers = MagicMock(return_value=initial_task)
        patch_path = 'orchestra.utils.task_lifecycle._preassign_workers'
        with patch(patch_path, new=mock_preassign_workers):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[0])
            mock_preassign_workers.assert_called_once_with(
                initial_task, AssignmentPolicyType.REVIEWER)

        # Reset project
        project.tasks.all().delete()

        # Create first task in test project
        create_subsequent_tasks(project)
        self.assertEquals(project.tasks.count(), 1)
        # Assign initial task to worker 4
        initial_task = assign_task(self.workers[4].id,
                                   project.tasks.first().id)
        # Submit task; next task should be created
        with patch('orchestra.utils.task_lifecycle._is_review_needed',
                   return_value=False):
            initial_task = submit_task(initial_task.id, {},
                                       Iteration.Status.REQUESTED_REVIEW,
                                       self.workers[4])
        self.assertEquals(project.tasks.count(), 2)
        related_task = project.tasks.exclude(id=initial_task.id).first()
        # Worker 4 is certified for related task and should have been assigned
        self.assertEquals(related_task.assignments.count(), 1)
        self.assertEquals(related_task.status, Task.Status.PROCESSING)
        self.assertTrue(related_task.is_worker_assigned(self.workers[4]))

        # Reset project
        project.tasks.all().delete()