diff --git a/cumulusci/tasks/metadata/package.py b/cumulusci/tasks/metadata/package.py index a1e546ae3b..d7689d8eb0 100644 --- a/cumulusci/tasks/metadata/package.py +++ b/cumulusci/tasks/metadata/package.py @@ -45,15 +45,19 @@ def process_common_components(response_messages: List, components: Dict): """Compare compoents in the api responce object with list of components and return common common components""" if not response_messages or not components: return components - for message in response_messages: + message_list = message.firstChild.nextSibling.firstChild.nodeValue.split("'") if len(message_list) > 1: component_type = message_list[1] message_txt = message_list[2] - if "is not available in this organization" in message_txt: del components[component_type] + elif "is unknown" in message_txt: + component_type = message_list[0].split(" ") + components[component_type[0]].remove(message_list[1]) + if len(components[component_type]) == 0: + del components[component_type] else: component_name = message_list[3] if component_name in components[component_type]: diff --git a/cumulusci/tasks/salesforce/check_components.py b/cumulusci/tasks/salesforce/check_components.py index 9ec3aa6922..c59f3c7583 100644 --- a/cumulusci/tasks/salesforce/check_components.py +++ b/cumulusci/tasks/salesforce/check_components.py @@ -3,6 +3,7 @@ import shutil import tempfile from collections import defaultdict +from itertools import chain from xml.etree.ElementTree import ParseError from defusedxml.minidom import parseString @@ -60,6 +61,7 @@ def _run_task(self): for component_type, component_names in components.items(): self.logger.debug(f"{component_type}: {', '.join(component_names)}") # check common components + components.pop("Settings", None) existing_components = process_common_components( api_retrieve_unpackaged_response, components ) @@ -102,12 +104,34 @@ def get_repo_existing_components(self, plan_or_flow_name, paths=""): # Temp dir to copy all deploy paths from task options temp_dir = tempfile.mkdtemp() self.logger.info(f"Temporary deploy directory created: {temp_dir}") - + mdapi_components = {} + mdapi_response_messages = [] for path in self.deploy_paths: full_path = os.path.join(self.project_config.repo_root, path) if not os.path.exists(full_path): self.logger.info(f"Skipping path: '{path}' - path doesn't exist") continue + elif "package.xml" in os.listdir(full_path): + package_xml_path = os.path.join(full_path, "package.xml") + source_xml_tree = metadata_tree.parse(package_xml_path) + components = metadata_tree.parse_package_xml_types( + "name", source_xml_tree + ) + response_messages = self._get_api_object_responce( + package_xml_path, source_xml_tree.version.text + ) + merged = {} + for key in set(components).union(mdapi_components): + merged[key] = list( + set( + chain( + components.get(key, []), mdapi_components.get(key, []) + ) + ) + ) + mdapi_components = merged + mdapi_response_messages.extend(response_messages) + continue self._copy_to_tempdir(path, temp_dir) ( @@ -117,6 +141,21 @@ def get_repo_existing_components(self, plan_or_flow_name, paths=""): # remove temp dir shutil.rmtree(temp_dir) + merged = {} + if components: + for key in set(components).union(mdapi_components): + merged[key] = list( + set(chain(components.get(key, []), mdapi_components.get(key, []))) + ) + components = merged + else: + components = mdapi_components + + if api_retrieve_unpackaged_response: + api_retrieve_unpackaged_response.extend(mdapi_response_messages) + else: + api_retrieve_unpackaged_response = mdapi_response_messages + return [components, api_retrieve_unpackaged_response] def _copy_to_tempdir(self, src_dir, temp_dir): diff --git a/cumulusci/tasks/salesforce/tests/test_check_components.py b/cumulusci/tasks/salesforce/tests/test_check_components.py index 549275be99..b7f963cab0 100644 --- a/cumulusci/tasks/salesforce/tests/test_check_components.py +++ b/cumulusci/tasks/salesforce/tests/test_check_components.py @@ -1,4 +1,4 @@ -from unittest.mock import ANY, MagicMock, mock_open, patch +from unittest.mock import ANY, MagicMock, Mock, mock_open, patch import pytest @@ -11,6 +11,210 @@ class TestCheckComponents: + @patch("os.path.exists", return_value=True) + @patch("os.remove") + @patch("os.path.isdir", return_value=True) + @patch("os.listdir", return_value=["package.xml"]) + @patch("os.path.join", side_effect=lambda *args: "/".join(args)) + @patch("cumulusci.core.sfdx.convert_sfdx_source") + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._is_plan", + return_value=False, + ) + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._freeze_steps", + return_value=[], + ) + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._collect_components_from_paths", + return_value=[{"Type1": ["Comp1"]}, []], + ) + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._get_api_object_responce", + return_value=[], + ) + @patch( + "builtins.open", + new_callable=mock_open, + read_data=""" + + + Delivery + ApexClass + + + Delivery__c + CustomObject + + 58.0 + + """, + ) + @patch("cumulusci.utils.xml.metadata_tree.parse") + @patch( + "cumulusci.utils.xml.metadata_tree.parse_package_xml_types", + return_value={"Type2": ["Comp2"]}, + ) + def test_get_repo_existing_components( + self, + mock_metadata_parse, + mock_open_file, + mock_convert_sfdx_source, + mock_path_join, + mock_listdir, + mock_isdir, + mock_remove, + mock_path_exists, + mock_is_plan, + mock_freeze_steps, + mock_get_api_object, + mock_parse, + mock_collect_components, + ): + org_config = Mock(scratch=True, config={}) + org_config.username = "test_user" + org_config.org_id = "test_org_id" + self.org_config = Mock(return_value=("test", org_config)) + project_config = create_project_config() + flow_config = { + "test": { + "steps": { + 1: { + "flow": "test2", + } + } + }, + "test2": { + "steps": { + 1: { + "task": "deploy", + "options": {"path": "force-app/main/default"}, + } + } + }, + } + plan_config = { + "title": "Test Install", + "slug": "install", + "tier": "primary", + "steps": {1: {"flow": "test"}}, + } + project_config.config["plans"] = { + "Test Install": plan_config, + } + project_config.config["flows"] = flow_config + + task = create_task(CheckComponents, {"name": "test2"}) + task.deploy_paths = ["test"] + + (components, response_messages) = task.get_repo_existing_components("test2") + assert "Type1" in components + assert "Type2" in components + assert "Comp1" in components["Type1"] + assert "Comp2" in components["Type2"] + + @patch("os.path.exists", return_value=True) + @patch("os.remove") + @patch("os.path.isdir", return_value=True) + @patch("os.listdir", return_value=["package.xml"]) + @patch("os.path.join", side_effect=lambda *args: "/".join(args)) + @patch("cumulusci.core.sfdx.convert_sfdx_source") + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._is_plan", + return_value=False, + ) + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._freeze_steps", + return_value=[], + ) + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._collect_components_from_paths", + return_value=[{"Type1": ["Comp1"]}, []], + ) + @patch( + "cumulusci.tasks.salesforce.check_components.CheckComponents._get_api_object_responce", + return_value=[], + ) + @patch( + "builtins.open", + new_callable=mock_open, + read_data=""" + + + Delivery + ApexClass + + + Delivery__c + CustomObject + + 58.0 + + """, + ) + @patch("cumulusci.utils.xml.metadata_tree.parse") + @patch( + "cumulusci.utils.xml.metadata_tree.parse_package_xml_types", + return_value={"Type2": ["Comp2"]}, + ) + def test_get_repo_existing_components_paths_paramter( + self, + mock_metadata_parse, + mock_open_file, + mock_convert_sfdx_source, + mock_path_join, + mock_listdir, + mock_isdir, + mock_remove, + mock_path_exists, + mock_is_plan, + mock_freeze_steps, + mock_get_api_object, + mock_parse, + mock_collect_components, + ): + org_config = Mock(scratch=True, config={}) + org_config.username = "test_user" + org_config.org_id = "test_org_id" + self.org_config = Mock(return_value=("test", org_config)) + project_config = create_project_config() + flow_config = { + "test": { + "steps": { + 1: { + "flow": "test2", + } + } + }, + "test2": { + "steps": { + 1: { + "task": "deploy", + "options": {"path": "force-app/main/default"}, + } + } + }, + } + plan_config = { + "title": "Test Install", + "slug": "install", + "tier": "primary", + "steps": {1: {"flow": "test"}}, + } + project_config.config["plans"] = { + "Test Install": plan_config, + } + project_config.config["flows"] = flow_config + + task = create_task(CheckComponents, {"name": "test2"}) + task.deploy_paths = ["test"] + + (components, response_messages) = task.get_repo_existing_components("", "src") + assert "Type1" in components + assert "Type2" in components + assert "Comp1" in components["Type1"] + assert "Comp2" in components["Type2"] + @patch("os.path.exists", return_value=True) @patch("os.remove") @patch("os.path.isdir", return_value=True)