"""Tests for timmy.event_bus — SensoryBus dispatcher.""" import pytest from timmy.event_bus import SensoryBus, get_sensory_bus from timmy.events import SensoryEvent def _make_event(event_type: str = "push", source: str = "gitea") -> SensoryEvent: return SensoryEvent(source=source, event_type=event_type) class TestSensoryBusEmitReceive: @pytest.mark.asyncio async def test_emit_calls_subscriber(self): bus = SensoryBus() received = [] bus.subscribe("push", lambda ev: received.append(ev)) ev = _make_event("push") count = await bus.emit(ev) assert count == 1 assert received == [ev] @pytest.mark.asyncio async def test_emit_async_handler(self): bus = SensoryBus() received = [] async def handler(ev: SensoryEvent): received.append(ev.event_type) bus.subscribe("morning", handler) await bus.emit(_make_event("morning", source="time")) assert received == ["morning"] @pytest.mark.asyncio async def test_no_match_returns_zero(self): bus = SensoryBus() bus.subscribe("push", lambda ev: None) count = await bus.emit(_make_event("issue_opened")) assert count == 0 @pytest.mark.asyncio async def test_wildcard_subscriber(self): bus = SensoryBus() received = [] bus.subscribe("*", lambda ev: received.append(ev.event_type)) await bus.emit(_make_event("push")) await bus.emit(_make_event("morning")) assert received == ["push", "morning"] @pytest.mark.asyncio async def test_handler_error_isolated(self): """A failing handler must not prevent other handlers from running.""" bus = SensoryBus() received = [] def bad_handler(ev: SensoryEvent): raise RuntimeError("boom") bus.subscribe("push", bad_handler) bus.subscribe("push", lambda ev: received.append("ok")) count = await bus.emit(_make_event("push")) assert count == 2 assert received == ["ok"] class TestSensoryBusRecent: @pytest.mark.asyncio async def test_recent_returns_last_n(self): bus = SensoryBus() for i in range(5): await bus.emit(_make_event(f"ev_{i}")) last_3 = bus.recent(3) assert len(last_3) == 3 assert [e.event_type for e in last_3] == ["ev_2", "ev_3", "ev_4"] @pytest.mark.asyncio async def test_recent_default(self): bus = SensoryBus() for i in range(3): await bus.emit(_make_event(f"ev_{i}")) assert len(bus.recent()) == 3 @pytest.mark.asyncio async def test_history_capped(self): bus = SensoryBus(max_history=5) for i in range(10): await bus.emit(_make_event(f"ev_{i}")) assert len(bus.recent(100)) == 5 class TestGetSensoryBus: def test_singleton(self): import timmy.event_bus as mod mod._bus = None # reset a = get_sensory_bus() b = get_sensory_bus() assert a is b mod._bus = None # cleanup