Skip to content

Commit 6e609ac

Browse files
authored
DEVOPS-657 feat: extend update_dependency task to skip install package dependency if package version is already installed on the org (#4)
* DEVOPS-657 feat: add dependency installation check to skip already installed packages * DEVOPS-657 test: add unit tests for unmanaged dependency checks and error handling
1 parent 72c95cc commit 6e609ac

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

cumulusci/tasks/salesforce/tests/test_update_dependencies.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,71 @@ def test_freeze__interactive_exception():
504504
task.freeze(step)
505505

506506
assert "cannot be frozen" in str(e)
507+
508+
509+
def test_is_dependency_already_installed_unmanaged():
510+
"""Test that unmanaged dependencies return False"""
511+
task = create_task(UpdateDependencies, {"dependencies": []})
512+
dependency = UnmanagedGitHubRefDependency.parse_obj({
513+
"github": "https://github.com/Test/TestRepo",
514+
"ref": "main",
515+
"subfolder": "src"
516+
})
517+
518+
assert task._is_dependency_already_installed(dependency) is False
519+
520+
521+
def test_is_dependency_already_installed_error_handling():
522+
"""Test error handling when installed packages can't be retrieved"""
523+
task = create_task(UpdateDependencies, {"dependencies": []})
524+
task.org_config = mock.Mock()
525+
# Mock the property to raise an exception
526+
type(task.org_config).installed_packages = mock.PropertyMock(side_effect=Exception)
527+
dependency = PackageVersionIdDependency(version_id="04t000000000000")
528+
529+
assert task._is_dependency_already_installed(dependency) is False
530+
531+
532+
def test_is_dependency_already_installed_version_id():
533+
"""Test package version ID dependency checks"""
534+
task = create_task(UpdateDependencies, {"dependencies": []})
535+
task.org_config = mock.Mock()
536+
537+
# Test with 18 character ID
538+
task.org_config.installed_packages = {"ns": [mock.Mock(id="04t000000000000AAA")]}
539+
dependency = PackageVersionIdDependency(version_id="04t000000000000AAA")
540+
assert task._is_dependency_already_installed(dependency) is True
541+
542+
# Test with 15 character ID
543+
task.org_config.installed_packages = {"ns": [mock.Mock(id="04t000000000000")]}
544+
dependency = PackageVersionIdDependency(version_id="04t000000000000AAA")
545+
assert task._is_dependency_already_installed(dependency) is True
546+
547+
548+
def test_is_dependency_already_installed_namespace_version():
549+
"""Test namespace version dependency checks"""
550+
task = create_task(UpdateDependencies, {"dependencies": []})
551+
task.org_config = mock.Mock()
552+
553+
# Test with version_id
554+
task.org_config.installed_packages = {"ns": [mock.Mock(id="04t000000000000AAA")]}
555+
dependency = PackageNamespaceVersionDependency(
556+
namespace="ns",
557+
version="1.0",
558+
version_id="04t000000000000AAA"
559+
)
560+
assert task._is_dependency_already_installed(dependency) is True
561+
562+
# Test with regular version
563+
mock_version = mock.Mock()
564+
mock_version.id = "04t000000000000AAA"
565+
task.org_config.installed_packages = {"ns@1.0": [mock_version]}
566+
dependency = PackageNamespaceVersionDependency(namespace="ns", version="1.0")
567+
assert task._is_dependency_already_installed(dependency) is True
568+
569+
# Test with beta version
570+
mock_version = mock.Mock()
571+
mock_version.id = "04t000000000000BBB"
572+
task.org_config.installed_packages = {"ns@1.0b2": [mock_version]}
573+
dependency = PackageNamespaceVersionDependency(namespace="ns", version="1.0 (Beta 2)")
574+
assert task._is_dependency_already_installed(dependency) is True

cumulusci/tasks/salesforce/update_dependencies.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ def _run_task(self):
226226
self.org_config.reset_installed_packages()
227227

228228
def _install_dependency(self, dependency):
229+
# Log and short-circuit if dependency version is already present on the org
230+
self.logger.info(f"Checking if already installed: {dependency}")
231+
if self._is_dependency_already_installed(dependency):
232+
self.logger.info(f"{dependency} is already installed; skipping.")
233+
return
229234
if isinstance(
230235
dependency, (PackageNamespaceVersionDependency, PackageVersionIdDependency)
231236
):
@@ -235,6 +240,63 @@ def _install_dependency(self, dependency):
235240
else:
236241
dependency.install(self.project_config, self.org_config)
237242

243+
def _is_dependency_already_installed(self, dependency) -> bool:
244+
"""Return True if the resolved managed package dependency is already installed."""
245+
if not isinstance(
246+
dependency, (PackageNamespaceVersionDependency, PackageVersionIdDependency)
247+
):
248+
self.logger.debug("Dependency is unmanaged metadata; no installed check needed.")
249+
return False
250+
251+
try:
252+
installed = self.org_config.installed_packages
253+
except Exception:
254+
self.logger.warning(
255+
"Could not retrieve installed packages from org; proceeding with install."
256+
)
257+
return False
258+
259+
installed_version_ids_18 = {v.id for versions in installed.values() for v in versions}
260+
installed_version_ids_15 = {vid[:15] for vid in installed_version_ids_18}
261+
262+
if isinstance(dependency, PackageVersionIdDependency):
263+
dep_id = dependency.version_id
264+
dep_id_15 = dep_id[:15] if dep_id else None
265+
is_installed = (
266+
dep_id in installed_version_ids_18 or dep_id_15 in installed_version_ids_15
267+
)
268+
self.logger.info(
269+
f"Already-installed check by version_id: {dep_id} (15:{dep_id_15}) -> {is_installed}"
270+
)
271+
return is_installed
272+
273+
if isinstance(dependency, PackageNamespaceVersionDependency):
274+
if getattr(dependency, "version_id", None):
275+
dep_id = dependency.version_id
276+
dep_id_15 = dep_id[:15] if dep_id else None
277+
is_installed = (
278+
dep_id in installed_version_ids_18 or dep_id_15 in installed_version_ids_15
279+
)
280+
self.logger.info(
281+
f"Already-installed check by version_id: {dep_id} (15:{dep_id_15}) -> {is_installed}"
282+
)
283+
return is_installed
284+
285+
version = dependency.version
286+
if "Beta" in version:
287+
version_string = version.split(" ")[0]
288+
beta = version.split(" ")[-1].strip(")")
289+
version = f"{version_string}b{beta}"
290+
291+
key = f"{dependency.namespace}@{version}"
292+
is_installed = key in installed
293+
self.logger.info(
294+
f"Already-installed check by namespace@version: {key} -> {is_installed}"
295+
)
296+
return is_installed
297+
298+
return False
299+
238300
def freeze(self, step):
239301
if self.options["interactive"]:
240302
raise CumulusCIException(

0 commit comments

Comments
 (0)