diff --git a/tests/integration/services/test_dep_update_probe.py b/tests/integration/services/test_dep_update_probe.py index 0da85888..2ae2007f 100644 --- a/tests/integration/services/test_dep_update_probe.py +++ b/tests/integration/services/test_dep_update_probe.py @@ -8,7 +8,7 @@ or committing/pushing. Fail-safe: a null/failing command returns False. from __future__ import annotations import subprocess -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from unittest.mock import AsyncMock, MagicMock import pytest @@ -36,9 +36,9 @@ def _make_read_clone(tmp_path: Path) -> Path: return repo -def _svc(read_clone: Path) -> WorkspaceService: - svc = WorkspaceService.__new__(WorkspaceService) - svc.ensure_read_clone = AsyncMock(return_value=read_clone) # type: ignore[method-assign] +def _svc(read_clone: Path) -> Any: + svc: Any = WorkspaceService.__new__(WorkspaceService) + svc.ensure_read_clone = AsyncMock(return_value=read_clone) return svc diff --git a/tests/integration/test_migration_013_drop_role.py b/tests/integration/test_migration_013_drop_role.py index 9b75a4a9..d905c759 100644 --- a/tests/integration/test_migration_013_drop_role.py +++ b/tests/integration/test_migration_013_drop_role.py @@ -7,12 +7,17 @@ class. The migration drops the unused one with a safety check. from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from sqlalchemy import text +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession + @pytest.mark.asyncio -async def test_no_column_uses_role_type(db_session) -> None: # type: ignore[no-untyped-def] +async def test_no_column_uses_role_type(db_session: AsyncSession) -> None: """Before dropping, confirm no column actually uses the `role` type. If this ever fails it means a column was added that references the @@ -33,7 +38,7 @@ async def test_no_column_uses_role_type(db_session) -> None: # type: ignore[no- @pytest.mark.asyncio -async def test_role_enum_dropped_after_upgrade(db_session) -> None: # type: ignore[no-untyped-def] +async def test_role_enum_dropped_after_upgrade(db_session: AsyncSession) -> None: """After migration 013 runs, only `agentrole` remains; `role` is gone.""" # This test runs against a db where migrations have been applied to head. # The conftest fixture should handle that — verify by reading the diff --git a/tests/integration/test_migration_014_drop_pm_approvals.py b/tests/integration/test_migration_014_drop_pm_approvals.py index 96f01af4..c867eb9f 100644 --- a/tests/integration/test_migration_014_drop_pm_approvals.py +++ b/tests/integration/test_migration_014_drop_pm_approvals.py @@ -7,12 +7,17 @@ tracking, RAG context). Only pm_approvals is truly orphaned. from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from sqlalchemy import text +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession + @pytest.mark.asyncio -async def test_pm_approvals_dropped(db_session) -> None: # type: ignore[no-untyped-def] +async def test_pm_approvals_dropped(db_session: AsyncSession) -> None: """pm_approvals column is gone from the tasks table.""" result = await db_session.execute( text( @@ -25,7 +30,9 @@ async def test_pm_approvals_dropped(db_session) -> None: # type: ignore[no-unty @pytest.mark.asyncio -async def test_quick_context_and_proactive_context_remain(db_session) -> None: # type: ignore[no-untyped-def] +async def test_quick_context_and_proactive_context_remain( + db_session: AsyncSession, +) -> None: """quick_context and proactive_context MUST remain — they're actively used.""" result = await db_session.execute( text( diff --git a/tests/integration/test_migration_observability.py b/tests/integration/test_migration_observability.py index 06213e0f..a8f061f7 100644 --- a/tests/integration/test_migration_observability.py +++ b/tests/integration/test_migration_observability.py @@ -10,12 +10,17 @@ discipline); these assertions guard the resulting schema shape. from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from sqlalchemy import text +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession + @pytest.mark.asyncio -async def test_revision_count_defaults_to_zero(db_session) -> None: # type: ignore[no-untyped-def] +async def test_revision_count_defaults_to_zero(db_session: AsyncSession) -> None: result = await db_session.execute( text( "SELECT column_default, is_nullable " @@ -30,7 +35,7 @@ async def test_revision_count_defaults_to_zero(db_session) -> None: # type: ign @pytest.mark.asyncio -async def test_audit_log_query_index_exists(db_session) -> None: # type: ignore[no-untyped-def] +async def test_audit_log_query_index_exists(db_session: AsyncSession) -> None: result = await db_session.execute( text( "SELECT indexname FROM pg_indexes " diff --git a/tests/unit/api/test_schemas_tasks.py b/tests/unit/api/test_schemas_tasks.py index 7e159495..a6f103a9 100644 --- a/tests/unit/api/test_schemas_tasks.py +++ b/tests/unit/api/test_schemas_tasks.py @@ -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 diff --git a/tests/unit/gateway/test_pr_gate_posts_review.py b/tests/unit/gateway/test_pr_gate_posts_review.py index 7929ede9..bad11c40 100644 --- a/tests/unit/gateway/test_pr_gate_posts_review.py +++ b/tests/unit/gateway/test_pr_gate_posts_review.py @@ -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 diff --git a/tests/unit/gateway/test_pr_review_hand_format_guard.py b/tests/unit/gateway/test_pr_review_hand_format_guard.py index da7a7831..9836745d 100644 --- a/tests/unit/gateway/test_pr_review_hand_format_guard.py +++ b/tests/unit/gateway/test_pr_review_hand_format_guard.py @@ -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: diff --git a/tests/unit/llm/test_providers.py b/tests/unit/llm/test_providers.py index 32668e11..5f8bad9b 100644 --- a/tests/unit/llm/test_providers.py +++ b/tests/unit/llm/test_providers.py @@ -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()) diff --git a/tests/unit/mcp_servers/test_do_server.py b/tests/unit/mcp_servers/test_do_server.py index df0f6c36..6c89ba4c 100644 --- a/tests/unit/mcp_servers/test_do_server.py +++ b/tests/unit/mcp_servers/test_do_server.py @@ -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() diff --git a/tests/unit/models/test_misc_models.py b/tests/unit/models/test_misc_models.py index e59af1ef..d0213fb2 100644 --- a/tests/unit/models/test_misc_models.py +++ b/tests/unit/models/test_misc_models.py @@ -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: diff --git a/tests/unit/runtime/test_ci_watch_loop.py b/tests/unit/runtime/test_ci_watch_loop.py index bc1a7d62..983a9295 100644 --- a/tests/unit/runtime/test_ci_watch_loop.py +++ b/tests/unit/runtime/test_ci_watch_loop.py @@ -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() diff --git a/tests/unit/runtime/test_dep_update_loop.py b/tests/unit/runtime/test_dep_update_loop.py index a1a1c00c..6742813e 100644 --- a/tests/unit/runtime/test_dep_update_loop.py +++ b/tests/unit/runtime/test_dep_update_loop.py @@ -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() diff --git a/tests/unit/runtime/test_no_spawn_human_roles.py b/tests/unit/runtime/test_no_spawn_human_roles.py index 23f08afd..b4a2b1d2 100644 --- a/tests/unit/runtime/test_no_spawn_human_roles.py +++ b/tests/unit/runtime/test_no_spawn_human_roles.py @@ -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", diff --git a/tests/unit/runtime/test_pm_closure_auto_resume.py b/tests/unit/runtime/test_pm_closure_auto_resume.py index 088e14e6..a9870cfe 100644 --- a/tests/unit/runtime/test_pm_closure_auto_resume.py +++ b/tests/unit/runtime/test_pm_closure_auto_resume.py @@ -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( diff --git a/tests/unit/runtime/test_readopt_running_agents.py b/tests/unit/runtime/test_readopt_running_agents.py index 8875b379..f8c232f8 100644 --- a/tests/unit/runtime/test_readopt_running_agents.py +++ b/tests/unit/runtime/test_readopt_running_agents.py @@ -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() diff --git a/tests/unit/services/test_conventions_cache_put.py b/tests/unit/services/test_conventions_cache_put.py index edd594d0..ed3504a9 100644 --- a/tests/unit/services/test_conventions_cache_put.py +++ b/tests/unit/services/test_conventions_cache_put.py @@ -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") diff --git a/tests/unit/services/test_conventions_resolve.py b/tests/unit/services/test_conventions_resolve.py index 65590e91..36b55f71 100644 --- a/tests/unit/services/test_conventions_resolve.py +++ b/tests/unit/services/test_conventions_resolve.py @@ -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: diff --git a/tests/unit/services/test_git_diff_base_fallback.py b/tests/unit/services/test_git_diff_base_fallback.py index 48140171..9eb222fd 100644 --- a/tests/unit/services/test_git_diff_base_fallback.py +++ b/tests/unit/services/test_git_diff_base_fallback.py @@ -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/ 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/ 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/ exists (open_pr pushed it) — diff must target origin/.""" 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/, not base... (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 diff --git a/tests/unit/services/test_git_is_behind_base.py b/tests/unit/services/test_git_is_behind_base.py index 630eaf85..df56e8b8 100644 --- a/tests/unit/services/test_git_is_behind_base.py +++ b/tests/unit/services/test_git_is_behind_base.py @@ -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) diff --git a/tests/unit/services/test_propagate_sessions_to_subtask.py b/tests/unit/services/test_propagate_sessions_to_subtask.py index 8750acaa..12e4b813 100644 --- a/tests/unit/services/test_propagate_sessions_to_subtask.py +++ b/tests/unit/services/test_propagate_sessions_to_subtask.py @@ -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]] = [] diff --git a/tests/unit/services/test_record_plan_progress.py b/tests/unit/services/test_record_plan_progress.py index 3bf1e4cc..b97677c2 100644 --- a/tests/unit/services/test_record_plan_progress.py +++ b/tests/unit/services/test_record_plan_progress.py @@ -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 diff --git a/tests/unit/services/test_services_base.py b/tests/unit/services/test_services_base.py index 63f1ed2e..3c30f320 100644 --- a/tests/unit/services/test_services_base.py +++ b/tests/unit/services/test_services_base.py @@ -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 diff --git a/tests/unit/test_notification_dedup.py b/tests/unit/test_notification_dedup.py index 06555d7c..6b96b3f7 100644 --- a/tests/unit/test_notification_dedup.py +++ b/tests/unit/test_notification_dedup.py @@ -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,