mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(tests): satisfy the full gate's tests/-scoped mypy (#577)
CI runs `mypy roboco/ tests/`; the bridge/cockpit suites were only gated against `mypy roboco/` locally. Real credentials dataclass instead of SimpleNamespace, AsyncMock casts where mocks sit behind typed fields, a return annotation on the fake stream, a None-guard on the consumer task, and a fresh registry lookup where mypy's literal narrowing read an assert as always-false. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -6,13 +6,17 @@ the stream consumer's turn/draft forwarding, idle sweep, and the engine's
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from roboco.services import telegram_bridge as bridge
|
from roboco.services import telegram_bridge as bridge
|
||||||
from roboco.services import telegram_inbound as ti
|
from roboco.services import telegram_inbound as ti
|
||||||
|
from roboco.services.telegram_credentials import TelegramCredentialsData
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import AsyncIterator
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@@ -35,7 +39,7 @@ def _engine() -> ti.TelegramInboundEngine:
|
|||||||
return ti.TelegramInboundEngine(_fake_session_db())
|
return ti.TelegramInboundEngine(_fake_session_db())
|
||||||
|
|
||||||
|
|
||||||
CREDS = SimpleNamespace(bot_token="123:ABC", chat_id="777")
|
CREDS = TelegramCredentialsData(bot_token="123:ABC", chat_id="777")
|
||||||
|
|
||||||
|
|
||||||
class FakeRegistry:
|
class FakeRegistry:
|
||||||
@@ -52,7 +56,7 @@ class FakeRegistry:
|
|||||||
self.parked.append((session_id, task_id))
|
self.parked.append((session_id, task_id))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def stream(self, _session_id: str):
|
async def stream(self, _session_id: str) -> AsyncIterator[dict[str, Any]]:
|
||||||
for event in self.events:
|
for event in self.events:
|
||||||
yield event
|
yield event
|
||||||
|
|
||||||
@@ -150,7 +154,7 @@ async def test_consumer_accumulates_turns_and_surfaces_drafts(
|
|||||||
bridge._SESSIONS["777"] = sess
|
bridge._SESSIONS["777"] = sess
|
||||||
await bridge._consume("777", sess)
|
await bridge._consume("777", sess)
|
||||||
|
|
||||||
calls = sess.client.send_message.await_args_list
|
calls = cast("AsyncMock", sess.client).send_message.await_args_list
|
||||||
# Turn text, then the draft card, then the end-of-session note.
|
# Turn text, then the draft card, then the end-of-session note.
|
||||||
assert calls[0].args[0] == "Hello CEO."
|
assert calls[0].args[0] == "Hello CEO."
|
||||||
assert "Fix <thing>" in calls[1].args[0]
|
assert "Fix <thing>" in calls[1].args[0]
|
||||||
@@ -161,7 +165,7 @@ async def test_consumer_accumulates_turns_and_surfaces_drafts(
|
|||||||
assert calls[-1].args[0] == "Session ended."
|
assert calls[-1].args[0] == "Session ended."
|
||||||
# Stream ended → session evicted, client closed.
|
# Stream ended → session evicted, client closed.
|
||||||
assert "777" not in bridge._SESSIONS
|
assert "777" not in bridge._SESSIONS
|
||||||
sess.client.close.assert_awaited_once()
|
cast("AsyncMock", sess.client).close.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -182,6 +186,7 @@ async def test_start_secretary_opens_session_and_consumer(
|
|||||||
orch.start_secretary_session.assert_awaited_once_with(
|
orch.start_secretary_session.assert_awaited_once_with(
|
||||||
sess.session_id, initial_message="plan my day"
|
sess.session_id, initial_message="plan my day"
|
||||||
)
|
)
|
||||||
|
assert sess.consumer is not None
|
||||||
await sess.consumer # the mocked _consume task completes cleanly
|
await sess.consumer # the mocked _consume task completes cleanly
|
||||||
|
|
||||||
|
|
||||||
@@ -299,6 +304,9 @@ async def test_intake_discard_keeps_session(
|
|||||||
assert parsed is not None
|
assert parsed is not None
|
||||||
await engine._handle_bridge_callback(parsed, "777", 5, CREDS, client)
|
await engine._handle_bridge_callback(parsed, "777", 5, CREDS, client)
|
||||||
|
|
||||||
assert sess.pending_draft is None
|
# Fresh registry lookup — asserting on the narrowed local would read as
|
||||||
assert "777" in bridge._SESSIONS
|
# always-false to mypy after the literal assignment above.
|
||||||
assert finish.await_args.args[2] is True
|
assert bridge._SESSIONS["777"].pending_draft is None
|
||||||
|
last_call = finish.await_args
|
||||||
|
assert last_call is not None
|
||||||
|
assert last_call.args[2] is True
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ and degrades to an all-zeros brief on an empty company.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, cast
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -161,7 +161,7 @@ async def test_today_composes_needs_you_fleet_and_ship(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await _seed_working_agent(db_session, awaiting.id)
|
await _seed_working_agent(db_session, cast("UUID", awaiting.id))
|
||||||
|
|
||||||
brief = await get_tg_cockpit_service(db_session).today()
|
brief = await get_tg_cockpit_service(db_session).today()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user