def _test_reverted_task(self, task, iteration, num_iterations, task_status, latest_data, expected_audit, revert_before=False): response = self._revert_task( task, iteration, revert_before=revert_before, commit=False) self.assertEqual(response.status_code, 200) task.refresh_from_db() fake_audit = load_encoded_json(response.content) self.assertEqual(fake_audit, expected_audit) self.assertEqual(task.status, Task.Status.COMPLETE) self.assertEqual(task.assignments.count(), 2) for assignment in task.assignments.all(): self.assertEqual(assignment.iterations.count(), 2) response = self._revert_task( task, iteration, revert_before=revert_before, commit=True) self.assertEqual(response.status_code, 200) task.refresh_from_db() audit = load_encoded_json(response.content) self.assertEqual(audit, fake_audit) task.refresh_from_db() self.assertEqual(task.status, task_status) self.assertEqual( get_iteration_history(task).count(), num_iterations) verify_iterations(task.id) if num_iterations: self.assertEqual( current_assignment(task).in_progress_task_data, latest_data) verify_iterations(task.id)
def verify_iterations(task_id): task = Task.objects.get(id=task_id) iterations = list(get_iteration_history(task).all()) if iterations: _verify_iteration_topology(iterations) _verify_iteration_data(iterations) _verify_iteration_datetimes(iterations)
def _test_reverted_task(self, task, iteration, num_iterations, task_status, latest_data, expected_audit, revert_before=False): response = self._revert_task( task, iteration, revert_before=revert_before, commit=False) self.assertEquals(response.status_code, 200) task.refresh_from_db() fake_audit = load_encoded_json(response.content) self.assertEqual(fake_audit, expected_audit) self.assertEquals(task.status, Task.Status.COMPLETE) self.assertEquals(task.assignments.count(), 2) for assignment in task.assignments.all(): self.assertEquals(assignment.iterations.count(), 2) response = self._revert_task( task, iteration, revert_before=revert_before, commit=True) self.assertEqual(response.status_code, 200) task.refresh_from_db() audit = load_encoded_json(response.content) self.assertEqual(audit, fake_audit) task.refresh_from_db() self.assertEqual(task.status, task_status) self.assertEqual( get_iteration_history(task).count(), num_iterations) verify_iterations(task.id) if num_iterations: self.assertEquals( current_assignment(task).in_progress_task_data, latest_data) verify_iterations(task.id)
def _expected_audit(self, complete_task, reverted_status=None, assignment_changes=None, iteration_changes=None): assignments = assignment_history(complete_task) iterations = get_iteration_history(complete_task) audit = { 'task': TaskSerializer(complete_task).data, 'assignments': [{ 'assignment': (TaskAssignmentSerializer(assignments.first()).data), 'change': 0, 'iterations': [{ 'change': 0, 'iteration': (IterationSerializer(iterations.all()[0]).data) }, { 'change': 0, 'iteration': (IterationSerializer(iterations.all()[2]).data) }] }, { 'assignment': (TaskAssignmentSerializer(assignments.last()).data), 'change': 0, 'iterations': [{ 'change': 0, 'iteration': (IterationSerializer(iterations.all()[1]).data) }, { 'change': 0, 'iteration': (IterationSerializer(iterations.all()[3]).data) }] }], } if reverted_status is not None: audit['reverted_status'] = reverted_status if assignment_changes is not None: for i, assignment_change in enumerate(assignment_changes): audit['assignments'][i]['change'] = assignment_change if iteration_changes is not None: for i, changes_per_assignment in enumerate(iteration_changes): for j, iteration_change in enumerate(changes_per_assignment): audit['assignments'][i]['iterations'][j][ 'change'] = iteration_change return audit
def _expected_audit(self, complete_task, reverted_status=None, assignment_changes=None, iteration_changes=None): assignments = assignment_history(complete_task) iterations = get_iteration_history(complete_task) audit = { 'task': TaskSerializer(complete_task).data, 'assignments': [ { 'assignment': ( TaskAssignmentSerializer(assignments.first()).data), 'change': 0, 'iterations': [ { 'change': 0, 'iteration': ( IterationSerializer(iterations.all()[0]).data) }, { 'change': 0, 'iteration': ( IterationSerializer(iterations.all()[2]).data) } ] }, { 'assignment': ( TaskAssignmentSerializer(assignments.last()).data), 'change': 0, 'iterations': [ { 'change': 0, 'iteration': ( IterationSerializer(iterations.all()[1]).data) }, { 'change': 0, 'iteration': ( IterationSerializer(iterations.all()[3]).data) } ] } ], } if reverted_status is not None: audit['reverted_status'] = reverted_status if assignment_changes is not None: for i, assignment_change in enumerate(assignment_changes): audit['assignments'][i]['change'] = assignment_change if iteration_changes is not None: for i, changes_per_assignment in enumerate(iteration_changes): for j, iteration_change in enumerate(changes_per_assignment): audit['assignments'][i][ 'iterations'][j]['change'] = iteration_change return audit
def _reverted_task_status(task_audit, revert_before): """ Reverts the status of an otherwise-reverted task. Args: task (dict): Audit containing task data to be changed upon revert. Returns: status (orchestra.models.Task.Status): The status of the task if it were reverted. """ task = Task.objects.get(id=task_audit['task']['id']) flattened_iterations = [ iteration_audit for assignment_audit in task_audit['assignments'] for iteration_audit in assignment_audit['iterations']] changed_items = _parse_changed_items(flattened_iterations, 'iteration') latest_iterations = ( get_iteration_history(task, reverse=True) .exclude(id__in=changed_items[RevertChange.DELETED.value])) num_iterations = latest_iterations.count() if num_iterations == 0: return Task.Status.AWAITING_PROCESSING elif revert_before: # Reverting before the first iteration in an assignment means the task # is pending review, since at least one iteration exists return Task.Status.PENDING_REVIEW else: # Revert to a processing iteration state if num_iterations == 1: return Task.Status.PROCESSING else: previous_status = latest_iterations[1].status if previous_status == Iteration.Status.REQUESTED_REVIEW: return Task.Status.REVIEWING else: return Task.Status.POST_REVIEW_PROCESSING
def _reverted_task_status(task_audit, revert_before): """ Reverts the status of an otherwise-reverted task. Args: task (dict): Audit containing task data to be changed upon revert. Returns: status (orchestra.models.Task.Status): The status of the task if it were reverted. """ task = Task.objects.get(id=task_audit['task']['id']) flattened_iterations = [ iteration_audit for assignment_audit in task_audit['assignments'] for iteration_audit in assignment_audit['iterations'] ] changed_items = _parse_changed_items(flattened_iterations, 'iteration') latest_iterations = (get_iteration_history(task, reverse=True).exclude( id__in=changed_items[RevertChange.DELETED.value])) num_iterations = latest_iterations.count() if num_iterations == 0: return Task.Status.AWAITING_PROCESSING elif revert_before: # Reverting before the first iteration in an assignment means the task # is pending review, since at least one iteration exists return Task.Status.PENDING_REVIEW else: # Revert to a processing iteration state if num_iterations == 1: return Task.Status.PROCESSING else: previous_status = latest_iterations[1].status if previous_status == Iteration.Status.REQUESTED_REVIEW: return Task.Status.REVIEWING else: return Task.Status.POST_REVIEW_PROCESSING
def test_revert_processing(self): task = setup_complete_task(self) reverted_status = Task.Status.PROCESSING expected_audit = self._expected_audit( task, reverted_status=reverted_status, assignment_changes=(RevertChange.REVERTED.value, RevertChange.DELETED.value), iteration_changes=((RevertChange.REVERTED.value, RevertChange.DELETED.value), (RevertChange.DELETED.value, RevertChange.DELETED.value))) self._test_reverted_task( task, iteration=get_iteration_history(task).all()[0], num_iterations=1, task_status=reverted_status, latest_data={'test': 'entry_resubmit'}, expected_audit=expected_audit) task.delete()
def test_revert_first_review(self): task = setup_complete_task(self) reverted_status = Task.Status.REVIEWING expected_audit = self._expected_audit( task, reverted_status=reverted_status, assignment_changes=(RevertChange.REVERTED.value, RevertChange.REVERTED.value), iteration_changes=((RevertChange.UNCHANGED.value, RevertChange.DELETED.value), (RevertChange.REVERTED.value, RevertChange.DELETED.value))) self._test_reverted_task( task, iteration=get_iteration_history(task).all()[1], num_iterations=2, task_status=reverted_status, latest_data={'test': 'reviewer_accept'}, expected_audit=expected_audit) task.delete()
def test_revert_processing(self): task = setup_complete_task(self) reverted_status = Task.Status.PROCESSING expected_audit = self._expected_audit( task, reverted_status=reverted_status, assignment_changes=( RevertChange.REVERTED.value, RevertChange.DELETED.value), iteration_changes=( (RevertChange.REVERTED.value, RevertChange.DELETED.value), (RevertChange.DELETED.value, RevertChange.DELETED.value) )) self._test_reverted_task( task, iteration=get_iteration_history(task).all()[0], num_iterations=1, task_status=reverted_status, latest_data={'test': 'entry_resubmit'}, expected_audit=expected_audit) task.delete()
def test_revert_first_review(self): task = setup_complete_task(self) reverted_status = Task.Status.REVIEWING expected_audit = self._expected_audit( task, reverted_status=reverted_status, assignment_changes=( RevertChange.REVERTED.value, RevertChange.REVERTED.value), iteration_changes=( (RevertChange.UNCHANGED.value, RevertChange.DELETED.value), (RevertChange.REVERTED.value, RevertChange.DELETED.value) )) self._test_reverted_task( task, iteration=get_iteration_history(task).all()[1], num_iterations=2, task_status=reverted_status, latest_data={'test': 'reviewer_accept'}, expected_audit=expected_audit) task.delete()
def test_revert_pending_review(self): task = setup_complete_task(self) reverted_status = Task.Status.PENDING_REVIEW expected_audit = self._expected_audit( task, reverted_status=reverted_status, assignment_changes=(RevertChange.REVERTED.value, RevertChange.DELETED.value), iteration_changes=((RevertChange.UNCHANGED.value, RevertChange.DELETED.value), (RevertChange.DELETED.value, RevertChange.DELETED.value))) self._test_reverted_task( task, iteration=get_iteration_history(task).all()[1], num_iterations=1, task_status=reverted_status, latest_data={'test': 'entry_resubmit'}, expected_audit=expected_audit, revert_before=True) task.delete()
def test_revert_pending_review(self): task = setup_complete_task(self) reverted_status = Task.Status.PENDING_REVIEW expected_audit = self._expected_audit( task, reverted_status=reverted_status, assignment_changes=( RevertChange.REVERTED.value, RevertChange.DELETED.value), iteration_changes=( (RevertChange.UNCHANGED.value, RevertChange.DELETED.value), (RevertChange.DELETED.value, RevertChange.DELETED.value) )) self._test_reverted_task( task, iteration=get_iteration_history(task).all()[1], num_iterations=1, task_status=reverted_status, latest_data={'test': 'entry_resubmit'}, expected_audit=expected_audit, revert_before=True) task.delete()
def _setup_tasks(test_case, tasks): # Create and assign tasks test_case.tasks = {} test_case.test_step = test_case.workflow_steps[ test_case.test_version_slug][test_case.test_step_slug] for task_slug, details in tasks.items(): task_pickup_time = BASE_DATETIME + timedelta(hours=1) task = TaskFactory( project=test_case.projects[details['project_name']], step=test_case.test_step, status=details['status'], start_datetime=task_pickup_time, ) test_case.tasks[task_slug] = task for i, (user_id, task_data, assignment_status) in enumerate(details['assignments']): assignment = TaskAssignmentFactory( worker=test_case.workers[user_id], task=task, status=assignment_status, assignment_counter=i, in_progress_task_data=task_data, start_datetime=_new_assignment_start_datetime(task)) # Each assignment must have at least one corresponding iteration Iteration.objects.create( assignment=assignment, start_datetime=assignment.start_datetime, end_datetime=assignment.start_datetime + ITERATION_DURATION, submitted_data=assignment.in_progress_task_data, status=Iteration.Status.REQUESTED_REVIEW) # Create time entry for each task. TimeEntryFactory(date='2016-04-04', time_worked=timedelta(minutes=30), assignment=assignment, worker=test_case.workers[user_id], description=( 'test description {}'.format(assignment.id))) cur_assignment = current_assignment(task) assignments = assignment_history(task).all() if cur_assignment and ( cur_assignment.status == TaskAssignment.Status.PROCESSING): # If there's a currently processing assignment, we'll need to # adjust the task's iteration sequence processing_counter = cur_assignment.assignment_counter if processing_counter != len(assignments) - 1: # If processing assignment is not the last in the hierarchy, we # need to reconstruct an iteration sequence: REQUESTED_REVIEW # up to the highest assignment counter, then PROVIDED_REVIEW # back down to the current assignment last_iteration = assignments.last().iterations.first() last_iteration.status = Iteration.Status.PROVIDED_REVIEW last_iteration.save() adjust_assignments = list(assignments)[processing_counter:-1] for assignment in reversed(adjust_assignments): last_iteration = get_iteration_history(task).last() Iteration.objects.create( assignment=assignment, start_datetime=last_iteration.end_datetime, end_datetime=( last_iteration.end_datetime + ITERATION_DURATION), submitted_data=assignment.in_progress_task_data, status=Iteration.Status.PROVIDED_REVIEW) # If there is a currently processing assignment, the task's last # iteration should still be processing last_iteration = get_iteration_history(task).last() last_iteration.end_datetime = None last_iteration.submitted_data = {} last_iteration.status = Iteration.Status.PROCESSING last_iteration.save() verify_iterations(task.id)
def setup_complete_task(test_case): # Microseconds are truncated when manually saving models test_start = timezone.now().replace(microsecond=0) times = { 'awaiting_pickup': test_start, 'entry_pickup': test_start + timedelta(hours=1), 'entry_submit': test_start + timedelta(hours=2), 'reviewer_pickup': test_start + timedelta(hours=3), 'reviewer_reject': test_start + timedelta(hours=4), 'entry_resubmit': test_start + timedelta(hours=5), 'reviewer_accept': test_start + timedelta(hours=6), } task = TaskFactory( project=test_case.projects['empty_project'], status=Task.Status.AWAITING_PROCESSING, step=test_case.test_step, start_datetime=times['awaiting_pickup']) workers = { 'entry': test_case.workers[0], 'reviewer': test_case.workers[1] } assign_task(workers['entry'].id, task.id) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.PROCESSING) submit_task( task.id, {'test': 'entry_submit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.PENDING_REVIEW) assign_task(workers['reviewer'].id, task.id) reviewer_assignment = task.assignments.get( worker=workers['reviewer']) # Modify assignment with correct datetime reviewer_assignment.start_datetime = times['reviewer_pickup'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.REVIEWING) submit_task( task.id, {'test': 'reviewer_reject'}, Iteration.Status.PROVIDED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.POST_REVIEW_PROCESSING) submit_task( task.id, {'test': 'entry_resubmit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.REVIEWING) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): submit_task( task.id, {'test': 'reviewer_accept'}, Iteration.Status.REQUESTED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.COMPLETE) test_case.assertEqual(task.assignments.count(), 2) for assignment in task.assignments.all(): test_case.assertEqual( assignment.status, TaskAssignment.Status.SUBMITTED) test_case.assertEqual(assignment.iterations.count(), 2) # Modify assignments with correct datetime new_datetime_labels = ('entry_pickup', 'reviewer_pickup') for i, assignment in enumerate(assignment_history(task).all()): assignment.start_datetime = times[new_datetime_labels[i]] assignment.save() # Modify iterations with correct datetime new_datetime_labels = ( ('entry_pickup', 'entry_submit'), ('reviewer_pickup', 'reviewer_reject'), ('reviewer_reject', 'entry_resubmit'), ('entry_resubmit', 'reviewer_accept') ) new_datetimes = [ (times[start_label], times[end_label]) for start_label, end_label in new_datetime_labels] for i, iteration in enumerate(get_iteration_history(task)): iteration.start_datetime, iteration.end_datetime = new_datetimes[i] iteration.save() verify_iterations(task.id) return task
def setup_complete_task(test_case): # Microseconds are truncated when manually saving models test_start = timezone.now().replace(microsecond=0) times = { 'awaiting_pickup': test_start, 'entry_pickup': test_start + timedelta(hours=1), 'entry_submit': test_start + timedelta(hours=2), 'reviewer_pickup': test_start + timedelta(hours=3), 'reviewer_reject': test_start + timedelta(hours=4), 'entry_resubmit': test_start + timedelta(hours=5), 'reviewer_accept': test_start + timedelta(hours=6), } task = TaskFactory( project=test_case.projects['empty_project'], status=Task.Status.AWAITING_PROCESSING, step=test_case.test_step, start_datetime=times['awaiting_pickup']) workers = { 'entry': test_case.workers[0], 'reviewer': test_case.workers[1] } assign_task(workers['entry'].id, task.id) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PROCESSING) submit_task( task.id, {'test': 'entry_submit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PENDING_REVIEW) assign_task(workers['reviewer'].id, task.id) reviewer_assignment = task.assignments.get( worker=workers['reviewer']) # Modify assignment with correct datetime reviewer_assignment.start_datetime = times['reviewer_pickup'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) submit_task( task.id, {'test': 'reviewer_reject'}, Iteration.Status.PROVIDED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING) submit_task( task.id, {'test': 'entry_resubmit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): submit_task( task.id, {'test': 'reviewer_accept'}, Iteration.Status.REQUESTED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.COMPLETE) test_case.assertEquals(task.assignments.count(), 2) for assignment in task.assignments.all(): test_case.assertEquals( assignment.status, TaskAssignment.Status.SUBMITTED) test_case.assertEquals(assignment.iterations.count(), 2) # Modify assignments with correct datetime new_datetime_labels = ('entry_pickup', 'reviewer_pickup') for i, assignment in enumerate(assignment_history(task).all()): assignment.start_datetime = times[new_datetime_labels[i]] assignment.save() # Modify iterations with correct datetime new_datetime_labels = ( ('entry_pickup', 'entry_submit'), ('reviewer_pickup', 'reviewer_reject'), ('reviewer_reject', 'entry_resubmit'), ('entry_resubmit', 'reviewer_accept') ) new_datetimes = [ (times[start_label], times[end_label]) for start_label, end_label in new_datetime_labels] for i, iteration in enumerate(get_iteration_history(task)): iteration.start_datetime, iteration.end_datetime = new_datetimes[i] iteration.save() verify_iterations(task.id) return task