class TestBasicWorkflowActions(TestCase): """ Tests for basic workflow actions """ def setUp(self): super(TestBasicWorkflowActions, self).setUp() self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.random_objects = self.object_generator.generate_random_objects() self.create_test_cases() def tearDown(self): pass def test_create_workflows(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) self.assertIsInstance(wflow, Workflow) task_groups = db.session.query(TaskGroup)\ .filter(TaskGroup.workflow_id == wflow.id).all() self.assertEqual(len(task_groups), len(self.one_time_workflow_1["task_groups"])) def test_workflows(self): for workflow in self.all_workflows: _, wflow = self.generator.generate_workflow(workflow) self.assertIsInstance(wflow, Workflow) task_groups = db.session.query(TaskGroup)\ .filter(TaskGroup.workflow_id == wflow.id).all() self.assertEqual(len(task_groups), len(workflow["task_groups"])) def test_activate_wf(self): for workflow in self.all_workflows: _, wflow = self.generator.generate_workflow(workflow) response, wflow = self.generator.activate_workflow(wflow) self.assert200(response) def test_one_time_workflow_edits(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) wf_dict = {"title": "modified one time wf"} self.generator.modify_workflow(wflow, data=wf_dict) modified_wf = db.session.query(Workflow).filter( Workflow.id == wflow.id).one() self.assertEqual(wf_dict["title"], modified_wf.title) def test_one_time_wf_activate(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) self.generator.generate_cycle(wflow) self.generator.activate_workflow(wflow) tasks = [ len(tg.get("task_group_tasks", [])) for tg in self.one_time_workflow_1["task_groups"] ] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wflow.id).all() active_wf = db.session.query(Workflow).filter( Workflow.id == wflow.id).one() self.assertEqual(sum(tasks), len(cycle_tasks)) self.assertEqual(active_wf.status, "Active") def test_one_time_wf_state_transition_dates(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) self.generator.generate_cycle(wflow) self.generator.activate_workflow(wflow) cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wflow.id).all() with freeze_time("2015-6-9"): today = dtm.date.today() transitions = [ ("In Progress", None, None), ("Finished", today, None), ("Declined", None, None), ("Finished", today, None), ("Verified", today, today), ("Finished", today, None), ] # Iterate over possible transitions and check if dates got set correctly for (status, expected_finished, expected_verified) in transitions: cycle_task = cycle_tasks[0] _, task = self.generator.modify_object(cycle_task, {"status": status}) self.assertEqual(task.finished_date, expected_finished) self.assertEqual(task.verified_date, expected_verified) def test_delete_calls(self): _, workflow = self.generator.generate_workflow() self.generator.generate_task_group(workflow) _, task_group = self.generator.generate_task_group(workflow) task_groups = db.session.query(TaskGroup).filter( TaskGroup.workflow_id == workflow.id).all() self.assertEqual(len(task_groups), 2) response = self.generator.api.delete(task_group) self.assert200(response) task_groups = db.session.query(TaskGroup).filter( TaskGroup.workflow_id == workflow.id).all() self.assertEqual(len(task_groups), 1) def create_test_cases(self): self.quarterly_wf_1 = { "title": "quarterly wf 1", "description": "", "unit": "month", "repeat_every": 3, "task_groups": [ { "title": "tg_1", "task_group_tasks": [ { "description": factories.random_str(100), }, { "description": factories.random_str(100), }, { "description": factories.random_str(100), }, ], }, ] } self.weekly_wf_1 = { "title": "weekly thingy", "description": "start this many a time", "unit": "week", "repeat_every": 1, "task_groups": [ { "title": "tg_2", "task_group_tasks": [ { "description": factories.random_str(100), }, { "title": "monday task", }, { "title": "weekend task", }, ], "task_group_objects": self.random_objects }, ] } self.one_time_workflow_1 = { "title": "one time wf test", "description": "some test workflow", "is_verification_needed": True, "task_groups": [{ "title": "tg_1", "task_group_tasks": [{}, {}, {}] }, { "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100) }, {}], "task_group_objects": self.random_objects[:2] }, { "title": "tg_3", "task_group_tasks": [{ "title": "simple task 1", "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }], "task_group_objects": self.random_objects }] } self.one_time_workflow_2 = { "title": "test_wf_title", "description": "some test workflow", "task_groups": [{ "title": "tg_1", "task_group_tasks": [{}, {}, {}] }, { "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100) }, {}], "task_group_objects": self.random_objects[:2] }, { "title": "tg_3", "task_group_tasks": [{ "title": "simple task 1", "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }], "task_group_objects": [] }] } self.monthly_workflow_1 = { "title": "monthly test wf", "description": "start this many a time", "unit": "month", "repeat_every": 1, "task_groups": [ { "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100), }, { "title": "monday task", }, { "title": "weekend task", }], "task_group_objects": self.random_objects }, ] } self.all_workflows = [ self.one_time_workflow_1, self.one_time_workflow_2, self.weekly_wf_1, self.monthly_workflow_1, self.quarterly_wf_1, ]
class TestWorkflowCycleStatePropagation(TestCase): """Test case for cycle task to cycle task group status propagation""" def setUp(self): super(TestWorkflowCycleStatePropagation, self).setUp() self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.weekly_wf = { "title": "weekly thingy", "description": "start this many a time", "unit": "week", "repeat_every": 1, "task_groups": [{ "title": "weekly task group", "task_group_tasks": [{ "title": "weekly task 1", "start_date": dtm.date(2016, 6, 10), "end_date": dtm.date(2016, 6, 13), }, { "title": "weekly task 1", "start_date": dtm.date(2016, 6, 10), "end_date": dtm.date(2016, 6, 13), }]}, ] } def test_weekly_state_transitions_assigned_inprogress(self): """Test that starting one cycle task changes cycle task group""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] self.assertEqual(ctg.status, "Assigned") cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks for cycle_task in cycle_tasks: self.assertEqual(cycle_task.status, "Assigned") # Move one task to In Progress _, first_ct = self.generator.modify_object( first_ct, {"status": "In Progress"}) self.assertEqual(first_ct.status, "In Progress") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "In Progress") # Undo operation _, first_ct = self.generator.modify_object( first_ct, {"status": "Assigned"}) self.assertEqual(first_ct.status, "Assigned") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Assigned") # Move both to in progress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "In Progress"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "In Progress") # Undo one cycle task _, first_ct = self.generator.modify_object( first_ct, {"status": "Assigned"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "In Progress") self.assertEqual(ctg.status, "In Progress") # Undo second cycle task _, second_ct = self.generator.modify_object( second_ct, {"status": "Assigned"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "Assigned") self.assertEqual(ctg.status, "Assigned") def test_weekly_state_transitions_inprogress_finished(self): """Test In Progress to Finished transitions""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to In Progress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "In Progress"}) # Test that moving one task to finished doesn't finish entire cycle _, first_ct = self.generator.modify_object( first_ct, {"status": "Finished"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Finished") self.assertEqual(second_ct.status, "In Progress") self.assertEqual(ctg.status, "In Progress") # Test moving second task to Finished - entire cycle should be finished _, second_ct = self.generator.modify_object( second_ct, {"status": "Finished"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(second_ct.status, "Finished") self.assertEqual(first_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Undo one task, cycle should be In Progress _, first_ct = self.generator.modify_object( first_ct, {"status": "In Progress"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "In Progress") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "In Progress") def test_weekly_state_transitions_finished_verified(self): """Test Finished to Verified transitions""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to In Progress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "In Progress"}) self.generator.modify_object( cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") for cycle_task in cycle_tasks: cycle_task = db.session.query(CycleTaskGroupObjectTask).get( cycle_task.id) self.assertEqual(cycle_task.status, "Finished") # Verify first CT _, first_ct = self.generator.modify_object( first_ct, {"status": "Verified"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Verify second CT _, second_ct = self.generator.modify_object( second_ct, {"status": "Verified"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Verified") self.assertEqual(ctg.status, "Verified") def test_weekly_state_transitions_finished_declined(self): """Test Finished to Declined transitions""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to In Progress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "In Progress"}) self.generator.modify_object( cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") # Decline first CT _, first_ct = self.generator.modify_object( first_ct, {"status": "Declined"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Declined") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "In Progress") def test_deleted_task_state_transitions(self): """Test In Progress to Finished transition after task is deleted""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] first_ct, second_ct = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() # Move first task to In Progress self.generator.modify_object(first_ct, {"status": "In Progress"}) self.generator.modify_object(first_ct, {"status": "Finished"}) # Delete second task response = self.generator.api.delete(second_ct) self.assert200(response) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") def test_cycle_change_on_ct_status_transition(self): """Test cycle is_current change on task Finished to In Progress transition """ with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query( CycleTaskGroup ).join( Cycle ).join( Workflow ).filter( Workflow.id == wf.id ).one() c_id = ctg.cycle.id first_ct, second_ct = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() self.api.put(first_ct, {"status": "Verified"}) self.api.put(second_ct, {"status": "Verified"}) # cycle now should have is_current == False cycle = db.session.query(Cycle).get(c_id) self.assertEqual(cycle.is_current, False) # Move second task back to In Progress self.api.put(second_ct, {"status": "In Progress"}) # cycle now should have is_current == True cycle = db.session.query(Cycle).get(ctg.cycle.id) self.assertEqual(cycle.is_current, True) @staticmethod def _get_obj(model, title): return db.session.query(model).filter(model.title == title).first() def test_cycle_task_group_dates(self): """Test status and dates when task status is changed """ wf_data = { "title": "test workflow", "unit": "week", "repeat_every": 1, "task_groups": [{ "title": "test group", "task_group_tasks": [{ "title": "task1", "start_date": dtm.date(2016, 6, 10), "end_date": dtm.date(2016, 6, 14), }, { "title": "task2", "start_date": dtm.date(2016, 6, 13), "end_date": dtm.date(2016, 6, 15), }] }] } with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(wf_data) self.generator.activate_workflow(wf) # check task group status and dates tg = self._get_obj(CycleTaskGroup, "test group") self.assertEqual(tg.end_date, dtm.date(2016, 6, 15)) self.assertEqual(tg.next_due_date, dtm.date(2016, 6, 14)) self.assertEqual(tg.status, "Assigned") # move task1 to Verified task1 = self._get_obj(CycleTaskGroupObjectTask, "task1") self.api.put(task1, {"status": "In Progress"}) self.api.put(task1, {"status": "Finished"}) self.api.put(task1, {"status": "Verified"}) # # check task group status and dates tg = self._get_obj(CycleTaskGroup, "test group") self.assertEqual(tg.end_date, dtm.date(2016, 6, 15)) self.assertEqual(tg.next_due_date, dtm.date(2016, 6, 15)) self.assertEqual(tg.status, "In Progress") # move task2 to Verified task2 = self._get_obj(CycleTaskGroupObjectTask, "task2") self.api.put(task2, {"status": "In Progress"}) self.api.put(task2, {"status": "Finished"}) self.api.put(task2, {"status": "Verified"}) # # check task group status and dates tg = self._get_obj(CycleTaskGroup, "test group") self.assertIsNone(tg.next_due_date) self.assertEqual(tg.status, "Verified") def test_empty_group_status(self): """Test status and dates when task group is empty """ wf_data = { "title": "test workflow", "unit": "week", "repeat_every": 1, "task_groups": [{ "title": "test group1", "task_group_tasks": [{ "title": "task1", "start_date": dtm.date(2016, 6, 10), "end_date": dtm.date(2016, 6, 13), }] }, { # second task group prevents from moving workflow to history "title": "test group2", "task_group_tasks": [{ "title": "task2", "start_date": dtm.date(2016, 6, 14), "end_date": dtm.date(2016, 6, 16), }] }] } with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(wf_data) self.generator.activate_workflow(wf) # check task group status and dates tg = self._get_obj(CycleTaskGroup, "test group1") self.assertEqual(tg.end_date, dtm.date(2016, 6, 13)) self.assertEqual(tg.next_due_date, dtm.date(2016, 6, 13)) self.assertEqual(tg.status, "Assigned") # move task2 to Verified task2 = self._get_obj(CycleTaskGroupObjectTask, "task2") self.api.put(task2, {"status": "In Progress"}) self.api.put(task2, {"status": "Finished"}) self.api.put(task2, {"status": "Verified"}) # check task group status cycle = self._get_obj(Cycle, "test workflow") self.assertEqual(cycle.status, "In Progress") # delete task1 task = self._get_obj(CycleTaskGroupObjectTask, "task1") self.api.delete(task) # # check task group status and dates tg = self._get_obj(CycleTaskGroup, "test group1") self.assertIsNone(tg.end_date) self.assertIsNone(tg.next_due_date) self.assertEqual(tg.status, "Deprecated") # # check cycle status cycle = self._get_obj(Cycle, "test workflow") self.assertEqual(cycle.status, "Verified")
class TestCycleTaskImportUpdate(BaseTestCycleTaskImportUpdate): """ This class contains simple cycle task update tests using import functionality """ CSV_DIR = join(abspath(dirname(__file__)), "test_csvs/") def setUp(self): super(TestCycleTaskImportUpdate, self).setUp() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.random_objects = self.object_generator.generate_random_objects(2) _, self.person_1 = self.object_generator.generate_person( user_role="Administrator") self.ftime_active = "2016-07-01" self.ftime_historical = "2014-05-01" self._create_test_cases_data() # It is needed because cycle-tasks are generated automatically with # 'slug' based on auto_increment 'id' field. # At start of each test we suppose that created cycle-task's 'slug' # lie in range from 1 to 10. db.session.execute('ALTER TABLE cycle_task_group_object_tasks ' 'AUTO_INCREMENT = 1') def test_cycle_task_correct(self): """Test cycle task update via import with correct data""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_correct.csv") self._check_csv_response(response, {}) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_warnings(self): """Test cycle task update via import with data which is the reason of warnings about non-importable columns.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_warnings.csv") self._check_csv_response(response, self.expected_warnings) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_create_error(self): """Test cycle task update via import with data which is the reason of errors about new cycle task creation.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_create_error.csv") self._check_csv_response(response, self.expected_create_error) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_date_error(self): """Test cycle task update via import with data which is the reason of errors about incorrect dates in csv file.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_date_error.csv") self._check_csv_response(response, self.expected_date_error) self._cmp_tasks(self.expected_cycle_task_date_error) def test_cycle_task_permission_error(self): """Test cycle task update via import with non-admin user which is the reason of error. Only admin can update cycle tasks via import.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): _, creator = self.object_generator.generate_person(user_role="Creator") response = self.import_file("cycle_task_correct.csv", person=creator) self._check_csv_response(response, self.expected_permission_error) # Cycle tasks' data shouldn't be changed in test DB after import run from # non-admin user expected_cycle_task_permission_error = {} expected_cycle_task_permission_error.update( self.generated_cycle_tasks_active) expected_cycle_task_permission_error.update( self.generated_cycle_tasks_historical) self._cmp_tasks(expected_cycle_task_permission_error) def _cmp_tasks(self, expected_ctasks): """Compare tasks values from argument's list and test DB.""" for ctask in db.session.query(CycleTaskGroupObjectTask).all(): if ctask.slug not in expected_ctasks: continue exp_task = expected_ctasks[ctask.slug] for attr, val in exp_task.iteritems(): self.assertEqual(str(getattr(ctask, attr, None)), val) # pylint: disable=too-many-arguments def _activate_workflow(self, ftime, workflow, task_group, task_group_tasks, random_object, cycle_tasks): """Helper which is responsible for active cycle-tasks creation""" with freeze_time(ftime): _, wf = self.wf_generator.generate_workflow(workflow) _, tg = self.wf_generator.generate_task_group(wf, task_group) for task in task_group_tasks: self.wf_generator.generate_task_group_task(tg, task) self.wf_generator.generate_task_group_object(tg, random_object) _, cycle = self.wf_generator.generate_cycle(wf) self.wf_generator.activate_workflow(wf) for exp_slug, exp_task in cycle_tasks.iteritems(): obj = db.session.query(CycleTaskGroupObjectTask).filter_by( slug=exp_slug ).first() if exp_task["status"] == "Verified": self.wf_generator.modify_object(obj, {"status": "Finished"}) self.wf_generator.modify_object(obj, {"status": exp_task["status"]}) self._cmp_tasks(cycle_tasks) return cycle def _generate_cycle_tasks(self): """Helper which is responsible for test data creation""" self._activate_workflow(self.ftime_active, self.workflow_active, self.task_group_active, self.task_group_tasks_active, self.random_objects[0], self.generated_cycle_tasks_active) cycle = self._activate_workflow(self.ftime_historical, self.workflow_historical, self.task_group_historical, self.task_group_tasks_historical, self.random_objects[1], self.generated_cycle_tasks_historical) with freeze_time(self.ftime_historical): cycle = Cycle.query.get(cycle.id) self.wf_generator.modify_object(cycle, data={"is_current": False}) def _create_test_cases_data(self): """Create test cases data: for object generation, expected data for checks""" def person_dict(person_id): """Return person data""" return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } self.workflow_active = { "title": "workflow active title", "description": "workflow active description", "frequency": "one_time", "owners": [person_dict(self.person_1.id)], "notify_on_change": False, } self.task_group_active = { "title": "task group active title", "contact": person_dict(self.person_1.id), } self.task_group_tasks_active = [{ "title": "task active title 1", "description": "task active description 1", "contact": person_dict(self.person_1.id), "start_date": "07/01/2016", "end_date": "07/06/2016", }, { "title": "task active title 2", "description": "task active description 2", "contact": person_dict(self.person_1.id), "start_date": "07/07/2016", "end_date": "07/12/2016", }, { "title": "task active title 3", "description": "task active description 3", "contact": person_dict(self.person_1.id), "start_date": "07/13/2016", "end_date": "07/18/2016", }, { "title": "task active title 4", "description": "task active description 4", "contact": person_dict(self.person_1.id), "start_date": "07/19/2016", "end_date": "07/24/2016", }, { "title": "task active title 5", "description": "task active description 5", "contact": person_dict(self.person_1.id), "start_date": "07/25/2016", "end_date": "07/30/2016", }] # Active cycle tasks which should be generated from previous structure # at the beginning of each test self.generated_cycle_tasks_active = { "CYCLETASK-1": { "title": self.task_group_tasks_active[0]["title"], "description": self.task_group_tasks_active[0]["description"], "start_date": "2016-07-01", "end_date": "2016-07-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-2": { "title": self.task_group_tasks_active[1]["title"], "description": self.task_group_tasks_active[1]["description"], "start_date": "2016-07-07", "end_date": "2016-07-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-3": { "title": self.task_group_tasks_active[2]["title"], "description": self.task_group_tasks_active[2]["description"], "start_date": "2016-07-13", "end_date": "2016-07-18", "finished_date": "None", "verified_date": "None", "status": "InProgress" }, "CYCLETASK-4": { "title": self.task_group_tasks_active[3]["title"], "description": self.task_group_tasks_active[3]["description"], "start_date": "2016-07-19", "end_date": "2016-07-24", "finished_date": "2016-07-01 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-5": { "title": self.task_group_tasks_active[4]["title"], "description": self.task_group_tasks_active[4]["description"], "start_date": "2016-07-25", "end_date": "2016-07-30", "finished_date": "2016-07-01 00:00:00", "verified_date": "2016-07-01 00:00:00", "status": "Verified" } } self.workflow_historical = { "title": "workflow historical title", "description": "workflow historical description", "frequency": "one_time", "owners": [person_dict(self.person_1.id)], "notify_on_change": False, } self.task_group_historical = { "title": "task group historical title", "contact": person_dict(self.person_1.id), } self.task_group_tasks_historical = [{ "title": "task historical title 1", "description": "task historical description 1", "contact": person_dict(self.person_1.id), "start_date": "05/01/2014", "end_date": "05/06/2014", }, { "title": "task historical title 2", "description": "task historical description 2", "contact": person_dict(self.person_1.id), "start_date": "05/07/2014", "end_date": "05/12/2014", }, { "title": "task historical title 3", "description": "task historical description 3", "contact": person_dict(self.person_1.id), "start_date": "05/13/2014", "end_date": "05/18/2014", }, { "title": "task historical title 4", "description": "task historical description 4", "contact": person_dict(self.person_1.id), "start_date": "05/19/2014", "end_date": "05/24/2014", }, { "title": "task historical title 5", "description": "task historical description 5", "contact": person_dict(self.person_1.id), "start_date": "05/25/2014", "end_date": "05/30/2014", }, ] # Historical cycle tasks which should be generated from previous structure # at the beginning of each test. self.generated_cycle_tasks_historical = { "CYCLETASK-6": { "title": self.task_group_tasks_historical[0]["title"], "description": self.task_group_tasks_historical[0]["description"], "start_date": "2014-05-01", "end_date": "2014-05-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-7": { "title": self.task_group_tasks_historical[1]["title"], "description": self.task_group_tasks_historical[1]["description"], "start_date": "2014-05-07", "end_date": "2014-05-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-8": { "title": self.task_group_tasks_historical[2]["title"], "description": self.task_group_tasks_historical[2]["description"], "start_date": "2014-05-13", "end_date": "2014-05-18", "finished_date": "None", "verified_date": "None", "status": "InProgress" }, "CYCLETASK-9": { "title": self.task_group_tasks_historical[3]["title"], "description": self.task_group_tasks_historical[3]["description"], "start_date": "2014-05-19", "end_date": "2014-05-24", "finished_date": "2014-05-01 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-10": { "title": self.task_group_tasks_historical[4]["title"], "description": self.task_group_tasks_historical[4]["description"], "start_date": "2014-05-25", "end_date": "2014-05-30", "finished_date": "2014-05-01 00:00:00", "verified_date": "2014-05-01 00:00:00", "status": "Verified" } } # Expected cycle tasks which should be created in correct cycle task update # case. It is needed for most tests. self.expected_cycle_task_correct = { "CYCLETASK-1": { "title": self.task_group_tasks_active[0]["title"] + " one", "description": self.task_group_tasks_active[0]["description"] + " one", "start_date": "2016-06-01", "end_date": "2016-06-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-2": { "title": self.task_group_tasks_active[1]["title"] + " two", "description": self.task_group_tasks_active[1]["description"] + " two", "start_date": "2016-06-07", "end_date": "2016-06-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-3": { "title": self.task_group_tasks_active[2]["title"] + " three", "description": self.task_group_tasks_active[2]["description"] + " three", "start_date": "2016-06-13", "end_date": "2016-06-18", "finished_date": "None", "verified_date": "None", "status": "InProgress" }, "CYCLETASK-4": { "title": self.task_group_tasks_active[3]["title"] + " four", "description": self.task_group_tasks_active[3]["description"] + " four", "start_date": "2016-06-19", "end_date": "2016-06-24", "finished_date": "2016-07-19 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-5": { "title": self.task_group_tasks_active[4]["title"] + " five", "description": self.task_group_tasks_active[4]["description"] + " five", "start_date": "2016-06-25", "end_date": "2016-06-30", "finished_date": "2016-07-25 00:00:00", "verified_date": "2016-08-30 00:00:00", "status": "Verified" }, "CYCLETASK-6": { "title": self.task_group_tasks_historical[0]["title"] + " one", "description": self.task_group_tasks_historical[0]["description"] + " one", "start_date": "2014-04-01", "end_date": "2014-04-06", "finished_date": "2014-05-01 00:00:00", "verified_date": "2014-06-06 00:00:00", "status": "Assigned" }, "CYCLETASK-7": { "title": self.task_group_tasks_historical[1]["title"] + " two", "description": self.task_group_tasks_historical[1]["description"] + " two", "start_date": "2014-04-07", "end_date": "2014-04-12", "finished_date": "2014-05-07 00:00:00", "verified_date": "None", "status": "Declined" }, "CYCLETASK-8": { "title": self.task_group_tasks_historical[2]["title"] + " three", "description": self.task_group_tasks_historical[2]["description"] + " three", "start_date": "2014-04-13", "end_date": "2014-04-18", "finished_date": "2014-05-13 00:00:00", "verified_date": "2014-06-18 00:00:00", "status": "InProgress" }, "CYCLETASK-9": { "title": self.task_group_tasks_historical[3]["title"] + " four", "description": self.task_group_tasks_historical[3]["description"] + " four", "start_date": "2014-04-19", "end_date": "2014-04-24", "finished_date": "2014-05-19 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-10": { "title": self.task_group_tasks_historical[4]["title"] + " five", "description": self.task_group_tasks_historical[4]["description"] + " five", "start_date": "2014-04-25", "end_date": "2014-04-30", "finished_date": "2014-05-25 00:00:00", "verified_date": "2014-06-30 00:00:00", "status": "Verified" } } # Below is description of warning for non-importable columns. It is needed # for test_cycle_task_warnings. importable_column_names = [] for field_name in CycleTaskGroupObjectTask.IMPORTABLE_FIELDS: if field_name == 'slug': continue # pylint: disable=protected-access name = CycleTaskGroupObjectTask._aliases.get(field_name, field_name) if isinstance(name, dict): name = name['display_name'] importable_column_names.append(name) self.expected_warnings = self.generate_expected_warning( *importable_column_names) # This is an error message which should be shown during # test_cycle_task_create_error test self.expected_create_error = { 'Cycle Task': { 'row_errors': {errors.CREATE_INSTANCE_ERROR.format(line=13)} } } # Below is expected date errors for test_cycle_task_date_error. They should # be shown during date validator's tests. self.expected_date_error = { 'Cycle Task': { 'row_errors': { errors.INVALID_START_END_DATES.format( line=3, start_date="Start Date", end_date="End Date", ), errors.INVALID_STATUS_DATE_CORRELATION.format( line=4, date="Actual Finish Date", status="not Finished", ), errors.INVALID_STATUS_DATE_CORRELATION.format( line=5, date="Actual Verified Date", status="not Verified", ), errors.INVALID_STATUS_DATE_CORRELATION.format( line=6, date="Actual Verified Date", status="not Verified", ), errors.INVALID_START_END_DATES.format( line=7, start_date="Actual Finish Date", end_date="Actual Verified Date", ), errors.INVALID_START_END_DATES.format( line=8, start_date="Start Date", end_date="End Date", ), errors.MISSING_VALUE_ERROR.format( line=9, column_name="Actual Finish Date", ), errors.INVALID_START_END_DATES.format( line=10, start_date="Actual Finish Date", end_date="Actual Verified Date", ), }, } } # Below is expected cycle-tasks data which should appear in test DB after # test_cycle_task_date_error run self.expected_cycle_task_date_error = dict() self.expected_cycle_task_date_error.update( self.generated_cycle_tasks_active) self.expected_cycle_task_date_error.update( self.generated_cycle_tasks_historical) self.expected_cycle_task_date_error["CYCLETASK-9"] = ( self.expected_cycle_task_correct["CYCLETASK-9"]) self.expected_cycle_task_date_error["CYCLETASK-10"] = ( self.expected_cycle_task_correct["CYCLETASK-10"]) # Expected error message which should be shown after # test_cycle_task_permission_error run self.expected_permission_error = { 'Cycle Task': { 'block_errors': {errors.PERMISSION_ERROR.format(line=2)} } }
class TestCycleTaskStatusChange(TestCase): """ This class contains simple one time workflow tests that are not in the gsheet test grid """ def setUp(self): super(TestCycleTaskStatusChange, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() all_models.Notification.query.delete() self.random_objects = self.object_generator.generate_random_objects(2) self.user = self.create_user_with_role(role="Administrator") self.secondary_assignee = self.create_user_with_role(role="Reader") self.create_test_cases() def init_decorator(init): def new_init(self, *args, **kwargs): init(self, *args, **kwargs) if hasattr(self, "created_at"): self.created_at = datetime.now() return new_init all_models.Notification.__init__ = init_decorator( all_models.Notification.__init__) def test_task_declined_notification_created(self): """Test declined cycle task notifications""" with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Declined") notif = self.get_notifications_by_type(task1, "cycle_task_declined") self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) def test_all_tasks_finished_notification_created(self): """Test all task completed notifications""" with freeze_time("2015-05-01 13:20:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) generate_cycle_tasks_notifs() notif = self.get_notifications_by_type( cycle, "all_cycle_tasks_completed") self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) notif = self.get_notifications_by_type( task1, "all_cycle_tasks_completed") self.assertEqual(notif, []) def test_multi_all_tasks_finished_notification_created(self): """Test several all task completed notifications""" with freeze_time("2015-05-01 13:20:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_2) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) generate_cycle_tasks_notifs() notif = self.get_notifications_by_type( cycle, "all_cycle_tasks_completed") # there is still one task in the cycle, so there should be no # notifications for all tasks completed self.assertEqual(notif, []) notif = self.get_notifications_by_type( task1, "all_cycle_tasks_completed") # The task was verified, so there should be no notifications left for due # dates. self.assertEqual(notif, []) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.task_change_status(task2) generate_cycle_tasks_notifs() notif = self.get_notifications_by_type( cycle, "all_cycle_tasks_completed") self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) @patch("ggrc.notifications.common.send_email") def test_single_task_declined(self, mock_mail): """Test moving the end date to the future on declined task. It is done before due_in and due_today notifications have been sent. """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Finished") _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) task_assignees = [self.user, self.secondary_assignee] with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Declined") _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn(user.email, notif_data) self.assertIn("task_declined", notif_data[user.email]) @patch("ggrc.notifications.common.send_email") def test_single_task_accepted(self, mock_mail): """Test moving the end date to the future on accepted task. It is done before due_in and due_today notifications have been sent. """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Finished") _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) task_assignees = [self.user, self.secondary_assignee] with freeze_time("2015-05-03 13:20:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) generate_cycle_tasks_notifs() _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertNotIn(user.email, notif_data) self.assertIn("all_tasks_completed", notif_data["*****@*****.**"]) @patch("ggrc.notifications.common.send_email") def test_end_cycle(self, mock_mail): """Manually ending cycle should stop all notifications for that cycle.""" with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) task_assignees = [self.user, self.secondary_assignee] with freeze_time("2015-05-03"): _, notif_data = common.get_daily_notifications() cycle = Cycle.query.get(cycle.id) for user in task_assignees: self.assertIn(user.email, notif_data) self.wf_generator.modify_object(cycle, data={"is_current": False}) cycle = Cycle.query.get(cycle.id) self.assertFalse(cycle.is_current) _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertNotIn(user.email, notif_data) def create_test_cases(self): def person_dict(person_id): return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } task_assignee_role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id task_secondary_assignee = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Secondary Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id self.one_time_workflow_1 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "is_verification_needed": True, # admin will be current user with id == 1 "task_groups": [{ "title": "single task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "single task in a wf", "access_control_list": [ acl_helper.get_acl_json(task_assignee_role_id, self.user.id), acl_helper.get_acl_json(task_secondary_assignee, self.secondary_assignee.id), ], "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }], }] } self.one_time_workflow_2 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "is_verification_needed": True, # admin will be current user with id == 1 "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [ { "title": "task 1", "description": "two taks in wf with different objects", "access_control_list": [ acl_helper.get_acl_json(task_assignee_role_id, self.user.id) ], "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }, { "title": "task 2", "description": "two taks in wf with different objects", "access_control_list": [ acl_helper.get_acl_json(task_assignee_role_id, self.user.id) ], "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), } ], "task_group_objects": self.random_objects }] } def get_notification_type(self, name): return all_models.NotificationType.query.filter( all_models.NotificationType.name == name).one() def task_change_status(self, task, status="Verified"): self.wf_generator.modify_object(task, data={"status": status}) task = CycleTaskGroupObjectTask.query.get(task.id) self.assertEqual(task.status, status) def get_notifications_by_type(self, obj, notif_type): return all_models.Notification.query.filter( and_( all_models.Notification.object_id == obj.id, all_models.Notification.object_type == obj.type, all_models.Notification.sent_at == None, # noqa all_models.Notification.notification_type == self.get_notification_type(notif_type))).all()
class TestPersonResource(TestCase, WithQueryApi): """Tests for special people api endpoints.""" def setUp(self): super(TestPersonResource, self).setUp() self.client.get("/login") self.api = Api() self.generator = WorkflowsGenerator() @staticmethod def _create_users_names_rbac(users): """Create name and Creator role for users, created vid PersonFactory""" if not users: return roles = {r.name: r for r in all_models.Role.query.all()} for user in users: user.name = user.email.split("@")[0] rbac_factories.UserRoleFactory(role=roles["Creator"], person=user) def assert_profile_get_successful(self, response, expected_datetime): """Verify assertions for successful GET profile method""" self.assert200(response) response_datetime = date_parser.parse(response.json["last_seen_whats_new"]) self.assertEqual(expected_datetime, response_datetime) @freeze_time("2018-05-20 12:23:17") def test_profile_get_successful(self): """Test person_profile GET method successfully achieves correct data""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) response = self.api.client.get("/api/people/{}/profile".format(user.id)) self.assert_profile_get_successful(response, default_date()) def test_profile_get_no_profile(self): """Test person_profile GET method achieves data with missing profile""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) profiles_table = PersonProfile.__table__ db_request = profiles_table.delete().where( profiles_table.c.person_id == user.id) db.engine.execute(db_request) with freeze_time("2018-05-28 23:30:10"): response = self.api.client.get("/api/people/{}/profile".format(user.id)) self.assert_profile_get_successful(response, default_date()) def test_profile_get_failed(self): """Test person_profiles GET method fails Now only logged user can request his profile """ with factories.single_commit(): valid_user = factories.PersonFactory() self._create_users_names_rbac([valid_user]) response = self.client.get( "/api/people/{}/profile".format(valid_user.id)) # logged with default user during setUp self.assert403(response) response = self.api.client.get( "/api/people/{}/profile".format(valid_user.id)) # not authorized user self.assert403(response) def assert_profile_put_successful(self, response, correct_response, user, new_date): """Verify assertions for successful PUT profile method""" self.assert200(response) self.assertEqual(response.json, correct_response) profile = PersonProfile.query.filter_by(person_id=user.id).first() self.assertEqual(profile.last_seen_whats_new, date_parser.parse(new_date)) def test_profile_put_successful(self): """Test person_profile PUT method for setting data and correct response""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) new_date = "2018-05-20 16:38:17" data = {"last_seen_whats_new": new_date} correct_response = {"Person": {"id": user.id, "profile": data}} response = self.api.client.put("/api/people/{}/profile".format(user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) self.assert_profile_put_successful(response, correct_response, user, new_date) def test_profile_put_no_profile(self): """Test person_profile PUT method for setting data for missing profile""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) new_date = "2018-05-20 22:05:17" data = {"last_seen_whats_new": new_date} correct_response = {"Person": {"id": user.id, "profile": data}} profiles_table = PersonProfile.__table__ db_request = profiles_table.delete().where( profiles_table.c.person_id == user.id) db.engine.execute(db_request) response = self.api.client.put("/api/people/{}/profile".format(user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) self.assert_profile_put_successful(response, correct_response, user, new_date) def test_profile_put_unauthorized(self): """Test person_profiles PUT method fails for unauthorized user""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) new_date = "2018-05-20 22:05:17" data = {"last_seen_whats_new": new_date} response = self.client.put("/api/people/{}/profile".format(user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) # logged with default user during setUp self.assert403(response) response = self.api.client.put("/api/people/{}/profile".format(user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) # not authorized user self.assert403(response) @ddt.data({"last_seen_whats_new": "NOT A 123 DAT456A"}, {"other_key": "2018-05-20 22:05:17", "one_more_key": 42}) def test_profile_put_corrupted_data(self, data): """Test person_profiles PUT method fails via incorrect request data If request doesn't have "last_seen_whats_new" key or date is incorrect, response is code 400 "Bad Request" """ with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) response = self.api.client.put("/api/people/{}/profile".format(user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) # missed key in request self.assert400(response) def test_task_count_empty(self): """Test query count without any workflows and tasks.""" user = all_models.Person.query.first() response = self.client.get("/api/people/{}/task_count".format(user.id)) self.assertEqual( response.json, {"open_task_count": 0, "has_overdue": False} ) @ddt.data( (True, [ ("task 1", "Finished", 3, True, 3), ("task 1", "Verified", 2, True, 3), ("task 2", "Declined", 2, True, 3), ("task 2", "Verified", 1, False, 3), ("task 2", "Finished", 2, True, 3), ("task 3", "Verified", 1, True, 3), ("task 2", "Verified", 0, False, 3), ]), (False, [ ("task 1", "Finished", 2, True, 3), ("task 2", "In Progress", 2, True, 3), ("task 2", "Finished", 1, False, 3), ("task 3", "Finished", 0, False, 3), ]), ) @ddt.unpack def test_task_count(self, is_verification_needed, transitions): """Test person task counts. This tests checks for correct task counts - with inactive workflows and - with overdue tasks - without overdue tasks - with finished overdue tasks The four checks are done in a single test due to complex differences between tests that make ddt cumbersome and the single test also improves integration test performance due to slow workflow setup stage. """ # pylint: disable=too-many-locals user = all_models.Person.query.first() dummy_user = factories.PersonFactory() user_id = user.id role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id secondary_role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Secondary Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id one_time_workflow = { "title": "Person resource test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [create_stub(user)], "is_verification_needed": is_verification_needed, "task_groups": [{ "title": "one time task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "task 1", "description": "some task", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id), acl_helper.get_acl_json(secondary_role_id, user.id) ], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }, { "title": "task 2", "description": "some task 3", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id), acl_helper.get_acl_json(secondary_role_id, user.id), acl_helper.get_acl_json(secondary_role_id, dummy_user.id) ], "start_date": date(2017, 5, 5), "end_date": date(2017, 9, 16), }, { "title": "task 3", "description": "some task 4", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id), acl_helper.get_acl_json(role_id, dummy_user.id) ], "start_date": date(2017, 6, 5), "end_date": date(2017, 10, 16), }, { "title": "dummy task 4", # task should not counted "description": "some task 4", "access_control_list": [ acl_helper.get_acl_json(role_id, dummy_user.id)], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 17), }, { "title": "dummy task 5", # task should not counted "description": "some task 4", "access_control_list": [ acl_helper.get_acl_json(role_id, dummy_user.id)], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 18), }], "task_group_objects": [] }] } inactive_workflow = { "title": "Activated workflow with archived cycles", "notify_on_change": True, "description": "Extra test workflow", "owners": [create_stub(user)], "task_groups": [{ "title": "Extra task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "not counted existing task", "description": "", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id)], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }], "task_group_objects": [] }] } with freeze_time("2017-10-16 05:09:10"): self.client.get("/login") # Activate normal one time workflow _, workflow = self.generator.generate_workflow(one_time_workflow) _, cycle = self.generator.generate_cycle(workflow) tasks = {t.title: t for t in cycle.cycle_task_group_object_tasks} _, workflow = self.generator.activate_workflow(workflow) # Activate and close the inactive workflow _, workflow = self.generator.generate_workflow(inactive_workflow) _, cycle = self.generator.generate_cycle(workflow) _, workflow = self.generator.activate_workflow(workflow) self.generator.modify_object(cycle, data={"is_current": False}) with freeze_time("2017-7-16 07:09:10"): self.client.get("/login") response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 3, "has_overdue": False} ) with freeze_time("2017-10-16 08:09:10"): # same day as task 3 end date self.client.get("/login") response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 3, "has_overdue": True} ) for task, status, count, overdue, my_work_count in transitions: self.generator.modify_object(tasks[task], data={"status": status}) task_count_response = \ self.client.get("/api/people/{}/task_count".format(user_id)) my_work_count_response = \ self.client.get("/api/people/{}/my_work_count".format(user_id)) self.assertEqual( task_count_response.json, {"open_task_count": count, "has_overdue": overdue} ) self.assertEqual( my_work_count_response.json["CycleTaskGroupObjectTask"], my_work_count ) def test_task_count_multiple_wfs(self): """Test task count with both verified and non verified workflows. This checks task counts with 4 tasks 2017, 8, 15 - verification needed 2017, 11, 18 - verification needed 2017, 8, 15 - No verification needed 2017, 11, 18 - No verification needed """ user = all_models.Person.query.first() user_id = user.id role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id workflow_template = { "title": "verified workflow", "owners": [create_stub(user)], "is_verification_needed": True, "task_groups": [{ "title": "one time task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "task 1", "description": "some task", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id)], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }, { "title": "dummy task 5", "description": "some task 4", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id)], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 18), }], "task_group_objects": [] }] } with freeze_time("2017-10-16 05:09:10"): self.client.get("/login") verified_workflow = workflow_template.copy() verified_workflow["is_verification_needed"] = True _, workflow = self.generator.generate_workflow(verified_workflow) _, cycle = self.generator.generate_cycle(workflow) verified_tasks = { task.title: task for task in cycle.cycle_task_group_object_tasks } _, workflow = self.generator.activate_workflow(workflow) non_verified_workflow = workflow_template.copy() non_verified_workflow["is_verification_needed"] = False _, workflow = self.generator.generate_workflow(non_verified_workflow) _, cycle = self.generator.generate_cycle(workflow) non_verified_tasks = { task.title: task for task in cycle.cycle_task_group_object_tasks } _, workflow = self.generator.activate_workflow(workflow) with freeze_time("2017-7-16 07:09:10"): self.client.get("/login") response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 4, "has_overdue": False} ) with freeze_time("2017-10-16 08:09:10"): self.client.get("/login") response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 4, "has_overdue": True} ) # transition 1, task that needs verification goes to finished state. This # transition should not change anything self.generator.modify_object( verified_tasks["task 1"], data={"status": "Finished"} ) response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 4, "has_overdue": True} ) # transition 2, task that needs verification goes to verified state. This # transition should reduce task count. self.generator.modify_object( verified_tasks["task 1"], data={"status": "Verified"} ) response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 3, "has_overdue": True} ) # transition 3, task that does not need verification goes into Finished # state. This transition should reduce task count and remove all overdue # tasks self.generator.modify_object( non_verified_tasks["task 1"], data={"status": "Finished"} ) response = self.client.get("/api/people/{}/task_count".format(user_id)) self.assertEqual( response.json, {"open_task_count": 2, "has_overdue": False} ) @ddt.data(("Creator", 403), ("Reader", 403), ("Editor", 200), ("Administrator", 200)) @ddt.unpack def test_person_editing(self, role_name, status): """{0} should receive {1} status code on edit Person.""" role = all_models.Role.query.filter( all_models.Role.name == role_name ).one() with factories.single_commit(): client_user = factories.PersonFactory() rbac_factories.UserRoleFactory(role=role, person=client_user) self.api.set_user(client_user) self.client.get("/login") base_email = "*****@*****.**" person = factories.PersonFactory(email=base_email) person_id = person.id new_email = "new_{}".format(base_email) resp = self.api.put(person, {"email": new_email}) self.assertEqual(status, resp.status_code) person = all_models.Person.query.get(person_id) if status == 200: self.assertEqual(new_email, person.email) else: self.assertEqual(base_email, person.email)
class TestPersonResource(TestCase, WithQueryApi): """Tests for special people api endpoints.""" def setUp(self): super(TestPersonResource, self).setUp() self.client.get("/login") self.api = Api() self.generator = WorkflowsGenerator() @staticmethod def _create_users_names_rbac(users): """Create name and Creator role for users, created vid PersonFactory""" if not users: return roles = {r.name: r for r in all_models.Role.query.all()} for user in users: user.name = user.email.split("@")[0] rbac_factories.UserRoleFactory(role=roles["Creator"], person=user) def assert_profile_get_successful(self, response, expected_datetime): """Verify assertions for successful GET profile method""" self.assert200(response) response_datetime = date_parser.parse( response.json["last_seen_whats_new"]) self.assertEqual(expected_datetime, response_datetime) @freeze_time("2018-05-20 12:23:17") def test_profile_get_successful(self): """Test person_profile GET method successfully achieves correct data""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) response = self.api.client.get("/api/people/{}/profile".format( user.id)) self.assert_profile_get_successful(response, default_date()) def test_profile_get_no_profile(self): """Test person_profile GET method achieves data with missing profile""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) profiles_table = PersonProfile.__table__ db_request = profiles_table.delete().where( profiles_table.c.person_id == user.id) db.engine.execute(db_request) with freeze_time("2018-05-28 23:30:10"): response = self.api.client.get("/api/people/{}/profile".format( user.id)) self.assert_profile_get_successful(response, default_date()) def test_profile_get_failed(self): """Test person_profiles GET method fails Now only logged user can request his profile """ with factories.single_commit(): valid_user = factories.PersonFactory() self._create_users_names_rbac([valid_user]) response = self.client.get("/api/people/{}/profile".format( valid_user.id)) # logged with default user during setUp self.assert403(response) response = self.api.client.get("/api/people/{}/profile".format( valid_user.id)) # not authorized user self.assert403(response) @ddt.data("Creator", "Reader", "Editor", "Administrator") def test_profile_post_empty_body(self, role_name): """Test person_profile POST method with empty body - {}.""" role = all_models.Role.query.filter( all_models.Role.name == role_name).one() with factories.single_commit(): user = factories.PersonFactory() rbac_factories.UserRoleFactory(role=role, person=user) self.api.set_user(person=user) response = self.api.send_request( self.api.client.post, data={}, api_link="/api/people/{}/profile".format(user.id)) self.assert405(response) def test_profile_post_unauthorized(self): """Test person_profile POST method with empty body - No Access.""" with factories.single_commit(): user = factories.PersonFactory() response = self.api.send_request( self.api.client.post, data={}, api_link="/api/people/{}/profile".format(user.id)) # not authorized user self.assert405(response) def assert_profile_put_successful(self, response, correct_response, user, expected): """Verify assertions for successful PUT profile method""" self.assert200(response) self.assertEqual(response.json, correct_response) profile = PersonProfile.query.filter_by(person_id=user.id).first() self.assertEqual(profile.last_seen_whats_new, date_parser.parse(expected)) @ddt.data(["2018-05-20 16:38:17", "2018-05-20 16:38:17"], ["2018-07-05T14:11:31Z", "2018-07-05T14:11:31"]) @ddt.unpack def test_profile_put_successful(self, new_date, expected_date): """Test person_profile PUT method for setting data and correct response""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) data = {"last_seen_whats_new": new_date} correct_response = {"Person": {"id": user.id, "profile": data}} response = self.api.client.put("/api/people/{}/profile".format( user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) self.assert_profile_put_successful(response, correct_response, user, expected_date) def test_profile_put_no_profile(self): """Test person_profile PUT method for setting data for missing profile""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) new_date = "2018-05-20 22:05:17" data = {"last_seen_whats_new": new_date} correct_response = {"Person": {"id": user.id, "profile": data}} profiles_table = PersonProfile.__table__ db_request = profiles_table.delete().where( profiles_table.c.person_id == user.id) db.engine.execute(db_request) response = self.api.client.put("/api/people/{}/profile".format( user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) self.assert_profile_put_successful(response, correct_response, user, new_date) def test_profile_put_unauthorized(self): """Test person_profiles PUT method fails for unauthorized user""" with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) new_date = "2018-05-20 22:05:17" data = {"last_seen_whats_new": new_date} response = self.client.put("/api/people/{}/profile".format(user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) # logged with default user during setUp self.assert403(response) response = self.api.client.put("/api/people/{}/profile".format( user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) # not authorized user self.assert403(response) @ddt.data({"last_seen_whats_new": "NOT A 123 DAT456A"}, { "other_key": "2018-05-20 22:05:17", "one_more_key": 42 }) def test_profile_put_corrupted_data(self, data): """Test person_profiles PUT method fails via incorrect request data If request doesn't have "last_seen_whats_new" key or date is incorrect, response is code 400 "Bad Request" """ with factories.single_commit(): user = factories.PersonFactory() self._create_users_names_rbac([user]) self.api.set_user(person=user) response = self.api.client.put("/api/people/{}/profile".format( user.id), content_type='application/json', data=json.dumps(data), headers=[('X-Requested-By', 'Tests')]) # missed key in request self.assert400(response) def test_task_count_empty(self): """Test query count without any workflows and tasks.""" user = all_models.Person.query.first() response = self.client.get("/api/people/{}/task_count".format(user.id)) self.assertEqual(response.json, { "open_task_count": 0, "has_overdue": False }) @ddt.data( (True, [ ("task 1", "Finished", 3, True, 3), ("task 1", "Verified", 2, True, 3), ("task 2", "Declined", 2, True, 3), ("task 2", "Verified", 1, False, 3), ("task 2", "Finished", 2, True, 3), ("task 3", "Verified", 1, True, 3), ("task 2", "Verified", 0, False, 3), ]), (False, [ ("task 1", "Finished", 2, True, 3), ("task 2", "In Progress", 2, True, 3), ("task 2", "Finished", 1, False, 3), ("task 3", "Finished", 0, False, 3), ]), ) @ddt.unpack def test_task_count(self, is_verification_needed, transitions): """Test person task counts. This tests checks for correct task counts - with inactive workflows and - with overdue tasks - without overdue tasks - with finished overdue tasks The four checks are done in a single test due to complex differences between tests that make ddt cumbersome and the single test also improves integration test performance due to slow workflow setup stage. """ # pylint: disable=too-many-locals user = all_models.Person.query.first() dummy_user = factories.PersonFactory() user_id = user.id role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id secondary_role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Secondary Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id one_time_workflow = { "title": "Person resource test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [create_stub(user)], "is_verification_needed": is_verification_needed, "task_groups": [{ "title": "one time task group", "contact": create_stub(user), "task_group_tasks": [ { "title": "task 1", "description": "some task", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id), acl_helper.get_acl_json(secondary_role_id, user.id) ], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }, { "title": "task 2", "description": "some task 3", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id), acl_helper.get_acl_json(secondary_role_id, user.id), acl_helper.get_acl_json(secondary_role_id, dummy_user.id) ], "start_date": date(2017, 5, 5), "end_date": date(2017, 9, 16), }, { "title": "task 3", "description": "some task 4", "access_control_list": [ acl_helper.get_acl_json(role_id, user.id), acl_helper.get_acl_json(role_id, dummy_user.id) ], "start_date": date(2017, 6, 5), "end_date": date(2017, 10, 16), }, { "title": "dummy task 4", # task should not counted "description": "some task 4", "access_control_list": [acl_helper.get_acl_json(role_id, dummy_user.id)], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 17), }, { "title": "dummy task 5", # task should not counted "description": "some task 4", "access_control_list": [acl_helper.get_acl_json(role_id, dummy_user.id)], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 18), } ], "task_group_objects": [] }] } inactive_workflow = { "title": "Activated workflow with archived cycles", "notify_on_change": True, "description": "Extra test workflow", "owners": [create_stub(user)], "task_groups": [{ "title": "Extra task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "not counted existing task", "description": "", "access_control_list": [acl_helper.get_acl_json(role_id, user.id)], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }], "task_group_objects": [] }] } with freeze_time("2017-10-16 05:09:10"): self.client.get("/login") # Activate normal one time workflow _, workflow = self.generator.generate_workflow(one_time_workflow) _, cycle = self.generator.generate_cycle(workflow) tasks = {t.title: t for t in cycle.cycle_task_group_object_tasks} _, workflow = self.generator.activate_workflow(workflow) # Activate and close the inactive workflow _, workflow = self.generator.generate_workflow(inactive_workflow) _, cycle = self.generator.generate_cycle(workflow) _, workflow = self.generator.activate_workflow(workflow) self.generator.modify_object(cycle, data={"is_current": False}) with freeze_time("2017-7-16 07:09:10"): self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 3, "has_overdue": False }) with freeze_time("2017-10-16 08:09:10"): # same day as task 3 end date self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 3, "has_overdue": True }) for task, status, count, overdue, my_work_count in transitions: self.generator.modify_object(tasks[task], data={"status": status}) task_count_response = \ self.client.get("/api/people/{}/task_count".format(user_id)) my_work_count_response = \ self.client.get("/api/people/{}/my_work_count".format(user_id)) self.assertEqual(task_count_response.json, { "open_task_count": count, "has_overdue": overdue }) self.assertEqual( my_work_count_response.json["CycleTaskGroupObjectTask"], my_work_count) def test_task_count_multiple_wfs(self): """Test task count with both verified and non verified workflows. This checks task counts with 4 tasks 2017, 8, 15 - verification needed 2017, 11, 18 - verification needed 2017, 8, 15 - No verification needed 2017, 11, 18 - No verification needed """ user = all_models.Person.query.first() user_id = user.id role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id workflow_template = { "title": "verified workflow", "owners": [create_stub(user)], "is_verification_needed": True, "task_groups": [{ "title": "one time task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "task 1", "description": "some task", "access_control_list": [acl_helper.get_acl_json(role_id, user.id)], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }, { "title": "dummy task 5", "description": "some task 4", "access_control_list": [acl_helper.get_acl_json(role_id, user.id)], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 18), }], "task_group_objects": [] }] } with freeze_time("2017-10-16 05:09:10"): self.client.get("/login") verified_workflow = workflow_template.copy() verified_workflow["is_verification_needed"] = True _, workflow = self.generator.generate_workflow(verified_workflow) _, cycle = self.generator.generate_cycle(workflow) verified_tasks = { task.title: task for task in cycle.cycle_task_group_object_tasks } _, workflow = self.generator.activate_workflow(workflow) non_verified_workflow = workflow_template.copy() non_verified_workflow["is_verification_needed"] = False _, workflow = self.generator.generate_workflow( non_verified_workflow) _, cycle = self.generator.generate_cycle(workflow) non_verified_tasks = { task.title: task for task in cycle.cycle_task_group_object_tasks } _, workflow = self.generator.activate_workflow(workflow) with freeze_time("2017-7-16 07:09:10"): self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 4, "has_overdue": False }) with freeze_time("2017-10-16 08:09:10"): self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 4, "has_overdue": True }) # transition 1, task that needs verification goes to finished state. This # transition should not change anything self.generator.modify_object(verified_tasks["task 1"], data={"status": "Finished"}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 4, "has_overdue": True }) # transition 2, task that needs verification goes to verified state. This # transition should reduce task count. self.generator.modify_object(verified_tasks["task 1"], data={"status": "Verified"}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 3, "has_overdue": True }) # transition 3, task that does not need verification goes into Finished # state. This transition should reduce task count and remove all overdue # tasks self.generator.modify_object(non_verified_tasks["task 1"], data={"status": "Finished"}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 2, "has_overdue": False }) @ddt.data(("Creator", 403), ("Reader", 403), ("Editor", 200), ("Administrator", 200)) @ddt.unpack def test_person_editing(self, role_name, status): """{0} should receive {1} status code on edit Person.""" role = all_models.Role.query.filter( all_models.Role.name == role_name).one() with factories.single_commit(): client_user = factories.PersonFactory() rbac_factories.UserRoleFactory(role=role, person=client_user) self.api.set_user(client_user) self.client.get("/login") base_email = "*****@*****.**" person = factories.PersonFactory(email=base_email) person_id = person.id new_email = "new_{}".format(base_email) resp = self.api.put(person, {"email": new_email}) self.assertEqual(status, resp.status_code) person = all_models.Person.query.get(person_id) if status == 200: self.assertEqual(new_email, person.email) else: self.assertEqual(base_email, person.email)
class TestWorkflowCycleStatePropagantion(TestCase): """Test case for cycle task to cycle task group status propagation""" def setUp(self): super(TestWorkflowCycleStatePropagantion, self).setUp() self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.weekly_wf = { "title": "weekly thingy", "description": "start this many a time", "frequency": "weekly", "task_groups": [{ "title": "weekly task group", "task_group_tasks": [{ "title": "weekly task 1", "relative_end_day": 1, "relative_end_month": None, "relative_start_day": 5, "relative_start_month": None, }, { "title": "weekly task 1", "relative_end_day": 1, "relative_end_month": None, "relative_start_day": 1, "relative_start_month": None, } ]}, ] } def test_weekly_state_transitions_assigned_inprogress(self): "Test that starting one cycle task changes cycle task group" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] self.assertEqual(ctg.status, "Assigned") cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks for cycle_task in cycle_tasks: self.assertEqual(cycle_task.status, "Assigned") # Move one task to InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) self.assertEqual(first_ct.status, "InProgress") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo operation _, first_ct = self.generator.modify_object( first_ct, {"status": "Assigned"}) self.assertEqual(first_ct.status, "Assigned") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Assigned") # Move both to in progress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo one cycle task _, first_ct = self.generator.modify_object( first_ct, {"status": "Assigned"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Undo second cycle task _, second_ct = self.generator.modify_object( second_ct, {"status": "Assigned"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "Assigned") self.assertEqual(ctg.status, "Assigned") def test_weekly_state_transitions_inprogress_finished(self): "Test In Progress to Finished transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) # Test that moving one task to finished doesn't finish entire cycle _, first_ct = self.generator.modify_object( first_ct, {"status": "Finished"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Finished") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Test moving second task to Finished - entire cycle should be finished _, second_ct = self.generator.modify_object( second_ct, {"status": "Finished"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(second_ct.status, "Finished") self.assertEqual(first_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Undo one task, cycle should be InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "InProgress") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress") def test_weekly_state_transitions_finished_verified(self): "Test Finished to Verified transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) self.generator.modify_object( cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") for cycle_task in cycle_tasks: cycle_task = db.session.query(CycleTaskGroupObjectTask).get( cycle_task.id) self.assertEqual(cycle_task.status, "Finished") # Verify first CT _, first_ct = self.generator.modify_object( first_ct, {"status": "Verified"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Verify second CT _, second_ct = self.generator.modify_object( second_ct, {"status": "Verified"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Verified") self.assertEqual(ctg.status, "Verified") def test_weekly_state_transitions_finished_declined(self): "Test Finished to Declined transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) self.generator.modify_object( cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") # Decline first CT _, first_ct = self.generator.modify_object( first_ct, {"status": "Declined"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Declined") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress")
class TestTaskOverdueNotificationsUsingAPI(TestTaskOverdueNotifications): """Tests for overdue notifications when changing Tasks with an API.""" # pylint: disable=invalid-name def setUp(self): super(TestTaskOverdueNotificationsUsingAPI, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() models.Notification.query.delete() self._fix_notification_init() self.random_objects = self.object_generator.generate_random_objects(2) _, self.user = self.object_generator.generate_person( user_role="Administrator") self._create_test_cases() @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_sending_overdue_notifications_for_tasks(self, is_vf_needed, _): """Overdue notifications should be sent for overdue tasks every day. Even if an overdue notification has already been sent, it should still be sent in every following daily digest f a task is still overdue. """ with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1_id = tasks[0].id task2_id = tasks[1].id user = models.Person.query.get(self.user.id) with freeze_time("2017-05-14 08:09:10"): _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) with freeze_time("2017-05-15 08:09:10"): # task 1 due date _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) with freeze_time("2017-05-16 08:09:10"): # task 2 due date _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) overdue_task_ids = sorted(user_notifs["task_overdue"].keys()) self.assertEqual(overdue_task_ids, [task1_id]) with freeze_time("2017-05-17 08:09:10"): # after both tasks' due dates _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) overdue_task_ids = sorted(user_notifs["task_overdue"].keys()) self.assertEqual(overdue_task_ids, [task1_id, task2_id]) common.send_daily_digest_notifications() # even after sending the overdue notifications, they are sent again the # day after, too with freeze_time("2017-05-18 08:09:10"): _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) overdue_task_ids = sorted(user_notifs["task_overdue"].keys()) self.assertEqual(overdue_task_ids, [task1_id, task2_id]) @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_adjust_overdue_notifications_on_task_due_date_change(self, is_vf_needed, _): """Sending overdue notifications should adjust to task due date changes.""" with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1, task2 = tasks self.wf_generator.modify_object(task2, {"end_date": date(2099, 12, 31)}) user = models.Person.query.get(self.user.id) with freeze_time("2017-05-16 08:09:10"): # a day after task1 due date _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) # change task1 due date, there should be no overdue notification anymore self.wf_generator.modify_object(task1, {"end_date": date(2017, 5, 16)}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) # change task1 due date to the past there should a notification again self.wf_generator.modify_object(task1, {"end_date": date(2017, 5, 14)}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_adjust_overdue_notifications_on_task_status_change(self, is_vf_needed, _): """Sending overdue notifications should take task status into account.""" with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1, task2 = tasks self.wf_generator.modify_object(task2, {"end_date": date(2099, 12, 31)}) user = models.Person.query.get(self.user.id) user_email = user.email if is_vf_needed: non_final_states = [CycleTaskGroupObjectTask.ASSIGNED, CycleTaskGroupObjectTask.IN_PROGRESS, CycleTaskGroupObjectTask.FINISHED, CycleTaskGroupObjectTask.DECLINED] final_state = CycleTaskGroupObjectTask.VERIFIED else: non_final_states = [CycleTaskGroupObjectTask.ASSIGNED, CycleTaskGroupObjectTask.IN_PROGRESS] final_state = CycleTaskGroupObjectTask.FINISHED with freeze_time("2017-05-16 08:09:10"): # a day after task1 due date for state in non_final_states: # clear all notifications before before changing the task status models.Notification.query.delete() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) self.wf_generator.modify_object(task1, {"status": state}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) # WITHOUT clearing the overdue notifications, move the task to "verified" # state, and the overdue notification should disappear. self.wf_generator.modify_object(task1, {"status": final_state}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertNotIn("task_overdue", user_notifs) @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_stop_sending_overdue_notification_if_task_gets_deleted(self, is_vf_needed, _): """Overdue notifications should not be sent for deleted tasks.""" with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1, task2 = tasks user = models.Person.query.get(self.user.id) user_email = user.email with freeze_time("2017-10-16 08:09:10"): # long after both task due dates _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 2) db.session.delete(task2) db.session.commit() _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) db.session.delete(task1) db.session.commit() _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) def _create_test_cases(self): """Create configuration to use for generating a new workflow.""" def person_dict(person_id): return { "href": "/api/people/" + str(person_id), "id": person_id, "type": "Person" } role_id = models.all_models.AccessControlRole.query.filter( models.all_models.AccessControlRole.name == "Task Assignees", models.all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id self.one_time_workflow = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", # admin will be current user with id == 1 "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "some task", "start_date": date(2017, 5, 5), # Friday "end_date": date(2017, 5, 15), "access_control_list": [ acl_helper.get_acl_json(role_id, self.user.id)], }, { "title": "task 2", "description": "some task 2", "start_date": date(2017, 5, 5), # Friday "end_date": date(2017, 5, 16), "access_control_list": [ acl_helper.get_acl_json(role_id, self.user.id)], }], "task_group_objects": self.random_objects }] }
class TestWorkflowCycleStatePropagantion(TestCase): """Test case for cycle task to cycle task group status propagation""" def setUp(self): TestCase.setUp(self) self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.weekly_wf = { "title": "weekly thingy", "description": "start this many a time", "frequency": "weekly", "task_groups": [ { "title": "weekly task group", "task_group_tasks": [{ "title": "weekly task 1", "relative_end_day": 1, "relative_end_month": None, "relative_start_day": 5, "relative_start_month": None, }, { "title": "weekly task 1", "relative_end_day": 1, "relative_end_month": None, "relative_start_day": 1, "relative_start_month": None, }] }, ] } def test_weekly_state_transitions_assigned_inprogress(self): "Test that starting one cycle task changes cycle task group" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] self.assertEqual(ctg.status, "Assigned") cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks for cycle_task in cycle_tasks: self.assertEqual(cycle_task.status, "Assigned") # Move one task to InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) self.assertEqual(first_ct.status, "InProgress") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo operation _, first_ct = self.generator.modify_object(first_ct, {"status": "Assigned"}) self.assertEqual(first_ct.status, "Assigned") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Assigned") # Move both to in progress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo one cycle task _, first_ct = self.generator.modify_object(first_ct, {"status": "Assigned"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Undo second cycle task _, second_ct = self.generator.modify_object( second_ct, {"status": "Assigned"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get( first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "Assigned") self.assertEqual(ctg.status, "Assigned") def test_weekly_state_transitions_inprogress_finished(self): "Test In Progress to Finished transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) # Test that moving one task to finished doesn't finish entire cycle _, first_ct = self.generator.modify_object(first_ct, {"status": "Finished"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Finished") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Test moving second task to Finished - entire cycle should be finished _, second_ct = self.generator.modify_object( second_ct, {"status": "Finished"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get( first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(second_ct.status, "Finished") self.assertEqual(first_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Undo one task, cycle should be InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "InProgress") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress") def test_weekly_state_transitions_finished_verified(self): "Test Finished to Verified transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) self.generator.modify_object(cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") for cycle_task in cycle_tasks: cycle_task = db.session.query(CycleTaskGroupObjectTask).get( cycle_task.id) self.assertEqual(cycle_task.status, "Finished") # Verify first CT _, first_ct = self.generator.modify_object(first_ct, {"status": "Verified"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Verify second CT _, second_ct = self.generator.modify_object( second_ct, {"status": "Verified"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get( first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Verified") self.assertEqual(ctg.status, "Verified") def test_weekly_state_transitions_finished_declined(self): "Test Finished to Declined transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) self.generator.modify_object(cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") # Decline first CT _, first_ct = self.generator.modify_object(first_ct, {"status": "Declined"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Declined") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress")
class TestOneTimeWfEndDateChange(TestCase): """ This class contains simple one time workflow tests that are not in the gsheet test grid """ def setUp(self): super(TestOneTimeWfEndDateChange, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() all_models.Notification.query.delete() self.random_objects = self.object_generator.generate_random_objects(2) self.user = self.create_user_with_role(role="Administrator") self.secondary_assignee = self.create_user_with_role(role="Reader") self.create_test_cases() def init_decorator(init): def new_init(self, *args, **kwargs): init(self, *args, **kwargs) if hasattr(self, "created_at"): self.created_at = datetime.now() return new_init all_models.Notification.__init__ = init_decorator( all_models.Notification.__init__) @patch("ggrc.notifications.common.send_email") def test_no_date_change(self, mock_mail): """Test a basic case with no moving end date""" with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) assignee = self.user task_assignees = [assignee, self.secondary_assignee] with freeze_time("2015-04-11 03:21:34"): _, notif_data = common.get_daily_notifications() # cycle started notifs available only for contact self.assertIn("cycle_started", notif_data[assignee.email]) with freeze_time("2015-05-02 03:21:34"): _, notif_data = common.get_daily_notifications() self.assertIn(assignee.email, notif_data) # cycle started notifs available only for contact self.assertIn("cycle_started", notif_data[assignee.email]) for user in task_assignees: self.assertNotIn("due_in", notif_data[user.email]) self.assertNotIn("due_today", notif_data[user.email]) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to admin, one to assigne and one to secondary assignee self.assertEqual(mock_mail.call_count, 3) with freeze_time("2015-05-04 03:21:34"): # one day before due date _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn(user.email, notif_data) self.assertIn("due_in", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_in"]), 2) with freeze_time("2015-05-04 03:21:34"): # one day before due date common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to admin and two each to assigne and secondary assignee self.assertEqual(mock_mail.call_count, 5) with freeze_time("2015-05-05 03:21:34"): # due date _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn("due_today", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_today"]), 2) @patch("ggrc.notifications.common.send_email") def test_move_end_date_to_future(self, mock_mail): """Test moving the end date to the future. It is done before due_in and due_today notifications have been sent. """ with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) assignee = self.user task_assignees = [assignee, self.secondary_assignee] with freeze_time("2015-04-11 03:21:34"): _, notif_data = common.get_daily_notifications() # cycle started notifs available only for contact self.assertIn("cycle_started", notif_data[assignee.email]) with freeze_time("2015-05-02 03:21:34"): _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn(user.email, notif_data) self.assertNotIn("due_in", notif_data[user.email]) self.assertNotIn("due_today", notif_data[user.email]) # cycle started notifs available only for contact self.assertIn("cycle_started", notif_data[assignee.email]) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to admin and one each to assigne and secondary assignee self.assertEqual(mock_mail.call_count, 3) with freeze_time("2015-05-03 03:21:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.wf_generator.modify_object( task1, data={"end_date": date(2015, 5, 15)}) self.wf_generator.modify_object( task2, data={"end_date": date(2015, 5, 15)}) with freeze_time("2015-05-04 03:21:34"): # one day before due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-05 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-14 03:21:34"): # due date _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn(user.email, notif_data) self.assertIn("due_in", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_in"]), len(self.random_objects)) with freeze_time("2015-05-15 03:21:34"): # due date _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn(user.email, notif_data) self.assertIn("due_in", notif_data[user.email]) self.assertIn("due_today", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_today"]), len(self.random_objects)) @patch("ggrc.notifications.common.send_email") def test_move_end_date_to_past(self, mock_mail): """Test moving an end date to the past""" with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to admin and one to assignee self.assertEqual(mock_mail.call_count, 3) with freeze_time("2015-05-03 03:21:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.wf_generator.modify_object( task1, data={"end_date": date(2015, 5, 1)}) self.wf_generator.modify_object( task2, data={"end_date": date(2015, 5, 1)}) task_assignees = [self.user, self.secondary_assignee] with freeze_time("2015-05-03 03:21:34"): # two days after due date _, notif_data = common.get_daily_notifications() self.assertNotEqual(notif_data, {}) for user in task_assignees: self.assertIn(user.email, notif_data) user_notifs = notif_data[user.email] self.assertNotIn("due_today", user_notifs) self.assertNotIn("due_in", user_notifs) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 2) @patch("ggrc.notifications.common.send_email") def test_move_end_date_to_today(self, mock_mail): """Test moving end date to today""" with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) task_assignees = [self.user, self.secondary_assignee] with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to admin and one to assigne self.assertEqual(mock_mail.call_count, 3) with freeze_time("2015-05-03 03:21:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.wf_generator.modify_object( task1, data={"end_date": date(2015, 5, 3)}) self.wf_generator.modify_object( task2, data={"end_date": date(2015, 5, 4)}) with freeze_time("2015-05-03 03:21:34"): # one day before due date _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertNotEqual(notif_data, {}) self.assertIn(user.email, notif_data) self.assertIn("due_today", notif_data[user.email]) self.assertIn("due_in", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_today"]), 1) common.send_daily_digest_notifications() with freeze_time("2015-05-04 03:21:34"): # due date (of task2) _, notif_data = common.get_daily_notifications() for user in task_assignees: self.assertIn(user.email, notif_data) self.assertIn("due_today", notif_data[user.email]) self.assertNotIn("due_in", notif_data[user.email]) common.send_daily_digest_notifications() # check that overdue notifications are sent even on days after the day a # task has become due, unlike the "due_today" and "due_in" notifications with freeze_time("2015-05-05 03:21:34"): # 1+ days after due date(s) _, notif_data = common.get_daily_notifications() self.assertNotEqual(notif_data, {}) for user in task_assignees: self.assertIn(user.email, notif_data) user_notifs = notif_data[user.email] self.assertNotIn("due_today", user_notifs) self.assertNotIn("due_in", user_notifs) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 2) def create_test_cases(self): def person_dict(person_id): return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } task_assignee_role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id task_secondary_assignee = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Secondary Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id self.one_time_workflow_1 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", # admin will be current user with id == 1 "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [ { "title": "task 1", "description": "some task", "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), "access_control_list": [ acl_helper.get_acl_json(task_assignee_role_id, self.user.id), acl_helper.get_acl_json( task_secondary_assignee, self.secondary_assignee.id), ], }, { "title": "task 2", "description": "some task 2", "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), "access_control_list": [ acl_helper.get_acl_json(task_assignee_role_id, self.user.id), acl_helper.get_acl_json( task_secondary_assignee, self.secondary_assignee.id), ], } ], "task_group_objects": self.random_objects }] }
class TestWorkflowCycleStatePropagation(TestCase): """Test case for cycle task to cycle task group status propagation""" def setUp(self): super(TestWorkflowCycleStatePropagation, self).setUp() self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.weekly_wf = { "title": "weekly thingy", "description": "start this many a time", "unit": "week", "repeat_every": 1, "task_groups": [{ "title": "weekly task group", "task_group_tasks": [{ "title": "weekly task 1", "start_date": dtm.date(2016, 6, 10), "end_date": dtm.date(2016, 6, 13), }, { "title": "weekly task 1", "start_date": dtm.date(2016, 6, 10), "end_date": dtm.date(2016, 6, 13), }]}, ] } def test_weekly_state_transitions_assigned_inprogress(self): """Test that starting one cycle task changes cycle task group""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] self.assertEqual(ctg.status, "Assigned") cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks for cycle_task in cycle_tasks: self.assertEqual(cycle_task.status, "Assigned") # Move one task to InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) self.assertEqual(first_ct.status, "InProgress") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo operation _, first_ct = self.generator.modify_object( first_ct, {"status": "Assigned"}) self.assertEqual(first_ct.status, "Assigned") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Assigned") # Move both to in progress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo one cycle task _, first_ct = self.generator.modify_object( first_ct, {"status": "Assigned"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Undo second cycle task _, second_ct = self.generator.modify_object( second_ct, {"status": "Assigned"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "Assigned") self.assertEqual(ctg.status, "Assigned") def test_weekly_state_transitions_inprogress_finished(self): """Test In Progress to Finished transitions""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) # Test that moving one task to finished doesn't finish entire cycle _, first_ct = self.generator.modify_object( first_ct, {"status": "Finished"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Finished") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Test moving second task to Finished - entire cycle should be finished _, second_ct = self.generator.modify_object( second_ct, {"status": "Finished"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(second_ct.status, "Finished") self.assertEqual(first_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Undo one task, cycle should be InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "InProgress") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress") def test_weekly_state_transitions_finished_verified(self): """Test Finished to Verified transitions""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) self.generator.modify_object( cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") for cycle_task in cycle_tasks: cycle_task = db.session.query(CycleTaskGroupObjectTask).get( cycle_task.id) self.assertEqual(cycle_task.status, "Finished") # Verify first CT _, first_ct = self.generator.modify_object( first_ct, {"status": "Verified"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Verify second CT _, second_ct = self.generator.modify_object( second_ct, {"status": "Verified"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get(first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Verified") self.assertEqual(ctg.status, "Verified") def test_weekly_state_transitions_finished_declined(self): """Test Finished to Declined transitions""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object( cycle_task, {"status": "InProgress"}) self.generator.modify_object( cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") # Decline first CT _, first_ct = self.generator.modify_object( first_ct, {"status": "Declined"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get(second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Declined") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress") def test_deleted_task_state_transitions(self): """Test InProgress to Finished transition after task is deleted""" with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] first_ct, second_ct = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() # Move first task to InProgress self.generator.modify_object(first_ct, {"status": "InProgress"}) self.generator.modify_object(first_ct, {"status": "Finished"}) # Delete second task response = self.generator.api.delete(second_ct) self.assert200(response) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") def test_cycle_change_on_ct_status_transition(self): """Test cycle is_current change on task Finished to InProgress transition """ with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query( CycleTaskGroup ).join( Cycle ).join( Workflow ).filter( Workflow.id == wf.id ).one() c_id = ctg.cycle.id first_ct, second_ct = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() self.api.put(first_ct, {"status": "Verified"}) self.api.put(second_ct, {"status": "Verified"}) # cycle now should have is_current == False cycle = db.session.query(Cycle).get(c_id) self.assertEqual(cycle.is_current, False) # Move second task back to InProgress self.api.put(second_ct, {"status": "InProgress"}) # cycle now should have is_current == True cycle = db.session.query(Cycle).get(ctg.cycle.id) self.assertEqual(cycle.is_current, True) def test_async_request_state_transitions(self): """Test asynchronous transitions""" def change_state(cycle_task, status): self.generator.api.put(cycle_task, {"status": status}) updated_wf = deepcopy(self.weekly_wf) updated_wf["task_groups"][0]["task_group_tasks"].extend( [{"title": "weekly task 1"} for _ in xrange(3)]) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 _, wf = self.generator.generate_workflow(updated_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() # Move all tasks to InProgress threads = [] for cycle_task in cycle_tasks: change_state(cycle_task, "InProgress") threads.append(Thread(target=change_state, args=(cycle_task, "Finished"))) for t in threads: t.start() for t in threads: t.join() db.session.commit() ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished")
class TestCycleTaskStatusChange(TestCase): """ This class contains simple one time workflow tests that are not in the gsheet test grid """ def setUp(self): super(TestCycleTaskStatusChange, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() Notification.query.delete() self.random_objects = self.object_generator.generate_random_objects(2) _, self.user = self.object_generator.generate_person( user_role="Administrator") self.create_test_cases() def init_decorator(init): def new_init(self, *args, **kwargs): init(self, *args, **kwargs) if hasattr(self, "created_at"): self.created_at = datetime.now() return new_init Notification.__init__ = init_decorator(Notification.__init__) def test_task_declined_notification_created(self): with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Declined") notif = db.session.query(Notification).filter(and_( Notification.object_id == task1.id, Notification.object_type == task1.type, Notification.sent_at == None, # noqa Notification.notification_type == self.get_notification_type( "cycle_task_declined" ) )).all() self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) def test_all_tasks_finished_notification_created(self): with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) notif = db.session.query(Notification).filter(and_( Notification.object_id == cycle.id, Notification.object_type == cycle.type, Notification.sent_at == None, # noqa Notification.notification_type == self.get_notification_type( "all_cycle_tasks_completed" ) )).all() self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) notif = db.session.query(Notification).filter(and_( Notification.object_id == task1.id, Notification.object_type == task1.type, Notification.sent_at == None, # noqa Notification.notification_type != self.get_notification_type( "all_cycle_tasks_completed" ) )).all() self.assertEqual(notif, []) def test_multi_all_tasks_finished_notification_created(self): with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_2) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) notif = db.session.query(Notification).filter(and_( Notification.object_id == cycle.id, Notification.object_type == cycle.type, Notification.sent_at == None, # noqa Notification.notification_type == self.get_notification_type( "all_cycle_tasks_completed" ) )).all() # there is still one task in the cycle, so there should be no # notifications for all tasks completed self.assertEqual(notif, []) notif = db.session.query(Notification).filter(and_( Notification.object_id == task1.id, Notification.object_type == task1.type, Notification.sent_at == None, # noqa Notification.notification_type != self.get_notification_type( "all_cycle_tasks_completed" ) )).all() # The task was verified, so there should be no notifications left for due # dates. self.assertEqual(notif, []) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.task_change_status(task2) notif = db.session.query(Notification).filter(and_( Notification.object_id == cycle.id, Notification.object_type == cycle.type, Notification.sent_at == None, # noqa Notification.notification_type == self.get_notification_type( "all_cycle_tasks_completed" ) )).all() self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) @patch("ggrc.notifications.common.send_email") def test_single_task_declined(self, mock_mail): """ test moving the end date to the future, befor due_in and due_today notifications have been sent """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Finished") _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Declined") user = Person.query.get(self.user.id) _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) self.assertIn("task_declined", notif_data[user.email]) @patch("ggrc.notifications.common.send_email") def test_single_task_accepted(self, mock_mail): """ test moving the end date to the future, befor due_in and due_today notifications have been sent """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Finished") _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-03"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) user = Person.query.get(self.user.id) _, notif_data = common.get_daily_notifications() self.assertNotIn(user.email, notif_data) self.assertIn("all_tasks_completed", notif_data["*****@*****.**"]) @patch("ggrc.notifications.common.send_email") def test_end_cycle(self, mock_mail): """ manaually ending a cycle should stop all notifications for that cycle """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-03"): _, notif_data = common.get_daily_notifications() cycle = Cycle.query.get(cycle.id) user = Person.query.get(self.user.id) self.assertIn(user.email, notif_data) self.wf_generator.modify_object(cycle, data={"is_current": False}) cycle = Cycle.query.get(cycle.id) self.assertFalse(cycle.is_current) _, notif_data = common.get_daily_notifications() self.assertNotIn(user.email, notif_data) def create_test_cases(self): def person_dict(person_id): return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } self.one_time_workflow_1 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [person_dict(self.user.id)], "task_groups": [{ "title": "single task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "single task in a wf", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }], }] } self.one_time_workflow_2 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [person_dict(self.user.id)], "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "two taks in wf with different objects", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }, { "title": "task 2", "description": "two taks in wf with different objects", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }], "task_group_objects": self.random_objects }] } def get_notification_type(self, name): return db.session.query(NotificationType).filter( NotificationType.name == name).one() def task_change_status(self, task, status="Verified"): self.wf_generator.modify_object( task, data={"status": status}) task = CycleTaskGroupObjectTask.query.get(task.id) self.assertEqual(task.status, status)
class TestCycleTaskImportUpdate(BaseTestCycleTaskImportUpdate): """ This class contains simple cycle task update tests using import functionality """ CSV_DIR = join(abspath(dirname(__file__)), "test_csvs/") IMPORTABLE_COLUMN_NAMES = [ "Summary", "Task Description", "Start Date", "Due Date", "State", "Task Assignees", "Task Secondary Assignees", ] def setUp(self): super(TestCycleTaskImportUpdate, self).setUp() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.random_objects = self.object_generator.generate_random_objects(2) self.person_1 = ggrc_factories.PersonFactory() self.ftime_active = "2016-07-01" self.ftime_historical = "2014-05-01" self._create_test_cases_data() # It is needed because cycle-tasks are generated automatically with # 'slug' based on auto_increment 'id' field. # At start of each test we suppose that created cycle-task's 'slug' # lie in range from 1 to 10. db.session.execute('ALTER TABLE cycle_task_group_object_tasks ' 'AUTO_INCREMENT = 1') def test_cycle_task_correct(self): """Test cycle task update via import with correct data""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): cycle_task = wf_factories.CycleTaskGroupObjectTaskFactory( description="task active description 1") self.import_data( collections.OrderedDict([ ("object_type", "Cycle Task Group Object Task"), ("Code*", cycle_task.slug), ("description", "task active description 1"), ])) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_warnings(self): """Test cycle task update via import with data which is the reason of warnings about non-importable columns.""" self._generate_cycle_tasks() expected_warnings = { 'Cycle Task': { 'block_warnings': { errors.ONLY_IMPORTABLE_COLUMNS_WARNING.format( line=2, columns=", ".join(self.IMPORTABLE_COLUMN_NAMES)) }, 'row_warnings': [] } } with freeze_time(self.ftime_active): person = ggrc_factories.PersonFactory() cycle_task = wf_factories.CycleTaskGroupObjectTaskFactory( title="task active title134567890") ct_data = collections.OrderedDict([ ("object_type", "Cycle Task Group Object Task"), ("Code*", cycle_task.slug), ("Summary*", "task active title1-Updated"), ("Task Description", "task active title1 description"), ("Start Date", self.ftime_historical), ("Due Date", self.ftime_active), ("Actual Finish Date", self.ftime_active), ("Actual Verified Date", self.ftime_active), ("State", "Assigned"), ("Task Assignees", person.email), ("Task Secondary Assignees", person.email), ]) response = self.import_data(ct_data) self._check_csv_response(response, expected_warnings) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_permission_error(self): """Test cycle task update via import with non-admin user which is the reason of error. Only admin can update cycle tasks via import.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): _, creator = self.object_generator.generate_person( user_role="Creator") cycle_task = wf_factories.CycleTaskGroupObjectTaskFactory( description="task active description 1") ct_data = collections.OrderedDict([ ("object_type", "Cycle Task Group Object Task"), ("Code*", cycle_task.slug), ]) response = self.import_data(ct_data, person=creator, safe=False) self._check_csv_response(response, self.expected_permission_error) # Cycle tasks' data shouldn't be changed in test DB after import run from # non-admin user expected_cycle_task_permission_error = {} expected_cycle_task_permission_error.update( self.generated_cycle_tasks_active) expected_cycle_task_permission_error.update( self.generated_cycle_tasks_historical) self._cmp_tasks(expected_cycle_task_permission_error) def _cmp_tasks(self, expected_ctasks): """Compare tasks values from argument's list and test DB.""" for ctask in db.session.query(CycleTaskGroupObjectTask).all(): if ctask.title not in expected_ctasks: continue exp_task = expected_ctasks[ctask.title] for attr, val in exp_task.iteritems(): self.assertEqual( str(getattr(ctask, attr, None)), val, "attr {} value for {} not expected".format( attr, ctask.title)) # pylint: disable=too-many-arguments def _activate_workflow(self, ftime, workflow, task_group, task_group_tasks, random_object, cycle_tasks): """Helper which is responsible for active cycle-tasks creation""" with freeze_time(ftime): _, wf = self.wf_generator.generate_workflow(workflow) _, tg = self.wf_generator.generate_task_group(wf, task_group) for task in task_group_tasks: self.wf_generator.generate_task_group_task(tg, task) self.wf_generator.generate_task_group_object(tg, random_object) _, cycle = self.wf_generator.generate_cycle(wf) self.wf_generator.activate_workflow(wf) for exp_slug, exp_task in cycle_tasks.iteritems(): obj = db.session.query(CycleTaskGroupObjectTask).filter_by( slug=exp_slug).first() if exp_task["status"] == "Verified": self.wf_generator.modify_object(obj, {"status": "Finished"}) self.wf_generator.modify_object(obj, {"status": exp_task["status"]}) self._cmp_tasks(cycle_tasks) return cycle def _generate_cycle_tasks(self): """Helper which is responsible for test data creation""" self._activate_workflow(self.ftime_active, self.workflow_active, self.task_group_active, self.task_group_tasks_active, self.random_objects[0], self.generated_cycle_tasks_active) cycle = self._activate_workflow(self.ftime_historical, self.workflow_historical, self.task_group_historical, self.task_group_tasks_historical, self.random_objects[1], self.generated_cycle_tasks_historical) with freeze_time(self.ftime_historical): cycle = Cycle.query.get(cycle.id) self.wf_generator.modify_object(cycle, data={"is_current": False}) def _create_test_cases_data(self): """Create test cases data: for object generation, expected data for checks""" def person_dict(person_id): """Return person data""" return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } wf_admin_role_id = { n: i for ( i, n) in role.get_custom_roles_for(Workflow.__name__).iteritems() }['Admin'] self.workflow_active = { "title": "workflow active title", "description": "workflow active description", "is_verification_needed": True, "access_control_list": [acl_helper.get_acl_json(wf_admin_role_id, self.person_1.id)], "notify_on_change": False, } self.task_group_active = { "title": "task group active title", "contact": person_dict(self.person_1.id), } self.task_group_tasks_active = [{ "title": "task active title 1", "description": "task active description 1", "contact": person_dict(self.person_1.id), "start_date": "07/01/2016", "end_date": "07/06/2016", }, { "title": "task active title 2", "description": "task active description 2", "contact": person_dict(self.person_1.id), "start_date": "07/07/2016", "end_date": "07/12/2016", }, { "title": "task active title 3", "description": "task active description 3", "contact": person_dict(self.person_1.id), "start_date": "07/13/2016", "end_date": "07/18/2016", }, { "title": "task active title 4", "description": "task active description 4", "contact": person_dict(self.person_1.id), "start_date": "07/19/2016", "end_date": "07/24/2016", }, { "title": "task active title 5", "description": "task active description 5", "contact": person_dict(self.person_1.id), "start_date": "07/25/2016", "end_date": "07/30/2016", }] # Active cycle tasks which should be generated from previous structure # at the beginning of each test self.generated_cycle_tasks_active = { "CYCLETASK-1": { "title": self.task_group_tasks_active[0]["title"], "description": self.task_group_tasks_active[0]["description"], "start_date": "2016-07-01", "end_date": "2016-07-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-2": { "title": self.task_group_tasks_active[1]["title"], "description": self.task_group_tasks_active[1]["description"], "start_date": "2016-07-07", "end_date": "2016-07-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-3": { "title": self.task_group_tasks_active[2]["title"], "description": self.task_group_tasks_active[2]["description"], "start_date": "2016-07-13", "end_date": "2016-07-18", "finished_date": "None", "verified_date": "None", "status": "In Progress" }, "CYCLETASK-4": { "title": self.task_group_tasks_active[3]["title"], "description": self.task_group_tasks_active[3]["description"], "start_date": "2016-07-19", "end_date": "2016-07-22", "finished_date": "2016-07-01", "verified_date": "None", "status": "Finished" }, "CYCLETASK-5": { "title": self.task_group_tasks_active[4]["title"], "description": self.task_group_tasks_active[4]["description"], "start_date": "2016-07-25", "end_date": "2016-07-29", "finished_date": "2016-07-01", "verified_date": "2016-07-01", "status": "Verified" } } self.workflow_historical = { "title": "workflow historical title", "description": "workflow historical description", "is_verification_needed": True, "access_control_list": [acl_helper.get_acl_json(wf_admin_role_id, self.person_1.id)], "notify_on_change": False, } self.task_group_historical = { "title": "task group historical title", "contact": person_dict(self.person_1.id), } self.task_group_tasks_historical = [ { "title": "task historical title 1", "description": "task historical description 1", "contact": person_dict(self.person_1.id), "start_date": "05/01/2014", "end_date": "05/06/2014", }, { "title": "task historical title 2", "description": "task historical description 2", "contact": person_dict(self.person_1.id), "start_date": "05/07/2014", "end_date": "05/12/2014", }, { "title": "task historical title 3", "description": "task historical description 3", "contact": person_dict(self.person_1.id), "start_date": "05/13/2014", "end_date": "05/18/2014", }, { "title": "task historical title 4", "description": "task historical description 4", "contact": person_dict(self.person_1.id), "start_date": "05/19/2014", "end_date": "05/24/2014", }, { "title": "task historical title 5", "description": "task historical description 5", "contact": person_dict(self.person_1.id), "start_date": "05/25/2014", "end_date": "05/30/2014", }, ] # Historical cycle tasks which should be generated from previous structure # at the beginning of each test. self.generated_cycle_tasks_historical = { "CYCLETASK-6": { "title": self.task_group_tasks_historical[0]["title"], "description": self.task_group_tasks_historical[0]["description"], "start_date": "2014-05-01", "end_date": "2014-05-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-7": { "title": self.task_group_tasks_historical[1]["title"], "description": self.task_group_tasks_historical[1]["description"], "start_date": "2014-05-07", "end_date": "2014-05-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-8": { "title": self.task_group_tasks_historical[2]["title"], "description": self.task_group_tasks_historical[2]["description"], "start_date": "2014-05-13", "end_date": "2014-05-16", "finished_date": "None", "verified_date": "None", "status": "In Progress" }, "CYCLETASK-9": { "title": self.task_group_tasks_historical[3]["title"], "description": self.task_group_tasks_historical[3]["description"], "start_date": "2014-05-19", "end_date": "2014-05-23", "finished_date": "2014-05-01", "verified_date": "None", "status": "Finished" }, "CYCLETASK-10": { "title": self.task_group_tasks_historical[4]["title"], "description": self.task_group_tasks_historical[4]["description"], "start_date": "2014-05-23", "end_date": "2014-05-30", "finished_date": "2014-05-01", "verified_date": "2014-05-01", "status": "Verified" } } # Expected cycle tasks which should be created in correct cycle task update # case. It is needed for most tests. self.expected_cycle_task_correct = { "CYCLETASK-1": { "title": self.task_group_tasks_active[0]["title"] + " one", "description": self.task_group_tasks_active[0]["description"] + " one", "start_date": "2016-06-01", "end_date": "2016-06-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-2": { "title": self.task_group_tasks_active[1]["title"] + " two", "description": self.task_group_tasks_active[1]["description"] + " two", "start_date": "2016-06-07", "end_date": "2016-06-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-3": { "title": self.task_group_tasks_active[2]["title"] + " three", "description": self.task_group_tasks_active[2]["description"] + " three", "start_date": "2016-06-13", "end_date": "2016-06-18", "finished_date": "None", "verified_date": "None", "status": "In Progress" }, "CYCLETASK-4": { "title": self.task_group_tasks_active[3]["title"] + " four", "description": self.task_group_tasks_active[3]["description"] + " four", "start_date": "2016-06-19", "end_date": "2016-06-24", "status": "Finished" }, "CYCLETASK-5": { "title": self.task_group_tasks_active[4]["title"] + " five", "description": self.task_group_tasks_active[4]["description"] + " five", "start_date": "2016-06-25", "end_date": "2016-06-30", "finished_date": "2016-07-01", "verified_date": "2016-07-01", "status": "Verified" }, "CYCLETASK-6": { "title": self.task_group_tasks_historical[0]["title"] + " one", "description": self.task_group_tasks_historical[0]["description"] + " one", "start_date": "2014-04-01", "end_date": "2014-04-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-7": { "title": self.task_group_tasks_historical[1]["title"] + " two", "description": self.task_group_tasks_historical[1]["description"] + " two", "start_date": "2014-04-07", "end_date": "2014-04-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-8": { "title": self.task_group_tasks_historical[2]["title"] + " three", "description": self.task_group_tasks_historical[2]["description"] + " three", "start_date": "2014-04-13", "end_date": "2014-04-18", "finished_date": "None", "verified_date": "None", "status": "In Progress" }, "CYCLETASK-9": { "title": self.task_group_tasks_historical[3]["title"] + " four", "description": self.task_group_tasks_historical[3]["description"] + " four", "start_date": "2014-04-19", "end_date": "2014-04-24", "status": "Finished" }, "CYCLETASK-10": { "title": self.task_group_tasks_historical[4]["title"] + " five", "description": self.task_group_tasks_historical[4]["description"] + " five", "start_date": "2014-04-25", "end_date": "2014-04-30", "finished_date": "2014-05-01", "verified_date": "2014-05-01", "status": "Verified" } } # This is an error message which should be shown during # test_cycle_task_create_error test self.expected_create_error = { 'Cycle Task': { 'row_errors': {errors.CREATE_INSTANCE_ERROR.format(line=13)} } } # Below is expected date errors for test_cycle_task_date_error. They should # be shown during date validator's tests. self.expected_date_error = { 'Cycle Task': { 'row_errors': { errors.INVALID_START_END_DATES.format( line=3, start_date="Start Date", end_date="End Date", ), errors.INVALID_START_END_DATES.format( line=8, start_date="Start Date", end_date="End Date", ), }, } } # Below is expected cycle-tasks data which should appear in test DB after # test_cycle_task_date_error run self.expected_cycle_task_date_error = dict() self.expected_cycle_task_date_error.update( self.generated_cycle_tasks_active) self.expected_cycle_task_date_error.update( self.generated_cycle_tasks_historical) self.expected_cycle_task_date_error["CYCLETASK-9"] = ( self.expected_cycle_task_correct["CYCLETASK-9"]) self.expected_cycle_task_date_error["CYCLETASK-10"] = ( self.expected_cycle_task_correct["CYCLETASK-10"]) # Expected error message which should be shown after # test_cycle_task_permission_error run self.expected_permission_error = { 'Cycle Task': { 'block_errors': {errors.PERMISSION_ERROR.format(line=2)} } }
class TestWfNotifsGenerator(TestCase): """Test wf cycle tasks notifications generation.""" def setUp(self): """Set up.""" super(TestWfNotifsGenerator, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() Notification.query.delete() with freeze_time("2015-05-01 14:29:00"): wf_slug = "wf1" with factories.single_commit(): wf = wf_factories.WorkflowFactory(slug=wf_slug) task_group = wf_factories.TaskGroupFactory(workflow=wf) wf_factories.TaskGroupTaskFactory( task_group=task_group, title='task1', start_date=datetime.now(), end_date=datetime.now() + timedelta(7) ) data = workflow_api.get_cycle_post_dict(wf) self.api.post(all_models.Cycle, data) wf = all_models.Workflow.query.filter_by(slug=wf_slug).one() self.cycle = all_models.Cycle.query.filter_by(workflow_id=wf.id).one() self.ctask = all_models.CycleTaskGroupObjectTask.query.filter_by( cycle_id=self.cycle.id).first() def test_ctasks_notifs_generator_daily_digest(self): """Test cycle tasks notifications generation job.""" with freeze_time("2015-05-01 14:29:00"): self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "manual_cycle_created", "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue") # Move task to Finished self.wf_generator.modify_object( self.ctask, data={"status": "Verified"}) generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "all_cycle_tasks_completed", "manual_cycle_created") # Undo finish self.wf_generator.modify_object( self.ctask, data={"status": "In Progress"}) generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue") self.wf_generator.modify_object( self.ctask, data={"status": "Declined"}) self.assert_notifications_for_object(self.ctask, "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue", "cycle_task_declined") @ddt.data(("2015-05-01 14:29:00", ("all_cycle_tasks_completed", "manual_cycle_created")), ("2015-05-02 07:29:00", ("all_cycle_tasks_completed", "manual_cycle_created")), ("2015-05-02 14:29:00", ("all_cycle_tasks_completed", "manual_cycle_created")), ("2015-05-03 07:29:00", ("manual_cycle_created",))) @ddt.unpack def test_cycle_task_update_timelines(self, _datetime, notifications): """Test cycle task has been updated: 1) the day before job is called; 2) the same day job is called before 08:00 AM UTC; 3) the same day job is called after 08:00 AM UTC; 4) two days before job is called. """ with freeze_time("2015-05-01 14:29:00"): # Move task to Finished self.wf_generator.modify_object( self.ctask, data={"status": "Verified"}) with freeze_time(_datetime): generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, *notifications) def test_ctasks_notifs_generator_daily_digest_called_twice(self): """No duplicated notifications should be generated""" with freeze_time("2015-05-01 14:29:00"): generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "manual_cycle_created", "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue") # Move task to Finished self.wf_generator.modify_object( self.ctask, data={"status": "Verified"}) generate_cycle_tasks_notifs() generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "all_cycle_tasks_completed", "manual_cycle_created") def test_ctasks_notifs_generator_cron_job(self): """Test cycle tasks notifications generation cron job.""" with freeze_time("2015-05-2 08:00:00"): generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "manual_cycle_created", "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue")
class TestTaskOverdueNotificationsUsingAPI(TestTaskOverdueNotifications): """Tests for overdue notifications when changing Tasks with an API.""" # pylint: disable=invalid-name def setUp(self): super(TestTaskOverdueNotificationsUsingAPI, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() models.Notification.query.delete() self._fix_notification_init() self.random_objects = self.object_generator.generate_random_objects(2) _, self.user = self.object_generator.generate_person( user_role="Administrator") self._create_test_cases() @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_sending_overdue_notifications_for_tasks(self, is_vf_needed, _): """Overdue notifications should be sent for overdue tasks every day. Even if an overdue notification has already been sent, it should still be sent in every following daily digest f a task is still overdue. """ with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1_id = tasks[0].id task2_id = tasks[1].id user = models.Person.query.get(self.user.id) with freeze_time("2017-05-14 08:09:10"): _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) with freeze_time("2017-05-15 08:09:10"): # task 1 due date _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) with freeze_time("2017-05-16 08:09:10"): # task 2 due date _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) overdue_task_ids = sorted(user_notifs["task_overdue"].keys()) self.assertEqual(overdue_task_ids, [task1_id]) with freeze_time("2017-05-17 08:09:10"): # after both tasks' due dates _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) overdue_task_ids = sorted(user_notifs["task_overdue"].keys()) self.assertEqual(overdue_task_ids, [task1_id, task2_id]) common.send_daily_digest_notifications() # even after sending the overdue notifications, they are sent again the # day after, too with freeze_time("2017-05-18 08:09:10"): _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) overdue_task_ids = sorted(user_notifs["task_overdue"].keys()) self.assertEqual(overdue_task_ids, [task1_id, task2_id]) @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_adjust_overdue_notifications_on_task_due_date_change(self, is_vf_needed, _): """Sending overdue notifications should adjust to task due date changes.""" with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1, task2 = tasks self.wf_generator.modify_object(task2, {"end_date": date(2099, 12, 31)}) user = models.Person.query.get(self.user.id) with freeze_time("2017-05-16 08:09:10"): # a day after task1 due date _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) # change task1 due date, there should be no overdue notification anymore self.wf_generator.modify_object(task1, {"end_date": date(2017, 5, 16)}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) # change task1 due date to the past there should a notification again self.wf_generator.modify_object(task1, {"end_date": date(2017, 5, 14)}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_adjust_overdue_notifications_on_task_status_change(self, is_vf_needed, _): """Sending overdue notifications should take task status into account.""" with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1, task2 = tasks self.wf_generator.modify_object(task2, {"end_date": date(2099, 12, 31)}) user = models.Person.query.get(self.user.id) user_email = user.email if is_vf_needed: non_final_states = [CycleTaskGroupObjectTask.ASSIGNED, CycleTaskGroupObjectTask.IN_PROGRESS, CycleTaskGroupObjectTask.FINISHED, CycleTaskGroupObjectTask.DECLINED] final_state = CycleTaskGroupObjectTask.VERIFIED else: non_final_states = [CycleTaskGroupObjectTask.ASSIGNED, CycleTaskGroupObjectTask.IN_PROGRESS] final_state = CycleTaskGroupObjectTask.FINISHED with freeze_time("2017-05-16 08:09:10"): # a day after task1 due date for state in non_final_states: # clear all notifications before before changing the task status models.Notification.query.delete() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) self.wf_generator.modify_object(task1, {"status": state}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) # WITHOUT clearing the overdue notifications, move the task to "verified" # state, and the overdue notification should disappear. self.wf_generator.modify_object(task1, {"status": final_state}) _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertNotIn("task_overdue", user_notifs) @ddt.data(True, False) @patch("ggrc.notifications.common.send_email") def test_stop_sending_overdue_notification_if_task_gets_deleted(self, is_vf_needed, _): """Overdue notifications should not be sent for deleted tasks.""" with freeze_time("2017-05-15 14:25:36"): tmp = self.one_time_workflow.copy() tmp['is_verification_needed'] = is_vf_needed _, workflow = self.wf_generator.generate_workflow(tmp) self.wf_generator.generate_cycle(workflow) response, workflow = self.wf_generator.activate_workflow(workflow) self.assert200(response) tasks = workflow.cycles[0].cycle_task_group_object_tasks task1, task2 = tasks user = models.Person.query.get(self.user.id) user_email = user.email with freeze_time("2017-10-16 08:09:10"): # long after both task due dates _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 2) db.session.delete(task2) db.session.commit() _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user_email, {}) self.assertIn("task_overdue", user_notifs) self.assertEqual(len(user_notifs["task_overdue"]), 1) db.session.delete(task1) db.session.commit() _, notif_data = common.get_daily_notifications() user_notifs = notif_data.get(user.email, {}) self.assertNotIn("task_overdue", user_notifs) def _create_test_cases(self): """Create configuration to use for generating a new workflow.""" def person_dict(person_id): return { "href": "/api/people/" + str(person_id), "id": person_id, "type": "Person" } role_id = models.all_models.AccessControlRole.query.filter( models.all_models.AccessControlRole.name == "Task Assignees", models.all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id self.one_time_workflow = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [person_dict(self.user.id)], "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "some task", "start_date": date(2017, 5, 5), # Friday "end_date": date(2017, 5, 15), "access_control_list": [{ "person": {"id": self.user.id, }, "ac_role_id": role_id, }], }, { "title": "task 2", "description": "some task 2", "start_date": date(2017, 5, 5), # Friday "end_date": date(2017, 5, 16), "access_control_list": [{ "person": {"id": self.user.id, }, "ac_role_id": role_id, }], }], "task_group_objects": self.random_objects }] }
class TestCycleTaskStatusChange(TestCase): """ This class contains simple one time workflow tests that are not in the gsheet test grid """ def setUp(self): TestCase.setUp(self) self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() Notification.query.delete() self.random_objects = self.object_generator.generate_random_objects(2) _, self.user = self.object_generator.generate_person( user_role="Administrator") self.create_test_cases() def init_decorator(init): def new_init(self, *args, **kwargs): init(self, *args, **kwargs) if hasattr(self, "created_at"): self.created_at = datetime.now() return new_init Notification.__init__ = init_decorator(Notification.__init__) def test_task_declined_notification_created(self): with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Declined") notif = db.session.query(Notification).filter( and_( Notification.object_id == task1.id, Notification.object_type == task1.type, Notification.sent_at == None, # noqa Notification.notification_type == self.get_notification_type("cycle_task_declined"))).all() self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) def test_all_tasks_finished_notification_created(self): with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) notif = db.session.query(Notification).filter( and_( Notification.object_id == cycle.id, Notification.object_type == cycle.type, Notification.sent_at == None, # noqa Notification.notification_type == self. get_notification_type("all_cycle_tasks_completed"))).all() self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) notif = db.session.query(Notification).filter( and_( Notification.object_id == task1.id, Notification.object_type == task1.type, Notification.sent_at == None, # noqa Notification.notification_type != self. get_notification_type("all_cycle_tasks_completed"))).all() self.assertEqual(notif, []) def test_multi_all_tasks_finished_notification_created(self): with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_2) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) notif = db.session.query(Notification).filter( and_( Notification.object_id == cycle.id, Notification.object_type == cycle.type, Notification.sent_at == None, # noqa Notification.notification_type == self. get_notification_type("all_cycle_tasks_completed"))).all() # there is still one task in the cycle, so there should be no # notifications for all tasks completed self.assertEqual(notif, []) notif = db.session.query(Notification).filter( and_( Notification.object_id == task1.id, Notification.object_type == task1.type, Notification.sent_at == None, # noqa Notification.notification_type != self. get_notification_type("all_cycle_tasks_completed"))).all() # The task was verified, so there should be no notifications left for due # dates. self.assertEqual(notif, []) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.task_change_status(task2) notif = db.session.query(Notification).filter( and_( Notification.object_id == cycle.id, Notification.object_type == cycle.type, Notification.sent_at == None, # noqa Notification.notification_type == self. get_notification_type("all_cycle_tasks_completed"))).all() self.assertEqual(len(notif), 1, "notifications: {}".format(str(notif))) @patch("ggrc.notifications.common.send_email") def test_single_task_declined(self, mock_mail): """ test moving the end date to the future, befor due_in and due_today notifications have been sent """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Finished") _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Declined") user = Person.query.get(self.user.id) _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) self.assertIn("task_declined", notif_data[user.email]) @patch("ggrc.notifications.common.send_email") def test_single_task_accepted(self, mock_mail): """ test moving the end date to the future, befor due_in and due_today notifications have been sent """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02"): common.send_daily_digest_notifications() cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1, "Finished") _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-03"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) self.task_change_status(task1) user = Person.query.get(self.user.id) _, notif_data = common.get_daily_notifications() self.assertNotIn(user.email, notif_data) self.assertIn("all_tasks_completed", notif_data["*****@*****.**"]) @patch("ggrc.notifications.common.send_email") def test_end_cycle(self, mock_mail): """ manaually ending a cycle should stop all notifications for that cycle """ with freeze_time("2015-05-01"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-03"): _, notif_data = common.get_daily_notifications() cycle = Cycle.query.get(cycle.id) user = Person.query.get(self.user.id) self.assertIn(user.email, notif_data) self.wf_generator.modify_object(cycle, data={"is_current": False}) cycle = Cycle.query.get(cycle.id) self.assertFalse(cycle.is_current) _, notif_data = common.get_daily_notifications() self.assertNotIn(user.email, notif_data) def create_test_cases(self): def person_dict(person_id): return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } self.one_time_workflow_1 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [person_dict(self.user.id)], "task_groups": [{ "title": "single task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "single task in a wf", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }], }] } self.one_time_workflow_2 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [person_dict(self.user.id)], "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [ { "title": "task 1", "description": "two taks in wf with different objects", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }, { "title": "task 2", "description": "two taks in wf with different objects", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), } ], "task_group_objects": self.random_objects }] } def get_notification_type(self, name): return db.session.query(NotificationType).filter( NotificationType.name == name).one() def task_change_status(self, task, status="Verified"): self.wf_generator.modify_object(task, data={"status": status}) task = CycleTaskGroupObjectTask.query.get(task.id) self.assertEqual(task.status, status)
class TestWfNotifsGenerator(TestCase): """Test wf cycle tasks notifications generation.""" def setUp(self): """Set up.""" super(TestWfNotifsGenerator, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() Notification.query.delete() with freeze_time("2015-05-01 14:29:00"): wf_slug = "wf1" with factories.single_commit(): wf = wf_factories.WorkflowFactory(slug=wf_slug, is_verification_needed=True) task_group = wf_factories.TaskGroupFactory(workflow=wf) wf_factories.TaskGroupTaskFactory(task_group=task_group, title='task1', start_date=datetime.now(), end_date=datetime.now() + timedelta(7)) data = workflow_api.get_cycle_post_dict(wf) self.api.post(all_models.Cycle, data) wf = all_models.Workflow.query.filter_by(slug=wf_slug).one() self.cycle = all_models.Cycle.query.filter_by( workflow_id=wf.id).one() self.ctask = all_models.CycleTaskGroupObjectTask.query.filter_by( cycle_id=self.cycle.id).first() def test_ctasks_notifs_generator_daily_digest(self): """Test cycle tasks notifications generation job.""" with freeze_time("2015-05-01 14:29:00"): self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "manual_cycle_created", "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue") # Move task to Finished self.wf_generator.modify_object(self.ctask, data={"status": "Verified"}) generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "all_cycle_tasks_completed", "manual_cycle_created") # Undo finish self.wf_generator.modify_object(self.ctask, data={"status": "In Progress"}) generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue") self.wf_generator.modify_object(self.ctask, data={"status": "Declined"}) self.assert_notifications_for_object(self.ctask, "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue", "cycle_task_declined") @ddt.data(("2015-05-01 14:29:00", ("all_cycle_tasks_completed", "manual_cycle_created")), ("2015-05-02 07:29:00", ("all_cycle_tasks_completed", "manual_cycle_created")), ("2015-05-02 14:29:00", ("all_cycle_tasks_completed", "manual_cycle_created")), ("2015-05-03 07:29:00", ("manual_cycle_created", ))) @ddt.unpack def test_cycle_task_update_timelines(self, _datetime, notifications): """Test cycle task has been updated: 1) the day before job is called; 2) the same day job is called before 08:00 AM UTC; 3) the same day job is called after 08:00 AM UTC; 4) two days before job is called. """ with freeze_time("2015-05-01 14:29:00"): # Move task to Finished self.wf_generator.modify_object(self.ctask, data={"status": "Verified"}) with freeze_time(_datetime): generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, *notifications) def test_ctasks_notifs_generator_daily_digest_called_twice(self): """No duplicated notifications should be generated""" with freeze_time("2015-05-01 14:29:00"): generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "manual_cycle_created", "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue") # Move task to Finished self.wf_generator.modify_object(self.ctask, data={"status": "Verified"}) generate_cycle_tasks_notifs() generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "all_cycle_tasks_completed", "manual_cycle_created") def test_ctasks_notifs_generator_cron_job(self): """Test cycle tasks notifications generation cron job.""" with freeze_time("2015-05-2 08:00:00"): generate_cycle_tasks_notifs() self.assert_notifications_for_object(self.cycle, "manual_cycle_created") self.assert_notifications_for_object(self.ctask, "manual_cycle_created", "cycle_task_due_in", "cycle_task_due_today", "cycle_task_overdue")
class TestBasicWorkflowActions(TestCase): """ Tests for basic workflow actions """ def setUp(self): super(TestBasicWorkflowActions, self).setUp() self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.random_objects = self.object_generator.generate_random_objects() self.create_test_cases() def tearDown(self): pass def test_create_workflows(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) self.assertIsInstance(wflow, Workflow) task_groups = db.session.query(TaskGroup)\ .filter(TaskGroup.workflow_id == wflow.id).all() self.assertEqual(len(task_groups), len(self.one_time_workflow_1["task_groups"])) def test_workflows(self): for workflow in self.all_workflows: _, wflow = self.generator.generate_workflow(workflow) self.assertIsInstance(wflow, Workflow) task_groups = db.session.query(TaskGroup)\ .filter(TaskGroup.workflow_id == wflow.id).all() self.assertEqual(len(task_groups), len(workflow["task_groups"])) def test_activate_wf(self): for workflow in self.all_workflows: _, wflow = self.generator.generate_workflow(workflow) response, wflow = self.generator.activate_workflow(wflow) self.assert200(response) def test_one_time_workflow_edits(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) wf_dict = {"title": "modified one time wf"} self.generator.modify_workflow(wflow, data=wf_dict) modified_wf = db.session.query(Workflow).filter( Workflow.id == wflow.id).one() self.assertEqual(wf_dict["title"], modified_wf.title) def test_one_time_wf_activate(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) self.generator.generate_cycle(wflow) self.generator.activate_workflow(wflow) tasks = [len(tg.get("task_group_tasks", [])) for tg in self.one_time_workflow_1["task_groups"]] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wflow.id).all() active_wf = db.session.query(Workflow).filter( Workflow.id == wflow.id).one() self.assertEqual(sum(tasks), len(cycle_tasks)) self.assertEqual(active_wf.status, "Active") def test_one_time_wf_state_transition_dates(self): _, wflow = self.generator.generate_workflow(self.one_time_workflow_1) self.generator.generate_cycle(wflow) self.generator.activate_workflow(wflow) cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wflow.id).all() with freeze_time("2015-6-9 13:00:00"): today = dtm.datetime.now() transitions = [ ("InProgress", None, None), ("Finished", today, None), ("Declined", None, None), ("Finished", today, None), ("Verified", today, today), ("Finished", today, None), ] # Iterate over possible transitions and check if dates got set correctly for (status, expected_finished, expected_verified) in transitions: cycle_task = cycle_tasks[0] _, task = self.generator.modify_object(cycle_task, {"status": status}) self.assertEqual(task.finished_date, expected_finished) self.assertEqual(task.verified_date, expected_verified) def test_delete_calls(self): _, workflow = self.generator.generate_workflow() self.generator.generate_task_group(workflow) _, task_group = self.generator.generate_task_group(workflow) task_groups = db.session.query(TaskGroup).filter( TaskGroup.workflow_id == workflow.id).all() self.assertEqual(len(task_groups), 2) response = self.generator.api.delete(task_group) self.assert200(response) task_groups = db.session.query(TaskGroup).filter( TaskGroup.workflow_id == workflow.id).all() self.assertEqual(len(task_groups), 1) def create_test_cases(self): self.quarterly_wf_1 = { "title": "quarterly wf 1", "description": "", "unit": "month", "repeat_every": 3, "task_groups": [{ "title": "tg_1", "task_group_tasks": [{ "description": factories.random_str(100), }, { "description": factories.random_str(100), }, { "description": factories.random_str(100), }, ], }, ] } self.weekly_wf_1 = { "title": "weekly thingy", "description": "start this many a time", "unit": "week", "repeat_every": 1, "task_groups": [{ "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100), }, { "title": "monday task", }, { "title": "weekend task", }, ], "task_group_objects": self.random_objects }, ] } self.one_time_workflow_1 = { "title": "one time wf test", "description": "some test workflow", "task_groups": [{ "title": "tg_1", "task_group_tasks": [{}, {}, {}] }, { "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100) }, {} ], "task_group_objects": self.random_objects[:2] }, { "title": "tg_3", "task_group_tasks": [{ "title": "simple task 1", "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) } ], "task_group_objects": self.random_objects } ] } self.one_time_workflow_2 = { "title": "test_wf_title", "description": "some test workflow", "task_groups": [ { "title": "tg_1", "task_group_tasks": [{}, {}, {}] }, { "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100) }, {}], "task_group_objects": self.random_objects[:2] }, { "title": "tg_3", "task_group_tasks": [{ "title": "simple task 1", "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }, { "title": factories.random_str(), "description": factories.random_str(100) }], "task_group_objects": [] } ] } self.monthly_workflow_1 = { "title": "monthly test wf", "description": "start this many a time", "unit": "month", "repeat_every": 1, "task_groups": [ { "title": "tg_2", "task_group_tasks": [{ "description": factories.random_str(100), }, { "title": "monday task", }, { "title": "weekend task", }], "task_group_objects": self.random_objects }, ] } self.all_workflows = [ self.one_time_workflow_1, self.one_time_workflow_2, self.weekly_wf_1, self.monthly_workflow_1, self.quarterly_wf_1, ]
class TestCycleTaskImportUpdate(TestCase): """ This class contains simple cycle task update tests using import functionality """ CSV_DIR = join(abspath(dirname(__file__)), "test_csvs/") def setUp(self): super(TestCycleTaskImportUpdate, self).setUp() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.random_objects = self.object_generator.generate_random_objects(2) _, self.person_1 = self.object_generator.generate_person( user_role="Administrator") self.ftime_active = "2016-07-01" self.ftime_historical = "2014-05-01" self._create_test_cases_data() # It is needed because cycle-tasks are generated automatically with # 'slug' based on auto_increment 'id' field. # At start of each test we suppose that created cycle-task's 'slug' # lie in range from 1 to 10. db.session.execute('ALTER TABLE cycle_task_group_object_tasks ' 'AUTO_INCREMENT = 1') def test_cycle_task_correct(self): """Test cycle task update via import with correct data""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_correct.csv") self._check_csv_response(response, {}) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_warnings(self): """Test cycle task update via import with data which is the reason of warnings about non-importable columns.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_warnings.csv") self._check_csv_response(response, self.expected_warnings) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_create_error(self): """Test cycle task update via import with data which is the reason of errors about new cycle task creation.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_create_error.csv") self._check_csv_response(response, self.expected_create_error) self._cmp_tasks(self.expected_cycle_task_correct) def test_cycle_task_date_error(self): """Test cycle task update via import with data which is the reason of errors about incorrect dates in csv file.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): response = self.import_file("cycle_task_date_error.csv") self._check_csv_response(response, self.expected_date_error) self._cmp_tasks(self.expected_cycle_task_date_error) def test_cycle_task_permission_error(self): """Test cycle task update via import with non-admin user which is the reason of error. Only admin can update cycle tasks via import.""" self._generate_cycle_tasks() with freeze_time(self.ftime_active): _, creator = self.object_generator.generate_person( user_role="Creator") response = self.import_file("cycle_task_correct.csv", person=creator) self._check_csv_response(response, self.expected_permission_error) # Cycle tasks' data shouldn't be changed in test DB after import run from # non-admin user expected_cycle_task_permission_error = {} expected_cycle_task_permission_error.update( self.generated_cycle_tasks_active) expected_cycle_task_permission_error.update( self.generated_cycle_tasks_historical) self._cmp_tasks(expected_cycle_task_permission_error) def _cmp_tasks(self, expected_ctasks): """Compare tasks values from argument's list and test DB.""" for ctask in db.session.query(CycleTaskGroupObjectTask).all(): if ctask.slug not in expected_ctasks: continue exp_task = expected_ctasks[ctask.slug] for attr, val in exp_task.iteritems(): self.assertEqual(str(getattr(ctask, attr, None)), val) # pylint: disable=too-many-arguments def _activate_workflow(self, ftime, workflow, task_group, task_group_tasks, random_object, cycle_tasks): """Helper which is responsible for active cycle-tasks creation""" with freeze_time(ftime): _, wf = self.wf_generator.generate_workflow(workflow) _, tg = self.wf_generator.generate_task_group(wf, task_group) for task in task_group_tasks: self.wf_generator.generate_task_group_task(tg, task) self.wf_generator.generate_task_group_object(tg, random_object) _, cycle = self.wf_generator.generate_cycle(wf) self.wf_generator.activate_workflow(wf) for exp_slug, exp_task in cycle_tasks.iteritems(): obj = db.session.query(CycleTaskGroupObjectTask).filter_by( slug=exp_slug).first() if exp_task["status"] == "Verified": self.wf_generator.modify_object(obj, {"status": "Finished"}) self.wf_generator.modify_object(obj, {"status": exp_task["status"]}) self._cmp_tasks(cycle_tasks) return cycle def _generate_cycle_tasks(self): """Helper which is responsible for test data creation""" self._activate_workflow(self.ftime_active, self.workflow_active, self.task_group_active, self.task_group_tasks_active, self.random_objects[0], self.generated_cycle_tasks_active) cycle = self._activate_workflow(self.ftime_historical, self.workflow_historical, self.task_group_historical, self.task_group_tasks_historical, self.random_objects[1], self.generated_cycle_tasks_historical) with freeze_time(self.ftime_historical): cycle = Cycle.query.get(cycle.id) self.wf_generator.modify_object(cycle, data={"is_current": False}) def _create_test_cases_data(self): """Create test cases data: for object generation, expected data for checks""" def person_dict(person_id): """Return person data""" return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } self.workflow_active = { "title": "workflow active title", "description": "workflow active description", "frequency": "one_time", "owners": [person_dict(self.person_1.id)], "notify_on_change": False, } self.task_group_active = { "title": "task group active title", "contact": person_dict(self.person_1.id), } self.task_group_tasks_active = [{ "title": "task active title 1", "description": "task active description 1", "contact": person_dict(self.person_1.id), "start_date": "07/01/2016", "end_date": "07/06/2016", }, { "title": "task active title 2", "description": "task active description 2", "contact": person_dict(self.person_1.id), "start_date": "07/07/2016", "end_date": "07/12/2016", }, { "title": "task active title 3", "description": "task active description 3", "contact": person_dict(self.person_1.id), "start_date": "07/13/2016", "end_date": "07/18/2016", }, { "title": "task active title 4", "description": "task active description 4", "contact": person_dict(self.person_1.id), "start_date": "07/19/2016", "end_date": "07/24/2016", }, { "title": "task active title 5", "description": "task active description 5", "contact": person_dict(self.person_1.id), "start_date": "07/25/2016", "end_date": "07/30/2016", }] # Active cycle tasks which should be generated from previous structure # at the beginning of each test self.generated_cycle_tasks_active = { "CYCLETASK-1": { "title": self.task_group_tasks_active[0]["title"], "description": self.task_group_tasks_active[0]["description"], "start_date": "2016-07-01", "end_date": "2016-07-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-2": { "title": self.task_group_tasks_active[1]["title"], "description": self.task_group_tasks_active[1]["description"], "start_date": "2016-07-07", "end_date": "2016-07-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-3": { "title": self.task_group_tasks_active[2]["title"], "description": self.task_group_tasks_active[2]["description"], "start_date": "2016-07-13", "end_date": "2016-07-18", "finished_date": "None", "verified_date": "None", "status": "InProgress" }, "CYCLETASK-4": { "title": self.task_group_tasks_active[3]["title"], "description": self.task_group_tasks_active[3]["description"], "start_date": "2016-07-19", "end_date": "2016-07-24", "finished_date": "2016-07-01 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-5": { "title": self.task_group_tasks_active[4]["title"], "description": self.task_group_tasks_active[4]["description"], "start_date": "2016-07-25", "end_date": "2016-07-30", "finished_date": "2016-07-01 00:00:00", "verified_date": "2016-07-01 00:00:00", "status": "Verified" } } self.workflow_historical = { "title": "workflow historical title", "description": "workflow historical description", "frequency": "one_time", "owners": [person_dict(self.person_1.id)], "notify_on_change": False, } self.task_group_historical = { "title": "task group historical title", "contact": person_dict(self.person_1.id), } self.task_group_tasks_historical = [ { "title": "task historical title 1", "description": "task historical description 1", "contact": person_dict(self.person_1.id), "start_date": "05/01/2014", "end_date": "05/06/2014", }, { "title": "task historical title 2", "description": "task historical description 2", "contact": person_dict(self.person_1.id), "start_date": "05/07/2014", "end_date": "05/12/2014", }, { "title": "task historical title 3", "description": "task historical description 3", "contact": person_dict(self.person_1.id), "start_date": "05/13/2014", "end_date": "05/18/2014", }, { "title": "task historical title 4", "description": "task historical description 4", "contact": person_dict(self.person_1.id), "start_date": "05/19/2014", "end_date": "05/24/2014", }, { "title": "task historical title 5", "description": "task historical description 5", "contact": person_dict(self.person_1.id), "start_date": "05/25/2014", "end_date": "05/30/2014", }, ] # Historical cycle tasks which should be generated from previous structure # at the beginning of each test. self.generated_cycle_tasks_historical = { "CYCLETASK-6": { "title": self.task_group_tasks_historical[0]["title"], "description": self.task_group_tasks_historical[0]["description"], "start_date": "2014-05-01", "end_date": "2014-05-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-7": { "title": self.task_group_tasks_historical[1]["title"], "description": self.task_group_tasks_historical[1]["description"], "start_date": "2014-05-07", "end_date": "2014-05-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-8": { "title": self.task_group_tasks_historical[2]["title"], "description": self.task_group_tasks_historical[2]["description"], "start_date": "2014-05-13", "end_date": "2014-05-18", "finished_date": "None", "verified_date": "None", "status": "InProgress" }, "CYCLETASK-9": { "title": self.task_group_tasks_historical[3]["title"], "description": self.task_group_tasks_historical[3]["description"], "start_date": "2014-05-19", "end_date": "2014-05-24", "finished_date": "2014-05-01 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-10": { "title": self.task_group_tasks_historical[4]["title"], "description": self.task_group_tasks_historical[4]["description"], "start_date": "2014-05-25", "end_date": "2014-05-30", "finished_date": "2014-05-01 00:00:00", "verified_date": "2014-05-01 00:00:00", "status": "Verified" } } # Expected cycle tasks which should be created in correct cycle task update # case. It is needed for most tests. self.expected_cycle_task_correct = { "CYCLETASK-1": { "title": self.task_group_tasks_active[0]["title"] + " one", "description": self.task_group_tasks_active[0]["description"] + " one", "start_date": "2016-06-01", "end_date": "2016-06-06", "finished_date": "None", "verified_date": "None", "status": "Assigned" }, "CYCLETASK-2": { "title": self.task_group_tasks_active[1]["title"] + " two", "description": self.task_group_tasks_active[1]["description"] + " two", "start_date": "2016-06-07", "end_date": "2016-06-12", "finished_date": "None", "verified_date": "None", "status": "Declined" }, "CYCLETASK-3": { "title": self.task_group_tasks_active[2]["title"] + " three", "description": self.task_group_tasks_active[2]["description"] + " three", "start_date": "2016-06-13", "end_date": "2016-06-18", "finished_date": "None", "verified_date": "None", "status": "InProgress" }, "CYCLETASK-4": { "title": self.task_group_tasks_active[3]["title"] + " four", "description": self.task_group_tasks_active[3]["description"] + " four", "start_date": "2016-06-19", "end_date": "2016-06-24", "finished_date": "2016-07-19 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-5": { "title": self.task_group_tasks_active[4]["title"] + " five", "description": self.task_group_tasks_active[4]["description"] + " five", "start_date": "2016-06-25", "end_date": "2016-06-30", "finished_date": "2016-07-25 00:00:00", "verified_date": "2016-08-30 00:00:00", "status": "Verified" }, "CYCLETASK-6": { "title": self.task_group_tasks_historical[0]["title"] + " one", "description": self.task_group_tasks_historical[0]["description"] + " one", "start_date": "2014-04-01", "end_date": "2014-04-06", "finished_date": "2014-05-01 00:00:00", "verified_date": "2014-06-06 00:00:00", "status": "Assigned" }, "CYCLETASK-7": { "title": self.task_group_tasks_historical[1]["title"] + " two", "description": self.task_group_tasks_historical[1]["description"] + " two", "start_date": "2014-04-07", "end_date": "2014-04-12", "finished_date": "2014-05-07 00:00:00", "verified_date": "None", "status": "Declined" }, "CYCLETASK-8": { "title": self.task_group_tasks_historical[2]["title"] + " three", "description": self.task_group_tasks_historical[2]["description"] + " three", "start_date": "2014-04-13", "end_date": "2014-04-18", "finished_date": "2014-05-13 00:00:00", "verified_date": "2014-06-18 00:00:00", "status": "InProgress" }, "CYCLETASK-9": { "title": self.task_group_tasks_historical[3]["title"] + " four", "description": self.task_group_tasks_historical[3]["description"] + " four", "start_date": "2014-04-19", "end_date": "2014-04-24", "finished_date": "2014-05-19 00:00:00", "verified_date": "None", "status": "Finished" }, "CYCLETASK-10": { "title": self.task_group_tasks_historical[4]["title"] + " five", "description": self.task_group_tasks_historical[4]["description"] + " five", "start_date": "2014-04-25", "end_date": "2014-04-30", "finished_date": "2014-05-25 00:00:00", "verified_date": "2014-06-30 00:00:00", "status": "Verified" } } # Below is description of warning for non-importable columns. It is needed # for test_cycle_task_warnings. importable_column_names = [] for field_name in CycleTaskGroupObjectTask.IMPORTABLE_FIELDS: if field_name == 'slug': continue # pylint: disable=protected-access importable_column_names.append( CycleTaskGroupObjectTask._aliases.get(field_name, field_name)) self.expected_warnings = { 'Cycle Task Group Object Task': { 'block_warnings': { errors.ONLY_IMPORTABLE_COLUMNS_WARNING.format( line=2, columns=", ".join(importable_column_names)) }, } } # This is an error message which should be shown during # test_cycle_task_create_error test self.expected_create_error = { 'Cycle Task Group Object Task': { 'row_errors': {errors.CREATE_INSTANCE_ERROR.format(line=13)} } } # Below is expected date errors for test_cycle_task_date_error. They should # be shown during date validator's tests. self.expected_date_error = { 'Cycle Task Group Object Task': { 'row_errors': { errors.INVALID_START_END_DATES.format( line=3, start_date="Start Date", end_date="End Date", ), errors.INVALID_STATUS_DATE_CORRELATION.format( line=4, date="Actual Finish Date", status="not Finished", ), errors.INVALID_STATUS_DATE_CORRELATION.format( line=5, date="Actual Verified Date", status="not Verified", ), errors.INVALID_STATUS_DATE_CORRELATION.format( line=6, date="Actual Verified Date", status="not Verified", ), errors.INVALID_START_END_DATES.format( line=7, start_date="Actual Finish Date", end_date="Actual Verified Date", ), errors.INVALID_START_END_DATES.format( line=8, start_date="Start Date", end_date="End Date", ), errors.MISSING_VALUE_ERROR.format( line=9, column_name="Actual Finish Date", ), errors.INVALID_START_END_DATES.format( line=10, start_date="Actual Finish Date", end_date="Actual Verified Date", ), }, } } # Below is expected cycle-tasks data which should appear in test DB after # test_cycle_task_date_error run self.expected_cycle_task_date_error = dict() self.expected_cycle_task_date_error.update( self.generated_cycle_tasks_active) self.expected_cycle_task_date_error.update( self.generated_cycle_tasks_historical) self.expected_cycle_task_date_error["CYCLETASK-9"] = ( self.expected_cycle_task_correct["CYCLETASK-9"]) self.expected_cycle_task_date_error["CYCLETASK-10"] = ( self.expected_cycle_task_correct["CYCLETASK-10"]) # Expected error message which should be shown after # test_cycle_task_permission_error run self.expected_permission_error = { 'Cycle Task Group Object Task': { 'block_errors': {errors.PERMISSION_ERROR.format(line=2)} } }
class TestPersonResource(TestCase, WithQueryApi): """Tests for special people api endpoints.""" def setUp(self): super(TestPersonResource, self).setUp() self.client.get("/login") self.generator = WorkflowsGenerator() def test_task_count_empty(self): """Test query count without any workflows and tasks.""" user = all_models.Person.query.first() response = self.client.get("/api/people/{}/task_count".format(user.id)) self.assertEqual(response.json, { "open_task_count": 0, "has_overdue": False }) @ddt.data( (True, [ ("task 1", "Finished", 3, True), ("task 1", "Verified", 2, True), ("task 2", "Declined", 2, True), ("task 2", "Verified", 1, False), ("task 2", "Finished", 2, True), ("task 3", "Verified", 1, True), ("task 2", "Verified", 0, False), ]), (False, [ ("task 1", "Finished", 2, True), ("task 2", "InProgress", 2, True), ("task 2", "Finished", 1, False), ("task 3", "Finished", 0, False), ]), ) @ddt.unpack def test_task_count(self, is_verification_needed, transitions): """Test person task counts. This tests checks for correct task counts - with inactive workflows and - with overdue tasks - without overdue tasks - with finished overdue tasks The four checks are done in a single test due to complex differences between tests that make ddt cumbersome and the single test also improves integration test performance due to slow workflow setup stage. """ # pylint: disable=too-many-locals user = all_models.Person.query.first() dummy_user = factories.PersonFactory() user_id = user.id role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id one_time_workflow = { "title": "Person resource test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [create_stub(user)], "is_verification_needed": is_verification_needed, "task_groups": [{ "title": "one time task group", "contact": create_stub(user), "task_group_tasks": [ { "title": "task 1", "description": "some task", "access_control_list": [{ "person": create_stub(user), "ac_role_id": role_id, }], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }, { "title": "task 2", "description": "some task 3", "access_control_list": [{ "person": create_stub(user), "ac_role_id": role_id, }], "start_date": date(2017, 5, 5), "end_date": date(2017, 9, 16), }, { "title": "task 3", "description": "some task 4", "access_control_list": [{ "person": create_stub(user), "ac_role_id": role_id, }, { "person": create_stub(dummy_user), "ac_role_id": role_id, }], "start_date": date(2017, 6, 5), "end_date": date(2017, 10, 16), }, { "title": "dummy task 4", # task should not counted "description": "some task 4", "access_control_list": [{ "person": create_stub(dummy_user), "ac_role_id": role_id, }], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 17), }, { "title": "dummy task 5", # task should not counted "description": "some task 4", "access_control_list": [{ "person": create_stub(dummy_user), "ac_role_id": role_id, }], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 18), } ], "task_group_objects": [] }] } inactive_workflow = { "title": "Activated workflow with archived cycles", "notify_on_change": True, "description": "Extra test workflow", "owners": [create_stub(user)], "task_groups": [{ "title": "Extra task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "not counted existing task", "description": "", "access_control_list": [{ "person": create_stub(user), "ac_role_id": role_id, }], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }], "task_group_objects": [] }] } with freeze_time("2017-10-16 05:09:10"): self.client.get("/login") # Activate normal one time workflow _, workflow = self.generator.generate_workflow(one_time_workflow) _, cycle = self.generator.generate_cycle(workflow) tasks = {t.title: t for t in cycle.cycle_task_group_object_tasks} _, workflow = self.generator.activate_workflow(workflow) # Activate and close the inactive workflow _, workflow = self.generator.generate_workflow(inactive_workflow) _, cycle = self.generator.generate_cycle(workflow) _, workflow = self.generator.activate_workflow(workflow) self.generator.modify_object(cycle, data={"is_current": False}) with freeze_time("2017-7-16 07:09:10"): self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 3, "has_overdue": False }) with freeze_time("2017-10-16 08:09:10"): # same day as task 3 end date self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 3, "has_overdue": True }) for task, status, count, overdue in transitions: self.generator.modify_object(tasks[task], data={"status": status}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": count, "has_overdue": overdue }) def test_task_count_multiple_wfs(self): """Test task count with both verified and non verified workflows. This checks task counts with 4 tasks 2017, 8, 15 - verification needed 2017, 11, 18 - verification needed 2017, 8, 15 - No verification needed 2017, 11, 18 - No verification needed """ user = all_models.Person.query.first() user_id = user.id role_id = all_models.AccessControlRole.query.filter( all_models.AccessControlRole.name == "Task Assignees", all_models.AccessControlRole.object_type == "TaskGroupTask", ).one().id workflow_template = { "title": "verified workflow", "owners": [create_stub(user)], "is_verification_needed": True, "task_groups": [{ "title": "one time task group", "contact": create_stub(user), "task_group_tasks": [{ "title": "task 1", "description": "some task", "access_control_list": [{ "person": create_stub(user), "ac_role_id": role_id, }], "start_date": date(2017, 5, 5), "end_date": date(2017, 8, 15), }, { "title": "dummy task 5", "description": "some task 4", "access_control_list": [{ "person": create_stub(user), "ac_role_id": role_id, }], "start_date": date(2017, 6, 5), "end_date": date(2017, 11, 18), }], "task_group_objects": [] }] } with freeze_time("2017-10-16 05:09:10"): self.client.get("/login") verified_workflow = workflow_template.copy() verified_workflow["is_verification_needed"] = True _, workflow = self.generator.generate_workflow(verified_workflow) _, cycle = self.generator.generate_cycle(workflow) verified_tasks = { task.title: task for task in cycle.cycle_task_group_object_tasks } _, workflow = self.generator.activate_workflow(workflow) non_verified_workflow = workflow_template.copy() non_verified_workflow["is_verification_needed"] = False _, workflow = self.generator.generate_workflow( non_verified_workflow) _, cycle = self.generator.generate_cycle(workflow) non_verified_tasks = { task.title: task for task in cycle.cycle_task_group_object_tasks } _, workflow = self.generator.activate_workflow(workflow) with freeze_time("2017-7-16 07:09:10"): self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 4, "has_overdue": False }) with freeze_time("2017-10-16 08:09:10"): self.client.get("/login") response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 4, "has_overdue": True }) # transition 1, task that needs verification goes to finished state. This # transition should not change anything self.generator.modify_object(verified_tasks["task 1"], data={"status": "Finished"}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 4, "has_overdue": True }) # transition 2, task that needs verification goes to verified state. This # transition should reduce task count. self.generator.modify_object(verified_tasks["task 1"], data={"status": "Verified"}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 3, "has_overdue": True }) # transition 3, task that does not need verification goes into Finished # state. This transition should reduce task count and remove all overdue # tasks self.generator.modify_object(non_verified_tasks["task 1"], data={"status": "Finished"}) response = self.client.get( "/api/people/{}/task_count".format(user_id)) self.assertEqual(response.json, { "open_task_count": 2, "has_overdue": False })
class TestOneTimeWfEndDateChange(TestCase): """ This class contains simple one time workflow tests that are not in the gsheet test grid """ def setUp(self): super(TestOneTimeWfEndDateChange, self).setUp() self.api = Api() self.wf_generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() Notification.query.delete() self.random_objects = self.object_generator.generate_random_objects(2) _, self.user = self.object_generator.generate_person( user_role="Administrator") self.create_test_cases() def init_decorator(init): def new_init(self, *args, **kwargs): init(self, *args, **kwargs) if hasattr(self, "created_at"): self.created_at = datetime.now() return new_init Notification.__init__ = init_decorator(Notification.__init__) @patch("ggrc.notifications.common.send_email") def test_no_date_change(self, mock_mail): def get_person(person_id): return db.session.query(Person).filter(Person.id == person_id).one() with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-04-11 03:21:34"): user = get_person(self.user.id) _, notif_data = common.get_daily_notifications() self.assertIn("cycle_started", notif_data[user.email]) with freeze_time("2015-05-02 03:21:34"): _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) self.assertIn("cycle_started", notif_data[user.email]) self.assertNotIn("due_in", notif_data[user.email]) self.assertNotIn("due_today", notif_data[user.email]) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to owner and one to assigne self.assertEqual(mock_mail.call_count, 2) with freeze_time("2015-05-04 03:21:34"): # one day before due date _, notif_data = common.get_daily_notifications() user = get_person(self.user.id) self.assertIn("due_in", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_in"]), 2) with freeze_time("2015-05-04 03:21:34"): # one day before due date common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to owner and one to assigne self.assertEqual(mock_mail.call_count, 3) with freeze_time("2015-05-05 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertIn("due_today", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_today"]), 2) @patch("ggrc.notifications.common.send_email") def test_move_end_date_to_future(self, mock_mail): """ test moving the end date to the future, befor due_in and due_today notifications have been sent """ def get_person(person_id): return db.session.query(Person).filter(Person.id == person_id).one() with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-04-11 03:21:34"): user = get_person(self.user.id) _, notif_data = common.get_daily_notifications() self.assertIn("cycle_started", notif_data[user.email]) with freeze_time("2015-05-02 03:21:34"): _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) self.assertIn("cycle_started", notif_data[user.email]) self.assertNotIn("due_in", notif_data[user.email]) self.assertNotIn("due_today", notif_data[user.email]) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to owner and one to assigne self.assertEqual(mock_mail.call_count, 2) with freeze_time("2015-05-03 03:21:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.wf_generator.modify_object( task1, data={"end_date": date(2015, 5, 15)}) self.wf_generator.modify_object( task2, data={"end_date": date(2015, 5, 15)}) with freeze_time("2015-05-04 03:21:34"): # one day befor due date _, notif_data = common.get_daily_notifications() user = get_person(self.user.id) self.assertEqual(notif_data, {}) with freeze_time("2015-05-05 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-14 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) self.assertIn("due_in", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_in"]), len(self.random_objects)) with freeze_time("2015-05-15 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) # yesterdays mail has not been sent self.assertIn("due_in", notif_data[user.email]) self.assertIn("due_today", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_today"]), len(self.random_objects)) @patch("ggrc.notifications.common.send_email") def test_move_end_date_to_past(self, mock_mail): def get_person(person_id): return db.session.query(Person).filter(Person.id == person_id).one() with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to owner and one to assigne self.assertEqual(mock_mail.call_count, 2) with freeze_time("2015-05-03 03:21:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.wf_generator.modify_object( task1, data={"end_date": date(2015, 5, 1)}) self.wf_generator.modify_object( task2, data={"end_date": date(2015, 5, 1)}) with freeze_time("2015-05-03 03:21:34"): # one day befor due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-04 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) with freeze_time("2015-05-05 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) @patch("ggrc.notifications.common.send_email") def test_move_end_date_to_today(self, mock_mail): def get_person(person_id): return db.session.query(Person).filter(Person.id == person_id).one() with freeze_time("2015-04-10 03:21:34"): _, workflow = self.wf_generator.generate_workflow( self.one_time_workflow_1) _, cycle = self.wf_generator.generate_cycle(workflow) self.wf_generator.activate_workflow(workflow) with freeze_time("2015-05-02 03:21:34"): common.send_daily_digest_notifications() _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) # one email to owner and one to assigne self.assertEqual(mock_mail.call_count, 2) with freeze_time("2015-05-03 03:21:34"): cycle = Cycle.query.get(cycle.id) task1 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[0].id) task2 = CycleTaskGroupObjectTask.query.get( cycle.cycle_task_group_object_tasks[1].id) self.wf_generator.modify_object( task1, data={"end_date": date(2015, 5, 3)}) self.wf_generator.modify_object( task2, data={"end_date": date(2015, 5, 4)}) with freeze_time("2015-05-03 03:21:34"): # one day befor due date user = get_person(self.user.id) _, notif_data = common.get_daily_notifications() self.assertNotEquals(notif_data, {}) self.assertIn(user.email, notif_data) self.assertIn("due_today", notif_data[user.email]) self.assertIn("due_in", notif_data[user.email]) self.assertEqual(len(notif_data[user.email]["due_today"]), 1) common.send_daily_digest_notifications() with freeze_time("2015-05-04 03:21:34"): # due date user = get_person(self.user.id) _, notif_data = common.get_daily_notifications() self.assertIn(user.email, notif_data) self.assertIn("due_today", notif_data[user.email]) self.assertNotIn("due_in", notif_data[user.email]) common.send_daily_digest_notifications() with freeze_time("2015-05-05 03:21:34"): # due date _, notif_data = common.get_daily_notifications() self.assertEqual(notif_data, {}) def create_test_cases(self): def person_dict(person_id): return { "href": "/api/people/%d" % person_id, "id": person_id, "type": "Person" } self.one_time_workflow_1 = { "title": "one time test workflow", "notify_on_change": True, "description": "some test workflow", "owners": [person_dict(self.user.id)], "task_groups": [{ "title": "one time task group", "contact": person_dict(self.user.id), "task_group_tasks": [{ "title": "task 1", "description": "some task", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }, { "title": "task 2", "description": "some task 2", "contact": person_dict(self.user.id), "start_date": date(2015, 5, 1), # friday "end_date": date(2015, 5, 5), }], "task_group_objects": self.random_objects }] }
class TestWorkflowCycleStatePropagantion(TestCase): """Test case for cycle task to cycle task group status propagation""" def setUp(self): super(TestWorkflowCycleStatePropagantion, self).setUp() self.api = Api() self.generator = WorkflowsGenerator() self.object_generator = ObjectGenerator() self.weekly_wf = { "title": "weekly thingy", "description": "start this many a time", "frequency": "weekly", "task_groups": [ { "title": "weekly task group", "task_group_tasks": [{ "title": "weekly task 1", "relative_end_day": 1, "relative_end_month": None, "relative_start_day": 5, "relative_start_month": None, }, { "title": "weekly task 1", "relative_end_day": 1, "relative_end_month": None, "relative_start_day": 1, "relative_start_month": None, }] }, ] } def test_weekly_state_transitions_assigned_inprogress(self): "Test that starting one cycle task changes cycle task group" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] self.assertEqual(ctg.status, "Assigned") cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks for cycle_task in cycle_tasks: self.assertEqual(cycle_task.status, "Assigned") # Move one task to InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) self.assertEqual(first_ct.status, "InProgress") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo operation _, first_ct = self.generator.modify_object(first_ct, {"status": "Assigned"}) self.assertEqual(first_ct.status, "Assigned") ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Assigned") # Move both to in progress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "InProgress") # Undo one cycle task _, first_ct = self.generator.modify_object(first_ct, {"status": "Assigned"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Undo second cycle task _, second_ct = self.generator.modify_object( second_ct, {"status": "Assigned"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get( first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Assigned") self.assertEqual(second_ct.status, "Assigned") self.assertEqual(ctg.status, "Assigned") def test_weekly_state_transitions_inprogress_finished(self): "Test In Progress to Finished transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) # Test that moving one task to finished doesn't finish entire cycle _, first_ct = self.generator.modify_object(first_ct, {"status": "Finished"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Finished") self.assertEqual(second_ct.status, "InProgress") self.assertEqual(ctg.status, "InProgress") # Test moving second task to Finished - entire cycle should be finished _, second_ct = self.generator.modify_object( second_ct, {"status": "Finished"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get( first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(second_ct.status, "Finished") self.assertEqual(first_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Undo one task, cycle should be InProgress _, first_ct = self.generator.modify_object( first_ct, {"status": "InProgress"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "InProgress") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress") def test_weekly_state_transitions_finished_verified(self): "Test Finished to Verified transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) self.generator.modify_object(cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") for cycle_task in cycle_tasks: cycle_task = db.session.query(CycleTaskGroupObjectTask).get( cycle_task.id) self.assertEqual(cycle_task.status, "Finished") # Verify first CT _, first_ct = self.generator.modify_object(first_ct, {"status": "Verified"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "Finished") # Verify second CT _, second_ct = self.generator.modify_object( second_ct, {"status": "Verified"}) first_ct = db.session.query(CycleTaskGroupObjectTask).get( first_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Verified") self.assertEqual(second_ct.status, "Verified") self.assertEqual(ctg.status, "Verified") def test_weekly_state_transitions_finished_declined(self): "Test Finished to Declined transitions" _, wf = self.generator.generate_workflow(self.weekly_wf) with freeze_time("2016-6-10 13:00:00"): # Friday, 6/10/2016 self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() first_ct, second_ct = cycle_tasks # Move both tasks to InProgress for cycle_task in cycle_tasks: self.generator.modify_object(cycle_task, {"status": "InProgress"}) self.generator.modify_object(cycle_task, {"status": "Finished"}) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") # Decline first CT _, first_ct = self.generator.modify_object(first_ct, {"status": "Declined"}) second_ct = db.session.query(CycleTaskGroupObjectTask).get( second_ct.id) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(first_ct.status, "Declined") self.assertEqual(second_ct.status, "Finished") self.assertEqual(ctg.status, "InProgress") def test_deleted_task_state_transitions(self): """Test InProgress to Finished transition after task is deleted""" _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] first_ct, second_ct = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() # Move first task to InProgress self.generator.modify_object(first_ct, {"status": "InProgress"}) self.generator.modify_object(first_ct, {"status": "Finished"}) # Delete second task response = self.generator.api.delete(second_ct) self.assert200(response) ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished") def test_cycle_change_on_ct_status_transition(self): """Test cycle is_current change on task Finished to InProgress transition """ _, wf = self.generator.generate_workflow(self.weekly_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).one() c_id = ctg.cycle.id first_ct, second_ct = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() self.generator.modify_object(first_ct, {"status": "Verified"}) self.generator.modify_object(second_ct, {"status": "Verified"}) # cycle now should have is_current == False cycle = db.session.query(Cycle).get(c_id) self.assertEqual(cycle.is_current, False) # Move second task back to InProgress self.generator.modify_object(second_ct, {"status": "InProgress"}) # cycle now should have is_current == True cycle = db.session.query(Cycle).get(ctg.cycle.id) self.assertEqual(cycle.is_current, True) def test_async_request_state_transitions(self): """Test asynchronous transitions""" def change_state(cycle_task, status): self.generator.api.put(cycle_task, {"status": status}) updated_wf = deepcopy(self.weekly_wf) updated_wf["task_groups"][0]["task_group_tasks"].extend([{ "title": "weekly task 1" } for _ in xrange(3)]) _, wf = self.generator.generate_workflow(updated_wf) self.generator.activate_workflow(wf) ctg = db.session.query(CycleTaskGroup).join(Cycle).join( Workflow).filter(Workflow.id == wf.id).all()[0] cycle_tasks = db.session.query(CycleTaskGroupObjectTask).join( Cycle).join(Workflow).filter(Workflow.id == wf.id).all() # Move all tasks to InProgress threads = [] for cycle_task in cycle_tasks: change_state(cycle_task, "InProgress") threads.append( Thread(target=change_state, args=(cycle_task, "Finished"))) for t in threads: t.start() for t in threads: t.join() db.session.commit() ctg = db.session.query(CycleTaskGroup).get(ctg.id) self.assertEqual(ctg.status, "Finished")