Skip to content

Commit

Permalink
chore(RHTAPWATCH-691): verify-rpms test to fail on unsigned RPMs
Browse files Browse the repository at this point in the history
The e2e test for the verify-rpms script checked
only a scenario where it should not fail when unsigned RPMs
or errors exists.

Adds tests for multiple scenarios - where the script should
and should not fail when encountering unsigned RPMs or other errors.

This lead to a refactor which changed the failing responsability
from which fails the process from "generate_output" to the
main function.

Signed-off-by: Omer <oamsalem@redhat.com>
  • Loading branch information
Omeramsc committed Dec 31, 2023
1 parent 6eea7e0 commit 8871cf1
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 82 deletions.
144 changes: 73 additions & 71 deletions tests/test_rpm_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
[
Expand All @@ -88,7 +60,6 @@
),
ProcessedImage(image="i2", unsigned_rpms=["their-rpm"]),
],
True,
dedent(
"""
Found unsigned RPMs:
Expand All @@ -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(
[
Expand All @@ -109,7 +80,6 @@
image="i2", unsigned_rpms=[], error="Failed to run second command"
),
],
True,
dedent(
"""
Encountered errors:
Expand All @@ -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


Expand Down Expand Up @@ -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",
],
Expand All @@ -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
21 changes: 10 additions & 11 deletions verify_rpms/rpm_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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,
)
Expand All @@ -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)

Expand Down

0 comments on commit 8871cf1

Please sign in to comment.