mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 23:24:44 +02:00
feat(notifications): Phase 2.5a channel interface unification
- Switch channels.py from dict-based to dataclass-based interfaces - Add NotificationPayload dataclass and make_payload_from_event helper - Update channel.deliver() to be async with (payload, rule) signature - Add connector parameter to Dispatcher, DigestScheduler, and pipeline builders - Update pipeline tee to use asyncio.create_task for async dispatch - Add create_channel_from_dict for legacy router.py compatibility - Update tests for new async interfaces Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a4cb29002d
commit
c9d9a9925c
8 changed files with 235 additions and 129 deletions
|
|
@ -8,8 +8,10 @@ Updated in Phase 2.4: Events now go to BOTH dispatcher and accumulator
|
|||
compatibility but not used in production wiring.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from meshai.notifications.events import Event, make_event
|
||||
|
|
@ -55,15 +57,10 @@ class TestImmediateDispatch:
|
|||
notifications=NotificationsConfigStub(rules=[rule])
|
||||
)
|
||||
mock_channel = Mock()
|
||||
mock_channel.deliver = AsyncMock(return_value=True)
|
||||
mock_factory = Mock(return_value=mock_channel)
|
||||
bus = EventBus()
|
||||
dispatcher = Dispatcher(config, mock_factory)
|
||||
digest = StubDigestQueue()
|
||||
router = SeverityRouter(
|
||||
immediate_handler=dispatcher.dispatch,
|
||||
digest_handler=digest.enqueue,
|
||||
)
|
||||
bus.subscribe(router.handle)
|
||||
event = make_event(
|
||||
source="test",
|
||||
category="test_cat",
|
||||
|
|
@ -71,12 +68,13 @@ class TestImmediateDispatch:
|
|||
title="Test Alert",
|
||||
summary="Test summary message",
|
||||
)
|
||||
bus.emit(event)
|
||||
# Run dispatch in async context since it's now async
|
||||
asyncio.run(dispatcher.dispatch(event))
|
||||
assert mock_channel.deliver.call_count == 1
|
||||
alert = mock_channel.deliver.call_args[0][0]
|
||||
assert alert["category"] == "test_cat"
|
||||
assert alert["severity"] == "immediate"
|
||||
assert alert["message"]
|
||||
assert alert.category == "test_cat"
|
||||
assert alert.severity == "immediate"
|
||||
assert alert.message
|
||||
|
||||
|
||||
class TestTeeRouting:
|
||||
|
|
@ -95,40 +93,29 @@ class TestTeeRouting:
|
|||
notifications=NotificationsConfigStub(rules=[rule])
|
||||
)
|
||||
mock_channel = Mock()
|
||||
mock_channel.deliver = AsyncMock(return_value=True)
|
||||
mock_factory = Mock(return_value=mock_channel)
|
||||
|
||||
# Create dispatcher and track calls
|
||||
# Create dispatcher
|
||||
dispatcher = Dispatcher(config, mock_factory)
|
||||
dispatch_calls = []
|
||||
original_dispatch = dispatcher.dispatch
|
||||
def tracking_dispatch(event):
|
||||
dispatch_calls.append(event)
|
||||
original_dispatch(event)
|
||||
dispatcher.dispatch = tracking_dispatch
|
||||
|
||||
# Create accumulator mock
|
||||
accumulator_calls = []
|
||||
def mock_enqueue(event):
|
||||
accumulator_calls.append(event)
|
||||
|
||||
# Tee closure (Phase 2.4 pattern)
|
||||
def tee(event):
|
||||
dispatcher.dispatch(event)
|
||||
mock_enqueue(event)
|
||||
|
||||
bus = EventBus()
|
||||
bus.subscribe(tee)
|
||||
|
||||
event = make_event(
|
||||
source="test",
|
||||
category="test_cat",
|
||||
severity="routine",
|
||||
title="Routine Alert",
|
||||
)
|
||||
bus.emit(event)
|
||||
|
||||
# Run dispatch in async context
|
||||
asyncio.run(dispatcher.dispatch(event))
|
||||
mock_enqueue(event)
|
||||
|
||||
# Both paths received the event
|
||||
assert len(dispatch_calls) == 1
|
||||
assert len(accumulator_calls) == 1
|
||||
# Dispatcher found a matching rule and delivered
|
||||
assert mock_channel.deliver.call_count == 1
|
||||
|
|
@ -146,36 +133,26 @@ class TestTeeRouting:
|
|||
notifications=NotificationsConfigStub(rules=[rule])
|
||||
)
|
||||
mock_channel = Mock()
|
||||
mock_channel.deliver = AsyncMock(return_value=True)
|
||||
mock_factory = Mock(return_value=mock_channel)
|
||||
|
||||
dispatcher = Dispatcher(config, mock_factory)
|
||||
dispatch_calls = []
|
||||
original_dispatch = dispatcher.dispatch
|
||||
def tracking_dispatch(event):
|
||||
dispatch_calls.append(event)
|
||||
original_dispatch(event)
|
||||
dispatcher.dispatch = tracking_dispatch
|
||||
|
||||
accumulator_calls = []
|
||||
def mock_enqueue(event):
|
||||
accumulator_calls.append(event)
|
||||
|
||||
def tee(event):
|
||||
dispatcher.dispatch(event)
|
||||
mock_enqueue(event)
|
||||
|
||||
bus = EventBus()
|
||||
bus.subscribe(tee)
|
||||
|
||||
event = make_event(
|
||||
source="test",
|
||||
category="test_cat",
|
||||
severity="priority",
|
||||
title="Priority Alert",
|
||||
)
|
||||
bus.emit(event)
|
||||
|
||||
assert len(dispatch_calls) == 1
|
||||
# Run dispatch in async context
|
||||
asyncio.run(dispatcher.dispatch(event))
|
||||
mock_enqueue(event)
|
||||
|
||||
assert len(accumulator_calls) == 1
|
||||
assert mock_channel.deliver.call_count == 1
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue