From efa3283a25dfdcdcd8382e2aaea8ea638133659a Mon Sep 17 00:00:00 2001 From: StylusFrost Date: Fri, 5 Jun 2026 13:42:29 +0200 Subject: [PATCH] fix(provider): return generic OutputOptions default instead of raising External providers that do not override get_output_options no longer abort the run with NotImplementedError. The base contract returns a generic ProviderOutputOptions, honoring arguments.output_filename and otherwise falling back to a provider-typed filename. Built-ins are unaffected. --- prowler/providers/common/provider.py | 15 ++++-- .../external/test_dynamic_provider_loading.py | 49 +++++++++++++++++-- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index 5e1ca42f14..c8561cd4bd 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -153,11 +153,16 @@ class Provider(ABC): """ raise NotImplementedError(f"{cls.__name__} has not implemented from_cli_args()") - def get_output_options(self, arguments, _bulk_checks_metadata): - """Create the provider-specific OutputOptions.""" - raise NotImplementedError( - f"{self.__class__.__name__} has not implemented get_output_options()" - ) + def get_output_options(self, arguments, bulk_checks_metadata): + """Return a generic OutputOptions default; override for provider-specific output.""" + from prowler.config.config import output_file_timestamp + from prowler.providers.common.models import ProviderOutputOptions + + output_options = ProviderOutputOptions(arguments, bulk_checks_metadata) + output_options.output_filename = getattr( + arguments, "output_filename", None + ) or (f"prowler-output-{self.type}-{output_file_timestamp}") + return output_options def get_stdout_detail(self, _finding) -> str: """Return the detail string for stdout reporting (region, location, etc.).""" diff --git a/tests/providers/external/test_dynamic_provider_loading.py b/tests/providers/external/test_dynamic_provider_loading.py index 749b7aff95..65087315e1 100644 --- a/tests/providers/external/test_dynamic_provider_loading.py +++ b/tests/providers/external/test_dynamic_provider_loading.py @@ -1798,11 +1798,52 @@ class TestBaseContractDefaults: with pytest.raises(NotImplementedError): FakeProviderNoHelpText.from_cli_args(MagicMock(), {}) - def test_get_output_options_raises_not_implemented(self): - """Base Provider.get_output_options raises NotImplementedError.""" + def test_get_output_options_returns_generic_default(self): + """Base Provider.get_output_options returns a generic ProviderOutputOptions + so an external provider that does not override it still produces output + instead of aborting the run with NotImplementedError.""" + from prowler.config.config import output_file_timestamp + from prowler.providers.common.models import ProviderOutputOptions + provider = FakeProviderNoHelpText() - with pytest.raises(NotImplementedError): - provider.get_output_options(MagicMock(), {}) + arguments = Namespace( + status=None, + output_formats=None, + output_directory=None, + output_filename=None, + verbose=None, + only_logs=None, + unix_timestamp=None, + shodan=None, + fixer=None, + ) + + output_options = provider.get_output_options(arguments, {}) + + assert isinstance(output_options, ProviderOutputOptions) + assert ( + output_options.output_filename + == f"prowler-output-{provider.type}-{output_file_timestamp}" + ) + + def test_get_output_options_honors_explicit_filename(self): + """A user-supplied output_filename is preserved by the default.""" + provider = FakeProviderNoHelpText() + arguments = Namespace( + status=None, + output_formats=None, + output_directory=None, + output_filename="custom-name", + verbose=None, + only_logs=None, + unix_timestamp=None, + shodan=None, + fixer=None, + ) + + output_options = provider.get_output_options(arguments, {}) + + assert output_options.output_filename == "custom-name" def test_get_stdout_detail_raises_not_implemented(self): """Base Provider.get_stdout_detail raises NotImplementedError."""