3059 lines
135 KiB
Python
3059 lines
135 KiB
Python
"""WP-090 contract and adversarial tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import copy
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
from urllib.parse import parse_qs
|
|
from uuid import UUID, uuid4
|
|
|
|
import httpx
|
|
from dulwich import porcelain
|
|
|
|
from connectors.gitea import FakeGiteaServer, GiteaConnector
|
|
from connectors.gitea.changesets import (
|
|
DulwichChangesetWorkspace,
|
|
apply_unified_patch,
|
|
validate_operations,
|
|
)
|
|
from connectors.gitea.managed_validation import MANAGED_VALIDATION_WORKFLOW
|
|
from connectors.gitea.server import _managed_validation_profile, _select_validation_workflow
|
|
from connectors.knowledge import KnowledgeConnector
|
|
from packages.connector_kit import (
|
|
ConnectorContext,
|
|
GiteaClient,
|
|
GiteaError,
|
|
GiteaRepositoryRegistry,
|
|
GiteaSettings,
|
|
InMemoryKnowledgeIndex,
|
|
PageRequest,
|
|
RegisteredRepository,
|
|
StaticTokenProvider,
|
|
)
|
|
from packages.hub_core.domain import Connector, Project
|
|
from services.control_api.gitea.registry import registration_from_project
|
|
|
|
PROJECT_ID = UUID("11111111-1111-1111-1111-111111111111")
|
|
OTHER_PROJECT_ID = UUID("22222222-2222-2222-2222-222222222222")
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW = (
|
|
ROOT / "tests" / "fixtures" / "projectbrain-managed-validation.yml"
|
|
).read_text(encoding="utf-8")
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW = (
|
|
ROOT / "tests" / "fixtures" / "projectbrain-managed-validation-browser.yml"
|
|
).read_text(encoding="utf-8")
|
|
|
|
|
|
def projectbrain_connector(
|
|
server: FakeGiteaServer | None = None,
|
|
) -> tuple[GiteaConnector, FakeGiteaServer]:
|
|
fixture = server or FakeGiteaServer()
|
|
fixture._repos["Jens/projectbrain"] = {
|
|
"owner": {"login": "Jens"},
|
|
"name": "projectbrain",
|
|
"full_name": "Jens/projectbrain",
|
|
"description": "ProjectBrain",
|
|
"private": True,
|
|
"default_branch": "main",
|
|
}
|
|
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW
|
|
protection = copy.deepcopy(fixture._branch_protections["acme/hub"][0])
|
|
protection["status_check_contexts"] = ["Pull request / quality (pull_request)"]
|
|
protection["required_approvals"] = 2
|
|
fixture._branch_protections["Jens/projectbrain"] = [protection]
|
|
settings = GiteaSettings(
|
|
base_url="http://gitea.test",
|
|
token_reference="docker-secret://gitea_mcp_token", # noqa: S106
|
|
allow_insecure_http_for_tests=True,
|
|
)
|
|
client = GiteaClient(
|
|
settings, StaticTokenProvider("fixture-token-must-not-leak"), transport=fixture.transport()
|
|
)
|
|
registry = GiteaRepositoryRegistry(
|
|
(
|
|
RegisteredRepository(
|
|
repository_ref="projectbrain-repository",
|
|
project_id=PROJECT_ID,
|
|
owner="Jens",
|
|
name="projectbrain",
|
|
source_uri="itworx-git://projectbrain/projectbrain-repository",
|
|
private=True,
|
|
),
|
|
)
|
|
)
|
|
return GiteaConnector(client, registry, write_client=client), fixture
|
|
|
|
|
|
def context(project_id: UUID = PROJECT_ID, slug: str = "hub") -> ConnectorContext:
|
|
return ConnectorContext(
|
|
workspace_id=uuid4(), project_id=project_id, project_slug=slug, environment="test"
|
|
)
|
|
|
|
|
|
def connector(
|
|
server: FakeGiteaServer | None = None, *, project_id: UUID = PROJECT_ID
|
|
) -> tuple[GiteaConnector, FakeGiteaServer]:
|
|
fixture = server or FakeGiteaServer()
|
|
settings = GiteaSettings(
|
|
base_url="http://gitea.test",
|
|
token_reference="docker-secret://gitea_mcp_token", # noqa: S106
|
|
allow_insecure_http_for_tests=True,
|
|
)
|
|
client = GiteaClient(
|
|
settings, StaticTokenProvider("fixture-token-must-not-leak"), transport=fixture.transport()
|
|
)
|
|
registry = GiteaRepositoryRegistry(
|
|
(
|
|
RegisteredRepository(
|
|
repository_ref="hub-repository",
|
|
project_id=project_id,
|
|
owner="acme",
|
|
name="hub",
|
|
source_uri="itworx-git://hub/hub-repository",
|
|
private=True,
|
|
),
|
|
)
|
|
)
|
|
return GiteaConnector(
|
|
client, registry, write_client=client, changeset_workspace=FakeChangesetWorkspace()
|
|
), fixture
|
|
|
|
|
|
class FakeChangesetWorkspace:
|
|
def apply(
|
|
self,
|
|
repository: RegisteredRepository,
|
|
branch: str,
|
|
message: str,
|
|
operations: object,
|
|
) -> dict[str, object]:
|
|
return {
|
|
"commitSha": "6" * 40,
|
|
"branch": branch,
|
|
"operationCount": len(operations), # type: ignore[arg-type]
|
|
"atomic": True,
|
|
}
|
|
|
|
|
|
class ClampedDiscoveryServer(FakeGiteaServer):
|
|
"""Model a Gitea deployment that caps requested discovery pages at fifty."""
|
|
|
|
def __init__(self, *, deny_user_repositories: bool = False) -> None:
|
|
super().__init__(deny_user_repositories=deny_user_repositories)
|
|
self.discovery_page_requests: list[tuple[str, int, int, int]] = []
|
|
|
|
def _handle(self, request: httpx.Request) -> httpx.Response:
|
|
if request.url.path == "/api/v1/repos/search":
|
|
query = parse_qs(request.url.query.decode("ascii"))
|
|
if "q" not in query:
|
|
self.recorded_methods.append(request.method)
|
|
self.recorded_authorizations.append(request.headers.get("authorization", ""))
|
|
self.recorded_paths.append(request.url.path)
|
|
repositories = list(self._repos.values())
|
|
if query.get("private") == ["true"]:
|
|
repositories = [item for item in repositories if item.get("private") is True]
|
|
return self._paged(repositories, query, request)
|
|
return super()._handle(request)
|
|
|
|
def _paged(
|
|
self, items: list[dict[str, object]], query: dict[str, list[str]], request: httpx.Request
|
|
) -> httpx.Response:
|
|
page = max(int(query.get("page", ["1"])[0]), 1)
|
|
requested_limit = max(int(query.get("limit", ["100"])[0]), 1)
|
|
effective_limit = min(requested_limit, 50)
|
|
start = (page - 1) * effective_limit
|
|
selected = items[start : start + effective_limit]
|
|
self.discovery_page_requests.append(
|
|
(request.url.path, page, requested_limit, len(selected))
|
|
)
|
|
return self._json(200, selected, request)
|
|
|
|
|
|
class GiteaConnectorTests(unittest.TestCase):
|
|
def test_registry_resolves_an_exact_qualified_name_within_project_scope(self) -> None:
|
|
instance, _ = connector()
|
|
|
|
resolved = instance.registry.resolve(PROJECT_ID, "Acme/Hub")
|
|
|
|
self.assertEqual(resolved.repository_ref, "hub-repository")
|
|
with self.assertRaisesRegex(GiteaError, "repository is not registered"):
|
|
instance.registry.resolve(OTHER_PROJECT_ID, "acme/hub")
|
|
with self.assertRaisesRegex(GiteaError, "repository is not registered"):
|
|
instance.registry.resolve(PROJECT_ID, "hub")
|
|
|
|
def test_managed_validation_is_preferred_over_legacy_workflow_order(self) -> None:
|
|
legacy = {"id": "legacy", "path": ".gitea/workflows/chatgpt-validation.yml"}
|
|
managed = {"id": "managed", "path": ".gitea/workflows/managed-validation.yml"}
|
|
self.assertIs(_select_validation_workflow([legacy, managed]), managed)
|
|
self.assertIs(_select_validation_workflow([managed, legacy]), managed)
|
|
|
|
def test_projectbrain_managed_validation_profile_is_pinned_and_repository_scoped(self) -> None:
|
|
self.assertEqual(
|
|
_managed_validation_profile("Jens/projectbrain", PROJECTBRAIN_VALIDATION_WORKFLOW),
|
|
"projectbrain",
|
|
)
|
|
self.assertEqual(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain", PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW
|
|
),
|
|
"projectbrain",
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile("Acme/hub", PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace("pnpm test:e2e:real", ""),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"pnpm test:e2e:real", "# pnpm test:e2e:real"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"pnpm test:e2e:real", 'echo "pnpm test:e2e:real"'
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
'export TEST_DATABASE_URL="${DATABASE_URL}"',
|
|
'# export TEST_DATABASE_URL="${DATABASE_URL}"',
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" image: postgres:16", " image: postgres:15"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(" pull_request:\n", ""),
|
|
)
|
|
)
|
|
bounded_browser_mutations = (
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"runs-on: itworx-ci-browser", "runs-on: linux-validation"
|
|
),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"--cgroup-parent=docker", "--cgroup-parent=untrusted"
|
|
),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace("--cpus=0.5", "--cpus=0.6"),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace("--memory=512m", "--memory=513m"),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"--pids-limit=128", "--pids-limit=129"
|
|
),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"image: postgres:16", "image: postgres:15"
|
|
),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"pnpm exec playwright install chromium chromium-headless-shell",
|
|
"pnpm exec playwright install --force chromium chromium-headless-shell",
|
|
),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"pnpm exec playwright install chromium chromium-headless-shell",
|
|
"pnpm exec playwright install --with-deps chromium chromium-headless-shell",
|
|
),
|
|
PROJECTBRAIN_BROWSER_VALIDATION_WORKFLOW.replace(
|
|
"pnpm test:e2e:real", "echo skipped-e2e"
|
|
),
|
|
)
|
|
for modified_workflow in bounded_browser_mutations:
|
|
with self.subTest(modified_workflow=modified_workflow[-160:]):
|
|
self.assertIsNone(
|
|
_managed_validation_profile("Jens/projectbrain", modified_workflow)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n", ' pull_request:\n paths: ["backend/**"]\n'
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n", " pull_request:\n types: [edited]\n"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace("branches: [main]", "branches: [master]"),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"branches: [main]", "branches: [main, '!main']"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n",
|
|
" pull_request:\n branches: [main, '!main']\n",
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" name: full\n", " name: full\n if: false\n"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" name: full\n", " name: full\n needs: [conditional-setup]\n"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" - name: Validate repository with a bounded profile\n",
|
|
" - name: Validate repository with a bounded profile\n if: false\n",
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" name: full\n", " name: full\n continue-on-error: true\n"
|
|
),
|
|
)
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain",
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" - name: Validate repository with a bounded profile\n",
|
|
" - name: Validate repository with a bounded profile\n"
|
|
" continue-on-error: true\n",
|
|
),
|
|
)
|
|
)
|
|
bypass_mutations = (
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pnpm test:e2e:real\n",
|
|
" cat <<'EOF'\n pnpm test:e2e:real\n EOF\n",
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pnpm --dir backend test:database\n",
|
|
" exit 0\n pnpm --dir backend test:database\n",
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pnpm test:e2e:real\n",
|
|
" if false; then\n"
|
|
" pnpm test:e2e:real\n"
|
|
" fi\n",
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pnpm test:e2e:real\n",
|
|
" export PATH=/tmp/untrusted:$PATH\n"
|
|
" pnpm test:e2e:real\n",
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 "
|
|
"# v4.2.2\n",
|
|
" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 "
|
|
"# v4.2.2\n"
|
|
" - run: echo injected-action\n",
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"run-name: \"Managed validation / ${{ inputs.profile || 'full' }}\"",
|
|
'run-name: "Managed validation / full"',
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" default: full\n", " default: test\n"
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"permissions:\n", "env:\n PATH: /tmp/untrusted\n\npermissions:\n"
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"permissions:\n", "defaults:\n run:\n shell: sh\n\npermissions:\n"
|
|
),
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"jobs:\n", "jobs:\n extra:\n runs-on: linux-validation\n steps: []\n"
|
|
),
|
|
)
|
|
for modified_workflow in bypass_mutations:
|
|
with self.subTest(modified_workflow=modified_workflow[-160:]):
|
|
self.assertIsNone(
|
|
_managed_validation_profile("Jens/projectbrain", modified_workflow)
|
|
)
|
|
|
|
alias_chain = ["level0: &level0 [marker]"]
|
|
for level in range(1, 23):
|
|
alias_chain.append(
|
|
f"level{level}: &level{level} [*level{level - 1}, *level{level - 1}]"
|
|
)
|
|
alias_bomb = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
"jobs:\n", "\n".join(alias_chain) + "\njobs:\n"
|
|
)
|
|
self.assertIsNone(_managed_validation_profile("Jens/projectbrain", alias_bomb))
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Jens/projectbrain", PROJECTBRAIN_VALIDATION_WORKFLOW + "\njobs: {}\n"
|
|
)
|
|
)
|
|
self.assertIsNone(_managed_validation_profile("Acme/hub", PROJECTBRAIN_VALIDATION_WORKFLOW))
|
|
self.assertEqual(
|
|
_managed_validation_profile("Acme/hub", MANAGED_VALIDATION_WORKFLOW),
|
|
"canonical",
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile(
|
|
"Acme/hub", MANAGED_VALIDATION_WORKFLOW + "\n# custom project workflow\n"
|
|
)
|
|
)
|
|
|
|
def test_projectbrain_profile_pins_coverage_audits_security_and_doc_checks(self) -> None:
|
|
required_commands = (
|
|
("root coverage gate", "pnpm run test:coverage", "pnpm --if-present test"),
|
|
(
|
|
"backend coverage gate",
|
|
"pnpm --filter projectbrain-api run test:coverage",
|
|
"pnpm --filter projectbrain-api run test",
|
|
),
|
|
("security audit", "pnpm run audit:security", "pnpm --if-present audit:security"),
|
|
("OpenAPI audit", "pnpm run audit:openapi", "echo skipped-openapi-audit"),
|
|
(
|
|
"architecture audit",
|
|
"pnpm run audit:architecture",
|
|
"echo skipped-architecture-audit",
|
|
),
|
|
("documentation audit", "pnpm run audit:docs", "echo skipped-docs-audit"),
|
|
("v1 freeze audit", "pnpm run audit:v1-freeze", "echo skipped-v1-freeze-audit"),
|
|
(
|
|
"Unraid template audit",
|
|
"pnpm run audit:unraid-template",
|
|
"echo skipped-unraid-template-audit",
|
|
),
|
|
(
|
|
"documentation fast-path check",
|
|
"node scripts/check-doc-links.mjs",
|
|
"echo skipped-doc-link-check",
|
|
),
|
|
(
|
|
"Unraid documentation fast-path check",
|
|
"node scripts/check-unraid-template-readiness.mjs",
|
|
"echo skipped-unraid-readiness-check",
|
|
),
|
|
)
|
|
for label, command, replacement in required_commands:
|
|
with self.subTest(gate=label):
|
|
self.assertIn(command, PROJECTBRAIN_VALIDATION_WORKFLOW)
|
|
modified_workflow = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
command, replacement, 1
|
|
)
|
|
self.assertIsNone(
|
|
_managed_validation_profile("Jens/projectbrain", modified_workflow)
|
|
)
|
|
|
|
def test_native_skills_are_complete_digest_verified_and_bounded(self) -> None:
|
|
from services.runtime.mcp_skills import SkillGetParams, SkillListParams, StaticSkills
|
|
|
|
extension = StaticSkills(ROOT / "plugins/itworx-gitea/skills")
|
|
self.assertEqual(
|
|
{item["frontmatter"]["name"] for item in extension.catalog},
|
|
{
|
|
"repository-engineer",
|
|
"portfolio-engineer",
|
|
"projectbrain-operator",
|
|
"projectbrain-feature-delivery",
|
|
"projectbrain-portfolio-curator",
|
|
},
|
|
)
|
|
resources = {
|
|
str(binding.resource.uri): binding.resource for binding in extension.resources()
|
|
}
|
|
for skill in extension.catalog:
|
|
self.assertEqual(skill["uri"], skill["resources"][0]["uri"])
|
|
for descriptor in skill["resources"]:
|
|
resource = resources[descriptor["uri"]]
|
|
self.assertEqual(
|
|
descriptor["digest"],
|
|
f"sha256:{hashlib.sha256(resource.text.encode('utf-8')).hexdigest()}",
|
|
)
|
|
listed = asyncio.run(extension._list(None, SkillListParams()))
|
|
self.assertEqual(listed["skills"], list(extension.catalog))
|
|
selected = extension.catalog[0]
|
|
fetched = asyncio.run(extension._get(None, SkillGetParams(uri=selected["uri"])))
|
|
self.assertEqual(fetched, {"skill": selected})
|
|
with self.assertRaisesRegex(ValueError, "not registered"):
|
|
asyncio.run(
|
|
extension._get(
|
|
None,
|
|
SkillGetParams(uri="skill://itworx-gitea/missing/SKILL.md"),
|
|
)
|
|
)
|
|
|
|
def test_registered_read_paths_and_source_metadata(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
self.assertEqual(
|
|
connector_instance.repository_get(current, "hub-repository")["data"]["fullName"],
|
|
"acme/hub",
|
|
)
|
|
self.assertEqual(
|
|
len(connector_instance.branch_list(current, "hub-repository")["data"]["items"]), 3
|
|
)
|
|
self.assertEqual(
|
|
connector_instance.commit_list(current, "hub-repository")["data"]["items"][0][
|
|
"authorName"
|
|
],
|
|
"Ada",
|
|
)
|
|
self.assertEqual(
|
|
connector_instance.release_list(current, "hub-repository")["data"]["items"][0][
|
|
"tag"
|
|
],
|
|
"v1.0.0",
|
|
)
|
|
self.assertEqual(
|
|
connector_instance.issue_list(current, "hub-repository")["data"]["items"][0][
|
|
"number"
|
|
],
|
|
1,
|
|
)
|
|
self.assertEqual(
|
|
connector_instance.pull_request_list(current, "hub-repository")["data"]["items"][0][
|
|
"number"
|
|
],
|
|
7,
|
|
)
|
|
pull = connector_instance.pull_request_get(current, "hub-repository", 7)["data"]
|
|
self.assertEqual(pull["baseRef"], "main")
|
|
self.assertEqual(pull["changedFiles"][0]["path"], "services/runtime/mcp_server.py")
|
|
statuses = connector_instance.commit_status_list(current, "hub-repository", "main")[
|
|
"data"
|
|
]["items"]
|
|
self.assertEqual(statuses[0]["state"], "success")
|
|
file_result = connector_instance.file_read(
|
|
current, "itworx-git://hub/hub-repository/README.md"
|
|
)
|
|
self.assertIn("ITWorx MCP Hub", file_result["data"]["content"])
|
|
self.assertEqual(file_result["meta"]["source"], "gitea:hub-repository")
|
|
self.assertTrue(all(method == "GET" for method in fixture.recorded_methods))
|
|
self.assertTrue(all(path.startswith("/api/v1/") for path in fixture.recorded_paths))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_repository_list_ref_is_reusable_across_read_contract(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
listed = connector_instance.repository_list(current)["data"]["items"][0]
|
|
repository_ref = listed["repositoryRef"]
|
|
default_branch = listed["defaultBranch"]
|
|
self.assertEqual(default_branch, "main")
|
|
self.assertEqual(
|
|
connector_instance.repository_get(current, repository_ref)["data"]["repositoryRef"],
|
|
repository_ref,
|
|
)
|
|
self.assertTrue(
|
|
connector_instance.branch_list(current, repository_ref)["data"]["items"]
|
|
)
|
|
self.assertTrue(
|
|
connector_instance.commit_list(current, repository_ref)["data"]["items"]
|
|
)
|
|
self.assertTrue(
|
|
connector_instance.commit_status_list(current, repository_ref, default_branch)[
|
|
"data"
|
|
]["items"]
|
|
)
|
|
self.assertTrue(
|
|
connector_instance.release_list(current, repository_ref)["data"]["items"]
|
|
)
|
|
self.assertTrue(connector_instance.issue_list(current, repository_ref)["data"]["items"])
|
|
portfolio_issues = connector_instance.issue_search(
|
|
current, state="open", exclude_repository_names=[]
|
|
)["data"]
|
|
self.assertEqual(portfolio_issues["items"][0]["repositoryRef"], repository_ref)
|
|
self.assertEqual(portfolio_issues["items"][0]["number"], 1)
|
|
self.assertEqual(
|
|
connector_instance.issue_search(
|
|
current, state="open", exclude_repository_names=["hub"]
|
|
)["data"]["items"],
|
|
[],
|
|
)
|
|
pulls = connector_instance.pull_request_list(current, repository_ref)["data"]["items"]
|
|
self.assertTrue(pulls)
|
|
self.assertEqual(
|
|
connector_instance.pull_request_get(current, repository_ref, pulls[0]["number"])[
|
|
"data"
|
|
]["number"],
|
|
pulls[0]["number"],
|
|
)
|
|
file_uri = f"{listed['sourceUri']}/README.md"
|
|
self.assertIn(
|
|
"ITWorx MCP Hub",
|
|
connector_instance.file_read(current, file_uri)["data"]["content"],
|
|
)
|
|
search = connector_instance.code_search(current, repository_ref, "ITWorx")
|
|
self.assertEqual(search["data"]["items"][0]["fileUri"], file_uri)
|
|
self.assertTrue(all(path.startswith("/api/v1/") for path in fixture.recorded_paths))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_repository_agent_context_search_feedback_validation_and_resume(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
fixture._branches.append(
|
|
{
|
|
"name": "codex/workflows",
|
|
"commit": {"id": "e" * 40},
|
|
"protected": False,
|
|
}
|
|
)
|
|
context_result = connector_instance.repository_context(current, "hub-repository")
|
|
context_pack = context_result["data"]
|
|
self.assertFalse(context_result["meta"]["cacheHit"])
|
|
self.assertEqual(context_pack["stacks"], [])
|
|
self.assertEqual(context_pack["documents"][0]["path"], "README.md")
|
|
search_result = connector_instance.code_search(
|
|
current, "hub-repository", "safe fixture"
|
|
)
|
|
self.assertTrue(search_result["meta"]["cacheHit"])
|
|
results = search_result["data"]
|
|
self.assertEqual(results["items"][0]["matches"][0]["line"], 3)
|
|
fixture._reviews = [
|
|
{"id": 11, "state": "REQUEST_CHANGES", "user": {"login": "reviewer"}}
|
|
]
|
|
feedback = connector_instance.pull_request_feedback(current, "hub-repository", 7)[
|
|
"data"
|
|
]
|
|
self.assertEqual(feedback["reviews"][0]["comments"][0]["path"], "README.md")
|
|
dispatched = connector_instance.validation_run(
|
|
current, "hub-repository", "codex/workflows", "full"
|
|
)["data"]
|
|
self.assertEqual(dispatched["id"], 42)
|
|
self.assertTrue(dispatched["dispatched"])
|
|
reused_runs = [
|
|
connector_instance.validation_run(
|
|
current, "hub-repository", "codex/workflows", "full"
|
|
)["data"]
|
|
for _ in range(4)
|
|
]
|
|
self.assertTrue(all(item["id"] == 42 for item in reused_runs))
|
|
self.assertTrue(all(item["dispatched"] is False for item in reused_runs))
|
|
self.assertTrue(all(item["reused"] is True for item in reused_runs))
|
|
self.assertTrue(all(item["deduplicatedRunId"] == 42 for item in reused_runs))
|
|
self.assertEqual(
|
|
sum(path.endswith("/dispatches") for path in fixture.recorded_paths), 1
|
|
)
|
|
validation = connector_instance.validation_get(
|
|
current, "hub-repository", 42, include_logs=True
|
|
)["data"]
|
|
self.assertEqual(validation["run"]["conclusion"], "success")
|
|
self.assertEqual(validation["run"]["jobs"][0]["conclusion"], "success")
|
|
self.assertIn("checks passed", validation["run"]["jobs"][0]["logs"])
|
|
self.assertTrue(validation["run"]["terminal"])
|
|
self.assertEqual(validation["run"]["pollAfterMs"], 0)
|
|
connector_instance.branch_create(current, "hub-repository", "codex/workflows", "main")
|
|
status = connector_instance.change_status(current, "hub-repository", "codex/workflows")[
|
|
"data"
|
|
]
|
|
self.assertEqual(status["pullRequest"]["number"], 7)
|
|
self.assertEqual(status["validationRuns"][0]["id"], 42)
|
|
self.assertEqual(status["validationState"], "succeeded")
|
|
self.assertTrue(status["terminal"])
|
|
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
|
|
large_tree = [
|
|
{
|
|
"path": f"src/generated-{index:03d}.cs",
|
|
"type": "blob",
|
|
"sha": f"{index:040x}",
|
|
"size": 100,
|
|
}
|
|
for index in range(200)
|
|
]
|
|
large_tree.append({"path": "README.md", "type": "blob", "sha": "d" * 40, "size": 100_000})
|
|
try:
|
|
with patch.object(
|
|
connector_instance.client,
|
|
"git_tree",
|
|
return_value={"sha": "a" * 40, "tree": large_tree, "truncated": False},
|
|
):
|
|
result = connector_instance.repository_context(context(), "hub-repository")
|
|
data = result["data"]
|
|
self.assertEqual(len(data["tree"]), 64)
|
|
self.assertTrue(data["treeTruncated"])
|
|
self.assertTrue(data["documentsTruncated"])
|
|
self.assertTrue(result["meta"]["truncated"])
|
|
self.assertLessEqual(len(data["documents"]), 4)
|
|
self.assertLessEqual(
|
|
sum(len(item["content"].encode("utf-8")) for item in data["documents"]),
|
|
8_192,
|
|
)
|
|
self.assertLess(len(json.dumps(result).encode("utf-8")), 32_768)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_failed_cached_validation_is_dispatched_again(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
first = connector_instance.validation_run(
|
|
context(), "hub-repository", "codex/workflows", "full"
|
|
)["data"]
|
|
self.assertTrue(first["dispatched"])
|
|
fixture._action_run.update({"status": "completed", "conclusion": "failure"})
|
|
|
|
retried = connector_instance.validation_run(
|
|
context(), "hub-repository", "codex/workflows", "full"
|
|
)["data"]
|
|
self.assertTrue(retried["dispatched"])
|
|
self.assertFalse(retried["reused"])
|
|
self.assertEqual(
|
|
sum(path.endswith("/dispatches") for path in fixture.recorded_paths), 2
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_changeset_preview_and_atomic_apply_support_patch_move_and_delete(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
operations = [
|
|
{
|
|
"kind": "patch",
|
|
"path": "README.md",
|
|
"expectedSha": "d" * 40,
|
|
"patch": (
|
|
"--- a/README.md\n+++ b/README.md\n"
|
|
"@@ -1,3 +1,3 @@\n # ITWorx MCP Hub\n \n-This is a safe fixture.\n"
|
|
"+This is an improved fixture.\n"
|
|
),
|
|
}
|
|
]
|
|
preview = connector_instance.changeset_preview(
|
|
context(), "hub-repository", "codex/workflows", operations
|
|
)["data"]
|
|
self.assertTrue(preview["valid"])
|
|
self.assertIn("improved fixture", preview["operations"][0]["diff"])
|
|
applied = connector_instance.changeset_apply(
|
|
context(),
|
|
"hub-repository",
|
|
"codex/workflows",
|
|
"Improve fixture",
|
|
operations,
|
|
)["data"]
|
|
self.assertTrue(applied["atomic"])
|
|
self.assertEqual(applied["commitSha"], "6" * 40)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_unified_patch_rejects_stale_context_and_operation_budgets(self) -> None:
|
|
original = "one\ntwo\n"
|
|
patch_text = "@@ -1,2 +1,2 @@\n one\n-two\n+three\n"
|
|
self.assertEqual(apply_unified_patch(original, patch_text), "one\nthree\n")
|
|
with self.assertRaisesRegex(GiteaError, "context does not match"):
|
|
apply_unified_patch("zero\ntwo\n", patch_text)
|
|
with self.assertRaisesRegex(GiteaError, "operation count"):
|
|
validate_operations([])
|
|
|
|
def test_dulwich_workspace_pushes_one_atomic_multi_file_commit(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
remote = root / "acme" / "hub.git"
|
|
remote.parent.mkdir(parents=True)
|
|
bare = porcelain.init(remote, bare=True)
|
|
bare.close()
|
|
seed = root / "seed"
|
|
repo = porcelain.init(seed)
|
|
(seed / "README.md").write_text("one\ntwo\n", encoding="utf-8")
|
|
porcelain.add(repo, ["README.md"])
|
|
porcelain.commit(
|
|
repo,
|
|
message=b"seed",
|
|
author=b"Test <test@example.invalid>",
|
|
committer=b"Test <test@example.invalid>",
|
|
)
|
|
porcelain.branch_create(repo, "codex/atomic")
|
|
porcelain.push(
|
|
repo,
|
|
str(remote),
|
|
refspecs=[
|
|
"refs/heads/master:refs/heads/master",
|
|
"refs/heads/codex/atomic:refs/heads/codex/atomic",
|
|
],
|
|
)
|
|
sha = repo.open_index()[b"README.md"].sha.decode("ascii")
|
|
workspace = DulwichChangesetWorkspace(
|
|
str(root), StaticTokenProvider("unused-local-token"), "docker-secret://test"
|
|
)
|
|
previous_identity = {name: os.environ.get(name) for name in ("USER", "EMAIL")}
|
|
observed_identity: list[tuple[str | None, str | None]] = []
|
|
|
|
def require_explicit_identity() -> tuple[str, str]:
|
|
identity = (os.environ.get("USER"), os.environ.get("EMAIL"))
|
|
observed_identity.append(identity)
|
|
self.assertEqual(identity, ("mcp-agent", "mcp-agent@example.com"))
|
|
return identity[0] or "", identity[1] or ""
|
|
|
|
with patch(
|
|
"dulwich.repo._get_default_identity",
|
|
side_effect=require_explicit_identity,
|
|
):
|
|
result = workspace.apply(
|
|
RegisteredRepository(
|
|
repository_ref="hub",
|
|
project_id=PROJECT_ID,
|
|
owner="acme",
|
|
name="hub",
|
|
source_uri="itworx-git://hub/hub",
|
|
private=True,
|
|
),
|
|
"codex/atomic",
|
|
"Atomic update",
|
|
[
|
|
{
|
|
"kind": "patch",
|
|
"path": "README.md",
|
|
"expectedSha": sha,
|
|
"patch": "@@ -1,2 +1,2 @@\n one\n-two\n+three\n",
|
|
},
|
|
{"kind": "create", "path": "new.txt", "content": "created\n"},
|
|
],
|
|
)
|
|
self.assertTrue(observed_identity)
|
|
self.assertEqual(
|
|
{name: os.environ.get(name) for name in ("USER", "EMAIL")},
|
|
previous_identity,
|
|
)
|
|
self.assertTrue(result["atomic"])
|
|
repo.close()
|
|
checkout = root / "checkout"
|
|
checkout_repo = porcelain.clone(str(remote), checkout, branch="codex/atomic")
|
|
self.assertEqual((checkout / "README.md").read_text(encoding="utf-8"), "one\nthree\n")
|
|
self.assertEqual((checkout / "new.txt").read_text(encoding="utf-8"), "created\n")
|
|
checkout_repo.close()
|
|
|
|
def test_repository_list_rejects_nonempty_repository_without_default_branch(self) -> None:
|
|
fixture = FakeGiteaServer()
|
|
fixture._repos["acme/hub"]["default_branch"] = None
|
|
connector_instance, _ = connector(fixture)
|
|
try:
|
|
with self.assertRaisesRegex(GiteaError, "without a default branch"):
|
|
connector_instance.repository_list(context())
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_project_and_repository_allowlist_isolation(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
with self.assertRaisesRegex(GiteaError, "not registered"):
|
|
connector_instance.repository_get(
|
|
context(OTHER_PROJECT_ID, "other"), "hub-repository"
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "not registered"):
|
|
connector_instance.repository_get(context(), "acme-hub")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_file_uri_path_escape_is_denied(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
for file_uri in (
|
|
"itworx-git://hub/hub-repository/../secret",
|
|
"itworx-git://hub/hub-repository/%2e%2e/secret",
|
|
"itworx-git://hub/hub-repository//absolute",
|
|
"itworx-git://other/hub-repository/README.md",
|
|
):
|
|
with self.subTest(file_uri=file_uri), self.assertRaises(GiteaError):
|
|
connector_instance.file_read(context(), file_uri)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_cursor_pagination_is_opaque_and_bounded(self) -> None:
|
|
connector_instance, _ = connector(FakeGiteaServer())
|
|
try:
|
|
current = context()
|
|
first = connector_instance.branch_list(
|
|
current, "hub-repository", page=PageRequest(limit=2)
|
|
)
|
|
self.assertEqual(len(first["data"]["items"]), 2)
|
|
self.assertTrue(first["data"]["nextCursor"])
|
|
second = connector_instance.branch_list(
|
|
current,
|
|
"hub-repository",
|
|
page=PageRequest(limit=2, cursor=first["data"]["nextCursor"]),
|
|
)
|
|
self.assertEqual(len(second["data"]["items"]), 1)
|
|
with self.assertRaises(ValueError):
|
|
connector_instance.branch_list(
|
|
current, "hub-repository", page=PageRequest(limit=101)
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_token_and_response_headers_are_not_exposed(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
output = repr(connector_instance.repository_get(context(), "hub-repository"))
|
|
self.assertNotIn("fixture-token-must-not-leak", output)
|
|
self.assertEqual(
|
|
fixture.recorded_authorizations[0], "token fixture-token-must-not-leak"
|
|
)
|
|
self.assertNotIn("authorization", output.lower())
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_redirect_to_other_origin_is_denied(self) -> None:
|
|
connector_instance, fixture = connector(
|
|
FakeGiteaServer(redirect_to="http://169.254.169.254/latest/meta-data")
|
|
)
|
|
try:
|
|
with self.assertRaisesRegex(GiteaError, "leaves the configured origin"):
|
|
connector_instance.repository_get(context(), "hub-repository")
|
|
self.assertEqual(len(fixture.recorded_methods), 1)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_unsupported_optional_api_is_degraded(self) -> None:
|
|
connector_instance, _ = connector(FakeGiteaServer(unsupported_search=True))
|
|
try:
|
|
with self.assertRaisesRegex(GiteaError, "unsupported"):
|
|
connector_instance.code_search(context(), "hub-repository", "bounded")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_control_plane_registration_projects_allowlist_and_camel_case_settings(self) -> None:
|
|
now = datetime.now(UTC)
|
|
project = Project(
|
|
workspace_id=uuid4(),
|
|
slug="hub",
|
|
name="Hub",
|
|
repository_provider="gitea",
|
|
repository_owner="acme",
|
|
repository_name="hub",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
connector_model = Connector(
|
|
project_id=project.id,
|
|
workspace_id=project.workspace_id,
|
|
type="gitea",
|
|
slug="gitea-default",
|
|
endpoint="https://gitea.example",
|
|
credential_reference="docker-secret://gitea_mcp_token", # noqa: S106
|
|
configuration={"requestTimeoutSeconds": 9, "maxPageSize": 50},
|
|
enabled=True,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
registration = registration_from_project(project, connector_model)
|
|
self.assertEqual(registration.settings.timeout_seconds, 9)
|
|
self.assertEqual(registration.settings.max_page_size, 50)
|
|
self.assertEqual(registration.repositories[0].qualified_name, "acme/hub")
|
|
self.assertNotIn("fixture-token-must-not-leak", repr(registration.settings))
|
|
|
|
def test_publication_separates_read_and_pr_first_write_tools(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
tools = connector_instance.tools()
|
|
names = {tool.canonical_name for tool in tools}
|
|
self.assertEqual(len(names), 37)
|
|
self.assertIn("gitea.research.search", names)
|
|
self.assertIn("gitea.research.fetch", names)
|
|
self.assertTrue(all(tool.read_only for tool in tools))
|
|
write_tools = list(connector_instance.write_tools())
|
|
self.assertEqual(len(write_tools), 21)
|
|
self.assertEqual({tool.risk_class for tool in write_tools}, {"R2", "R3"})
|
|
self.assertTrue(
|
|
all(tool.required_scopes == ("project:{project}:write",) for tool in write_tools)
|
|
)
|
|
schemas = {tool.canonical_name: tool.input_schema for tool in write_tools}
|
|
repository_create = next(
|
|
tool for tool in write_tools if tool.canonical_name == "gitea.repository.create"
|
|
)
|
|
self.assertEqual(repository_create.version, "1.8.0")
|
|
self.assertEqual(
|
|
schemas["gitea.repository.create"]["required"],
|
|
["name", "description", "private", "autoInit", "defaultBranch"],
|
|
)
|
|
self.assertIs(
|
|
schemas["gitea.repository.create"]["properties"]["autoInit"]["const"], True
|
|
)
|
|
self.assertEqual(
|
|
schemas["gitea.repository.create"]["properties"]["defaultBranch"]["const"],
|
|
"main",
|
|
)
|
|
self.assertIn("expectedPlanHash", schemas["gitea.branchprotection.ensure"]["required"])
|
|
self.assertEqual(
|
|
schemas["gitea.branchprotection.ensure"]["properties"]["applyScope"]["enum"],
|
|
["all", "protection_only"],
|
|
)
|
|
portfolio_plan_properties = schemas["gitea.portfolio.governance.apply"]["properties"][
|
|
"plans"
|
|
]["items"]["properties"]
|
|
self.assertEqual(
|
|
portfolio_plan_properties["applyScope"]["enum"], ["all", "protection_only"]
|
|
)
|
|
self.assertEqual(
|
|
schemas["gitea.portfolio.governance.apply"]["properties"]["plans"]["maxItems"],
|
|
5,
|
|
)
|
|
self.assertIn("expectedSha", schemas["gitea.file.update"]["required"])
|
|
self.assertIn("expectedSha", schemas["gitea.file.delete"]["required"])
|
|
self.assertIn("head", schemas["gitea.pullrequest.create"]["required"])
|
|
self.assertEqual(
|
|
schemas["gitea.pullrequest.close"]["required"],
|
|
["repositoryRef", "number", "expectedHeadSha"],
|
|
)
|
|
self.assertIn("number", schemas["gitea.issue.comment.create"]["required"])
|
|
self.assertIn("number", schemas["gitea.issue.update"]["required"])
|
|
self.assertIn("operations", schemas["gitea.changeset.apply"]["required"])
|
|
self.assertIn("profile", schemas["gitea.validation.run"]["required"])
|
|
self.assertIn("expectedHeadSha", schemas["gitea.pullrequest.merge"]["required"])
|
|
self.assertEqual(
|
|
schemas["gitea.pullrequest.sync"]["required"],
|
|
[
|
|
"repositoryRef",
|
|
"number",
|
|
"expectedHeadSha",
|
|
"expectedBaseSha",
|
|
"strategy",
|
|
"dryRun",
|
|
],
|
|
)
|
|
self.assertIn("expectedHeadSha", schemas["gitea.branch.delete"]["required"])
|
|
self.assertIn("profile", schemas["gitea.workspace.run"]["required"])
|
|
self.assertIn(
|
|
"expectedOrchestrationId",
|
|
schemas["gitea.orchestration.start"]["required"],
|
|
)
|
|
self.assertNotIn("gitea.pullrequest.merge", names)
|
|
self.assertNotIn("gitea.branch.delete", names)
|
|
self.assertEqual(
|
|
connector_instance.resources()[0].uri_template,
|
|
"itworx-git://{project}/{repositoryRef}/{path}",
|
|
)
|
|
tree = connector_instance.tree_list(context(), "hub-repository")["data"]["items"]
|
|
self.assertEqual(tree[0]["path"], "README.md")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_runtime_hides_write_tools_until_feature_flag_is_enabled(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
with (
|
|
patch.dict(os.environ, {"GITEA_ENABLE_PR_WRITES": ""}),
|
|
patch(
|
|
"services.runtime.connector_gitea.build_connector",
|
|
return_value=(connector_instance, {"hub"}),
|
|
),
|
|
patch(
|
|
"services.runtime.connector_gitea.load_connector_identity_key",
|
|
return_value=b"x" * 32,
|
|
),
|
|
):
|
|
from services.runtime.connector_gitea import build_server
|
|
|
|
server = build_server()
|
|
tools = asyncio.run(server.list_tools())
|
|
|
|
names = {tool.name for tool in tools}
|
|
self.assertIn("gitea.repository.list", names)
|
|
self.assertIn("gitea.tree.list", names)
|
|
self.assertNotIn("gitea.file.create", names)
|
|
self.assertNotIn("gitea.file.delete", names)
|
|
self.assertNotIn("gitea.file.update", names)
|
|
self.assertNotIn("gitea.repository.create", names)
|
|
self.assertNotIn("gitea.issue.create", names)
|
|
self.assertNotIn("gitea.issue.update", names)
|
|
self.assertNotIn("gitea.pullrequest.create", names)
|
|
self.assertNotIn("gitea.changeset.apply", names)
|
|
self.assertNotIn("gitea.validation.run", names)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_runtime_registers_only_bounded_write_tools_when_enabled(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
with (
|
|
patch.dict(os.environ, {"GITEA_ENABLE_PR_WRITES": "true"}),
|
|
patch(
|
|
"services.runtime.connector_gitea.build_connector",
|
|
return_value=(connector_instance, {"hub"}),
|
|
),
|
|
patch(
|
|
"services.runtime.connector_gitea.load_connector_identity_key",
|
|
return_value=b"x" * 32,
|
|
),
|
|
):
|
|
from services.runtime.connector_gitea import build_server
|
|
|
|
server = build_server()
|
|
tools = asyncio.run(server.list_tools())
|
|
resources = asyncio.run(server.list_resources())
|
|
prompts = asyncio.run(server.list_prompts())
|
|
|
|
self.assertEqual(server.version, "1.14.0")
|
|
self.assertIn("plan-locked governance tools", server.instructions)
|
|
names = {tool.name for tool in tools}
|
|
ensure_tool = next(
|
|
tool for tool in tools if tool.name == "gitea.branchprotection.ensure"
|
|
)
|
|
portfolio_apply_tool = next(
|
|
tool for tool in tools if tool.name == "gitea.portfolio.governance.apply"
|
|
)
|
|
self.assertEqual(
|
|
ensure_tool.input_schema["properties"]["applyScope"]["enum"],
|
|
["all", "protection_only"],
|
|
)
|
|
portfolio_schema = portfolio_apply_tool.input_schema
|
|
plan_ref = portfolio_schema["properties"]["plans"]["items"]["$ref"]
|
|
plan_schema_name = plan_ref.rsplit("/", maxsplit=1)[-1]
|
|
plan_schema = portfolio_schema["$defs"][plan_schema_name]
|
|
self.assertEqual(
|
|
plan_schema["properties"]["applyScope"]["enum"], ["all", "protection_only"]
|
|
)
|
|
self.assertEqual(portfolio_schema["properties"]["plans"]["maxItems"], 5)
|
|
self.assertTrue(
|
|
{
|
|
"gitea.repository.create",
|
|
"gitea.branchprotection.ensure",
|
|
"gitea.portfolio.governance.apply",
|
|
"gitea.branch.create",
|
|
"gitea.issue.create",
|
|
"gitea.issue.update",
|
|
"gitea.issue.comment.create",
|
|
"gitea.file.create",
|
|
"gitea.file.update",
|
|
"gitea.file.delete",
|
|
"gitea.pullrequest.create",
|
|
"gitea.pullrequest.update",
|
|
"gitea.pullrequest.close",
|
|
"gitea.pullrequest.sync",
|
|
"gitea.changeset.apply",
|
|
"gitea.validation.run",
|
|
"gitea.validation.failure.get",
|
|
"gitea.change.reconcile",
|
|
"gitea.pullrequest.merge",
|
|
"gitea.branch.delete",
|
|
}.issubset(names)
|
|
)
|
|
self.assertEqual(len(names), 58)
|
|
self.assertIn("gitea.selftest.run", names)
|
|
repository_create_tool = next(
|
|
tool for tool in tools if tool.name == "gitea.repository.create"
|
|
)
|
|
self.assertEqual(
|
|
repository_create_tool.input_schema["required"],
|
|
["name", "description", "private", "autoInit", "defaultBranch"],
|
|
)
|
|
self.assertIn("search", names)
|
|
self.assertIn("fetch", names)
|
|
search_tool = next(tool for tool in tools if tool.name == "search")
|
|
fetch_tool = next(tool for tool in tools if tool.name == "fetch")
|
|
self.assertEqual(search_tool.input_schema["required"], ["query"])
|
|
self.assertEqual(fetch_tool.input_schema["required"], ["id"])
|
|
self.assertIn("results", search_tool.output_schema["properties"])
|
|
self.assertIn("text", fetch_tool.output_schema["properties"])
|
|
self.assertIn("io.modelcontextprotocol/skills", server._lowlevel_server.extensions)
|
|
self.assertNotIn("io.modelcontextprotocol/ui", server._lowlevel_server.extensions)
|
|
self.assertIn("repository-engineer", {prompt.name for prompt in prompts})
|
|
self.assertIn("portfolio-engineer", {prompt.name for prompt in prompts})
|
|
self.assertFalse(
|
|
any(str(resource.uri).startswith("ui://itworx-gitea/") for resource in resources)
|
|
)
|
|
app_tool_names = {
|
|
tool.name for tool in tools if tool.meta and tool.meta.get("openai/outputTemplate")
|
|
}
|
|
self.assertEqual(app_tool_names, set())
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_runtime_requires_a_distinct_write_token_secret(self) -> None:
|
|
base_environment = {
|
|
"GITEA_ENABLE_PR_WRITES": "true",
|
|
"GITEA_ENDPOINT": "https://gitea.test",
|
|
"GITEA_REPOSITORIES_JSON": ('[{"project":"hub","owner":"acme","name":"hub"}]'),
|
|
"GITEA_TOKEN_FILE": "/run/secrets/gitea_read_token",
|
|
}
|
|
from services.runtime.connector_gitea import build_connector
|
|
|
|
with (
|
|
patch.dict(os.environ, base_environment, clear=True),
|
|
self.assertRaisesRegex(ValueError, "GITEA_WRITE_TOKEN_FILE is required"),
|
|
):
|
|
build_connector()
|
|
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{**base_environment, "GITEA_WRITE_TOKEN_FILE": "/run/secrets/gitea_read_token"},
|
|
clear=True,
|
|
),
|
|
self.assertRaisesRegex(ValueError, "separate secret files"),
|
|
):
|
|
build_connector()
|
|
|
|
def test_runtime_derives_fixed_write_owner_and_admits_focused_profiles(self) -> None:
|
|
environment = {
|
|
"GITEA_ENABLE_PR_WRITES": "true",
|
|
"GITEA_ENDPOINT": "https://gitea.test",
|
|
"GITEA_REPOSITORIES_JSON": ('[{"project":"gitea-all","owner":"acme","name":"hub"}]'),
|
|
"GITEA_ADDITIONAL_PROJECTS": "gitea-loop",
|
|
"GITEA_TOKEN_FILE": "/run/secrets/gitea_read_token",
|
|
"GITEA_WRITE_TOKEN_FILE": "/run/secrets/gitea_write_token",
|
|
}
|
|
from services.runtime.common import deterministic_id
|
|
from services.runtime.connector_gitea import build_connector
|
|
|
|
with patch.dict(os.environ, environment, clear=True):
|
|
connector_instance, projects = build_connector()
|
|
try:
|
|
self.assertEqual(connector_instance.write_owner, "acme")
|
|
self.assertEqual(projects, {"gitea-all", "gitea-loop"})
|
|
assert connector_instance.repository_create_project is not None
|
|
self.assertEqual(connector_instance.repository_create_project[1], "gitea-all")
|
|
focused = connector_instance.registry.list(deterministic_id("project", "gitea-loop"))
|
|
self.assertEqual([item.qualified_name for item in focused], ["acme/hub"])
|
|
resolved = connector_instance.registry.resolve(
|
|
deterministic_id("project", "gitea-loop"), "hub"
|
|
)
|
|
self.assertEqual(resolved.qualified_name, "acme/hub")
|
|
with self.assertRaisesRegex(GiteaError, "not registered"):
|
|
connector_instance.registry.resolve(uuid4(), "hub")
|
|
finally:
|
|
connector_instance.client.close()
|
|
assert connector_instance.write_client is not None
|
|
connector_instance.write_client.close()
|
|
|
|
def test_runtime_requires_explicit_write_owner_for_multiple_namespaces(self) -> None:
|
|
environment = {
|
|
"GITEA_ENABLE_PR_WRITES": "true",
|
|
"GITEA_ENDPOINT": "https://gitea.test",
|
|
"GITEA_REPOSITORIES_JSON": (
|
|
'[{"project":"gitea-all","owner":"acme","name":"hub"},'
|
|
'{"project":"gitea-all","owner":"other","name":"service"}]'
|
|
),
|
|
"GITEA_TOKEN_FILE": "/run/secrets/gitea_read_token",
|
|
"GITEA_WRITE_TOKEN_FILE": "/run/secrets/gitea_write_token",
|
|
}
|
|
from services.runtime.connector_gitea import build_connector
|
|
|
|
with (
|
|
patch.dict(os.environ, environment, clear=True),
|
|
self.assertRaisesRegex(ValueError, "GITEA_WRITE_OWNER is required"),
|
|
):
|
|
build_connector()
|
|
|
|
def test_safe_write_workflow_is_branch_scoped_and_pr_first(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
branch = connector_instance.branch_create(
|
|
current, "hub-repository", "chatgpt/readme-update", "main"
|
|
)["data"]
|
|
self.assertTrue(branch["created"])
|
|
issue = connector_instance.issue_create(
|
|
current, "hub-repository", "Document safe write mode", "Track the rollout."
|
|
)["data"]
|
|
self.assertEqual(issue["number"], 2)
|
|
changed_issue = connector_instance.issue_update(
|
|
current,
|
|
"hub-repository",
|
|
2,
|
|
"Document governed write mode",
|
|
"Tracked and completed.",
|
|
"closed",
|
|
)["data"]
|
|
self.assertTrue(changed_issue["updated"])
|
|
self.assertEqual(changed_issue["state"], "closed")
|
|
comment = connector_instance.issue_comment_create(
|
|
current, "hub-repository", 2, "Implementation is ready for review."
|
|
)["data"]
|
|
self.assertEqual(comment["commentId"], 99)
|
|
created = connector_instance.file_create(
|
|
current,
|
|
"hub-repository",
|
|
"README.md",
|
|
"chatgpt/readme-update",
|
|
"# Updated\n",
|
|
"docs: update README",
|
|
)["data"]
|
|
self.assertEqual(created["operation"], "created")
|
|
updated = connector_instance.file_update(
|
|
current,
|
|
"hub-repository",
|
|
"README.md",
|
|
"chatgpt/readme-update",
|
|
"# Updated again\n",
|
|
"docs: refine README",
|
|
"9" * 40,
|
|
)["data"]
|
|
self.assertEqual(updated["operation"], "updated")
|
|
pull = connector_instance.pull_request_create(
|
|
current,
|
|
"hub-repository",
|
|
"chatgpt/readme-update",
|
|
"main",
|
|
"Update README",
|
|
"Please review this bounded change.",
|
|
)["data"]
|
|
self.assertTrue(pull["created"])
|
|
self.assertEqual(pull["headRef"], "chatgpt/readme-update")
|
|
self.assertEqual(
|
|
fixture.recorded_methods,
|
|
[
|
|
"GET",
|
|
"POST",
|
|
"POST",
|
|
"GET",
|
|
"PATCH",
|
|
"POST",
|
|
"GET",
|
|
"POST",
|
|
"GET",
|
|
"PUT",
|
|
"POST",
|
|
],
|
|
)
|
|
self.assertEqual(fixture.recorded_bodies[0]["old_ref_name"], "main")
|
|
self.assertNotIn("fixture-token-must-not-leak", repr(fixture.recorded_bodies))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_repository_create_is_idempotent_and_immediately_registered(self) -> None:
|
|
connector_instance, fixture = connector(ClampedDiscoveryServer())
|
|
try:
|
|
created = connector_instance.repository_create(
|
|
context(),
|
|
"projectbrain-created",
|
|
"Created through the governed MCP tool.",
|
|
True,
|
|
True,
|
|
"main",
|
|
)["data"]
|
|
self.assertTrue(created["created"])
|
|
self.assertTrue(created["availableImmediately"])
|
|
self.assertTrue(created["initialized"])
|
|
self.assertTrue(created["branchReady"])
|
|
self.assertTrue(created["governanceReady"])
|
|
self.assertTrue(created["managedValidationReady"])
|
|
self.assertTrue(created["defaultBranchProtected"])
|
|
self.assertFalse(created["repairedEmptyRepository"])
|
|
self.assertEqual(created["headSha"], "a" * 40)
|
|
self.assertEqual(created["repositoryRef"], "all-projectbrain-created")
|
|
self.assertEqual(
|
|
connector_instance.repository_get(context(), created["repositoryRef"])["data"][
|
|
"fullName"
|
|
],
|
|
"acme/projectbrain-created",
|
|
)
|
|
|
|
reused = connector_instance.repository_create(
|
|
context(), "projectbrain-created", private=True
|
|
)["data"]
|
|
self.assertFalse(reused["created"])
|
|
self.assertEqual(reused["repositoryRef"], created["repositoryRef"])
|
|
self.assertEqual(fixture.recorded_methods.count("POST"), 3)
|
|
|
|
with self.assertRaisesRegex(GiteaError, "outside the safe policy"):
|
|
connector_instance.repository_create(context(), "../unsafe")
|
|
with self.assertRaisesRegex(GiteaError, "requires an initialized default branch"):
|
|
connector_instance.repository_create(
|
|
context(), "projectbrain-empty-request", auto_init=False
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "canonical main branch"):
|
|
connector_instance.repository_create(
|
|
context(), "projectbrain-master-request", default_branch="master"
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "different visibility"):
|
|
connector_instance.repository_create(
|
|
context(), "projectbrain-created", private=False
|
|
)
|
|
|
|
public_repository = connector_instance.repository_create(
|
|
context(), "projectbrain-public", private=False
|
|
)["data"]
|
|
self.assertNotIn("/api/v1/user", fixture.recorded_paths)
|
|
restarted, _ = connector(fixture)
|
|
try:
|
|
restarted.discovery_projects = {PROJECT_ID: "hub"}
|
|
refs = {
|
|
item["repositoryRef"]
|
|
for item in restarted.repository_list(context(), page=PageRequest(limit=100))[
|
|
"data"
|
|
]["items"]
|
|
}
|
|
self.assertIn(public_repository["repositoryRef"], refs)
|
|
self.assertIn("/api/v1/user/repos", fixture.recorded_paths)
|
|
finally:
|
|
restarted.client.close()
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_governance_preview_ensure_verify_is_plan_locked_and_idempotent(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._managed_workflows.pop("acme/hub")
|
|
fixture._branch_protections["acme/hub"] = []
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(current, "hub-repository")[
|
|
"data"
|
|
]
|
|
self.assertEqual(preview["workflow"]["action"], "create")
|
|
self.assertEqual(preview["branchProtection"]["action"], "create")
|
|
self.assertFalse(preview["compliant"])
|
|
with self.assertRaisesRegex(GiteaError, "preview the repository again"):
|
|
connector_instance.branch_protection_ensure(current, "hub-repository", "0" * 64)
|
|
|
|
applied = connector_instance.branch_protection_ensure(
|
|
current, "hub-repository", preview["planHash"]
|
|
)["data"]
|
|
self.assertTrue(applied["changed"])
|
|
self.assertTrue(applied["verified"])
|
|
verified = connector_instance.portfolio_governance_verify(current, ["hub-repository"])[
|
|
"data"
|
|
]
|
|
self.assertEqual(verified["summary"], {"total": 1, "compliant": 1, "attention": 0})
|
|
|
|
replay = connector_instance.branch_protection_ensure(
|
|
current, "hub-repository", applied["planHash"]
|
|
)["data"]
|
|
self.assertFalse(replay["changed"])
|
|
self.assertTrue(replay["verified"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_custom_workflow_is_preserved_while_full_and_quality_are_added(
|
|
self,
|
|
) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
original_workflow = fixture._managed_workflows["Jens/projectbrain"]
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
|
|
"Pull request / quality (pull_request)"
|
|
]
|
|
original_protection = copy.deepcopy(fixture._branch_protections["Jens/projectbrain"][0])
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
|
|
self.assertEqual(preview["workflow"]["action"], "none")
|
|
self.assertNotEqual(
|
|
preview["workflow"]["currentDigest"],
|
|
hashlib.sha256(MANAGED_VALIDATION_WORKFLOW.encode()).hexdigest(),
|
|
)
|
|
self.assertEqual(
|
|
preview["branchProtection"]["desired"]["status_check_contexts"],
|
|
[
|
|
"Managed validation / full (pull_request)",
|
|
"Pull request / quality (pull_request)",
|
|
],
|
|
)
|
|
self.assertEqual(preview["branchProtection"]["action"], "update")
|
|
|
|
applied = connector_instance.branch_protection_ensure(
|
|
current, "projectbrain-repository", preview["planHash"]
|
|
)["data"]
|
|
|
|
self.assertTrue(applied["changed"])
|
|
self.assertTrue(applied["verified"])
|
|
self.assertTrue(applied["compliant"])
|
|
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], original_workflow)
|
|
self.assertEqual(
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"],
|
|
[
|
|
"Managed validation / full (pull_request)",
|
|
"Pull request / quality (pull_request)",
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
fixture._branch_protections["Jens/projectbrain"][0]["required_approvals"],
|
|
original_protection["required_approvals"],
|
|
)
|
|
workflow_writes = [
|
|
(method, path)
|
|
for method, path in zip(
|
|
fixture.recorded_methods, fixture.recorded_paths, strict=True
|
|
)
|
|
if path.endswith("/.gitea/workflows/managed-validation.yml")
|
|
and method in {"POST", "PUT", "DELETE"}
|
|
]
|
|
self.assertEqual(workflow_writes, [])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_all_scope_fails_if_gitea_drops_an_extra_required_context(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
|
|
"Pull request / quality (pull_request)",
|
|
"Additional required check",
|
|
]
|
|
original_read = connector_instance.client.branch_protections
|
|
read_count = 0
|
|
|
|
def drop_extra_context(repository: RegisteredRepository) -> list[dict[str, object]]:
|
|
nonlocal read_count
|
|
read_count += 1
|
|
if read_count == 3:
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"].remove(
|
|
"Additional required check"
|
|
)
|
|
return original_read(repository)
|
|
|
|
try:
|
|
current = context()
|
|
with patch.object(
|
|
connector_instance.client,
|
|
"branch_protections",
|
|
side_effect=drop_extra_context,
|
|
):
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertIn(
|
|
"Additional required check",
|
|
preview["branchProtection"]["desired"]["status_check_contexts"],
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "did not retain"):
|
|
connector_instance.branch_protection_ensure(
|
|
current, "projectbrain-repository", preview["planHash"]
|
|
)
|
|
self.assertNotIn(
|
|
"Additional required check",
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"],
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_all_scope_rejects_an_accepted_workflow_digest_change(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
|
|
"Pull request / quality (pull_request)"
|
|
]
|
|
modified_workflow = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n", " pull_request:\n branches: [main]\n"
|
|
)
|
|
original_update = connector_instance.client.update_branch_protection
|
|
|
|
def update_protection_then_change_workflow(
|
|
repository: RegisteredRepository,
|
|
branch: str,
|
|
payload: dict[str, object],
|
|
) -> object:
|
|
response = original_update(repository, branch, payload)
|
|
fixture._managed_workflows["Jens/projectbrain"] = modified_workflow
|
|
return response
|
|
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
|
|
with patch.object(
|
|
connector_instance.client,
|
|
"update_branch_protection",
|
|
side_effect=update_protection_then_change_workflow,
|
|
):
|
|
with self.assertRaisesRegex(GiteaError, "did not retain"):
|
|
connector_instance.branch_protection_ensure(
|
|
current, "projectbrain-repository", preview["planHash"]
|
|
)
|
|
self.assertEqual(
|
|
_managed_validation_profile("Jens/projectbrain", modified_workflow),
|
|
"projectbrain",
|
|
)
|
|
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], modified_workflow)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_custom_profile_blocks_unsafe_existing_protection(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
protection = fixture._branch_protections["Jens/projectbrain"][0]
|
|
protection["enable_push"] = True
|
|
protection["block_admin_merge_override"] = False
|
|
original_protection = copy.deepcopy(protection)
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
|
|
self.assertIn(
|
|
"managed-validation-projectbrain-protection-safety-locks-invalid",
|
|
preview["blockers"],
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "unresolved blockers"):
|
|
connector_instance.branch_protection_ensure(
|
|
current, "projectbrain-repository", preview["planHash"]
|
|
)
|
|
self.assertEqual(
|
|
fixture._branch_protections["Jens/projectbrain"][0], original_protection
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_all_scope_creates_safe_protection_without_rewriting_profile(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
original_workflow = fixture._managed_workflows["Jens/projectbrain"]
|
|
fixture._branch_protections["Jens/projectbrain"] = []
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertEqual(preview["workflow"]["profile"], "projectbrain")
|
|
self.assertEqual(preview["branchProtection"]["action"], "create")
|
|
applied = connector_instance.branch_protection_ensure(
|
|
current, "projectbrain-repository", preview["planHash"]
|
|
)["data"]
|
|
self.assertTrue(applied["compliant"])
|
|
self.assertTrue(applied["verified"])
|
|
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], original_workflow)
|
|
created_protection = fixture._branch_protections["Jens/projectbrain"][0]
|
|
self.assertEqual(
|
|
created_protection["status_check_contexts"],
|
|
[
|
|
"Managed validation / full (pull_request)",
|
|
"Pull request / quality (pull_request)",
|
|
],
|
|
)
|
|
self.assertTrue(created_protection["block_admin_merge_override"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_protection_only_is_hash_bound_and_preserves_workflow(
|
|
self,
|
|
) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
invalid_workflow = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(" pull_request:\n", "")
|
|
fixture._managed_workflows["Jens/projectbrain"] = invalid_workflow
|
|
protection = fixture._branch_protections["Jens/projectbrain"][0]
|
|
protection["status_check_contexts"] = [
|
|
"Pull request / quality (pull_request)",
|
|
"Additional required check",
|
|
]
|
|
protection["required_approvals"] = 2
|
|
protection["require_signed_commits"] = True
|
|
original_protection = copy.deepcopy(protection)
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertEqual(preview["workflow"]["profile"], "projectbrain-required")
|
|
self.assertEqual(preview["workflow"]["action"], "none")
|
|
self.assertIn("managed-validation-projectbrain-profile-invalid", preview["blockers"])
|
|
self.assertFalse(preview["compliant"])
|
|
self.assertTrue(preview["protectionOnlyAvailable"])
|
|
self.assertNotEqual(preview["planHash"], preview["protectionOnlyPlanHash"])
|
|
with self.assertRaisesRegex(GiteaError, "unresolved blockers"):
|
|
connector_instance.branch_protection_ensure(
|
|
current, "projectbrain-repository", preview["planHash"]
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "preview the repository again"):
|
|
connector_instance.branch_protection_ensure(
|
|
current,
|
|
"projectbrain-repository",
|
|
preview["planHash"],
|
|
apply_scope="protection_only",
|
|
)
|
|
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], invalid_workflow)
|
|
self.assertEqual(
|
|
fixture._branch_protections["Jens/projectbrain"][0], original_protection
|
|
)
|
|
|
|
portfolio_result = connector_instance.portfolio_governance_apply(
|
|
current,
|
|
[
|
|
{
|
|
"repositoryRef": "projectbrain-repository",
|
|
"planHash": preview["protectionOnlyPlanHash"],
|
|
"applyScope": "protection_only",
|
|
}
|
|
],
|
|
)["data"]
|
|
self.assertEqual(portfolio_result["summary"], {"total": 1, "applied": 1, "failed": 0})
|
|
self.assertEqual(portfolio_result["items"][0]["applyScope"], "protection_only")
|
|
updated_protection = fixture._branch_protections["Jens/projectbrain"][0]
|
|
self.assertEqual(
|
|
updated_protection["status_check_contexts"],
|
|
[
|
|
"Additional required check",
|
|
"Managed validation / full (pull_request)",
|
|
"Pull request / quality (pull_request)",
|
|
],
|
|
)
|
|
self.assertEqual(updated_protection["required_approvals"], 2)
|
|
self.assertTrue(updated_protection["require_signed_commits"])
|
|
self.assertEqual(fixture._managed_workflows["Jens/projectbrain"], invalid_workflow)
|
|
self.assertEqual(
|
|
fixture.recorded_bodies[-1],
|
|
{
|
|
"status_check_contexts": [
|
|
"Additional required check",
|
|
"Managed validation / full (pull_request)",
|
|
"Pull request / quality (pull_request)",
|
|
]
|
|
},
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_protection_only_requires_an_existing_enabled_rule(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n", ""
|
|
)
|
|
fixture._branch_protections["Jens/projectbrain"] = []
|
|
try:
|
|
preview = connector_instance.branch_protection_preview(
|
|
context(), "projectbrain-repository"
|
|
)["data"]
|
|
self.assertFalse(preview["protectionOnlyAvailable"])
|
|
self.assertIsNone(preview["protectionOnlyPlanHash"])
|
|
self.assertEqual(preview["branchProtection"]["action"], "create")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_protection_only_fails_if_gitea_drops_an_extra_context(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n", ""
|
|
)
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"] = [
|
|
"Pull request / quality (pull_request)",
|
|
"Additional required check",
|
|
]
|
|
original_read = connector_instance.client.branch_protections
|
|
read_count = 0
|
|
|
|
def drop_extra_context(repository: RegisteredRepository) -> list[dict[str, object]]:
|
|
nonlocal read_count
|
|
read_count += 1
|
|
if read_count == 3:
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"].remove(
|
|
"Additional required check"
|
|
)
|
|
return original_read(repository)
|
|
|
|
try:
|
|
current = context()
|
|
with patch.object(
|
|
connector_instance.client,
|
|
"branch_protections",
|
|
side_effect=drop_extra_context,
|
|
):
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
self.assertTrue(preview["protectionOnlyAvailable"])
|
|
with self.assertRaisesRegex(GiteaError, "did not retain"):
|
|
connector_instance.branch_protection_ensure(
|
|
current,
|
|
"projectbrain-repository",
|
|
preview["protectionOnlyPlanHash"],
|
|
apply_scope="protection_only",
|
|
)
|
|
self.assertNotIn(
|
|
"Additional required check",
|
|
fixture._branch_protections["Jens/projectbrain"][0]["status_check_contexts"],
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_projectbrain_protection_only_rejects_other_protection_changes(self) -> None:
|
|
connector_instance, fixture = projectbrain_connector()
|
|
fixture._managed_workflows["Jens/projectbrain"] = PROJECTBRAIN_VALIDATION_WORKFLOW.replace(
|
|
" pull_request:\n", ""
|
|
)
|
|
original_read = connector_instance.client.branch_protections
|
|
read_count = 0
|
|
|
|
def changed_protection_response(
|
|
repository: RegisteredRepository,
|
|
) -> list[dict[str, object]]:
|
|
nonlocal read_count
|
|
protections = original_read(repository)
|
|
read_count += 1
|
|
if read_count == 3:
|
|
protections[0]["required_approvals"] = 0
|
|
protections[0]["block_admin_merge_override"] = False
|
|
return protections
|
|
|
|
try:
|
|
current = context()
|
|
with patch.object(
|
|
connector_instance.client,
|
|
"branch_protections",
|
|
side_effect=changed_protection_response,
|
|
):
|
|
preview = connector_instance.branch_protection_preview(
|
|
current, "projectbrain-repository"
|
|
)["data"]
|
|
with self.assertRaisesRegex(GiteaError, "did not retain"):
|
|
connector_instance.branch_protection_ensure(
|
|
current,
|
|
"projectbrain-repository",
|
|
preview["protectionOnlyPlanHash"],
|
|
apply_scope="protection_only",
|
|
)
|
|
self.assertEqual(
|
|
fixture._managed_workflows["Jens/projectbrain"],
|
|
PROJECTBRAIN_VALIDATION_WORKFLOW.replace(" pull_request:\n", ""),
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_governance_update_never_relaxes_protected_main_push(
|
|
self,
|
|
) -> None:
|
|
fixture = FakeGiteaServer(deny_protected_workflow_writes=True)
|
|
fixture._managed_workflows["acme/hub"] = "name: stale managed validation\n"
|
|
original_protection = copy.deepcopy(fixture._branch_protections["acme/hub"][0])
|
|
connector_instance, _ = connector(fixture)
|
|
try:
|
|
current = context()
|
|
preview = connector_instance.branch_protection_preview(current, "hub-repository")[
|
|
"data"
|
|
]
|
|
self.assertEqual(preview["workflow"]["action"], "update")
|
|
|
|
with self.assertRaisesRegex(GiteaError, "validated pull request"):
|
|
connector_instance.branch_protection_ensure(
|
|
current, "hub-repository", preview["planHash"]
|
|
)
|
|
|
|
self.assertEqual(
|
|
fixture._managed_workflows["acme/hub"], "name: stale managed validation\n"
|
|
)
|
|
self.assertEqual(fixture._branch_protections["acme/hub"][0], original_protection)
|
|
protection_writes = [body for body in fixture.recorded_bodies if "enable_push" in body]
|
|
self.assertFalse(protection_writes)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_repository_create_retains_repository_when_governance_fails(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
with patch.object(
|
|
connector_instance,
|
|
"branch_protection_ensure",
|
|
side_effect=GiteaError(
|
|
"GITEA_ACCESS_DENIED", "fixture denial", upstream_status=403
|
|
),
|
|
):
|
|
with self.assertRaises(GiteaError) as raised:
|
|
connector_instance.repository_create(context(), "recover-created")
|
|
self.assertEqual(raised.exception.code, "GITEA_REPOSITORY_GOVERNANCE_PENDING")
|
|
self.assertEqual(raised.exception.upstream_status, 403)
|
|
self.assertNotIn("DELETE", fixture.recorded_methods)
|
|
self.assertEqual(
|
|
connector_instance.repository_get(context(), "all-recover-created")["data"][
|
|
"fullName"
|
|
],
|
|
"acme/recover-created",
|
|
)
|
|
repaired = connector_instance.repository_create(context(), "recover-created")["data"]
|
|
self.assertFalse(repaired["created"])
|
|
self.assertTrue(repaired["governanceReady"])
|
|
self.assertEqual(fixture.recorded_paths.count("/api/v1/user/repos"), 1)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_repository_create_repairs_an_existing_empty_repository(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._repos["acme/projectbrain-empty"] = {
|
|
"owner": {"login": "acme"},
|
|
"name": "projectbrain-empty",
|
|
"full_name": "acme/projectbrain-empty",
|
|
"description": "",
|
|
"private": True,
|
|
"empty": True,
|
|
"default_branch": "",
|
|
"html_url": "https://gitea.example/acme/projectbrain-empty",
|
|
}
|
|
try:
|
|
result = connector_instance.repository_create(
|
|
context(),
|
|
"projectbrain-empty",
|
|
"Initialized after an interrupted bootstrap.",
|
|
True,
|
|
True,
|
|
"main",
|
|
)["data"]
|
|
self.assertFalse(result["created"])
|
|
self.assertTrue(result["repairedEmptyRepository"])
|
|
self.assertTrue(result["branchReady"])
|
|
self.assertEqual(result["defaultBranch"], "main")
|
|
self.assertIn(
|
|
"/api/v1/repos/acme/projectbrain-empty/contents/README.md", fixture.recorded_paths
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_focused_repository_create_registers_in_primary_gitea_project(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
connector_instance.repository_create_project = (PROJECT_ID, "hub")
|
|
try:
|
|
created = connector_instance.repository_create(
|
|
context(OTHER_PROJECT_ID, "gitea-loop"), "focused-created"
|
|
)["data"]
|
|
self.assertTrue(created["created"])
|
|
self.assertEqual(created["repositoryRef"], "all-focused-created")
|
|
self.assertEqual(
|
|
connector_instance.repository_get(context(), created["repositoryRef"])["data"][
|
|
"fullName"
|
|
],
|
|
"acme/focused-created",
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "not registered"):
|
|
connector_instance.repository_get(
|
|
context(OTHER_PROJECT_ID, "gitea-loop"), created["repositoryRef"]
|
|
)
|
|
self.assertNotIn("/api/v1/user", fixture.recorded_paths)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_file_delete_is_change_branch_scoped_and_sha_locked(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
deleted = connector_instance.file_delete(
|
|
context(),
|
|
"hub-repository",
|
|
"README.md",
|
|
"chatgpt/remove-readme",
|
|
"docs: remove obsolete readme",
|
|
"d" * 40,
|
|
)["data"]
|
|
self.assertEqual(deleted["operation"], "deleted")
|
|
self.assertEqual(fixture.recorded_methods, ["GET", "DELETE"])
|
|
with self.assertRaises(GiteaError):
|
|
connector_instance.file_delete(
|
|
context(), "hub-repository", "README.md", "main", "delete", "d" * 40
|
|
)
|
|
with self.assertRaises(GiteaError):
|
|
connector_instance.file_delete(
|
|
context(),
|
|
"hub-repository",
|
|
"README.md",
|
|
"chatgpt/remove-readme",
|
|
"delete",
|
|
"stale",
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_extended_read_and_review_surface_is_bounded(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
fixture._branches.append(
|
|
{
|
|
"name": "codex/workflows",
|
|
"commit": {"id": "e" * 40},
|
|
"protected": False,
|
|
}
|
|
)
|
|
commit = connector_instance.commit_get(current, "hub-repository", "main")["data"]
|
|
self.assertTrue(commit["verified"])
|
|
self.assertEqual(commit["files"][0]["path"], "README.md")
|
|
comparison = connector_instance.ref_compare(
|
|
current, "hub-repository", "main", "codex/workflows"
|
|
)["data"]
|
|
self.assertEqual(comparison["totalCommits"], 1)
|
|
health = connector_instance.repository_health(current, "hub-repository")["data"]
|
|
self.assertTrue(health["defaultBranchProtected"])
|
|
self.assertTrue(health["validationWorkflowAvailable"])
|
|
runs = connector_instance.validation_list(
|
|
current,
|
|
"hub-repository",
|
|
"codex/workflows",
|
|
page=PageRequest(limit=10),
|
|
)["data"]["items"]
|
|
self.assertEqual(runs[0]["conclusion"], "success")
|
|
review = connector_instance.change_review(current, "hub-repository", "codex/workflows")[
|
|
"data"
|
|
]
|
|
self.assertTrue(review["readyToMerge"])
|
|
self.assertEqual(review["comparison"]["files"][0]["path"], "README.md")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_capability_connection_and_portfolio_surfaces_are_bounded(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
capabilities = connector_instance.capabilities_get(current)["data"]
|
|
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")
|
|
self.assertEqual(capabilities["toolCount"], 58)
|
|
self.assertEqual(capabilities["registeredHandlerCount"], 58)
|
|
self.assertEqual(capabilities["missingHandlers"], [])
|
|
self.assertEqual(capabilities["extraHandlers"], [])
|
|
self.assertEqual(capabilities["subsystems"]["runtime"], "READY")
|
|
self.assertEqual(capabilities["subsystems"]["write"], "READY")
|
|
self.assertIn("buildSha", capabilities)
|
|
self.assertEqual(capabilities["riskCounts"], {"R1": 37, "R2": 18, "R3": 3})
|
|
self.assertEqual(len(capabilities["capabilityFingerprint"]), 64)
|
|
self.assertFalse(capabilities["arbitraryExecution"])
|
|
|
|
connection = connector_instance.connection_check(current)["data"]
|
|
self.assertTrue(connection["connected"])
|
|
self.assertEqual(connection["toolCount"], 58)
|
|
self.assertEqual(connection["credentialMode"], "mounted-secret")
|
|
|
|
selftest = connector_instance.selftest_run(current)["data"]
|
|
self.assertEqual(selftest["status"], "PASS")
|
|
self.assertTrue(selftest["probeOnly"])
|
|
self.assertEqual(selftest["mutationsAttempted"], 0)
|
|
self.assertEqual(
|
|
{item["name"] for item in selftest["checks"]},
|
|
{
|
|
"handler-parity",
|
|
"gitea-connection",
|
|
"write-runtime",
|
|
"managed-actions",
|
|
"upstream-merge-compatibility",
|
|
},
|
|
)
|
|
|
|
repository_health = connector_instance.repository_health(current, "hub-repository")[
|
|
"data"
|
|
]
|
|
portfolio = connector_instance.portfolio_health(current, ["hub-repository"])["data"]
|
|
self.assertEqual(portfolio["summary"]["total"], 1)
|
|
self.assertTrue(portfolio["items"][0]["healthy"])
|
|
self.assertEqual(
|
|
portfolio["items"][0]["openPullRequestCount"],
|
|
len(repository_health["openPullRequests"]),
|
|
)
|
|
self.assertEqual(
|
|
portfolio["items"][0]["safeBranchCount"],
|
|
len(repository_health["changeBranches"]),
|
|
)
|
|
self.assertEqual(portfolio["items"][0]["snapshotId"], repository_health["snapshotId"])
|
|
self.assertEqual(
|
|
portfolio["items"][0]["sourceRevision"], repository_health["sourceRevision"]
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "1-25 unique"):
|
|
connector_instance.portfolio_health(current, ["hub-repository", "hub-repository"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_company_knowledge_search_and_fetch_are_citable_and_scope_bound(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
current = context()
|
|
search = connector_instance.research_search(current, "ITWorx Hub")
|
|
self.assertGreaterEqual(len(search["results"]), 1)
|
|
result = search["results"][0]
|
|
self.assertTrue(result["id"].startswith("gitea:"))
|
|
self.assertIn("/src/commit/", result["url"])
|
|
fetched = connector_instance.research_fetch(current, result["id"])
|
|
self.assertEqual(fetched["id"], result["id"])
|
|
self.assertIn("ITWorx MCP Hub", fetched["text"])
|
|
self.assertEqual(fetched["metadata"]["repositoryRef"], "hub-repository")
|
|
with self.assertRaisesRegex(GiteaError, "document ID"):
|
|
connector_instance.research_fetch(current, "gitea:not-valid-base64")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_readiness_proves_upstream_access_and_caches_the_probe(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
first = connector_instance.readiness()
|
|
second = connector_instance.readiness()
|
|
self.assertEqual(first, second)
|
|
self.assertEqual(first["upstream"], "reachable")
|
|
self.assertEqual(first["toolCount"], 58)
|
|
self.assertEqual(len(first["capabilityFingerprint"]), 64)
|
|
self.assertEqual(fixture.recorded_paths.count("/api/v1/repos/search"), 1)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
unavailable, _ = connector(FakeGiteaServer(unsupported_search=True))
|
|
try:
|
|
with self.assertRaises(GiteaError):
|
|
unavailable.readiness()
|
|
finally:
|
|
unavailable.client.close()
|
|
|
|
def test_workspace_is_durable_and_runs_only_fixed_profiles(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
try:
|
|
current = context()
|
|
first = connector_instance.workspace_status(
|
|
current, "hub-repository", "codex/workflows"
|
|
)["data"]
|
|
second = connector_instance.workspace_status(
|
|
current, "hub-repository", "codex/workflows"
|
|
)["data"]
|
|
self.assertEqual(first["workspaceId"], second["workspaceId"])
|
|
self.assertEqual(first["stateVersion"], second["stateVersion"])
|
|
self.assertEqual(first["execution"]["mode"], "managed-actions")
|
|
self.assertFalse(first["execution"]["arbitraryCommands"])
|
|
self.assertEqual(first["pollAfterMs"], 0)
|
|
self.assertTrue(first["terminal"])
|
|
self.assertRegex(first["validationStateVersion"], r"^[0-9a-f]{24}$")
|
|
|
|
run = connector_instance.workspace_run(
|
|
current, "hub-repository", "codex/workflows", "test"
|
|
)["data"]
|
|
self.assertEqual(run["workspaceId"], first["workspaceId"])
|
|
self.assertEqual(run["executionMode"], "managed-actions")
|
|
self.assertIn(run["validationState"], {"queued", "running", "succeeded"})
|
|
with self.assertRaisesRegex(GiteaError, "not allowlisted"):
|
|
connector_instance.workspace_run(
|
|
current, "hub-repository", "codex/workflows", "shell"
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_cross_repository_orchestration_is_previewed_locked_and_resumable(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
current = context()
|
|
plan = connector_instance.orchestration_plan(
|
|
current, ["hub-repository"], "dependency-refresh"
|
|
)["data"]
|
|
self.assertEqual(plan["items"][0]["branch"], "codex/dependency-refresh")
|
|
with self.assertRaisesRegex(GiteaError, "plan changed"):
|
|
connector_instance.orchestration_start(
|
|
current,
|
|
["hub-repository"],
|
|
"dependency-refresh",
|
|
"0" * 24,
|
|
)
|
|
started = connector_instance.orchestration_start(
|
|
current,
|
|
["hub-repository"],
|
|
"dependency-refresh",
|
|
plan["orchestrationId"],
|
|
)["data"]
|
|
self.assertEqual(started["items"][0]["state"], "created")
|
|
resumed = connector_instance.orchestration_start(
|
|
current,
|
|
["hub-repository"],
|
|
"dependency-refresh",
|
|
plan["orchestrationId"],
|
|
)["data"]
|
|
self.assertEqual(resumed["items"][0]["state"], "resumed")
|
|
with self.assertRaisesRegex(GiteaError, "branchSuffix"):
|
|
connector_instance.orchestration_plan(current, ["hub-repository"], "../unsafe")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_reviewed_lifecycle_requires_sha_validation_merge_and_cleanup(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
updated = connector_instance.pull_request_update(
|
|
current,
|
|
"hub-repository",
|
|
7,
|
|
"e" * 40,
|
|
title="Reviewed repository workflow",
|
|
body="Validated and ready.",
|
|
)["data"]
|
|
self.assertTrue(updated["updated"])
|
|
with self.assertRaisesRegex(GiteaError, "head changed"):
|
|
connector_instance.pull_request_merge(
|
|
current, "hub-repository", 7, "d" * 40, "squash"
|
|
)
|
|
merged = connector_instance.pull_request_merge(
|
|
current, "hub-repository", 7, "e" * 40, "squash"
|
|
)["data"]
|
|
self.assertTrue(merged["merged"])
|
|
fixture._branches.append(
|
|
{
|
|
"name": "codex/workflows",
|
|
"commit": {"id": "e" * 40},
|
|
"protected": False,
|
|
}
|
|
)
|
|
deleted = connector_instance.branch_delete(
|
|
current, "hub-repository", "codex/workflows", "e" * 40
|
|
)["data"]
|
|
self.assertTrue(deleted["deleted"])
|
|
self.assertNotIn("codex/workflows", {item["name"] for item in fixture._branches})
|
|
merge_body = next(
|
|
body for body in fixture.recorded_bodies if body.get("do") == "squash"
|
|
)
|
|
self.assertNotIn("Do", merge_body)
|
|
self.assertFalse(merge_body["force_merge"])
|
|
self.assertFalse(merge_body["delete_branch_after_merge"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_pull_request_close_is_sha_locked_and_closes_only_an_open_pull_request(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
with self.assertRaisesRegex(GiteaError, "repository is not registered"):
|
|
connector_instance.pull_request_close(
|
|
context(OTHER_PROJECT_ID, "other"), "hub-repository", 7, "e" * 40
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "head changed"):
|
|
connector_instance.pull_request_close(current, "hub-repository", 7, "d" * 40)
|
|
|
|
closed = connector_instance.pull_request_close(current, "hub-repository", 7, "e" * 40)[
|
|
"data"
|
|
]
|
|
|
|
self.assertTrue(closed["closed"])
|
|
self.assertEqual(closed["state"], "closed")
|
|
self.assertEqual(closed["headSha"], "e" * 40)
|
|
self.assertIn({"state": "closed"}, fixture.recorded_bodies)
|
|
with self.assertRaisesRegex(GiteaError, "only an open pull request"):
|
|
connector_instance.pull_request_close(current, "hub-repository", 7, "e" * 40)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_closed_unmerged_pull_request_branch_can_be_deleted_with_exact_sha(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
connector_instance.pull_request_close(context(), "hub-repository", 7, "e" * 40)
|
|
|
|
deleted = connector_instance.branch_delete(
|
|
context(), "hub-repository", "codex/workflows", "e" * 40
|
|
)["data"]
|
|
|
|
self.assertTrue(deleted["deleted"])
|
|
self.assertEqual(deleted["cleanupDisposition"], "closed-unmerged")
|
|
self.assertEqual(deleted["closedPullRequest"], 7)
|
|
self.assertEqual(deleted["preconditionMode"], "optimistic-double-read")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_branch_delete_rejects_open_pr_and_unrelated_same_sha(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
fixture._branches.extend(
|
|
[
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False},
|
|
{"name": "codex/unrelated", "commit": {"id": "e" * 40}, "protected": False},
|
|
]
|
|
)
|
|
with self.assertRaisesRegex(GiteaError, "open pull request"):
|
|
connector_instance.branch_delete(
|
|
context(), "hub-repository", "codex/workflows", "e" * 40
|
|
)
|
|
connector_instance.pull_request_close(context(), "hub-repository", 7, "e" * 40)
|
|
with self.assertRaisesRegex(GiteaError, "merged or closed pull request"):
|
|
connector_instance.branch_delete(
|
|
context(), "hub-repository", "codex/unrelated", "e" * 40
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_gitea_125_merge_uses_legacy_strategy_field_without_forcing(self) -> None:
|
|
fixture = FakeGiteaServer(gitea_version="1.25.5")
|
|
connector_instance, _ = connector(fixture)
|
|
try:
|
|
merged = connector_instance.pull_request_merge(
|
|
context(), "hub-repository", 7, "e" * 40, "squash"
|
|
)["data"]
|
|
self.assertTrue(merged["merged"])
|
|
merge_body = next(
|
|
body for body in fixture.recorded_bodies if body.get("Do") == "squash"
|
|
)
|
|
self.assertNotIn("do", merge_body)
|
|
self.assertFalse(merge_body["force_merge"])
|
|
self.assertFalse(merge_body["merge_when_checks_succeed"])
|
|
self.assertFalse(merge_body["delete_branch_after_merge"])
|
|
self.assertEqual(fixture.recorded_paths.count("/api/v1/version"), 1)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_unknown_gitea_version_fails_closed_before_merge_write(self) -> None:
|
|
fixture = FakeGiteaServer(gitea_version="1.28.0")
|
|
connector_instance, _ = connector(fixture)
|
|
try:
|
|
with self.assertRaisesRegex(
|
|
GiteaError,
|
|
"requires an identified 1.14, 1.25, 1.26, 1.27, or 28.x",
|
|
) as raised:
|
|
connector_instance.pull_request_merge(
|
|
context(), "hub-repository", 7, "e" * 40, "squash"
|
|
)
|
|
self.assertEqual(raised.exception.code, "GITEA_VERSION_UNSUPPORTED")
|
|
self.assertNotIn("/api/v1/repos/acme/hub/pulls/7/merge", fixture.recorded_paths)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_unrelated_merge_405_fails_closed_without_a_schema_retry(self) -> None:
|
|
fixture = FakeGiteaServer(deny_merge_transition=True)
|
|
connector_instance, _ = connector(fixture)
|
|
try:
|
|
with self.assertRaises(GiteaError) as raised:
|
|
connector_instance.pull_request_merge(
|
|
context(), "hub-repository", 7, "e" * 40, "squash"
|
|
)
|
|
self.assertEqual(raised.exception.code, "GITEA_UPSTREAM_MERGE_REJECTED")
|
|
self.assertEqual(
|
|
fixture.recorded_paths.count("/api/v1/repos/acme/hub/pulls/7/merge"), 1
|
|
)
|
|
merge_body = next(body for body in fixture.recorded_bodies if "do" in body)
|
|
self.assertNotIn("Do", merge_body)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_account_discovery_adds_future_repositories_without_changing_existing_refs(
|
|
self,
|
|
) -> None:
|
|
fixture = ClampedDiscoveryServer()
|
|
fixture._repos["acme/future"] = {
|
|
"owner": {"login": "acme"},
|
|
"name": "future",
|
|
"full_name": "acme/future",
|
|
"description": "Future project",
|
|
"private": True,
|
|
"default_branch": "main",
|
|
"updated_at": "2026-08-25T10:00:00Z",
|
|
}
|
|
connector_instance, _ = connector(fixture)
|
|
connector_instance.discovery_projects = {PROJECT_ID: "hub"}
|
|
try:
|
|
items = connector_instance.repository_list(context(), page=PageRequest(limit=100))[
|
|
"data"
|
|
]["items"]
|
|
refs = {item["repositoryRef"] for item in items}
|
|
self.assertIn("hub-repository", refs)
|
|
self.assertIn("all-future", refs)
|
|
self.assertIn("/api/v1/repos/search", fixture.recorded_paths)
|
|
self.assertIn("/api/v1/user/repos", fixture.recorded_paths)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_account_discovery_continues_after_provider_clamps_page_limit(self) -> None:
|
|
fixture = ClampedDiscoveryServer()
|
|
for index in range(85):
|
|
name = f"batch-{index:03}"
|
|
fixture._repos[f"acme/{name}"] = {
|
|
"owner": {"login": "acme"},
|
|
"name": name,
|
|
"full_name": f"acme/{name}",
|
|
"private": True,
|
|
"default_branch": "main",
|
|
}
|
|
fixture._repos["acme/workmesh"] = {
|
|
"owner": {"login": "acme"},
|
|
"name": "workmesh",
|
|
"full_name": "acme/workmesh",
|
|
"private": True,
|
|
"default_branch": "main",
|
|
}
|
|
connector_instance, _ = connector(fixture)
|
|
connector_instance.discovery_projects = {PROJECT_ID: "hub"}
|
|
try:
|
|
items = connector_instance.repository_list(context(), page=PageRequest(limit=100))[
|
|
"data"
|
|
]["items"]
|
|
self.assertIn("all-workmesh", {item["repositoryRef"] for item in items})
|
|
self.assertEqual(
|
|
fixture.discovery_page_requests,
|
|
[
|
|
("/api/v1/repos/search", 1, 100, 50),
|
|
("/api/v1/repos/search", 2, 100, 37),
|
|
("/api/v1/repos/search", 3, 100, 0),
|
|
("/api/v1/user/repos", 1, 100, 50),
|
|
("/api/v1/user/repos", 2, 100, 37),
|
|
("/api/v1/user/repos", 3, 100, 0),
|
|
],
|
|
)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_account_discovery_still_fails_closed_after_one_hundred_full_pages(self) -> None:
|
|
fixture = ClampedDiscoveryServer()
|
|
for index in range(5_000):
|
|
name = f"bounded-{index:04}"
|
|
fixture._repos[f"acme/{name}"] = {
|
|
"owner": {"login": "acme"},
|
|
"name": name,
|
|
"full_name": f"acme/{name}",
|
|
"private": True,
|
|
"default_branch": "main",
|
|
}
|
|
connector_instance, _ = connector(fixture)
|
|
connector_instance.discovery_projects = {PROJECT_ID: "hub"}
|
|
try:
|
|
with self.assertRaises(GiteaError) as raised:
|
|
connector_instance._refresh_discovered(PROJECT_ID)
|
|
self.assertEqual(raised.exception.code, "GITEA_DISCOVERY_LIMIT")
|
|
self.assertEqual(len(fixture.discovery_page_requests), 100)
|
|
self.assertEqual(fixture.discovery_page_requests[-1][1:], (100, 100, 50))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_account_discovery_keeps_private_results_when_user_repo_list_is_denied(self) -> None:
|
|
fixture = ClampedDiscoveryServer(deny_user_repositories=True)
|
|
connector_instance, _ = connector(fixture)
|
|
connector_instance.discovery_projects = {PROJECT_ID: "hub"}
|
|
try:
|
|
items = connector_instance.repository_list(context(), page=PageRequest(limit=100))[
|
|
"data"
|
|
]["items"]
|
|
self.assertIn("hub-repository", {item["repositoryRef"] for item in items})
|
|
self.assertIn("/api/v1/repos/search", fixture.recorded_paths)
|
|
self.assertIn("/api/v1/user/repos", fixture.recorded_paths)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_discovery_refs_are_deterministic_for_same_named_future_repositories(self) -> None:
|
|
repositories = [
|
|
{"owner": {"login": "zeta"}, "name": "shared", "private": True},
|
|
{"owner": {"login": "alpha"}, "name": "shared", "private": True},
|
|
]
|
|
first = GiteaRepositoryRegistry()
|
|
second = GiteaRepositoryRegistry()
|
|
first.add_discovered(PROJECT_ID, "hub", repositories)
|
|
second.add_discovered(PROJECT_ID, "hub", list(reversed(repositories)))
|
|
first_refs = {item.qualified_name: item.repository_ref for item in first.list(PROJECT_ID)}
|
|
second_refs = {item.qualified_name: item.repository_ref for item in second.list(PROJECT_ID)}
|
|
self.assertEqual(first_refs, second_refs)
|
|
self.assertEqual(first_refs["alpha/shared"], "all-alpha-shared")
|
|
self.assertEqual(first_refs["zeta/shared"], "all-zeta-shared")
|
|
|
|
def test_write_policy_accepts_any_non_default_branch_and_rejects_unsafe_targets(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
try:
|
|
current = context()
|
|
created = connector_instance.branch_create(
|
|
current, "hub-repository", "m15-backup-restore-dr", "main"
|
|
)["data"]
|
|
self.assertEqual(created["name"], "m15-backup-restore-dr")
|
|
pull = connector_instance.pull_request_create(
|
|
current,
|
|
"hub-repository",
|
|
"m15-backup-restore-dr",
|
|
"main",
|
|
"M15 backup and restore",
|
|
)["data"]
|
|
self.assertEqual(pull["headRef"], "m15-backup-restore-dr")
|
|
for branch in ("main", "feature/../main", "feature/x.lock"):
|
|
with self.subTest(branch=branch), self.assertRaises(GiteaError):
|
|
connector_instance.branch_create(current, "hub-repository", branch, "main")
|
|
with self.assertRaises(GiteaError):
|
|
connector_instance.file_update(
|
|
current,
|
|
"hub-repository",
|
|
"README.md",
|
|
"chatgpt/safe",
|
|
"content",
|
|
"message",
|
|
"not-a-sha",
|
|
)
|
|
with self.assertRaises(GiteaError):
|
|
connector_instance.file_create(
|
|
current,
|
|
"hub-repository",
|
|
"../secret",
|
|
"chatgpt/safe",
|
|
"content",
|
|
"message",
|
|
)
|
|
self.assertEqual(fixture.recorded_methods, ["GET", "POST", "POST", "GET"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_write_redirect_is_never_followed(self) -> None:
|
|
connector_instance, fixture = connector(
|
|
FakeGiteaServer(redirect_to="https://gitea.test/login")
|
|
)
|
|
try:
|
|
with self.assertRaisesRegex(GiteaError, "write redirects are not followed"):
|
|
connector_instance.issue_create(context(), "hub-repository", "Title")
|
|
self.assertEqual(fixture.recorded_methods, ["POST"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_write_methods_fail_closed_without_a_separate_write_client(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
connector_instance.write_client = None
|
|
try:
|
|
with self.assertRaisesRegex(GiteaError, "not enabled"):
|
|
connector_instance.issue_create(context(), "hub-repository", "Title")
|
|
self.assertEqual(fixture.recorded_methods, [])
|
|
self.assertTrue(connector_instance.health()["readOnly"])
|
|
self.assertEqual(connector_instance.readiness()["writeMode"], "disabled")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_gitea_failure_does_not_replace_knowledge_surface(self) -> None:
|
|
connector_instance, _ = connector(FakeGiteaServer(redirect_to="http://169.254.169.254/"))
|
|
try:
|
|
knowledge = KnowledgeConnector(index=InMemoryKnowledgeIndex())
|
|
self.assertEqual(knowledge.readiness()["status"], "ready")
|
|
with self.assertRaises(GiteaError):
|
|
connector_instance.repository_get(context(), "hub-repository")
|
|
self.assertEqual(knowledge.health()["status"], "healthy")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_secret_reference_and_transport_policy_are_strict(self) -> None:
|
|
with self.assertRaises(ValueError):
|
|
GiteaSettings(base_url="http://gitea.test", token_reference="raw-token") # noqa: S106
|
|
with self.assertRaises(ValueError):
|
|
GiteaSettings(
|
|
base_url="https://user:pass@gitea.test",
|
|
token_reference="docker-secret://gitea", # noqa: S106
|
|
)
|
|
|
|
def test_validation_workflows_use_an_isolated_python_environment(self) -> None:
|
|
local = (ROOT / ".gitea/workflows/chatgpt-validation.yml").read_text(encoding="utf-8")
|
|
scoped = (ROOT / ".gitea/scoped_workflows/chatgpt-validation.yml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
self.assertEqual(local, scoped)
|
|
self.assertIn('python -m venv "${RUNNER_TEMP}/chatgpt-uv"', local)
|
|
self.assertIn('UV_PROJECT_ENVIRONMENT="${RUNNER_TEMP}/chatgpt-project-venv"', local)
|
|
self.assertNotIn("--break-system-packages", local)
|
|
|
|
def test_managed_validation_is_pull_request_only_and_bounds_python_discovery(self) -> None:
|
|
self.assertIn(" pull_request:", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertIn(" workflow_dispatch:", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertNotIn(" push:", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertIn("git ls-files -z '*.py'", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertLess(
|
|
MANAGED_VALIDATION_WORKFLOW.index(
|
|
'export PATH="${RUNNER_TEMP}/managed-python/bin:${PATH}"'
|
|
),
|
|
MANAGED_VALIDATION_WORKFLOW.index("if [[ -f package.json ]]"),
|
|
)
|
|
self.assertIn("'^(<<<<<<< |=======$|>>>>>>> )'", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertNotIn("'^(<<<<<<<|=======|>>>>>>>)'", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertNotIn("python -m compileall", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertNotIn("--all-extras", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertIn("if: ${{ hashFiles('*.sln') != '' }}", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertIn(
|
|
"uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4",
|
|
MANAGED_VALIDATION_WORKFLOW,
|
|
)
|
|
self.assertIn("python_tests_ran=1", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertIn("run_node_test=0", MANAGED_VALIDATION_WORKFLOW)
|
|
self.assertIn("(?:pytest|unittest)", MANAGED_VALIDATION_WORKFLOW)
|
|
|
|
def test_governance_file_commit_skips_unrelated_push_workflows(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._managed_workflows.clear()
|
|
try:
|
|
preview = connector_instance.branch_protection_preview(context(), "hub-repository")[
|
|
"data"
|
|
]
|
|
connector_instance.branch_protection_ensure(
|
|
context(), "hub-repository", preview["planHash"]
|
|
)
|
|
workflow_commits = [
|
|
str(body.get("message", ""))
|
|
for body in fixture.recorded_bodies
|
|
if body.get("content")
|
|
]
|
|
self.assertTrue(workflow_commits)
|
|
self.assertTrue(all("[skip ci]" in message for message in workflow_commits))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_repository_engineer_eval_contract_references_published_safe_tools(self) -> None:
|
|
connector_instance, _ = connector()
|
|
try:
|
|
published = {
|
|
tool.canonical_name
|
|
for tool in connector_instance.tools() + connector_instance.write_tools()
|
|
}
|
|
payload = json.loads(
|
|
(ROOT / "evals/gitea-repository-engineer.json").read_text(encoding="utf-8")
|
|
)
|
|
self.assertGreaterEqual(len(payload["cases"]), 8)
|
|
for case in payload["cases"]:
|
|
for tool in case.get("expectedTools", []):
|
|
self.assertIn(tool, published)
|
|
self.assertTrue(
|
|
all(isinstance(tool, str) for tool in case.get("forbiddenTools", []))
|
|
)
|
|
self.assertNotIn("shell.execute", published)
|
|
self.assertNotIn("gitea.repository.delete", published)
|
|
destructive = {"gitea.branch.delete"}
|
|
confirmation_cases = {
|
|
tool
|
|
for case in payload["cases"]
|
|
if case.get("requiresConfirmation")
|
|
for tool in case.get("expectedTools", [])
|
|
}
|
|
self.assertTrue(destructive.issubset(confirmation_cases))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_validation_failure_extraction_is_bounded_and_sanitized(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._action_run.update({"status": "completed", "conclusion": "failure"})
|
|
fixture._action_job.update(
|
|
{
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"steps": [
|
|
{"name": "Unit tests", "conclusion": "failure"},
|
|
],
|
|
}
|
|
)
|
|
fixture._action_job_logs = (
|
|
"prepare\nRun python -m pytest\ncollecting\nERROR test_example failed\n"
|
|
"process exited with code 2\nafter\n"
|
|
)
|
|
try:
|
|
result = connector_instance.validation_failure_get(context(), "hub-repository", 42)
|
|
data = result["data"]
|
|
self.assertEqual(data["failureCount"], 1)
|
|
self.assertFalse(data["fullLogsIncluded"])
|
|
failure = data["failures"][0]
|
|
self.assertEqual(failure["step"], "Unit tests")
|
|
self.assertEqual(failure["exitCode"], 2)
|
|
self.assertEqual(failure["command"], "Run python -m pytest")
|
|
self.assertLessEqual(len(failure["relevantLogs"]), 100)
|
|
self.assertNotIn("fixture-token", json.dumps(result))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_validation_failure_extraction_enforces_aggregate_budget_and_inline_redaction(
|
|
self,
|
|
) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._action_run.update({"status": "completed", "conclusion": "failure"})
|
|
jobs = [
|
|
{
|
|
"id": index + 1,
|
|
"name": f"failure-{index}",
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"steps": [{"name": "test", "conclusion": "failure"}],
|
|
}
|
|
for index in range(30)
|
|
]
|
|
logs = "\n".join(
|
|
f"ERROR failure line {index} GITEA_TOKEN=opaquecredentialvalue{index}"
|
|
for index in range(150)
|
|
)
|
|
try:
|
|
with (
|
|
patch.object(connector_instance.client, "action_run_jobs", return_value=jobs),
|
|
patch.object(
|
|
connector_instance.client,
|
|
"action_job_logs",
|
|
return_value=(logs, False),
|
|
),
|
|
):
|
|
result = connector_instance.validation_failure_get(context(), "hub-repository", 42)
|
|
data = result["data"]
|
|
self.assertLessEqual(data["failureCount"], 20)
|
|
self.assertLessEqual(sum(len(item["relevantLogs"]) for item in data["failures"]), 100)
|
|
rendered = json.dumps(result)
|
|
self.assertNotIn("opaquecredentialvalue", rendered)
|
|
self.assertIn("<redacted>", rendered)
|
|
self.assertTrue(result["meta"]["truncated"])
|
|
self.assertLess(len(rendered.encode()), 262_144)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_change_reconcile_and_sha_locked_pull_request_sync(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
try:
|
|
reconciled = connector_instance.change_reconcile(
|
|
context(), "hub-repository", "codex/workflows"
|
|
)["data"]
|
|
self.assertEqual(reconciled["recommendedNextAction"], "review-and-merge")
|
|
self.assertFalse(reconciled["mutationPerformed"])
|
|
|
|
preview = connector_instance.pull_request_sync(
|
|
context(),
|
|
"hub-repository",
|
|
7,
|
|
"e" * 40,
|
|
"a" * 40,
|
|
"rebase",
|
|
True,
|
|
)["data"]
|
|
self.assertFalse(preview["synced"])
|
|
self.assertEqual(preview["conflictFiles"], [])
|
|
self.assertFalse(preview["conflictDetailsAvailable"])
|
|
|
|
synced = connector_instance.pull_request_sync(
|
|
context(),
|
|
"hub-repository",
|
|
7,
|
|
"e" * 40,
|
|
"a" * 40,
|
|
"rebase",
|
|
False,
|
|
)["data"]
|
|
self.assertTrue(synced["synced"])
|
|
self.assertTrue(synced["validationRequired"])
|
|
self.assertEqual(synced["newHeadSha"], "d" * 40)
|
|
self.assertEqual(synced["preconditionMode"], "optimistic-double-read")
|
|
self.assertTrue(
|
|
any(path.endswith("/pulls/7/update") for path in fixture.recorded_paths)
|
|
)
|
|
|
|
with self.assertRaises(GiteaError) as raised:
|
|
connector_instance.pull_request_sync(
|
|
context(),
|
|
"hub-repository",
|
|
7,
|
|
"e" * 40,
|
|
"a" * 40,
|
|
"merge",
|
|
False,
|
|
)
|
|
self.assertEqual(raised.exception.code, "GITEA_CONFLICT")
|
|
self.assertEqual(raised.exception.actual_state, "d" * 40)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_change_reconcile_detects_outdated_branch_on_gitea_127(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
fixture._reverse_compare_commits = [
|
|
{"sha": "d" * 40, "commit": {"message": "Advance default branch"}}
|
|
]
|
|
try:
|
|
reconciled = connector_instance.change_reconcile(
|
|
context(), "hub-repository", "codex/workflows"
|
|
)["data"]
|
|
self.assertEqual(reconciled["branchBehindBy"], 1)
|
|
self.assertEqual(reconciled["recommendedNextAction"], "sync-or-resolve-conflicts")
|
|
self.assertTrue(reconciled["validationRequired"])
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_pull_request_sync_fails_closed_when_state_moves_during_preparation(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
current = copy.deepcopy(fixture._pulls[0])
|
|
moved = copy.deepcopy(current)
|
|
moved["head"]["sha"] = "f" * 40
|
|
update_path = "/api/v1/repos/acme/hub/pulls/7/update"
|
|
try:
|
|
with patch.object(
|
|
connector_instance.client,
|
|
"pull_request",
|
|
side_effect=[current, moved],
|
|
):
|
|
with self.assertRaises(GiteaError) as raised:
|
|
connector_instance.pull_request_sync(
|
|
context(),
|
|
"hub-repository",
|
|
7,
|
|
"e" * 40,
|
|
"a" * 40,
|
|
"merge",
|
|
False,
|
|
)
|
|
self.assertEqual(raised.exception.code, "GITEA_CONCURRENT_MODIFICATION")
|
|
self.assertTrue(raised.exception.safe_to_retry)
|
|
self.assertFalse(any(path.startswith(update_path) for path in fixture.recorded_paths))
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_change_review_soak_stays_bounded_and_runtime_remains_usable(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
fixture._compare_commits = [
|
|
{
|
|
"sha": f"{index:040x}",
|
|
"commit": {"message": "bounded review " + ("x" * 5000)},
|
|
}
|
|
for index in range(60)
|
|
]
|
|
try:
|
|
for _ in range(100):
|
|
review = connector_instance.change_review(
|
|
context(), "hub-repository", "codex/workflows"
|
|
)
|
|
self.assertTrue(review["data"]["comparison"]["truncated"])
|
|
self.assertLess(len(json.dumps(review).encode()), 524_288)
|
|
capabilities = connector_instance.capabilities_get(context())["data"]
|
|
self.assertEqual(capabilities["toolCount"], 58)
|
|
self.assertEqual(capabilities["subsystems"]["session"], "RESUMABLE")
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_change_review_concurrent_drift_is_recoverable(self) -> None:
|
|
connector_instance, fixture = connector()
|
|
fixture._branches.append(
|
|
{"name": "codex/workflows", "commit": {"id": "e" * 40}, "protected": False}
|
|
)
|
|
moved = copy.deepcopy(fixture._pulls[0])
|
|
moved["head"]["sha"] = "f" * 40
|
|
try:
|
|
with patch.object(connector_instance.client, "pull_request", return_value=moved):
|
|
with self.assertRaises(GiteaError) as raised:
|
|
connector_instance.change_review(context(), "hub-repository", "codex/workflows")
|
|
error = raised.exception
|
|
self.assertEqual(error.code, "GITEA_CONCURRENT_MODIFICATION")
|
|
self.assertTrue(error.retryable)
|
|
self.assertTrue(error.safe_to_retry)
|
|
self.assertEqual(error.expected_state, "e" * 40)
|
|
self.assertEqual(error.actual_state, "f" * 40)
|
|
finally:
|
|
connector_instance.client.close()
|
|
|
|
def test_resilient_mcp_server_preserves_structured_gitea_error(self) -> None:
|
|
from mcp.shared.exceptions import MCPError
|
|
|
|
from services.runtime.mcp_server import ResilientMCPServer
|
|
|
|
server = ResilientMCPServer("resilience-test")
|
|
|
|
@server.tool(name="gitea.test.error")
|
|
async def failing_tool() -> dict[str, object]:
|
|
raise GiteaError(
|
|
"GITEA_UPSTREAM_UNAVAILABLE",
|
|
"Gitea is temporarily unavailable",
|
|
retryable=True,
|
|
safe_to_retry=True,
|
|
upstream_status=503,
|
|
)
|
|
|
|
with self.assertRaises(MCPError) as raised:
|
|
asyncio.run(server.call_tool("gitea.test.error", {}))
|
|
self.assertEqual(raised.exception.code, -32050)
|
|
self.assertEqual(raised.exception.data["operation"], "gitea.test.error")
|
|
self.assertEqual(raised.exception.data["upstreamStatus"], 503)
|
|
self.assertTrue(raised.exception.data["safeToRetry"])
|
|
|
|
def test_runtime_supervisor_recovers_after_a_following_successful_request(self) -> None:
|
|
from packages.hub_runtime.supervisor import RuntimeSupervisor
|
|
|
|
supervisor = RuntimeSupervisor()
|
|
supervisor.started()
|
|
supervisor.failed("UnexpectedFailure", fatal=True)
|
|
self.assertEqual(supervisor.snapshot()["circuitState"], "DEGRADED")
|
|
|
|
supervisor.started()
|
|
supervisor.completed()
|
|
recovered = supervisor.snapshot()
|
|
self.assertEqual(recovered["circuitState"], "CLOSED")
|
|
self.assertEqual(recovered["recoveryCount"], 1)
|
|
self.assertEqual(recovered["lastFatalErrorCode"], "UnexpectedFailure")
|
|
|
|
def test_gitea_get_retries_transient_failures_but_write_does_not(self) -> None:
|
|
calls: list[str] = []
|
|
|
|
def handle(request: httpx.Request) -> httpx.Response:
|
|
calls.append(request.method)
|
|
if request.method == "GET" and calls.count("GET") < 3:
|
|
return httpx.Response(503, json={"message": "temporary"}, request=request)
|
|
if request.method == "POST":
|
|
return httpx.Response(503, json={"message": "temporary"}, request=request)
|
|
return httpx.Response(
|
|
200,
|
|
json={"name": "hub", "private": True, "default_branch": "main"},
|
|
request=request,
|
|
)
|
|
|
|
settings = GiteaSettings(
|
|
base_url="http://gitea.test",
|
|
token_reference="docker-secret://gitea_mcp_token", # noqa: S106
|
|
allow_insecure_http_for_tests=True,
|
|
)
|
|
client = GiteaClient(
|
|
settings,
|
|
StaticTokenProvider("fixture-token"),
|
|
transport=httpx.MockTransport(handle),
|
|
)
|
|
repository = RegisteredRepository(
|
|
repository_ref="hub-repository",
|
|
project_id=PROJECT_ID,
|
|
owner="acme",
|
|
name="hub",
|
|
source_uri="itworx-git://hub/hub-repository",
|
|
private=True,
|
|
)
|
|
try:
|
|
with patch("packages.connector_kit.gitea.time.sleep"):
|
|
self.assertEqual(client.repository(repository)["name"], "hub")
|
|
with self.assertRaises(GiteaError) as raised:
|
|
client.create_issue(repository, "title", "body")
|
|
self.assertEqual(calls, ["GET", "GET", "GET", "POST"])
|
|
self.assertTrue(raised.exception.retryable)
|
|
self.assertFalse(raised.exception.safe_to_retry)
|
|
envelope = raised.exception.envelope(operation="gitea.issue.create")
|
|
self.assertEqual(envelope["subsystem"], "upstream_gitea")
|
|
self.assertFalse(envelope["safeToRetry"])
|
|
finally:
|
|
client.close()
|
|
|
|
def test_gitea_same_origin_redirect_get_keeps_structured_retry_semantics(self) -> None:
|
|
calls = 0
|
|
|
|
def handle(request: httpx.Request) -> httpx.Response:
|
|
nonlocal calls
|
|
calls += 1
|
|
if calls == 1:
|
|
return httpx.Response(
|
|
307,
|
|
headers={"location": str(request.url)},
|
|
request=request,
|
|
)
|
|
if calls < 4:
|
|
raise httpx.ConnectError("temporary", request=request)
|
|
return httpx.Response(
|
|
200,
|
|
json={"name": "hub", "private": True, "default_branch": "main"},
|
|
request=request,
|
|
)
|
|
|
|
client = GiteaClient(
|
|
GiteaSettings(
|
|
base_url="http://gitea.test",
|
|
token_reference="docker-secret://gitea_mcp_token", # noqa: S106
|
|
allow_insecure_http_for_tests=True,
|
|
),
|
|
StaticTokenProvider("fixture-token"),
|
|
transport=httpx.MockTransport(handle),
|
|
)
|
|
repository = RegisteredRepository(
|
|
repository_ref="hub-repository",
|
|
project_id=PROJECT_ID,
|
|
owner="acme",
|
|
name="hub",
|
|
source_uri="itworx-git://hub/hub-repository",
|
|
private=True,
|
|
)
|
|
try:
|
|
with patch("packages.connector_kit.gitea.time.sleep"):
|
|
self.assertEqual(client.repository(repository)["name"], "hub")
|
|
self.assertEqual(calls, 4)
|
|
finally:
|
|
client.close()
|
|
|
|
@unittest.skipUnless(
|
|
os.getenv("GITEA_LIVE_TESTS") == "1", "requires explicitly enabled configured Gitea"
|
|
)
|
|
def test_live_configured_gitea(self) -> None:
|
|
self.skipTest("Live acceptance is environment-owned and requires external credentials")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|