Repository: SFDO-Tooling/CumulusCI
Pull request #3424 (2022-11-03 05:27:33)

Fix deploy of Omnistudio Remote Site Settings for new Salesforce DNS syntaxes (e.g. .*scratch.my.salesforce, *.demo.my.salesforce, etc.)
# Changes
- Allow multi-org compatible remote site setting injection for visualforce, legacy visualforce, and lightning web component remote site settings for Omnistudio development.


@W-11958099 


Patch:
diff --git a/cumulusci/tasks/vlocity/vlocity.py b/cumulusci/tasks/vlocity/vlocity.py
index 6d523e7e1..c64cb299e 100644
--- a/cumulusci/tasks/vlocity/vlocity.py
+++ b/cumulusci/tasks/vlocity/vlocity.py
@@ -1,3 +1,4 @@
+import re
 import sys
 from abc import ABC
 from typing import Final
@@ -66,7 +67,10 @@ class VlocitySimpleJobTask(VlocityBaseTask, ABC):
     """Abstract class for working with the `vlocity` CLI tool"""
 
     task_options: dict = {
-        "job_file": {"description": "Filepath to the jobfile", "required": True},
+        "job_file": {
+            "description": "Filepath to the jobfile",
+            "required": True,
+        },
         "extra": {"description": "Any extra arguments to pass to the vlocity CLI"},
     }
 
@@ -112,46 +116,137 @@ class OmniStudioDeployRemoteSiteSettings(AddRemoteSiteSettings):
 
     def _get_options(self) -> RSSOptions:
         namespace = self.options.get("namespace") or OMNI_NAMESPACE
