Compare commits
1 Commits
fix/flaky-
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e69228b793 |
@@ -1,10 +1,21 @@
|
||||
"""Tests for the async event bus (infrastructure.events.bus)."""
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from infrastructure.events.bus import Event, EventBus, emit, event_bus, on
|
||||
import infrastructure.events.bus as bus_module
|
||||
from infrastructure.events.bus import (
|
||||
Event,
|
||||
EventBus,
|
||||
emit,
|
||||
event_bus,
|
||||
get_event_bus,
|
||||
init_event_bus_persistence,
|
||||
on,
|
||||
)
|
||||
|
||||
|
||||
class TestEvent:
|
||||
@@ -349,3 +360,111 @@ class TestEventBusPersistence:
|
||||
assert mode == "wal"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
async def test_persist_event_exception_is_swallowed(self, tmp_path):
|
||||
"""_persist_event must not propagate SQLite errors."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
bus = EventBus()
|
||||
bus.enable_persistence(tmp_path / "events.db")
|
||||
|
||||
# Make the INSERT raise an OperationalError
|
||||
mock_conn = MagicMock()
|
||||
mock_conn.execute.side_effect = sqlite3.OperationalError("simulated failure")
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def fake_ctx():
|
||||
yield mock_conn
|
||||
|
||||
with patch.object(bus, "_get_persistence_conn", fake_ctx):
|
||||
# Should not raise
|
||||
bus._persist_event(Event(type="x", source="s"))
|
||||
|
||||
async def test_replay_exception_returns_empty(self, tmp_path):
|
||||
"""replay() must return [] when SQLite query fails."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
bus = EventBus()
|
||||
bus.enable_persistence(tmp_path / "events.db")
|
||||
|
||||
mock_conn = MagicMock()
|
||||
mock_conn.execute.side_effect = sqlite3.OperationalError("simulated failure")
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def fake_ctx():
|
||||
yield mock_conn
|
||||
|
||||
with patch.object(bus, "_get_persistence_conn", fake_ctx):
|
||||
result = bus.replay()
|
||||
assert result == []
|
||||
|
||||
|
||||
# ── Singleton helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSingletonHelpers:
|
||||
"""Test get_event_bus(), init_event_bus_persistence(), and module __getattr__."""
|
||||
|
||||
def test_get_event_bus_returns_same_instance(self):
|
||||
"""get_event_bus() is a true singleton."""
|
||||
a = get_event_bus()
|
||||
b = get_event_bus()
|
||||
assert a is b
|
||||
|
||||
def test_module_event_bus_attr_is_singleton(self):
|
||||
"""Accessing bus_module.event_bus via __getattr__ returns the singleton."""
|
||||
assert bus_module.event_bus is get_event_bus()
|
||||
|
||||
def test_module_getattr_unknown_raises(self):
|
||||
"""Accessing an unknown module attribute raises AttributeError."""
|
||||
with pytest.raises(AttributeError):
|
||||
_ = bus_module.no_such_attr # type: ignore[attr-defined]
|
||||
|
||||
def test_init_event_bus_persistence_sets_path(self, tmp_path):
|
||||
"""init_event_bus_persistence() enables persistence on the singleton."""
|
||||
bus = get_event_bus()
|
||||
original_path = bus._persistence_db_path
|
||||
try:
|
||||
bus._persistence_db_path = None # reset for the test
|
||||
db_path = tmp_path / "test_init.db"
|
||||
init_event_bus_persistence(db_path)
|
||||
assert bus._persistence_db_path == db_path
|
||||
finally:
|
||||
bus._persistence_db_path = original_path
|
||||
|
||||
def test_init_event_bus_persistence_is_idempotent(self, tmp_path):
|
||||
"""Calling init_event_bus_persistence() twice keeps the first path."""
|
||||
bus = get_event_bus()
|
||||
original_path = bus._persistence_db_path
|
||||
try:
|
||||
bus._persistence_db_path = None
|
||||
first_path = tmp_path / "first.db"
|
||||
second_path = tmp_path / "second.db"
|
||||
init_event_bus_persistence(first_path)
|
||||
init_event_bus_persistence(second_path) # should be ignored
|
||||
assert bus._persistence_db_path == first_path
|
||||
finally:
|
||||
bus._persistence_db_path = original_path
|
||||
|
||||
def test_init_event_bus_persistence_default_path(self):
|
||||
"""init_event_bus_persistence() uses 'data/events.db' when no path given."""
|
||||
bus = get_event_bus()
|
||||
original_path = bus._persistence_db_path
|
||||
try:
|
||||
bus._persistence_db_path = None
|
||||
# Patch enable_persistence to capture what path it receives
|
||||
captured = {}
|
||||
|
||||
def fake_enable(path: Path) -> None:
|
||||
captured["path"] = path
|
||||
|
||||
with patch.object(bus, "enable_persistence", side_effect=fake_enable):
|
||||
init_event_bus_persistence()
|
||||
|
||||
assert captured["path"] == Path("data/events.db")
|
||||
finally:
|
||||
bus._persistence_db_path = original_path
|
||||
|
||||
Reference in New Issue
Block a user