Repository: SFDO-Tooling/CumulusCI
Pull request #3669 (2023-10-05 12:13:55)

Feature/listmetadatatypes


Patch:
diff --git a/cumulusci/cumulusci.yml b/cumulusci/cumulusci.yml
index 3714013e8..6a3bf9842 100644
--- a/cumulusci/cumulusci.yml
+++ b/cumulusci/cumulusci.yml
@@ -477,6 +477,10 @@ tasks:
         options:
             path: packaged
         group: Salesforce Metadata
+    describe_metadatatypes:
+        class_path: cumulusci.tasks.salesforce.DescribeMetadataTypes
+        description: Retrieves the metadata types supported by the org based on the api version
+        group: Salesforce Metadata
     retrieve_src:
         description: Retrieves the packaged metadata into the src directory
         class_path: cumulusci.tasks.salesforce.RetrievePackaged
diff --git a/cumulusci/salesforce_api/metadata.py b/cumulusci/salesforce_api/metadata.py
index 9c13e5665..8ef8c013b 100644
--- a/cumulusci/salesforce_api/metadata.py
+++ b/cumulusci/salesforce_api/metadata.py
@@ -678,3 +678,44 @@ class ApiNewProfile(BaseMetadataApiCall):
             )
         # Unknown response
         raise MetadataApiError(f"Unexpected response: {response.text}", response)
+
+
+class ApiListMetadataTypes(BaseMetadataApiCall):
+    check_interval = 1
+    soap_envelope_start = soap_envelopes.DESCRIBE_METADATA
+    soap_action_start = "describemetadatatypes"
+
+    def __init__(self, task, api_version=None):
+        super(ApiListMetadataTypes, self).__init__(task)
+        self.metadata_types = []
+        self.api_version = (
+            api_version
+            if api_version
+            else task.project_config.project__package__api_version
+        )
+
+    def _build_envelope_start(self):
+
+        return self.soap_envelope_start.format(
+            api_version=self.api_version,
+        )
+
+    def _process_response(self, response):
+        self.metadata_types = []
+        metaobjects = parseString(response.content).getElementsByTagName(
+            "metadataObjects"
+        )
+
+        for metadataobject in metaobjects:
+            self.metadata_types.append(
+                self._get_element_value(metadataobject, "xmlName")
+            )
+            child_elements = metadataobject.getElementsByTagName("childXmlNames")
+            child_xml_names = [
+                element.firstChild.nodeValue for element in child_elements
+            ]
+            self.metadata_types += child_xml_names
+        self.metadata_types.sort()
+        self.status = "Done"
+        self.task.logger.info(self.status)
+        return self.metadata_types
diff --git a/cumulusci/salesforce_api/soap_envelopes.py b/cumulusci/salesforce_api/soap_envelopes.py
index a68562834..ae6e8ec81 100644
--- a/cumulusci/salesforce_api/soap_envelopes.py
+++ b/cumulusci/salesforce_api/soap_envelopes.py
@@ -161,3 +161,17 @@ CREATE_PROFILE = """<?xml version="1.0" encoding="utf-8"?>
     </urn:create>
   </soapenv:Body>
 </soapenv:Envelope>"""