-        if ".scratch.my" in self.org_config.instance_url:
-            visualforce_url: str = self.org_config.instance_url.replace(
-                ".scratch.my.salesforce.com",
-                f"--{namespace}.scratch.{self.org_config.instance_name}.visual.force.com",
-            )
-            legacy_visualforce_url: str = self.org_config.instance_url.replace(
-                ".scratch.my.salesforce.com",
-                f"--{namespace}.scratch.vf.force.com",
-            )
-        else:
-            visualforce_url: str = self.org_config.instance_url.replace(
-                ".my.salesforce.com",
-                f"--{namespace}.{self.org_config.instance_name}.visual.force.com",
-            )
-            legacy_visualforce_url: str = self.org_config.instance_url.replace(
-                ".my.salesforce.com",
-                f"--{namespace}.vf.force.com",
-            )
-        lightning_url: str = self.org_config.instance_url.replace(
-            ".my.salesforce.com", ".lightning.force.com"
+        urls = prepare_remote_site_urls(
+            self.org_config.instance_url,
+            self.org_config.instance_name,
+            namespace,
         )
-
         self.options = {
             **self.options,
             "records": [
                 {
                     "full_name": VF_RSS_NAME,
-                    "url": visualforce_url,
+                    "url": urls["visualforce_url"],
                     "is_active": True,
                 },
                 {
                     "full_name": VF_LEGACY_RSS_NAME,
-                    "url": legacy_visualforce_url,
+                    "url": urls["legacy_visualforce_url"],
                     "is_active": True,
                 },
                 {
                     "full_name": LWC_RSS_NAME,
-                    "url": lightning_url,
+                    "url": urls["lightning_url"],
                     "is_active": True,
                 },
             ],
         }
         return super()._get_options()
+
+
+def create_vf_url(
+    namespace: str,
+    instance_url: str,
+    legacy: bool = False,
+    instance: str = None,
+    org: str = None,
+    dns: str = ".my.salesforce.com",
+):
+    """
+    Helper function to create visualforce or legacy visualforce
+    remote site settings. Function takes a namespace,and injects it
+    into remote site setting url provided.
+
+    A legacy boolean parameter is passed to determine whether to construct
+    the legacy or visualforce url convention.
+
+    An optional org parameter is available to determine the url convention
+    to inject the org type into the remote site setting url
+    to follow the appropriate convention.
+
+    Lastly an optional dns value is given to abstract away the common
+    .my.salesforce.com convention that is common for all instance urls.
+    """
+    if legacy:
+        if org:
+            url = instance_url.replace(
+                f".{org}{dns}", f"--{namespace}.{org}.vf.force.com"
+            )
+        else:
+            url = instance_url.replace(f"{dns}", f"--{namespace}.vf.force.com")
+    else:
+        if org:
+            url = instance_url.replace(
+                f".{org}{dns}",
+                f"--{namespace}.{org}.{instance}.visual.force.com",
+            )
+        else:
+            url = instance_url.replace(
+                f"{dns}",
+                f"--{namespace}.{instance}.visual.force.com",
+            )
+    return url
+
+
+def prepare_remote_site_urls(
+    instance_url: str,
+    instance: str,
+    namespace: str,
+    dns: str = ".my.salesforce.com",
+):
+    """
+    Helper function to create all 3 remote site settings required
+    for Omnistudio Development.
+
+    This includes the following remote site settings:
+
+    - visualforce remote site setting (helper function used)
+    - legacy_visualforce remote site setting (helper function used)
+    - lightning (web component) remote site setting
+
+    Lastly an optional dns value is given to abstract away the common
+    .my.salesforce.com convention that is common for all instance urls.
+    """
+
+    match = re.match(f"(?P<Name>[^.]+)\\.(?P<Category>[^.]+){dns}", instance_url)
+    category = None
+    if match:
+        category = match.group("Category")
+
+    if category and f"{category}{dns}" in instance_url:
+        visualforce_url = create_vf_url(
+            namespace,
+            instance_url,
+            False,
+            instance,
+            category,
+        )
+        legacy_visualforce_url = create_vf_url(
+            namespace,
+            instance_url,
+            True,
+            instance,
+            category,
+        )
+    else:
+        visualforce_url = create_vf_url(
+            namespace,
+            instance_url,
+            False,
+            instance,
+            None,
+        )
+        legacy_visualforce_url = create_vf_url(
+            namespace,
+            instance_url,
+            True,
+            instance,
+            None,
+        )
+
+    lightning_url = instance_url.replace(f"{dns}", ".lightning.force.com")
+    return {
+        "visualforce_url": visualforce_url,
+        "legacy_visualforce_url": legacy_visualforce_url,
+        "lightning_url": lightning_url,
+    }


Test patch:
diff --git a/cumulusci/tasks/vlocity/tests/test_vlocity.py b/cumulusci/tasks/vlocity/tests/test_vlocity.py
index eb7b1d75a..9d7fdd53c 100644
--- a/cumulusci/tasks/vlocity/tests/test_vlocity.py
+++ b/cumulusci/tasks/vlocity/tests/test_vlocity.py
@@ -19,6 +19,17 @@ from cumulusci.tasks.vlocity.vlocity import (
     VlocityRetrieveTask,
 )
 
+ORG_TYPES = {
+    "develop": "https://inspiration-velocity-34802-dev-ed.develop.my.salesforce.com/",
+    "patch": "https://inspiration-velocity-34802-dev-ed.patch.my.salesforce.com/",
+    "trailblaze": "https://inspiration-velocity-34802-dev-ed.trailblaze.my.salesforce.com/",
+    "scratch": "https://inspiration-velocity-34802-dev-ed.scratch.my.salesforce.com/",
+    "demo": "https://inspiration-velocity-34802-dev-ed.demo.my.salesforce.com/",
+    "sandbox": "https://inspiration-velocity-34802-dev-ed.sandbox.my.salesforce.com/",
+    "free": "https://inspiration-velocity-34802-dev-ed.free.my.salesforce.com/",
+}
+
+
 username = "foo"
 org_name = "dev"
 access_token = "foo.bar.baz"
@@ -88,7 +99,11 @@ def test_vlocity_simple_job(
 
     task_config = TaskConfig(
         config={
-            "options": {"job_file": "vlocity.yaml", "org": org_name, "extra": extra}
+            "options": {
+                "job_file": "vlocity.yaml",
+                "org": org_name,
+                "extra": extra,
+            }
         }
     )
     task = task_class(project_config, task_config, org_config)
@@ -121,32 +136,34 @@ test_cases = [
 def test_deploy_omni_studio_site_settings_scratch(
     project_config, task_config, expected_namespace
 ):
-    org_config = mock.Mock(
-        installed_packages=[],
-        instance_url="https://inspiration-velocity-34802-dev-ed.scratch.my.salesforce.com/",
-        instance_name="CS28",
-        scratch=True,
-    )
-
-    task = OmniStudioDeployRemoteSiteSettings(project_config, task_config, org_config)
-    rss_options = task._get_options()
-    records = rss_options.records
-
-    expected_site_names = set([VF_RSS_NAME, VF_LEGACY_RSS_NAME, LWC_RSS_NAME])
-    actual_site_names = set([r.full_name for r in records])
-    assert expected_site_names == actual_site_names
-
-    # when no 'namespace' option is specified, we default to the omni studio namespace
-    expected_urls = set(
-        [
-            f"https://inspiration-velocity-34802-dev-ed--{expected_namespace}.scratch.{org_config.instance_name}.visual.force.com/",
-            "https://inspiration-velocity-34802-dev-ed.scratch.lightning.force.com/",
-            f"https://inspiration-velocity-34802-dev-ed--{expected_namespace}.scratch.vf.force.com/",
-        ]
-    )
+    for org_type, url in ORG_TYPES.items():
+        org_config = mock.Mock(
+            installed_packages=[],
+            instance_url=url,
+            instance_name="CS28",
+            scratch=True,
+        )
 
-    actual_urls = set([r.url for r in records])
-    assert expected_urls == actual_urls
+        task = OmniStudioDeployRemoteSiteSettings(
+            project_config, task_config, org_config
+        )
+        rss_options = task._get_options()
+        records = rss_options.records
+
+        expected_site_names = set([VF_RSS_NAME, VF_LEGACY_RSS_NAME, LWC_RSS_NAME])
+        actual_site_names = set([r.full_name for r in records])
+        assert expected_site_names == actual_site_names
+
+        # when no 'namespace' option is specified, we default to the omni studio namespace
+        expected_urls = set(
+            [
+                f"https://inspiration-velocity-34802-dev-ed--{expected_namespace}.{org_type}.{org_config.instance_name}.visual.force.com/",
+                f"https://inspiration-velocity-34802-dev-ed.{org_type}.lightning.force.com/",
+                f"https://inspiration-velocity-34802-dev-ed--{expected_namespace}.{org_type}.vf.force.com/",
+            ]
+        )
+        actual_urls = set([r.url for r in records])
+        assert expected_urls == actual_urls
 
 
 @pytest.mark.parametrize("task_config,expected_namespace", test_cases)
@@ -209,7 +226,9 @@ class TestVlocityIntegration:
         task_config = project_config.get_task("omni:test_success")
         task_class = import_global(task_config.class_path)
         task = create_task(
-            task_class, task_config.options, project_config=task_config.project_config
+            task_class,
+            task_config.options,
+            project_config=task_config.project_config,
         )
         try:
             task()
@@ -222,7 +241,9 @@ class TestVlocityIntegration:
         task_config = project_config.get_task("omni:test_failure")
         task_class = import_global(task_config.class_path)
         task = create_task(
-            task_class, task_config.options, project_config=task_config.project_config
+            task_class,
+            task_config.options,
+            project_config=task_config.project_config,
         )
         with pytest.raises(CommandException):
             task()