diff --git a/tests/test_rpm_verifier.py b/tests/test_rpm_verifier.py index 3132007..e11ef1d 100644 --- a/tests/test_rpm_verifier.py +++ b/tests/test_rpm_verifier.py @@ -2,6 +2,7 @@ from pathlib import Path from subprocess import CalledProcessError from textwrap import dedent +from typing import Callable from unittest.mock import MagicMock, call, create_autospec, sentinel import pytest @@ -19,67 +20,38 @@ @pytest.mark.parametrize( - ("processed_images", "fail", "expected_print", "expected_fail"), + ("processed_images", "expected_print", "expected_failures_exist"), [ pytest.param( [ProcessedImage(image="i1", unsigned_rpms=[])], - False, - "No unsigned RPMs found.", - False, - id="No unsigned RPMs + do not fail if unsigned", - ), - pytest.param( - [ - ProcessedImage(image="i1", unsigned_rpms=[]), - ProcessedImage(image="i2", unsigned_rpms=[]), - ], - True, "No unsigned RPMs found.", False, - id="No unsigned RPMs + fail if unsigned", + id="No unsigned RPMs + no errors. Do not report failures", ), pytest.param( [ProcessedImage(image="i1", unsigned_rpms=["my-rpm"])], - False, dedent( """ Found unsigned RPMs: i1: my-rpm """ ).strip(), - False, - id="1 unsigned + do not fail if unsigned", - ), - pytest.param( - [ - ProcessedImage(image="i1", unsigned_rpms=["my-rpm", "another-rpm"]), - ProcessedImage(image="i2", unsigned_rpms=[]), - ], True, - dedent( - """ - Found unsigned RPMs: - i1: my-rpm, another-rpm - """ - ).strip(), - True, - id="an image with multiple unsigned and another without + fail if unsigned", + id="Unsigned RPM + no errors. Report failures", ), pytest.param( [ ProcessedImage(image="i1", unsigned_rpms=["my-rpm", "another-rpm"]), - ProcessedImage(image="i2", unsigned_rpms=["their-rpm"]), + ProcessedImage(image="i2", unsigned_rpms=[]), ], - True, dedent( """ Found unsigned RPMs: i1: my-rpm, another-rpm - i2: their-rpm """ ).strip(), True, - id="multiple images with unsigned rpms + fail if unsigned", + id="An image with multiple unsigned and another without. Report failure", ), pytest.param( [ @@ -88,7 +60,6 @@ ), ProcessedImage(image="i2", unsigned_rpms=["their-rpm"]), ], - True, dedent( """ Found unsigned RPMs: @@ -98,7 +69,7 @@ """ ).strip(), True, - id="Error in running command + image with unsigned rpms + fail if unsigned", + id="Error in running command + image with unsigned RPMs. Report failure", ), pytest.param( [ @@ -109,7 +80,6 @@ image="i2", unsigned_rpms=[], error="Failed to run second command" ), ], - True, dedent( """ Encountered errors: @@ -118,40 +88,18 @@ """ ).strip(), True, - id="2 Errors in running command + fail if unsigned", - ), - pytest.param( - [ - ProcessedImage( - image="i1", unsigned_rpms=[], error="Failed to run first command" - ), - ProcessedImage( - image="i2", unsigned_rpms=["my-rpm", "another-rpm"], error="" - ), - ], - False, - dedent( - """ - Found unsigned RPMs: - i2: my-rpm, another-rpm - Encountered errors: - i1: Failed to run first command - """ - ).strip(), - False, - id="Errors in running command + image with unsigned rpms + do not fail if unsigned", + id="Multiple errors in running command + no unsigned RPMs. Report failure", ), ], ) def test_generate_output( processed_images: list[ProcessedImage], - fail: bool, expected_print: str, - expected_fail: bool, + expected_failures_exist: bool, ) -> None: """Test generate output""" - fail_out, print_out = generate_output(processed_images, fail) - assert fail_out == expected_fail + fail_out, print_out = generate_output(processed_images) + assert fail_out == expected_failures_exist assert print_out == expected_print @@ -353,24 +301,53 @@ def mock_image_processor(self, monkeypatch: MonkeyPatch) -> MagicMock: return mock @pytest.fixture() - def mock_generate_output(self, monkeypatch: MonkeyPatch) -> MagicMock: - """Monkey-patched generate_output""" - mock = create_autospec(generate_output, return_value=(False, "")) - monkeypatch.setattr(rpm_verifier, generate_output.__name__, mock) - return mock + def create_generate_output_mock( + self, monkeypatch: MonkeyPatch + ) -> Callable[[bool], MagicMock]: + """Create a generate_output mock with different results according to + the `fail_unsigned` flag""" + def _mock_generate_output(with_failures: bool = False) -> MagicMock: + """Monkey-patched generate_output""" + mock = create_autospec( + generate_output, return_value=(with_failures, sentinel.output_gen_out) + ) + monkeypatch.setattr(rpm_verifier, generate_output.__name__, mock) + return mock + + return _mock_generate_output + + @pytest.mark.parametrize( + "fail_unsigned, has_errors", + [ + pytest.param( + False, + False, + id="Should pass if there are errors, and there are errors.", + ), + pytest.param( + False, True, id="Should pass if there are errors, and there are none." + ), + pytest.param( + True, False, id="Should fail if there are errors, and there are none." + ), + ], + ) def test_main( self, - mock_generate_output: MagicMock, + create_generate_output_mock: MagicMock, mock_image_processor: MagicMock, + fail_unsigned: bool, + has_errors: bool, ) -> None: """Test call to rpm_verifier.py main function""" + generate_output_mock = create_generate_output_mock(with_failures=has_errors) rpm_verifier.main( # pylint: disable=no-value-for-parameter args=[ "--input", "img1", "--fail-unsigned", - "true", + fail_unsigned, "--workdir", "some/path", ], @@ -379,5 +356,30 @@ def test_main( ) assert mock_image_processor.return_value.call_count == 1 + generate_output_mock.assert_called_once_with([sentinel.output]) mock_image_processor.return_value.assert_has_calls([call("img1")]) - mock_generate_output.assert_called_once_with([sentinel.output], True) + + def test_main_fail_on_unsigned_rpm_or_errors( + self, + create_generate_output_mock: MagicMock, + mock_image_processor: MagicMock, + ) -> None: + """Test call to rpm_verifier.py main function fails + when whe 'fail-unsigned' flag is used and there are unsigned RPMs + """ + fail_unsigned: bool = True + create_generate_output_mock(with_failures=True) + with pytest.raises(SystemExit) as err: + rpm_verifier.main( # pylint: disable=no-value-for-parameter + args=[ + "--input", + "img1", + "--fail-unsigned", + fail_unsigned, + "--workdir", + "some/path", + ], + obj={}, + standalone_mode=False, + ) + assert err.value.code == sentinel.output_gen_out diff --git a/verify_rpms/rpm_verifier.py b/verify_rpms/rpm_verifier.py index d65eb21..65183ba 100644 --- a/verify_rpms/rpm_verifier.py +++ b/verify_rpms/rpm_verifier.py @@ -72,28 +72,27 @@ def get_unsigned_rpms(rpmdb: Path, runner: Callable = run) -> list[str]: ] -def generate_output( - processed_images: Iterable[ProcessedImage], fail_unsigned: bool -) -> tuple[bool, str]: +def generate_output(processed_images: Iterable[ProcessedImage]) -> tuple[bool, str]: """ Generate the output that should be printed to the user based on the processed images - and decide whether the script should fail or not + results. :param processed_images: each element contains a name and a list of unsigned images - :param fail_unsigned: whether the script should fail in case unsigned rpms found + + :returns: The status and summary of the image processing. """ + failures = True with_unsigned_rpms = [img for img in processed_images if img.unsigned_rpms] with_error = [img for img in processed_images if img.error] if not with_unsigned_rpms and not with_error: output = "No unsigned RPMs found." - fail = False + failures = False else: output = "\n".join([str(img) for img in with_unsigned_rpms]) output = f"Found unsigned RPMs:\n{output}" if with_unsigned_rpms else "" error_str = "\n".join([f"{img.image}: {img.error}" for img in with_error]) output = f"{output}\nEncountered errors:\n{error_str}" if error_str else output - fail = fail_unsigned - return fail, output.lstrip() + return failures, output.lstrip() def parse_image_input(image_input: str) -> list[str]: @@ -146,7 +145,7 @@ def __call__(self, img: str) -> ProcessedImage: ) @click.option( "--fail-unsigned", - help="Exit with failure if unsigned RPMs found", + help="Exit with failure if unsigned RPMs or other errors were found", type=bool, required=True, ) @@ -167,8 +166,8 @@ def main(img_input: str, fail_unsigned: bool, workdir: Path) -> None: processor, container_images ) - fail, output = generate_output(list(processed_images), fail_unsigned) - if fail: + failures_occured, output = generate_output(list(processed_images)) + if failures_occured and fail_unsigned: sys.exit(output) print(output)