feat: fold bot-bottle-orchestrator into bot_bottle/orchestrator subpackage
Moves the orchestrator into bot_bottle/orchestrator/ so one install gets everything. Entry point is now `python -m bot_bottle.orchestrator run`. - Add bot_bottle/orchestrator/ with all 14 modules (verbatim move; internal imports were already relative, so no changes inside orchestrator modules) - Rewrite bootstrap.py: remove the lazy bot_bottle import guard, use direct relative imports from ..contrib.* - Add bot_bottle/contrib/forge/base.py: ScopedForge (read-anywhere / write-scoped) - Add bot_bottle/contrib/gitea/client.py: GiteaClient + GiteaForge (urllib.request only) - Add bot_bottle/contrib/gitea/forge_state.py: ForgeState + SqliteForgeStateStore - Add tests/unit/orchestrator/ (82 tests: 63 migrated + 19 new for contrib modules) Closes #321
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Shared test doubles: a duck-typed forge and bottle runner."""
|
||||
|
||||
# Test doubles mirror an API shape; some params are intentionally unused.
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from bot_bottle.orchestrator.runner import RunResult, slugify
|
||||
|
||||
|
||||
class FakeForge:
|
||||
def __init__(self, members: tuple[str, ...] = ()) -> None:
|
||||
self.members = set(members)
|
||||
self.comments: list[tuple[int, str]] = []
|
||||
self.descriptions: list[tuple[int, str]] = []
|
||||
self.scope_denied: set[int] = set()
|
||||
|
||||
def is_org_member(self, org: str, username: str) -> bool:
|
||||
return username in self.members
|
||||
|
||||
def read_issue(self, number: int) -> dict[str, object]:
|
||||
return {"number": number, "kind": "issue"}
|
||||
|
||||
def read_pr(self, number: int) -> dict[str, object]:
|
||||
return {"number": number, "merged": False}
|
||||
|
||||
def read_comments(self, number: int) -> list[dict[str, object]]:
|
||||
return [{"id": 1, "user": "alice", "body": "hi"}]
|
||||
|
||||
def post_comment(self, number: int, body: str) -> None:
|
||||
if number in self.scope_denied:
|
||||
raise PermissionError(f"write to #{number} denied")
|
||||
self.comments.append((number, body))
|
||||
|
||||
def update_description(self, number: int, body: str) -> None:
|
||||
if number in self.scope_denied:
|
||||
raise PermissionError(f"write to #{number} denied")
|
||||
self.descriptions.append((number, body))
|
||||
|
||||
|
||||
class FakeRunner:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[tuple[object, ...]] = []
|
||||
|
||||
def start(
|
||||
self,
|
||||
*,
|
||||
agent: str,
|
||||
bottles: Sequence[str],
|
||||
label: str,
|
||||
prompt: str,
|
||||
forge_env: dict[str, str],
|
||||
) -> RunResult:
|
||||
self.calls.append(("start", agent, tuple(bottles), label, prompt, dict(forge_env)))
|
||||
return RunResult(slug=slugify(label), exit_code=0)
|
||||
|
||||
def freeze(self, slug: str) -> int:
|
||||
self.calls.append(("freeze", slug))
|
||||
return 0
|
||||
|
||||
def resume(self, slug: str, prompt: str) -> RunResult:
|
||||
self.calls.append(("resume", slug, prompt))
|
||||
return RunResult(slug=slug, exit_code=0)
|
||||
|
||||
def destroy(self, slug: str) -> int:
|
||||
self.calls.append(("destroy", slug))
|
||||
return 0
|
||||
Reference in New Issue
Block a user