Source code for tests.test_attestation

# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Tests for the attestation bundle naming rule."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from repomatic.release.attestation import (
    ATTESTATION_SUFFIX,
    bundle_filename,
    bundle_subjects,
    missing_subjects,
    pack_attestation,
    verify_attestation,
)
from repomatic.upstream import DEFAULT_REPO
from tests.conftest import write_bundle


[docs] def test_bundle_subjects_reads_every_name(tmp_path): """Subjects come back in the order the statement lists them.""" bundle = write_bundle(tmp_path / "b.json", "papaya.tar.gz", "mango.zip") assert bundle_subjects(bundle) == ("papaya.tar.gz", "mango.zip")
[docs] @pytest.mark.parametrize( ("content", "message"), ( ("not json at all", "not valid JSON"), ('{"mediaType": "x"}', "carries no DSSE envelope payload"), ('{"dsseEnvelope": {"payload": "!!!"}}', "unreadable payload"), ), ) def test_bundle_subjects_rejects_malformed(tmp_path, content, message): """A file that is not a readable bundle fails with a specific message.""" bundle = tmp_path / "b.json" bundle.write_text(content, encoding="UTF-8") with pytest.raises(ValueError, match=message): bundle_subjects(bundle)
[docs] def test_bundle_subjects_rejects_empty_subject_list(tmp_path): """A bundle attesting nothing has no name to give.""" bundle = write_bundle(tmp_path / "b.json") with pytest.raises(ValueError, match="attests no subject"): bundle_subjects(bundle)
[docs] @pytest.mark.parametrize("name", ("../papaya.zip", "nested/papaya.zip")) def test_bundle_subjects_rejects_path_subject(tmp_path, name): """A subject naming a path could escape the asset directory.""" bundle = write_bundle(tmp_path / "b.json", name) with pytest.raises(ValueError, match="not a bare filename"): bundle_subjects(bundle)
[docs] @pytest.mark.parametrize( ("subjects", "set_name", "expected"), ( # A lone subject lends the bundle its whole filename, extension kept, # so the two sort together on an alphabetically listed release page. (("papaya.tar.gz",), None, "papaya.tar.gz.attestation.json"), ( ("papaya-1.2.3-linux-arm64.bin",), None, "papaya-1.2.3-linux-arm64.bin.attestation.json", ), # A set name is ignored when there is a subject to name it after. (("papaya.zip",), "mango-set", "papaya.zip.attestation.json"), # Several subjects share one bundle, which takes the set name. ( ("papaya.zip", "mango.zip"), "fruit-extra-assets", "fruit-extra-assets.attestation.json", ), ), ) def test_bundle_filename(subjects, set_name, expected): """One subject names the bundle; several fall back to the set name.""" assert bundle_filename(subjects, set_name) == expected
[docs] def test_bundle_filename_needs_a_set_name_for_several_subjects(): """No single filename can claim a bundle covering several assets.""" with pytest.raises(ValueError, match="pass --name"): bundle_filename(("papaya.zip", "mango.zip"))
[docs] def test_bundle_filename_rejects_no_subject(): """Naming a bundle that attests nothing is a programming error.""" with pytest.raises(ValueError, match="attests no subject"): bundle_filename(())
[docs] def test_pack_attestation_names_bundle_and_lists_uploads(tmp_path): """The bundle lands beside its subject, and both are listed for upload.""" (tmp_path / "papaya.tar.gz").write_bytes(b"tarball") bundle = write_bundle(tmp_path / "raw.json", "papaya.tar.gz") uploads = pack_attestation(bundle, tmp_path) assert [path.name for path in uploads] == [ "papaya.tar.gz", "papaya.tar.gz.attestation.json", ] named = tmp_path / f"papaya.tar.gz{ATTESTATION_SUFFIX}" assert named.read_bytes() == bundle.read_bytes() # Idempotent: a second run copies the same bytes over the same name. assert pack_attestation(bundle, tmp_path) == uploads
[docs] def test_pack_attestation_covers_a_whole_set(tmp_path): """Several subjects yield one set-named bundle and every asset.""" (tmp_path / "papaya.zip").write_bytes(b"one") (tmp_path / "mango.zip").write_bytes(b"two") bundle = write_bundle(tmp_path / "raw.json", "papaya.zip", "mango.zip") uploads = pack_attestation(bundle, tmp_path, "fruit-extra-assets") assert [path.name for path in uploads] == [ "fruit-extra-assets.attestation.json", "mango.zip", "papaya.zip", ]
[docs] def test_pack_attestation_rejects_a_missing_subject(tmp_path): """A bundle covering a file that is not there attested another tree.""" (tmp_path / "papaya.zip").write_bytes(b"one") bundle = write_bundle(tmp_path / "raw.json", "papaya.zip", "mango.zip") with pytest.raises(ValueError, match=r"mango\.zip, missing from"): pack_attestation(bundle, tmp_path, "fruit-extra-assets")
[docs] def test_missing_subjects_names_what_the_bundle_lacks(tmp_path): """A declared asset whose artifact never landed is absent from the bundle.""" bundle = write_bundle(tmp_path / "raw.json", "papaya.zip") assert missing_subjects(bundle, ["papaya.zip", "mango.zip", "kiwi.zip"]) == [ "kiwi.zip", "mango.zip", ] assert missing_subjects(bundle, ["papaya.zip"]) == []
[docs] def test_verify_attestation_requires_the_upstream_signer(tmp_path): """The release lanes of the upstream repository signed every attestation.""" asset = tmp_path / "papaya.zip" with patch("repomatic.release.attestation.run_gh_command") as mock_gh: verify_attestation(asset, "user/repo") mock_gh.assert_called_once_with([ "attestation", "verify", str(asset), "--repo", "user/repo", "--signer-repo", DEFAULT_REPO, ])