This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/agent-team/tests/test_github_live.py
Adam Moussa 0842ff778b feat(agent-team): P4 live github/claude_code transports + GitHub-issue intake
Live github (issue-comment poster) and claude_code (file-drop) transports, plus GithubIntake (labeled issue -> coordinator.start_task, de-duped). run-team _build_transport now wires github/claude_code live (was SystemExit) + adds the intake-github subcommand. claude_code drop-path also neutralizes backslash (defense-in-depth).
2026-06-18 13:23:02 -04:00

333 lines
11 KiB
Python

"""Unit tests for agent_team.transport.github_live (§3.3.1, §7.1 P4).
The live poster is the production ``requests`` backing for the §3.3.1 injected
``HttpPost`` seam. These tests prove the contract entirely with mocks (no
network, and ``requests`` itself is never required): the poster performs the
REST POST and returns ``(status, data)``; the posted comment carries the
``<!-- shq:<question_id> -->`` marker; the new comment ``id`` round-trips as the
``channel_ref`` through a real ``GitHubTransport``; a missing package / token
fails loudly; and a non-2xx response surfaces as ``GitHubApiError``.
No CI, OIDC, git-apply, or GitHub Actions surface is touched — this is human-gate
transport I/O only (production default stays P2: clarify->plan->review).
"""
from __future__ import annotations
import importlib
from typing import Any
import pytest
from agent_team.transport.base import GITHUB_MARKER_TEMPLATE, QuestionSet, Transport
from agent_team.transport.github_adapter import GitHubApiError, GitHubTransport
from agent_team.transport.github_live import (
build_github_poster,
build_live_github_transport,
)
# --------------------------------------------------------------------------- #
# Test doubles #
# --------------------------------------------------------------------------- #
class _FakeResponse:
"""A ``requests.Response``-like object: ``status_code`` + ``json()``."""
def __init__(self, status_code: int, data: dict[str, Any]) -> None:
self.status_code = status_code
self._data = data
def json(self) -> dict[str, Any]:
return self._data
class _FakeSession:
"""A fake ``requests.Session`` recording ``post`` kwargs and scripting a reply."""
def __init__(
self, status_code: int = 201, data: dict[str, Any] | None = None
) -> None:
self.response = _FakeResponse(
status_code, {"id": 987654321} if data is None else data
)
self.calls: list[dict[str, Any]] = []
def post(
self, url: str, *, headers: dict[str, str], json: dict[str, Any]
) -> _FakeResponse:
self.calls.append({"url": url, "headers": headers, "json": json})
return self.response
def _question_set() -> QuestionSet:
return QuestionSet(
thread_id="task-7",
question_id="q-42",
turn=1,
questions=["Ship it?"],
context={"repo": "agent-team"},
)
# --------------------------------------------------------------------------- #
# Clean import without requests #
# --------------------------------------------------------------------------- #
def test_module_imports_without_requests(monkeypatch: pytest.MonkeyPatch) -> None:
"""The module imports cleanly even when ``requests`` cannot be imported."""
import builtins
real_import = builtins.__import__
def _blocked_import(name: str, *args: Any, **kwargs: Any) -> Any:
if name == "requests" or name.startswith("requests."):
raise ImportError("requests is blocked for this test")
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", _blocked_import)
module = importlib.reload(
importlib.import_module("agent_team.transport.github_live")
)
assert hasattr(module, "build_github_poster")
assert hasattr(module, "build_live_github_transport")
# --------------------------------------------------------------------------- #
# Happy path: injected fake client #
# --------------------------------------------------------------------------- #
def test_poster_returns_status_and_data() -> None:
"""The poster forwards to the client and returns ``(status, data)``."""
session = _FakeSession()
poster = build_github_poster(client=session)
status, data = poster(
"https://api.github.com/repos/o/r/issues/1/comments",
headers={"Authorization": "Bearer x"},
json_body={"body": "hi"},
)
assert status == 201
assert data["id"] == 987654321
assert len(session.calls) == 1
assert session.calls[0]["json"] == {"body": "hi"}
def test_post_question_round_trips_comment_id_as_channel_ref() -> None:
"""Wired through a real ``GitHubTransport``, the comment id is the channel_ref."""
session = _FakeSession(data={"id": 555})
transport = GitHubTransport(
owner="Sea-Haven-Industries",
repo="agent-team",
issue_number=1,
http_post=build_github_poster(client=session),
token_provider=lambda: "ghp_fake",
)
channel_ref = transport.post_question(
thread_id="task-7",
question_id="q-42",
turn=1,
question_set=_question_set(),
deadline="2026-06-18T00:00:00Z",
)
assert channel_ref == "555"
def test_posted_comment_carries_question_id_marker() -> None:
"""The posted comment body embeds ``<!-- shq:<question_id> -->``."""
session = _FakeSession()
transport = GitHubTransport(
owner="Sea-Haven-Industries",
repo="agent-team",
issue_number=1,
http_post=build_github_poster(client=session),
token_provider=lambda: "ghp_fake",
)
transport.post_question(
thread_id="task-7",
question_id="q-42",
turn=1,
question_set=_question_set(),
deadline="2026-06-18T00:00:00Z",
)
assert len(session.calls) == 1
body = session.calls[0]["json"]["body"]
assert GITHUB_MARKER_TEMPLATE.format(question_id="q-42") in body
def test_convenience_transport_factory_round_trips_and_parses() -> None:
"""``build_live_github_transport`` wires the poster and the marker round-trips."""
session = _FakeSession(data={"id": 777})
transport = build_live_github_transport(
owner="Sea-Haven-Industries",
repo="agent-team",
issue_number=1,
token="ghp_fake",
client=session,
)
assert isinstance(transport, Transport)
channel_ref = transport.post_question(
thread_id="task-7",
question_id="q-42",
turn=1,
question_set=_question_set(),
deadline="2026-06-18T00:00:00Z",
)
assert channel_ref == "777"
# The factory threads one token into the transport's auth header.
assert session.calls[0]["headers"]["Authorization"] == "Bearer ghp_fake"
# The marker the poster shipped round-trips back through parse_answer: a
# human reply quoting the question comment recovers the same question_id.
posted_body = session.calls[0]["json"]["body"]
reply = "> " + posted_body.replace("\n", "\n> ") + "\nLooks good, ship it."
question_id, answer, via = transport.parse_answer(
{"comment": {"body": reply, "user": {"login": "adam"}}}
)
assert question_id == "q-42"
assert answer == "Looks good, ship it."
assert via == "github:adam"
def test_response_with_status_attr_accepted() -> None:
"""A response exposing ``status`` (not ``status_code``) is also accepted."""
class _StatusOnly:
status = 200
def json(self) -> dict[str, Any]:
return {"id": 1}
class _Session:
def post(self, url: str, **kwargs: Any) -> _StatusOnly:
return _StatusOnly()
poster = build_github_poster(client=_Session())
status, data = poster("u", headers={}, json_body={})
assert status == 200
assert data["id"] == 1
def test_empty_body_coerces_to_dict() -> None:
"""A non-dict JSON body coerces to ``{}`` (adapter's missing-id guard fires)."""
class _NullJson:
status_code = 201
def json(self) -> Any:
return None
class _Session:
def post(self, url: str, **kwargs: Any) -> _NullJson:
return _NullJson()
poster = build_github_poster(client=_Session())
status, data = poster("u", headers={}, json_body={})
assert status == 201
assert data == {}
# --------------------------------------------------------------------------- #
# Failure modes #
# --------------------------------------------------------------------------- #
def test_non_2xx_response_raises_github_api_error() -> None:
"""A non-2xx response surfaces as ``GitHubApiError`` carrying the status."""
session = _FakeSession(status_code=403, data={"message": "Forbidden"})
poster = build_github_poster(client=session)
with pytest.raises(GitHubApiError) as excinfo:
poster("u", headers={}, json_body={"body": "x"})
assert excinfo.value.status == 403
def test_unsupported_response_raises_type_error() -> None:
"""A response with neither status nor json() is fatal (not a silent success)."""
class _Session:
def post(self, url: str, **kwargs: Any) -> object:
return object()
poster = build_github_poster(client=_Session())
with pytest.raises(TypeError, match="status_code"):
poster("u", headers={}, json_body={})
def test_missing_token_raises_runtime_error(monkeypatch: pytest.MonkeyPatch) -> None:
"""No token and no GITHUB_TOKEN raises a clear RuntimeError.
Stub ``requests`` into ``sys.modules`` so the deferred import SUCCEEDS and
the no-token branch is what's under test (avoids local-vs-CI drift where a
missing package would otherwise mask the token check).
"""
import sys
from types import ModuleType
fake = ModuleType("requests")
fake.Session = lambda: type(
"S", (), {"headers": {}, "post": lambda self, *a, **k: None}
)() # type: ignore[attr-defined]
monkeypatch.setitem(sys.modules, "requests", fake)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
with pytest.raises(RuntimeError, match="GitHub token"):
build_github_poster()
def test_missing_package_raises_runtime_error(monkeypatch: pytest.MonkeyPatch) -> None:
"""A missing ``requests`` package raises a clear RuntimeError."""
import builtins
real_import = builtins.__import__
def _blocked_import(name: str, *args: Any, **kwargs: Any) -> Any:
if name == "requests" or name.startswith("requests."):
raise ImportError("requests is blocked for this test")
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", _blocked_import)
monkeypatch.setenv("GITHUB_TOKEN", "ghp_present")
with pytest.raises(RuntimeError, match="requests is unavailable"):
build_github_poster()
def test_token_falls_back_to_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""With ``requests`` stubbed, a GITHUB_TOKEN env var builds a session cleanly."""
import sys
from types import ModuleType
captured: dict[str, Any] = {}
class _Session:
def __init__(self) -> None:
self.headers: dict[str, str] = {}
def post(self, *a: Any, **k: Any) -> None: # pragma: no cover - unused
return None
def _make_session() -> _Session:
session = _Session()
captured["session"] = session
return session
fake = ModuleType("requests")
fake.Session = _make_session # type: ignore[attr-defined]
monkeypatch.setitem(sys.modules, "requests", fake)
monkeypatch.setenv("GITHUB_TOKEN", "ghp_from_env")
poster = build_github_poster()
assert callable(poster)
assert captured["session"].headers["Authorization"] == "Bearer ghp_from_env"