+
+DESCRIBE_METADATA = """<?xml version="1.0" encoding="utf-8"?>
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://soap.sforce.com/2006/04/metadata">
+  <soapenv:Header>
+    <tns:SessionHeader>
+      <tns:sessionId>###SESSION_ID###</tns:sessionId>
+    </tns:SessionHeader>
+  </soapenv:Header>
+  <soapenv:Body>
+    <tns:describeMetadata>
+      <asOfVersion>{api_version}</asOfVersion>
+    </tns:describeMetadata>
+  </soapenv:Body>
+</soapenv:Envelope>"""
diff --git a/cumulusci/tasks/salesforce/DescribeMetadataTypes.py b/cumulusci/tasks/salesforce/DescribeMetadataTypes.py
new file mode 100644
index 000000000..49c605c37
--- /dev/null
+++ b/cumulusci/tasks/salesforce/DescribeMetadataTypes.py
@@ -0,0 +1,26 @@
+from cumulusci.salesforce_api.metadata import ApiListMetadataTypes
+from cumulusci.tasks.salesforce import BaseRetrieveMetadata
+
+
+class DescribeMetadataTypes(BaseRetrieveMetadata):
+    api_class = ApiListMetadataTypes
+    task_options = {
+        "api_version": {
+            "description": "Override the API version used to list metadatatypes"
+        },
+    }
+
+    def _init_options(self, kwargs):
+        super(DescribeMetadataTypes, self)._init_options(kwargs)
+        if "api_version" not in self.options:
+            self.options[
+                "api_version"
+            ] = self.project_config.project__package__api_version
+
+    def _get_api(self):
+        return self.api_class(self, self.options.get("api_version"))
+
+    def _run_task(self):
+        api_object = self._get_api()
+        metadata_list = api_object()
+        self.logger.info("Metadata Types supported by org:\n" + str(metadata_list))
diff --git a/cumulusci/tasks/salesforce/__init__.py b/cumulusci/tasks/salesforce/__init__.py
index f81c25d55..670c55660 100644
--- a/cumulusci/tasks/salesforce/__init__.py
+++ b/cumulusci/tasks/salesforce/__init__.py
@@ -30,6 +30,7 @@ ORIGINS = {
     "PublishCommunity": "cumulusci.tasks.salesforce.PublishCommunity",
     "RetrievePackaged": "cumulusci.tasks.salesforce.RetrievePackaged",
     "RetrieveReportsAndDashboards": "cumulusci.tasks.salesforce.RetrieveReportsAndDashboards",
+    "DescribeMetadataTypes": "cumulusci.tasks.salesforce.DescribeMetadataTypes",
     "RetrieveUnpackaged": "cumulusci.tasks.salesforce.RetrieveUnpackaged",
     "SOQLQuery": "cumulusci.tasks.salesforce.SOQLQuery",
     "SetTDTMHandlerStatus": "cumulusci.tasks.salesforce.trigger_handlers",


Test patch:
diff --git a/cumulusci/salesforce_api/tests/metadata_test_strings.py b/cumulusci/salesforce_api/tests/metadata_test_strings.py
index 8ab145a75..977e76a29 100644
--- a/cumulusci/salesforce_api/tests/metadata_test_strings.py
+++ b/cumulusci/salesforce_api/tests/metadata_test_strings.py
@@ -19,3 +19,6 @@ retrieve_result = '<?xml version="1.0" encoding="utf-8"?>\n<testing>\n  <zipFile
 status_envelope = '<?xml version="1.0" encoding="utf-8"?>\n<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">\n  <soap:Header>\n    <SessionHeader xmlns="http://soap.sforce.com/2006/04/metadata">\n      <sessionId>###SESSION_ID###</sessionId>\n    </SessionHeader>\n  </soap:Header>\n  <soap:Body>\n    <checkStatus xmlns="http://soap.sforce.com/2006/04/metadata">\n      <asyncProcessId>{process_id}</asyncProcessId>\n    </checkStatus>\n  </soap:Body>\n</soap:Envelope>'
 
 deploy_status_envelope = '<?xml version="1.0" encoding="utf-8"?>\n<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">\n  <soap:Header>\n    <SessionHeader xmlns="http://soap.sforce.com/2006/04/metadata">\n      <sessionId>###SESSION_ID###</sessionId>\n    </SessionHeader>\n  </soap:Header>\n  <soap:Body>\n    <checkDeployStatus xmlns="http://soap.sforce.com/2006/04/metadata">\n      <asyncProcessId>{process_id}</asyncProcessId>\n      <includeDetails>true</includeDetails>\n    </checkDeployStatus>\n  </soap:Body>\n</soap:Envelope>'
+
+list_metadata_types_envelope = '<?xml version="1.0" encoding="utf-8"?>\n<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://soap.sforce.com/2006/04/metadata">\n  <soapenv:Header>\n    <tns:SessionHeader>\n      <tns:sessionId>###SESSION_ID###</tns:sessionId>\n    </tns:SessionHeader>\n  </soapenv:Header>\n  <soapenv:Body>\n    <tns:describeMetadata>\n      <asOfVersion>{api_version}</asOfVersion>\n    </tns:describeMetadata>\n  </soapenv:Body>\n</soapenv:Envelope>'
+list_metadata_types_result = '<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://soap.sforce.com/2006/04/metadata"><soapenv:Body><describeMetadataResponse><result><metadataObjects><childXmlNames>WorkflowFieldUpdate</childXmlNames><directoryName>workflows</directoryName><inFolder>false</inFolder><metaFile>false</metaFile><suffix>workflow</suffix><xmlName>Workflow</xmlName></metadataObjects><organizationNamespace></organizationNamespace><partialSaveAllowed>true</partialSaveAllowed><testRequired>false</testRequired></result></describeMetadataResponse></soapenv:Body></soapenv:Envelope>'
diff --git a/cumulusci/salesforce_api/tests/test_metadata.py b/cumulusci/salesforce_api/tests/test_metadata.py
index 7e578a34b..9df439f67 100644
--- a/cumulusci/salesforce_api/tests/test_metadata.py
+++ b/cumulusci/salesforce_api/tests/test_metadata.py
@@ -19,6 +19,7 @@ from cumulusci.salesforce_api.exceptions import (
 from cumulusci.salesforce_api.metadata import (
     ApiDeploy,
     ApiListMetadata,
+    ApiListMetadataTypes,
     ApiRetrieveInstalledPackages,
     ApiRetrievePackaged,
     ApiRetrieveUnpackaged,
@@ -36,6 +37,8 @@ from cumulusci.salesforce_api.tests.metadata_test_strings import (
     list_metadata_result,
     list_metadata_result_bad_val,
     list_metadata_start_envelope,
+    list_metadata_types_envelope,
+    list_metadata_types_result,
     result_envelope,
     retrieve_packaged_start_envelope,
     retrieve_result,
@@ -843,6 +846,42 @@ class TestApiListMetadata(TestBaseTestMetadataApi):
             api()
 
 
+class TestApiListMetadataTypes(TestBaseTestMetadataApi):
+    api_class = ApiListMetadataTypes
+    envelope_start = list_metadata_types_envelope
+
+    def setup_method(self):
+        super().setup_method()
+        self.metadata_types = None
+        self.api_version = self.project_config.project__package__api_version
+
+    def _response_call_success_result(self, response_result):
+        return list_metadata_types_result
+
+    def _expected_call_success_result(self, response_result=None):
+        metadata_types = ["Workflow", "WorkflowFieldUpdate"]
+        return metadata_types
+
+    def _create_instance(self, task, api_version=None):
+        return super()._create_instance(task, api_version)
+
+    @responses.activate
+    def test_call_success(self):
+        org_config = {
+            "instance_url": "https://na12.salesforce.com",
+            "id": "https://login.salesforce.com/id/00D000000000000ABC/005000000000000ABC",
+            "access_token": "0123456789",
+        }
+        task = self._create_task(org_config=org_config)
+        api = self._create_instance(task)
+        if not self.api_class.soap_envelope_start:
+            api.soap_envelope_start = "{api_version}"
+        self._mock_call_mdapi(api, list_metadata_types_result)
+
+        metadata_types = api()
+        assert metadata_types == self._expected_call_success_result()
+
+
 class TestApiRetrieveUnpackaged(TestBaseTestMetadataApi):
     maxDiff = None
     api_class = ApiRetrieveUnpackaged
diff --git a/cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py b/cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py
new file mode 100644
index 000000000..9715786b3
--- /dev/null
+++ b/cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py
@@ -0,0 +1,18 @@
+from unittest import mock
+
+from cumulusci.tasks.salesforce import DescribeMetadataTypes
+
+from .util import create_task
+
+
+class TestRetrieveMetadataTypes:
+    def test_run_task(self):
+        task = create_task(DescribeMetadataTypes)
+        task._get_api = mock.Mock()
+        task()
+        task._get_api.assert_called_once()
+
+    def test_run_task_with_apiversion(self):
+        task = create_task(DescribeMetadataTypes, {"api_version": 8.0})
+        assert task.options.get("api_version") == 8.0
+        task._get_api()