Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from os import path
from pathlib import Path
from typing import Optional
Expand All @@ -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)
Expand All @@ -46,8 +57,16 @@ 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):
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

def restore_from_secondary_command(self, document: Document, manifest_file_path: str) -> Optional[Document]:
restore_content = execute_commands(
Expand Down
105 changes: 105 additions & 0 deletions tests/cli/files_collector/sca/test_restore_maven_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
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_depends_on(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_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': []},
]
}
)
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)
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'),
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]
Loading