mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-05 03:31:54 +00:00
e3013d9918
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
125 lines
4.8 KiB
Python
125 lines
4.8 KiB
Python
"""Unit tests for prowler.lib.check.tool_wrapper.
|
|
|
|
Covers the leaf helper directly (Provider.is_tool_wrapper_provider delegates
|
|
to it). Tests the frozenset fast path, the entry-point fallback for external
|
|
plug-ins, the broken-plug-in path, the no-match path, and the module-level
|
|
cache.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_ep_class_cache():
|
|
"""Reset the leaf module's cache between tests so they stay independent."""
|
|
from prowler.lib.check import tool_wrapper
|
|
|
|
tool_wrapper._ep_class_cache.clear()
|
|
yield
|
|
tool_wrapper._ep_class_cache.clear()
|
|
|
|
|
|
def _make_entry_point(name, cls):
|
|
"""Create a mock entry point whose `load()` returns `cls`."""
|
|
ep = MagicMock()
|
|
ep.name = name
|
|
ep.load.return_value = cls
|
|
return ep
|
|
|
|
|
|
class TestIsToolWrapperProvider:
|
|
"""is_tool_wrapper_provider: frozenset + entry-point fallback."""
|
|
|
|
@pytest.mark.parametrize("name", ["iac", "llm", "image"])
|
|
def test_returns_true_for_builtin_tool_wrappers(self, name):
|
|
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
|
|
|
assert is_tool_wrapper_provider(name) is True
|
|
|
|
@pytest.mark.parametrize("name", ["aws", "azure", "gcp", "github", "kubernetes"])
|
|
def test_returns_false_for_regular_builtins(self, name):
|
|
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
|
|
|
assert is_tool_wrapper_provider(name) is False
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_returns_true_for_external_plugin_with_flag(self, mock_eps):
|
|
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
|
|
|
cls = MagicMock(is_external_tool_provider=True)
|
|
mock_eps.return_value = [_make_entry_point("custom_wrapper", cls)]
|
|
|
|
assert is_tool_wrapper_provider("custom_wrapper") is True
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_returns_false_for_external_plugin_without_flag(self, mock_eps):
|
|
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
|
|
|
cls = MagicMock(is_external_tool_provider=False)
|
|
mock_eps.return_value = [_make_entry_point("vanilla_external", cls)]
|
|
|
|
assert is_tool_wrapper_provider("vanilla_external") is False
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_returns_false_for_unknown_provider(self, mock_eps):
|
|
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
|
|
|
mock_eps.return_value = []
|
|
|
|
assert is_tool_wrapper_provider("does-not-exist") is False
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_builtin_name_shortcircuits_before_loading_same_name_plugin(self, mock_eps):
|
|
"""A plug-in registered under a built-in's name cannot flip the
|
|
built-in onto the tool-wrapper path, and its module is never loaded."""
|
|
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
|
|
|
|
malicious = _make_entry_point("aws", MagicMock(is_external_tool_provider=True))
|
|
mock_eps.return_value = [malicious]
|
|
|
|
# `aws` is a built-in, so classification short-circuits to False...
|
|
assert is_tool_wrapper_provider("aws") is False
|
|
# ...and the shadowing plug-in's code is never executed via ep.load().
|
|
malicious.load.assert_not_called()
|
|
|
|
|
|
class TestLoadEpClass:
|
|
"""_load_ep_class: cache, broken plug-ins, no-match."""
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_caches_result_across_calls(self, mock_eps):
|
|
from prowler.lib.check.tool_wrapper import _load_ep_class
|
|
|
|
cls = MagicMock(is_external_tool_provider=True)
|
|
mock_eps.return_value = [_make_entry_point("cached_one", cls)]
|
|
|
|
first = _load_ep_class("cached_one")
|
|
second = _load_ep_class("cached_one")
|
|
|
|
assert first is cls
|
|
assert second is cls
|
|
# entry_points consulted only on the first call
|
|
assert mock_eps.call_count == 1
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_returns_none_for_broken_plugin(self, mock_eps):
|
|
from prowler.lib.check.tool_wrapper import _load_ep_class
|
|
|
|
broken_ep = MagicMock()
|
|
broken_ep.name = "broken"
|
|
broken_ep.load.side_effect = ImportError("plug-in is broken")
|
|
mock_eps.return_value = [broken_ep]
|
|
|
|
assert _load_ep_class("broken") is None
|
|
|
|
@patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
|
|
def test_returns_none_when_no_entry_point_matches(self, mock_eps):
|
|
from prowler.lib.check.tool_wrapper import _load_ep_class
|
|
|
|
cls = MagicMock()
|
|
mock_eps.return_value = [_make_entry_point("other_provider", cls)]
|
|
|
|
assert _load_ep_class("missing_provider") is None
|