Beispiel #1
0
class TestSalesforceToolingTask(unittest.TestCase):
    def setUp(self):
        self.api_version = 36.0
        self.global_config = BaseGlobalConfig(
            {"project": {
                "package": {
                    "api_version": self.api_version
                }
            }})
        self.project_config = BaseProjectConfig(self.global_config,
                                                config={"noyaml": True})
        self.project_config.config["project"] = {
            "package": {
                "api_version": self.api_version
            }
        }
        self.project_config.config["services"] = {
            "connectedapp": {
                "attributes": {
                    "client_id": {}
                }
            }
        }
        self.keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(self.keychain)

        self.task_config = TaskConfig()
        self.org_config = OrgConfig(
            {
                "instance_url": "https://example.com",
                "access_token": "abc123"
            }, "test")
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version)

    def test_get_tooling_object(self):
        task = BaseSalesforceApiTask(self.project_config, self.task_config,
                                     self.org_config)
        task._init_task()
        obj = task._get_tooling_object("TestObject")
        url = self.base_tooling_url + "sobjects/TestObject/"
        self.assertEqual(obj.base_url, url)

    def test_default_client_name(self):
        task = BaseSalesforceApiTask(self.project_config, self.task_config,
                                     self.org_config)
        task._init_task()
        self.assertIn("Sforce-Call-Options", task.sf.headers)
        self.assertIn("CumulusCI/", task.sf.headers["Sforce-Call-Options"])

    def test_connected_app_client_name(self):
        self.project_config.keychain.set_service(
            "connectedapp", ServiceConfig({"client_id": "test123"}))

        task = BaseSalesforceApiTask(self.project_config, self.task_config,
                                     self.org_config)
        task._init_task()
        self.assertIn("Sforce-Call-Options", task.sf.headers)
        self.assertNotIn("CumulusCI/", task.sf.headers["Sforce-Call-Options"])
        self.assertIn("test123", task.sf.headers["Sforce-Call-Options"])
Beispiel #2
0
def _make_task(task_class, task_config):
    task_config = TaskConfig(task_config)
    global_config = BaseGlobalConfig()
    project_config = BaseProjectConfig(global_config, config={"noyaml": True})
    keychain = BaseProjectKeychain(project_config, "")
    project_config.set_keychain(keychain)
    org_config = DummyOrgConfig(
        {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
    )
    return task_class(project_config, task_config, org_config)
def _make_task(task_class, task_config):
    task_config = TaskConfig(task_config)
    global_config = BaseGlobalConfig()
    project_config = BaseProjectConfig(global_config, config={"noyaml": True})
    keychain = BaseProjectKeychain(project_config, "")
    project_config.set_keychain(keychain)
    org_config = DummyOrgConfig(
        {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
    )
    return task_class(project_config, task_config, org_config)
class TestSalesforceToolingTask(unittest.TestCase):
    def setUp(self):
        self.api_version = 36.0
        self.global_config = BaseGlobalConfig(
            {"project": {"package": {"api_version": self.api_version}}}
        )
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )
        self.project_config.config["project"] = {
            "package": {"api_version": self.api_version}
        }
        self.project_config.config["services"] = {
            "connectedapp": {"attributes": {"client_id": {}}}
        }
        self.keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(self.keychain)

        self.task_config = TaskConfig()
        self.org_config = OrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version
        )

    def test_get_tooling_object(self):
        task = BaseSalesforceApiTask(
            self.project_config, self.task_config, self.org_config
        )
        task._init_task()
        obj = task._get_tooling_object("TestObject")
        url = self.base_tooling_url + "sobjects/TestObject/"
        self.assertEqual(obj.base_url, url)

    def test_default_client_name(self):
        task = BaseSalesforceApiTask(
            self.project_config, self.task_config, self.org_config
        )
        task._init_task()
        self.assertIn("Sforce-Call-Options", task.sf.headers)
        self.assertIn("CumulusCI/", task.sf.headers["Sforce-Call-Options"])

    def test_connected_app_client_name(self):
        self.project_config.keychain.set_service(
            "connectedapp", ServiceConfig({"client_id": "test123"})
        )

        task = BaseSalesforceApiTask(
            self.project_config, self.task_config, self.org_config
        )
        task._init_task()
        self.assertIn("Sforce-Call-Options", task.sf.headers)
        self.assertNotIn("CumulusCI/", task.sf.headers["Sforce-Call-Options"])
        self.assertIn("test123", task.sf.headers["Sforce-Call-Options"])
    def _run_task(self):
        # Find or create Version
        if not self.dry_run:
            product = self._find_product()
            version = self._find_or_create_version(product)

        # Check out the specified tag
        repo_owner = self.project_config.repo_owner
        repo_name = self.project_config.repo_name
        gh = self.project_config.get_github_api()
        repo = gh.repository(repo_owner, repo_name)
        if self.tag:
            tag = self.options["tag"]
            self.commit = repo.tag(repo.ref("tags/" +
                                            tag).object.sha).object.sha
        self.logger.info("Downloading commit {} of {} from GitHub".format(
            self.commit, repo.full_name))
        zf = download_extract_github(gh,
                                     repo_owner,
                                     repo_name,
                                     ref=self.commit)
        with temporary_dir() as project_dir:
            zf.extractall(project_dir)
            project_config = BaseProjectConfig(
                self.project_config.global_config_obj,
                repo_info={
                    "root": project_dir,
                    "owner": repo_owner,
                    "name": repo_name,
                    "url": self.project_config.repo_url,
                    "branch": self.tag or self.commit,
                    "commit": self.commit,
                },
            )
            project_config.set_keychain(self.project_config.keychain)

            # Create each plan
            for plan_name, plan_config in self.plan_configs.items():
                steps = self._freeze_steps(project_config, plan_config)
                self.logger.debug("Prepared steps:\n" +
                                  json.dumps(steps, indent=4))
                if not self.dry_run:
                    self._publish_plan(product, version, plan_name,
                                       plan_config, steps)

            # Update version to set is_listed=True
            if self.publish:
                self._call_api(
                    "PATCH",
                    "/versions/{}".format(version["id"]),
                    json={"is_listed": True},
                )
                self.logger.info("Published Version {}".format(version["url"]))
