def test_accept_deleted_version(self): date1 = self._store_fetched_table() date2 = self._store_fetched_table() self.step.notifications = False self.step.stored_data_version = date1 self.step.save() delta = self.run_with_async_db( commands.do( SetStepDataVersion, workflow_id=self.workflow.id, step=self.step, new_version=date2, ) ) self.step.stored_objects.get(stored_at=date1).delete() self.run_with_async_db(commands.undo(self.workflow.id)) self.step.refresh_from_db() self.assertEqual(self.step.stored_data_version, date1) self.run_with_async_db(commands.redo(self.workflow.id)) self.step.refresh_from_db() self.assertEqual(self.step.stored_data_version, date2)
def test_delete_tab(self): workflow = Workflow.create_and_init() # tab-1 tab2 = workflow.tabs.create(position=1, slug="tab-2") workflow.tabs.create(position=2, slug="tab-3") self.run_with_async_db( commands.do(DeleteTab, workflow_id=workflow.id, tab=tab2) ) tab2.refresh_from_db() # it is only _soft_-deleted. self.assertEqual(tab2.is_deleted, True) self.assertEqual( list(workflow.live_tabs.values_list("slug", "position")), [("tab-1", 0), ("tab-3", 1)], ) self.run_with_async_db(commands.undo(workflow.id)) tab2.refresh_from_db() self.assertEqual(tab2.is_deleted, False) self.assertEqual( list(workflow.live_tabs.values_list("slug", "position")), [("tab-1", 0), ("tab-2", 1), ("tab-3", 2)], ) self.run_with_async_db(commands.redo(workflow.id)) tab2.refresh_from_db() self.assertEqual(tab2.is_deleted, True) self.assertEqual( list(workflow.live_tabs.values_list("slug", "position")), [("tab-1", 0), ("tab-3", 1)], )
def test_delete_lone_step(self): workflow = Workflow.create_and_init() # tab-1 tab1 = workflow.tabs.first() step = tab1.steps.create( order=0, slug="step-1", last_relevant_delta_id=workflow.last_delta_id, params={"url": ""}, ) self.run_with_async_db( commands.do(DeleteStep, workflow_id=workflow.id, step=step)) step.refresh_from_db() # it is only _soft_-deleted. self.assertEqual(step.is_deleted, True) self.assertEqual(list(tab1.live_steps.values_list("slug", "order")), []) self.run_with_async_db(commands.undo(workflow.id)) step.refresh_from_db() self.assertEqual(step.is_deleted, False) self.assertEqual(list(tab1.live_steps.values_list("slug", "order")), [("step-1", 0)]) self.run_with_async_db(commands.redo(workflow.id)) step.refresh_from_db() # it is only _soft_-deleted. self.assertEqual(step.is_deleted, True) self.assertEqual(list(tab1.live_steps.values_list("slug", "order")), [])
def test_reorder_slugs(self): workflow = Workflow.create_and_init() # tab slug: tab-1 workflow.tabs.create(position=1, slug="tab-2") workflow.tabs.create(position=2, slug="tab-3") cmd = self.run_with_async_db( commands.do( ReorderTabs, workflow_id=workflow.id, new_order=["tab-3", "tab-1", "tab-2"], )) self.assertEqual( list(workflow.live_tabs.values_list("slug", "position")), [("tab-3", 0), ("tab-1", 1), ("tab-2", 2)], ) self.run_with_async_db(commands.undo(workflow.id)) self.assertEqual( list(workflow.live_tabs.values_list("slug", "position")), [("tab-1", 0), ("tab-2", 1), ("tab-3", 2)], ) self.run_with_async_db(commands.redo(workflow.id)) self.assertEqual( list(workflow.live_tabs.values_list("slug", "position")), [("tab-3", 0), ("tab-1", 1), ("tab-2", 2)], )
def test_change_notes(self): workflow = Workflow.create_and_init() step = workflow.tabs.first().steps.create( order=0, slug="step-1", notes="text1", last_relevant_delta_id=workflow.last_delta_id, ) # do self.run_with_async_db( commands.do( SetStepNote, workflow_id=workflow.id, step=step, new_value="text2", ) ) self.assertEqual(step.notes, "text2") step.refresh_from_db() self.assertEqual(step.notes, "text2") # undo self.run_with_async_db(commands.undo(workflow.id)) step.refresh_from_db() self.assertEqual(step.notes, "text1") # redo self.run_with_async_db(commands.redo(workflow.id)) step.refresh_from_db() self.assertEqual(step.notes, "text2")
def test_change_data_version(self): # Create two data versions, use the second date1 = self._store_fetched_table() date2 = self._store_fetched_table() self.wf_module.stored_data_version = date2 self.wf_module.save() self.workflow.refresh_from_db() v1 = self.workflow.last_delta_id # Change back to first version cmd = self.run_with_async_db( commands.do( ChangeDataVersionCommand, workflow_id=self.workflow.id, wf_module=self.wf_module, new_version=date1, )) self.assertEqual(self.wf_module.stored_data_version, date1) self.workflow.refresh_from_db() v2 = cmd.id # workflow revision should have been incremented self.assertEqual(self.wf_module.last_relevant_delta_id, v2) # undo self.run_with_async_db(commands.undo(cmd)) self.assertEqual(self.wf_module.last_relevant_delta_id, v1) self.assertEqual(self.wf_module.stored_data_version, date2) # redo self.run_with_async_db(commands.redo(cmd)) self.assertEqual(self.wf_module.last_relevant_delta_id, v2) self.assertEqual(self.wf_module.stored_data_version, date1)
def test_change_notes(self): workflow = Workflow.create_and_init() wf_module = workflow.tabs.first().wf_modules.create( order=0, slug="step-1", notes="text1", last_relevant_delta_id=workflow.last_delta_id, ) # do cmd = self.run_with_async_db( commands.do( ChangeWfModuleNotesCommand, workflow_id=workflow.id, wf_module=wf_module, new_value="text2", ) ) self.assertEqual(wf_module.notes, "text2") wf_module.refresh_from_db() self.assertEqual(wf_module.notes, "text2") # undo self.run_with_async_db(commands.undo(cmd)) self.assertEqual(wf_module.notes, "text1") wf_module.refresh_from_db() self.assertEqual(wf_module.notes, "text1") # redo self.run_with_async_db(commands.redo(cmd)) self.assertEqual(wf_module.notes, "text2") wf_module.refresh_from_db() self.assertEqual(wf_module.notes, "text2")
def test_accept_deleted_version(self): """ Let the user choose whichever version is desired, even if it does not exist. The errors will be user-visible ... _later_. """ date1 = self._store_fetched_table() date2 = self._store_fetched_table() self.wf_module.notifications = False self.wf_module.stored_data_version = date1 self.wf_module.save() delta = self.run_with_async_db( commands.do( ChangeDataVersionCommand, workflow_id=self.workflow.id, wf_module=self.wf_module, new_version=date2, )) self.wf_module.stored_objects.get(stored_at=date1).delete() self.run_with_async_db(commands.undo(delta)) self.wf_module.refresh_from_db() self.assertEqual(self.wf_module.stored_data_version, date1) self.run_with_async_db(commands.redo(delta)) self.wf_module.refresh_from_db() self.assertEqual(self.wf_module.stored_data_version, date2)
def test_old_style_params(self): all_modules = self.tab.live_steps step1 = self.tab.steps.create(order=0, slug="step-1") step2 = self.tab.steps.create(order=1, slug="step-2") step3 = self.tab.steps.create(order=2, slug="step-3") # Let's build a new-style Delta, then overwrite it to be old-style cmd = self.run_with_async_db( commands.do( ReorderSteps, workflow_id=self.workflow.id, tab=self.tab, slugs=["step-3", "step-2", "step-1"], )) cmd.values_for_backward = { "legacy_format": [ { "id": step1.id, "order": 0 }, { "id": step2.id, "order": 1 }, { "id": step3.id, "order": 2 }, ] } cmd.values_for_forward = { "legacy_format": [ { "id": step3.id, "order": 0 }, { "id": step2.id, "order": 1 }, { "id": step1.id, "order": 2 }, ] } cmd.save(update_fields=["values_for_backward", "values_for_forward"]) self.run_with_async_db(commands.undo(self.workflow.id)) self.assertEqual( list(all_modules.values_list("id", flat=True)), [step1.id, step2.id, step3.id], ) self.run_with_async_db(commands.redo(self.workflow.id)) self.assertEqual( list(all_modules.values_list("id", flat=True)), [step3.id, step2.id, step1.id], )
def test_redo_after_final_delta(self, send_update): send_update.side_effect = async_noop workflow = Workflow.create_and_init(name="hello") self.run_with_async_db( commands.do(SetWorkflowTitle, workflow_id=workflow.id, new_value="1")) send_update.reset_mock() self.run_with_async_db(commands.redo(workflow.id)) send_update.assert_not_called()
def test_reorder_modules(self): all_modules = self.tab.live_wf_modules v1 = self.delta.id step1 = self.tab.wf_modules.create(last_relevant_delta_id=v1, order=0, slug="step-1") step2 = self.tab.wf_modules.create(last_relevant_delta_id=v1, order=1, slug="step-2") step3 = self.tab.wf_modules.create(last_relevant_delta_id=v1, order=2, slug="step-3") cmd = self.run_with_async_db( commands.do( ReorderModulesCommand, workflow_id=self.workflow.id, tab=self.tab, new_order=[step1.id, step3.id, step2.id], )) v2 = cmd.id self.assertWfModuleVersions([v1, v2, v2]) step2.refresh_from_db() step3.refresh_from_db() self.assertEqual( list(all_modules.values_list("id", flat=True)), [step1.id, step3.id, step2.id], ) # undo self.run_with_async_db(commands.undo(cmd)) self.assertWfModuleVersions([v1, v1, v1]) step2.refresh_from_db() step3.refresh_from_db() self.assertEqual( list(all_modules.values_list("id", flat=True)), [step1.id, step2.id, step3.id], ) # redo self.run_with_async_db(commands.redo(cmd)) self.assertWfModuleVersions([v1, v2, v2]) step2.refresh_from_db() step3.refresh_from_db() self.assertEqual( list(all_modules.values_list("id", flat=True)), [step1.id, step3.id, step2.id], )
def test_redo_modify_last_applied_at(self): date0 = datetime.datetime(2000, 1, 1) date1 = datetime.datetime.now() with freeze_time(date0): workflow = Workflow.create_and_init() delta = self.run_with_async_db( commands.do(SetWorkflowTitle, workflow_id=workflow.id, new_value="1")) self.run_with_async_db(commands.undo(workflow.id)) with freeze_time(date1): self.run_with_async_db(commands.redo(workflow.id)) delta.refresh_from_db() self.assertEqual(delta.last_applied_at, date1)
def test_add_module(self): all_modules = self.tab.live_steps v1 = 1 existing_module = self.tab.steps.create( order=0, slug="step-1", last_relevant_delta_id=v1, params={"url": ""}, ) # Add a module, insert before the existing one, check to make sure it # went there and old one is after cmd = self.run_with_async_db( commands.do( AddStep, workflow_id=self.workflow.id, tab=self.workflow.tabs.first(), slug="step-2", module_id_name=self.module_zipfile.module_id, position=0, param_values={"url": "https://x.com"}, ) ) self.assertEqual(all_modules.count(), 2) added_module = all_modules.get(order=0) self.assertNotEqual(added_module, existing_module) # Test that supplied param is written self.assertEqual(added_module.params["url"], "https://x.com") bumped_module = all_modules.get(order=1) self.assertEqual(bumped_module, existing_module) # undo! undo! ahhhhh everything is on fire! undo! self.run_with_async_db(commands.undo(self.workflow.id)) self.assertEqual(all_modules.count(), 1) self.assertEqual(all_modules.first(), existing_module) # wait no, we wanted that module self.run_with_async_db(commands.redo(self.workflow.id)) self.assertEqual(all_modules.count(), 2) added_module = all_modules.get(order=0) self.assertNotEqual(added_module, existing_module) bumped_module = all_modules.get(order=1) self.assertEqual(bumped_module, existing_module)
def test_set_name(self): workflow = Workflow.create_and_init() tab = workflow.tabs.first() tab.name = "foo" tab.save(update_fields=["name"]) self.run_with_async_db( commands.do(SetTabName, workflow_id=workflow.id, tab=tab, new_name="bar") ) tab.refresh_from_db() self.assertEqual(tab.name, "bar") self.run_with_async_db(commands.undo(workflow.id)) tab.refresh_from_db() self.assertEqual(tab.name, "foo") self.run_with_async_db(commands.redo(workflow.id)) tab.refresh_from_db() self.assertEqual(tab.name, "bar")
def test_change_title(self): workflow = Workflow.create_and_init(name="title1") # Change back to second title, see if it saved self.run_with_async_db( commands.do(SetWorkflowTitle, workflow_id=workflow.id, new_value="title2") ) workflow.refresh_from_db() self.assertEqual(workflow.name, "title2") # test DB change # undo self.run_with_async_db(commands.undo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.name, "title1") # test DB change # redo self.run_with_async_db(commands.redo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.name, "title2")
def test_redo_modify_updated_at(self, send_update): send_update.side_effect = async_noop workflow = Workflow.create_and_init() self.run_with_async_db( commands.do(SetWorkflowTitle, workflow_id=workflow.id, new_value="1")) self.run_with_async_db(commands.undo(workflow.id)) date0 = datetime.datetime.now() - datetime.timedelta(days=1) workflow.updated_at = date0 # reset workflow.save(update_fields=["updated_at"]) self.run_with_async_db(commands.redo(workflow.id)) workflow.refresh_from_db() self.assertGreater(workflow.updated_at, date0) send_update.assert_called() update = send_update.call_args[0][1] self.assertEqual(update.workflow.updated_at, workflow.updated_at)
def test_adjust_selected_tab_position(self): # tab slug: tab-1 workflow = Workflow.create_and_init(selected_tab_position=2) workflow.tabs.create(position=1, slug="tab-2") workflow.tabs.create(position=2, slug="tab-3") self.run_with_async_db( commands.do( ReorderTabs, workflow_id=workflow.id, new_order=["tab-3", "tab-1", "tab-2"], )) workflow.refresh_from_db() self.assertEqual(workflow.selected_tab_position, 0) self.run_with_async_db(commands.undo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.selected_tab_position, 2) self.run_with_async_db(commands.redo(workflow.id)) workflow.refresh_from_db() self.assertEqual(workflow.selected_tab_position, 0)
def test_redo_nonfirst_history_item(self, send_update): send_update.side_effect = async_noop workflow = Workflow.create_and_init(name="hello") delta1 = self.run_with_async_db( commands.do(SetWorkflowTitle, workflow_id=workflow.id, new_value="1")) delta2 = self.run_with_async_db( commands.do(SetWorkflowTitle, workflow_id=workflow.id, new_value="2")) self.run_with_async_db(commands.undo(workflow.id)) send_update.reset_mock() self.run_with_async_db(commands.redo(workflow.id)) send_update.assert_called() workflow.refresh_from_db() self.assertEqual(workflow.name, "2") self.assertEqual(workflow.last_delta_id, delta2.id) self.assertEqual(list(workflow.deltas.values_list("id", flat=True)), [delta1.id, delta2.id])
def test_change_data_version(self): # Create two data versions, use the second date1 = self._store_fetched_table(stored_at=datetime.datetime(2021, 6, 24)) date2 = self._store_fetched_table(stored_at=datetime.datetime(2021, 6, 23)) self.step.stored_data_version = date2 self.step.save(update_fields=["stored_data_version"]) self.workflow.refresh_from_db() v1 = self.workflow.last_delta_id # Change back to first version cmd = self.run_with_async_db( commands.do( SetStepDataVersion, workflow_id=self.workflow.id, step=self.step, new_version=isoparse("2021-06-24T00:00:00.000000Z"), ) ) self.assertEqual(self.step.stored_data_version, date1) self.workflow.refresh_from_db() v2 = cmd.id # workflow revision should have been incremented self.step.refresh_from_db() self.assertEqual(self.step.last_relevant_delta_id, v2) # undo self.run_with_async_db(commands.undo(self.workflow.id)) self.step.refresh_from_db() self.assertEqual(self.step.last_relevant_delta_id, v1) self.assertEqual(self.step.stored_data_version, date2) # redo self.run_with_async_db(commands.redo(self.workflow.id)) self.step.refresh_from_db() self.assertEqual(self.step.last_relevant_delta_id, v2) self.assertEqual(self.step.stored_data_version, date1)
def test_duplicate_nonempty_unrendered_tab(self, send_update, queue_render): send_update.side_effect = async_noop queue_render.side_effect = async_noop workflow = Workflow.create_and_init() init_delta_id = workflow.last_delta_id tab = workflow.tabs.first() tab.selected_step_position = 1 tab.save(update_fields=["selected_step_position"]) # step1 and step2 have not yet been rendered. (But while we're # duplicating, conceivably a render could be running; so when we # duplicate them, we need to queue a render.) step1 = tab.steps.create( order=0, slug="step-1", module_id_name="x", params={"p": "s1"}, last_relevant_delta_id=init_delta_id, ) tab.steps.create( order=1, slug="step-2", module_id_name="y", params={"p": "s2"}, last_relevant_delta_id=init_delta_id, ) cmd = self.run_with_async_db( commands.do( DuplicateTab, workflow_id=workflow.id, from_tab=tab, slug="tab-2", name="Tab 2", )) # Adds new tab cmd.tab.refresh_from_db() [step1dup, step2dup] = list(cmd.tab.live_steps.all()) self.assertFalse(cmd.tab.is_deleted) self.assertEqual(cmd.tab.slug, "tab-2") self.assertEqual(cmd.tab.name, "Tab 2") self.assertEqual(cmd.tab.selected_step_position, 1) self.assertEqual(step1dup.order, 0) self.assertEqual(step1dup.module_id_name, "x") self.assertEqual(step1dup.params, {"p": "s1"}) self.assertEqual( step1dup.last_relevant_delta_id, # `cmd.id` would be intuitive, but that would be hard # to implement (and we assume we don't need to). # (Duplicate also duplicates _cache values_, which # means it's expensive to tweak step1's delta ID.) step1.last_relevant_delta_id, ) self.assertEqual(step2dup.order, 1) self.assertEqual(step2dup.module_id_name, "y") self.assertEqual(step2dup.params, {"p": "s2"}) self.assertNotEqual(step1dup.id, step1.id) delta = send_update.mock_calls[0][1][1] self.assertEqual(delta.tabs["tab-2"].step_ids, [step1dup.id, step2dup.id]) self.assertEqual(set(delta.steps.keys()), set([step1dup.id, step2dup.id])) step1update = delta.steps[step1dup.id] self.assertEqual(step1update.last_relevant_delta_id, step1.last_relevant_delta_id) # We should call render: we don't know whether there's a render queued; # and these new steps are in need of render. queue_render.assert_called_with(workflow.id, cmd.id) queue_render.reset_mock() # so we can assert next time # undo self.run_with_async_db(commands.undo(workflow.id)) cmd.tab.refresh_from_db() self.assertTrue(cmd.tab.is_deleted) delta = send_update.mock_calls[1][1][1] self.assertEqual(delta.clear_tab_slugs, frozenset(["tab-2"])) self.assertEqual(delta.clear_step_ids, frozenset([step1dup.id, step2dup.id])) # No need to call render(): these modules can't possibly have changed, # and nobody cares what's in their cache. queue_render.assert_not_called() # redo self.run_with_async_db(commands.redo(workflow.id)) # Need to call render() again -- these modules are still out-of-date queue_render.assert_called_with(workflow.id, cmd.id)
def test_redo_with_no_history(self, websockets_notify): websockets_notify.side_effect = async_noop workflow = Workflow.create_and_init(name="hello") self.run_with_async_db(commands.redo(workflow.id)) websockets_notify.assert_not_called()
def test_change_parameters(self): # Setup: workflow with loadurl module # # loadurl is a good choice because it has three parameters, two of # which are useful. workflow = Workflow.create_and_init() ModuleVersion.create_or_replace_from_spec({ "id_name": "loadurl", "name": "loadurl", "category": "Clean", "parameters": [ { "id_name": "url", "type": "string" }, { "id_name": "has_header", "type": "checkbox", "name": "HH" }, { "id_name": "version_select", "type": "custom" }, ], }) params1 = { "url": "http://example.org", "has_header": True, "version_select": "", } wf_module = workflow.tabs.first().wf_modules.create( module_id_name="loadurl", order=0, slug="step-1", last_relevant_delta_id=workflow.last_delta_id, params=params1, ) # Create and apply delta. It should change params. cmd = self.run_with_async_db( commands.do( ChangeParametersCommand, workflow_id=workflow.id, wf_module=wf_module, new_values={ "url": "http://example.com/foo", "has_header": False }, )) wf_module.refresh_from_db() params2 = { "url": "http://example.com/foo", "has_header": False, "version_select": "", } self.assertEqual(wf_module.params, params2) # undo self.run_with_async_db(commands.undo(cmd)) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params1) # redo self.run_with_async_db(commands.redo(cmd)) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params2)
def test_duplicate_empty_tab(self, ws_notify, queue_render): ws_notify.side_effect = async_noop workflow = Workflow.create_and_init() tab = workflow.tabs.first() cmd = self.run_with_async_db( commands.do( DuplicateTabCommand, workflow_id=workflow.id, from_tab=tab, slug="tab-2", name="Tab 2", ) ) # Adds new tab cmd.tab.refresh_from_db() self.assertFalse(cmd.tab.is_deleted) self.assertEqual(cmd.tab.slug, "tab-2") self.assertEqual(cmd.tab.name, "Tab 2") ws_notify.assert_called_with( workflow.id, { "updateWorkflow": { "name": workflow.name, "public": False, "last_update": cmd.datetime.isoformat(), "tab_slugs": ["tab-1", "tab-2"], }, "updateTabs": { "tab-2": { "slug": "tab-2", "name": "Tab 2", "wf_module_ids": [], "selected_wf_module_position": None, } }, "updateWfModules": {}, }, ) # Backward: should delete tab self.run_with_async_db(commands.undo(cmd)) cmd.tab.refresh_from_db() self.assertTrue(cmd.tab.is_deleted) ws_notify.assert_called_with( workflow.id, { "updateWorkflow": { "name": workflow.name, "public": False, "last_update": workflow.last_delta.datetime.isoformat(), "tab_slugs": ["tab-1"], }, "clearTabSlugs": ["tab-2"], "clearWfModuleIds": [], }, ) # Forward: should bring us back self.run_with_async_db(commands.redo(cmd)) cmd.tab.refresh_from_db() self.assertFalse(cmd.tab.is_deleted) ws_notify.assert_called_with( workflow.id, { "updateWorkflow": { "name": workflow.name, "public": False, "last_update": cmd.datetime.isoformat(), "tab_slugs": ["tab-1", "tab-2"], }, "updateTabs": { "tab-2": { "slug": "tab-2", "name": "Tab 2", "wf_module_ids": [], "selected_wf_module_position": None, } }, "updateWfModules": {}, }, ) # There should never be a render: we aren't changing any module # outputs. queue_render.assert_not_called()
def test_add_module(self): existing_module = self.tab.wf_modules.create( order=0, slug="step-1", last_relevant_delta_id=self.delta.id, params={"url": ""}, ) all_modules = self.tab.live_wf_modules self.workflow.refresh_from_db() v1 = self.workflow.last_delta_id # Add a module, insert before the existing one, check to make sure it # went there and old one is after cmd = self.run_with_async_db( commands.do( AddModuleCommand, workflow_id=self.workflow.id, tab=self.workflow.tabs.first(), slug="step-2", module_id_name=self.module_version.id_name, position=0, param_values={"url": "https://x.com"}, )) self.assertEqual(all_modules.count(), 2) added_module = all_modules.get(order=0) self.assertNotEqual(added_module, existing_module) # Test that supplied param is written self.assertEqual(added_module.params["url"], "https://x.com") bumped_module = all_modules.get(order=1) self.assertEqual(bumped_module, existing_module) # workflow revision should have been incremented self.workflow.refresh_from_db() self.assertGreater(self.workflow.last_delta_id, v1) # Check the delta chain (short, but should be sweet) self.workflow.refresh_from_db() self.assertEqual(self.workflow.last_delta, cmd) self.assertEqual(cmd.prev_delta_id, self.delta.id) with self.assertRaises(Delta.DoesNotExist): cmd.next_delta # undo! undo! ahhhhh everything is on fire! undo! self.run_with_async_db(commands.undo(cmd)) self.assertEqual(all_modules.count(), 1) self.assertEqual(all_modules.first(), existing_module) # wait no, we wanted that module self.run_with_async_db(commands.redo(cmd)) self.assertEqual(all_modules.count(), 2) added_module = all_modules.get(order=0) self.assertNotEqual(added_module, existing_module) bumped_module = all_modules.get(order=1) self.assertEqual(bumped_module, existing_module) # Undo and test deleting the un-applied command. Should delete dangling # WfModule too self.run_with_async_db(commands.undo(cmd)) self.assertEqual(all_modules.count(), 1) self.assertEqual(all_modules.first(), existing_module) cmd.delete_with_successors() with self.assertRaises(WfModule.DoesNotExist): all_modules.get(pk=added_module.id) # should be gone
def test_redo_with_no_history(self, send_update): send_update.side_effect = async_noop workflow = Workflow.create_and_init(name="hello") self.run_with_async_db(commands.redo(workflow.id)) send_update.assert_not_called()
def test_change_parameters(self): # Setup: workflow with loadurl module # # loadurl is a good choice because it has three parameters, two of # which are useful. workflow = Workflow.create_and_init() module_zipfile = create_module_zipfile( "loadsomething", spec_kwargs={ "parameters": [ {"id_name": "url", "type": "string"}, {"id_name": "has_header", "type": "checkbox", "name": "HH"}, {"id_name": "version_select", "type": "custom"}, ] }, ) params1 = { "url": "http://example.org", "has_header": True, "version_select": "", } wf_module = workflow.tabs.first().wf_modules.create( module_id_name="loadurl", order=0, slug="step-1", last_relevant_delta_id=workflow.last_delta_id, params=params1, cached_migrated_params=params1, cached_migrated_params_module_version=module_zipfile.version, ) # Create and apply delta. It should change params. self.kernel.migrate_params.side_effect = lambda m, p: p with self.assertLogs(level=logging.INFO): cmd = self.run_with_async_db( commands.do( ChangeParametersCommand, workflow_id=workflow.id, wf_module=wf_module, new_values={"url": "http://example.com/foo", "has_header": False}, ) ) wf_module.refresh_from_db() params2 = { "url": "http://example.com/foo", "has_header": False, "version_select": "", } self.assertEqual(wf_module.params, params2) # undo with self.assertLogs(level=logging.INFO): # building clientside.Update will migrate_params(), so we need # to capture logs. self.run_with_async_db(commands.undo(cmd)) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params1) # redo with self.assertLogs(level=logging.INFO): # building clientside.Update will migrate_params(), so we need # to capture logs. self.run_with_async_db(commands.redo(cmd)) wf_module.refresh_from_db() self.assertEqual(wf_module.params, params2)
def test_delete_custom_report_blocks(self, send_update): future_none = asyncio.Future() future_none.set_result(None) send_update.return_value = future_none workflow = Workflow.create_and_init(has_custom_report=True) # tab-1 tab1 = workflow.tabs.first() step1 = tab1.steps.create( order=0, slug="step-1", last_relevant_delta_id=workflow.last_delta_id, params={"url": ""}, ) step2 = tab1.steps.create( order=0, slug="step-2", last_relevant_delta_id=workflow.last_delta_id, params={"url": ""}, ) # Report will include the step twice, and have another step elsewhere # that should not be touched block1 = workflow.blocks.create(position=0, slug="block-step-1-1", block_type="Chart", step=step1) block2 = workflow.blocks.create(position=1, slug="block-step-2", block_type="Chart", step=step2) block3 = workflow.blocks.create(position=2, slug="block-step-1-2", block_type="Chart", step=step1) self.run_with_async_db( commands.do(DeleteStep, workflow_id=workflow.id, step=step1)) with self.assertRaises(Block.DoesNotExist): block1.refresh_from_db() with self.assertRaises(Block.DoesNotExist): block3.refresh_from_db() block2.refresh_from_db() self.assertEqual(block2.position, 0) send_update.assert_called() update = send_update.call_args[0][1] self.assertEqual(update.workflow.block_slugs, ["block-step-2"]) self.assertEqual(update.tabs, {"tab-1": clientside.TabUpdate(step_ids=[step2.id])}) self.assertEqual(update.clear_step_ids, frozenset([step1.id])) self.assertEqual(update.blocks, {}) self.run_with_async_db(commands.undo(workflow.id)) # The old blocks are deleted. We expect new blocks with new IDs. with self.assertRaises(Block.DoesNotExist): block1.refresh_from_db() with self.assertRaises(Block.DoesNotExist): block3.refresh_from_db() new_block1 = workflow.blocks.get(slug=block1.slug) new_block3 = workflow.blocks.get(slug=block3.slug) self.assertEqual(new_block1.step_id, step1.id) self.assertEqual(new_block3.step_id, step1.id) block2.refresh_from_db() self.assertEqual(new_block1.position, 0) self.assertEqual(block2.position, 1) self.assertEqual(new_block3.position, 2) send_update.assert_called() update = send_update.call_args[0][1] self.assertEqual( update.workflow.block_slugs, ["block-step-1-1", "block-step-2", "block-step-1-2"], ) self.assertEqual( update.tabs, {"tab-1": clientside.TabUpdate(step_ids=[step1.id, step2.id])}) self.assertEqual( update.blocks, { "block-step-1-1": clientside.ChartBlock("step-1"), "block-step-1-2": clientside.ChartBlock("step-1"), }, ) self.run_with_async_db(commands.redo(workflow.id)) block2.refresh_from_db() self.assertEqual(block2.position, 0)
def test_duplicate_empty_tab(self, send_update, queue_render): send_update.side_effect = async_noop workflow = Workflow.create_and_init() tab = workflow.tabs.first() cmd = self.run_with_async_db( commands.do( DuplicateTab, workflow_id=workflow.id, from_tab=tab, slug="tab-2", name="Tab 2", )) # Adds new tab cmd.tab.refresh_from_db() self.assertFalse(cmd.tab.is_deleted) self.assertEqual(cmd.tab.slug, "tab-2") self.assertEqual(cmd.tab.name, "Tab 2") workflow.refresh_from_db() send_update.assert_called_with( workflow.id, clientside.Update( workflow=clientside.WorkflowUpdate( updated_at=workflow.updated_at, tab_slugs=["tab-1", "tab-2"]), tabs={ "tab-2": clientside.TabUpdate( slug="tab-2", name="Tab 2", step_ids=[], selected_step_index=None, ) }, ), ) # Backward: should delete tab self.run_with_async_db(commands.undo(workflow.id)) cmd.tab.refresh_from_db() self.assertTrue(cmd.tab.is_deleted) workflow.refresh_from_db() send_update.assert_called_with( workflow.id, clientside.Update( workflow=clientside.WorkflowUpdate( updated_at=workflow.updated_at, tab_slugs=["tab-1"]), clear_tab_slugs=frozenset(["tab-2"]), ), ) # Forward: should bring us back self.run_with_async_db(commands.redo(workflow.id)) cmd.tab.refresh_from_db() self.assertFalse(cmd.tab.is_deleted) workflow.refresh_from_db() send_update.assert_called_with( workflow.id, clientside.Update( workflow=clientside.WorkflowUpdate( updated_at=workflow.updated_at, tab_slugs=["tab-1", "tab-2"]), tabs={ "tab-2": clientside.TabUpdate( slug="tab-2", name="Tab 2", step_ids=[], selected_step_index=None, ) }, ), ) # There should never be a render: we aren't changing any module # outputs. queue_render.assert_not_called()