"""Version negotiation canary: read-only diagnostics and fail-closed merge gates.""" from __future__ import annotations import json from unittest.mock import patch import pytest from connectors.gitea import FakeGiteaServer from packages.connector_kit import GiteaError from tests.test_gitea_connector import connector, context from tests.test_gitea_merge_version_compat import _client @pytest.mark.parametrize( ("version", "normalized", "field"), [ ("28.0.0", "28.0.0", "do"), (" v28.0.0+gitea.1 ", "28.0.0", "do"), ("v1.25.5+vendor.7", "1.25.5", "Do"), ("1.26.4", "1.26.4", "do"), ("1.27.2", "1.27.2", "do"), ], ) def test_upstream_discovery_normalizes_verified_release(version, normalized, field) -> None: client, fixture, _ = _client(version) try: diagnostics = client.upstream_compatibility() assert diagnostics["upstreamVersion"] == version.strip().split("+", 1)[0] assert diagnostics["normalizedVersion"] == normalized assert diagnostics["compatibility"] == "SUPPORTED" assert diagnostics["mergeStrategyField"] == field assert diagnostics["source"] == "/api/v1/version" assert fixture.recorded_methods == ["GET"] finally: client.close() @pytest.mark.parametrize( "metadata", [ "AKIA" + "A" * 16, "glpat-" + "x" * 24, "eyJ" + "a" * 15 + "." + "b" * 15 + "." + "c" * 15, "fixture-token", "a1" * 20, ], ) @pytest.mark.parametrize("separator", ["+", "-"]) def test_version_diagnostics_exclude_secret_shaped_metadata(metadata, separator): instance, fixture = connector(FakeGiteaServer(gitea_version="28.0.0" + separator + metadata)) try: data = instance.capabilities_get(context())["data"] diagnostics = data["upstreamGitea"] assert metadata not in json.dumps(data) assert diagnostics["upstreamVersion"] == "28.0.0" assert diagnostics["normalizedVersion"] == "28.0.0" assert diagnostics["compatibility"] == ("SUPPORTED" if separator == "+" else "UNSUPPORTED") assert "POST" not in fixture.recorded_methods finally: instance.client.close() @pytest.mark.parametrize( ("version", "verdict"), [ ("1.24.9", "UNSUPPORTED"), ("1.28.0", "UNSUPPORTED"), ("2.0.0", "UNSUPPORTED"), ("29.0.0", "UNSUPPORTED"), ("28.0.0-rc.1", "UNSUPPORTED"), ("", "UNKNOWN"), ("garbage", "UNKNOWN"), ("28.0.0+", "UNKNOWN"), ("28.0.0-", "UNKNOWN"), ("28.00.0", "UNKNOWN"), ("28.0.0\nforged", "UNKNOWN"), ], ) def test_unknown_or_untested_upstream_fails_before_write(version, verdict) -> None: client, fixture, repository = _client(version) try: diagnostics = client.upstream_compatibility() assert diagnostics["compatibility"] == verdict assert diagnostics["mergeStrategyField"] is None with pytest.raises(GiteaError) as raised: client.merge_pull_request(repository, 7, method="squash", head_commit_id="e" * 40) assert raised.value.code == "GITEA_VERSION_UNSUPPORTED" assert "POST" not in fixture.recorded_methods finally: client.close() @pytest.mark.parametrize("payload", [{}, {"version": None}, {"version": 28}, []]) def test_missing_or_non_string_upstream_version_cannot_fall_back_to_connector(payload) -> None: instance, fixture = connector() try: with patch.object(instance.client, "_request", return_value=payload): data = instance.capabilities_get(context())["data"] assert data["connectorVersion"] == "1.14.0" assert data["upstreamGitea"]["compatibility"] == "UNKNOWN" assert data["upstreamGitea"]["upstreamVersion"] is None assert data["subsystems"]["upstream_gitea"] == "UNVERIFIED" assert data["subsystems"]["merge"] == "UNAVAILABLE" assert fixture.recorded_methods == [] finally: instance.client.close() def test_connector_and_server_versions_are_distinct_in_read_only_canary() -> None: instance, fixture = connector(FakeGiteaServer(gitea_version="28.0.0")) try: data = instance.capabilities_get(context())["data"] assert data["version"] == data["connectorVersion"] == "1.14.0" assert data["upstreamGitea"]["upstreamVersion"] == "28.0.0" assert data["upstreamGitea"]["compatibility"] == "SUPPORTED" assert data["subsystems"]["upstream_gitea"] == "READY" assert data["subsystems"]["merge"] == "READY" canary = instance.selftest_run(context())["data"] assert canary["status"] == "PASS" assert any(item["name"] == "upstream-merge-compatibility" for item in canary["checks"]) assert "POST" not in fixture.recorded_methods finally: instance.client.close() def test_diagnostics_cache_expires_and_rechecks_after_dependency_recovery() -> None: client, fixture, _ = _client("28.0.0") try: with patch("packages.connector_kit.gitea.time.monotonic", return_value=100): assert client.upstream_compatibility()["compatibility"] == "SUPPORTED" fixture.gitea_version = "29.0.0" with patch("packages.connector_kit.gitea.time.monotonic", return_value=120): assert client.upstream_compatibility()["compatibility"] == "SUPPORTED" with patch("packages.connector_kit.gitea.time.monotonic", return_value=131): assert client.upstream_compatibility()["compatibility"] == "UNSUPPORTED" fixture.gitea_version = "28.0.0" with patch("packages.connector_kit.gitea.time.monotonic", return_value=162): assert client.upstream_compatibility()["compatibility"] == "SUPPORTED" assert fixture.recorded_paths.count("/api/v1/version") == 3 finally: client.close() def test_merge_rechecks_server_even_with_cached_successful_diagnostics() -> None: client, fixture, repository = _client("28.0.0") try: assert client.upstream_compatibility()["compatibility"] == "SUPPORTED" fixture.gitea_version = "29.0.0" with pytest.raises(GiteaError) as raised: client.merge_pull_request(repository, 7, method="squash", head_commit_id="e" * 40) assert raised.value.code == "GITEA_VERSION_UNSUPPORTED" assert "POST" not in fixture.recorded_methods assert fixture.recorded_paths.count("/api/v1/version") == 2 finally: client.close() def test_version_outage_invalidates_cached_success_and_recovers() -> None: client, fixture, repository = _client("28.0.0") try: assert client.upstream_compatibility()["compatibility"] == "SUPPORTED" with patch.object( client, "_request", side_effect=GiteaError("GITEA_UNAVAILABLE", "upstream request unavailable"), ): with pytest.raises(GiteaError) as raised: client.merge_pull_request(repository, 7, method="squash", head_commit_id="e" * 40) assert raised.value.code == "GITEA_UNAVAILABLE" assert client.upstream_compatibility()["compatibility"] == "UNAVAILABLE" assert "POST" not in fixture.recorded_methods assert client.upstream_compatibility(force_refresh=True)["compatibility"] == "SUPPORTED" finally: client.close() def test_readiness_reports_failed_version_probe_without_disabling_read_service() -> None: instance, _ = connector() original_request = instance.client._request def request(path, *args, **kwargs): if path == "version": raise GiteaError("GITEA_UNAVAILABLE", "upstream request unavailable") return original_request(path, *args, **kwargs) try: with patch.object(instance.client, "_request", side_effect=request): data = instance.readiness() assert data["status"] == "ready" assert data["upstream"] == "unavailable" assert data["subsystems"]["upstream_gitea"] == "UNAVAILABLE" assert data["subsystems"]["merge"] == "UNAVAILABLE" assert data["upstreamGitea"]["errorCode"] == "GITEA_UNAVAILABLE" finally: instance.client.close() @pytest.mark.parametrize("mutation", ["wrong-head", "wrong-event", "failed"]) def test_current_version_still_requires_exact_successful_pr_validation(mutation) -> None: instance, fixture = connector(FakeGiteaServer(gitea_version="28.0.0")) if mutation == "wrong-head": fixture._action_run["head_sha"] = "d" * 40 elif mutation == "wrong-event": fixture._action_run["event"] = "workflow_dispatch" else: fixture._action_run["conclusion"] = "failure" try: with pytest.raises(GiteaError): instance.pull_request_merge(context(), "hub-repository", 7, "e" * 40, "squash") assert "/api/v1/repos/acme/hub/pulls/7/merge" not in fixture.recorded_paths assert fixture._pulls[0].get("merged") is not True finally: instance.client.close() def test_current_release_allows_only_sha_locked_non_forced_merge() -> None: instance, fixture = connector(FakeGiteaServer(gitea_version="28.0.0")) try: result = instance.pull_request_merge(context(), "hub-repository", 7, "e" * 40, "squash")[ "data" ] assert result["validationRunId"] == 42 body = next(body for body in fixture.recorded_bodies if body.get("do") == "squash") assert body["head_commit_id"] == "e" * 40 assert body["force_merge"] is False assert body["merge_when_checks_succeed"] is False finally: instance.client.close()