Beispiel #6
0
def _make_task(task_class, task_config):
    task_config = TaskConfig(task_config)
    universal_config = UniversalConfig()
    project_config = BaseProjectConfig(
        universal_config,
        config={"noyaml": True, "project": {"package": {"api_version": "46.0"}}},
    )
    keychain = BaseProjectKeychain(project_config, "")
    project_config.set_keychain(keychain)
    org_config = DummyOrgConfig(
        {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
    )
    return task_class(project_config, task_config, org_config)
    def _run_task(self):
        # Find or create Version
        if not self.dry_run:
            product = self._find_product()
            version = self._find_or_create_version(product)

        # Check out the specified tag
        repo_owner = self.project_config.repo_owner
        repo_name = self.project_config.repo_name
        gh = self.project_config.get_github_api()
        repo = gh.repository(repo_owner, repo_name)
        tag = self.options["tag"]
        commit_sha = repo.tag(repo.ref("tags/" + tag).object.sha).object.sha
        self.logger.info(
            "Downloading commit {} of {} from GitHub".format(commit_sha, repo.full_name)
        )
        zf = download_extract_github(gh, repo_owner, repo_name, ref=commit_sha)
        with temporary_dir() as project_dir:
            zf.extractall(project_dir)
            project_config = BaseProjectConfig(
                self.project_config.global_config_obj,
                repo_info={
                    "root": project_dir,
                    "owner": repo_owner,
                    "name": repo_name,
                    "url": self.project_config.repo_url,
                    "branch": tag,
                    "commit": commit_sha,
                },
            )
            project_config.set_keychain(self.project_config.keychain)

            # Create each plan
            for plan_name, plan_config in self.plan_configs.items():
                steps = self._freeze_steps(project_config, plan_config)
                self.logger.debug("Prepared steps:\n" + json.dumps(steps, indent=4))
                if not self.dry_run:
                    self._publish_plan(product, version, plan_name, plan_config, steps)

            # Update version to set is_listed=True
            if not self.dry_run:
                self._call_api(
                    "PATCH",
                    "/versions/{}".format(version["id"]),
                    json={"is_listed": True},
                )
                self.logger.info("Published Version {}".format(version["url"]))
Beispiel #8
0
class TestSFDXBaseTask(unittest.TestCase):
    """ Tests for the Base Task type """

    @classmethod
    def setUpClass(cls):
        super(TestSFDXBaseTask, cls).setUpClass()
        logger = logging.getLogger(cumulusci.core.tasks.__name__)
        logger.setLevel(logging.DEBUG)
        cls._task_log_handler = MockLoggingHandler(logging.DEBUG)
        logger.addHandler(cls._task_log_handler)

    def setUp(self):
        self.global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(self.global_config)
        self.task_config = TaskConfig()

        keychain = BaseProjectKeychain(self.project_config, '')
        app_config = ConnectedAppOAuthConfig()
        keychain.set_connected_app(app_config)
        self.project_config.set_keychain(keychain)

        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages

    def test_base_task(self):
        """ The command is prefixed w/ sfdx """

        self.task_config.config['options'] = {
            'command': 'force:org --help',
        }
        task = SFDXBaseTask(self.project_config, self.task_config)

        try:
            task()
        except CommandException:
            pass

        self.assertEqual('sfdx force:org --help', task.options['command'])

    @patch('cumulusci.tasks.sfdx.SFDXOrgTask._update_credentials',
           MagicMock(return_value=None))
    @patch('cumulusci.tasks.command.Command._run_task',
           MagicMock(return_value=None))
    def test_keychain_org_creds(self):
        """ Keychain org creds are passed by env var """

        self.task_config.config['options'] = {
            'command': 'force:org --help',
        }
        access_token = '00d123'
        org_config = OrgConfig({
            'access_token': access_token,
            'instance_url': 'https://test.salesforce.com'
        },'test')

        task = SFDXOrgTask(
            self.project_config, self.task_config, org_config
        )

        try:
            task()
        except CommandException:
            pass

        self.assertIn('SFDX_INSTANCE_URL', task._get_env())
        self.assertIn('SFDX_USERNAME', task._get_env())
        self.assertIn(access_token, task._get_env()['SFDX_USERNAME'])

    def test_scratch_org_username(self):
        """ Scratch Org credentials are passed by -u flag """

        self.task_config.config['options'] = {
            'command': 'force:org --help',
        }
        org_config = OrgConfig({
            'access_token': 'test access token',
            'instance_url': 'test instance url',
        }, 'test')

        task = SFDXOrgTask(
            self.project_config, self.task_config, org_config
        )

        try:
            task()
        except CommandException:
            pass

        env = task._get_env()
        print env
        self.assertEquals(env.get('SFDX_USERNAME'), 'test access token')
        self.assertEquals(env.get('SFDX_INSTANCE_URL'), 'test instance url')
class TestRunBatchApex(unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.global_config = BaseGlobalConfig(
            {"project": {"api_version": self.api_version}}
        )
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "class_name": "ADDR_Seasonal_BATCH",
            "poll_interval": 1,
        }
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )
        self.project_config.config["project"] = {
            "package": {"api_version": self.api_version}
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version
        )

    def _get_query_resp(self):
        return {
            "size": 1,
            "totalSize": 1,
            "done": True,
            "queryLocator": None,
            "entityTypeName": "AsyncApexJob",
            "records": [
                {
                    "attributes": {
                        "type": "AsyncApexJob",
                        "url": "/services/data/v43.0/tooling/sobjects/AsyncApexJob/707L0000014nnPHIAY",
                    },
                    "Id": "707L0000014nnPHIAY",
                    "ApexClass": {
                        "attributes": {
                            "type": "ApexClass",
                            "url": "/services/data/v43.0/tooling/sobjects/ApexClass/01pL000000109ndIAA",
                        },
                        "Name": "ADDR_Seasonal_BATCH",
                    },
                    "Status": "Completed",
                    "ExtendedStatus": None,
                    "TotalJobItems": 1,
                    "JobItemsProcessed": 1,
                    "NumberOfErrors": 0,
                    "CreatedDate": "2018-08-07T16:00:56.000+0000",
                    "CompletedDate": "2018-08-07T16:01:57.000+0000",
                }
            ],
        }

    def _get_url_and_task(self):
        task = BatchApexWait(self.project_config, self.task_config, self.org_config)
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+ApexClass.Name%2C+Status%2C+ExtendedStatus%2C+TotalJobItems%2C+JobItemsProcessed%2C+NumberOfErrors%2C+CreatedDate%2C+CompletedDate+FROM+AsyncApexJob+WHERE+JobType%3D%27BatchApex%27+AND+ApexClass.Name%3D%27ADDR_Seasonal_BATCH%27+ORDER+BY+CreatedDate+DESC+LIMIT+1"
        )
        return task, url

    @responses.activate
    def test_run_batch_apex_status_fail(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["NumberOfErrors"] = 1
        response["records"][0]["ExtendedStatus"] = "Bad Status"
        responses.add(responses.GET, url, json=response)
        with self.assertRaises(SalesforceException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err.args[0], "Bad Status")

    @responses.activate
    def test_run_batch_apex_status_ok(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_batch_apex_calc_delta(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        responses.add(responses.GET, url, json=response)
        task()
        self.assertEqual(task.delta, 61)
Beispiel #10
0
    def _run_task(self):
        # Find or create Version
        product = self._find_product()
        if not self.dry_run:
            version = self._find_or_create_version(product)
            if self.labels_path and "slug" in product:
                self._publish_labels(product["slug"])

        # Check out the specified tag
        repo_owner = self.project_config.repo_owner
        repo_name = self.project_config.repo_name
        gh = self.project_config.get_github_api()
        repo = gh.repository(repo_owner, repo_name)
        if self.tag:
            tag = self.options["tag"]
            self.commit = repo.tag(repo.ref("tags/" + tag).object.sha).object.sha
        self.logger.info(
            "Downloading commit {} of {} from GitHub".format(
                self.commit, repo.full_name
            )
        )
        zf = download_extract_github(gh, repo_owner, repo_name, ref=self.commit)
        with temporary_dir() as project_dir:
            zf.extractall(project_dir)
            project_config = BaseProjectConfig(
                self.project_config.universal_config_obj,
                repo_info={
                    "root": project_dir,
                    "owner": repo_owner,
                    "name": repo_name,
                    "url": self.project_config.repo_url,
                    "branch": self.tag or self.commit,
                    "commit": self.commit,
                },
            )
            project_config.set_keychain(self.project_config.keychain)

            # Create each plan
            for plan_name, plan_config in self.plan_configs.items():

                self._add_labels(
                    plan_config,
                    f"plan:{plan_name}",
                    {
                        "title": "title of installation plan",
                        "preflight_message": "shown before user starts installation (markdown)",
                        "preflight_message_additional": "shown before user starts installation (markdown)",
                        "post_install_message": "shown after successful installation (markdown)",
                        "post_install_message_additional": "shown after successful installation (markdown)",
                        "error_message": "shown after failed installation (markdown)",
                    },
                )
                checks = plan_config.get("checks") or []
                for check in checks:
                    self._add_label(
                        "checks", check.get("message"), "shown if validation fails"
                    )

                steps = self._freeze_steps(project_config, plan_config)
                self.logger.debug("Prepared steps:\n" + json.dumps(steps, indent=4))
                for step in steps:
                    # avoid separate labels for installing each package
                    if step["name"].startswith("Install "):
                        self._add_label(
                            "steps",
                            "Install {product} {version}",
                            "title of installation step",
                        )
                    else:
                        self._add_label(
                            "steps", step["name"], "title of installation step"
                        )
                    self._add_label(
                        "steps",
                        step.get("description"),
                        "description of installation step",
                    )
                    for check in step["task_config"].get("checks", []):
                        self._add_label(
                            "checks", check.get("message"), "shown if validation fails"
                        )

                if not self.dry_run:
                    self._publish_plan(product, version, plan_name, plan_config, steps)

            # Update version to set is_listed=True
            if self.publish:
                self._call_api(
                    "PATCH",
                    "/versions/{}".format(version["id"]),
                    json={"is_listed": True},
                )
                self.logger.info("Published Version {}".format(version["url"]))

        # Save labels
        self._save_labels()
class TestRunApexTests(unittest.TestCase):

    def setUp(self):
        self.api_version = 38.0
        self.global_config = BaseGlobalConfig(
            {'project': {'api_version': self.api_version}})
        self.task_config = TaskConfig()
        self.task_config.config['options'] = {
            'junit_output': 'results_junit.xml',
            'poll_interval': 1,
            'test_name_match': '%_TEST',
        }
        self.project_config = BaseProjectConfig(self.global_config)
        self.project_config.config['project'] = {'package': {
            'api_version': self.api_version}}
        keychain = BaseProjectKeychain(self.project_config, '')
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig({
            'id': 'foo/1',
            'instance_url': 'example.com',
            'access_token': 'abc123',
        }, 'test')
        self.base_tooling_url = 'https://{}/services/data/v{}/tooling/'.format(
            self.org_config.instance_url, self.api_version)

    def _mock_apex_class_query(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+Id%2C+Name+' +
            'FROM+ApexClass+WHERE+NamespacePrefix+%3D+null' +
            '+AND+%28Name+LIKE+%27%25_TEST%27%29')
        expected_response = {
            'done': True,
            'records': [{'Id': 1, 'Name': 'TestClass_TEST'}],
            'totalSize': 1,
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_get_test_results(self):
        url = (self.base_tooling_url + 'query/?q=%0ASELECT+Id%2CApexClassId%2CTestTimestamp%2C%0A+++++++Message%2CMethodName%2COutcome%2C%0A+++++++RunTime%2CStackTrace%2C%0A+++++++%28SELECT+%0A++++++++++Id%2CCallouts%2CAsyncCalls%2CDmlRows%2CEmail%2C%0A++++++++++LimitContext%2CLimitExceptions%2CMobilePush%2C%0A++++++++++QueryRows%2CSosl%2CCpu%2CDml%2CSoql+%0A++++++++FROM+ApexTestResults%29+%0AFROM+ApexTestResult+%0AWHERE+AsyncApexJobId%3D%27JOB_ID1234567%27%0A')

        expected_response = {
            'done': True,

            'records': [{
                "attributes": {
                    "type": "ApexTestResult",
                    "url": "/services/data/v40.0/tooling/sobjects/ApexTestResult/07M41000009gbT3EAI"
                },
                "ApexClass": {
                    "attributes": {
                        "type": "ApexClass",
                        "url": "/services/data/v40.0/tooling/sobjects/ApexClass/01p4100000Fu4Z0AAJ"
                    },
                    "Name": "EP_TaskDependency_TEST"
                },
                "ApexClassId": 1,
                "ApexLogId": 1,
                "TestTimestamp": "2017-07-18T20:36:04.000+0000",
                "Id": "07M41000009gbT3EAI",
                "Message": "Test Passed",
                "MethodName": "TestMethod",
                "Outcome": "Pass",
                "QueueItem": {
                    "attributes": {
                        "type": "ApexTestQueueItem",
                        "url": "/services/data/v40.0/tooling/sobjects/ApexTestQueueItem/70941000000q7VsAAI"
                    },
                    "Status": "Completed",
                    "ExtendedStatus": "(4/4)"
                },
                "RunTime": 1707,
                "StackTrace": "1. ParentFunction\n2. ChildFunction",
                "Status": 'Completed',
                'Name': 'TestClass_TEST',
                "ApexTestResults": {
                    "size": 1,
                    "totalSize": 1,
                    "done": True,
                    "queryLocator": None,
                    "entityTypeName": "ApexTestResultLimits",
                    "records": [{
                        "attributes": {
                            "type": "ApexTestResultLimits",
                            "url": "/services/data/v40.0/tooling/sobjects/ApexTestResultLimits/05n41000002Y7OQAA0"
                        },
                        "Id": "05n41000002Y7OQAA0",
                        "Callouts": 0,
                        "AsyncCalls": 0,
                        "DmlRows": 5,
                        "Email": 0,
                        "LimitContext": "SYNC",
                        "LimitExceptions": None,
                        "MobilePush": 0,
                        "QueryRows": 20,
                        "Sosl": 0,
                        "Cpu": 471,
                        "Dml": 4,
                        "Soql": 5
                    }]
                }
            }]}

        responses.add(responses.GET, url,
                      match_querystring=True, json=expected_response)

    def _mock_tests_complete(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+Id%2C+Status%2C+' +
            'ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27' +
            'JOB_ID1234567%27')
        expected_response = {
            'done': True,
            'records': [{'Status': 'Completed'}],
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_run_tests(self):
        url = self.base_tooling_url + 'runTestsAsynchronous'
        expected_response = 'JOB_ID1234567'
        responses.add(responses.POST, url, json=expected_response)

    @responses.activate
    def test_run_task(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(
            self.project_config, self.task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 4)
class TestRunCustomSettingsWait(MockLoggerMixin, unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.universal_config = UniversalConfig(
            {"project": {"api_version": self.api_version}}
        )
        self.task_config = TaskConfig()
        self.project_config = BaseProjectConfig(
            self.universal_config, config={"noyaml": True}
        )
        self.project_config.config["project"] = {
            "package": {"api_version": self.api_version}
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = DummyOrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_normal_url = "{}/services/data/v{}/query/".format(
            self.org_config.instance_url, self.api_version
        )
        self.task_log = self._task_log_handler.messages

    def _get_query_resp(self):
        return {
            "size": 1,
            "totalSize": 1,
            "done": True,
            "queryLocator": None,
            "entityTypeName": "Customizable_Rollup_Setings__c",
            "records": [
                {
                    "attributes": {
                        "type": "Customizable_Rollup_Setings__c",
                        "url": "/services/data/v47.0/sobjects/Customizable_Rollup_Setings__c/707L0000014nnPHIAY",
                    },
                    "Id": "707L0000014nnPHIAY",
                    "SetupOwnerId": "00Dxxxxxxxxxxxx",
                    "Customizable_Rollups_Enabled__c": True,
                    "Rollups_Account_Batch_Size__c": 200,
                }
            ],
        }

    def _get_url_and_task(self):
        task = CustomSettingValueWait(
            self.project_config, self.task_config, self.org_config
        )
        url = self.base_normal_url
        return task, url

    @responses.activate
    def test_run_custom_settings_wait_match_bool(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Customizable_Rollups_Enabled__c",
            "value": True,
            "poll_interval": 1,
        }

        # simulate finding no settings record and then finding one with the expected value
        task, url = self._get_url_and_task()
        responses.add(
            responses.GET, url, json={"totalSize": 0, "done": True, "records": []}
        )
        response = self._get_query_resp()
        response["records"][0]["Customizable_Rollups_Enabled__c"] = True
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_custom_settings_wait_match_bool_changed_case(self):
        self.task_config.config["options"] = {
            "object": "CUSTOMIZABLE_Rollup_Setings__c",
            "field": "CUSTOMIZABLE_Rollups_enabled__C",
            "value": True,
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Customizable_Rollups_Enabled__c"] = True
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_custom_settings_wait_match_int(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Rollups_Account_Batch_Size__c",
            "value": 200,
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Rollups_Account_Batch_Size__c"] = 200
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_custom_settings_wait_match_str(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Rollups_Account_Batch_Size__c",
            "value": "asdf",
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Rollups_Account_Batch_Size__c"] = "asdf"
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_custom_settings_wait_not_settings_object(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Rollups_Account_Batch_Size__c",
            "value": 200,
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        responses.add(
            responses.GET,
            url,
            status=400,
            json=[
                {
                    "message": "\nSELECT SetupOwnerId FROM npe5__Affiliation__c\n       ^\nERROR at Row:1:Column:8\nNo such column 'SetupOwnerId' on entity 'npe5__Affiliation__c'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.",
                    "errorCode": "INVALID_FIELD",
                }
            ],
        )

        with self.assertRaises(TaskOptionsError) as e:
            task()

        assert "supported" in str(e.exception)

    def test_apply_namespace__managed(self):
        self.project_config.config["project"]["package"]["namespace"] = "ns"
        self.task_config.config["options"] = {
            "object": "%%%NAMESPACE%%%Test__c",
            "field": "Field__c",
            "value": "x",
            "managed": True,
            "namespaced": True,
        }
        task, url = self._get_url_and_task()
        task.object_name = "%%%NAMESPACE%%%Test__c"
        task.field_name = "%%%NAMESPACE%%%Field__c"
        task._apply_namespace()
        assert task.object_name == "ns__Test__c"
        assert task.field_name == "ns__Field__c"
class TestRunApexTests(unittest.TestCase):

    def setUp(self):
        self.api_version = 38.0
        self.global_config = BaseGlobalConfig(
            {'project': {'api_version': self.api_version}})
        self.task_config = TaskConfig()
        self.task_config.config['options'] = {
            'junit_output': 'results_junit.xml',
            'poll_interval': 1,
            'test_name_match': '%_TEST',
        }
        self.project_config = BaseProjectConfig(self.global_config)
        self.project_config.config['project'] = {'package': {
            'api_version': self.api_version}}
        keychain = BaseProjectKeychain(self.project_config, '')
        app_config = ConnectedAppOAuthConfig()
        keychain.set_connected_app(app_config)
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig({
            'id': 'foo/1',
            'instance_url': 'example.com',
            'access_token': 'abc123',
        })
        self.base_tooling_url = 'https://{}/services/data/v{}/tooling/'.format(
            self.org_config.instance_url, self.api_version)

    def _mock_apex_class_query(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+Id%2C+Name+' +
            'FROM+ApexClass+WHERE+NamespacePrefix+%3D+null' +
            '+AND+%28Name+LIKE+%27%25_TEST%27%29')
        expected_response = {
            'done': True,
            'records': [{'Id': 1, 'Name': 'TestClass_TEST'}],
            'totalSize': 1,
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_get_test_results(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+StackTrace%2C+' +
            'Message%2C+ApexLogId%2C+AsyncApexJobId%2C+MethodName%2C+' +
            'Outcome%2C+ApexClassId%2C+TestTimestamp+FROM+ApexTestResult+' +
            'WHERE+AsyncApexJobId+%3D+%27JOB_ID1234567%27')
        expected_response = {
            'done': True,
            'records': [{
                'ApexClassId': 1,
                'ApexLogId': 1,
                'Id': 1,
                'Message': 'Test passed',
                'MethodName': 'TestMethod',
                'Name': 'TestClass_TEST',
                'Outcome': 'Pass',
                'StackTrace': '1. ParentFunction\n2. ChildFunction',
                'Status': 'Completed',
            }],
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_tests_complete(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+Id%2C+Status%2C+' +
            'ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27' +
            'JOB_ID1234567%27')
        expected_response = {
            'done': True,
            'records': [{'Status': 'Completed'}],
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_run_tests(self):
        url = self.base_tooling_url + 'runTestsAsynchronous'
        expected_response = 'JOB_ID1234567'
        responses.add(responses.POST, url, json=expected_response)

    @responses.activate
    def test_run_task(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(
            self.project_config, self.task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 4)
class TestMrbelvederePublish(unittest.TestCase):
    def setUp(self):
        self.project_config = BaseProjectConfig(BaseGlobalConfig(), get_base_config())
        self.project_config.config["project"]["package"]["namespace"] = "npsp"
        self.project_config.config["project"]["dependencies"] = [
            {"namespace": "nochangedep", "version": "1.0"},
            {"namespace": "changedep", "version": "1.1"},
        ]
        keychain = BaseProjectKeychain(self.project_config, "")
        keychain.set_service(
            "mrbelvedere",
            ServiceConfig({"base_url": "http://mrbelvedere", "api_key": "1234"}),
        )
        self.project_config.set_keychain(keychain)
        self.task_config = TaskConfig({"options": {"tag": "beta/1.0-Beta_2"}})

    @responses.activate
    def test_run_task(self):
        responses.add(
            responses.GET,
            "http://mrbelvedere/npsp/dependencies/beta",
            body=json.dumps(
                [
                    {"namespace": "npsp", "number": "1.0 (Beta 1)"},
                    {"namespace": "changedep", "number": "1.0"},
                    {"namespace": "nochangedep", "number": "1.0"},
                    {"namespace": "other", "number": "1.0"},
                ]
            ),
        )
        responses.add(
            responses.POST, "http://mrbelvedere/npsp/dependencies/beta", status=200
        )

        task = MrbelvederePublish(self.project_config, self.task_config)
        task()

        result = json.loads(responses.calls[-1].request.body)
        self.assertEqual(
            [
                {"namespace": "changedep", "number": "1.1"},
                {"namespace": "npsp", "number": "1.0 (Beta 2)"},
            ],
            result,
        )

    @responses.activate
    def test_run_task__http_error(self):
        responses.add(
            responses.POST, "http://mrbelvedere/npsp/dependencies/beta", status=500
        )

        task = MrbelvederePublish(self.project_config, self.task_config)
        task._get_current_dependencies = mock.Mock(
            return_value=[{"namespace": "npsp", "number": "1.0 (Beta 1)"}]
        )
        with self.assertRaises(MrbelvedereError):
            task()

    def test_clean_dependencies(self):
        task = MrbelvederePublish(self.project_config, self.task_config)
        result = task._clean_dependencies(
            [
                {"github": "https://github.com/SFDO-Tooling/CumulusCI-Test"},
                {"namespace": "foo", "version": "1.0"},
                {"namespace": "foo", "version": "2.0", "dependencies": []},
            ]
        )
        self.assertEqual([{"namespace": "foo", "number": "2.0"}], result)

    def test_clean_dependencies__no_deps(self):
        task = MrbelvederePublish(self.project_config, self.task_config)
        result = task._clean_dependencies([])
        self.assertEqual([], result)
Beispiel #15
0
class TestRunBatchApex(MockLoggerMixin, unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.universal_config = UniversalConfig(
            {"project": {"api_version": self.api_version}}
        )
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "class_name": "ADDR_Seasonal_BATCH",
            "poll_interval": 1,
        }
        self.project_config = BaseProjectConfig(
            self.universal_config, config={"noyaml": True}
        )
        self.project_config.config["project"] = {
            "package": {"api_version": self.api_version}
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version
        )
        self.task_log = self._task_log_handler.messages

    def _get_query_resp(self):
        return {
            "size": 1,
            "totalSize": 1,
            "done": True,
            "queryLocator": None,
            "entityTypeName": "AsyncApexJob",
            "records": [
                {
                    "attributes": {
                        "type": "AsyncApexJob",
                        "url": "/services/data/v43.0/tooling/sobjects/AsyncApexJob/707L0000014nnPHIAY",
                    },
                    "Id": "707L0000014nnPHIAY",
                    "ApexClass": {
                        "attributes": {
                            "type": "ApexClass",
                            "url": "/services/data/v43.0/tooling/sobjects/ApexClass/01pL000000109ndIAA",
                        },
                        "Name": "ADDR_Seasonal_BATCH",
                    },
                    "Status": "Completed",
                    "ExtendedStatus": None,
                    "TotalJobItems": 1,
                    "JobItemsProcessed": 1,
                    "NumberOfErrors": 0,
                    "CreatedDate": "2018-08-07T16:00:56.000+0000",
                    "CompletedDate": "2018-08-07T16:01:57.000+0000",
                }
            ],
        }

    def _update_job_result(self, response: dict, result_dict: dict):
        "Extend the result from _get_query_resp with additional batch records"
        template_result = response["records"][-1]  # use the last result as a template
        assert isinstance(template_result, dict)
        new_result = {**template_result, **result_dict}  # copy with variations
        old_subjob_results = [  # set completed for all old subjob results
            {**record, "Status": "Completed"} for record in response["records"]
        ]
        result_list = [
            new_result
        ] + old_subjob_results  # prepend new result because SOQL is order by DESC
        return {**response, "records": result_list}

    def _get_url_and_task(self):
        task = BatchApexWait(self.project_config, self.task_config, self.org_config)
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+ApexClass.Name%2C+Status%2C+ExtendedStatus%2C+TotalJobItems%2C+JobItemsProcessed%2C+NumberOfErrors%2C+CreatedDate%2C+CompletedDate+FROM+AsyncApexJob+WHERE+JobType%3D%27BatchApex%27+AND+ApexClass.Name%3D%27ADDR_Seasonal_BATCH%27++++ORDER+BY+CreatedDate+DESC++LIMIT+1+"
        )
        return task, url

    @responses.activate
    def test_run_batch_apex_status_fail(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["NumberOfErrors"] = 1
        response["records"][0]["ExtendedStatus"] = "Bad Status"
        responses.add(responses.GET, url, json=response)
        with self.assertRaises(SalesforceException) as cm:
            task()
        err = cm.exception
        self.assertIn("Bad Status", err.args[0])

    @responses.activate
    def test_run_batch_apex_number_mismatch(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["JobItemsProcessed"] = 1
        response["records"][0]["TotalJobItems"] = 3
        responses.add(responses.GET, url, json=response)
        task()

        self.assertIn("The final record counts do not add up.", self.task_log["info"])

    @responses.activate
    def test_run_batch_apex_status_ok(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_batch_apex_calc_elapsed_time(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        responses.add(responses.GET, url, json=response)
        task()
        self.assertEqual(task.elapsed_time(task.subjobs), 61)

    @responses.activate
    def test_run_batch_apex_status_aborted(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Status"] = "Aborted"
        response["records"][0]["JobItemsProcessed"] = 1
        response["records"][0]["TotalJobItems"] = 3
        responses.add(responses.GET, url, json=response)
        with self.assertRaises(SalesforceException) as e:
            task()
        assert "aborted" in str(e.exception)

    @responses.activate
    def test_run_batch_apex_status_failed(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Status"] = "Failed"
        response["records"][0]["JobItemsProcessed"] = 1
        response["records"][0]["TotalJobItems"] = 3
        responses.add(responses.GET, url, json=response)
        self.task_log["info"] = []
        with self.assertRaises(SalesforceException) as e:
            task()
        assert "failure" in str(e.exception)

    @responses.activate
    def test_chained_subjobs(self):
        "Test subjobs that kick off a successor before they complete"
        task, url = self._get_url_and_task()
        url2 = (
            url.split("?")[0]
            + "?q=SELECT+Id%2C+ApexClass.Name%2C+Status%2C+ExtendedStatus%2C+TotalJobItems%2C+JobItemsProcessed%2C+NumberOfErrors%2C+CreatedDate%2C+CompletedDate+FROM+AsyncApexJob+WHERE+JobType%3D%27BatchApex%27+AND+ApexClass.Name%3D%27ADDR_Seasonal_BATCH%27++AND+CreatedDate+%3E%3D+2018-08-07T16%3A00%3A00Z++ORDER+BY+CreatedDate+DESC++"
        )

        # batch 1
        response = self._get_query_resp()
        batch_record = response["records"][0]
        batch_record.update(
            {
                "JobItemsProcessed": 1,
                "TotalJobItems": 3,
                "NumberOfErrors": 0,
                "Status": "Processing",
                "CreatedDate": "2018-08-07T16:00:00.000+0000",
            }
        )

        responses.add(responses.GET, url, json=response)

        # batch 2: 1 error
        response = self._update_job_result(
            response, {"Id": "Id2", "NumberOfErrors": 1, "Status": "Processing"}
        )
        responses.add(responses.GET, url2, json=response)

        # batch 3: found another error
        response = self._update_job_result(
            response, {"Id": "Id2", "NumberOfErrors": 2, "Status": "Processing"}
        )
        responses.add(responses.GET, url2, json=response)

        # batch 4: Complete, no errors in this sub-batch
        response = self._update_job_result(
            response,
            {
                "NumberOfErrors": 0,
                "Id": "Id4",
                "Status": "Completed",
                "CompletedDate": "2018-08-07T16:10:00.000+0000",  # 10 minutes passed
            },
        )
        responses.add(responses.GET, url2, json=response.copy())

        with self.assertRaises(SalesforceException) as e:
            task()

        assert len(task.subjobs) == 4
        summary = task.summarize_subjobs(task.subjobs)
        assert not summary["Success"]
        assert not summary["CountsAddUp"]
        assert summary["ElapsedTime"] == 10 * 60
        assert summary["JobItemsProcessed"] == 4
        assert summary["TotalJobItems"] == 12
        assert summary["NumberOfErrors"] == 3
        assert "batch errors" in str(e.exception)

    @responses.activate
    def test_chained_subjobs_beginning(self):
        "Test the first subjob that kicks off a successor before they complete"
        task, url = self._get_url_and_task()
        url2 = (
            url.split("?")[0]
            + "?q=SELECT+Id%2C+ApexClass.Name%2C+Status%2C+ExtendedStatus%2C+TotalJobItems%2C+JobItemsProcessed%2C+NumberOfErrors%2C+CreatedDate%2C+CompletedDate+FROM+AsyncApexJob+WHERE+JobType%3D%27BatchApex%27+AND+ApexClass.Name%3D%27ADDR_Seasonal_BATCH%27++AND+CreatedDate+%3E%3D+2018-08-07T16%3A00%3A00Z++ORDER+BY+CreatedDate+DESC++"
        )

        # batch 1
        response = self._get_query_resp()
        responses.add(responses.GET, url2, json=response)

        batch_record = response["records"][0]
        batch_record.update(
            {
                "JobItemsProcessed": 1,
                "TotalJobItems": 3,
                "NumberOfErrors": 0,
                "Status": "Preparing",
                "CreatedDate": "2018-08-07T16:00:00.000+0000",
                "CompletedDate": None,
            }
        )

        real_poll_action = task._poll_action
        counter = 0

        def mock_poll_action():
            nonlocal counter
            counter += 1
            if counter == 1:
                task.poll_complete = False
                return real_poll_action()
            else:
                rc = real_poll_action()
                task.poll_complete = True
                return rc

        task._poll_action = mock_poll_action

        responses.add(responses.GET, url, json=response)

        task()
        assert counter == 2
        assert task.poll_complete
        summary = task.summarize_subjobs(task.subjobs)
        assert not summary["NumberOfErrors"]

    @responses.activate
    def test_chained_subjobs_halfway(self):
        "Test part-way through a series of subjobs that kick off a successor before they complete"
        task, url = self._get_url_and_task()
        url2 = (
            url.split("?")[0]
            + "?q=SELECT+Id%2C+ApexClass.Name%2C+Status%2C+ExtendedStatus%2C+TotalJobItems%2C+JobItemsProcessed%2C+NumberOfErrors%2C+CreatedDate%2C+CompletedDate+FROM+AsyncApexJob+WHERE+JobType%3D%27BatchApex%27+AND+ApexClass.Name%3D%27ADDR_Seasonal_BATCH%27++AND+CreatedDate+%3E%3D+2018-08-07T16%3A00%3A00Z++ORDER+BY+CreatedDate+DESC++"
        )

        # batch 1
        response = self._get_query_resp()
        responses.add(responses.GET, url2, json=response)

        batch_record = response["records"][0]
        batch_record.update(
            {
                "JobItemsProcessed": 1,
                "TotalJobItems": 3,
                "NumberOfErrors": 0,
                "Status": "Preparing",
                "CreatedDate": "2018-08-07T16:00:00.000+0000",
                "CompletedDate": "2018-08-07T16:05:00.000+0000",
            }
        )

        # batch 2: 1 error
        response = self._update_job_result(
            response, {"Id": "Id2", "NumberOfErrors": 1, "CompletedDate": None}
        )
        responses.add(responses.GET, url2, json=response)

        real_poll_action = task._poll_action
        counter = 0

        def mock_poll_action():
            nonlocal counter
            counter += 1
            if counter == 1:
                task.poll_complete = False
                return real_poll_action()
            else:
                rc = real_poll_action()
                task.poll_complete = True
                return rc

        task._poll_action = mock_poll_action

        responses.add(responses.GET, url, json=response)

        task()
        assert counter == 2
        assert task.poll_complete
        summary = task.summarize_subjobs(task.subjobs)
        assert not summary["NumberOfErrors"]

    @responses.activate
    def test_job_not_found(self):

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"] = []
        responses.add(responses.GET, url, json=response)

        with self.assertRaises(SalesforceException) as e:
            task()

        assert "found" in str(e.exception)
Beispiel #16
0
class TestAnonymousApexTask(unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.global_config = BaseGlobalConfig(
            {"project": {
                "api_version": self.api_version
            }})
        self.tmpdir = tempfile.mkdtemp(dir=".")
        apex_path = os.path.join(self.tmpdir, "test.apex")
        with open(apex_path, "w") as f:
            f.write('System.debug("from file")')
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "path": apex_path,
            "apex": 'system.debug("Hello World!")',
            "namespaced": True,
        }
        self.project_config = BaseProjectConfig(self.global_config,
                                                config={"noyaml": True})
        self.project_config.config = {
            "project": {
                "package": {
                    "namespace": "abc",
                    "api_version": self.api_version
                }
            }
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version)

    def tearDown(self):
        shutil.rmtree(self.tmpdir)

    def _get_url_and_task(self):
        task = AnonymousApexTask(self.project_config, self.task_config,
                                 self.org_config)
        url = self.base_tooling_url + "executeAnonymous"
        return task, url

    def test_validate_options(self):
        task_config = TaskConfig({})
        with self.assertRaises(TaskOptionsError):
            AnonymousApexTask(self.project_config, task_config,
                              self.org_config)

    def test_run_from_path_outside_repo(self):
        task_config = TaskConfig({"options": {"path": "/"}})
        task = AnonymousApexTask(self.project_config, task_config,
                                 self.org_config)
        with self.assertRaises(TaskOptionsError):
            task()

    def test_run_path_not_found(self):
        task_config = TaskConfig({"options": {"path": "bogus"}})
        task = AnonymousApexTask(self.project_config, task_config,
                                 self.org_config)
        with self.assertRaises(TaskOptionsError):
            task()

    def test_prepare_apex(self):
        task = AnonymousApexTask(self.project_config, self.task_config,
                                 self.org_config)
        before = "String %%%NAMESPACE%%%str = 'foo';"
        expected = "String abc__str = 'foo';"
        self.assertEqual(expected, task._prepare_apex(before))

    @responses.activate
    def test_run_anonymous_apex_success(self):
        task, url = self._get_url_and_task()
        resp = {"compiled": True, "success": True}
        responses.add(responses.GET, url, status=200, json=resp)
        task()

    @responses.activate
    def test_run_string_only(self):
        task_config = TaskConfig(
            {"options": {
                "apex": 'System.debug("test");'
            }})
        task = AnonymousApexTask(self.project_config, task_config,
                                 self.org_config)
        url = self.base_tooling_url + "executeAnonymous"
        responses.add(responses.GET,
                      url,
                      status=200,
                      json={
                          "compiled": True,
                          "success": True
                      })
        task()

    @responses.activate
    def test_run_anonymous_apex_status_fail(self):
        task, url = self._get_url_and_task()
        responses.add(responses.GET, url, status=418, body="I'm a teapot")
        with self.assertRaises(SalesforceGeneralError) as cm:
            task()
        err = cm.exception
        self.assertEqual(str(err),
                         "Error Code 418. Response content: I'm a teapot")
        self.assertTrue(err.url.startswith(url))
        self.assertEqual(err.status, 418)
        self.assertEqual(err.content, "I'm a teapot")

    @responses.activate
    def test_run_anonymous_apex_compile_except(self):
        task, url = self._get_url_and_task()
        problem = "Unexpected token '('."
        resp = {
            "compiled": False,
            "compileProblem": problem,
            "success": False,
            "line": 1,
            "column": 13,
            "exceptionMessage": "",
            "exceptionStackTrace": "",
            "logs": "",
        }
        responses.add(responses.GET, url, status=200, json=resp)
        with self.assertRaises(ApexCompilationException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err.args[0], 1)
        self.assertEqual(err.args[1], problem)

    @responses.activate
    def test_run_anonymous_apex_except(self):
        task, url = self._get_url_and_task()
        problem = "Unexpected token '('."
        trace = "Line 0, Column 99"
        resp = {
            "compiled": True,
            "compileProblem": "",
            "success": False,
            "line": 1,
            "column": 13,
            "exceptionMessage": problem,
            "exceptionStackTrace": trace,
            "logs": "",
        }
        responses.add(responses.GET, url, status=200, json=resp)
        with self.assertRaises(ApexException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err.args[0], problem)
        self.assertEqual(err.args[1], trace)
Beispiel #17
0
class TestSFDXBaseTask(MockLoggerMixin, unittest.TestCase):
    """ Tests for the Base Task type """
    def setUp(self):
        self.universal_config = UniversalConfig()
        self.project_config = BaseProjectConfig(self.universal_config,
                                                config={"noyaml": True})
        self.task_config = TaskConfig()

        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)

        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages

    def test_base_task(self):
        """ The command is prefixed w/ sfdx """

        self.task_config.config["options"] = {
            "command": "force:org",
            "extra": "--help"
        }
        task = SFDXBaseTask(self.project_config, self.task_config)

        try:
            task()
        except CommandException:
            pass

        self.assertEqual("force:org", task.options["command"])
        self.assertEqual("sfdx force:org --help", task._get_command())

    @patch(
        "cumulusci.tasks.sfdx.SFDXOrgTask._update_credentials",
        MagicMock(return_value=None),
    )
    @patch("cumulusci.tasks.command.Command._run_task",
           MagicMock(return_value=None))
    def test_keychain_org_creds(self):
        """ Keychain org creds are passed by env var """

        self.task_config.config["options"] = {"command": "force:org --help"}
        access_token = "00d123"
        org_config = OrgConfig(
            {
                "access_token": access_token,
                "instance_url": "https://test.salesforce.com",
            },
            "test",
        )

        task = SFDXOrgTask(self.project_config, self.task_config, org_config)

        try:
            task()
        except CommandException:
            pass

        self.assertIn("SFDX_INSTANCE_URL", task._get_env())
        self.assertIn("SFDX_DEFAULTUSERNAME", task._get_env())
        self.assertIn(access_token, task._get_env()["SFDX_DEFAULTUSERNAME"])

    def test_scratch_org_username(self):
        """ Scratch Org credentials are passed by -u flag """
        self.task_config.config["options"] = {"command": "force:org --help"}
        org_config = ScratchOrgConfig({"username": "******"}, "test")

        task = SFDXOrgTask(self.project_config, self.task_config, org_config)
        self.assertIn("-u [email protected]", task._get_command())
class TestCreateConnectedApp(MockLoggerMixin, unittest.TestCase):
    """ Tests for the CreateConnectedApp task """

    def setUp(self):
        self.global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )

        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)

        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages
        self.base_command = "sfdx force:mdapi:deploy --wait {}".format(
            CreateConnectedApp.deploy_wait
        )
        self.label = "Test_Label"
        self.username = "******"
        self.email = "TestUser@Email"
        self.task_config = TaskConfig(
            {
                "options": {
                    "label": self.label,
                    "username": self.username,
                    "email": self.email,
                }
            }
        )

    def test_init_options(self):
        """ Passed options are correctly initialized """
        self.task_config.config["options"]["connect"] = True
        self.task_config.config["options"]["overwrite"] = True
        task = CreateConnectedApp(self.project_config, self.task_config)
        self.assertEqual(
            task.options["command"], "{} -u {}".format(self.base_command, self.username)
        )
        self.assertEqual(task.options["label"], self.label)
        self.assertEqual(task.options["username"], self.username)
        self.assertEqual(task.options["email"], self.email)
        self.assertIs(task.options["connect"], True)
        self.assertIs(task.options["overwrite"], True)

    def test_init_options_invalid_label(self):
        """ Non-alphanumeric + _ label raises TaskOptionsError """
        self.task_config.config["options"]["label"] = "Test Label"
        with pytest.raises(TaskOptionsError, match="^label value must contain only"):
            CreateConnectedApp(self.project_config, self.task_config)

    def test_init_options_email_default(self):
        """ email option defaults to email from github service """
        del self.task_config.config["options"]["email"]
        self.project_config.config["services"] = {
            "github": {"attributes": {"email": {}}}
        }
        self.project_config.keychain.set_service(
            "github", ServiceConfig({"email": self.email}), True
        )
        task = CreateConnectedApp(self.project_config, self.task_config)
        self.assertEqual(task.options["email"], self.email)

    def test_init_options_email_not_found(self):
        """ TaskOptionsError is raised if no email provided and no github service exists """
        del self.task_config.config["options"]["email"]
        self.project_config.config["services"] = {"github": {"attributes": {}}}
        with pytest.raises(TaskOptionsError, match="github"):
            CreateConnectedApp(self.project_config, self.task_config)

    @mock.patch("cumulusci.tasks.connectedapp.CreateConnectedApp._set_default_username")
    def test_init_options_default_username(self, set_mock):
        """ Not passing username calls _get_default_username """
        del self.task_config.config["options"]["username"]
        try:
            CreateConnectedApp(self.project_config, self.task_config)
        except Exception:
            pass
        set_mock.assert_called_once()

    @mock.patch("cumulusci.tasks.connectedapp.CreateConnectedApp._run_command")
    def test_set_default_username(self, run_command_mock):
        """ _set_default_username calls _run_command """
        task = CreateConnectedApp(self.project_config, self.task_config)
        run_command_mock.side_effect = lambda **kw: kw["output_handler"](
            b'{"result":[{"value":"username"}]}'
        )
        task._set_default_username()
        run_command_mock.assert_called_once()
        self.assertEqual(
            self.task_log["info"], ["Getting username for the default devhub from sfdx"]
        )

    def test_process_json_output(self):
        """ _process_json_output returns valid json """
        task = CreateConnectedApp(self.project_config, self.task_config)
        output = task._process_json_output('{"foo":"bar"}')
        self.assertEqual(output, {"foo": "bar"})

    def test_process_json_output_invalid(self):
        """ _process_json_output with invalid input logs output and raises JSONDecodeError """
        task = CreateConnectedApp(self.project_config, self.task_config)
        with pytest.raises(JSONDecodeError):
            task._process_json_output("invalid")
        self.assertEqual(
            self.task_log["error"], ["Failed to parse json from output: invalid"]
        )

    @mock.patch(
        "cumulusci.tasks.connectedapp.CreateConnectedApp._set_default_username",
        MagicMock(return_value=None),
    )
    def test_process_devhub_output(self):
        """ username is parsed from json response """
        del self.task_config.config["options"]["username"]
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._process_devhub_output('{"result":[{"value":"' + self.username + '"}]}')
        self.assertEqual(task.options.get("username"), self.username)

    @mock.patch(
        "cumulusci.tasks.connectedapp.CreateConnectedApp._set_default_username",
        MagicMock(return_value=None),
    )
    def test_process_devhub_output_not_configured(self):
        """ TaskOptionsError is raised if no username provided and no default found """
        del self.task_config.config["options"]["username"]
        task = CreateConnectedApp(self.project_config, self.task_config)
        with pytest.raises(TaskOptionsError, match="^No sfdx config found"):
            task._process_devhub_output('{"result":[{}]}')

    def test_generate_id_and_secret(self):
        """ client_id and client_secret are generated correctly """
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._generate_id_and_secret()
        self.assertEqual(len(task.client_id), task.client_id_length)
        self.assertEqual(len(task.client_secret), task.client_secret_length)
        self.assertNotEqual(re.match(r"^\w+$", task.client_id), None)
        self.assertNotEqual(re.match(r"^\w+$", task.client_secret), None)

    def test_build_package(self):
        """ tempdir is populated with connected app and package.xml """
        task = CreateConnectedApp(self.project_config, self.task_config)
        with temporary_dir() as tempdir:
            task.tempdir = tempdir
            connected_app_path = os.path.join(
                task.tempdir, "connectedApps", "{}.connectedApp".format(self.label)
            )
            task._build_package()
            self.assertTrue(os.path.isdir(os.path.join(task.tempdir, "connectedApps")))
            self.assertTrue(os.path.isfile(os.path.join(task.tempdir, "package.xml")))
            self.assertTrue(os.path.isfile(connected_app_path))
            with open(connected_app_path, "r") as f:
                connected_app = f.read()
                self.assertTrue("<label>{}<".format(self.label) in connected_app)
                self.assertTrue("<contactEmail>{}<".format(self.email) in connected_app)
                self.assertTrue(
                    "<consumerKey>{}<".format(task.client_id) in connected_app
                )
                self.assertTrue(
                    "<consumerSecret>{}<".format(task.client_secret) in connected_app
                )

    def test_connect_service(self):
        """ connected app gets added to the keychain connected_app service """
        self.project_config.config["services"] = {
            "connected_app": {
                "attributes": {"callback_url": {}, "client_id": {}, "client_secret": {}}
            }
        }
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._connect_service()
        connected_app = self.project_config.keychain.get_service("connected_app")
        self.assertEqual(connected_app.callback_url, "http://*****:*****@mock.patch("cumulusci.tasks.sfdx.SFDXBaseTask._run_task")
    def test_run_task(self, run_task_mock):
        """ _run_task formats command, calls SFDXBaseTask._run_task, and does not connect service by default """
        self.project_config.config["services"] = {
            "connected_app": {
                "attributes": {"callback_url": {}, "client_id": {}, "client_secret": {}}
            }
        }
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._run_task()
        run_task_mock.assert_called_once()
        self.assertFalse(os.path.isdir(task.tempdir))
        self.assertEqual(
            task.options["command"],
            self.base_command + " -u {} -d {}".format(self.username, task.tempdir),
        )
        with pytest.raises(ServiceNotConfigured):
            self.project_config.keychain.get_service("connected_app")

    @mock.patch("cumulusci.tasks.sfdx.SFDXBaseTask._run_task")
    @mock.patch("cumulusci.tasks.connectedapp.CreateConnectedApp._connect_service")
    def test_run_task_connect(self, run_task_mock, connect_service_mock):
        """ _run_task calls _connect_service if connect option is True """
        self.project_config.config["services"] = {
            "connected_app": {
                "attributes": {"callback_url": {}, "client_id": {}, "client_secret": {}}
            }
        }
        self.task_config.config["options"]["connect"] = True
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._run_task()
        run_task_mock.assert_called_once()
        connect_service_mock.assert_called_once()
Beispiel #19
0
class TestCreateConnectedApp(MockLoggerMixin, unittest.TestCase):
    """ Tests for the CreateConnectedApp task """

    def setUp(self):
        self.global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )

        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)

        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages
        self.base_command = "sfdx force:mdapi:deploy --wait {}".format(
            CreateConnectedApp.deploy_wait
        )
        self.label = "Test_Label"
        self.username = "******"
        self.email = "TestUser@Email"
        self.task_config = TaskConfig(
            {
                "options": {
                    "label": self.label,
                    "username": self.username,
                    "email": self.email,
                }
            }
        )

    def test_init_options(self):
        """ Passed options are correctly initialized """
        self.task_config.config["options"]["connect"] = True
        self.task_config.config["options"]["overwrite"] = True
        task = CreateConnectedApp(self.project_config, self.task_config)
        self.assertEqual(
            task.options["command"], "{} -u {}".format(self.base_command, self.username)
        )
        self.assertEqual(task.options["label"], self.label)
        self.assertEqual(task.options["username"], self.username)
        self.assertEqual(task.options["email"], self.email)
        self.assertIs(task.options["connect"], True)
        self.assertIs(task.options["overwrite"], True)

    def test_init_options_invalid_label(self):
        """ Non-alphanumeric + _ label raises TaskOptionsError """
        self.task_config.config["options"]["label"] = "Test Label"
        with pytest.raises(TaskOptionsError, match="^label value must contain only"):
            CreateConnectedApp(self.project_config, self.task_config)

    def test_init_options_email_default(self):
        """ email option defaults to email from github service """
        del self.task_config.config["options"]["email"]
        self.project_config.config["services"] = {
            "github": {"attributes": {"email": {}}}
        }
        self.project_config.keychain.set_service(
            "github", ServiceConfig({"email": self.email}), True
        )
        task = CreateConnectedApp(self.project_config, self.task_config)
        self.assertEqual(task.options["email"], self.email)

    def test_init_options_email_not_found(self):
        """ TaskOptionsError is raised if no email provided and no github service exists """
        del self.task_config.config["options"]["email"]
        self.project_config.config["services"] = {"github": {"attributes": {}}}
        with pytest.raises(TaskOptionsError, match="github"):
            CreateConnectedApp(self.project_config, self.task_config)

    @mock.patch("cumulusci.tasks.connectedapp.CreateConnectedApp._set_default_username")
    def test_init_options_default_username(self, set_mock):
        """ Not passing username calls _get_default_username """
        del self.task_config.config["options"]["username"]
        try:
            CreateConnectedApp(self.project_config, self.task_config)
        except Exception:
            pass
        set_mock.assert_called_once()

    @mock.patch("cumulusci.tasks.connectedapp.CreateConnectedApp._run_command")
    def test_set_default_username(self, run_command_mock):
        """ _set_default_username calls _run_command """
        task = CreateConnectedApp(self.project_config, self.task_config)
        run_command_mock.side_effect = lambda **kw: kw["output_handler"](
            b'{"result":[{"value":"username"}]}'
        )
        task._set_default_username()
        run_command_mock.assert_called_once()
        self.assertEqual(
            self.task_log["info"], ["Getting username for the default devhub from sfdx"]
        )

    def test_process_json_output(self):
        """ _process_json_output returns valid json """
        task = CreateConnectedApp(self.project_config, self.task_config)
        output = task._process_json_output('{"foo":"bar"}')
        self.assertEqual(output, {"foo": "bar"})

    def test_process_json_output_invalid(self):
        """ _process_json_output with invalid input logs output and raises JSONDecodeError """
        task = CreateConnectedApp(self.project_config, self.task_config)
        with pytest.raises(JSONDecodeError):
            task._process_json_output("invalid")
        self.assertEqual(
            self.task_log["error"], ["Failed to parse json from output: invalid"]
        )

    @mock.patch(
        "cumulusci.tasks.connectedapp.CreateConnectedApp._set_default_username",
        MagicMock(return_value=None),
    )
    def test_process_devhub_output(self):
        """ username is parsed from json response """
        del self.task_config.config["options"]["username"]
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._process_devhub_output('{"result":[{"value":"' + self.username + '"}]}')
        self.assertEqual(task.options.get("username"), self.username)

    @mock.patch(
        "cumulusci.tasks.connectedapp.CreateConnectedApp._set_default_username",
        MagicMock(return_value=None),
    )
    def test_process_devhub_output_not_configured(self):
        """ TaskOptionsError is raised if no username provided and no default found """
        del self.task_config.config["options"]["username"]
        task = CreateConnectedApp(self.project_config, self.task_config)
        with pytest.raises(TaskOptionsError, match="^No sfdx config found"):
            task._process_devhub_output('{"result":[{}]}')

    def test_generate_id_and_secret(self):
        """ client_id and client_secret are generated correctly """
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._generate_id_and_secret()
        self.assertEqual(len(task.client_id), task.client_id_length)
        self.assertEqual(len(task.client_secret), task.client_secret_length)
        self.assertNotEqual(re.match(r"^\w+$", task.client_id), None)
        self.assertNotEqual(re.match(r"^\w+$", task.client_secret), None)

    def test_build_package(self):
        """ tempdir is populated with connected app and package.xml """
        task = CreateConnectedApp(self.project_config, self.task_config)
        with temporary_dir() as tempdir:
            task.tempdir = tempdir
            connected_app_path = os.path.join(
                task.tempdir, "connectedApps", "{}.connectedApp".format(self.label)
            )
            task._build_package()
            self.assertTrue(os.path.isdir(os.path.join(task.tempdir, "connectedApps")))
            self.assertTrue(os.path.isfile(os.path.join(task.tempdir, "package.xml")))
            self.assertTrue(os.path.isfile(connected_app_path))
            with open(connected_app_path, "r") as f:
                connected_app = f.read()
                self.assertTrue("<label>{}<".format(self.label) in connected_app)
                self.assertTrue("<contactEmail>{}<".format(self.email) in connected_app)
                self.assertTrue(
                    "<consumerKey>{}<".format(task.client_id) in connected_app
                )
                self.assertTrue(
                    "<consumerSecret>{}<".format(task.client_secret) in connected_app
                )

    def test_connect_service(self):
        """ connected app gets added to the keychain connected_app service """
        self.project_config.config["services"] = {
            "connected_app": {
                "attributes": {"callback_url": {}, "client_id": {}, "client_secret": {}}
            }
        }
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._connect_service()
        connected_app = self.project_config.keychain.get_service("connected_app")
        self.assertEqual(connected_app.callback_url, "http://*****:*****@mock.patch("cumulusci.tasks.sfdx.SFDXBaseTask._run_task")
    def test_run_task(self, run_task_mock):
        """ _run_task formats command, calls SFDXBaseTask._run_task, and does not connect service by default """
        self.project_config.config["services"] = {
            "connected_app": {
                "attributes": {"callback_url": {}, "client_id": {}, "client_secret": {}}
            }
        }
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._run_task()
        run_task_mock.assert_called_once()
        self.assertFalse(os.path.isdir(task.tempdir))
        self.assertEqual(
            task.options["command"],
            self.base_command + " -u {} -d {}".format(self.username, task.tempdir),
        )
        connected_app = self.project_config.keychain.get_service("connected_app")
        assert connected_app is DEFAULT_CONNECTED_APP

    @mock.patch("cumulusci.tasks.sfdx.SFDXBaseTask._run_task")
    @mock.patch("cumulusci.tasks.connectedapp.CreateConnectedApp._connect_service")
    def test_run_task_connect(self, run_task_mock, connect_service_mock):
        """ _run_task calls _connect_service if connect option is True """
        self.project_config.config["services"] = {
            "connected_app": {
                "attributes": {"callback_url": {}, "client_id": {}, "client_secret": {}}
            }
        }
        self.task_config.config["options"]["connect"] = True
        task = CreateConnectedApp(self.project_config, self.task_config)
        task._run_task()
        run_task_mock.assert_called_once()
        connect_service_mock.assert_called_once()
class TestRunAnonApex(unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.global_config = BaseGlobalConfig(
            {"project": {
                "api_version": self.api_version
            }})
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "apex": 'system.debug("Hello World!")'
        }
        self.project_config = BaseProjectConfig(self.global_config)
        self.project_config.config["project"] = {
            "package": {
                "api_version": self.api_version
            }
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "example.com",
                "access_token": "abc123"
            },
            "test",
        )
        self.base_tooling_url = "https://{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version)

    def _get_url_and_task(self):
        task = AnonymousApexTask(self.project_config, self.task_config,
                                 self.org_config)
        url = task.tooling.base_url + "executeAnonymous"
        return task, url

    @responses.activate
    def test_run_anonymous_apex_status_fail(self):
        task, url = self._get_url_and_task()
        responses.add(responses.GET, url, status=418, body="I'm a teapot")
        with self.assertRaises(SalesforceGeneralError) as cm:
            task()
        err = cm.exception
        self.assertEqual(str(err),
                         "Error Code 418. Response content: I'm a teapot")
        self.assertTrue(err.url.startswith(url))
        self.assertEqual(err.status, 418)
        self.assertEqual(err.content, "I'm a teapot")

    @responses.activate
    def test_run_anonymous_apex_compile_except(self):
        task, url = self._get_url_and_task()
        problem = "Unexpected token '('."
        resp = {
            "compiled": False,
            "compileProblem": problem,
            "success": False,
            "line": 1,
            "column": 13,
            "exceptionMessage": "",
            "exceptionStackTrace": "",
            "logs": "",
        }
        responses.add(responses.GET, url, status=200, json=resp)
        with self.assertRaises(ApexCompilationException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err[0], 1)
        self.assertEqual(err[1], problem)

    @responses.activate
    def test_run_anonymous_apex_except(self):
        task, url = self._get_url_and_task()
        problem = "Unexpected token '('."
        trace = "Line 0, Column 99"
        resp = {
            "compiled": True,
            "compileProblem": "",
            "success": False,
            "line": 1,
            "column": 13,
            "exceptionMessage": problem,
            "exceptionStackTrace": trace,
            "logs": "",
        }
        responses.add(responses.GET, url, status=200, json=resp)
        with self.assertRaises(ApexException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err[0], problem)
        self.assertEqual(err[1], trace)
class TestRunCustomSettingsWait(MockLoggerMixin, unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.global_config = BaseGlobalConfig(
            {"project": {
                "api_version": self.api_version
            }})
        self.task_config = TaskConfig()
        self.project_config = BaseProjectConfig(self.global_config,
                                                config={"noyaml": True})
        self.project_config.config["project"] = {
            "package": {
                "api_version": self.api_version
            }
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_normal_url = "{}/services/data/v{}/query/".format(
            self.org_config.instance_url, self.api_version)
        self.task_log = self._task_log_handler.messages

    def _get_query_resp(self):
        return {
            "size":
            1,
            "totalSize":
            1,
            "done":
            True,
            "queryLocator":
            None,
            "entityTypeName":
            "Customizable_Rollup_Setings__c",
            "records": [{
                "attributes": {
                    "type":
                    "Customizable_Rollup_Setings__c",
                    "url":
                    "/services/data/v47.0/sobjects/Customizable_Rollup_Setings__c/707L0000014nnPHIAY",
                },
                "Id": "707L0000014nnPHIAY",
                "SetupOwnerId": "00Dxxxxxxxxxxxx",
                "Customizable_Rollups_Enabled__c": True,
                "Rollups_Account_Batch_Size__c": 200,
            }],
        }

    def _get_url_and_task(self):
        task = CustomSettingValueWait(self.project_config, self.task_config,
                                      self.org_config)
        url = self.base_normal_url
        return task, url

    @responses.activate
    def test_run_custom_settings_wait_match_bool(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Customizable_Rollups_Enabled__c",
            "value": True,
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Customizable_Rollups_Enabled__c"] = True
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_custom_settings_wait_match_int(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Rollups_Account_Batch_Size__c",
            "value": 200,
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["Rollups_Account_Batch_Size__c"] = 200
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_custom_settings_wait_bad_object(self):
        self.task_config.config["options"] = {
            "object": "Customizable_Rollup_Setings__c",
            "field": "Rollups_Account_Batch_Size__c",
            "value": 200,
            "poll_interval": 1,
        }

        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["SetupOwnerId"] = "00X"
        responses.add(responses.GET, url, json=response)
        # task()

        with self.assertRaises(SalesforceException) as e:
            task()

        assert "found" in str(e.exception)
Beispiel #22
0
class TestMrbelvederePublish(unittest.TestCase):
    def setUp(self):
        global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(global_config,
                                                config=global_config.config)
        self.project_config.config["project"]["package"]["namespace"] = "npsp"
        self.project_config.config["project"]["dependencies"] = [
            {
                "namespace": "nochangedep",
                "version": "1.0"
            },
            {
                "namespace": "changedep",
                "version": "1.1"
            },
        ]
        keychain = BaseProjectKeychain(self.project_config, "")
        keychain.set_service(
            "mrbelvedere",
            ServiceConfig({
                "base_url": "http://mrbelvedere",
                "api_key": "1234"
            }),
        )
        self.project_config.set_keychain(keychain)
        self.task_config = TaskConfig({"options": {"tag": "beta/1.0-Beta_2"}})

    @responses.activate
    def test_run_task(self):
        responses.add(
            responses.GET,
            "http://mrbelvedere/npsp/dependencies/beta",
            body=json.dumps([
                {
                    "namespace": "npsp",
                    "number": "1.0 (Beta 1)"
                },
                {
                    "namespace": "changedep",
                    "number": "1.0"
                },
                {
                    "namespace": "nochangedep",
                    "number": "1.0"
                },
                {
                    "namespace": "other",
                    "number": "1.0"
                },
            ]),
        )
        responses.add(responses.POST,
                      "http://mrbelvedere/npsp/dependencies/beta",
                      status=200)

        task = MrbelvederePublish(self.project_config, self.task_config)
        task()

        result = json.loads(responses.calls[-1].request.body)
        self.assertEqual(
            [
                {
                    "namespace": "changedep",
                    "number": "1.1"
                },
                {
                    "namespace": "npsp",
                    "number": "1.0 (Beta 2)"
                },
            ],
            result,
        )

    @responses.activate
    def test_run_task__http_error(self):
        responses.add(responses.POST,
                      "http://mrbelvedere/npsp/dependencies/beta",
                      status=500)

        task = MrbelvederePublish(self.project_config, self.task_config)
        task._get_current_dependencies = mock.Mock(
            return_value=[{
                "namespace": "npsp",
                "number": "1.0 (Beta 1)"
            }])
        with self.assertRaises(MrbelvedereError):
            task()

    def test_clean_dependencies(self):
        task = MrbelvederePublish(self.project_config, self.task_config)
        result = task._clean_dependencies([
            {
                "github": "https://github.com/SFDO-Tooling/CumulusCI-Test"
            },
            {
                "namespace": "foo",
                "version": "1.0"
            },
            {
                "namespace": "foo",
                "version": "2.0",
                "dependencies": []
            },
        ])
        self.assertEqual([{"namespace": "foo", "number": "2.0"}], result)

    def test_clean_dependencies__no_deps(self):
        task = MrbelvederePublish(self.project_config, self.task_config)
        result = task._clean_dependencies([])
        self.assertEqual([], result)
class TestRunApexTests(unittest.TestCase):
    def setUp(self):
        self.api_version = 38.0
        self.global_config = BaseGlobalConfig(
            {"project": {"api_version": self.api_version}}
        )
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
        }
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )
        self.project_config.config["project"] = {
            "package": {"api_version": self.api_version}
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version
        )

    def _mock_apex_class_query(self):
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+Name+"
            + "FROM+ApexClass+WHERE+NamespacePrefix+%3D+null"
            + "+AND+%28Name+LIKE+%27%25_TEST%27%29"
        )
        expected_response = {
            "done": True,
            "records": [{"Id": 1, "Name": "TestClass_TEST"}],
            "totalSize": 1,
        }
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_get_test_results(self, outcome="Pass"):
        url = (
            self.base_tooling_url
            + "query/?q=%0ASELECT+Id%2CApexClassId%2CTestTimestamp%2C%0A+++++++Message%2CMethodName%2COutcome%2C%0A+++++++RunTime%2CStackTrace%2C%0A+++++++%28SELECT+%0A++++++++++Id%2CCallouts%2CAsyncCalls%2CDmlRows%2CEmail%2C%0A++++++++++LimitContext%2CLimitExceptions%2CMobilePush%2C%0A++++++++++QueryRows%2CSosl%2CCpu%2CDml%2CSoql+%0A++++++++FROM+ApexTestResults%29+%0AFROM+ApexTestResult+%0AWHERE+AsyncApexJobId%3D%27JOB_ID1234567%27%0A"
        )

        expected_response = {
            "done": True,
            "records": [
                {
                    "attributes": {
                        "type": "ApexTestResult",
                        "url": "/services/data/v40.0/tooling/sobjects/ApexTestResult/07M41000009gbT3EAI",
                    },
                    "ApexClass": {
                        "attributes": {
                            "type": "ApexClass",
                            "url": "/services/data/v40.0/tooling/sobjects/ApexClass/01p4100000Fu4Z0AAJ",
                        },
                        "Name": "EP_TaskDependency_TEST",
                    },
                    "ApexClassId": 1,
                    "ApexLogId": 1,
                    "TestTimestamp": "2017-07-18T20:36:04.000+0000",
                    "Id": "07M41000009gbT3EAI",
                    "Message": "Test Passed",
                    "MethodName": "TestMethod",
                    "Outcome": outcome,
                    "QueueItem": {
                        "attributes": {
                            "type": "ApexTestQueueItem",
                            "url": "/services/data/v40.0/tooling/sobjects/ApexTestQueueItem/70941000000q7VsAAI",
                        },
                        "Status": "Completed",
                        "ExtendedStatus": "(4/4)",
                    },
                    "RunTime": 1707,
                    "StackTrace": "1. ParentFunction\n2. ChildFunction",
                    "Status": "Completed",
                    "Name": "TestClass_TEST",
                    "ApexTestResults": {
                        "size": 1,
                        "totalSize": 1,
                        "done": True,
                        "queryLocator": None,
                        "entityTypeName": "ApexTestResultLimits",
                        "records": [
                            {
                                "attributes": {
                                    "type": "ApexTestResultLimits",
                                    "url": "/services/data/v40.0/tooling/sobjects/ApexTestResultLimits/05n41000002Y7OQAA0",
                                },
                                "Id": "05n41000002Y7OQAA0",
                                "Callouts": 0,
                                "AsyncCalls": 0,
                                "DmlRows": 5,
                                "Email": 0,
                                "LimitContext": "SYNC",
                                "LimitExceptions": None,
                                "MobilePush": 0,
                                "QueryRows": 20,
                                "Sosl": 0,
                                "Cpu": 471,
                                "Dml": 4,
                                "Soql": 5,
                            }
                        ],
                    },
                }
            ],
        }

        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_tests_complete(self):
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+Status%2C+"
            + "ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27"
            + "JOB_ID1234567%27"
        )
        expected_response = {"done": True, "records": [{"Status": "Completed"}]}
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_run_tests(self, success=True):
        url = self.base_tooling_url + "runTestsAsynchronous"
        if success:
            expected_response = "JOB_ID1234567"
            responses.add(responses.POST, url, json=expected_response)
        else:
            responses.add(responses.POST, url, status=http.client.SERVICE_UNAVAILABLE)

    @responses.activate
    def test_run_task(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 4)

    @responses.activate
    def test_run_task__server_error(self):
        self._mock_apex_class_query()
        self._mock_run_tests(success=False)
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        with self.assertRaises(SalesforceGeneralError):
            task()

    @responses.activate
    def test_run_task__failed(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results("Fail")
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    def test_get_namespace_filter__managed(self):
        task_config = TaskConfig({"options": {"managed": True, "namespace": "testns"}})
        task = RunApexTests(self.project_config, task_config, self.org_config)
        namespace = task._get_namespace_filter()
        self.assertEqual("'testns'", namespace)

    def test_get_namespace_filter__managed_no_namespace(self):
        task_config = TaskConfig({"options": {"managed": True}})
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(TaskOptionsError):
            namespace = task._get_namespace_filter()

    def test_get_test_class_query__exclude(self):
        task_config = TaskConfig(
            {"options": {"test_name_match": "%_TEST", "test_name_exclude": "EXCL"}}
        )
        task = RunApexTests(self.project_config, task_config, self.org_config)
        query = task._get_test_class_query()
        self.assertEqual(
            "SELECT Id, Name FROM ApexClass WHERE NamespacePrefix = null "
            "AND (Name LIKE '%_TEST') AND (NOT Name LIKE 'EXCL')",
            query,
        )

    def test_run_task__no_tests(self):
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task._get_test_classes = MagicMock(return_value={"totalSize": 0})
        task()
        self.assertIsNone(task.result)
Beispiel #24
0
    def _run_task(self):
        tag = self.options["tag"]

        repo_owner = self.project_config.repo_owner
        repo_name = self.project_config.repo_name
        gh = self.project_config.get_github_api()
        repo = gh.repository(repo_owner, repo_name)
        commit_sha = repo.tag(repo.ref("tags/" + tag).object.sha).object.sha
        self.logger.info("Downloading commit {} of {}/{} from GitHub".format(
            commit_sha, repo_owner, repo_name))
        zf = download_extract_github(gh, repo_owner, repo_name, ref=commit_sha)
        with temporary_dir() as project_dir:
            zf.extractall(project_dir)
            project_config = BaseProjectConfig(
                self.project_config.global_config_obj,
                repo_info={
                    "root": project_dir,
                    "owner": repo_owner,
                    "name": repo_name,
                    "url": self.project_config.repo_url,
                    "branch": tag,
                    "commit": commit_sha,
                },
            )
            project_config.set_keychain(self.project_config.keychain)
            steps = self._freeze_steps(project_config)
        self.logger.debug("Publishing steps:\n" + json.dumps(steps, indent=4))

        # create version (not listed yet)
        product_url = self.base_url + "/products/{}".format(
            self.options["product_id"])
        label = self.project_config.get_version_for_tag(tag)
        version = self._call_api(
            "POST",
            "/versions",
            json={
                "product": product_url,
                "label": label,
                "description": self.options.get("description", ""),
                "is_production": True,
                "commit_ish": self.project_config.repo_commit,
                "is_listed": False,
            },
        )
        self.logger.info("Created {}".format(version["url"]))

        # create plan
        plan_template_id = self.options.get("plan_template_id")
        plan_template_url = (self.base_url +
                             "/plantemplates/{}".format(plan_template_id)
                             if plan_template_id else None)
        plan = self._call_api(
            "POST",
            "/plans",
            json={
                "plan_template":
                plan_template_url,
                "post_install_message_additional":
                self.options.get("post_install_message_additional", ""),
                "preflight_message_additional":
                self.options.get("preflight_message_additional", ""),
                "steps":
                steps,
                "tier":
                self.options["tier"],
                "title":
                self.options["title"],
                "version":
                version["url"],
            },
        )
        self.logger.info("Created Plan {}".format(plan["url"]))

        # create plan slug
        planslug = self._call_api(
            "POST",
            "/planslug",
            json={
                "parent": plan["url"],
                "slug": self.options["slug"]
            },
        )
        self.logger.info("Created PlanSlug {}".format(planslug["url"]))

        # update version to set is_listed=True
        self._call_api("PATCH",
                       "/versions/{}".format(version["id"]),
                       json={"is_listed": True})
        self.logger.info("Published Version {}".format(version["url"]))
Beispiel #25
0
class TestSFDXBaseTask(unittest.TestCase):
    """ Tests for the Base Task type """

    @classmethod
    def setUpClass(cls):
        super(TestSFDXBaseTask, cls).setUpClass()
        logger = logging.getLogger(cumulusci.core.tasks.__name__)
        logger.setLevel(logging.DEBUG)
        cls._task_log_handler = MockLoggingHandler(logging.DEBUG)
        logger.addHandler(cls._task_log_handler)

    def setUp(self):
        self.global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(self.global_config)
        self.task_config = TaskConfig()

        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)

        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages

    def test_base_task(self):
        """ The command is prefixed w/ sfdx """

        self.task_config.config["options"] = {"command": "force:org --help"}
        task = SFDXBaseTask(self.project_config, self.task_config)

        try:
            task()
        except CommandException:
            pass

        self.assertEqual("sfdx force:org --help", task.options["command"])

    @patch(
        "cumulusci.tasks.sfdx.SFDXOrgTask._update_credentials",
        MagicMock(return_value=None),
    )
    @patch("cumulusci.tasks.command.Command._run_task", MagicMock(return_value=None))
    def test_keychain_org_creds(self):
        """ Keychain org creds are passed by env var """

        self.task_config.config["options"] = {"command": "force:org --help"}
        access_token = "00d123"
        org_config = OrgConfig(
            {
                "access_token": access_token,
                "instance_url": "https://test.salesforce.com",
            },
            "test",
        )

        task = SFDXOrgTask(self.project_config, self.task_config, org_config)

        try:
            task()
        except CommandException:
            pass

        self.assertIn("SFDX_INSTANCE_URL", task._get_env())
        self.assertIn("SFDX_USERNAME", task._get_env())
        self.assertIn(access_token, task._get_env()["SFDX_USERNAME"])

    def test_scratch_org_username(self):
        """ Scratch Org credentials are passed by -u flag """

        self.task_config.config["options"] = {"command": "force:org --help"}
        org_config = OrgConfig(
            {"access_token": "test access token", "instance_url": "test instance url"},
            "test",
        )

        task = SFDXOrgTask(self.project_config, self.task_config, org_config)

        try:
            task()
        except CommandException:
            pass

        env = task._get_env()
        print(env)
        self.assertEquals(env.get("SFDX_USERNAME"), "test access token")
        self.assertEquals(env.get("SFDX_INSTANCE_URL"), "test instance url")
Beispiel #26
0
class TestGitHubSource(unittest.TestCase, MockUtil):
    def setUp(self):
        self.repo_api_url = "https://api.github.com/repos/TestOwner/TestRepo"
        universal_config = UniversalConfig()
        self.project_config = BaseProjectConfig(universal_config)
        self.project_config.set_keychain(
            BaseProjectKeychain(self.project_config, None))
        self.project_config.keychain.set_service(
            "github",
            ServiceConfig({
                "username": "******",
                "password": "******",
                "email": "*****@*****.**",
            }),
        )

    @responses.activate
    def test_resolve__default(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git"})
        assert (
            repr(source) ==
            "<GitHubSource GitHub: TestOwner/TestRepo @ tags/release/1.0 (tag_sha)>"
        )

    @responses.activate
    def test_resolve__default_no_release(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            status=404,
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/heads/main",
            json=self._get_expected_ref("heads/main", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git"})
        assert (repr(source) ==
                "<GitHubSource GitHub: TestOwner/TestRepo @ main (abcdef)>")

    @responses.activate
    def test_resolve__commit(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "commit": "abcdef"
            },
        )
        assert repr(
            source) == "<GitHubSource GitHub: TestOwner/TestRepo @ abcdef>"

    @responses.activate
    def test_resolve__ref(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/main",
            json=self._get_expected_ref("main", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "ref": "main"
            },
        )
        assert (repr(source) ==
                "<GitHubSource GitHub: TestOwner/TestRepo @ main (abcdef)>")

    @responses.activate
    def test_resolve__branch(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/heads/main",
            json=self._get_expected_ref("main", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "branch": "main"
            },
        )
        assert (repr(source) ==
                "<GitHubSource GitHub: TestOwner/TestRepo @ main (abcdef)>")

    @responses.activate
    def test_resolve__tag(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "tag": "release/1.0",
            },
        )
        assert (
            repr(source) ==
            "<GitHubSource GitHub: TestOwner/TestRepo @ tags/release/1.0 (abcdef)>"
        )

    @responses.activate
    def test_resolve__latest_release(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "release": "latest",
            },
        )
        assert (
            repr(source) ==
            "<GitHubSource GitHub: TestOwner/TestRepo @ tags/release/1.0 (tag_sha)>"
        )

    @responses.activate
    def test_resolve__latest_beta(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases",
            json=[self._get_expected_release("beta/1.0-Beta_1")],
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/beta/1.0-Beta_1",
            json=self._get_expected_tag_ref("beta/1.0-Beta_1", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "release": "latest_beta",
            },
        )
        assert (
            repr(source) ==
            "<GitHubSource GitHub: TestOwner/TestRepo @ tags/beta/1.0-Beta_1 (tag_sha)>"
        )

    @responses.activate
    def test_resolve__previous_release(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases",
            json=[
                self._get_expected_release("release/2.0"),
                self._get_expected_release("beta/1.0-Beta_1", prerelease=True),
                self._get_expected_release("release/1.0"),
            ],
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "release": "previous",
            },
        )
        assert (
            repr(source) ==
            "<GitHubSource GitHub: TestOwner/TestRepo @ tags/release/1.0 (tag_sha)>"
        )

    @responses.activate
    def test_resolve__release_not_found(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            status=404,
        )

        with pytest.raises(DependencyResolutionError):
            GitHubSource(
                self.project_config,
                {
                    "github": "https://github.com/TestOwner/TestRepo.git",
                    "release": "latest",
                },
            )

    @responses.activate
    def test_resolve__bogus_release(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )

        with pytest.raises(DependencyResolutionError):
            GitHubSource(
                self.project_config,
                {
                    "github": "https://github.com/TestOwner/TestRepo.git",
                    "release": "bogus",
                },
            )

    @responses.activate
    def test_fetch(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )
        f = io.BytesIO()
        zf = zipfile.ZipFile(f, "w")
        zfi = zipfile.ZipInfo("toplevel/")
        zf.writestr(zfi, "")
        zf.writestr(
            "toplevel/cumulusci.yml",
            yaml.dump({
                "project": {
                    "package": {
                        "name_managed": "Test Product",
                        "namespace": "ns"
                    }
                }
            }),
        )
        zf.close()
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/zipball/tag_sha",
            body=f.getvalue(),
            content_type="application/zip",
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git"})
        with temporary_dir() as d:
            project_config = source.fetch()
            assert isinstance(project_config, BaseProjectConfig)
            assert project_config.repo_root == os.path.join(
                os.path.realpath(d), ".cci", "projects", "TestRepo", "tag_sha")

    @responses.activate
    @mock.patch("cumulusci.core.source.github.download_extract_github")
    def test_fetch__cleans_up_after_failed_extract(self,
                                                   download_extract_github):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )
        # Set up a fake IOError while extracting the zipball
        download_extract_github.return_value = mock.Mock(extractall=mock.Mock(
            side_effect=IOError))

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git"})
        with temporary_dir():
            with pytest.raises(IOError):
                source.fetch()
            assert not pathlib.Path(".cci", "projects", "TestRepo",
                                    "tag_sha").exists()

    @responses.activate
    def test_hash(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git"})
        assert hash(source) == hash(
            ("https://github.com/TestOwner/TestRepo", "tag_sha"))

    @responses.activate
    def test_frozenspec(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git"})
        assert source.frozenspec == {
            "github": "https://github.com/TestOwner/TestRepo",
            "commit": "tag_sha",
            "description": "tags/release/1.0",
        }
Beispiel #27
0
class TestGitHubSource(unittest.TestCase, MockUtil):
    def setUp(self):
        self.repo_api_url = "https://api.github.com/repos/TestOwner/TestRepo"
        global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(global_config)
        self.project_config.set_keychain(BaseProjectKeychain(self.project_config, None))
        self.project_config.keychain.set_service(
            "github",
            ServiceConfig(
                {
                    "username": "******",
                    "password": "******",
                    "email": "*****@*****.**",
                }
            ),
        )

    @responses.activate
    def test_resolve__latest(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config, {"github": "https://github.com/TestOwner/TestRepo.git"}
        )
        assert (
            repr(source)
            == "<GitHubSource GitHub: TestOwner/TestRepo @ tags/release/1.0 (tag_sha)>"
        )

    @responses.activate
    def test_resolve__no_release(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            status=404,
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/heads/master",
            json=self._get_expected_ref("heads/master", "abcdef"),
        )

        source = GitHubSource(
            self.project_config, {"github": "https://github.com/TestOwner/TestRepo.git"}
        )
        assert (
            repr(source)
            == "<GitHubSource GitHub: TestOwner/TestRepo @ master (abcdef)>"
        )

    @responses.activate
    def test_resolve__commit(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git", "commit": "abcdef"},
        )
        assert repr(source) == "<GitHubSource GitHub: TestOwner/TestRepo @ abcdef>"

    @responses.activate
    def test_resolve__ref(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/master",
            json=self._get_expected_ref("master", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git", "ref": "master"},
        )
        assert (
            repr(source)
            == "<GitHubSource GitHub: TestOwner/TestRepo @ master (abcdef)>"
        )

    @responses.activate
    def test_resolve__branch(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/heads/master",
            json=self._get_expected_ref("master", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {"github": "https://github.com/TestOwner/TestRepo.git", "branch": "master"},
        )
        assert (
            repr(source)
            == "<GitHubSource GitHub: TestOwner/TestRepo @ master (abcdef)>"
        )

    @responses.activate
    def test_fetch(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )
        f = io.BytesIO()
        zf = zipfile.ZipFile(f, "w")
        zfi = zipfile.ZipInfo("toplevel/")
        zf.writestr(zfi, "")
        zf.writestr(
            "toplevel/cumulusci.yml",
            yaml.dump(
                {
                    "project": {
                        "package": {"name_managed": "Test Product", "namespace": "ns"}
                    }
                }
            ),
        )
        zf.close()
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/zipball/tag_sha",
            body=f.getvalue(),
            content_type="application/zip",
        )

        source = GitHubSource(
            self.project_config, {"github": "https://github.com/TestOwner/TestRepo.git"}
        )
        with temporary_dir() as d:
            project_config = source.fetch()
            assert isinstance(project_config, BaseProjectConfig)
            assert project_config.repo_root == os.path.join(
                os.path.realpath(d), ".cci", "projects", "TestRepo", "tag_sha"
            )

    @responses.activate
    def test_resolve__tag(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "abcdef"),
        )

        source = GitHubSource(
            self.project_config,
            {
                "github": "https://github.com/TestOwner/TestRepo.git",
                "tag": "release/1.0",
            },
        )
        assert (
            repr(source)
            == "<GitHubSource GitHub: TestOwner/TestRepo @ tags/release/1.0 (abcdef)>"
        )

    @responses.activate
    def test_hash(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config, {"github": "https://github.com/TestOwner/TestRepo.git"}
        )
        assert hash(source) == hash(
            ("https://github.com/TestOwner/TestRepo", "tag_sha")
        )

    @responses.activate
    def test_frozenspec(self):
        responses.add(
            method=responses.GET,
            url=self.repo_api_url,
            json=self._get_expected_repo(owner="TestOwner", name="TestRepo"),
        )
        responses.add(
            "GET",
            "https://api.github.com/repos/TestOwner/TestRepo/releases/latest",
            json=self._get_expected_release("release/1.0"),
        )
        responses.add(
            "GET",
            f"https://api.github.com/repos/TestOwner/TestRepo/git/refs/tags/release/1.0",
            json=self._get_expected_tag_ref("release/1.0", "tag_sha"),
        )

        source = GitHubSource(
            self.project_config, {"github": "https://github.com/TestOwner/TestRepo.git"}
        )
        assert source.frozenspec == {
            "github": "https://github.com/TestOwner/TestRepo",
            "commit": "tag_sha",
            "description": "tags/release/1.0",
        }
Beispiel #28
0
class TestRunApexTests(MockLoggerMixin, unittest.TestCase):
    def setUp(self):
        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages
        self.api_version = 38.0
        self.universal_config = UniversalConfig(
            {"project": {"api_version": self.api_version}}
        )
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
        }
        self.project_config = BaseProjectConfig(
            self.universal_config, config={"noyaml": True}
        )
        self.project_config.config["project"] = {
            "package": {"api_version": self.api_version}
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version
        )

    def _mock_apex_class_query(self, name="TestClass_TEST", namespace=None):
        namespace_param = "null" if namespace is None else f"%27{namespace}%27"
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+Name+"
            + f"FROM+ApexClass+WHERE+NamespacePrefix+%3D+{namespace_param}"
            + "+AND+%28Name+LIKE+%27%25_TEST%27%29"
        )
        expected_response = {
            "done": True,
            "records": [{"Id": 1, "Name": name}],
            "totalSize": 1,
        }
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _get_mock_test_query_results(self, methodnames, outcomes, messages):
        record_base = {
            "attributes": {
                "type": "ApexTestResult",
                "url": "/services/data/v40.0/tooling/sobjects/ApexTestResult/07M41000009gbT3EAI",
            },
            "ApexClass": {
                "attributes": {
                    "type": "ApexClass",
                    "url": "/services/data/v40.0/tooling/sobjects/ApexClass/01p4100000Fu4Z0AAJ",
                },
                "Name": "EP_TaskDependency_TEST",
            },
            "ApexClassId": 1,
            "ApexLogId": 1,
            "TestTimestamp": "2017-07-18T20:36:04.000+0000",
            "Id": "07M41000009gbT3EAI",
            "Message": None,
            "MethodName": None,
            "Outcome": None,
            "QueueItem": {
                "attributes": {
                    "type": "ApexTestQueueItem",
                    "url": "/services/data/v40.0/tooling/sobjects/ApexTestQueueItem/70941000000q7VsAAI",
                },
                "Status": "Completed",
                "ExtendedStatus": "(4/4)",
            },
            "RunTime": 1707,
            "StackTrace": "1. ParentFunction\n2. ChildFunction",
            "Status": "Completed",
            "Name": "TestClass_TEST",
            "ApexTestResults": {
                "size": 1,
                "totalSize": 1,
                "done": True,
                "queryLocator": None,
                "entityTypeName": "ApexTestResultLimits",
                "records": [
                    {
                        "attributes": {
                            "type": "ApexTestResultLimits",
                            "url": "/services/data/v40.0/tooling/sobjects/ApexTestResultLimits/05n41000002Y7OQAA0",
                        },
                        "Id": "05n41000002Y7OQAA0",
                        "Callouts": 0,
                        "AsyncCalls": 0,
                        "DmlRows": 5,
                        "Email": 0,
                        "LimitContext": "SYNC",
                        "LimitExceptions": None,
                        "MobilePush": 0,
                        "QueryRows": 20,
                        "Sosl": 0,
                        "Cpu": 471,
                        "Dml": 4,
                        "Soql": 5,
                    }
                ],
            },
        }

        return_value = {"done": True, "records": []}

        for (method_name, outcome, message) in zip(methodnames, outcomes, messages):
            this_result = deepcopy(record_base)
            this_result["Message"] = message
            this_result["Outcome"] = outcome
            this_result["MethodName"] = method_name
            return_value["records"].append(this_result)

        return return_value

    def _get_mock_test_query_url(self, job_id):
        return (
            self.base_tooling_url
            + "query/?q=%0ASELECT+Id%2CApexClassId%2CTestTimestamp%2C%0A+++++++Message%2CMethodName%2COutcome%2C%0A+++++++RunTime%2CStackTrace%2C%0A+++++++%28SELECT%0A++++++++++Id%2CCallouts%2CAsyncCalls%2CDmlRows%2CEmail%2C%0A++++++++++LimitContext%2CLimitExceptions%2CMobilePush%2C%0A++++++++++QueryRows%2CSosl%2CCpu%2CDml%2CSoql%0A++++++++FROM+ApexTestResults%29%0AFROM+ApexTestResult%0AWHERE+AsyncApexJobId%3D%27{}%27%0A".format(
                job_id
            )
        )

    def _get_mock_testqueueitem_status_query_url(self, job_id):
        return (
            self.base_tooling_url
            + f"query/?q=SELECT+Id%2C+Status%2C+ExtendedStatus%2C+ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27{job_id}%27+AND+Status+%3D+%27Failed%27"
        )

    def _mock_get_test_results(
        self, outcome="Pass", message="Test Passed", job_id="JOB_ID1234567"
    ):
        url = self._get_mock_test_query_url(job_id)

        expected_response = self._get_mock_test_query_results(
            ["TestMethod"], [outcome], [message]
        )
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_get_test_results_multiple(
        self, method_names, outcomes, messages, job_id="JOB_ID1234567"
    ):
        url = self._get_mock_test_query_url(job_id)

        expected_response = self._get_mock_test_query_results(
            method_names, outcomes, messages
        )
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_get_failed_test_classes(self, job_id="JOB_ID1234567"):
        url = self._get_mock_testqueueitem_status_query_url(job_id)

        responses.add(
            responses.GET,
            url,
            match_querystring=True,
            json={"totalSize": 0, "records": [], "done": True},
        )

    def _mock_get_failed_test_classes_failure(self, job_id="JOB_ID1234567"):
        url = self._get_mock_testqueueitem_status_query_url(job_id)

        responses.add(
            responses.GET,
            url,
            match_querystring=True,
            json={
                "totalSize": 1,
                "records": [
                    {
                        "Id": "0000000000000000",
                        "ApexClassId": 1,
                        "Status": "Failed",
                        "ExtendedStatus": "Double-plus ungood",
                    }
                ],
                "done": True,
            },
        )

    def _mock_get_symboltable(self):
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+SymbolTable+FROM+ApexClass+WHERE+Name%3D%27TestClass_TEST%27"
        )

        responses.add(
            responses.GET,
            url,
            json={
                "records": [
                    {
                        "SymbolTable": {
                            "methods": [
                                {"name": "test1", "annotations": [{"name": "isTest"}]}
                            ]
                        }
                    }
                ]
            },
        )

    def _mock_get_symboltable_failure(self):
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+SymbolTable+FROM+ApexClass+WHERE+Name%3D%27TestClass_TEST%27"
        )

        responses.add(responses.GET, url, json={"records": []})

    def _mock_tests_complete(self, job_id="JOB_ID1234567"):
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+Status%2C+"
            + "ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27"
            + "{}%27".format(job_id)
        )
        expected_response = {
            "done": True,
            "totalSize": 1,
            "records": [{"Status": "Completed"}],
        }
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_tests_processing(self, job_id="JOB_ID1234567"):
        url = (
            self.base_tooling_url
            + "query/?q=SELECT+Id%2C+Status%2C+"
            + "ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27"
            + "{}%27".format(job_id)
        )
        expected_response = {
            "done": True,
            "totalSize": 1,
            "records": [{"Status": "Processing", "ApexClassId": 1}],
        }
        responses.add(
            responses.GET, url, match_querystring=True, json=expected_response
        )

    def _mock_run_tests(self, success=True, body="JOB_ID1234567"):
        url = self.base_tooling_url + "runTestsAsynchronous"
        if success:
            responses.add(responses.POST, url, json=body)
        else:
            responses.add(responses.POST, url, status=http.client.SERVICE_UNAVAILABLE)

    @responses.activate
    def test_run_task(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 5)

    @responses.activate
    def test_run_task__server_error(self):
        self._mock_apex_class_query()
        self._mock_run_tests(success=False)
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        with self.assertRaises(SalesforceGeneralError):
            task()

    @responses.activate
    def test_run_task__failed(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes()
        self._mock_tests_complete()
        self._mock_get_test_results("Fail")
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    @responses.activate
    def test_run_task__failed_class_level(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes_failure()
        self._mock_tests_complete()
        self._mock_get_test_results()
        self._mock_get_symboltable()
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    @responses.activate
    def test_run_task__failed_class_level_no_symboltable(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes_failure()
        self._mock_tests_complete()
        self._mock_get_test_results()
        self._mock_get_symboltable_failure()
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        with self.assertRaises(CumulusCIException):
            task()

    @responses.activate
    def test_run_task__failed_class_level_no_symboltable__spring20_managed(self):
        self._mock_apex_class_query(name="ns__Test_TEST", namespace="ns")
        self._mock_run_tests()
        self._mock_get_failed_test_classes_failure()
        self._mock_tests_complete()
        self._mock_get_test_results()
        self._mock_get_symboltable_failure()
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "managed": True,
            "namespace": "ns",
        }

        task = RunApexTests(self.project_config, task_config, self.org_config)
        task._get_test_methods_for_class = Mock()

        task()

        task._get_test_methods_for_class.assert_not_called()

    @responses.activate
    def test_run_task__retry_tests(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_run_tests(body="JOBID_9999")
        self._mock_get_failed_test_classes()
        self._mock_get_failed_test_classes(job_id="JOBID_9999")
        self._mock_tests_complete()
        self._mock_tests_complete(job_id="JOBID_9999")
        self._mock_get_test_results("Fail", "UNABLE_TO_LOCK_ROW")
        self._mock_get_test_results(job_id="JOBID_9999")

        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 9)

    @responses.activate
    def test_run_task__retry_tests_with_retry_always(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_run_tests(body="JOBID_9999")
        self._mock_run_tests(body="JOBID_9990")
        self._mock_get_failed_test_classes()
        self._mock_get_failed_test_classes(job_id="JOBID_9999")
        self._mock_get_failed_test_classes(job_id="JOBID_9990")
        self._mock_tests_complete()
        self._mock_tests_complete(job_id="JOBID_9999")
        self._mock_tests_complete(job_id="JOBID_9990")
        self._mock_get_test_results_multiple(
            ["TestOne", "TestTwo"],
            ["Fail", "Fail"],
            ["UNABLE_TO_LOCK_ROW", "LimitException"],
        )
        self._mock_get_test_results_multiple(
            ["TestOne"], ["Pass"], [""], job_id="JOBID_9999"
        )
        self._mock_get_test_results_multiple(
            ["TestTwo"], ["Fail"], ["LimitException"], job_id="JOBID_9990"
        )
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
            "retry_always": True,
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    @responses.activate
    def test_run_task__retry_tests_fails(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_run_tests(body="JOBID_9999")
        self._mock_get_failed_test_classes()
        self._mock_get_failed_test_classes(job_id="JOBID_9999")
        self._mock_tests_complete()
        self._mock_tests_complete(job_id="JOBID_9999")
        self._mock_get_test_results("Fail", "UNABLE_TO_LOCK_ROW")
        self._mock_get_test_results("Fail", "DUPLICATES_DETECTED", job_id="JOBID_9999")

        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    @responses.activate
    def test_run_task__processing(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_processing()
        self._mock_get_failed_test_classes()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task()
        log = self._task_log_handler.messages
        assert "Completed: 0  Processing: 1 (TestClass_TEST)  Queued: 0" in log["info"]

    @responses.activate
    def test_run_task__not_verbose(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_processing()
        self._mock_get_failed_test_classes()  # this returns all passes
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task()
        log = self._task_log_handler.messages
        assert "Class: TestClass_TEST" not in log["info"]

    @responses.activate
    def test_run_task__verbose(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes_failure()
        self._mock_tests_complete()
        self._mock_get_test_results()
        self._mock_get_symboltable()
        task_config = TaskConfig()
        task_config.config["options"] = {
            "verbose": True,
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(CumulusCIException):
            task()
        log = self._task_log_handler.messages
        assert "Class: TestClass_TEST" in log["info"]

    @responses.activate
    def test_run_task__no_code_coverage(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task._check_code_coverage = Mock()
        task()
        task._check_code_coverage.assert_not_called()

    @responses.activate
    def test_run_task__checks_code_coverage(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "required_org_code_coverage_percent": "90",
        }

        org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        org_config._installed_packages = {"TEST": StrictVersion("1.2.3")}
        task = RunApexTests(self.project_config, task_config, org_config)
        task._check_code_coverage = Mock()
        task()
        task._check_code_coverage.assert_called_once()

    def test_code_coverage_integer(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "required_org_code_coverage_percent": 90,
        }

        org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        task = RunApexTests(self.project_config, task_config, org_config)

        assert task.code_coverage_level == 90

    def test_code_coverage_percentage(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "required_org_code_coverage_percent": "90%",
        }

        org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        task = RunApexTests(self.project_config, task_config, org_config)

        assert task.code_coverage_level == 90

    def test_exception_bad_code_coverage(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "required_org_code_coverage_percent": "foo",
        }

        with self.assertRaises(TaskOptionsError):
            RunApexTests(self.project_config, task_config, self.org_config)

    @responses.activate
    def test_run_task__code_coverage_managed(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_get_failed_test_classes()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "namespace": "TEST",
            "required_org_code_coverage_percent": "90",
        }
        org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        org_config._installed_packages = {"TEST": StrictVersion("1.2.3")}

        task = RunApexTests(self.project_config, task_config, org_config)
        task._check_code_coverage = Mock()
        task()
        task._check_code_coverage.assert_not_called()

    def test_check_code_coverage(self):
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task.code_coverage_level = 90
        task.tooling = Mock()
        task.tooling.query.return_value = {
            "records": [{"PercentCovered": 90}],
            "totalSize": 1,
        }

        task._check_code_coverage()
        task.tooling.query.assert_called_once_with(
            "SELECT PercentCovered FROM ApexOrgWideCoverage"
        )

    def test_check_code_coverage__fail(self):
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task.code_coverage_level = 90
        task.tooling = Mock()
        task.tooling.query.return_value = {
            "records": [{"PercentCovered": 89}],
            "totalSize": 1,
        }

        with self.assertRaises(ApexTestException):
            task._check_code_coverage()

    def test_is_retriable_failure(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": [
                "UNABLE_TO_LOCK_ROW",
                "unable to obtain exclusive access to this record",
            ],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task._init_options(task_config.config["options"])

        self.assertTrue(
            task._is_retriable_failure(
                {
                    "Message": "UNABLE_TO_LOCK_ROW",
                    "StackTrace": "test",
                    "Outcome": "Fail",
                }
            )
        )
        self.assertTrue(
            task._is_retriable_failure(
                {
                    "Message": "TEST",
                    "StackTrace": "unable to obtain exclusive access to this record",
                    "Outcome": "Fail",
                }
            )
        )
        self.assertFalse(
            task._is_retriable_failure(
                {
                    "Message": "DUPLICATES_DETECTED",
                    "StackTrace": "test",
                    "Outcome": "Fail",
                }
            )
        )

    def test_init_options__regexes(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task._init_options(task_config.config["options"])

        self.assertIsNotNone(
            task.options["retry_failures"][0].search("UNABLE_TO_LOCK_ROW: test failed")
        )

    def test_init_options__bad_regexes(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["("],
        }
        with self.assertRaises(TaskOptionsError):
            task = RunApexTests(self.project_config, task_config, self.org_config)
            task._init_options(task_config.config["options"])

    def test_get_namespace_filter__managed(self):
        task_config = TaskConfig({"options": {"managed": True, "namespace": "testns"}})
        task = RunApexTests(self.project_config, task_config, self.org_config)
        namespace = task._get_namespace_filter()
        self.assertEqual("'testns'", namespace)

    def test_get_namespace_filter__managed_no_namespace(self):
        task_config = TaskConfig({"options": {"managed": True}})
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(TaskOptionsError):
            task._get_namespace_filter()

    def test_get_test_class_query__exclude(self):
        task_config = TaskConfig(
            {"options": {"test_name_match": "%_TEST", "test_name_exclude": "EXCL"}}
        )
        task = RunApexTests(self.project_config, task_config, self.org_config)
        query = task._get_test_class_query()
        self.assertEqual(
            "SELECT Id, Name FROM ApexClass WHERE NamespacePrefix = null "
            "AND (Name LIKE '%_TEST') AND (NOT Name LIKE 'EXCL')",
            query,
        )

    def test_run_task__no_tests(self):
        task = RunApexTests(self.project_config, self.task_config, self.org_config)
        task._get_test_classes = MagicMock(return_value={"totalSize": 0})
        task()
        self.assertIsNone(task.result)
Beispiel #29
0
class TestRunApexTests(unittest.TestCase):

    def setUp(self):
        self.api_version = 38.0
        self.global_config = BaseGlobalConfig(
            {'project': {'api_version': self.api_version}})
        self.task_config = TaskConfig()
        self.task_config.config['options'] = {
            'junit_output': 'results_junit.xml',
            'poll_interval': 1,
            'test_name_match': '%_TEST',
        }
        self.project_config = BaseProjectConfig(self.global_config)
        self.project_config.config['project'] = {'package': {
            'api_version': self.api_version}}
        keychain = BaseProjectKeychain(self.project_config, '')
        app_config = ConnectedAppOAuthConfig()
        keychain.set_connected_app(app_config)
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig({
            'id': 'foo/1',
            'instance_url': 'example.com',
            'access_token': 'abc123',
        })
        self.base_tooling_url = 'https://{}/services/data/v{}/tooling/'.format(
            self.org_config.instance_url, self.api_version)

    def _mock_apex_class_query(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+Id%2C+Name+' +
            'FROM+ApexClass+WHERE+NamespacePrefix+%3D+null' +
            '+AND+%28Name+LIKE+%27%25_TEST%27%29')
        expected_response = {
            'done': True,
            'records': [{'Id': 1, 'Name': 'TestClass_TEST'}],
            'totalSize': 1,
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_get_test_results(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+StackTrace%2C+' +
            'Message%2C+ApexLogId%2C+AsyncApexJobId%2C+MethodName%2C+' +
            'Outcome%2C+ApexClassId%2C+TestTimestamp+FROM+ApexTestResult+' +
            'WHERE+AsyncApexJobId+%3D+%27JOB_ID1234567%27')
        expected_response = {
            'done': True,
            'records': [{
                'ApexClassId': 1,
                'ApexLogId': 1,
                'Id': 1,
                'Message': 'Test passed',
                'MethodName': 'TestMethod',
                'Name': 'TestClass_TEST',
                'Outcome': 'Pass',
                'StackTrace': '1. ParentFunction\n2. ChildFunction',
                'Status': 'Completed',
            }],
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_tests_complete(self):
        url = (self.base_tooling_url + 'query/?q=SELECT+Id%2C+Status%2C+' +
            'ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27' +
            'JOB_ID1234567%27')
        expected_response = {
            'done': True,
            'records': [{'Status': 'Completed'}],
        }
        responses.add(responses.GET, url, match_querystring=True,
            json=expected_response)

    def _mock_run_tests(self):
        url = self.base_tooling_url + 'runTestsAsynchronous'
        expected_response = 'JOB_ID1234567'
        responses.add(responses.POST, url, json=expected_response)

    @responses.activate
    def test_run_task(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(
            self.project_config, self.task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 4)
Beispiel #30
0
class TestRunApexTests(unittest.TestCase):
    def setUp(self):
        self.api_version = 38.0
        self.global_config = BaseGlobalConfig(
            {"project": {
                "api_version": self.api_version
            }})
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
        }
        self.project_config = BaseProjectConfig(self.global_config,
                                                config={"noyaml": True})
        self.project_config.config["project"] = {
            "package": {
                "api_version": self.api_version
            }
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version)

    def _mock_apex_class_query(self):
        url = (self.base_tooling_url + "query/?q=SELECT+Id%2C+Name+" +
               "FROM+ApexClass+WHERE+NamespacePrefix+%3D+null" +
               "+AND+%28Name+LIKE+%27%25_TEST%27%29")
        expected_response = {
            "done": True,
            "records": [{
                "Id": 1,
                "Name": "TestClass_TEST"
            }],
            "totalSize": 1,
        }
        responses.add(responses.GET,
                      url,
                      match_querystring=True,
                      json=expected_response)

    def _get_mock_test_query_results(self, outcome, message):
        return {
            "done":
            True,
            "records": [{
                "attributes": {
                    "type":
                    "ApexTestResult",
                    "url":
                    "/services/data/v40.0/tooling/sobjects/ApexTestResult/07M41000009gbT3EAI",
                },
                "ApexClass": {
                    "attributes": {
                        "type":
                        "ApexClass",
                        "url":
                        "/services/data/v40.0/tooling/sobjects/ApexClass/01p4100000Fu4Z0AAJ",
                    },
                    "Name": "EP_TaskDependency_TEST",
                },
                "ApexClassId": 1,
                "ApexLogId": 1,
                "TestTimestamp": "2017-07-18T20:36:04.000+0000",
                "Id": "07M41000009gbT3EAI",
                "Message": message,
                "MethodName": "TestMethod",
                "Outcome": outcome,
                "QueueItem": {
                    "attributes": {
                        "type":
                        "ApexTestQueueItem",
                        "url":
                        "/services/data/v40.0/tooling/sobjects/ApexTestQueueItem/70941000000q7VsAAI",
                    },
                    "Status": "Completed",
                    "ExtendedStatus": "(4/4)",
                },
                "RunTime": 1707,
                "StackTrace": "1. ParentFunction\n2. ChildFunction",
                "Status": "Completed",
                "Name": "TestClass_TEST",
                "ApexTestResults": {
                    "size":
                    1,
                    "totalSize":
                    1,
                    "done":
                    True,
                    "queryLocator":
                    None,
                    "entityTypeName":
                    "ApexTestResultLimits",
                    "records": [{
                        "attributes": {
                            "type":
                            "ApexTestResultLimits",
                            "url":
                            "/services/data/v40.0/tooling/sobjects/ApexTestResultLimits/05n41000002Y7OQAA0",
                        },
                        "Id": "05n41000002Y7OQAA0",
                        "Callouts": 0,
                        "AsyncCalls": 0,
                        "DmlRows": 5,
                        "Email": 0,
                        "LimitContext": "SYNC",
                        "LimitExceptions": None,
                        "MobilePush": 0,
                        "QueryRows": 20,
                        "Sosl": 0,
                        "Cpu": 471,
                        "Dml": 4,
                        "Soql": 5,
                    }],
                },
            }],
        }

    def _get_mock_test_query_url(self, job_id):
        return (
            self.base_tooling_url +
            "query/?q=%0ASELECT+Id%2CApexClassId%2CTestTimestamp%2C%0A+++++++Message%2CMethodName%2COutcome%2C%0A+++++++RunTime%2CStackTrace%2C%0A+++++++%28SELECT+%0A++++++++++Id%2CCallouts%2CAsyncCalls%2CDmlRows%2CEmail%2C%0A++++++++++LimitContext%2CLimitExceptions%2CMobilePush%2C%0A++++++++++QueryRows%2CSosl%2CCpu%2CDml%2CSoql+%0A++++++++FROM+ApexTestResults%29+%0AFROM+ApexTestResult+%0AWHERE+AsyncApexJobId%3D%27{}%27%0A"
            .format(job_id))

    def _mock_get_test_results(self,
                               outcome="Pass",
                               message="Test Passed",
                               job_id="JOB_ID1234567"):
        url = self._get_mock_test_query_url(job_id)

        expected_response = self._get_mock_test_query_results(outcome, message)
        responses.add(responses.GET,
                      url,
                      match_querystring=True,
                      json=expected_response)

    def _mock_tests_complete(self, job_id="JOB_ID1234567"):
        url = (self.base_tooling_url + "query/?q=SELECT+Id%2C+Status%2C+" +
               "ApexClassId+FROM+ApexTestQueueItem+WHERE+ParentJobId+%3D+%27" +
               "{}%27".format(job_id))
        expected_response = {
            "done": True,
            "records": [{
                "Status": "Completed"
            }]
        }
        responses.add(responses.GET,
                      url,
                      match_querystring=True,
                      json=expected_response)

    def _mock_run_tests(self, success=True, body="JOB_ID1234567"):
        url = self.base_tooling_url + "runTestsAsynchronous"
        if success:
            responses.add(responses.POST, url, json=body)
        else:
            responses.add(responses.POST,
                          url,
                          status=http.client.SERVICE_UNAVAILABLE)

    @responses.activate
    def test_run_task(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results()
        task = RunApexTests(self.project_config, self.task_config,
                            self.org_config)
        task()
        self.assertEqual(len(responses.calls), 4)

    @responses.activate
    def test_run_task__server_error(self):
        self._mock_apex_class_query()
        self._mock_run_tests(success=False)
        task = RunApexTests(self.project_config, self.task_config,
                            self.org_config)
        with self.assertRaises(SalesforceGeneralError):
            task()

    @responses.activate
    def test_run_task__failed(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_tests_complete()
        self._mock_get_test_results("Fail")
        task = RunApexTests(self.project_config, self.task_config,
                            self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    @responses.activate
    def test_run_task__retry_tests(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_run_tests(body="JOBID_9999")
        self._mock_tests_complete()
        self._mock_tests_complete(job_id="JOBID_9999")
        self._mock_get_test_results("Fail", "UNABLE_TO_LOCK_ROW")
        self._mock_get_test_results(job_id="JOBID_9999")

        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task()
        self.assertEqual(len(responses.calls), 7)

    @responses.activate
    def test_run_task__retry_tests_fails(self):
        self._mock_apex_class_query()
        self._mock_run_tests()
        self._mock_run_tests(body="JOBID_9999")
        self._mock_tests_complete()
        self._mock_tests_complete(job_id="JOBID_9999")
        self._mock_get_test_results("Fail", "UNABLE_TO_LOCK_ROW")
        self._mock_get_test_results("Fail",
                                    "DUPLICATES_DETECTED",
                                    job_id="JOBID_9999")

        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(ApexTestException):
            task()

    def test_is_retriable_failure(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output":
            "results_junit.xml",
            "poll_interval":
            1,
            "test_name_match":
            "%_TEST",
            "retry_failures": [
                "UNABLE_TO_LOCK_ROW",
                "unable to obtain exclusive access to this record",
            ],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task._init_options(task_config.config["options"])

        self.assertTrue(
            task._is_retriable_failure({
                "Message": "UNABLE_TO_LOCK_ROW",
                "StackTrace": "test",
                "Outcome": "Fail",
            }))
        self.assertTrue(
            task._is_retriable_failure({
                "Message": "TEST",
                "StackTrace":
                "unable to obtain exclusive access to this record",
                "Outcome": "Fail",
            }))
        self.assertFalse(
            task._is_retriable_failure({
                "Message": "DUPLICATES_DETECTED",
                "StackTrace": "test",
                "Outcome": "Fail",
            }))
        self.assertFalse(
            task._is_retriable_failure({
                "Message": "UNABLE_TO_LOCK_ROW",
                "StackTrace": "test",
                "Outcome": "Pass",
            }))

    def test_init_options__regexes(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["UNABLE_TO_LOCK_ROW"],
        }
        task = RunApexTests(self.project_config, task_config, self.org_config)
        task._init_options(task_config.config["options"])

        self.assertIsNotNone(task.options["retry_failures"][0].search(
            "UNABLE_TO_LOCK_ROW: test failed"))

    def test_init_options__bad_regexes(self):
        task_config = TaskConfig()
        task_config.config["options"] = {
            "junit_output": "results_junit.xml",
            "poll_interval": 1,
            "test_name_match": "%_TEST",
            "retry_failures": ["("],
        }
        with self.assertRaises(TaskOptionsError):
            task = RunApexTests(self.project_config, task_config,
                                self.org_config)
            task._init_options(task_config.config["options"])

    def test_get_namespace_filter__managed(self):
        task_config = TaskConfig(
            {"options": {
                "managed": True,
                "namespace": "testns"
            }})
        task = RunApexTests(self.project_config, task_config, self.org_config)
        namespace = task._get_namespace_filter()
        self.assertEqual("'testns'", namespace)

    def test_get_namespace_filter__managed_no_namespace(self):
        task_config = TaskConfig({"options": {"managed": True}})
        task = RunApexTests(self.project_config, task_config, self.org_config)
        with self.assertRaises(TaskOptionsError):
            task._get_namespace_filter()

    def test_get_test_class_query__exclude(self):
        task_config = TaskConfig({
            "options": {
                "test_name_match": "%_TEST",
                "test_name_exclude": "EXCL"
            }
        })
        task = RunApexTests(self.project_config, task_config, self.org_config)
        query = task._get_test_class_query()
        self.assertEqual(
            "SELECT Id, Name FROM ApexClass WHERE NamespacePrefix = null "
            "AND (Name LIKE '%_TEST') AND (NOT Name LIKE 'EXCL')",
            query,
        )

    def test_run_task__no_tests(self):
        task = RunApexTests(self.project_config, self.task_config,
                            self.org_config)
        task._get_test_classes = MagicMock(return_value={"totalSize": 0})
        task()
        self.assertIsNone(task.result)
class TestSFDXBaseTask(MockLoggerMixin, unittest.TestCase):
    """ Tests for the Base Task type """

    def setUp(self):
        self.global_config = BaseGlobalConfig()
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )
        self.task_config = TaskConfig()

        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)

        self._task_log_handler.reset()
        self.task_log = self._task_log_handler.messages

    def test_base_task(self):
        """ The command is prefixed w/ sfdx """

        self.task_config.config["options"] = {"command": "force:org", "extra": "--help"}
        task = SFDXBaseTask(self.project_config, self.task_config)

        try:
            task()
        except CommandException:
            pass

        self.assertEqual("sfdx force:org --help", task.options["command"])

    @patch(
        "cumulusci.tasks.sfdx.SFDXOrgTask._update_credentials",
        MagicMock(return_value=None),
    )
    @patch("cumulusci.tasks.command.Command._run_task", MagicMock(return_value=None))
    def test_keychain_org_creds(self):
        """ Keychain org creds are passed by env var """

        self.task_config.config["options"] = {"command": "force:org --help"}
        access_token = "00d123"
        org_config = OrgConfig(
            {
                "access_token": access_token,
                "instance_url": "https://test.salesforce.com",
            },
            "test",
        )

        task = SFDXOrgTask(self.project_config, self.task_config, org_config)

        try:
            task()
        except CommandException:
            pass

        self.assertIn("SFDX_INSTANCE_URL", task._get_env())
        self.assertIn("SFDX_USERNAME", task._get_env())
        self.assertIn(access_token, task._get_env()["SFDX_USERNAME"])

    def test_scratch_org_username(self):
        """ Scratch Org credentials are passed by -u flag """
        self.task_config.config["options"] = {"command": "force:org --help"}
        org_config = ScratchOrgConfig({"username": "******"}, "test")

        task = SFDXOrgTask(self.project_config, self.task_config, org_config)
        self.assertIn("-u [email protected]", task.options["command"])
Beispiel #32
0
class TestRunBatchApex(unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.global_config = BaseGlobalConfig(
            {"project": {
                "api_version": self.api_version
            }})
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "class_name": "ADDR_Seasonal_BATCH",
            "poll_interval": 1,
        }
        self.project_config = BaseProjectConfig(self.global_config,
                                                config={"noyaml": True})
        self.project_config.config["project"] = {
            "package": {
                "api_version": self.api_version
            }
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version)

    def _get_query_resp(self):
        return {
            "size":
            1,
            "totalSize":
            1,
            "done":
            True,
            "queryLocator":
            None,
            "entityTypeName":
            "AsyncApexJob",
            "records": [{
                "attributes": {
                    "type":
                    "AsyncApexJob",
                    "url":
                    "/services/data/v43.0/tooling/sobjects/AsyncApexJob/707L0000014nnPHIAY",
                },
                "Id": "707L0000014nnPHIAY",
                "ApexClass": {
                    "attributes": {
                        "type":
                        "ApexClass",
                        "url":
                        "/services/data/v43.0/tooling/sobjects/ApexClass/01pL000000109ndIAA",
                    },
                    "Name": "ADDR_Seasonal_BATCH",
                },
                "Status": "Completed",
                "ExtendedStatus": None,
                "TotalJobItems": 1,
                "JobItemsProcessed": 1,
                "NumberOfErrors": 0,
                "CreatedDate": "2018-08-07T16:00:56.000+0000",
                "CompletedDate": "2018-08-07T16:01:57.000+0000",
            }],
        }

    def _get_url_and_task(self):
        task = BatchApexWait(self.project_config, self.task_config,
                             self.org_config)
        url = (
            self.base_tooling_url +
            "query/?q=SELECT+Id%2C+ApexClass.Name%2C+Status%2C+ExtendedStatus%2C+TotalJobItems%2C+JobItemsProcessed%2C+NumberOfErrors%2C+CreatedDate%2C+CompletedDate+FROM+AsyncApexJob+WHERE+JobType%3D%27BatchApex%27+AND+ApexClass.Name%3D%27ADDR_Seasonal_BATCH%27+ORDER+BY+CreatedDate+DESC+LIMIT+1"
        )
        return task, url

    @responses.activate
    def test_run_batch_apex_status_fail(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        response["records"][0]["NumberOfErrors"] = 1
        response["records"][0]["ExtendedStatus"] = "Bad Status"
        responses.add(responses.GET, url, json=response)
        with self.assertRaises(SalesforceException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err.args[0], "Bad Status")

    @responses.activate
    def test_run_batch_apex_status_ok(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        responses.add(responses.GET, url, json=response)
        task()

    @responses.activate
    def test_run_batch_apex_calc_delta(self):
        task, url = self._get_url_and_task()
        response = self._get_query_resp()
        responses.add(responses.GET, url, json=response)
        task()
        self.assertEqual(task.delta, 61)
class TestAnonymousApexTask(unittest.TestCase):
    def setUp(self):
        self.api_version = 42.0
        self.global_config = BaseGlobalConfig(
            {"project": {"api_version": self.api_version}}
        )
        self.tmpdir = tempfile.mkdtemp(dir=".")
        apex_path = os.path.join(self.tmpdir, "test.apex")
        with open(apex_path, "w") as f:
            f.write('System.debug("from file")')
        self.task_config = TaskConfig()
        self.task_config.config["options"] = {
            "path": apex_path,
            "apex": 'system.debug("Hello World!")',
            "namespaced": True,
        }
        self.project_config = BaseProjectConfig(
            self.global_config, config={"noyaml": True}
        )
        self.project_config.config = {
            "project": {
                "package": {"namespace": "abc", "api_version": self.api_version}
            }
        }
        keychain = BaseProjectKeychain(self.project_config, "")
        self.project_config.set_keychain(keychain)
        self.org_config = OrgConfig(
            {
                "id": "foo/1",
                "instance_url": "https://example.com",
                "access_token": "abc123",
            },
            "test",
        )
        self.base_tooling_url = "{}/services/data/v{}/tooling/".format(
            self.org_config.instance_url, self.api_version
        )

    def tearDown(self):
        shutil.rmtree(self.tmpdir)

    def _get_url_and_task(self):
        task = AnonymousApexTask(self.project_config, self.task_config, self.org_config)
        url = self.base_tooling_url + "executeAnonymous"
        return task, url

    def test_validate_options(self):
        task_config = TaskConfig({})
        with self.assertRaises(TaskOptionsError):
            AnonymousApexTask(self.project_config, task_config, self.org_config)

    def test_run_from_path_outside_repo(self):
        task_config = TaskConfig({"options": {"path": "/"}})
        task = AnonymousApexTask(self.project_config, task_config, self.org_config)
        with self.assertRaises(TaskOptionsError):
            task()

    def test_run_path_not_found(self):
        task_config = TaskConfig({"options": {"path": "bogus"}})
        task = AnonymousApexTask(self.project_config, task_config, self.org_config)
        with self.assertRaises(TaskOptionsError):
            task()

    def test_prepare_apex(self):
        task = AnonymousApexTask(self.project_config, self.task_config, self.org_config)
        before = "String %%%NAMESPACE%%%str = 'foo';"
        expected = "String abc__str = 'foo';"
        self.assertEqual(expected, task._prepare_apex(before))

    @responses.activate
    def test_run_anonymous_apex_success(self):
        task, url = self._get_url_and_task()
        resp = {"compiled": True, "success": True}
        responses.add(responses.GET, url, status=200, json=resp)
        task()

    @responses.activate
    def test_run_string_only(self):
        task_config = TaskConfig({"options": {"apex": 'System.debug("test");'}})
        task = AnonymousApexTask(self.project_config, task_config, self.org_config)
        url = self.base_tooling_url + "executeAnonymous"
        responses.add(
            responses.GET, url, status=200, json={"compiled": True, "success": True}
        )
        task()

    @responses.activate
    def test_run_anonymous_apex_status_fail(self):
        task, url = self._get_url_and_task()
        responses.add(responses.GET, url, status=418, body="I'm a teapot")
        with self.assertRaises(SalesforceGeneralError) as cm:
            task()
        err = cm.exception
        self.assertEqual(str(err), "Error Code 418. Response content: I'm a teapot")
        self.assertTrue(err.url.startswith(url))
        self.assertEqual(err.status, 418)
        self.assertEqual(err.content, "I'm a teapot")

    @responses.activate
    def test_run_anonymous_apex_compile_except(self):
        task, url = self._get_url_and_task()
        problem = "Unexpected token '('."
        resp = {
            "compiled": False,
            "compileProblem": problem,
            "success": False,
            "line": 1,
            "column": 13,
            "exceptionMessage": "",
            "exceptionStackTrace": "",
            "logs": "",
        }
        responses.add(responses.GET, url, status=200, json=resp)
        with self.assertRaises(ApexCompilationException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err.args[0], 1)
        self.assertEqual(err.args[1], problem)

    @responses.activate
    def test_run_anonymous_apex_except(self):
        task, url = self._get_url_and_task()
        problem = "Unexpected token '('."
        trace = "Line 0, Column 99"
        resp = {
            "compiled": True,
            "compileProblem": "",
            "success": False,
            "line": 1,
            "column": 13,
            "exceptionMessage": problem,
            "exceptionStackTrace": trace,
            "logs": "",
        }
        responses.add(responses.GET, url, status=200, json=resp)
        with self.assertRaises(ApexException) as cm:
            task()
        err = cm.exception
        self.assertEqual(err.args[0], problem)
        self.assertEqual(err.args[1], trace)