def test_find_step_by_name_not_first(self, mock_class): """ The _find_step_by_name method skips tasks that don't exist """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "name_response", "options": { "response": "^^pass_name.name" }, }, }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() task = flow._find_step_by_name("name_response") self.assertEquals( "cumulusci.core.tests.test_flows._TaskResponseName", task.task_config.class_path, )
def test_find_step_by_name_not_first(self, mock_class): """ The _find_step_by_name method skips tasks that don't exist """ # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'pass_name' }, 2: { 'task': 'name_response', 'options': { 'response': '^^pass_name.name' } }, } }) flow = BaseFlow( self.project_config, flow_config, self.org_config, ) flow() task = flow._find_step_by_name('name_response') self.assertEquals( 'cumulusci.core.tests.test_flows._TaskResponseName', task.task_config.class_path, )
def test_render_task_config_empty_value(self, mock_class): """ The _render_task_config method skips option values of None """ # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run a tasks', 'steps': { 1: { 'task': 'name_response', 'options': { 'response': None, }, }, }, }) flow = BaseFlow( self.project_config, flow_config, self.org_config, ) flow() task = flow._find_step_by_name('name_response') config = flow._render_task_config(task) self.assertEquals(['Options:'], config)
def test_find_step_by_name_no_steps(self, mock_class): """ Running a flow with no steps throws an error """ # instantiate a flow with two tasks flow_config = FlowConfig({"description": "Run two tasks"}) flow = BaseFlow(self.project_config, flow_config, self.org_config) self.assertIsNone(flow._find_step_by_name("task")) with self.assertRaises(FlowConfigError): flow()
def test_find_task_by_name_no_tasks(self, mock_class): """ The _find_task_by_name method skips tasks that don't exist """ # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run two tasks', }) flow = BaseFlow( self.project_config, flow_config, self.org_config, ) self.assertEquals(None, flow._find_task_by_name('missing'))
def test_pass_around_values(self, mock_class): """ A flow's options reach into return values from other tasks. """ mock_class.return_value = None # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'pass_name' }, 2: { 'task': 'name_response', 'options': { 'response': '^^pass_name.name' } }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) # run the flow flow() # the flow results for the second task should be 'name' self.assertEquals('supername', flow.step_results[1])
def test_task_options(self, mock_class): """ A flow can accept task options and pass them to the task. """ mock_class.return_value = None # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'name_response', 'options': { 'response': 'foo' } }, } }) flow = BaseFlow( self.project_config, flow_config, self.org_config, options={'name_response__response': 'bar'}, ) # run the flow flow() # the flow results for the first task should be 'bar' self.assertEquals('bar', flow.step_results[0])
def test_skip_task_value_none(self, mock_class): """ A flow skips any tasks whose name is None to allow override via yaml """ # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'pass_name' }, 2: { 'task': 'None' }, } }) flow = BaseFlow( self.project_config, flow_config, self.org_config, skip=['name_response'], ) # run the flow flow() # the number of tasks in the flow should be 1 instead of 2 self.assertEquals(1, len(flow.step_results))
def test_skip_kwarg(self, mock_class): """ A flow can receive during init a list of tasks to skip """ # instantiate a flow with two tasks flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'pass_name' }, 2: { 'task': 'name_response', 'options': { 'response': '^^pass_name.name' } }, } }) flow = BaseFlow( self.project_config, flow_config, self.org_config, skip=['name_response'], ) # run the flow flow() # the number of tasks in the flow should be 1 instead of 2 self.assertEquals(1, len(flow.step_results))
def test_skip_task_value_none(self, mock_class): """ A flow skips any tasks whose name is None to allow override via yaml """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "None" } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config, skip=["name_response"]) # run the flow flow() # the number of tasks in the flow should be 1 instead of 2 self.assertEquals(1, len(flow.step_results))
def test_skip_kwarg(self, mock_class): """ A flow can receive during init a list of tasks to skip """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "name_response", "options": { "response": "^^pass_name.name" }, }, }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config, skip=["name_response"]) # run the flow flow() # the number of tasks in the flow should be 1 instead of 2 self.assertEquals(1, len(flow.step_results))
def test_task_options(self, mock_class): """ A flow can accept task options and pass them to the task. """ mock_class.return_value = None # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "name_response", "options": { "response": "foo" } } }, }) flow = BaseFlow( self.project_config, flow_config, self.org_config, options={"name_response__response": "bar"}, ) # run the flow flow() # the flow results for the first task should be 'bar' self.assertEquals("bar", flow.step_results[0])
def test_pass_around_values(self, mock_class): """ A flow's options reach into return values from other tasks. """ mock_class.return_value = None # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "name_response", "options": { "response": "^^pass_name.name" }, }, }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) # run the flow flow() # the flow results for the second task should be 'name' self.assertEquals("supername", flow.step_results[1])
def test_init(self, mock_class): """ BaseFlow initializes and offers a logger """ flow_config = FlowConfig({}) mock_class.return_value = None flow = BaseFlow(self.project_config, flow_config, self.org_config) self.assertEquals(hasattr(flow, 'logger'), True)
def test_call__not_prepped(self, mock_class): flow_config = FlowConfig({}) flow = BaseFlow(self.project_config, flow_config, self.org_config, prep=False) with self.assertRaises(FlowNotReadyError): flow()
def test_find_step_by_name_no_steps(self, mock_class): """ Running a flow with no steps throws an error """ # instantiate a flow with two tasks flow_config = FlowConfig({"description": "Run two tasks"}) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow()
def test_call_no_tasks(self, mock_class): """ A flow with no tasks will have no responses. """ flow_config = FlowConfig({'description': 'Run no tasks', 'steps': {}}) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEqual([], flow.step_return_values) self.assertEqual([], flow.steps)
def flow_run(config, flow_name, org, delete_org, debug, o, skip, no_prompt): # Check environment config.check_keychain() # Get necessary configs org, org_config = config.get_org(org) if delete_org and not org_config.scratch: raise click.UsageError( "--delete-org can only be used with a scratch org") flow_config = config.project_config.get_flow(flow_name) # Parse command line options and add to task config options = {} if o: for option in o: options[option[0]] = option[1] # Create the flow and handle initialization exceptions try: flow = BaseFlow( config.project_config, flow_config, org_config, options, skip, name=flow_name, ) flow() except (TaskRequiresSalesforceOrg, TaskOptionsError) as e: exception = click.UsageError(e.message) handle_exception_debug(config, debug, throw_exception=exception) except ( ApexTestException, BrowserTestFailure, MetadataComponentFailure, MetadataApiError, ScratchOrgException, ) as e: exception = click.ClickException("Failed: {}".format( e.__class__.__name__)) handle_exception_debug(config, debug, throw_exception=exception) except Exception: handle_exception_debug(config, debug, no_prompt=no_prompt) # Delete the scratch org if --delete-org was set if delete_org: try: org_config.delete_org() except Exception as e: click.echo( "Scratch org deletion failed. Ignoring the error below to complete the flow:" ) click.echo(e.message)
def test_no_id_if_run_from_flow(self): """ A salesforce_task will not log the org id if run from a flow """ task = _SfdcTask(self.project_config, self.task_config, self.org_config, flow=BaseFlow(self.project_config, FlowConfig(), self.org_config)) task() self.assertFalse(any(ORG_ID in s for s in self.task_log['info']))
def test_rejects_flow_and_task_in_same_step(self, mock_class): flow_config = FlowConfig( {"steps": { 1: { "task": "pass_name", "flow": "nested_flow" } }}) with self.assertRaises(FlowConfigError): BaseFlow(self.project_config, flow_config, self.org_config)
def test_no_id_if_run_from_flow(self, mock_class): """ A salesforce_task will not log the org id if run from a flow """ mock_class.return_value = None task = _SfdcTask( self.project_config, self.task_config, self.org_config, flow=BaseFlow(self.project_config, FlowConfig(), self.org_config), ) task() self.assertFalse(any(ORG_ID in s for s in self.task_log["info"]))
def test_task_raises_exception_fail(self, mock_class): """ A flow aborts when a task raises an exception """ flow_config = FlowConfig({ 'description': 'Run a task', 'steps': { 1: { 'task': 'raise_exception' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) self.assertRaises(Exception, flow)
def test_task_raises_exception_fail(self, mock_class): """ A flow aborts when a task raises an exception """ flow_config = FlowConfig({ "description": "Run a task", "steps": { 1: { "task": "raise_exception" } } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) self.assertRaises(Exception, flow)
def test_render_task_config_empty_value(self, mock_class): """ The _render_task_config method skips option values of None """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run a tasks", "steps": { 1: { "task": "name_response", "options": { "response": None } } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() task = flow._find_step_by_name("name_response") config = flow._render_task_config(task) self.assertEquals(["Options:"], config)
def test_find_step_by_name__flow(self, mock_class): flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "flow": "nested_flow" }, 2: { "task": "name_response", "options": { "response": "^^nested_flow.pass_name.name", "from_flow": "^^nested_flow.name", }, }, }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() step = flow._find_step_by_name("nested_flow") self.assertIsInstance(step, BaseFlow)
def test_check_infinite_flows(self, mock_class): self.project_config.config["flows"] = { "nested_flow": { "description": "A flow that runs inside another flow", "steps": { 1: { "flow": "nested_flow" } }, } } flow_config = FlowConfig({"steps": {1: {"flow": "nested_flow"}}}) with self.assertRaises(FlowInfiniteLoopError): BaseFlow(self.project_config, flow_config, self.org_config)
def flow_run(config, flow_name, org, delete_org, debug, o, skip, no_prompt): # Get necessary configs org, org_config = config.get_org(org) if delete_org and not org_config.scratch: raise click.UsageError( "--delete-org can only be used with a scratch org") flow_config = config.project_config.get_flow(flow_name) # Parse command line options and add to task config options = {} if o: for option in o: options[option[0]] = option[1] # Create the flow and handle initialization exceptions try: flow = BaseFlow( config.project_config, flow_config, org_config, options, skip, name=flow_name, ) flow() except CumulusCIUsageError as e: exception = click.UsageError(str(e)) handle_exception_debug(config, debug, throw_exception=exception) except (CumulusCIFailure, ScratchOrgException) as e: exception = click.ClickException("Failed: {}".format( e.__class__.__name__)) handle_exception_debug(config, debug, throw_exception=exception) except Exception: handle_exception_debug(config, debug, no_prompt=no_prompt) # Delete the scratch org if --delete-org was set if delete_org: try: org_config.delete_org() except Exception as e: click.echo( "Scratch org deletion failed. Ignoring the error below to complete the flow:" ) click.echo(str(e)) config.alert("Flow Complete: {}".format(flow_name))
def test_call_task_not_found(self, mock_class): """ A flow with reference to a task that doesn't exist in the project will throw a TaskNotFoundError """ flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'pass_name' }, 2: { 'task': 'do_delightulthings' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config)
def test_call_task_not_found(self, mock_class): """ A flow with reference to a task that doesn't exist in the project will throw a TaskNotFoundError """ flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "do_delightulthings" } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config)
def test_nested_flow_options(self, mock_class): flow_config = FlowConfig({ "description": "Run a flow with task options", "steps": { 1: { "flow": "nested_flow", "options": { "pass_name": { "foo": "bar" } } } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEqual("bar", flow.steps[0].options["pass_name__foo"])