678 lines
30 KiB
Python
678 lines
30 KiB
Python
"""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
|
|
)
|