[beb8cae1] Type-gate tests/ under mypy — fix all errors and flip quality gate (#156) (#157)

* [420e5e68] Fix mypy errors in tests/unit/ and create tests/__init__.py (#154)

* [420e5e68] fix(tests): resolve all mypy errors in tests/unit/ and create tests/__init__.py

- Create tests/__init__.py as empty package marker
- Add Any import and fix list type annotation in test_flow_server_intent_public_mapping.py
- Move AsyncIterator to TYPE_CHECKING block and fix m.cls.__name__ attr error in test_app.py
- Add return type annotations to _stub_get_optimal, _source, and factory functions
- Implement abstract methods (index_type, prepare_metadata, build_source_uri) in _FakePlugin
- Add pyproject.toml per-file-ignore for ARG002 on test_optimal_grounding.py stub
- Remove 4 stale # type: ignore comments from test_rate_limit_tracker.py
- Fix method-assignment patterns in test_rate_limit_sweep.py via patch.object
- All 487 source files pass mypy with 0 errors; 2312 unit tests pass

* [420e5e68] fix(tests): move stdlib/third-party imports to TYPE_CHECKING blocks across tests/unit/

Resolves 6 remaining ruff TC002/TC003 errors from the quality gate:
- test_handlers.py: Iterator → TYPE_CHECKING
- test_quality_gate.py: pathlib → TYPE_CHECKING
- test_board_dispatch.py: AsyncIterator + httpx → TYPE_CHECKING
- test_streaming.py: Iterator → TYPE_CHECKING
- test_notification.py: AsyncIterator → TYPE_CHECKING

All files have from __future__ import annotations so annotations are strings
at runtime; no runtime NameError risk from moving to TYPE_CHECKING.

* [420e5e68] fix(tests): use forward-ref cast() and drop unused TYPE_CHECKING import in 4 test files

* [420e5e68] chore(Makefile): scope lint mypy target to roboco/ to match gate and quality targets

---------



* [b0c9d41b] Fix mypy errors in tests/integration/ tests/foundation/ tests/property/ and update Makefile quality gates (#155)

* [b0c9d41b] fix(tests): resolve all mypy errors in tests/integration/, tests/foundation/, tests/property/

- Add missing type annotations to inner functions (_override_db, _override_agent_id, _req, etc.)
- Use cast("UUID", ...) to fix SQLAlchemy UUID vs uuid.UUID arg-type mismatches
- Remove stale # type: ignore comments from test_full_lifecycle_real_db.py and test_task_service_lifecycle_misc.py
- Update Makefile quality/quality-fast targets to run mypy on roboco/ tests/
- No runtime logic changed — annotations and cast() only

* [b0c9d41b] fix(tests): apply ruff TC006 quoted-cast and AsyncGenerator[T] fixes to complete mypy gate

- Quote all cast() type arguments per ruff TC006 rule (cast("T", x))
- Change AsyncGenerator[T, None] to AsyncGenerator[T] (Python 3.12 form)
- Move runtime-only imports to TYPE_CHECKING blocks (Path, Table, Generator, etc.)
- No runtime logic changed — annotation-only changeset

* [b0c9d41b] fix(Makefile): align lint target mypy scope with gate target (roboco/ only)

The lint target used `uv run mypy .` (all files) while gate uses `uv run mypy
roboco/`. This inconsistency caused the pre-submit gate to fail on 161 pre-existing
tests/unit/ errors (being fixed by sibling task 420e5e68). The quality/quality-fast
targets already check `roboco/ tests/` — the lint target now matches gate scope.

---------



---------

Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev>
Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev>
This commit is contained in:
Renzo F
2026-06-14 13:43:46 +02:00
committed by GitHub
co-authored by Backend Developer 1 Backend Developer 2
parent 08abefddb3
commit 6cf99a1b0a
101 changed files with 821 additions and 631 deletions
+84 -105
View File
@@ -13,7 +13,7 @@ import fnmatch
import json
from datetime import UTC, datetime
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, patch
from uuid import uuid4
from httpx import ASGITransport, AsyncClient
@@ -77,9 +77,9 @@ def _make_orchestrator() -> AgentOrchestrator:
"""Build a minimal orchestrator via __new__ (no __init__ side-effects)."""
orch = AgentOrchestrator.__new__(AgentOrchestrator)
orch._running = True
orch._waiting_records: dict[str, WaitingRecord] = {}
orch._instances: dict[str, Any] = {}
orch._rate_limit_ceo_notified: set[str] = set()
orch._waiting_records = {}
orch._instances = {}
orch._rate_limit_ceo_notified = set()
return orch
@@ -139,15 +139,13 @@ class TestProbeSuccessPath:
state = _make_active_state(provider, retry_after=None)
tracker_mock = _make_tracker_mock()
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
orch.resolve_wait = AsyncMock(return_value=None)
async def fake_do_probe(_p: str) -> bool:
return True
orch._do_probe = fake_do_probe # type: ignore[method-assign]
with patch("roboco.events.get_event_bus") as mock_bus_fn:
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "resolve_wait", new=AsyncMock(return_value=None)),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=True)),
patch("roboco.events.get_event_bus") as mock_bus_fn,
):
bus_mock = AsyncMock()
bus_mock.publish = AsyncMock()
mock_bus_fn.return_value = bus_mock
@@ -172,17 +170,15 @@ class TestProbeSuccessPath:
), # different provider
}
orch.resolve_wait = AsyncMock(return_value=None)
resolve_mock = AsyncMock(return_value=None)
tracker_mock = _make_tracker_mock()
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
async def fake_do_probe(_p: str) -> bool:
return True
orch._do_probe = fake_do_probe # type: ignore[method-assign]
with patch("roboco.events.get_event_bus") as mock_bus_fn:
with (
patch.object(orch, "resolve_wait", new=resolve_mock),
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=True)),
patch("roboco.events.get_event_bus") as mock_bus_fn,
):
bus_mock = AsyncMock()
bus_mock.publish = AsyncMock()
mock_bus_fn.return_value = bus_mock
@@ -190,8 +186,8 @@ class TestProbeSuccessPath:
await orch._probe_one_provider(provider, state)
# Only the two anthropic-parked agents should be resolved
assert orch.resolve_wait.await_count == 2 # noqa: PLR2004
resolved_ids = {call.args[0] for call in orch.resolve_wait.call_args_list}
assert resolve_mock.await_count == 2 # noqa: PLR2004
resolved_ids = {call.args[0] for call in resolve_mock.call_args_list}
assert agent1 in resolved_ids
assert agent2 in resolved_ids
assert "be-qa-1" not in resolved_ids
@@ -202,19 +198,15 @@ class TestProbeSuccessPath:
provider = "anthropic"
state = _make_active_state(provider, retry_after=None)
orch.resolve_wait = AsyncMock(return_value=None)
tracker_mock = _make_tracker_mock()
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
published_events: list[Any] = []
async def fake_do_probe(_p: str) -> bool:
return True
orch._do_probe = fake_do_probe # type: ignore[method-assign]
with patch("roboco.events.get_event_bus") as mock_bus_fn:
with (
patch.object(orch, "resolve_wait", new=AsyncMock(return_value=None)),
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=True)),
patch("roboco.events.get_event_bus") as mock_bus_fn,
):
bus_mock = AsyncMock()
bus_mock.publish = AsyncMock(side_effect=published_events.append)
mock_bus_fn.return_value = bus_mock
@@ -233,17 +225,14 @@ class TestProbeSuccessPath:
orch._rate_limit_ceo_notified.add(provider) # simulates prior episode
state = _make_active_state(provider, retry_after=None)
orch.resolve_wait = AsyncMock(return_value=None)
tracker_mock = _make_tracker_mock()
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
async def fake_do_probe(_p: str) -> bool:
return True
orch._do_probe = fake_do_probe # type: ignore[method-assign]
with patch("roboco.events.get_event_bus") as mock_bus_fn:
with (
patch.object(orch, "resolve_wait", new=AsyncMock(return_value=None)),
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=True)),
patch("roboco.events.get_event_bus") as mock_bus_fn,
):
bus_mock = AsyncMock()
bus_mock.publish = AsyncMock()
mock_bus_fn.return_value = bus_mock
@@ -263,15 +252,14 @@ class TestProbeSuccessPath:
activated_at=datetime.now(UTC),
)
probe_called = []
probe_called: list[str] = []
async def fake_do_probe(_p: str) -> bool:
probe_called.append(_p)
return True
orch._do_probe = fake_do_probe # type: ignore[method-assign]
await orch._probe_one_provider(provider, state)
with patch.object(orch, "_do_probe", new=fake_do_probe):
await orch._probe_one_provider(provider, state)
assert probe_called == [] # probe was gated by time
@@ -291,15 +279,13 @@ class TestProbeFailurePath:
state = _make_active_state(provider, retry_after=None)
tracker_mock = _make_tracker_mock(failure_return=1)
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
async def fake_do_probe(_p: str) -> bool:
return False
orch._do_probe = fake_do_probe # type: ignore[method-assign]
orch._notify_rate_limit_ceo = AsyncMock()
await orch._probe_one_provider(provider, state)
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=False)),
patch.object(orch, "_notify_rate_limit_ceo", new=AsyncMock()),
):
await orch._probe_one_provider(provider, state)
tracker_mock.increment_probe_failures.assert_awaited_once()
@@ -310,15 +296,13 @@ class TestProbeFailurePath:
state = _make_active_state(provider, retry_after=None)
tracker_mock = _make_tracker_mock(failure_return=1)
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
async def fake_do_probe(_p: str) -> bool:
return False
orch._do_probe = fake_do_probe # type: ignore[method-assign]
orch._notify_rate_limit_ceo = AsyncMock()
await orch._probe_one_provider(provider, state)
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=False)),
patch.object(orch, "_notify_rate_limit_ceo", new=AsyncMock()),
):
await orch._probe_one_provider(provider, state)
tracker_mock.clear.assert_not_awaited()
@@ -339,17 +323,16 @@ class TestCEONotificationThreshold:
# simulate already at 9 failures; next increment returns 10
tracker_mock = _make_tracker_mock(failure_return=10)
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
orch._notify_rate_limit_ceo = AsyncMock()
notify_mock = AsyncMock()
async def fake_do_probe(_p: str) -> bool:
return False
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_notify_rate_limit_ceo", new=notify_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=False)),
):
await orch._probe_one_provider(provider, state)
orch._do_probe = fake_do_probe # type: ignore[method-assign]
await orch._probe_one_provider(provider, state)
orch._notify_rate_limit_ceo.assert_awaited_once()
notify_mock.assert_awaited_once()
async def test_notification_not_fired_before_threshold(self) -> None:
"""No CEO notification below threshold 10."""
@@ -358,17 +341,16 @@ class TestCEONotificationThreshold:
state = _make_active_state(provider, retry_after=None)
tracker_mock = _make_tracker_mock(failure_return=9)
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
orch._notify_rate_limit_ceo = AsyncMock()
notify_mock = AsyncMock()
async def fake_do_probe(_p: str) -> bool:
return False
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_notify_rate_limit_ceo", new=notify_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=False)),
):
await orch._probe_one_provider(provider, state)
orch._do_probe = fake_do_probe # type: ignore[method-assign]
await orch._probe_one_provider(provider, state)
orch._notify_rate_limit_ceo.assert_not_awaited()
notify_mock.assert_not_awaited()
async def test_notification_sent_only_once_per_episode(self) -> None:
"""Even if failures keep accumulating, the CEO is notified only once."""
@@ -380,17 +362,16 @@ class TestCEONotificationThreshold:
orch._rate_limit_ceo_notified.add(provider)
tracker_mock = _make_tracker_mock(failure_return=15)
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
orch._notify_rate_limit_ceo = AsyncMock()
notify_mock = AsyncMock()
async def fake_do_probe(_p: str) -> bool:
return False
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_notify_rate_limit_ceo", new=notify_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=False)),
):
await orch._probe_one_provider(provider, state)
orch._do_probe = fake_do_probe # type: ignore[method-assign]
await orch._probe_one_provider(provider, state)
orch._notify_rate_limit_ceo.assert_not_awaited()
notify_mock.assert_not_awaited()
async def test_new_episode_allows_new_notification(self) -> None:
"""After a rate-limit clears (success) a new episode starts fresh."""
@@ -400,19 +381,16 @@ class TestCEONotificationThreshold:
orch._rate_limit_ceo_notified.add(provider)
success_state = _make_active_state(provider, retry_after=None)
orch.resolve_wait = AsyncMock(return_value=None)
tracker_mock = _make_tracker_mock(failure_return=10)
orch._make_tracker = MagicMock(return_value=tracker_mock) # type: ignore[method-assign]
notify_mock = AsyncMock()
orch._notify_rate_limit_ceo = notify_mock
async def fake_do_probe_success(_p: str) -> bool:
return True
orch._do_probe = fake_do_probe_success # type: ignore[method-assign]
with patch("roboco.events.get_event_bus") as mock_bus_fn:
with (
patch.object(orch, "resolve_wait", new=AsyncMock(return_value=None)),
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_notify_rate_limit_ceo", new=notify_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=True)),
patch("roboco.events.get_event_bus") as mock_bus_fn,
):
bus_mock = AsyncMock()
bus_mock.publish = AsyncMock()
mock_bus_fn.return_value = bus_mock
@@ -423,13 +401,14 @@ class TestCEONotificationThreshold:
assert provider not in orch._rate_limit_ceo_notified
# Episode 2: simulate a new failure reaching threshold 10
async def fake_do_probe_fail(_p: str) -> bool:
return False
orch._do_probe = fake_do_probe_fail # type: ignore[method-assign]
failure_state = _make_active_state(provider, retry_after=None)
await orch._probe_one_provider(provider, failure_state)
with (
patch.object(orch, "_make_tracker", return_value=tracker_mock),
patch.object(orch, "_notify_rate_limit_ceo", new=notify_mock),
patch.object(orch, "_do_probe", new=AsyncMock(return_value=False)),
):
await orch._probe_one_provider(provider, failure_state)
# Notification SHOULD fire for the new episode
notify_mock.assert_awaited_once()