Skip to content

Commit e0e5281

Browse files
CM-64214: Fix missing dependency paths in Maven CLI scan (#456)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent da8a2ab commit e0e5281

2 files changed

Lines changed: 126 additions & 2 deletions

File tree

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

Lines changed: 21 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,16 @@ 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+
fallback = self.restore_from_secondary_command(document, manifest_file_path)
66+
if fallback is not None and fallback.content is not None:
67+
return fallback
68+
69+
return restore_dependencies_document
5170

5271
def restore_from_secondary_command(self, document: Document, manifest_file_path: str) -> Optional[Document]:
5372
restore_content = execute_commands(
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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_depends_on(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_depends_on(self) -> None:
31+
content = json.dumps(
32+
{
33+
'dependencies': [
34+
{'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty-all@4.1.0']},
35+
{'ref': 'pkg:maven/io.netty/netty-all@4.1.0', 'dependsOn': []},
36+
]
37+
}
38+
)
39+
assert _has_dependency_graph(content) is True
40+
41+
def test_returns_false_when_content_is_invalid_json(self) -> None:
42+
assert _has_dependency_graph('not valid json {{{') is False
43+
44+
45+
class TestRestoreMavenDependenciesFallback:
46+
def _make_instance(self) -> RestoreMavenDependencies:
47+
ctx = MagicMock()
48+
ctx.obj = {}
49+
return RestoreMavenDependencies(ctx=ctx, is_git_diff=False, command_timeout=60)
50+
51+
def test_falls_back_to_secondary_command_when_bom_has_no_dependency_graph(self) -> None:
52+
instance = self._make_instance()
53+
document = MagicMock(spec=Document)
54+
document.content = 'some content'
55+
56+
bom_doc = MagicMock(spec=Document)
57+
bom_doc.content = json.dumps({'dependencies': []})
58+
fallback_doc = MagicMock(spec=Document)
59+
fallback_doc.content = '[INFO] com.example:root:jar:1.0\n+- io.netty:netty-all:jar:4.1.0'
60+
61+
with (
62+
patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'),
63+
patch(
64+
'cycode.cli.files_collector.sca.maven.restore_maven_dependencies.BaseRestoreDependencies.try_restore_dependencies',
65+
return_value=bom_doc,
66+
),
67+
patch.object(instance, 'restore_from_secondary_command', return_value=fallback_doc) as mock_fallback,
68+
):
69+
result = instance.try_restore_dependencies(document)
70+
71+
mock_fallback.assert_called_once_with(document, '/project/pom.xml')
72+
assert result is fallback_doc
73+
74+
def test_returns_bom_document_when_dependency_graph_is_present(self) -> None:
75+
instance = self._make_instance()
76+
document = MagicMock(spec=Document)
77+
document.content = 'some content'
78+
79+
bom_doc = MagicMock(spec=Document)
80+
bom_doc.content = json.dumps(
81+
{
82+
'dependencies': [
83+
{'ref': 'pkg:maven/com.example/root@1.0', 'dependsOn': ['pkg:maven/io.netty/netty@4.1.0']}
84+
]
85+
}
86+
)
87+
88+
with (
89+
patch.object(instance, 'get_manifest_file_path', return_value='/project/pom.xml'),
90+
patch(
91+
'cycode.cli.files_collector.sca.maven.restore_maven_dependencies.BaseRestoreDependencies.try_restore_dependencies',
92+
return_value=bom_doc,
93+
),
94+
patch.object(instance, 'restore_from_secondary_command') as mock_fallback,
95+
):
96+
result = instance.try_restore_dependencies(document)
97+
98+
mock_fallback.assert_not_called()
99+
assert result is bom_doc
100+
101+
def test_uses_plugin_version_2_9_1(self) -> None:
102+
instance = self._make_instance()
103+
commands = instance.get_commands('/path/to/pom.xml')
104+
assert len(commands) == 1
105+
assert 'org.cyclonedx:cyclonedx-maven-plugin:2.9.1:makeAggregateBom' in commands[0]

0 commit comments

Comments
 (0)