[fix] resolve 16 mypy errors across 9 test files (make quality gate)

type-clean the test files so make quality (mypy roboco/ tests/) is green:
- Any-typed locals for the two TypeError-asserting scoping tests (bypass
  the required-arg check without getattr/ruff B009)
- Any-typed view for the shutdown-drain _drain_bg_tasks override (bypass
  mypy method-assign without setattr/ruff B010)
- cast("uuid.UUID", ...) / cast("UUID", ...) for SQLAlchemy UUID[Any]
  returns (TC006-quoted), config=None for AgentInstance stubs, None-narrowed
  await_args, Iterator return on a yielding fixture, UUID annotation on the
  _task helper. No type:ignore / noqa.
This commit is contained in:
Renn F
2026-06-29 02:33:09 +02:00
parent 041d0694d3
commit 592c84da5d
9 changed files with 44 additions and 20 deletions
@@ -71,7 +71,7 @@ async def _seed_project(db: AsyncSession, slug: str) -> ProjectTable:
return project return project
def _task(project_id, *, branch: str, status: TaskStatus) -> TaskTable: def _task(project_id: UUID, *, branch: str, status: TaskStatus) -> TaskTable:
return TaskTable( return TaskTable(
id=uuid4(), id=uuid4(),
title=f"task {branch}", title=f"task {branch}",
@@ -102,8 +102,10 @@ async def test_branch_owned_only_by_its_own_project(db_session: AsyncSession) ->
proj_b = await _seed_project(db_session, "gca-collide-b") proj_b = await _seed_project(db_session, "gca-collide-b")
db_session.add_all( db_session.add_all(
[ [
_task(proj_a.id, branch=_BRANCH, status=TaskStatus.IN_PROGRESS), _task(
_task(proj_b.id, branch=_BRANCH, status=TaskStatus.COMPLETED), cast("UUID", proj_a.id), branch=_BRANCH, status=TaskStatus.IN_PROGRESS
),
_task(cast("UUID", proj_b.id), branch=_BRANCH, status=TaskStatus.COMPLETED),
] ]
) )
await db_session.flush() await db_session.flush()
+11 -5
View File
@@ -1170,14 +1170,20 @@ async def _seed_messages_same_timestamp(
timestamp so the equal-timestamp pagination skip is reproducible. Returns timestamp so the equal-timestamp pagination skip is reproducible. Returns
``(session_id, message_ids)``.""" ``(session_id, message_ids)``."""
ch = await svc.create_channel(_channel_req(uuid4().hex[:6])) ch = await svc.create_channel(_channel_req(uuid4().hex[:6]))
grp = await svc.create_group(GroupCreateRequest(name="g1", channel_id=ch.id)) grp = await svc.create_group(
sess = await svc.create_session(SessionCreateRequest(group_id=grp.id)) GroupCreateRequest(name="g1", channel_id=cast("uuid.UUID", ch.id))
)
sess = await svc.create_session(
SessionCreateRequest(group_id=cast("uuid.UUID", grp.id))
)
ids: list[UUID] = [] ids: list[UUID] = []
for i in range(count): for i in range(count):
m = await svc.send_message( m = await svc.send_message(
MessageCreateRequest(agent_id=aid, session_id=sess.id, content=f"m-{i}") MessageCreateRequest(
agent_id=aid, session_id=cast("uuid.UUID", sess.id), content=f"m-{i}"
) )
ids.append(m.id) )
ids.append(cast("uuid.UUID", m.id))
fixed = datetime.now(UTC) fixed = datetime.now(UTC)
rows = ( rows = (
( (
@@ -1191,7 +1197,7 @@ async def _seed_messages_same_timestamp(
for row in rows: for row in rows:
row.timestamp = fixed row.timestamp = fixed
await session.flush() await session.flush()
return sess.id, ids return cast("uuid.UUID", sess.id), ids
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -8,7 +8,7 @@ SQLAlchemy ``after_commit`` events and a recording bus stand-in.
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, cast
from uuid import UUID, uuid4 from uuid import UUID, uuid4
import pytest import pytest
@@ -99,7 +99,7 @@ async def _seed_agents_and_notification(
metrics={}, metrics={},
) )
db.add(r) db.add(r)
recipient_ids.append(r.id) recipient_ids.append(cast("UUID", r.id))
await db.flush() await db.flush()
notification = NotificationTable( notification = NotificationTable(
@@ -113,7 +113,7 @@ async def _seed_agents_and_notification(
) )
db.add(notification) db.add(notification)
await db.flush() await db.flush()
return notification.id, notification return cast("UUID", notification.id), notification
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -152,7 +152,10 @@ async def test_stop_is_idempotent_double_call_is_noop() -> None:
drain_calls += 1 drain_calls += 1
await real_drain() await real_drain()
orch._drain_bg_tasks = counting_drain # Override via an Any-typed view so the assignment bypasses mypy's
# method-assign check while staying a plain attribute write (no setattr).
orch_any: Any = orch
orch_any._drain_bg_tasks = counting_drain
await orch.stop() await orch.stop()
assert drain_calls == 1, "first stop() drained the bg tasks" assert drain_calls == 1, "first stop() drained the bg tasks"
@@ -61,7 +61,9 @@ def _wire(monitor: dict[str, Any]) -> Any:
# Mirrors the real prepare's registration side-effect so the RED test # Mirrors the real prepare's registration side-effect so the RED test
# observes the STARTING instance the current code leaks. # observes the STARTING instance the current code leaks.
cfg = SimpleNamespace(provider_type="anthropic", model="opus") cfg = SimpleNamespace(provider_type="anthropic", model="opus")
inst = AgentInstance(agent_id="be-dev-1", state=AgentState.STARTING, config=cfg) inst = AgentInstance(
agent_id="be-dev-1", state=AgentState.STARTING, config=None
)
return cfg, inst, None return cfg, inst, None
return _readiness_gate, _git_context, _route, _prepare return _readiness_gate, _git_context, _route, _prepare
@@ -116,7 +118,7 @@ async def test_not_parked_spawn_still_runs_prepare_and_launches(
return AgentInstance( return AgentInstance(
agent_id="be-dev-1", agent_id="be-dev-1",
state=AgentState.ACTIVE, state=AgentState.ACTIVE,
config=SimpleNamespace(provider_type="anthropic", model="opus"), config=None,
) )
monkeypatch.setattr(orch, "_readiness_gate", _rg) monkeypatch.setattr(orch, "_readiness_gate", _rg)
@@ -146,7 +148,7 @@ async def test_running_agent_not_bailed_by_parked_check(
existing = AgentInstance( existing = AgentInstance(
agent_id="be-dev-1", agent_id="be-dev-1",
state=AgentState.ACTIVE, state=AgentState.ACTIVE,
config=SimpleNamespace(provider_type="anthropic", model="opus"), config=None,
) )
orch._instances["be-dev-1"] = existing orch._instances["be-dev-1"] = existing
@@ -280,4 +280,6 @@ async def test_check_health_skips_agent_on_inspect_timeout_not_aborts(
assert hang.killed assert hang.killed
# a2 WAS reached despite a1's timeout — the sweep continued. # a2 WAS reached despite a1's timeout — the sweep continued.
handle.assert_awaited_once() handle.assert_awaited_once()
assert handle.await_args.args[0] == "a2" call = handle.await_args
assert call is not None
assert call.args[0] == "a2"
@@ -9,7 +9,7 @@ from __future__ import annotations
import asyncio import asyncio
from pathlib import Path from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from typing import Any from typing import TYPE_CHECKING, Any
from unittest.mock import patch from unittest.mock import patch
from uuid import UUID from uuid import UUID
@@ -20,6 +20,9 @@ from roboco.runtime.orchestrator import (
) )
from roboco.services import prompter_live from roboco.services import prompter_live
if TYPE_CHECKING:
from collections.abc import Iterator
def _make_orchestrator() -> AgentOrchestrator: def _make_orchestrator() -> AgentOrchestrator:
"""AgentOrchestrator with constructor I/O skipped; a RUNNING minimal one.""" """AgentOrchestrator with constructor I/O skipped; a RUNNING minimal one."""
@@ -97,7 +100,7 @@ def _wire_secretary_spawn_mocks(
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _fresh_registry() -> None: def _fresh_registry() -> Iterator[None]:
"""Isolate the process-wide live registry per test.""" """Isolate the process-wide live registry per test."""
prev = prompter_live._RegistryHolder.instance prev = prompter_live._RegistryHolder.instance
prompter_live._RegistryHolder.instance = prompter_live.PrompterLiveRegistry() prompter_live._RegistryHolder.instance = prompter_live.PrompterLiveRegistry()
@@ -86,8 +86,11 @@ async def test_close_pull_request_requires_project_id() -> None:
recorder: list[object] = [] recorder: list[object] = []
svc = _service(recorder) svc = _service(recorder)
# Bind to an Any-typed local so mypy doesn't flag the missing project_id;
# the call still reaches the runtime, where it raises TypeError as asserted.
closer: Any = svc.close_pull_request
with pytest.raises(TypeError): with pytest.raises(TypeError):
await svc.close_pull_request(_PR_NUMBER, comment="superseded") # no project_id await closer(_PR_NUMBER, comment="superseded")
# The unscoped lookup was never issued — no SQL reached the session. # The unscoped lookup was never issued — no SQL reached the session.
assert recorder == [] assert recorder == []
@@ -90,8 +90,11 @@ async def test_pr_target_requires_project_id() -> None:
recorder: list[object] = [] recorder: list[object] = []
svc = _service(recorder) svc = _service(recorder)
# Bind to an Any-typed local so mypy doesn't flag the missing project_id;
# the call still reaches the runtime, where it raises TypeError as asserted.
target: Any = svc.pr_target
with pytest.raises(TypeError): with pytest.raises(TypeError):
await svc.pr_target(_PR_NUMBER) # missing required project_id await target(_PR_NUMBER)
# The unscoped lookup was never issued — no SQL reached the session. # The unscoped lookup was never issued — no SQL reached the session.
assert recorder == [] assert recorder == []