"""Deterministic in-memory Gitea HTTP transport for contract tests.""" from __future__ import annotations import base64 import json from typing import Any from urllib.parse import parse_qs, unquote, urlparse import httpx from .managed_validation import ( MANAGED_VALIDATION_CONTEXT, MANAGED_VALIDATION_PATH, MANAGED_VALIDATION_WORKFLOW, ) class FakeGiteaServer: """A tiny bounded Gitea API fixture with observable requests and no network.""" def __init__( self, *, unsupported_search: bool = False, deny_user_repositories: bool = False, deny_protected_workflow_writes: bool = False, redirect_to: str | None = None, gitea_version: str = "1.26.4", deny_merge_transition: bool = False, ) -> None: self.unsupported_search = unsupported_search self.deny_user_repositories = deny_user_repositories self.deny_protected_workflow_writes = deny_protected_workflow_writes self.redirect_to = redirect_to self.gitea_version = gitea_version self.deny_merge_transition = deny_merge_transition self.recorded_methods: list[str] = [] self.recorded_authorizations: list[str] = [] self.recorded_paths: list[str] = [] self.recorded_bodies: list[dict[str, Any]] = [] self._repos = { "acme/hub": { "owner": {"login": "acme"}, "name": "hub", "full_name": "acme/hub", "description": "ITWorx MCP Hub", "private": True, "default_branch": "main", "updated_at": "2026-08-01T10:00:00Z", "token": "fixture-token-must-not-leak", } } self._branches: list[dict[str, Any]] = [ {"name": "main", "commit": {"id": "a" * 40}, "protected": True}, {"name": "release", "commit": {"id": "b" * 40}, "protected": False}, {"name": "feature", "commit": {"id": "c" * 40}, "protected": False}, ] self._branch_protections: dict[str, list[dict[str, Any]]] = { "acme/hub": [ { "rule_name": "main", "branch_name": "main", "priority": 1, "enable_push": False, "enable_push_whitelist": False, "push_whitelist_usernames": [], "push_whitelist_teams": [], "push_whitelist_deploy_keys": False, "enable_force_push": False, "enable_force_push_allowlist": False, "force_push_allowlist_usernames": [], "force_push_allowlist_teams": [], "force_push_allowlist_deploy_keys": False, "enable_merge_whitelist": False, "merge_whitelist_usernames": [], "merge_whitelist_teams": [], "enable_bypass_allowlist": False, "bypass_allowlist_usernames": [], "bypass_allowlist_teams": [], "enable_status_check": True, "status_check_contexts": [MANAGED_VALIDATION_CONTEXT], "required_approvals": 0, "enable_approvals_whitelist": False, "approvals_whitelist_username": [], "approvals_whitelist_teams": [], "block_on_rejected_reviews": True, "block_on_official_review_requests": False, "block_on_outdated_branch": True, "dismiss_stale_approvals": True, "ignore_stale_approvals": False, "require_signed_commits": False, "protected_file_patterns": "", "unprotected_file_patterns": "", "block_admin_merge_override": True, } ] } self._managed_workflows: dict[str, str] = {"acme/hub": MANAGED_VALIDATION_WORKFLOW} self._commits = [ { "sha": "a" * 40, "commit": {"message": "Add bounded connector", "author": {"name": "Ada"}}, }, {"sha": "b" * 40, "commit": {"message": "Document the API", "author": {"name": "Lin"}}}, {"sha": "c" * 40, "commit": {"message": "Add tests", "author": {"name": "Kai"}}}, ] self._compare_commits = [self._commits[0]] self._reverse_compare_commits: list[dict[str, Any]] = [] self._releases = [ {"tag_name": "v1.0.0", "name": "First release", "draft": False, "prerelease": False} ] self._issues = [ { "number": 1, "title": "Bounded read API", "state": "open", "html_url": "https://gitea.example/acme/hub/issues/1", } ] self._pulls: list[dict[str, Any]] = [ { "number": 7, "title": "Add governed Codex workflows", "state": "open", "draft": False, "mergeable": True, "html_url": "https://gitea.example/acme/hub/pulls/7", "head": {"ref": "codex/workflows", "sha": "e" * 40}, "base": {"ref": "main", "sha": "a" * 40}, "updated_at": "2026-08-21T20:00:00Z", } ] self._pull_files = [ { "filename": "services/runtime/mcp_server.py", "status": "modified", "additions": 42, "deletions": 2, } ] self._statuses = [ { "status": "success", "context": MANAGED_VALIDATION_CONTEXT, "description": "All checks passed", "target_url": "https://gitea.example/acme/hub/actions/runs/42", "created_at": "2026-08-21T20:05:00Z", } ] self._file = "# ITWorx MCP Hub\n\nThis is a safe fixture.\n" self._reviews: list[dict[str, Any]] = [] self._review_comments = [ {"id": 12, "path": "README.md", "new_position": 1, "body": "Clarify this."} ] self._action_run = { "id": 42, "name": "ChatGPT validation", "status": "completed", "conclusion": "success", "event": "pull_request", "head_branch": "codex/workflows", "head_sha": "e" * 40, "workflow_id": "managed-validation.yml", "html_url": "https://gitea.example/acme/hub/actions/runs/42", } self._action_job = { "id": 43, "name": "full", "status": "completed", "conclusion": "success", } self._action_job_logs = "all checks passed\n" def transport(self) -> httpx.MockTransport: return httpx.MockTransport(self._handle) def _handle(self, request: httpx.Request) -> httpx.Response: self.recorded_methods.append(request.method) self.recorded_authorizations.append(request.headers.get("authorization", "")) if self.redirect_to is not None: return httpx.Response(302, headers={"location": self.redirect_to}, request=request) parsed = urlparse(str(request.url)) self.recorded_paths.append(parsed.path) if not parsed.path.startswith("/api/v1/"): return self._json(404, {"message": "API prefix missing"}, request) path = parsed.path.removeprefix("/api/v1/") query = parse_qs(parsed.query) body: dict[str, Any] = {} if request.content: decoded = json.loads(request.content.decode("utf-8")) body = decoded if isinstance(decoded, dict) else {} self.recorded_bodies.append(body) if path == "version" and request.method == "GET": return self._json(200, {"version": self.gitea_version}, request) if path == "repos/search": if self.unsupported_search: return self._json(501, {"message": "unsupported"}, request) if "q" not in query: repositories = list(self._repos.values()) if query.get("private") == ["true"]: repositories = [item for item in repositories if item.get("private") is True] return self._json(200, {"ok": True, "data": repositories}, request) return self._json( 200, { "ok": True, "data": [{"repository": {"full_name": "acme/hub"}, "path": "README.md"}], }, request, ) if path == "user" and request.method == "GET": return self._json(200, {"login": "acme"}, request) if path == "user/repos" and request.method == "GET": if self.deny_user_repositories: return self._json(403, {"message": "denied"}, request) return self._paged(list(self._repos.values()), query, request) if path == "user/repos" and request.method == "POST": name = str(body.get("name", "")) qualified_name = f"acme/{name}" if qualified_name in self._repos: return self._json(409, {"message": "repository already exists"}, request) auto_init = bool(body.get("auto_init", True)) repository = { "owner": {"login": "acme"}, "name": name, "full_name": qualified_name, "description": str(body.get("description", "")), "private": bool(body.get("private", True)), "empty": not auto_init, "default_branch": str(body.get("default_branch", "main")) if auto_init else "", "html_url": f"https://gitea.example/{qualified_name}", "updated_at": "2026-08-26T15:00:00Z", } self._repos[qualified_name] = repository return self._json(201, repository, request) if path == "repos/issues/search" and request.method == "GET": state = str(query.get("state", ["open"])[0]) issues = [ { **item, "repository": { "full_name": "acme/hub", "name": "hub", "owner": {"login": "acme"}, }, "updated_at": "2026-08-01T10:00:00Z", "labels": [], } for item in self._issues if state == "all" or item.get("state") == state ] return self._paged(issues, query, request) if path.startswith("repos/"): path = path.removeprefix("repos/") parts = path.split("/") qualified_name = "/".join(parts[:2]) if len(parts) >= 2 and qualified_name not in self._repos: return self._json(404, {"message": "not found"}, request) if len(parts) == 2: if request.method == "DELETE": self._repos.pop(qualified_name, None) self._branch_protections.pop(qualified_name, None) self._managed_workflows.pop(qualified_name, None) return httpx.Response(204, request=request) if request.method != "GET": return self._json(405, {"message": "method not allowed"}, request) return self._json(200, self._repos[qualified_name], request, leaked_header=True) if parts[2] == "branch_protections": protections = self._branch_protections.setdefault(qualified_name, []) if len(parts) == 3: if request.method == "POST": created = dict(body) created.setdefault("rule_name", str(body.get("branch_name", ""))) created.setdefault("branch_name", str(body.get("rule_name", ""))) protections.append(created) branch_name = str(created.get("rule_name") or created.get("branch_name")) for branch in self._branches: if branch.get("name") == branch_name: branch["protected"] = True return self._json(201, created, request) return self._json(200, protections, request) rule_name = unquote(parts[3]) protection = next( ( item for item in protections if item.get("rule_name") == rule_name or item.get("branch_name") == rule_name ), None, ) if protection is None: return self._json(404, {"message": "not found"}, request) if request.method == "PATCH": protection.update(body) return self._json(200, protection, request) if request.method == "DELETE": protections.remove(protection) return httpx.Response(204, request=request) return self._json(200, protection, request) if parts[2] == "branches": if request.method == "POST": created = { "name": body.get("new_branch_name", ""), "commit": {"id": "f" * 40}, "protected": False, } self._branches.append(created) return self._json(201, created, request) if len(parts) == 4: branch_name = unquote(parts[3]) matched_branch: dict[str, object] | None = None for item in self._branches: if item.get("name") == branch_name: matched_branch = item break if matched_branch is None: return self._json(404, {"message": "not found"}, request) if request.method == "DELETE": self._branches.remove(matched_branch) return httpx.Response(204, request=request) return self._json(200, matched_branch, request) return self._paged(self._branches, query, request) if parts[2] == "commits" and len(parts) == 3: return self._paged(self._commits, query, request) if parts[2:4] == ["git", "commits"] and len(parts) == 5: return self._json( 200, { **self._commits[0], "sha": unquote(parts[4]), "verification": {"verified": True, "reason": "gpg"}, "stats": {"additions": 42, "deletions": 2, "total": 44}, "files": [{"filename": "README.md", "status": "modified"}], "html_url": "https://gitea.example/acme/hub/commit/example", }, request, ) if parts[2] == "compare" and len(parts) == 4: compare_range = unquote(parts[3]) reverse_ranges: set[str] = set() for pull in self._pulls: head = pull.get("head") base = pull.get("base") if isinstance(head, dict) and isinstance(base, dict): reverse_ranges.add(f"{head.get('sha', '')}...{base.get('sha', '')}") compare_commits = ( self._reverse_compare_commits if compare_range in reverse_ranges else self._compare_commits ) return self._json( 200, { "total_commits": len(compare_commits), "commits": compare_commits, "files": [{"filename": "README.md", "status": "modified"}], }, request, ) if parts[2] == "releases": return self._paged(self._releases, query, request) if parts[2] == "issues": if request.method == "POST" and len(parts) == 3: created_issue = { "number": len(self._issues) + 1, "title": body.get("title", ""), "state": "open", "html_url": f"https://gitea.example/acme/hub/issues/{len(self._issues) + 1}", } self._issues.append(created_issue) return self._json(201, created_issue, request) if len(parts) == 4: issue_number = int(parts[3]) issue = next( (item for item in self._issues if item.get("number") == issue_number), None ) if issue is None: return self._json(404, {"message": "not found"}, request) if request.method == "PATCH": issue.update( { key: value for key, value in body.items() if key in {"title", "body", "state"} } ) return self._json(200, issue, request) if request.method == "POST" and len(parts) == 5 and parts[4] == "comments": return self._json( 201, { "id": 99, "body": body.get("body", ""), "html_url": f"https://gitea.example/acme/hub/issues/{parts[3]}#issuecomment-99", }, request, ) return self._paged(self._issues, query, request) if parts[2] == "pulls" and len(parts) == 3: if request.method == "POST": created_pull = { "number": len(self._pulls) + 8, "title": body.get("title", ""), "state": "open", "draft": False, "mergeable": True, "html_url": "https://gitea.example/acme/hub/pulls/8", "head": {"ref": body.get("head", ""), "sha": "f" * 40}, "base": {"ref": body.get("base", ""), "sha": "a" * 40}, "updated_at": "2026-08-24T20:00:00Z", } self._pulls.append(created_pull) return self._json(201, created_pull, request) return self._paged(self._pulls, query, request) if parts[2] == "pulls" and len(parts) == 4 and parts[3] == "7": if request.method == "PATCH": self._pulls[0].update( {key: value for key, value in body.items() if key in {"title", "body", "state"}} ) if body.get("state") == "open": self._action_run.update( event="pull_request", status="queued", conclusion="", head_sha=self._pulls[0]["head"]["sha"], ) return self._json(200, self._pulls[0], request) if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "update"]: if query.get("style") not in [["merge"], ["rebase"]]: return self._json(422, {"message": "invalid style"}, request) head = self._pulls[0].get("head") base = self._pulls[0].get("base") if not isinstance(head, dict) or not isinstance(base, dict): return self._json(500, {"message": "invalid fake pull request"}, request) head["sha"] = "d" * 40 base["sha"] = "a" * 40 return self._json(200, {}, request) if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "merge"]: if self.deny_merge_transition: return self._json(405, {"message": "merge blocked by repository policy"}, request) strategy_key = "Do" if self.gitea_version.startswith(("1.14.", "1.25.")) else "do" if body.get(strategy_key) not in { "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", }: return self._json(405, {"message": "unsupported merge strategy"}, request) self._pulls[0].update({"state": "closed", "merged": True, "merge_commit_sha": "6" * 40}) for branch in self._branches: if branch.get("name") == "main": branch["commit"]["id"] = "6" * 40 return self._json(200, {}, request) if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "files"]: return self._paged(self._pull_files, query, request) if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "reviews"]: return self._paged(self._reviews, query, request) if ( parts[2] == "pulls" and len(parts) == 7 and parts[3:5] == ["7", "reviews"] and parts[6] == "comments" ): return self._json(200, self._review_comments, request) if parts[2] == "commits" and len(parts) == 5 and parts[4] == "statuses": return self._paged(self._statuses, query, request) if parts[2:4] == ["git", "trees"] and len(parts) == 5: if self.unsupported_search: return self._json(501, {"message": "unsupported"}, request) return self._json( 200, { "sha": "a" * 40, "tree": [ { "path": "README.md", "type": "blob", "sha": "d" * 40, "size": len(self._file.encode()), } ], "truncated": False, }, request, ) if parts[2:4] == ["git", "blobs"] and len(parts) == 5: return self._json( 200, { "sha": parts[4], "encoding": "base64", "content": base64.b64encode(self._file.encode()).decode(), "size": len(self._file.encode()), }, request, ) if parts[2:4] == ["actions", "workflows"] and len(parts) == 4: workflows = [] if qualified_name in self._managed_workflows: workflows.append( { "id": "managed-validation.yml", "name": "Managed validation", "path": MANAGED_VALIDATION_PATH, } ) return self._json( 200, {"workflows": workflows}, request, ) if parts[2:4] == ["actions", "workflows"] and len(parts) == 6 and parts[5] == "dispatches": raw_inputs = body.get("inputs") inputs: dict[str, Any] = raw_inputs if isinstance(raw_inputs, dict) else {} profile = str(inputs.get("profile", "full")) ref = str(body.get("ref", "")) dispatch_branch: dict[str, Any] | None = next( (item for item in self._branches if item.get("name") == ref), None ) head_sha = str( (dispatch_branch or {}).get("commit", {}).get("id", self._action_run["head_sha"]) ) self._action_run.update( { "event": "workflow_dispatch", "head_branch": ref, "head_sha": head_sha, "workflow_id": unquote(parts[4]), "display_title": f"Managed validation / {profile}", } ) self._action_job["name"] = "full" return self._json(200, self._action_run, request) if parts[2:4] == ["actions", "runs"] and len(parts) == 4: return self._json(200, {"workflow_runs": [self._action_run]}, request) if parts[2:4] == ["actions", "runs"] and len(parts) == 6 and parts[5] == "rerun": self._action_run.update(status="queued", conclusion="") return httpx.Response(204, request=request) if parts[2:4] == ["actions", "runs"] and len(parts) == 5: return self._json(200, self._action_run, request) if parts[2:4] == ["actions", "runs"] and len(parts) == 6 and parts[5] == "jobs": return self._json(200, {"jobs": [self._action_job]}, request) if parts[2:4] == ["actions", "jobs"] and len(parts) == 6 and parts[5] == "logs": return httpx.Response( 200, headers={"content-type": "text/plain"}, content=self._action_job_logs.encode(), request=request, ) if parts[2] == "contents" and len(parts) == 3: return self._json( 200, [ { "name": "README.md", "path": "README.md", "type": "file", "size": len(self._file.encode()), "sha": "d" * 40, } ], request, ) if ( parts[2] == "contents" and len(parts) >= 4 and "/".join(parts[3:]) == MANAGED_VALIDATION_PATH ): existing = self._managed_workflows.get(qualified_name) protection = next(iter(self._branch_protections.get(qualified_name, [])), None) temporary_push_allowed = bool( protection and protection.get("enable_push") is True and protection.get("enable_push_whitelist") is True and "acme" in protection.get("push_whitelist_usernames", []) ) if ( request.method in {"POST", "PUT"} and self.deny_protected_workflow_writes and protection is not None and not temporary_push_allowed ): return self._json(403, {"message": "protected branch"}, request) if request.method == "POST": if existing is not None: return self._json(422, {"message": "already exists"}, request) self._managed_workflows[qualified_name] = base64.b64decode( str(body.get("content", "")) ).decode() return self._json( 201, {"content": {"path": MANAGED_VALIDATION_PATH, "sha": "9" * 40}}, request, ) if existing is None: return self._json(404, {"message": "not found"}, request) if request.method == "PUT": self._managed_workflows[qualified_name] = base64.b64decode( str(body.get("content", "")) ).decode() return self._json( 200, {"content": {"path": MANAGED_VALIDATION_PATH, "sha": "9" * 40}}, request, ) if request.method == "DELETE": self._managed_workflows.pop(qualified_name, None) return self._json(200, {"commit": {"sha": "7" * 40}}, request) return self._json( 200, { "name": "managed-validation.yml", "path": MANAGED_VALIDATION_PATH, "sha": "d" * 40, "content": base64.b64encode(existing.encode()).decode(), "encoding": "base64", }, request, ) if parts[2] == "contents" and len(parts) >= 4 and "/".join(parts[3:]) == "README.md": if request.method == "DELETE": return self._json(200, {"commit": {"sha": "7" * 40}}, request) if request.method in {"POST", "PUT"}: if request.method == "POST" and self._repos[qualified_name].get("empty"): branch_name = str(body.get("branch", "main")) self._repos[qualified_name]["empty"] = False self._repos[qualified_name]["default_branch"] = branch_name if not any(item.get("name") == branch_name for item in self._branches): self._branches.append( { "name": branch_name, "commit": {"id": "8" * 40}, "protected": False, } ) return self._json( 201 if request.method == "POST" else 200, { "content": {"path": "README.md", "sha": "9" * 40}, "commit": {"sha": "8" * 40}, }, request, ) return self._json( 200, { "name": "README.md", "path": "README.md", "sha": "d" * 40, "content": base64.b64encode(self._file.encode()).decode(), "encoding": "base64", "token": "fixture-token-must-not-leak", }, request, ) return self._json(404, {"message": "not found"}, request) def _paged( self, items: list[dict[str, Any]], query: dict[str, list[str]], request: httpx.Request ) -> httpx.Response: page = max(int(query.get("page", ["1"])[0]), 1) limit = max(int(query.get("limit", ["100"])[0]), 1) start = (page - 1) * limit return self._json(200, items[start : start + limit], request) @staticmethod def _json( status: int, payload: object, request: httpx.Request, *, leaked_header: bool = False ) -> httpx.Response: headers = {"content-type": "application/json"} if leaked_header: headers["x-fixture-token"] = "fixture-token-must-not-leak" return httpx.Response( status, headers=headers, content=json.dumps(payload).encode(), request=request )