mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
+ tests
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user