Publish curated ITWorx_MCP_Hub source from 7fde2770ba344c684b7e7a6b74f763dc19e7c560
This commit is contained in:
1 parent
18cf749bf5
commit
89703431af
11 files changed
+1247
-114
No files matched your search
@@ -699,6 +699,69 @@ class GiteaConnectorTests(unittest.TestCase):
|
||||
finally:
|
||||
connector_instance.client.close()
|
||||
|
||||
def test_validation_logs_do_not_request_unstarted_jobs(self) -> None:
|
||||
connector_instance, fixture = connector()
|
||||
fixture._action_run.update(status="in_progress", conclusion="")
|
||||
jobs = [
|
||||
{"id": 1, "name": "backend", "status": "in_progress", "conclusion": ""},
|
||||
*[
|
||||
{"id": index, "name": status, "status": status, "conclusion": ""}
|
||||
for index, status in enumerate(("requested", "queued", "waiting", "pending"), 2)
|
||||
],
|
||||
]
|
||||
|
||||
def available_logs(_repository: RegisteredRepository, job_id: int) -> tuple[str, bool]:
|
||||
if job_id != 1:
|
||||
raise GiteaError("GITEA_NOT_FOUND", "Gitea resource was not found")
|
||||
return "checks running", False
|
||||
|
||||
try:
|
||||
with (
|
||||
patch.object(connector_instance.client, "action_run_jobs", return_value=jobs),
|
||||
patch.object(
|
||||
connector_instance.client, "action_job_logs", side_effect=available_logs
|
||||
) as log_read,
|
||||
):
|
||||
result = connector_instance.validation_get(
|
||||
context(), "hub-repository", 42, include_logs=True
|
||||
)
|
||||
projected = result["data"]["run"]["jobs"]
|
||||
self.assertEqual(log_read.call_count, 1)
|
||||
self.assertEqual(log_read.call_args.args[1], 1)
|
||||
self.assertEqual(projected[0]["logs"], "checks running")
|
||||
self.assertFalse(projected[0]["logsUnavailable"])
|
||||
self.assertTrue(all(job["logsUnavailable"] for job in projected[1:]))
|
||||
self.assertTrue(
|
||||
all(job["logs"] == "[job not started; logs unavailable]" for job in projected[1:])
|
||||
)
|
||||
self.assertEqual(result["data"]["run"]["validationState"], "running")
|
||||
self.assertNotIn("fixture-token", json.dumps(result))
|
||||
finally:
|
||||
connector_instance.client.close()
|
||||
|
||||
def test_validation_logs_preserve_started_job_errors(self) -> None:
|
||||
connector_instance, _fixture = connector()
|
||||
job = {"id": 1, "name": "backend", "status": "in_progress", "conclusion": ""}
|
||||
try:
|
||||
for code in ("GITEA_ACCESS_DENIED", "GITEA_UNAVAILABLE"):
|
||||
with (
|
||||
self.subTest(code=code),
|
||||
patch.object(connector_instance.client, "action_run_jobs", return_value=[job]),
|
||||
patch.object(
|
||||
connector_instance.client,
|
||||
"action_job_logs",
|
||||
side_effect=GiteaError(code, "bounded upstream error"),
|
||||
) as log_read,
|
||||
self.assertRaises(GiteaError) as raised,
|
||||
):
|
||||
connector_instance.validation_get(
|
||||
context(), "hub-repository", 42, include_logs=True
|
||||
)
|
||||
self.assertEqual(raised.exception.code, code)
|
||||
self.assertEqual(log_read.call_count, 1)
|
||||
finally:
|
||||
connector_instance.client.close()
|
||||
|
||||
def test_repository_context_stays_below_connector_response_budget(self) -> None:
|
||||
connector_instance, fixture = connector()
|
||||
fixture._file = "R" * 100_000
|
||||
@@ -2028,7 +2091,7 @@ class GiteaConnectorTests(unittest.TestCase):
|
||||
try:
|
||||
current = context()
|
||||
capabilities = connector_instance.capabilities_get(current)["data"]
|
||||
self.assertEqual(fixture.recorded_paths, [])
|
||||
self.assertEqual(fixture.recorded_paths, ["/api/v1/version"])
|
||||
self.assertEqual(capabilities["version"], "1.14.0")
|
||||
self.assertEqual(capabilities["changeBranchPrefixes"], [])
|
||||
self.assertEqual(capabilities["workBranchPolicy"], "any-valid-non-default-branch")
|
||||
@@ -2054,7 +2117,13 @@ class GiteaConnectorTests(unittest.TestCase):
|
||||
self.assertEqual(selftest["mutationsAttempted"], 0)
|
||||
self.assertEqual(
|
||||
{item["name"] for item in selftest["checks"]},
|
||||
{"handler-parity", "gitea-connection", "write-runtime", "managed-actions"},
|
||||
{
|
||||
"handler-parity",
|
||||
"gitea-connection",
|
||||
"write-runtime",
|
||||
"managed-actions",
|
||||
"upstream-merge-compatibility",
|
||||
},
|
||||
)
|
||||
|
||||
repository_health = connector_instance.repository_health(current, "hub-repository")[
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.connector_kit import GiteaError, PageRequest
|
||||
@@ -66,30 +64,17 @@ def test_merge_uses_status_linked_run_when_actions_wrapper_is_empty() -> None:
|
||||
def test_merge_accepts_exact_head_managed_run_when_gitea_omits_workflow_id() -> None:
|
||||
connector_instance, fixture = connector()
|
||||
_add_validated_head_branch(fixture)
|
||||
original_action_runs = connector_instance.client.action_runs
|
||||
|
||||
def action_runs_without_workflow_id(*args, **kwargs):
|
||||
return [
|
||||
{key: value for key, value in run.items() if key != "workflow_id"}
|
||||
for run in original_action_runs(*args, **kwargs)
|
||||
]
|
||||
|
||||
fixture._action_run.pop("workflow_id")
|
||||
try:
|
||||
with patch.object(
|
||||
connector_instance.client,
|
||||
"action_runs",
|
||||
side_effect=action_runs_without_workflow_id,
|
||||
):
|
||||
review = connector_instance.change_review(
|
||||
context(), "hub-repository", "codex/workflows"
|
||||
)["data"]
|
||||
assert review["readyToMerge"] is True
|
||||
assert review["validationRuns"][0]["id"] > 0
|
||||
assert review["validationRuns"][0]["workflowId"] == ""
|
||||
|
||||
merged = connector_instance.pull_request_merge(
|
||||
context(), "hub-repository", 7, "e" * 40, "squash"
|
||||
)["data"]
|
||||
review = connector_instance.change_review(context(), "hub-repository", "codex/workflows")[
|
||||
"data"
|
||||
]
|
||||
assert review["readyToMerge"] is True
|
||||
assert review["validationRuns"][0]["id"] > 0
|
||||
assert review["validationRuns"][0]["workflowId"] == ""
|
||||
merged = connector_instance.pull_request_merge(
|
||||
context(), "hub-repository", 7, "e" * 40, "squash"
|
||||
)["data"]
|
||||
|
||||
assert merged["merged"] is True
|
||||
assert merged["validationRunId"] > 0
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
"""Exact-head native Gitea Actions discovery without synthetic PR proof."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.connector_kit import GiteaError
|
||||
from tests.test_gitea_connector import connector, context
|
||||
|
||||
HEAD = "e" * 40
|
||||
BRANCH = "codex/workflows"
|
||||
|
||||
|
||||
def native_run(**changes):
|
||||
return {
|
||||
"id": 42,
|
||||
"run_number": 17,
|
||||
"run_attempt": 1,
|
||||
"name": None,
|
||||
"workflow_id": None,
|
||||
"display_title": "ci: validate actual commit message",
|
||||
"path": "managed-validation.yml@refs/pull/7/head",
|
||||
"event": "pull_request",
|
||||
"head_branch": "",
|
||||
"head_sha": HEAD,
|
||||
"status": "completed",
|
||||
"conclusion": "success",
|
||||
**changes,
|
||||
}
|
||||
|
||||
|
||||
def setup_native(runs=None):
|
||||
instance, fixture = connector()
|
||||
fixture._branches.append({"name": BRANCH, "commit": {"id": HEAD}, "protected": False})
|
||||
fixture._action_run = native_run()
|
||||
native = runs if runs is not None else [fixture._action_run]
|
||||
original = instance.client._request
|
||||
requests = []
|
||||
|
||||
def request(path, *args, **kwargs):
|
||||
if path.endswith("/actions/runs"):
|
||||
query = args[0] if args else kwargs.get("params", {})
|
||||
requests.append(dict(query))
|
||||
if query.get("branch"):
|
||||
return {"workflow_runs": [], "total_count": 0}
|
||||
assert query.get("head_sha") == HEAD
|
||||
return {"workflow_runs": copy.deepcopy(native), "total_count": len(native)}
|
||||
return original(path, *args, **kwargs)
|
||||
|
||||
instance.client._request = request
|
||||
return instance, fixture, requests
|
||||
|
||||
|
||||
def test_change_status_discovers_original_native_pr_run_with_null_workflow_id():
|
||||
instance, _fixture, requests = setup_native()
|
||||
try:
|
||||
status = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert status["validationRuns"][0]["id"] == 42
|
||||
assert status["validationRuns"][0]["workflowId"] == ""
|
||||
assert status["validationRuns"][0]["workflowPath"] == native_run()["path"]
|
||||
assert status["validationState"] == "succeeded"
|
||||
assert status["readyToMerge"] is True
|
||||
assert "run-validation" not in status["nextActions"]
|
||||
assert requests and all("branch" not in item for item in requests)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_validation_ensure_reuses_bound_native_canonical_pr_run():
|
||||
instance, fixture, requests = setup_native()
|
||||
try:
|
||||
result = instance.pull_request_validation_ensure(context(), "hub-repository", 7, HEAD)[
|
||||
"data"
|
||||
]
|
||||
assert result["state"] == "success"
|
||||
assert result["runId"] == 42
|
||||
assert result["reused"] is True
|
||||
assert requests and all(item.get("head_sha") == HEAD for item in requests)
|
||||
assert "POST" not in fixture.recorded_methods
|
||||
assert "PATCH" not in fixture.recorded_methods
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"changes",
|
||||
[
|
||||
{"path": "managed-validation.yml", "pull_requests": []},
|
||||
{"path": "security.yml@refs/pull/7/head"},
|
||||
{"path": "managed-validation.yml@refs/pull/8/head"},
|
||||
{"pull_requests": [{"number": 8}]},
|
||||
{"workflow_id": "security.yml", "status": "queued", "conclusion": ""},
|
||||
{"path": "security.yml@refs/pull/7/head", "conclusion": "failure"},
|
||||
],
|
||||
)
|
||||
def test_validation_ensure_does_not_reuse_or_rerun_unbound_native_run(changes):
|
||||
instance, fixture, _requests = setup_native([native_run(**changes)])
|
||||
fixture._action_run.update(changes)
|
||||
try:
|
||||
with (
|
||||
patch.object(instance._writer(), "rerun_action_run") as rerun,
|
||||
patch.object(
|
||||
instance._writer(),
|
||||
"close_pull_request",
|
||||
side_effect=GiteaError("GITEA_ACCESS_DENIED", "PR event trigger unavailable"),
|
||||
) as close,
|
||||
pytest.raises(GiteaError) as error,
|
||||
):
|
||||
instance.pull_request_validation_ensure(context(), "hub-repository", 7, HEAD)
|
||||
assert error.value.code == "GITEA_ACCESS_DENIED"
|
||||
close.assert_called_once()
|
||||
rerun.assert_not_called()
|
||||
assert "POST" not in fixture.recorded_methods
|
||||
assert "PATCH" not in fixture.recorded_methods
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"changes",
|
||||
[
|
||||
{"head_sha": "d" * 40},
|
||||
{"head_branch": "other"},
|
||||
{"path": "managed-validation.yml@refs/pull/8/head"},
|
||||
{"pull_requests": [{"number": 8}]},
|
||||
{"pull_requests": [{"number": 7, "head": {"ref": "other", "sha": HEAD}}]},
|
||||
{"pull_requests": [{"number": 7, "head": {"ref": BRANCH, "sha": "d" * 40}}]},
|
||||
{"pull_requests": "malformed"},
|
||||
{"pull_requests": {}},
|
||||
{"pull_requests": [{"number": 7, "head": {"ref": [], "sha": HEAD}}]},
|
||||
{"path": "managed-validation.yml", "pull_requests": []},
|
||||
],
|
||||
)
|
||||
def test_native_exact_head_read_rejects_contradictory_or_unbound_pr_metadata(changes):
|
||||
instance, fixture, _requests = setup_native([native_run(**changes)])
|
||||
fixture._action_run.update(changes)
|
||||
try:
|
||||
repository = instance._repo(context(), "hub-repository")
|
||||
assert (
|
||||
instance.client.exact_head_action_runs(
|
||||
repository,
|
||||
branch=BRANCH,
|
||||
head_sha=HEAD,
|
||||
pull_request_number=7,
|
||||
)
|
||||
== []
|
||||
)
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.pull_request_merge(context(), "hub-repository", 7, HEAD, "squash")
|
||||
assert error.value.code == "GITEA_PR_VALIDATION_REQUIRED"
|
||||
assert not any(body.get("Do") == "squash" for body in fixture.recorded_bodies)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"security.yml@refs/pull/7/head",
|
||||
"untrusted/managed-validation.yml@refs/pull/7/head",
|
||||
"managed-validation.yml@refs/pull/7/head@extra",
|
||||
],
|
||||
)
|
||||
def test_native_workflow_title_cannot_substitute_for_canonical_path(path):
|
||||
instance, fixture, _requests = setup_native([native_run(path=path)])
|
||||
fixture._action_run["path"] = path
|
||||
try:
|
||||
status = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert status["validationRuns"] == []
|
||||
assert status["readyToMerge"] is False
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.pull_request_merge(context(), "hub-repository", 7, HEAD, "squash")
|
||||
assert error.value.code == "GITEA_PR_VALIDATION_REQUIRED"
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_newer_native_failed_attempt_controls_validation_projection():
|
||||
runs = [native_run(), native_run(id=44, run_attempt=2, conclusion="failure")]
|
||||
instance, fixture, _requests = setup_native(runs)
|
||||
fixture._statuses[0]["status"] = "failure"
|
||||
try:
|
||||
status = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert status["validationRuns"][0]["id"] == 44
|
||||
assert status["validationRuns"][0]["runAttempt"] == 2
|
||||
assert status["readyToMerge"] is False
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure", ["duplicate", "empty", "changed", "overflow"])
|
||||
def test_native_inventory_rejects_incomplete_or_nonadvancing_pages(failure):
|
||||
instance, _fixture, _requests = setup_native()
|
||||
original = instance.client._request
|
||||
|
||||
def request(path, *args, **kwargs):
|
||||
if path.endswith("/actions/runs"):
|
||||
query = args[0]
|
||||
if query["page"] == 1:
|
||||
return {"workflow_runs": [native_run()], "total_count": 2}
|
||||
if failure == "duplicate":
|
||||
return {"workflow_runs": [native_run()], "total_count": 2}
|
||||
if failure == "empty":
|
||||
return {"workflow_runs": [], "total_count": 2}
|
||||
if failure == "changed":
|
||||
return {"workflow_runs": [native_run(id=43)], "total_count": 3}
|
||||
return {"workflow_runs": [native_run(id=43), native_run(id=44)], "total_count": 2}
|
||||
return original(path, *args, **kwargs)
|
||||
|
||||
instance.client._request = request
|
||||
try:
|
||||
repository = instance._repo(context(), "hub-repository")
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.client.exact_head_action_runs(
|
||||
repository,
|
||||
branch=BRANCH,
|
||||
head_sha=HEAD,
|
||||
pull_request_number=7,
|
||||
)
|
||||
assert error.value.code in {"GITEA_ACTION_RUNS_INVALID", "GITEA_ACTION_RUNS_TRUNCATED"}
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_validation_get_preserves_skipped_job_metadata_without_missing_logs():
|
||||
instance, _fixture = connector()
|
||||
jobs = [
|
||||
{"id": 43, "name": "preflight", "status": "completed", "conclusion": "failure"},
|
||||
{"id": 44, "name": "build", "status": "completed", "conclusion": "skipped"},
|
||||
]
|
||||
try:
|
||||
with (
|
||||
patch.object(instance.client, "action_run_jobs", return_value=jobs),
|
||||
patch.object(
|
||||
instance.client,
|
||||
"action_job_logs",
|
||||
return_value=("preflight failed", False),
|
||||
) as logs,
|
||||
):
|
||||
result = instance.validation_get(context(), "hub-repository", 42, True)["data"]["run"]
|
||||
assert len(result["jobs"]) == 2
|
||||
assert result["jobs"][1]["conclusion"] == "skipped"
|
||||
assert result["jobs"][1]["logsUnavailable"] is True
|
||||
assert logs.call_count == 1
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_validation_get_marks_expected_missing_logs_without_suppressing_other_errors():
|
||||
instance, _fixture = connector()
|
||||
try:
|
||||
with patch.object(
|
||||
instance.client,
|
||||
"action_job_logs",
|
||||
side_effect=GiteaError(
|
||||
"GITEA_NOT_FOUND",
|
||||
"logs unavailable",
|
||||
),
|
||||
):
|
||||
result = instance.validation_get(context(), "hub-repository", 42, True)["data"]["run"]
|
||||
assert result["jobs"][0]["logsUnavailable"] is True
|
||||
assert result["jobs"][0]["status"] == "completed"
|
||||
with (
|
||||
patch.object(
|
||||
instance.client,
|
||||
"action_job_logs",
|
||||
side_effect=GiteaError(
|
||||
"GITEA_ACCESS_DENIED",
|
||||
"access denied",
|
||||
),
|
||||
),
|
||||
pytest.raises(GiteaError) as error,
|
||||
):
|
||||
instance.validation_get(context(), "hub-repository", 42, True)
|
||||
assert error.value.code == "GITEA_ACCESS_DENIED"
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_legacy_branch_read_prefers_actual_native_run_before_status_fallback():
|
||||
instance, _fixture, _requests = setup_native()
|
||||
try:
|
||||
repository = instance._repo(context(), "hub-repository")
|
||||
runs = instance.client.action_runs(repository, branch=BRANCH, page=1, limit=25)
|
||||
assert len(runs) == 1 and runs[0]["id"] == 42
|
||||
assert "validation_evidence" not in runs[0]
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_native_read_walks_all_actual_pages_and_returns_newest_attempt_first():
|
||||
instance, _fixture, _requests = setup_native()
|
||||
original = instance.client._request
|
||||
seen = []
|
||||
|
||||
def request(path, *args, **kwargs):
|
||||
if path.endswith("/actions/runs"):
|
||||
page = args[0]["page"]
|
||||
seen.append(page)
|
||||
assert kwargs["max_attempts"] == 1 and 0 < kwargs["timeout_seconds"] <= 10
|
||||
return {"workflow_runs": [native_run(id=41 + page, run_attempt=page)], "total_count": 2}
|
||||
return original(path, *args, **kwargs)
|
||||
|
||||
instance.client._request = request
|
||||
try:
|
||||
repository = instance._repo(context(), "hub-repository")
|
||||
runs = instance.client.exact_head_action_runs(
|
||||
repository, branch=BRANCH, head_sha=HEAD, pull_request_number=7
|
||||
)
|
||||
assert seen == [1, 2] and [run["id"] for run in runs] == [43, 42]
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("changes", [{"id": True}, {"run_attempt": 0}, {"run_number": None}])
|
||||
def test_native_inventory_rejects_malformed_run_ordering(changes):
|
||||
instance, _fixture, _requests = setup_native([native_run(**changes)])
|
||||
try:
|
||||
repository = instance._repo(context(), "hub-repository")
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.client.exact_head_action_runs(
|
||||
repository, branch=BRANCH, head_sha=HEAD, pull_request_number=7
|
||||
)
|
||||
assert error.value.code == "GITEA_ACTION_RUNS_INVALID"
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("length", [39, 41, 63, 65])
|
||||
def test_native_read_rejects_non_git_sha_lengths(length):
|
||||
instance, fixture, _requests = setup_native()
|
||||
try:
|
||||
repository = instance._repo(context(), "hub-repository")
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.client.exact_head_action_runs(repository, branch=BRANCH, head_sha="e" * length)
|
||||
assert error.value.code == "GITEA_TARGET_INVALID"
|
||||
assert not fixture.recorded_bodies
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_dispatch_exact_branch_ref_remains_bound_to_original_run():
|
||||
instance, fixture = connector()
|
||||
try:
|
||||
fixture._branches.append({"name": BRANCH, "commit": {"id": HEAD}, "protected": False})
|
||||
dispatched = instance.validation_run(context(), "hub-repository", BRANCH, "full")["data"]
|
||||
assert fixture._action_run["head_branch"] == f"refs/heads/{BRANCH}"
|
||||
result = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert result["validationRuns"][0]["id"] == dispatched["id"] == 42
|
||||
bad = dict(fixture._action_run, head_branch="refs/heads/other")
|
||||
assert not instance.client.matches_exact_head_action_run(bad, branch=BRANCH, head_sha=HEAD)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_blank_native_pr_number_without_workflow_identity_cannot_authorize_merge():
|
||||
run = native_run(path=None, workflow_id=None, pull_requests=[{"number": 7}])
|
||||
instance, fixture, _requests = setup_native([run])
|
||||
fixture._action_run = dict(run)
|
||||
try:
|
||||
result = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert result["validationRuns"] == []
|
||||
assert result["readyToMerge"] is False
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.pull_request_merge(context(), "hub-repository", 7, HEAD, "squash")
|
||||
assert error.value.code == "GITEA_PR_VALIDATION_REQUIRED"
|
||||
assert not any(body.get("Do") == "squash" for body in fixture.recorded_bodies)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_legacy_missing_workflow_identity_requires_exact_expected_branch_for_merge():
|
||||
instance, fixture = connector()
|
||||
fixture._action_run.pop("workflow_id", None)
|
||||
fixture._action_run["head_branch"] = "other"
|
||||
try:
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.pull_request_merge(context(), "hub-repository", 7, HEAD, "squash")
|
||||
assert error.value.code == "GITEA_PR_VALIDATION_REQUIRED"
|
||||
assert not any(body.get("Do") == "squash" for body in fixture.recorded_bodies)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_actual_run_22404_null_head_branch_retains_pr236_original_proof():
|
||||
instance, fixture = connector()
|
||||
actual_head = "c19a8620de6a24241b63aae4dbe52c0e5505aa62"
|
||||
run = native_run(
|
||||
id=22404,
|
||||
head_sha=actual_head,
|
||||
head_branch=None,
|
||||
run_number=1177,
|
||||
path="managed-validation.yml@refs/pull/236/head",
|
||||
)
|
||||
fixture._action_run = run
|
||||
fixture._pulls[0]["number"] = 236
|
||||
fixture._pulls[0]["head"]["sha"] = actual_head
|
||||
fixture._branches.append({"name": BRANCH, "commit": {"id": actual_head}, "protected": False})
|
||||
fixture._statuses[0]["target_url"] = "https://gitea.example/acme/hub/actions/runs/22404"
|
||||
original_request = instance.client._request
|
||||
# The general fake transport serves only PR7; preserve actual PR236 payload.
|
||||
instance.client._request = lambda path, *args, **kwargs: original_request(
|
||||
path.replace("/pulls/236", "/pulls/7"), *args, **kwargs
|
||||
)
|
||||
try:
|
||||
result = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert result["validationRuns"][0]["id"] == 22404
|
||||
assert result["validationRuns"][0]["headBranch"] == ""
|
||||
assert result["validationRuns"][0]["workflowPath"] == run["path"]
|
||||
assert result["readyToMerge"] is True
|
||||
merged = instance.pull_request_merge(
|
||||
context(), "hub-repository", 236, actual_head, "squash"
|
||||
)["data"]
|
||||
assert merged["validationRunId"] == 22404
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("branch_metadata", [None, False, 0, [], {}])
|
||||
def test_native_nonstring_branch_requires_null_pr_shape_and_workflow_identity(branch_metadata):
|
||||
run = native_run(head_branch=branch_metadata, path=None, pull_requests=[{"number": 7}])
|
||||
instance, fixture, _requests = setup_native([run])
|
||||
fixture._action_run = dict(run)
|
||||
try:
|
||||
result = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert result["validationRuns"] == []
|
||||
assert result["readyToMerge"] is False
|
||||
with pytest.raises(GiteaError):
|
||||
instance.pull_request_merge(context(), "hub-repository", 7, HEAD, "squash")
|
||||
assert not any(body.get("Do") == "squash" for body in fixture.recorded_bodies)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_null_native_branch_does_not_hide_failed_required_workflow_context():
|
||||
instance, fixture, _requests = setup_native([native_run(head_branch=None)])
|
||||
fixture._action_run["head_branch"] = None
|
||||
fixture._branch_protections["acme/hub"][0]["status_check_contexts"].append(
|
||||
"Security / scan (pull_request)"
|
||||
)
|
||||
fixture._statuses.append({"context": "Security / scan (pull_request)", "status": "failure"})
|
||||
try:
|
||||
result = instance.change_status(context(), "hub-repository", BRANCH)["data"]
|
||||
assert result["validationRuns"][0]["id"] == 42
|
||||
assert result["readyToMerge"] is False
|
||||
with pytest.raises(GiteaError) as error:
|
||||
instance.pull_request_merge(context(), "hub-repository", 7, HEAD, "squash")
|
||||
assert error.value.code == "GITEA_REQUIRED_CHECK_BLOCKED"
|
||||
assert not any(body.get("Do") == "squash" for body in fixture.recorded_bodies)
|
||||
finally:
|
||||
instance.client.close()
|
||||
|
||||
|
||||
def test_null_branch_is_not_absent_branch_for_push_or_dispatch():
|
||||
instance, fixture = connector()
|
||||
try:
|
||||
for event in ("push", "workflow_dispatch"):
|
||||
assert not instance.client.matches_exact_head_action_run(
|
||||
native_run(head_branch=None, event=event), branch=BRANCH, head_sha=HEAD
|
||||
)
|
||||
finally:
|
||||
instance.client.close()
|
||||
@@ -0,0 +1,230 @@
|
||||
"""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()
|
||||
@@ -9,8 +9,9 @@ def test_managed_validation_uses_resolvable_setup_dotnet_v4_pin() -> None:
|
||||
assert "actions/setup-dotnet@67a3573c9a9863f9c594539f4ab511d57bb3ce9" not in workflow
|
||||
|
||||
|
||||
def test_managed_validation_uses_internal_gitea_for_checkout() -> None:
|
||||
def test_managed_validation_uses_canonical_https_gitea_for_checkout() -> None:
|
||||
with open(".gitea/workflows/managed-validation.yml", encoding="utf-8") as workflow_file:
|
||||
workflow = workflow_file.read()
|
||||
|
||||
assert "github-server-url: http://gitea-runner-internal:3000" in workflow
|
||||
assert workflow.count("github-server-url: https://gitea.example.com") == 2
|
||||
assert "http://gitea-runner-internal:3000" not in workflow
|
||||
@@ -16,6 +16,22 @@ def release_job() -> dict:
|
||||
return workflow["jobs"]["release-images"]
|
||||
|
||||
|
||||
def test_all_checkouts_use_the_canonical_authenticated_https_origin() -> None:
|
||||
workflow = yaml.safe_load(
|
||||
(ROOT / ".gitea/workflows/managed-validation.yml").read_text(encoding="utf-8")
|
||||
)
|
||||
checkouts = [
|
||||
step
|
||||
for job in workflow["jobs"].values()
|
||||
for step in job["steps"]
|
||||
if step.get("uses") == CHECKOUT
|
||||
]
|
||||
assert len(checkouts) == 2
|
||||
for checkout in checkouts:
|
||||
assert checkout["with"]["github-server-url"] == "https://gitea.example.com"
|
||||
assert "token" not in checkout["with"]
|
||||
|
||||
|
||||
def test_release_build_requires_successful_validation_and_exact_main_push() -> None:
|
||||
job = release_job()
|
||||
assert job["name"] == "release-images"
|
||||
|
||||
Reference in new issue
Block a user