"""Behavioral tests for the exact protected-run release artifact payload.""" from __future__ import annotations import copy import hashlib import json from pathlib import Path import pytest from scripts import package_mcp_hub_release_artifact as packager SERVICES = ( "connector-appops", "connector-gitea", "connector-ingestion", "connector-knowledge", "connector-mobilityops", "connector-projectbrain", "connector-stitch", "connector-unraid", "contextforge", "control-api", "dataplane-router", "docker-read-broker", "ingestion-clamd", "postgres", "redis", "web", "worker", ) REPORT_NAMES = { "trivy-db-metadata.json", *( name for service in SERVICES for name in (f"vulnerability-{service}.json", f"secret-{service}.json") ), } REVISION = "a" * 40 TREE = "b" * 40 RUN_CREATED_AT = "2026-10-03T10:11:12.123Z" def _json_bytes(value: object) -> bytes: return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode() def _fixture(tmp_path: Path) -> tuple[dict[str, object], dict[str, dict[str, str]], Path, Path]: image_tar = tmp_path / "inputs" / "images.tar" report_dir = tmp_path / "inputs" / "reports" report_dir.mkdir(parents=True) image_tar.parent.mkdir(parents=True, exist_ok=True) image_tar.write_bytes(b"fixture-docker-save-archive\n") images: dict[str, dict[str, str]] = {} report_time = "2026-10-03T10:12:00Z" for index, service in enumerate(SERVICES, start=1): image_id = f"sha256:{index:064x}" image_ref = f"registry.example/itworx/{service}:protected-main@sha256:{index:064x}" images[service] = { "imageRef": image_ref, "imageId": image_id, "archiveRef": f"registry.example/itworx/{service}:archive-{REVISION[:12]}", } vulnerability = { "SchemaVersion": 2, "CreatedAt": report_time, "Trivy": {"Version": "0.73.0"}, "Metadata": { "ImageID": image_id, "ImageConfig": {"os": "linux", "architecture": "amd64"}, }, "Results": [ { "Packages": [{"Name": "fixture-package", "Version": "1.0"}], "Vulnerabilities": [], } ], } secret = { "ImageID": image_id, "CreatedAt": report_time, "TrivyVersion": "0.73.0", "FindingCount": 0, "SensitiveFindingDetailsPersisted": False, "Results": [{"FindingCount": 0}], } (report_dir / f"vulnerability-{service}.json").write_bytes(_json_bytes(vulnerability)) (report_dir / f"secret-{service}.json").write_bytes(_json_bytes(secret)) database = { "UpdatedAt": report_time, "DownloadedAt": report_time, "Version": 2, "NextUpdate": "2026-10-04T10:12:00Z", } (report_dir / "trivy-db-metadata.json").write_bytes(_json_bytes(database)) build = { "id": REVISION[:12], "repository": "jens/itworx_mcp_hub", "revision": REVISION, "tree": TREE, "treeDigest": hashlib.sha256(f"git-tree:{TREE}".encode()).hexdigest(), "builtAt": RUN_CREATED_AT, } context: dict[str, object] = { "repository": "jens/itworx_mcp_hub", "revision": REVISION, "tree": TREE, "workflowPath": ".gitea/workflows/managed-validation.yml", "workflowRunId": 2468, "workflowRunCreatedAt": RUN_CREATED_AT, "artifactName": f"hub-release-candidate-{REVISION}", "build": build, "sourceDateEpoch": "1791022272", } return context, images, image_tar, report_dir def _package( output_dir: Path, context: dict[str, object], images: dict[str, dict[str, str]], image_tar: Path, report_dir: Path, ) -> dict[str, object]: return packager.package_candidate_artifact( output_dir=output_dir, manifest_context=context, images=images, image_tar_path=image_tar, report_dir=report_dir, ) @pytest.fixture(autouse=True) def _bind_fixture_to_test_git(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( packager, "_git_source_identity", lambda: (REVISION, TREE, "1791022272"), ) def test_package_contains_exact_importer_payload_and_binds_original_bytes(tmp_path: Path) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) output = tmp_path / "artifact" returned_manifest = _package(output, context, images, image_tar, report_dir) expected_names = {"candidate-manifest.json", "images.tar", *REPORT_NAMES} assert {path.name for path in output.iterdir()} == expected_names assert all(path.is_file() and not path.is_symlink() for path in output.iterdir()) manifest_bytes = (output / "candidate-manifest.json").read_bytes() manifest = json.loads(manifest_bytes) tar_bytes = image_tar.read_bytes() assert manifest == returned_manifest assert set(manifest) == { "schema", "repository", "sourceRevision", "sourceTree", "workflowPath", "workflowRunId", "artifactName", "build", "sourceDateEpoch", "dockerTarBytes", "dockerTarSha256", "images", "reports", } assert manifest["schema"] == "itworx.mcp-hub-image-artifact/v1" assert manifest["repository"] == context["repository"] assert manifest["sourceRevision"] == context["revision"] assert manifest["sourceTree"] == context["tree"] assert manifest["workflowPath"] == context["workflowPath"] assert manifest["workflowRunId"] == context["workflowRunId"] assert manifest["artifactName"] == context["artifactName"] assert manifest["build"] == context["build"] assert manifest["sourceDateEpoch"] == context["sourceDateEpoch"] assert manifest["dockerTarBytes"] == len(tar_bytes) assert manifest["dockerTarSha256"] == hashlib.sha256(tar_bytes).hexdigest() assert manifest["images"] == images assert manifest["reports"] == { name: { "sha256": hashlib.sha256((report_dir / name).read_bytes()).hexdigest(), "bytes": (report_dir / name).stat().st_size, } for name in sorted(REPORT_NAMES) } assert (output / "images.tar").read_bytes() == tar_bytes for name in REPORT_NAMES: assert (output / name).read_bytes() == (report_dir / name).read_bytes() def test_canonicalizes_the_exact_workflow_run_timestamp_to_importer_format( tmp_path: Path, ) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) context["workflowRunCreatedAt"] = "2026-10-03T10:11:12Z" build = dict(context["build"]) build["builtAt"] = "2026-10-03T10:11:12.000Z" context["build"] = build manifest = _package(tmp_path / "artifact", context, images, image_tar, report_dir) assert manifest["build"]["builtAt"] == "2026-10-03T10:11:12.000Z" @pytest.mark.parametrize( ("mutation", "value"), [ ("revision", "c" * 40), ("tree", "d" * 40), ("run_id", 0), ("run_time", "2026-10-03T10:11:12Z"), ("artifact_name", "hub-release-candidate-wrong"), ("epoch", "01791022272"), ], ) def test_rejects_unbound_frozen_context_without_publishing( tmp_path: Path, mutation: str, value: object ) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) context = copy.deepcopy(context) if mutation == "revision": context["revision"] = value elif mutation == "tree": context["tree"] = value elif mutation == "run_id": context["workflowRunId"] = value elif mutation == "run_time": context["workflowRunCreatedAt"] = value elif mutation == "artifact_name": context["artifactName"] = value else: context["sourceDateEpoch"] = value output = tmp_path / "artifact" with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert not output.exists() assert not list(tmp_path.glob(".hub-release-artifact-*")) @pytest.mark.parametrize("report_change", ["missing", "extra", "oversized", "wrong_image"]) def test_rejects_invalid_fixed_report_inventory_without_partial_output( tmp_path: Path, report_change: str ) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) if report_change == "missing": (report_dir / "secret-worker.json").unlink() elif report_change == "extra": (report_dir / "unlisted.json").write_text("{}", encoding="utf-8") elif report_change == "oversized": (report_dir / "secret-worker.json").write_bytes(b" " * (2 * 1024**2 + 1)) else: data = json.loads((report_dir / "vulnerability-worker.json").read_text(encoding="utf-8")) data["Metadata"]["ImageID"] = "sha256:" + "f" * 64 (report_dir / "vulnerability-worker.json").write_bytes(_json_bytes(data)) output = tmp_path / "artifact" with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert not output.exists() def test_rejects_secret_findings_instead_of_redacting_or_rewriting_them(tmp_path: Path) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) secret_path = report_dir / "secret-worker.json" secret = json.loads(secret_path.read_text(encoding="utf-8")) secret["FindingCount"] = 1 secret["SensitiveFindingDetailsPersisted"] = True secret_path.write_bytes(_json_bytes(secret)) original = secret_path.read_bytes() output = tmp_path / "artifact" with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert not output.exists() assert secret_path.read_bytes() == original def test_rejects_output_collision_without_overwriting_existing_files(tmp_path: Path) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) output = tmp_path / "artifact" output.mkdir() marker = output / "keep.txt" marker.write_text("preserve", encoding="utf-8") with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert marker.read_text(encoding="utf-8") == "preserve" assert {path.name for path in output.iterdir()} == {"keep.txt"} def test_rejects_output_directory_created_at_atomic_publish_boundary( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) output = tmp_path / "artifact" original_publish = packager._rename_noreplace def create_collision_then_publish(source: Path, destination: Path) -> None: destination.mkdir() original_publish(source, destination) monkeypatch.setattr(packager, "_rename_noreplace", create_collision_then_publish) with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert output.is_dir() assert list(output.iterdir()) == [] assert not list(tmp_path.glob(".hub-release-artifact-*")) def test_rejects_wrong_image_inventory_without_partial_output(tmp_path: Path) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) images.pop("worker") output = tmp_path / "artifact" with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert not output.exists() def test_rejects_source_identity_that_does_not_match_checkout( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) monkeypatch.setattr( packager, "_git_source_identity", lambda: ("f" * 40, TREE, "1791022272"), ) output = tmp_path / "artifact" with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert not output.exists() def test_rejects_hardlinked_report_without_publishing(tmp_path: Path) -> None: context, images, image_tar, report_dir = _fixture(tmp_path) secret_path = report_dir / "secret-worker.json" secret_path.unlink() try: secret_path.hardlink_to(report_dir / "secret-web.json") except OSError as exc: pytest.skip(f"filesystem does not support the hard-link fixture: {exc}") output = tmp_path / "artifact" with pytest.raises(packager.ArtifactRejected): _package(output, context, images, image_tar, report_dir) assert not output.exists()