Skip to content

Commit d796e76

Browse files
amitmoskovitzclaude
andcommitted
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 <noreply@anthropic.com>
1 parent da8a2ab commit d796e76

2 files changed

Lines changed: 119 additions & 2 deletions

File tree

cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from os import path
23
from pathlib import Path
34
from typing import Optional
@@ -20,6 +21,16 @@
2021
MAVEN_DEP_TREE_FILE_NAME = 'bcde.mvndeps'
2122

2223

24+
def _has_dependency_graph(bom_content: Optional[str]) -> bool:
25+
try:
26+
if not bom_content:
27+
return False
28+
bom = json.loads(bom_content)
29+
return any(dep.get('dependsOn') for dep in bom.get('dependencies', []))
30+
except Exception:
31+
return False
32+
33+
2334
class RestoreMavenDependencies(BaseRestoreDependencies):
2435
def __init__(self, ctx: typer.Context, is_git_diff: bool, command_timeout: int) -> None:
2536
super().__init__(ctx, is_git_diff, command_timeout)
@@ -46,8 +57,14 @@ def try_restore_dependencies(self, document: Document) -> Optional[Document]:
4657
if document.content is None:
4758
return self.restore_from_secondary_command(document, manifest_file_path)
4859

49-
# super() reads the content and cleans up any generated file; no re-read needed
50-
return super().try_restore_dependencies(document)
60+
restore_dependencies_document = super().try_restore_dependencies(document)
61+
if restore_dependencies_document is None:
62+
return None
63+
64+
if not _has_dependency_graph(restore_dependencies_document.content):
65+
return self.restore_from_secondary_command(document, manifest_file_path)
66+
67+
return restore_dependencies_document
5168

5269
def restore_from_secondary_command(self, document: Document, manifest_file_path: str) -> Optional[Document]:
5370
restore_content = execute_commands(
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import json
2+
from unittest.mock import MagicMock, patch
3+
4+
from cycode.cli.files_collector.sca.maven.restore_maven_dependencies import (
5+
RestoreMavenDependencies,
6+
_has_dependency_graph,
7+
)
8+
from cycode.cli.models import Document
9+
10+
11+
class TestHasDependencyGraph:
12+
def test_returns_false_when_content_is_none(self) -> None:
13+
assert _has_dependency_graph(None) is False
14+
15+
def test_returns_false_when_content_is_empty_string(self) -> None:
16+
assert _has_dependency_graph('') is False
17+
18+
def test_returns_false_when_dependencies_section_is_missing(self) -> None:
19+
content = json.dumps({'components': [{'name': 'foo'}]})
20+
assert _has_dependency_graph(content) is False
21+
22+
def test_returns_false_when_all_dependencies_have_empty_dependsOn(self) -> None:
23+
content = json.dumps({'dependencies': [{'ref': 'pkg:maven/foo/bar@1.0', 'dependsOn': []}]})
24+
assert _has_dependency_graph(content) is False
25+
26+
def test_returns_false_when_dependencies_list_is_empty(self) -> None:
27+
content = json.dumps({'dependencies': []})
28+
assert _has_dependency_graph(content) is False
29+
30+
def test_returns_true_when_at_least_one_dependency_has_dependsOn(self) -> None:
31+
content = json.dumps({
32+
'dependencies': [
33+
{'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty-all@4.1.0']},
34+
{'ref': 'pkg:maven/io.netty/netty-all@4.1.0', 'dependsOn': []},
35+
]
36+
})
37+
assert _has_dependency_graph(content) is True
38+
39+
def test_returns_false_when_content_is_invalid_json(self) -> None:
40+
assert _has_dependency_graph('not valid json {{{') is False
41+
42+
43+
class TestRestoreMavenDependenciesFallback:
44+
def _make_instance(self) -> RestoreMavenDependencies:
45+
ctx = MagicMock()
46+
ctx.obj = {}
47+
return RestoreMavenDependencies(ctx=ctx, is_git_diff=False, command_timeout=60)
48+
49+
def test_falls_back_to_secondary_command_when_bom_has_no_dependency_graph(self) -> None:
50+
instance = self._make_instance()
51+
document = MagicMock(spec=Document)
52+
document.content = 'some content'
53+
54+
bom_doc = MagicMock(spec=Document)
55+
bom_doc.content = json.dumps({'dependencies': []})
56+
fallback_doc = MagicMock(spec=Document)
57+
58+
with (
59+
patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'),
60+
patch(
61+
'cycode.cli.files_collector.sca.maven.restore_maven_dependencies.BaseRestoreDependencies.try_restore_dependencies',
62+
return_value=bom_doc,
63+
),
64+
patch.object(instance, 'restore_from_secondary_command', return_value=fallback_doc) as mock_fallback,
65+
):
66+
result = instance.try_restore_dependencies(document)
67+
68+
mock_fallback.assert_called_once_with(document, '/project/pom.xml')
69+
assert result is fallback_doc
70+
71+
def test_returns_bom_document_when_dependency_graph_is_present(self) -> None:
72+
instance = self._make_instance()
73+
document = MagicMock(spec=Document)
74+
document.content = 'some content'
75+
76+
bom_doc = MagicMock(spec=Document)
77+
bom_doc.content = json.dumps({
78+
'dependencies': [
79+
{'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty@4.1.0']}
80+
]
81+
})
82+
83+
with (
84+
patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'),
85+
patch(
86+
'cycode.cli.files_collector.sca.maven.restore_maven_dependencies.BaseRestoreDependencies.try_restore_dependencies',
87+
return_value=bom_doc,
88+
),
89+
patch.object(instance, 'restore_from_secondary_command') as mock_fallback,
90+
):
91+
result = instance.try_restore_dependencies(document)
92+
93+
mock_fallback.assert_not_called()
94+
assert result is bom_doc
95+
96+
def test_uses_plugin_version_2_9_1(self) -> None:
97+
instance = self._make_instance()
98+
commands = instance.get_commands('/path/to/pom.xml')
99+
assert len(commands) == 1
100+
assert 'org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom' in commands[0]

0 commit comments

Comments
 (0)