Files
prowler/tests/lib/check/tool_wrapper_test.py
2026-06-08 17:47:22 +02:00

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