Publish curated ITWorx_MCP_Hub source from 7fde2770ba344c684b7e7a6b74f763dc19e7c560

This commit is contained in:
Public Source Publisher committed 2026-10-14 00:05:58 +00:00
1 parent 18cf749bf5
commit 89703431af
11 files changed
+1247 -114

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export
This source snapshot was generated from the private canonical repository at revision `0253e7acef01ad952266ff180ed434e22b986ddc`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
This source snapshot was generated from the private canonical repository at revision `7fde2770ba344c684b7e7a6b74f763dc19e7c560`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+25 -15
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"sourceRevision": "0253e7acef01ad952266ff180ed434e22b986ddc",
"sourceRevision": "7fde2770ba344c684b7e7a6b74f763dc19e7c560",
"files": [
{
"path": ".dockerignore",
@@ -1759,8 +1759,8 @@
},
{
"path": "connectors/gitea/server.py",
"sha256": "4a07c6171893742d7a25e5cbdaa29eef438fc2d381ae93249a4bb6c1be493421",
"bytes": 228331
"sha256": "bf1219aa38d650c5821167511137e9c6e27e21714436b430782f1c75418db441",
"bytes": 232805
},
{
"path": "connectors/knowledge/__init__.py",
@@ -2169,13 +2169,13 @@
},
{
"path": "packages/connector_kit/gitea.py",
"sha256": "118e657d06142f71db40741b08d25504e30f13754bf0f67fc5f45f865cb41e2e",
"bytes": 50270
"sha256": "fbc116935c7221ffcad4a41fddfa9c40a21a725364f8fa8bb52d9ef2ea9db333",
"bytes": 54778
},
{
"path": "packages/connector_kit/gitea_resilient.py",
"sha256": "78e6f6a43023721c686630f39107bda92b5c4cc77a978ed5bf5a105247abe72f",
"bytes": 4442
"sha256": "83a27fdb883f25ab17d0ebea9f41c9fe61679cd2bfeea4535abdd057a17d1ab9",
"bytes": 12348
},
{
"path": "packages/connector_kit/knowledge.py",
@@ -3239,8 +3239,8 @@
},
{
"path": "tests/test_gitea_connector.py",
"sha256": "4fcd0e2a2aa822ca24958d3dcae2eb11f0c2c223da3fbdcc763631a57734b29f",
"bytes": 135127
"sha256": "fe6d6d9e83f5f96239f22653e4f1815fe824cffdef2e7f8c26c52df61aa6fc1c",
"bytes": 138362
},
{
"path": "tests/test_gitea_merge_eligibility.py",
@@ -3254,14 +3254,19 @@
},
{
"path": "tests/test_gitea_merge_validation_fallback.py",
"sha256": "44a7825b2c9b591cb4c64f8dec539ed7c36298bdf7b2418e2855e56e2496a84d",
"bytes": 7467
"sha256": "9ce7be992b4878209ee232ec1cf30681059284128a713088de47f2fa22c08b5c",
"bytes": 6987
},
{
"path": "tests/test_gitea_merge_version_compat.py",
"sha256": "8ae5c98a3056d2e1afe9c50fbd7b39f019e31cce744c21d89feeb513668d024e",
"bytes": 2717
},
{
"path": "tests/test_gitea_native_pr_discovery.py",
"sha256": "46249e45eaa7bdd3810f803cd8ab8416983e442d44f40bbf5289ba2925fe448c",
"bytes": 18959
},
{
"path": "tests/test_gitea_pr_status_fallback.py",
"sha256": "7cc54cfb5172b657613fe26da1f08518eb43c79f7c180e264f8f263a9b80b5ee",
@@ -3277,6 +3282,11 @@
"sha256": "0b8357ea0a39319d80f5bcac8250de806866667329872d17429f2565cba6d8e0",
"bytes": 1861
},
{
"path": "tests/test_gitea_upstream_discovery.py",
"sha256": "184cd84aa7647989893c3f62b96bcd1df82a70c3fa8daced33532ded9aab38af",
"bytes": 9661
},
{
"path": "tests/test_gitea_validation_dedup.py",
"sha256": "8e29587c67183aaf5abdd5851b0b2a283074ecb33e953ebd75c7dcfce9b171d4",
@@ -3334,8 +3344,8 @@
},
{
"path": "tests/test_managed_validation_action_pins.py",
"sha256": "8e4d3e81862570d281259b6eabadbe9568d2d48e56c240b1fa1da6a73efd3eb4",
"bytes": 721
"sha256": "e80aeed714214a611399d703dc72a114ea9dde987ce36e7e821768ae5cc6d327",
"bytes": 792
},
{
"path": "tests/test_mcp_hub_ingestion_publisher.py",
@@ -3424,8 +3434,8 @@
},
{
"path": "tests/test_release_images_workflow.py",
"sha256": "92e1e03d71aaab8ef2f31eb6643d42b9e6189b9ccca86f1f0b55425d841512c2",
"bytes": 2510
"sha256": "055e9adb93816464381e4de32b0c11e86d54555606a17041ba1ee52f36d73487",
"bytes": 3063
},
{
"path": "tests/test_stitch_connector.py",
+148 -49
View File
@@ -1900,9 +1900,12 @@ class GiteaConnector:
"status": str(item.get("status", ""))[:32],
"conclusion": str(item.get("conclusion", ""))[:32],
"event": str(item.get("event", ""))[:64],
"headBranch": str(item.get("head_branch", ""))[:300],
"headBranch": str("" if item.get("head_branch") is None else item["head_branch"])[:300],
"headSha": str(item.get("head_sha", ""))[:64],
"workflowId": str(item.get("workflow_id", ""))[:2_048],
"workflowId": str(item.get("workflow_id") or "")[:2_048],
"workflowPath": str(item.get("path") or "")[:2_048],
"runNumber": max(int(item.get("run_number", item.get("id", 0))), 0),
"runAttempt": max(int(item.get("run_attempt", 0)), 0),
"displayTitle": safe_snippet(str(item.get("display_title", "")), max_bytes=500),
"createdAt": str(item.get("created_at", ""))[:64],
"updatedAt": str(item.get("updated_at", ""))[:64],
@@ -1932,14 +1935,33 @@ class GiteaConnector:
identities = cls._workflow_identity_values(workflow)
if not identities:
return []
if not any(str(item.get("workflow_id", "")).strip() for item in runs):
return runs if allow_legacy_without_workflow_id else []
return [
item
for item in runs
if str(item.get("workflow_id", "")).strip().casefold() in identities
or str(item.get("workflow_id", "")).strip().casefold().rsplit("/", 1)[-1] in identities
]
output: list[dict[str, Any]] = []
for item in runs:
raw_path = item.get("path")
workflow_id = str(item.get("workflow_id") or "").strip().casefold()
if raw_path is not None:
if not isinstance(raw_path, str) or not raw_path or len(raw_path) > 512:
continue
parts = raw_path.split("@")
if len(parts) > 2 or parts[0].casefold() not in identities:
continue
if len(parts) == 2 and not re.fullmatch(
r"refs/(?:pull/[1-9][0-9]*/head|heads/[A-Za-z0-9][A-Za-z0-9._/-]*)",
parts[1],
):
continue
elif not workflow_id:
if (
allow_legacy_without_workflow_id
and isinstance(item.get("head_branch"), str)
and bool(item["head_branch"])
):
output.append(item)
continue
if workflow_id and workflow_id not in identities:
continue
output.append(item)
return output
def _find_reusable_validation_run(
self,
@@ -1994,10 +2016,29 @@ class GiteaConnector:
job_id = max(int(job.get("id", 0)), 0)
logs = ""
logs_truncated = False
logs_unavailable = False
if include_logs and job_id:
logs, logs_truncated = self.client.action_job_logs(repository, job_id)
filtered_logs = safe_content(logs)
logs = filtered_logs or "[log excluded: secret-like content detected]"
if str(job.get("status", "")).casefold() in {
"requested",
"queued",
"waiting",
"pending",
}:
# Gitea returns 404 before a job has an effective task/log.
logs = "[job not started; logs unavailable]"
logs_unavailable = True
elif job.get("conclusion") == "skipped" and not job.get("started_at"):
logs_unavailable = True
else:
try:
logs, logs_truncated = self.client.action_job_logs(repository, job_id)
except GiteaError as exc:
if exc.code != "GITEA_NOT_FOUND":
raise
logs_unavailable = True
else:
filtered_logs = safe_content(logs)
logs = filtered_logs or "[log excluded: secret-like content detected]"
projected_jobs.append(
{
"id": job_id,
@@ -2008,6 +2049,7 @@ class GiteaConnector:
"completedAt": str(job.get("completed_at", ""))[:64],
"logs": logs,
"logsTruncated": logs_truncated,
"logsUnavailable": logs_unavailable,
}
)
any_truncated = any_truncated or logs_truncated
@@ -2182,12 +2224,41 @@ class GiteaConnector:
None,
)
workflow = _select_validation_workflow(self.client.action_workflows(repository))
commit_sha = str((branch_payload.get("commit") or {}).get("id", ""))[:64]
runs = self._runs_for_workflow(
self.client.action_runs(repository, branch=safe_branch, page=1, limit=100), workflow
self.client.exact_head_action_runs(
repository,
branch=safe_branch,
head_sha=commit_sha,
pull_request_number=int(pull["number"]) if pull is not None else None,
),
workflow,
)
if not runs:
# Preserve honest stale branch history, never synthetic or current
# contradictory PR evidence, when the exact-head inventory is empty.
history = self.client.action_runs(repository, branch=safe_branch, page=1, limit=100)
history = [
item
for item in history
if (
type(item.get("id")) is int
and item["id"] > 0
and item.get("head_branch") == safe_branch
and isinstance(item.get("head_sha"), str)
and re.fullmatch(r"(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})", item["head_sha"])
and item["head_sha"] != commit_sha
and self.client.matches_exact_head_action_run(
item,
branch=safe_branch,
head_sha=item["head_sha"],
pull_request_number=int(pull["number"]) if pull is not None else None,
)
)
]
runs = self._runs_for_workflow(history, workflow)
projected_runs = [self._action_run_projection(item) for item in runs]
latest_run = projected_runs[0] if projected_runs else None
commit_sha = str((branch_payload.get("commit") or {}).get("id", ""))[:64]
merge_eligibility: dict[str, Any] = {
"eligible": False,
"headSha": commit_sha,
@@ -2443,9 +2514,13 @@ class GiteaConnector:
published_names = {str(item["name"]) for item in descriptors}
handler_names = set(self._tool_handlers())
runtime = runtime_snapshot()
upstream = (self.write_client or self.client).upstream_compatibility()
merge_supported = upstream["compatibility"] == "SUPPORTED"
return {
"server": "itworx-gitea",
"version": self.version,
"connectorVersion": self.version,
"upstreamGitea": upstream,
"buildSha": os.environ.get("GITEA_CONNECTOR_BUILD_SHA", "unknown")[:64],
"capabilityFingerprint": hashlib.sha256(encoded).hexdigest(),
"toolCount": len(publications),
@@ -2470,7 +2545,14 @@ class GiteaConnector:
"catalog": "READY" if published_names == handler_names else "DEGRADED",
"dispatcher": "READY",
"worker": "READY" if runtime["circuitState"] == "CLOSED" else "DEGRADED",
"upstream_gitea": "UNVERIFIED",
"upstream_gitea": (
"READY"
if merge_supported
else "UNAVAILABLE"
if upstream["compatibility"] == "UNAVAILABLE"
else "UNVERIFIED"
),
"merge": "READY" if merge_supported and self.write_client else "UNAVAILABLE",
"search": "READY",
"write": "READY" if self.write_client is not None else "DISABLED",
"actions": "READY" if self.write_client is not None else "READ_ONLY",
@@ -2506,6 +2588,8 @@ class GiteaConnector:
"capabilityFingerprint": capability["capabilityFingerprint"],
"toolCount": capability["toolCount"],
"credentialMode": "mounted-secret",
"connectorVersion": capability["connectorVersion"],
"upstreamGitea": capability["upstreamGitea"],
},
source="gitea:connection",
)
@@ -2533,6 +2617,13 @@ class GiteaConnector:
"status": "PASS" if connection["connected"] else "FAIL",
"repositoryCount": connection["repositoryCount"],
},
{
"name": "upstream-merge-compatibility",
"status": "PASS"
if capability["upstreamGitea"]["compatibility"] == "SUPPORTED"
else "FAIL",
"diagnostics": capability["upstreamGitea"],
},
{
"name": "write-runtime",
"status": "PASS" if self.write_client is not None else "DISABLED",
@@ -3930,32 +4021,24 @@ class GiteaConnector:
):
raise GiteaError("GITEA_PROTECTION_DRIFT", "canonical PR check is not required")
workflow = _select_validation_workflow(self.client.action_workflows(repository))
safe_branch = _validate_ref(str(head.get("ref", "")))
def exact_runs() -> list[dict[str, Any]]:
runs: list[dict[str, Any]] = []
for page in range(1, 6):
batch = self.client.repository_action_runs(repository, page=page, limit=100)
runs.extend(batch)
if len(batch) < 100:
break
return sorted(
(
self._action_run_projection(item)
for item in runs
if str(item.get("event", "")) == "pull_request"
and str(item.get("head_sha", "")) == expected_head_sha
and (
not isinstance(item.get("pull_requests"), list)
or not item["pull_requests"]
or any(
int(pr.get("number", 0)) == number
for pr in item["pull_requests"]
if isinstance(pr, dict)
)
)
runs = self._runs_for_workflow(
self.client.exact_head_action_runs(
repository,
branch=safe_branch,
head_sha=expected_head_sha,
pull_request_number=number,
),
key=lambda item: int(item.get("id", 0)),
reverse=True,
workflow,
)
return [
self._action_run_projection(item)
for item in runs
if item.get("event") == "pull_request"
]
runs = exact_runs()
statuses = self.client.commit_statuses(repository, expected_head_sha, 1, 100)
@@ -4337,10 +4420,16 @@ class GiteaConnector:
"GITEA_PR_VALIDATION_REQUIRED",
"the required status has no verifiable canonical validation run",
)
run = self._action_run_projection(self.client.action_run(repository, int(run_match[1])))
raw_run = self.client.action_run(repository, int(run_match[1]))
run = self._action_run_projection(raw_run)
workflow = _select_validation_workflow(self.client.action_workflows(repository))
workflow_id = str(run.get("workflowId", "")).strip().casefold()
identities = self._workflow_identity_values(workflow or {})
canonical_run = bool(self._runs_for_workflow([raw_run], workflow))
canonical_run = canonical_run and self.client.matches_exact_head_action_run(
raw_run,
branch=str(head.get("ref", "")),
head_sha=expected_head_sha,
pull_request_number=number,
)
jobs = self.client.action_run_jobs(repository, int(run_match[1]))
full_job = next((job for job in jobs if job.get("name") == "full"), None)
if (
@@ -4349,11 +4438,7 @@ class GiteaConnector:
or str(run.get("event", "")) != "pull_request"
or str(run.get("status", "")) != "completed"
or str(run.get("conclusion", "")) != "success"
or (
workflow_id
and workflow_id not in identities
and workflow_id.rsplit("/", 1)[-1] not in identities
)
or not canonical_run
or not isinstance(full_job, dict)
or full_job.get("status") != "completed"
or full_job.get("conclusion") != "success"
@@ -4614,13 +4699,18 @@ class GiteaConnector:
key=lambda item: str(item["name"]),
)
runtime = runtime_snapshot()
upstream = (self.write_client or self.client).upstream_compatibility()
snapshot = {
"status": "ready",
"component": "gitea-connector",
"readOnly": self.write_client is None,
"writeMode": "pr-first" if self.write_client is not None else "disabled",
"atomicChangesets": self.changeset_workspace is not None,
"upstream": "reachable",
"upstream": "unavailable"
if upstream["compatibility"] == "UNAVAILABLE"
else "reachable",
"connectorVersion": self.version,
"upstreamGitea": upstream,
"toolCount": len(publications),
"capabilityFingerprint": hashlib.sha256(
json.dumps(descriptors, sort_keys=True, separators=(",", ":")).encode()
@@ -4632,7 +4722,16 @@ class GiteaConnector:
"dispatcher": "READY",
"worker": ("READY" if runtime["circuitState"] == "CLOSED" else "DEGRADED"),
"session": "RESUMABLE",
"upstream_gitea": "READY",
"upstream_gitea": (
"READY"
if upstream["compatibility"] == "SUPPORTED"
else "UNAVAILABLE"
if upstream["compatibility"] == "UNAVAILABLE"
else "UNVERIFIED"
),
"merge": "READY"
if upstream["compatibility"] == "SUPPORTED" and self.write_client
else "UNAVAILABLE",
},
}
with self._readiness_lock:
+99 -16
View File
@@ -3,11 +3,13 @@
from __future__ import annotations
import base64
import copy
import ipaddress
import json
import re
import time
from collections.abc import Mapping
from datetime import UTC, datetime
from pathlib import Path
from threading import RLock
from typing import Any, Protocol, cast
@@ -331,6 +333,13 @@ class GiteaClient:
"""Small bounded Gitea API client with same-origin redirect enforcement."""
_path_part = re.compile(r"^[^/\\?#\x00-\x1f\x7f]+$")
_semver = re.compile(
r"v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)"
r"(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?"
r"(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?"
)
_version_cache_seconds = 30
_supported_ranges = (">=1.14.0 <1.15.0", ">=1.25.0 <1.28.0", ">=28.0.0 <29.0.0")
def __init__(
self,
@@ -347,7 +356,8 @@ class GiteaClient:
follow_redirects=False,
transport=transport,
)
self._merge_strategy_key: str | None = None
self._version_lock = RLock()
self._version_cache: tuple[float, dict[str, Any]] | None = None
def close(self) -> None:
self._client.close()
@@ -813,27 +823,100 @@ class GiteaClient:
),
)
def _merge_strategy_field(self) -> str:
"""Resolve the versioned Gitea merge field before the single write attempt."""
def upstream_compatibility(self, *, force_refresh: bool = False) -> dict[str, Any]:
"""Read the upstream version, independent of this connector's build version.
if self._merge_strategy_key is not None:
return self._merge_strategy_key
payload = self._request(self._path("version"))
version = payload.get("version") if isinstance(payload, dict) else None
legacy_match = re.fullmatch(r"1\.(14|25|26|27)(?:\.\d+)?(?:[-+].*)?", str(version or ""))
modern_match = re.fullmatch(r"(28)(?:\.\d+){1,2}(?:[-+].*)?", str(version or ""))
if legacy_match is None and modern_match is None:
Diagnostics cache for 30 seconds; each merge negotiates again immediately
before its one write. Failed probes replace cached success and never fall
back to the connector version. Gitea 28 drops the historical ``1.`` prefix.
"""
with self._version_lock:
now = time.monotonic()
if (
not force_refresh
and self._version_cache is not None
and now - self._version_cache[0] < self._version_cache_seconds
):
return copy.deepcopy(self._version_cache[1])
diagnostics: dict[str, Any] = {
"source": "/api/v1/version",
"upstreamVersion": None,
"normalizedVersion": None,
"compatibility": "UNKNOWN",
"reason": "upstream_version_missing_or_malformed",
"mergeStrategyField": None,
"supportedRanges": list(self._supported_ranges),
"prereleasesSupported": False,
"checkedAt": datetime.now(UTC).isoformat(),
"cacheSeconds": self._version_cache_seconds,
"errorCode": None,
}
try:
payload = self._request(self._path("version"))
except GiteaError as exc:
diagnostics.update(
compatibility="UNAVAILABLE",
reason="upstream_version_probe_failed",
errorCode=exc.code,
)
else:
version = payload.get("version") if isinstance(payload, dict) else None
# Do not reflect arbitrary upstream data (or secret-like content).
if isinstance(version, str) and len(version) <= 128:
version = version.strip()
match = self._semver.fullmatch(version)
if match is not None:
release = tuple(int(match.group(index)) for index in (1, 2, 3))
prerelease = match.group(4)
invalid_prerelease = prerelease is not None and any(
part.isdigit() and len(part) > 1 and part.startswith("0")
for part in prerelease.split(".")
)
if not invalid_prerelease:
# Arbitrary SemVer suffixes can contain opaque credentials.
# Display only the numeric release; negotiate the full parsed version.
display_version = ("v" if version.startswith("v") else "") + ".".join(
str(part) for part in release
)
diagnostics.update(
upstreamVersion=display_version,
normalizedVersion=display_version.removeprefix("v"),
compatibility="UNSUPPORTED",
reason="upstream_release_outside_tested_ranges",
)
supported = (
(1, 14, 0) <= release < (1, 15, 0)
or (1, 25, 0) <= release < (1, 28, 0)
or (28, 0, 0) <= release < (29, 0, 0)
)
if prerelease is not None:
diagnostics["reason"] = "upstream_prerelease_not_tested"
elif supported:
diagnostics.update(
compatibility="SUPPORTED",
reason="upstream_release_in_tested_ranges",
mergeStrategyField=("Do" if release < (1, 26, 0) else "do"),
)
self._version_cache = (time.monotonic(), copy.deepcopy(diagnostics))
return diagnostics
def _merge_strategy_field(self) -> str:
"""Negotiate against the current upstream immediately before the merge write."""
diagnostics = self.upstream_compatibility(force_refresh=True)
if diagnostics["compatibility"] != "SUPPORTED":
raise GiteaError(
"GITEA_VERSION_UNSUPPORTED",
diagnostics["errorCode"] or "GITEA_VERSION_UNSUPPORTED",
(
"Gitea merge compatibility requires an identified "
"1.14, 1.25, 1.26, 1.27, or 28.x server"
"1.14, 1.25, 1.26, 1.27, or 28.x stable server; "
f"{diagnostics['reason']}"
),
category="compatibility",
expected_state="Stable upstream release in tested compatibility ranges",
actual_state=str(diagnostics["compatibility"]),
recovery_hint="Inspect gitea.capabilities.get upstreamGitea diagnostics",
)
self._merge_strategy_key = (
"Do" if legacy_match is not None and legacy_match.group(1) in {"14", "25"} else "do"
)
return self._merge_strategy_key
return str(diagnostics["mergeStrategyField"])
def update_pull_request_branch(
self,
+179 -4
View File
@@ -1,9 +1,9 @@
"""Compatibility hardening for Gitea validation discovery and request latency.
Some Gitea versions expose pull-request check results as commit statuses while
returning an empty branch-filtered Actions run wrapper. The base client treats
collections strictly; this subclass preserves that strictness while translating
a fully-green exact-head status set into one synthetic completed validation run.
Native Gitea PR runs can omit their head branch. This client reads actual
exact-head runs without a branch filter and checks available PR identity.
Older status-only adapters retain an explicitly labeled legacy fallback;
exact-head native reads never synthesize a workflow run or merge proof.
Large repositories can require longer than the historical 15-second client
budget to commit files, apply changesets, or materialize a server-side squash
@@ -15,6 +15,8 @@ base client and are not duplicated here.
from __future__ import annotations
import re
import time
from typing import Any
import httpx
@@ -40,6 +42,174 @@ class GiteaClient(BaseGiteaClient):
)
super().__init__(resilient_settings, token_provider, transport=transport)
@staticmethod
def matches_exact_head_action_run(
run: dict[str, Any],
*,
branch: str,
head_sha: str,
pull_request_number: int | None = None,
) -> bool:
if run.get("head_sha") != head_sha:
return False
head_branch = run.get("head_branch", "")
event = run.get("event")
if head_branch is None and event == "pull_request":
head_branch = ""
if event == "workflow_dispatch" and head_branch == f"refs/heads/{branch}":
head_branch = branch
if not isinstance(head_branch, str) or head_branch not in {"", branch}:
return False
if not head_branch and event != "pull_request":
return False
path = run.get("path")
path_number: int | None = None
if path is not None:
if not isinstance(path, str) or len(path) > 512 or path.count("@") > 1:
return False
if "@" in path:
ref = path.split("@", 1)[1]
match = re.fullmatch(r"refs/pull/([1-9][0-9]*)/head", ref)
if match:
path_number = int(match[1])
if event != "pull_request" or path_number > 2_147_483_647:
return False
elif ref != f"refs/heads/{branch}" or event == "pull_request":
return False
raw_references = run.get("pull_requests")
references = [] if raw_references is None else raw_references
if not isinstance(references, list):
return False
numbers: list[int] = []
for reference in references:
if not isinstance(reference, dict):
return False
number = reference.get("number")
if number is not None:
if type(number) is not int or not 1 <= number <= 2_147_483_647:
return False
numbers.append(number)
head = reference.get("head")
if head is not None:
if not isinstance(head, dict):
return False
if head.get("ref") not in (None, "", branch) or head.get("sha") not in (
None,
"",
head_sha,
):
return False
if event == "pull_request" and pull_request_number is not None:
if path_number not in {None, pull_request_number} or any(
number != pull_request_number for number in numbers
):
return False
# Legacy adapters identify PR runs by a real branch and SHA. Native
# blank-branch runs need affirmative PR number/ref evidence.
if path_number != pull_request_number and pull_request_number not in numbers:
return head_branch == branch and not path
return True
def exact_head_action_runs(
self,
repository: RegisteredRepository,
*,
branch: str,
head_sha: str,
pull_request_number: int | None = None,
) -> list[dict[str, Any]]:
"""Read bounded actual run evidence; never synthesize a native validation."""
if not re.fullmatch(r"(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})", head_sha):
raise GiteaError("GITEA_TARGET_INVALID", "exact Actions head SHA is invalid")
if pull_request_number is not None and (
type(pull_request_number) is not int or not 1 <= pull_request_number <= 2_147_483_647
):
raise GiteaError("GITEA_TARGET_INVALID", "exact Actions PR number is invalid")
output: list[dict[str, Any]] = []
seen: set[tuple[int, int]] = set()
expected_count: int | None = None
deadline = time.monotonic() + 20
for page in range(1, 11):
if time.monotonic() >= deadline:
raise GiteaError(
"GITEA_ACTION_RUNS_TRUNCATED", "exact Actions read exceeded its bound"
)
payload = self._request(
self._path("repos", repository.owner, repository.name, "actions", "runs"),
{"head_sha": head_sha, "page": page, "limit": 100},
timeout_seconds=min(10.0, max(0.1, deadline - time.monotonic())),
max_attempts=1,
)
count = payload.get("total_count") if isinstance(payload, dict) else None
if isinstance(payload, dict) and "total_count" in payload:
if type(count) is not int or not 0 <= count <= 1000:
raise GiteaError("GITEA_ACTION_RUNS_INVALID", "invalid exact Actions count")
if expected_count is not None and count != expected_count:
raise GiteaError("GITEA_ACTION_RUNS_TRUNCATED", "exact Actions count changed")
expected_count = count
elif expected_count is not None:
raise GiteaError("GITEA_ACTION_RUNS_TRUNCATED", "exact Actions count disappeared")
if isinstance(payload, dict):
payload = (
payload.get("workflow_runs")
if "workflow_runs" in payload
else payload.get("runs")
)
runs = self._as_list(payload)
if len(runs) > 100:
raise GiteaError("GITEA_ACTION_RUNS_INVALID", "excess exact Actions page")
for run in runs:
identifier, attempt = run.get("id"), run.get("run_attempt", 0)
number = run.get("run_number", identifier)
if (
type(identifier) is not int
or identifier < 1
or type(attempt) is not int
or attempt < 0
or ("run_attempt" in run and attempt == 0)
or type(number) is not int
or number < 1
or (identifier, attempt) in seen
):
raise GiteaError(
"GITEA_ACTION_RUNS_INVALID", "invalid or repeated exact Actions run"
)
seen.add((identifier, attempt))
output.append(run)
if expected_count is not None:
if len(output) > expected_count:
raise GiteaError("GITEA_ACTION_RUNS_INVALID", "excess exact Actions runs")
complete = len(output) == expected_count
else:
complete = len(runs) < 100
if complete:
if time.monotonic() >= deadline:
raise GiteaError(
"GITEA_ACTION_RUNS_TRUNCATED", "exact Actions read exceeded its bound"
)
matching = [
run
for run in output
if self.matches_exact_head_action_run(
run,
branch=branch,
head_sha=head_sha,
pull_request_number=pull_request_number,
)
]
return sorted(
matching,
key=lambda run: (
run.get("run_number", run["id"]),
run.get("run_attempt", 0),
run["id"],
),
reverse=True,
)
if not runs:
raise GiteaError("GITEA_ACTION_RUNS_TRUNCATED", "incomplete exact Actions page")
raise GiteaError("GITEA_ACTION_RUNS_TRUNCATED", "exact Actions pages exceeded their bound")
def action_runs(
self,
repository: RegisteredRepository,
@@ -78,6 +248,11 @@ class GiteaClient(BaseGiteaClient):
):
return []
native_runs = self.exact_head_action_runs(repository, branch=branch, head_sha=head_sha)
if native_runs:
offset = (page - 1) * limit
return native_runs[offset : offset + limit]
statuses = self.commit_statuses(repository, head_sha, 1, 100)
if not statuses:
return []
+71 -2
View File
@@ -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")[
+10 -25
View File
@@ -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
+465
View File
@@ -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()
+230
View File
@@ -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()
+3 -2
View File
@@ -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
View File
@@ -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"