Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(RHTAPWATCH-691): verify-rpms test to fail on unsigned RPMs #35

Merged
merged 1 commit into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"""
yftacherzog marked this conversation as resolved.
Show resolved Hide resolved
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