Shared publish_safely helper at decnet/bus/publish.py so the nine workers about to be wired into the bus don't each copy-paste the "never raise back at the caller" contract. Mutator drops its private copy and imports the canonical one. topics.py gains the attacker.* hierarchy (observed, scored, session.started, session.ended) and a system_health(worker) builder for per-worker health heartbeats — both prerequisites for the worker rollout under DEBT-031.
65 lines
2.2 KiB
Python
65 lines
2.2 KiB
Python
"""Tests for :mod:`decnet.bus.publish`.
|
|
|
|
The whole point of ``publish_safely`` is that it never raises back at the
|
|
caller. These tests pin that contract: ``None`` bus is a no-op, a real
|
|
bus publishes, and a raising bus is swallowed + logged.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
import pytest
|
|
|
|
from decnet.bus.base import BaseBus, Event, Subscription
|
|
from decnet.bus.fake import FakeBus
|
|
from decnet.bus.publish import publish_safely
|
|
|
|
|
|
class _ExplodingBus(BaseBus):
|
|
"""Minimal bus whose ``publish`` always raises."""
|
|
|
|
async def connect(self) -> None: # pragma: no cover - trivial
|
|
return None
|
|
|
|
async def publish(self, topic, payload, *, event_type=""):
|
|
raise RuntimeError("transport exploded")
|
|
|
|
def subscribe(self, pattern: str) -> Subscription: # pragma: no cover
|
|
raise NotImplementedError
|
|
|
|
async def close(self) -> None: # pragma: no cover - trivial
|
|
return None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_publish_safely_none_bus_is_noop() -> None:
|
|
# Must not raise. A worker that couldn't connect at startup passes
|
|
# bus=None and expects every call to silently no-op.
|
|
await publish_safely(None, "system.log", {"msg": "hi"})
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_publish_safely_delivers_on_live_bus() -> None:
|
|
bus = FakeBus()
|
|
await bus.connect()
|
|
try:
|
|
sub = bus.subscribe("system.log")
|
|
async with sub:
|
|
await publish_safely(bus, "system.log", {"msg": "hi"}, event_type="log")
|
|
event = await sub.__anext__()
|
|
assert isinstance(event, Event)
|
|
assert event.topic == "system.log"
|
|
assert event.type == "log"
|
|
assert event.payload == {"msg": "hi"}
|
|
finally:
|
|
await bus.close()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_publish_safely_swallows_transport_errors(caplog: pytest.LogCaptureFixture) -> None:
|
|
caplog.set_level(logging.WARNING, logger="bus.publish")
|
|
# The exploding bus would crash the caller without publish_safely.
|
|
# After wrapping, the caller sees nothing but a log line.
|
|
await publish_safely(_ExplodingBus(), "system.log", {"msg": "hi"})
|
|
assert any("bus publish failed" in rec.message for rec in caplog.records)
|