This commit is contained in:
Renn F
2026-05-05 05:50:01 +02:00
parent 85ef124c8f
commit b6903490f1
35 changed files with 9777 additions and 0 deletions
+221
View File
@@ -0,0 +1,221 @@
"""Event handler coverage — fanout to notification service."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
import pytest
from roboco.events.bus import Event, EventType
from roboco.events.handlers import (
_get_doc_id,
_get_pm_id,
_get_qa_id,
handle_blocker_resolved,
handle_handoff_created,
handle_qa_result,
handle_session_boundary,
handle_task_status_change,
set_event_context,
)
def _make_event(event_type: EventType, **data) -> Event:
return Event(
type=event_type,
data=data,
source_agent="be-dev-1",
)
@pytest.fixture(autouse=True)
def reset_context():
"""Reset event context after each test."""
yield
set_event_context(notification_service=None, orchestrator=None)
# ---------------------------------------------------------------------------
# ID builders
# ---------------------------------------------------------------------------
def test_get_pm_id() -> None:
assert _get_pm_id("backend") == "ba-pm"
def test_get_qa_id() -> None:
assert _get_qa_id("frontend") == "fr-qa"
def test_get_doc_id() -> None:
assert _get_doc_id("backend") == "ba-doc"
# ---------------------------------------------------------------------------
# Task status handlers — no-op when no notification service
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_handle_task_blocked_without_context_is_noop() -> None:
event = _make_event(
EventType.TASK_BLOCKED, task_id=str(uuid4()), team="backend", reason="x"
)
# No notification_service set — does nothing.
await handle_task_status_change(event)
@pytest.mark.asyncio
async def test_handle_task_blocked_calls_send_blocker() -> None:
notif = MagicMock()
notif.send_blocker_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(
EventType.TASK_BLOCKED, task_id=str(uuid4()), team="backend", reason="x"
)
await handle_task_status_change(event)
notif.send_blocker_notification.assert_called_once()
@pytest.mark.asyncio
async def test_handle_task_blocked_skips_when_no_team() -> None:
notif = MagicMock()
notif.send_blocker_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(EventType.TASK_BLOCKED, task_id=str(uuid4()))
await handle_task_status_change(event)
notif.send_blocker_notification.assert_not_called()
@pytest.mark.asyncio
async def test_handle_task_awaiting_qa() -> None:
notif = MagicMock()
notif.send_qa_ready_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(
EventType.TASK_AWAITING_QA, task_id=str(uuid4()), team="backend"
)
await handle_task_status_change(event)
notif.send_qa_ready_notification.assert_called_once()
@pytest.mark.asyncio
async def test_handle_task_qa_failed() -> None:
notif = MagicMock()
notif.send_qa_failed_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(
EventType.TASK_QA_FAILED,
task_id=str(uuid4()),
assigned_to="be-dev-1",
qa_notes="please fix",
)
await handle_task_status_change(event)
notif.send_qa_failed_notification.assert_called_once()
@pytest.mark.asyncio
async def test_handle_task_qa_failed_no_assigned_to_skips() -> None:
notif = MagicMock()
notif.send_qa_failed_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(EventType.TASK_QA_FAILED, task_id=str(uuid4()))
await handle_task_status_change(event)
notif.send_qa_failed_notification.assert_not_called()
@pytest.mark.asyncio
async def test_handle_task_awaiting_docs() -> None:
notif = MagicMock()
notif.send_docs_ready_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(
EventType.TASK_AWAITING_DOCS, task_id=str(uuid4()), team="backend"
)
await handle_task_status_change(event)
notif.send_docs_ready_notification.assert_called_once()
@pytest.mark.asyncio
async def test_handle_task_status_change_unknown_type_noop() -> None:
"""No mapping for TASK_CREATED — does nothing."""
event = _make_event(EventType.TASK_CREATED, task_id=str(uuid4()))
await handle_task_status_change(event) # No raise.
# ---------------------------------------------------------------------------
# Session and handoff handlers
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_handle_session_boundary_logs_and_returns() -> None:
"""Just exercises logging — no notification fanout in this handler."""
event = _make_event(
EventType.SESSION_CLOSED,
session_id=str(uuid4()),
group_id=str(uuid4()),
reason="timeout",
)
await handle_session_boundary(event)
@pytest.mark.asyncio
async def test_handle_handoff_created_calls_notification() -> None:
notif = MagicMock()
notif.send_handoff_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(
EventType.HANDOFF_CREATED,
task_id=str(uuid4()),
handoff_id=str(uuid4()),
team="backend",
)
await handle_handoff_created(event)
notif.send_handoff_notification.assert_called_once()
@pytest.mark.asyncio
async def test_handle_handoff_created_no_team_skips_notification() -> None:
notif = MagicMock()
notif.send_handoff_notification = AsyncMock()
set_event_context(notification_service=notif)
event = _make_event(
EventType.HANDOFF_CREATED,
task_id=str(uuid4()),
handoff_id=str(uuid4()),
)
await handle_handoff_created(event)
notif.send_handoff_notification.assert_not_called()
# ---------------------------------------------------------------------------
# QA result + blocker resolved
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_handle_qa_result_passed() -> None:
"""QA passed event triggers wait resolution if dev is waiting."""
orch = MagicMock()
orch.get_waiting_agents = MagicMock(return_value={})
orch.resolve_wait = AsyncMock()
set_event_context(orchestrator=orch)
event = _make_event(
EventType.TASK_QA_PASSED,
task_id=str(uuid4()),
assigned_to="be-dev-1",
)
await handle_qa_result(event)
@pytest.mark.asyncio
async def test_handle_blocker_resolved_logs() -> None:
event = _make_event(
EventType.TASK_UNBLOCKED,
task_id=str(uuid4()),
agent_id="be-dev-1",
resolution="fixed",
)
await handle_blocker_resolved(event)