"""Deterministic Gitea double used by the release-artifact browser journey.""" from __future__ import annotations import json from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from threading import Event from urllib.parse import parse_qs, urlsplit REPOSITORY = { "id": 7, "name": "mobile", "full_name": "acme/mobile", "description": "Mobile filing acceptance repository", "html_url": "http://127.0.0.1/acme/mobile", "owner": {"login": "acme"}, } USER = {"id": 1, "login": "timmy", "full_name": "Timmy"} AVAILABLE_ISSUES = [ { "id": 101, "number": 41, "title": "Ship mobile capture", "body": "Complete the capture flow on a phone.", "state": "open", "html_url": "http://127.0.0.1/acme/mobile/issues/41", "repository": REPOSITORY, "user": USER, "assignees": [], "labels": [{"name": "P1"}], "updated_at": "2026-08-15T12:00:00Z", }, { "id": 102, "number": 42, "title": "Polish desktop filters", "body": "Keep the desktop filter controls clear.", "state": "open", "html_url": "http://127.0.0.1/acme/mobile/issues/42", "repository": REPOSITORY, "user": USER, "assignees": [], "labels": [], "updated_at": "2026-08-15T11:00:00Z", }, ] class FakeGiteaServer(ThreadingHTTPServer): def __init__(self, address: tuple[str, int]): super().__init__(address, FakeGiteaHandler) self.created_issues: list[dict] = [] self.issue_creation_enabled = False self.issue_creation_ready = Event() self.assigned_issue_numbers = [issue["number"] for issue in AVAILABLE_ISSUES] self.comments: list[tuple[int, str]] = [] self.activity_comments: dict[int, list[dict]] = {} self.edited_comments: list[tuple[int, int, str]] = [] self.requests: list[tuple[str, str]] = [] class FakeGiteaHandler(BaseHTTPRequestHandler): server: FakeGiteaServer def log_message(self, _format: str, *_args: object) -> None: return def _json(self, status: int, payload: object, **headers: str) -> None: body = json.dumps(payload).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) for name, value in headers.items(): self.send_header(name, value) self.end_headers() self.wfile.write(body) def do_GET(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API parsed = urlsplit(self.path) path = parsed.path query = parse_qs(parsed.query) self.server.requests.append(("GET", self.path)) if path == "/api/v1/user": self._json(200, USER) elif path == "/api/v1/user/repos": self._json(200, [REPOSITORY], **{"X-Total-Count": "1"}) elif path in {"/api/v1/repos/acme/mobile", "/api/v1/repos/acme/mobile/"}: self._json(200, REPOSITORY) elif path == "/api/v1/repos/search": self._json(200, {"data": [REPOSITORY], "ok": True}) elif path == "/api/v1/repos/acme/mobile/assignees": self._json(200, [ USER, {"id": 2, "login": "alex", "full_name": "Alexander", "active": True}, ]) elif path == "/api/v1/repos/issues/search": is_assigned_scan = query.get("assigned") == ["true"] and query.get("type") == ["issues"] is_available_scan = not any( name in query for name in ("assigned", "created", "review_requested", "q") ) and query.get("type") == ["issues"] assigned = [ {**issue, "assignees": [USER]} for issue in AVAILABLE_ISSUES if issue["number"] in self.server.assigned_issue_numbers ] available = [ issue for issue in AVAILABLE_ISSUES if issue["number"] not in self.server.assigned_issue_numbers ] issues = assigned if is_assigned_scan else available if is_available_scan else [] self._json(200, issues, **{"X-Total-Count": str(len(issues))}) elif path.startswith("/api/v1/repos/acme/mobile/issues/comments/"): try: comment_id = int(path.rsplit("/", 1)[-1]) except ValueError: comment_id = 0 comment = next((item for comments in self.server.activity_comments.values() for item in comments if item.get("id") == comment_id), None) self._json(200, {**comment, "user": USER}) if comment else self._json(404, {"message": "not found"}) elif path.startswith("/api/v1/repos/acme/mobile/issues/") and path.endswith("/comments"): try: number = int(path.split("/")[-2]) except ValueError: number = 0 comments = self.server.activity_comments.get(number, []) self._json(200, comments, **{"X-Total-Count": str(len(comments))}) elif path.startswith("/api/v1/repos/acme/mobile/issues/") and path.endswith("/dependencies"): self._json(200, []) elif path.startswith("/api/v1/repos/acme/mobile/issues/"): try: number = int(path.rsplit("/", 1)[-1]) except ValueError: number = 0 issue = next((item for item in AVAILABLE_ISSUES if item["number"] == number), None) if issue and number in self.server.assigned_issue_numbers: issue = {**issue, "assignees": [USER]} self._json(200, issue) if issue else self._json(404, {"message": "not found"}) elif path.startswith("/api/v1/"): self._json(200, [], **{"X-Total-Count": "0"}) else: self._json(404, {"message": "not found"}) def do_POST(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API path = urlsplit(self.path).path self.server.requests.append(("POST", self.path)) length = int(self.headers.get("Content-Length", "0")) payload = json.loads(self.rfile.read(length) or b"{}") if path.startswith("/api/v1/repos/acme/mobile/issues/") and path.endswith("/comments"): try: number = int(path.split("/")[-2]) except ValueError: self._json(404, {"message": "not found"}) return body = str(payload.get("body", "")) self.server.comments.append((number, body)) self._json( 201, { "id": len(self.server.comments), "body": body, "user": USER, "created_at": "2026-08-16T20:00:00Z", "updated_at": "2026-08-16T20:00:00Z", "html_url": f"http://127.0.0.1/acme/mobile/issues/{number}#issuecomment-1", }, ) return if path != "/api/v1/repos/acme/mobile/issues": self._json(404, {"message": "not found"}) return if not self.server.issue_creation_enabled: self.server.issue_creation_ready.wait(timeout=45) if not self.server.issue_creation_enabled: self._json(503, {"message": "release journey is still offline"}) return self.server.created_issues.append(payload) assignee = payload.get("assignee") self._json( 201, { "id": 101, "number": 41, "title": payload.get("title", ""), "body": payload.get("body", ""), "state": "open", "html_url": "http://127.0.0.1/acme/mobile/issues/41", "assignees": [{"login": assignee}] if assignee else [], "labels": [], "milestone": None, "due_date": payload.get("due_date"), "updated_at": "2026-08-15T12:00:00Z", }, ) def do_PATCH(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API path = urlsplit(self.path).path self.server.requests.append(("PATCH", self.path)) length = int(self.headers.get("Content-Length", "0")) payload = json.loads(self.rfile.read(length) or b"{}") parts = path.strip("/").split("/") if len(parts) == 8 and parts[:7] == ["api", "v1", "repos", "acme", "mobile", "issues", "comments"]: try: comment_id = int(parts[7]) except ValueError: self._json(404, {"message": "not found"}) return match = next(((number, item) for number, comments in self.server.activity_comments.items() for item in comments if item.get("id") == comment_id), None) if match is None: self._json(404, {"message": "not found"}) return number, comment = match body = str(payload.get("body", "")) comment["body"] = body self.server.edited_comments.append((number, comment_id, body)) self._json(200, {**comment, "user": USER, "updated_at": "2026-08-18T01:00:00Z"}) return if len(parts) == 9 and parts[:5] == ["api", "v1", "repos", "acme", "mobile"] and parts[5] == "issues" and parts[7] == "comments": try: number, comment_id = int(parts[6]), int(parts[8]) except ValueError: self._json(404, {"message": "not found"}) return comments = self.server.activity_comments.get(number, []) comment = next((item for item in comments if item.get("id") == comment_id), None) if comment is None: self._json(404, {"message": "not found"}) return body = str(payload.get("body", "")) comment["body"] = body self.server.edited_comments.append((number, comment_id, body)) self._json(200, {**comment, "user": USER, "updated_at": "2026-08-18T01:00:00Z"}) return if not path.startswith("/api/v1/repos/acme/mobile/issues/"): self._json(404, {"message": "not found"}) return try: number = int(path.rsplit("/", 1)[-1]) except ValueError: self._json(404, {"message": "not found"}) return issue = next((item for item in AVAILABLE_ISSUES if item["number"] == number), None) if issue is None: self._json(404, {"message": "not found"}) return assignee = payload.get("assignee") self.server.assigned_issue_numbers.append(number) self._json(200, {**issue, "assignees": [{"login": assignee}] if assignee else []})