[chore] remove all remaining type:ignore suppressions from tests/

Converts 115 `# type: ignore[...]` suppressions across 23 test files to
no-suppression patterns (helper-return widening to Any, local Any aliases,
cc:Any aliases, cast at narrow call sites, typed fixtures) so the hard
no-type:ignore convention holds across tests/. No test logic or assertions
changed — only mock-wiring mechanics and type annotations.

Gate: ruff check tests/ clean; mypy tests/ (538 files) clean; 176 changed-file
tests pass. Zero real suppressions remain (the 7 grep hits are 3 hygiene-
checker string-literal test inputs and 4 prose mentions in comments).
This commit is contained in:
Renn F
2026-06-28 17:00:20 +02:00
parent 4d4bf084c5
commit f826285651
23 changed files with 177 additions and 175 deletions
+11 -10
View File
@@ -11,7 +11,7 @@ from __future__ import annotations
from datetime import UTC, datetime
from types import SimpleNamespace
from typing import Any
from typing import Any, cast
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import UUID, uuid4
@@ -212,7 +212,8 @@ def test_parse_uuid_list_with_valid() -> None:
def test_parse_uuid_list_skips_empty_strings() -> None:
raw = uuid4()
out = _parse_uuid_list([str(raw), "", None]) # type: ignore[list-item]
vals: list[Any] = [str(raw), "", None]
out = _parse_uuid_list(cast("list[str]", vals))
assert raw in out
assert len(out) == 1
@@ -278,7 +279,7 @@ def test_task_update_sequence_rejects_negative() -> None:
# ---------------------------------------------------------------------------
def _stub_task(*, with_project: bool = False) -> SimpleNamespace:
def _stub_task(*, with_project: bool = False) -> Any:
"""Build a TaskTable stand-in that matches task_to_response's reads."""
return SimpleNamespace(
id=uuid4(),
@@ -337,7 +338,7 @@ def test_task_to_response_omits_slug_when_project_not_loaded() -> None:
fake_inspector = MagicMock()
fake_inspector.unloaded = {"project"}
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
resp = task_to_response(stub) # type: ignore[arg-type]
resp = task_to_response(stub)
assert resp.project_slug is None
@@ -346,7 +347,7 @@ def test_task_to_response_includes_slug_when_project_loaded() -> None:
fake_inspector = MagicMock()
fake_inspector.unloaded = set() # project IS loaded
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
resp = task_to_response(stub) # type: ignore[arg-type]
resp = task_to_response(stub)
assert resp.project_slug == "proj-1"
@@ -364,7 +365,7 @@ def test_task_to_response_serializes_cell_projects_when_loaded() -> None:
fake_inspector = MagicMock()
fake_inspector.unloaded = set() # cell_projects IS loaded
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
resp = task_to_response(stub) # type: ignore[arg-type]
resp = task_to_response(stub)
assert resp.cell_projects == [
ProductCellMapping(team=Team.BACKEND, project_id=be_proj),
ProductCellMapping(team=Team.FRONTEND, project_id=fe_proj),
@@ -378,7 +379,7 @@ def test_task_to_response_omits_cell_projects_when_unloaded() -> None:
fake_inspector = MagicMock()
fake_inspector.unloaded = {"cell_projects"}
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
resp = task_to_response(stub) # type: ignore[arg-type]
resp = task_to_response(stub)
assert resp.cell_projects == []
@@ -393,7 +394,7 @@ def test_task_to_response_serializes_all_note_sections() -> None:
fake_inspector = MagicMock()
fake_inspector.unloaded = {"project"}
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
resp = task_to_response(stub) # type: ignore[arg-type]
resp = task_to_response(stub)
assert resp.pr_reviewer_notes == "## Findings\n- looks good"
assert resp.doc_notes == "Updated the README"
assert resp.notes_structured == {"pr_review": {"verdict": "passed"}}
@@ -404,7 +405,7 @@ def test_task_list_to_response_returns_list() -> None:
fake_inspector = MagicMock()
fake_inspector.unloaded = {"project"}
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
out = task_list_to_response(stubs) # type: ignore[arg-type]
out = task_list_to_response(stubs)
assert len(out) == len(stubs)
@@ -418,7 +419,7 @@ def _stub_response() -> Any:
fake_inspector = MagicMock()
fake_inspector.unloaded = {"project"}
with patch("roboco.api.schemas.tasks.sa_inspect", return_value=fake_inspector):
resp = task_to_response(_stub_task()) # type: ignore[arg-type]
resp = task_to_response(_stub_task())
return resp
@@ -15,7 +15,7 @@ import pytest
from roboco.services.gateway.choreographer import Choreographer, ChoreographerDeps
def _make_choreographer(git: AsyncMock) -> Choreographer:
def _make_choreographer(git: AsyncMock) -> Any:
base: dict[str, Any] = {
"task": AsyncMock(),
"work_session": AsyncMock(),
@@ -25,9 +25,9 @@ def _make_choreographer(git: AsyncMock) -> Choreographer:
"audit": AsyncMock(),
"evidence_repo": AsyncMock(),
}
c = Choreographer(ChoreographerDeps(**base))
c: Any = Choreographer(ChoreographerDeps(**base))
# _project_slug_for hits the project service; stub it for the unit.
c._project_slug_for = AsyncMock(return_value="proj") # type: ignore[method-assign]
c._project_slug_for = AsyncMock(return_value="proj")
return c
@@ -32,7 +32,7 @@ from roboco.foundation.policy import lifecycle as spec_module
from roboco.services.gateway.choreographer import Choreographer, ChoreographerDeps
def _make_choreographer() -> Choreographer:
def _make_choreographer() -> Any:
base: dict[str, Any] = {
"task": AsyncMock(),
"work_session": AsyncMock(),
@@ -45,12 +45,12 @@ def _make_choreographer() -> Choreographer:
return Choreographer(ChoreographerDeps(**base))
def _stub_post_path(c: Choreographer, *, reviewer_id: Any, t: Any) -> None:
def _stub_post_path(c: Any, *, reviewer_id: Any, t: Any) -> None:
"""Drive ``post_pr_review`` past preflight + the verdict-consistency gate so
the hand-format guard is the thing under test. The runner / side-effects are
stubbed so a passing case does not hit GitHub or the DB transition."""
agent = MagicMock(role="pr_reviewer", slug="be-pr-reviewer")
c._post_pr_review_preflight = AsyncMock( # type: ignore[method-assign]
c._post_pr_review_preflight = AsyncMock(
return_value=(
agent,
"pr_reviewer",
@@ -58,13 +58,13 @@ def _stub_post_path(c: Choreographer, *, reviewer_id: Any, t: Any) -> None:
spec_module.Context(actor_id=reviewer_id),
)
)
c._verdict_consistency_gate = AsyncMock(return_value=None) # type: ignore[method-assign]
c._project_slug_for = AsyncMock(return_value="proj") # type: ignore[method-assign]
c._resolve_post_body = MagicMock(return_value="generated body") # type: ignore[method-assign]
c._verdict_consistency_gate = AsyncMock(return_value=None)
c._project_slug_for = AsyncMock(return_value="proj")
c._resolve_post_body = MagicMock(return_value="generated body")
runner = MagicMock()
runner.run_intent = AsyncMock(return_value=t)
c._verb_runner = MagicMock(return_value=runner) # type: ignore[method-assign]
c._post_review_side_effects = AsyncMock() # type: ignore[method-assign]
c._verb_runner = MagicMock(return_value=runner)
c._post_review_side_effects = AsyncMock()
def _task() -> Any:
+3 -1
View File
@@ -13,6 +13,7 @@ safety properties the Grok provider must hold:
from __future__ import annotations
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@@ -303,7 +304,8 @@ async def test_claude_spawn_delegates_to_host() -> None:
async def test_claude_spawn_wraps_host_error() -> None:
host = _FakeHost()
host._spawn_container = AsyncMock(side_effect=RuntimeError("docker down")) # type: ignore[method-assign]
cc: Any = host
cc._spawn_container = AsyncMock(side_effect=RuntimeError("docker down"))
provider = ClaudeCodeProvider(host)
with pytest.raises(ProviderError, match="docker down"):
await provider.spawn(_config())
+8 -7
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import json
import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
@@ -29,7 +30,7 @@ _DO_TEST_MANIFEST = {
@pytest.fixture
def do_module(monkeypatch): # type: ignore[no-untyped-def]
def do_module(monkeypatch: pytest.MonkeyPatch) -> Any:
monkeypatch.setenv("ROBOCO_AGENT_ID", "00000000-0000-0000-0000-000000000001")
monkeypatch.setenv("ROBOCO_AGENT_ROLE", "developer")
monkeypatch.setenv("ROBOCO_ORCHESTRATOR_URL", "http://test-orchestrator:8000")
@@ -44,7 +45,7 @@ def do_module(monkeypatch): # type: ignore[no-untyped-def]
return srv
def test_commit_posts_message_and_files(do_module): # type: ignore[no-untyped-def]
def test_commit_posts_message_and_files(do_module: Any) -> None:
fake_client = MagicMock()
fake_client.__enter__.return_value = fake_client
fake_response = MagicMock()
@@ -60,7 +61,7 @@ def test_commit_posts_message_and_files(do_module): # type: ignore[no-untyped-d
assert kwargs["json"] == {"message": "feat(api): add /healthz", "files": ["foo.py"]}
def test_note_default_scope_note(do_module): # type: ignore[no-untyped-def]
def test_note_default_scope_note(do_module: Any) -> None:
fake_client = MagicMock()
fake_client.__enter__.return_value = fake_client
fake_response = MagicMock()
@@ -74,7 +75,7 @@ def test_note_default_scope_note(do_module): # type: ignore[no-untyped-def]
assert kwargs["json"]["scope"] == "note"
def test_note_with_scope_reflect(do_module): # type: ignore[no-untyped-def]
def test_note_with_scope_reflect(do_module: Any) -> None:
fake_client = MagicMock()
fake_client.__enter__.return_value = fake_client
fake_response = MagicMock()
@@ -88,7 +89,7 @@ def test_note_with_scope_reflect(do_module): # type: ignore[no-untyped-def]
assert kwargs["json"]["scope"] == "reflect"
def test_say_posts_channel_and_text(do_module): # type: ignore[no-untyped-def]
def test_say_posts_channel_and_text(do_module: Any) -> None:
fake_client = MagicMock()
fake_client.__enter__.return_value = fake_client
fake_response = MagicMock()
@@ -106,7 +107,7 @@ def test_say_posts_channel_and_text(do_module): # type: ignore[no-untyped-def]
}
def test_dm_posts_all_fields(do_module): # type: ignore[no-untyped-def]
def test_dm_posts_all_fields(do_module: Any) -> None:
fake_client = MagicMock()
fake_client.__enter__.return_value = fake_client
fake_response = MagicMock()
@@ -125,7 +126,7 @@ def test_dm_posts_all_fields(do_module): # type: ignore[no-untyped-def]
}
def test_evidence_posts_task_id(do_module): # type: ignore[no-untyped-def]
def test_evidence_posts_task_id(do_module: Any) -> None:
fake_client = MagicMock()
fake_client.__enter__.return_value = fake_client
fake_response = MagicMock()
+4 -2
View File
@@ -7,6 +7,7 @@ properties, factory functions, lookup helpers, and __post_init__ branches.
from __future__ import annotations
from pathlib import Path
from typing import Any, cast
from unittest.mock import patch
from uuid import uuid4
@@ -65,7 +66,8 @@ def test_agent_instance_post_init_assigns_uuid_when_falsy() -> None:
# Forcing an empty UUID(int=0) is falsy → the post_init triggers re-assign.
inst = AgentInstance.__new__(AgentInstance)
# Fill required dataclass fields explicitly so __post_init__ runs cleanly.
inst.id = None # type: ignore[assignment]
cc: Any = inst
cc.id = None
inst.agent_id = "be-dev-1"
inst.state = OrchestratorAgentState.OFFLINE
inst.container_id = None
@@ -185,7 +187,7 @@ def test_a2a_state_to_task_status_unknown_returns_pending() -> None:
pass
fake = _FakeState()
assert a2a_state_to_task_status(fake) == "pending" # type: ignore[arg-type]
assert a2a_state_to_task_status(cast("Any", fake)) == "pending"
def test_a2a_state_to_task_status_known() -> None:
+4 -4
View File
@@ -16,7 +16,7 @@ from roboco.config import settings
from roboco.runtime.orchestrator import AgentOrchestrator
def _orch() -> AgentOrchestrator:
def _orch() -> Any:
return AgentOrchestrator.__new__(AgentOrchestrator)
@@ -25,7 +25,7 @@ async def test_loop_noop_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "ci_watch_enabled", False)
orch = _orch()
cycle = AsyncMock()
orch._run_ci_watch_cycle = cycle # type: ignore[method-assign]
orch._run_ci_watch_cycle = cycle
await orch._ci_watch_loop() # must return immediately, no infinite loop
cycle.assert_not_awaited()
@@ -55,7 +55,7 @@ def _db_ctx(db: Any) -> Any:
@pytest.mark.asyncio
async def test_cycle_warns_and_skips_engine_when_empty() -> None:
orch = _orch()
orch._load_ci_watch_set = AsyncMock(return_value=[]) # type: ignore[method-assign]
orch._load_ci_watch_set = AsyncMock(return_value=[])
get_eng = MagicMock()
with (
patch("roboco.db.get_db_context", _db_ctx(MagicMock())),
@@ -69,7 +69,7 @@ async def test_cycle_warns_and_skips_engine_when_empty() -> None:
async def test_cycle_runs_engine_when_watch_set_present() -> None:
orch = _orch()
watch = [MagicMock()]
orch._load_ci_watch_set = AsyncMock(return_value=watch) # type: ignore[method-assign]
orch._load_ci_watch_set = AsyncMock(return_value=watch)
db = MagicMock()
db.commit = AsyncMock()
engine = MagicMock()
+4 -4
View File
@@ -16,7 +16,7 @@ from roboco.config import settings
from roboco.runtime.orchestrator import AgentOrchestrator
def _orch() -> AgentOrchestrator:
def _orch() -> Any:
return AgentOrchestrator.__new__(AgentOrchestrator)
@@ -25,7 +25,7 @@ async def test_loop_noop_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "dep_update_enabled", False)
orch = _orch()
cycle = AsyncMock()
orch._run_dep_update_cycle = cycle # type: ignore[method-assign]
orch._run_dep_update_cycle = cycle
await orch._dep_update_loop()
cycle.assert_not_awaited()
@@ -55,7 +55,7 @@ def _db_ctx(db: Any) -> Any:
@pytest.mark.asyncio
async def test_cycle_warns_and_skips_engine_when_empty() -> None:
orch = _orch()
orch._load_dep_update_set = AsyncMock(return_value=[]) # type: ignore[method-assign]
orch._load_dep_update_set = AsyncMock(return_value=[])
get_eng = MagicMock()
with (
patch("roboco.db.get_db_context", _db_ctx(MagicMock())),
@@ -69,7 +69,7 @@ async def test_cycle_warns_and_skips_engine_when_empty() -> None:
async def test_cycle_runs_engine_when_eligible_present() -> None:
orch = _orch()
eligible = [MagicMock()]
orch._load_dep_update_set = AsyncMock(return_value=eligible) # type: ignore[method-assign]
orch._load_dep_update_set = AsyncMock(return_value=eligible)
db = MagicMock()
db.commit = AsyncMock()
engine = MagicMock()
+21 -20
View File
@@ -20,6 +20,7 @@ or future is covered, because they all go through `spawn_agent`.
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import pytest
@@ -28,7 +29,7 @@ from roboco.runtime.orchestrator import AgentOrchestrator, AgentReadinessError
from roboco.seeds.initial_data import AGENT_UUIDS
def _orch() -> AgentOrchestrator:
def _orch() -> Any:
# The human-role guard is the first statement in spawn_agent and only
# consults the pure `role_for_slug` + the module logger — no self state
# — so a bare (un-initialized) orchestrator is sufficient to exercise it.
@@ -75,7 +76,7 @@ async def test_spawn_agent_does_not_refuse_real_agent() -> None:
async def _ready(_aid: str, _tid: str | None) -> str | None:
return "stubbed-not-ready"
orch._readiness_gate = _ready # type: ignore[assignment]
orch._readiness_gate = _ready
with pytest.raises(AgentReadinessError) as exc_info:
await orch.spawn_agent("be-dev-1", task_id="t-1")
@@ -89,15 +90,15 @@ async def test_spawn_agent_does_not_refuse_real_agent() -> None:
# ---------------------------------------------------------------------------
def _a2a_orch(ceo_uuid: str) -> AgentOrchestrator:
def _a2a_orch(ceo_uuid: str) -> Any:
"""A bare orchestrator with the a2a-dispatch collaborators stubbed."""
orch = object.__new__(AgentOrchestrator)
orch: Any = object.__new__(AgentOrchestrator)
# _dispatch_a2a_work consults: _fetch_notifications, _resolve_agent_slug,
# _is_agent_active, spawn_agent. _resolve_agent_slug is pure (module
# UUID_TO_SLUG) so it works unstubbed; stub the rest.
orch.spawn_agent = AsyncMock() # type: ignore[method-assign]
orch._is_agent_active = MagicMock(return_value=False) # type: ignore[method-assign]
orch._fetch_notifications = AsyncMock( # type: ignore[method-assign]
orch.spawn_agent = AsyncMock()
orch._is_agent_active = MagicMock(return_value=False)
orch._fetch_notifications = AsyncMock(
return_value=[
{"id": "n1", "to_agents": [ceo_uuid], "body": "board handoff"},
]
@@ -119,7 +120,7 @@ async def test_dispatch_a2a_skips_ceo_target() -> None:
await orch._dispatch_a2a_work(client)
orch.spawn_agent.assert_not_awaited() # type: ignore[attr-defined]
orch.spawn_agent.assert_not_awaited()
@pytest.mark.asyncio
@@ -128,7 +129,7 @@ async def test_dispatch_a2a_skips_intake_and_secretary_targets() -> None:
for slug in ("intake-1", "secretary-1"):
orch = _a2a_orch(AGENT_UUIDS[slug])
await orch._dispatch_a2a_work(MagicMock())
orch.spawn_agent.assert_not_awaited() # type: ignore[attr-defined]
orch.spawn_agent.assert_not_awaited()
@pytest.mark.asyncio
@@ -141,8 +142,8 @@ async def test_dispatch_a2a_still_spawns_real_agent_target() -> None:
await orch._dispatch_a2a_work(client)
orch.spawn_agent.assert_awaited_once() # type: ignore[attr-defined]
_args, kwargs = orch.spawn_agent.call_args # type: ignore[attr-defined]
orch.spawn_agent.assert_awaited_once()
_args, kwargs = orch.spawn_agent.call_args
assert kwargs.get("agent_id") == "be-dev-1"
@@ -152,10 +153,10 @@ async def test_dispatch_a2a_mixed_targets_skips_only_human() -> None:
real agent once and never the CEO."""
ceo_uuid = AGENT_UUIDS["ceo"]
be_uuid = AGENT_UUIDS["be-dev-1"]
orch = object.__new__(AgentOrchestrator)
orch.spawn_agent = AsyncMock() # type: ignore[method-assign]
orch._is_agent_active = MagicMock(return_value=False) # type: ignore[method-assign]
orch._fetch_notifications = AsyncMock( # type: ignore[method-assign]
orch: Any = object.__new__(AgentOrchestrator)
orch.spawn_agent = AsyncMock()
orch._is_agent_active = MagicMock(return_value=False)
orch._fetch_notifications = AsyncMock(
return_value=[{"id": "n1", "to_agents": [ceo_uuid, be_uuid]}]
)
client = MagicMock()
@@ -177,11 +178,11 @@ async def test_dispatch_pm_review_skips_ceo_assignee() -> None:
"""An awaiting_pm_review task assigned to the CEO must NOT respawn a CEO
container, and must NOT abort the dispatcher's tick (which would stall
other PM-review respawns behind it). The skip leaves it for the human."""
orch = object.__new__(AgentOrchestrator)
orch.spawn_agent = AsyncMock() # type: ignore[method-assign]
orch._is_agent_active = MagicMock(return_value=False) # type: ignore[method-assign]
orch._pm_respawn_should_gate = AsyncMock(return_value=False) # type: ignore[method-assign]
orch._fetch_tasks = AsyncMock( # type: ignore[method-assign]
orch: Any = object.__new__(AgentOrchestrator)
orch.spawn_agent = AsyncMock()
orch._is_agent_active = MagicMock(return_value=False)
orch._pm_respawn_should_gate = AsyncMock(return_value=False)
orch._fetch_tasks = AsyncMock(
return_value=[
{
"id": "t1",
@@ -18,36 +18,36 @@ triggers only its own recovery helper.
from __future__ import annotations
from typing import cast
from typing import Any, cast
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from roboco.runtime.orchestrator import AgentOrchestrator
def _orch() -> AgentOrchestrator:
def _orch() -> Any:
with patch.object(AgentOrchestrator, "__init__", return_value=None):
return AgentOrchestrator.__new__(AgentOrchestrator)
def _ready_orch() -> AgentOrchestrator:
def _ready_orch() -> Any:
"""Orchestrator with every closure gate stubbed so _maybe_spawn_pm_closure
reaches the spawn (descendants terminal, not recently paused, not
already promoted, PM idle)."""
orch = _orch()
orch._is_recently_paused = MagicMock(return_value=False) # type: ignore[method-assign]
orch._fetch_all_descendants = AsyncMock( # type: ignore[method-assign]
orch._is_recently_paused = MagicMock(return_value=False)
orch._fetch_all_descendants = AsyncMock(
return_value=[{"id": "leaf", "status": "completed"}]
)
orch._all_descendants_terminal = MagicMock(return_value=True) # type: ignore[method-assign]
orch._already_promoted_for_closure = MagicMock(return_value=False) # type: ignore[method-assign]
orch._closure_pm_for_team = MagicMock(return_value="be-pm") # type: ignore[method-assign]
orch._is_agent_active = MagicMock(return_value=False) # type: ignore[method-assign]
orch._build_pm_closure_prompt = MagicMock(return_value="PROMPT") # type: ignore[method-assign]
orch._task_git_context = MagicMock(return_value=None) # type: ignore[method-assign]
orch.spawn_agent = AsyncMock() # type: ignore[method-assign]
orch._auto_resume_paused_parent = AsyncMock() # type: ignore[method-assign]
orch._auto_recover_blocked_parent = AsyncMock() # type: ignore[method-assign]
orch._all_descendants_terminal = MagicMock(return_value=True)
orch._already_promoted_for_closure = MagicMock(return_value=False)
orch._closure_pm_for_team = MagicMock(return_value="be-pm")
orch._is_agent_active = MagicMock(return_value=False)
orch._build_pm_closure_prompt = MagicMock(return_value="PROMPT")
orch._task_git_context = MagicMock(return_value=None)
orch.spawn_agent = AsyncMock()
orch._auto_resume_paused_parent = AsyncMock()
orch._auto_recover_blocked_parent = AsyncMock()
return orch
@@ -102,7 +102,7 @@ async def test_non_paused_parent_is_not_resumed() -> None:
async def test_resume_skipped_when_closure_gate_blocks_spawn() -> None:
"""If descendants aren't terminal there is no spawn — and no resume."""
orch = _ready_orch()
orch._all_descendants_terminal = MagicMock(return_value=False) # type: ignore[method-assign]
orch._all_descendants_terminal = MagicMock(return_value=False)
client = AsyncMock()
await orch._maybe_spawn_pm_closure(
@@ -12,6 +12,7 @@ reaper's live-skip and the spawn gate see the live agent immediately.
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import pytest
@@ -20,7 +21,7 @@ from roboco.runtime.orchestrator import AgentOrchestrator, AgentState
_EXPECTED_READOPTED = 2
def _orch() -> AgentOrchestrator:
def _orch() -> Any:
orch = AgentOrchestrator.__new__(AgentOrchestrator) # bypass __init__
orch._instances = {}
return orch
@@ -35,7 +36,7 @@ async def test_readopts_running_containers_as_active() -> None:
slug = name.removeprefix("roboco-agent-")
return (slug in running, 0)
orch._inspect_container_state = AsyncMock(side_effect=inspect) # type: ignore[method-assign]
orch._inspect_container_state = AsyncMock(side_effect=inspect)
n = await orch._readopt_running_agents()
@@ -51,7 +52,7 @@ async def test_readopt_leaves_already_tracked_instance_untouched() -> None:
orch = _orch()
sentinel = MagicMock()
orch._instances = {"be-dev-1": sentinel}
orch._inspect_container_state = AsyncMock(return_value=(True, 0)) # type: ignore[method-assign]
orch._inspect_container_state = AsyncMock(return_value=(True, 0))
await orch._readopt_running_agents()
@@ -61,7 +62,7 @@ async def test_readopt_leaves_already_tracked_instance_untouched() -> None:
@pytest.mark.asyncio
async def test_readopt_inert_when_nothing_running() -> None:
orch = _orch()
orch._inspect_container_state = AsyncMock(return_value=(False, None)) # type: ignore[method-assign]
orch._inspect_container_state = AsyncMock(return_value=(False, None))
n = await orch._readopt_running_agents()
@@ -72,7 +73,7 @@ async def test_readopt_inert_when_nothing_running() -> None:
@pytest.mark.asyncio
async def test_readopt_swallows_probe_errors() -> None:
orch = _orch()
orch._inspect_container_state = AsyncMock(side_effect=RuntimeError("no docker")) # type: ignore[method-assign]
orch._inspect_container_state = AsyncMock(side_effect=RuntimeError("no docker"))
n = await orch._readopt_running_agents()
@@ -87,8 +88,8 @@ async def test_readopt_records_container_id_so_health_check_can_see_exit() -> No
# is stranded under a phantom ACTIVE instance forever. Re-adopt must capture
# the real container id so the health loop can observe the later exit.
orch = _orch()
orch._inspect_container_state = AsyncMock(return_value=(True, 0)) # type: ignore[method-assign]
orch._resolve_container_id = AsyncMock(return_value="deadbeef1234") # type: ignore[method-assign]
orch._inspect_container_state = AsyncMock(return_value=(True, 0))
orch._resolve_container_id = AsyncMock(return_value="deadbeef1234")
await orch._readopt_running_agents()
@@ -17,7 +17,7 @@ the session — while the happy path still adds + commits the savepoint.
from __future__ import annotations
from typing import Any
from typing import Any, cast
from uuid import uuid4
import pytest
@@ -89,7 +89,7 @@ async def test_cache_put_tolerates_concurrent_duplicate_without_poisoning() -> N
# a savepoint; _cache_put returns cleanly, the session is not poisoned, and
# no full rollback undoes the outer task-create transaction.
session = _FakeSession(duplicate=True)
svc = ConventionsService(session=session) # type: ignore[arg-type]
svc = ConventionsService(session=cast("Any", session))
await svc._cache_put(uuid4(), "deadbeef", _mapping(), "ok")
@@ -101,7 +101,7 @@ async def test_cache_put_tolerates_concurrent_duplicate_without_poisoning() -> N
@pytest.mark.asyncio
async def test_cache_put_happy_path_adds_and_releases_savepoint() -> None:
session = _FakeSession(duplicate=False)
svc = ConventionsService(session=session) # type: ignore[arg-type]
svc = ConventionsService(session=cast("Any", session))
await svc._cache_put(uuid4(), "deadbeef", _mapping(), "ok")
@@ -4,7 +4,7 @@ from __future__ import annotations
import subprocess
from types import SimpleNamespace
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, cast
from roboco.services.conventions import ConventionsService
@@ -33,7 +33,7 @@ def _git_repo(root: Path) -> str:
def _svc() -> ConventionsService:
return ConventionsService(session=None) # type: ignore[arg-type]
return ConventionsService(session=cast("Any", None))
def test_resolve_reads_clone_head_and_backfills(tmp_path: Path) -> None:
@@ -18,7 +18,7 @@ import pytest
from roboco.services.git import GitService
def _git_service() -> GitService:
def _git_service() -> Any:
return GitService.__new__(GitService)
@@ -26,8 +26,8 @@ def _git_service() -> GitService:
async def test_resolve_diff_base_uses_parent_when_pushed() -> None:
"""When origin/<parent> exists, use it (normal case)."""
svc = _git_service()
svc._run_git = AsyncMock() # type: ignore[method-assign]
svc._ref_exists = AsyncMock(return_value=True) # type: ignore[method-assign]
svc._run_git = AsyncMock()
svc._ref_exists = AsyncMock(return_value=True)
ws = Path("/tmp/ws")
base = await svc._resolve_diff_base(
@@ -42,12 +42,10 @@ async def test_resolve_diff_base_falls_back_when_parent_absent() -> None:
"""When origin/<parent> does NOT exist (cell-PM branch never pushed),
fall back to the repo default branch via origin/HEAD."""
svc = _git_service()
svc._run_git = AsyncMock() # type: ignore[method-assign]
svc._run_git = AsyncMock()
# parent ref absent → _ref_exists False for the parent check.
svc._ref_exists = AsyncMock(return_value=False) # type: ignore[method-assign]
svc._default_branch_ref = AsyncMock( # type: ignore[method-assign]
return_value="origin/master"
)
svc._ref_exists = AsyncMock(return_value=False)
svc._default_branch_ref = AsyncMock(return_value="origin/master")
ws = Path("/tmp/ws")
base = await svc._resolve_diff_base(
@@ -84,7 +82,7 @@ async def test_default_branch_ref_fallback_when_no_head() -> None:
# symbolic-ref fails; fetches succeed but ref never verifies.
return type("R", (), {"returncode": 1, "stdout": ""})()
svc._ref_exists = AsyncMock(return_value=False) # type: ignore[method-assign]
svc._ref_exists = AsyncMock(return_value=False)
with patch.object(svc, "_run_git", new=fake_run):
ref = await svc._default_branch_ref(Path("/tmp/ws"))
assert ref == "origin/master"
@@ -107,8 +105,8 @@ _BR = "feature/backend/root1234--cellpm56--dev78901"
async def test_resolve_head_ref_prefers_local_branch_in_dev_clone() -> None:
"""Dev's own clone has the local branch — use it unchanged."""
svc = _git_service()
svc._run_git = AsyncMock() # type: ignore[method-assign]
svc._ref_exists = AsyncMock(return_value=True) # type: ignore[method-assign]
svc._run_git = AsyncMock()
svc._ref_exists = AsyncMock(return_value=True)
head = await svc._resolve_head_ref(Path("/tmp/ws"), _BR)
assert head == _BR
@@ -119,7 +117,7 @@ async def test_resolve_head_ref_falls_back_to_origin_in_foreign_clone() -> None:
"""QA/doc/PM clone has no local branch but origin/<branch> exists
(open_pr pushed it) — diff must target origin/<branch>."""
svc = _git_service()
svc._run_git = AsyncMock() # type: ignore[method-assign]
svc._run_git = AsyncMock()
async def ref_exists(_ws: Any, ref: str) -> bool:
# local branch absent; only the remote-tracking ref resolves.
@@ -141,7 +139,7 @@ async def test_resolve_head_ref_fetches_branch_before_resolving() -> None:
calls.append(args)
return type("R", (), {"returncode": 0, "stdout": ""})()
svc._ref_exists = AsyncMock(return_value=True) # type: ignore[method-assign]
svc._ref_exists = AsyncMock(return_value=True)
with patch.object(svc, "_run_git", new=fake_run):
await svc._resolve_head_ref(Path("/tmp/ws"), _BR)
assert ["fetch", "origin", _BR] in calls
@@ -153,18 +151,10 @@ async def test_diff_targets_origin_head_in_foreign_clone() -> None:
base...origin/<branch>, not base...<bare-local-branch> (which is
unresolvable there and silently produced an empty diff)."""
svc = _git_service()
svc._workspace_for_branch = AsyncMock( # type: ignore[method-assign]
return_value=Path("/tmp/qa-ws")
)
svc._resolve_diff_base = AsyncMock( # type: ignore[method-assign]
return_value="origin/master"
)
svc._resolve_head_ref = AsyncMock( # type: ignore[method-assign]
return_value=f"origin/{_BR}"
)
svc._token_for_branch = AsyncMock( # type: ignore[method-assign]
return_value="tok"
)
svc._workspace_for_branch = AsyncMock(return_value=Path("/tmp/qa-ws"))
svc._resolve_diff_base = AsyncMock(return_value="origin/master")
svc._resolve_head_ref = AsyncMock(return_value=f"origin/{_BR}")
svc._token_for_branch = AsyncMock(return_value="tok")
captured: list[list[str]] = []
async def fake_run(_ws: Any, args: list[str], **_kw: Any) -> Any:
@@ -187,18 +177,10 @@ async def test_diff_targets_origin_head_in_foreign_clone() -> None:
async def test_list_changed_files_targets_origin_head_in_foreign_clone() -> None:
"""Same fix on the files_changed path (#154 evidence)."""
svc = _git_service()
svc._workspace_for_branch = AsyncMock( # type: ignore[method-assign]
return_value=Path("/tmp/qa-ws")
)
svc._resolve_diff_base = AsyncMock( # type: ignore[method-assign]
return_value="origin/master"
)
svc._resolve_head_ref = AsyncMock( # type: ignore[method-assign]
return_value=f"origin/{_BR}"
)
svc._token_for_branch = AsyncMock( # type: ignore[method-assign]
return_value="tok"
)
svc._workspace_for_branch = AsyncMock(return_value=Path("/tmp/qa-ws"))
svc._resolve_diff_base = AsyncMock(return_value="origin/master")
svc._resolve_head_ref = AsyncMock(return_value=f"origin/{_BR}")
svc._token_for_branch = AsyncMock(return_value="tok")
captured: list[list[str]] = []
async def fake_run(_ws: Any, args: list[str], **_kw: Any) -> Any:
@@ -219,18 +201,10 @@ async def test_diff_honours_explicit_base_with_resolved_head() -> None:
"""The incremental dev path (base=HEAD~1) still works: explicit base
is preserved, head still goes through _resolve_head_ref."""
svc = _git_service()
svc._workspace_for_branch = AsyncMock( # type: ignore[method-assign]
return_value=Path("/tmp/dev-ws")
)
svc._resolve_diff_base = AsyncMock( # type: ignore[method-assign]
return_value="SHOULD_NOT_BE_USED"
)
svc._resolve_head_ref = AsyncMock( # type: ignore[method-assign]
return_value=_BR
)
svc._token_for_branch = AsyncMock( # type: ignore[method-assign]
return_value=None
)
svc._workspace_for_branch = AsyncMock(return_value=Path("/tmp/dev-ws"))
svc._resolve_diff_base = AsyncMock(return_value="SHOULD_NOT_BE_USED")
svc._resolve_head_ref = AsyncMock(return_value=_BR)
svc._token_for_branch = AsyncMock(return_value=None)
captured: list[list[str]] = []
async def fake_run(_ws: Any, args: list[str], **_kw: Any) -> Any:
@@ -266,10 +240,8 @@ async def test_resolve_diff_base_refetches_default_branch_with_token() -> None:
return type("R", (), {"returncode": 0, "stdout": ""})()
# parent ref never exists → fall back to default branch.
svc._ref_exists = AsyncMock(return_value=False) # type: ignore[method-assign]
svc._default_branch_ref = AsyncMock( # type: ignore[method-assign]
return_value="origin/master"
)
svc._ref_exists = AsyncMock(return_value=False)
svc._default_branch_ref = AsyncMock(return_value="origin/master")
with patch.object(svc, "_run_git", new=fake_run):
base = await svc._resolve_diff_base(Path("/tmp/ws"), _BR, token="tok")
@@ -291,7 +263,7 @@ async def test_resolve_head_ref_fetch_is_authenticated() -> None:
seen.append((args, kw.get("token")))
return type("R", (), {"returncode": 0, "stdout": ""})()
svc._ref_exists = AsyncMock(return_value=True) # type: ignore[method-assign]
svc._ref_exists = AsyncMock(return_value=True)
with patch.object(svc, "_run_git", new=fake_run):
await svc._resolve_head_ref(Path("/tmp/ws"), _BR, token="tok")
assert (["fetch", "origin", _BR], "tok") in seen
@@ -302,5 +274,5 @@ async def test_token_for_branch_is_best_effort_none() -> None:
"""Unresolvable branch/project must yield None (degrade to unauth),
never raise inside the evidence-assembly path."""
svc = _git_service()
svc._task_for_branch = AsyncMock(return_value=None) # type: ignore[method-assign]
svc._task_for_branch = AsyncMock(return_value=None)
assert await svc._token_for_branch(_BR) is None
@@ -23,8 +23,8 @@ _BASE = "feature/backend/parent12345"
_HEAD = "feature/backend/abc12345"
def _git_service() -> GitService:
svc = GitService.__new__(GitService)
def _git_service() -> Any:
svc: Any = GitService.__new__(GitService)
svc.log = MagicMock()
return svc
@@ -45,14 +45,14 @@ def _project() -> Any:
return MagicMock(slug="roboco")
async def _wire(svc: GitService, *, rev_list_stdout: str) -> AsyncMock:
async def _wire(svc: Any, *, rev_list_stdout: str) -> AsyncMock:
"""Stub the workspace/token resolution + _run_git; return the run mock."""
svc._project_for_task = AsyncMock(return_value=_project()) # type: ignore[method-assign]
svc._resolve_workspace_agent_id = MagicMock(return_value=uuid4()) # type: ignore[method-assign]
svc.get_workspace = AsyncMock(return_value=_WORKSPACE) # type: ignore[method-assign]
svc._get_project_token_or_raise = AsyncMock(return_value=_TOKEN) # type: ignore[method-assign]
svc._project_for_task = AsyncMock(return_value=_project())
svc._resolve_workspace_agent_id = MagicMock(return_value=uuid4())
svc.get_workspace = AsyncMock(return_value=_WORKSPACE)
svc._get_project_token_or_raise = AsyncMock(return_value=_TOKEN)
run = AsyncMock(side_effect=[_result(), _result(stdout=rev_list_stdout)])
svc._run_git = run # type: ignore[method-assign]
svc._run_git = run
return run
@@ -116,7 +116,7 @@ async def test_is_behind_base_requires_branch_name() -> None:
@pytest.mark.asyncio
async def test_is_behind_base_raises_when_project_missing() -> None:
svc = _git_service()
svc._project_for_task = AsyncMock(return_value=None) # type: ignore[method-assign]
svc._project_for_task = AsyncMock(return_value=None)
with pytest.raises(NotFoundError):
await svc.is_behind_base(_task(), base_branch=_BASE)
@@ -26,10 +26,10 @@ def _link(session_id: object, relationship_type: str) -> MagicMock:
@pytest.mark.asyncio
async def test_propagate_links_every_parent_session_to_subtask() -> None:
"""Every link on the parent gets re-attached to the new subtask."""
svc = MessagingService.__new__(MessagingService)
svc: Any = MessagingService.__new__(MessagingService)
parent_session = uuid4()
review_session = uuid4()
svc.get_sessions_for_task = AsyncMock( # type: ignore[method-assign]
svc.get_sessions_for_task = AsyncMock(
return_value=[
_link(parent_session, "discussion"),
_link(review_session, "review"),
@@ -67,9 +67,9 @@ async def test_propagate_links_every_parent_session_to_subtask() -> None:
@pytest.mark.asyncio
async def test_propagate_no_parent_sessions_returns_empty() -> None:
"""When the parent has no session links, propagation is a no-op."""
svc = MessagingService.__new__(MessagingService)
svc.get_sessions_for_task = AsyncMock(return_value=[]) # type: ignore[method-assign]
svc.link_session_to_task = AsyncMock() # type: ignore[method-assign]
svc: Any = MessagingService.__new__(MessagingService)
svc.get_sessions_for_task = AsyncMock(return_value=[])
svc.link_session_to_task = AsyncMock()
out = await svc.propagate_sessions_to_subtask(uuid4(), uuid4(), uuid4())
assert out == []
@@ -80,8 +80,8 @@ async def test_propagate_no_parent_sessions_returns_empty() -> None:
async def test_propagate_unknown_relationship_type_defaults_to_discussion() -> None:
"""Garbage relationship_type on the parent link doesn't crash; it
defaults to DISCUSSION so the subtask is still linked."""
svc = MessagingService.__new__(MessagingService)
svc.get_sessions_for_task = AsyncMock( # type: ignore[method-assign]
svc: Any = MessagingService.__new__(MessagingService)
svc.get_sessions_for_task = AsyncMock(
return_value=[_link(uuid4(), "definitely-not-a-real-type")]
)
calls: list[dict[str, Any]] = []
@@ -22,9 +22,9 @@ _PCT_NONE = 0
_PCT_FALLBACK = 42
def _svc_with_task(task: Any) -> TaskService:
svc = TaskService.__new__(TaskService)
svc.get = AsyncMock(return_value=task) # type: ignore[method-assign]
def _svc_with_task(task: Any) -> Any:
svc: Any = TaskService.__new__(TaskService)
svc.get = AsyncMock(return_value=task)
svc.session = MagicMock()
svc.session.flush = AsyncMock()
return svc
@@ -119,6 +119,6 @@ async def test_no_checklist_falls_back_to_supplied_percentage() -> None:
@pytest.mark.asyncio
async def test_missing_task_returns_none() -> None:
svc = TaskService.__new__(TaskService)
svc.get = AsyncMock(return_value=None) # type: ignore[method-assign]
svc: Any = TaskService.__new__(TaskService)
svc.get = AsyncMock(return_value=None)
assert await svc.record_plan_progress(uuid4(), uuid4(), "x") is None
+3 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from typing import Any, cast
import pytest
from roboco.services.base import (
BaseService,
@@ -120,7 +122,7 @@ def test_base_service_binds_session_and_logger() -> None:
service_name = "x"
fake_session = object()
svc = _Svc(fake_session) # type: ignore[arg-type]
svc = _Svc(cast("Any", fake_session))
assert svc.session is fake_session
assert svc.log is not None
+5 -2
View File
@@ -8,6 +8,7 @@ suites; here we assert the branch wiring with a mocked db context.
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
@@ -52,7 +53,8 @@ async def test_create_notification_suppresses_same_purpose_duplicate() -> None:
db.commit = AsyncMock()
svc = NotificationService()
svc._resolve_recipients = AsyncMock(return_value=[uuid4()]) # type: ignore[method-assign]
cc: Any = svc
cc._resolve_recipients = AsyncMock(return_value=[uuid4()])
with (
patch(
"roboco.services.notification.get_db_context",
@@ -90,7 +92,8 @@ async def test_informational_knowledge_share_not_deduped() -> None:
db.commit = AsyncMock()
svc = NotificationService()
svc._resolve_recipients = AsyncMock(return_value=[uuid4()]) # type: ignore[method-assign]
cc: Any = svc
cc._resolve_recipients = AsyncMock(return_value=[uuid4()])
params = CreateNotificationParams(
notification_type=NotificationType.KNOWLEDGE_SHARE,
priority=NotificationPriority.NORMAL,