From d796e76e16334c3da073c858392bb01f1ac540db Mon Sep 17 00:00:00 2001 From: amitmoskovitz Date: Mon, 18 May 2026 17:22:59 +0300 Subject: [PATCH 1/6] CM-64214: Fix missing dependency paths in Maven CLI scan Upgrade CycloneDX Maven plugin from 2.7.4 to 2.9.1 for more complete dependency graphs, and fall back to dependency:tree when the generated BOM has no dependency edges. Co-Authored-By: Claude Sonnet 4.6 --- .../sca/maven/restore_maven_dependencies.py | 21 +++- .../sca/test_restore_maven_dependencies.py | 100 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 tests/cli/files_collector/sca/test_restore_maven_dependencies.py diff --git a/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py b/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py index 740ccca9..a8ec7559 100644 --- a/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +++ b/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py @@ -1,3 +1,4 @@ +import json from os import path from pathlib import Path from typing import Optional @@ -20,6 +21,16 @@ MAVEN_DEP_TREE_FILE_NAME = 'bcde.mvndeps' +def _has_dependency_graph(bom_content: Optional[str]) -> bool: + try: + if not bom_content: + return False + bom = json.loads(bom_content) + return any(dep.get('dependsOn') for dep in bom.get('dependencies', [])) + except Exception: + return False + + class RestoreMavenDependencies(BaseRestoreDependencies): def __init__(self, ctx: typer.Context, is_git_diff: bool, command_timeout: int) -> None: super().__init__(ctx, is_git_diff, command_timeout) @@ -46,8 +57,14 @@ def try_restore_dependencies(self, document: Document) -> Optional[Document]: if document.content is None: return self.restore_from_secondary_command(document, manifest_file_path) - # super() reads the content and cleans up any generated file; no re-read needed - return super().try_restore_dependencies(document) + restore_dependencies_document = super().try_restore_dependencies(document) + if restore_dependencies_document is None: + return None + + if not _has_dependency_graph(restore_dependencies_document.content): + return self.restore_from_secondary_command(document, manifest_file_path) + + return restore_dependencies_document def restore_from_secondary_command(self, document: Document, manifest_file_path: str) -> Optional[Document]: restore_content = execute_commands( diff --git a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py new file mode 100644 index 00000000..59962e99 --- /dev/null +++ b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py @@ -0,0 +1,100 @@ +import json +from unittest.mock import MagicMock, patch + +from cycode.cli.files_collector.sca.maven.restore_maven_dependencies import ( + RestoreMavenDependencies, + _has_dependency_graph, +) +from cycode.cli.models import Document + + +class TestHasDependencyGraph: + def test_returns_false_when_content_is_none(self) -> None: + assert _has_dependency_graph(None) is False + + def test_returns_false_when_content_is_empty_string(self) -> None: + assert _has_dependency_graph('') is False + + def test_returns_false_when_dependencies_section_is_missing(self) -> None: + content = json.dumps({'components': [{'name': 'foo'}]}) + assert _has_dependency_graph(content) is False + + def test_returns_false_when_all_dependencies_have_empty_dependsOn(self) -> None: + content = json.dumps({'dependencies': [{'ref': 'pkg:maven/foo/bar@1.0', 'dependsOn': []}]}) + assert _has_dependency_graph(content) is False + + def test_returns_false_when_dependencies_list_is_empty(self) -> None: + content = json.dumps({'dependencies': []}) + assert _has_dependency_graph(content) is False + + def test_returns_true_when_at_least_one_dependency_has_dependsOn(self) -> None: + content = json.dumps({ + 'dependencies': [ + {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty-all@4.1.0']}, + {'ref': 'pkg:maven/io.netty/netty-all@4.1.0', 'dependsOn': []}, + ] + }) + assert _has_dependency_graph(content) is True + + def test_returns_false_when_content_is_invalid_json(self) -> None: + assert _has_dependency_graph('not valid json {{{') is False + + +class TestRestoreMavenDependenciesFallback: + def _make_instance(self) -> RestoreMavenDependencies: + ctx = MagicMock() + ctx.obj = {} + return RestoreMavenDependencies(ctx=ctx, is_git_diff=False, command_timeout=60) + + def test_falls_back_to_secondary_command_when_bom_has_no_dependency_graph(self) -> None: + instance = self._make_instance() + document = MagicMock(spec=Document) + document.content = 'some content' + + bom_doc = MagicMock(spec=Document) + bom_doc.content = json.dumps({'dependencies': []}) + fallback_doc = MagicMock(spec=Document) + + with ( + patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'), + patch( + 'cycode.cli.files_collector.sca.maven.restore_maven_dependencies.BaseRestoreDependencies.try_restore_dependencies', + return_value=bom_doc, + ), + patch.object(instance, 'restore_from_secondary_command', return_value=fallback_doc) as mock_fallback, + ): + result = instance.try_restore_dependencies(document) + + mock_fallback.assert_called_once_with(document, '/project/pom.xml') + assert result is fallback_doc + + def test_returns_bom_document_when_dependency_graph_is_present(self) -> None: + instance = self._make_instance() + document = MagicMock(spec=Document) + document.content = 'some content' + + bom_doc = MagicMock(spec=Document) + bom_doc.content = json.dumps({ + 'dependencies': [ + {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty@4.1.0']} + ] + }) + + with ( + patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'), + patch( + 'cycode.cli.files_collector.sca.maven.restore_maven_dependencies.BaseRestoreDependencies.try_restore_dependencies', + return_value=bom_doc, + ), + patch.object(instance, 'restore_from_secondary_command') as mock_fallback, + ): + result = instance.try_restore_dependencies(document) + + mock_fallback.assert_not_called() + assert result is bom_doc + + def test_uses_plugin_version_2_9_1(self) -> None: + instance = self._make_instance() + commands = instance.get_commands('/path/to/pom.xml') + assert len(commands) == 1 + assert 'org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom' in commands[0] From 382bdd8e70ad78e4ffcd9106e475e038613c82f1 Mon Sep 17 00:00:00 2001 From: amitmoskovitz Date: Mon, 18 May 2026 19:27:28 +0300 Subject: [PATCH 2/6] CM-64214: Fix ruff N802 lint errors in test method names Co-Authored-By: Claude Sonnet 4.6 --- .../files_collector/sca/test_restore_maven_dependencies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py index 59962e99..8c022694 100644 --- a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py +++ b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py @@ -19,7 +19,7 @@ def test_returns_false_when_dependencies_section_is_missing(self) -> None: content = json.dumps({'components': [{'name': 'foo'}]}) assert _has_dependency_graph(content) is False - def test_returns_false_when_all_dependencies_have_empty_dependsOn(self) -> None: + def test_returns_false_when_all_dependencies_have_empty_depends_on(self) -> None: content = json.dumps({'dependencies': [{'ref': 'pkg:maven/foo/bar@1.0', 'dependsOn': []}]}) assert _has_dependency_graph(content) is False @@ -27,7 +27,7 @@ def test_returns_false_when_dependencies_list_is_empty(self) -> None: content = json.dumps({'dependencies': []}) assert _has_dependency_graph(content) is False - def test_returns_true_when_at_least_one_dependency_has_dependsOn(self) -> None: + def test_returns_true_when_at_least_one_dependency_has_depends_on(self) -> None: content = json.dumps({ 'dependencies': [ {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty-all@4.1.0']}, From 57addd97b9b80e7fcc73447ab0b1d3a92ac6ff5d Mon Sep 17 00:00:00 2001 From: amitmoskovitz Date: Mon, 18 May 2026 19:52:40 +0300 Subject: [PATCH 3/6] CM-64214: Fix ruff format issues in test file Co-Authored-By: Claude Sonnet 4.6 --- .../sca/test_restore_maven_dependencies.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py index 8c022694..921f0a0f 100644 --- a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py +++ b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py @@ -28,12 +28,14 @@ def test_returns_false_when_dependencies_list_is_empty(self) -> None: assert _has_dependency_graph(content) is False def test_returns_true_when_at_least_one_dependency_has_depends_on(self) -> None: - content = json.dumps({ - 'dependencies': [ - {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty-all@4.1.0']}, - {'ref': 'pkg:maven/io.netty/netty-all@4.1.0', 'dependsOn': []}, - ] - }) + content = json.dumps( + { + 'dependencies': [ + {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty-all@4.1.0']}, + {'ref': 'pkg:maven/io.netty/netty-all@4.1.0', 'dependsOn': []}, + ] + } + ) assert _has_dependency_graph(content) is True def test_returns_false_when_content_is_invalid_json(self) -> None: @@ -74,11 +76,13 @@ def test_returns_bom_document_when_dependency_graph_is_present(self) -> None: document.content = 'some content' bom_doc = MagicMock(spec=Document) - bom_doc.content = json.dumps({ - 'dependencies': [ - {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty@4.1.0']} - ] - }) + bom_doc.content = json.dumps( + { + 'dependencies': [ + {'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty@4.1.0']} + ] + } + ) with ( patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'), From 8dd044037d6133f0addd1594c1ac71ccec2a542b Mon Sep 17 00:00:00 2001 From: amitmoskovitz Date: Mon, 18 May 2026 19:55:31 +0300 Subject: [PATCH 4/6] CM-64214: Fall back to BOM when dependency:tree also produces no content Co-Authored-By: Claude Sonnet 4.6 --- .../files_collector/sca/maven/restore_maven_dependencies.py | 4 +++- .../files_collector/sca/test_restore_maven_dependencies.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py b/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py index a8ec7559..53ed269f 100644 --- a/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +++ b/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py @@ -62,7 +62,9 @@ def try_restore_dependencies(self, document: Document) -> Optional[Document]: return None if not _has_dependency_graph(restore_dependencies_document.content): - return self.restore_from_secondary_command(document, manifest_file_path) + fallback = self.restore_from_secondary_command(document, manifest_file_path) + if fallback is not None and fallback.content is not None: + return fallback return restore_dependencies_document diff --git a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py index 921f0a0f..fc49cb91 100644 --- a/tests/cli/files_collector/sca/test_restore_maven_dependencies.py +++ b/tests/cli/files_collector/sca/test_restore_maven_dependencies.py @@ -56,6 +56,7 @@ def test_falls_back_to_secondary_command_when_bom_has_no_dependency_graph(self) bom_doc = MagicMock(spec=Document) bom_doc.content = json.dumps({'dependencies': []}) fallback_doc = MagicMock(spec=Document) + fallback_doc.content = '[INFO] com.example:root:jar:1.0\n+- io.netty:netty-all:jar:4.1.0' with ( patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'), From b5e88553f474748d18eaddd9500d0282248ec4c1 Mon Sep 17 00:00:00 2001 From: amitmoskovitz Date: Mon, 18 May 2026 19:59:43 +0300 Subject: [PATCH 5/6] CM-64214: Trigger CI re-run From 8c549fbf2257ce622684ab3c2f9f12224d498f0e Mon Sep 17 00:00:00 2001 From: amitmoskovitz Date: Mon, 18 May 2026 20:02:28 +0300 Subject: [PATCH 6/6] CM-64214: Trigger CI re-run