Files
cyclone/backend/tests/test_pubsub.py
T

100 lines
2.7 KiB
Python

"""Tests for the in-process EventBus.
These exercise the drop-oldest overflow strategy, per-kind fan-out, and
the non-blocking publish contract. They are async because the bus
exposes an async iterator for subscribers.
"""
from __future__ import annotations
import asyncio
import time
import pytest
from cyclone.pubsub import EventBus
@pytest.mark.asyncio
async def test_publish_no_subscribers_is_noop():
bus = EventBus()
# No subscribers at all — must not raise.
await bus.publish("x", {"a": 1})
@pytest.mark.asyncio
async def test_publish_to_n_subscribers_delivers_to_all():
bus = EventBus()
queues = [bus.subscribe(["claim"]) for _ in range(3)]
await bus.publish("claim", {"id": "C1"})
received = [await asyncio.wait_for(q.__anext__(), timeout=0.5) for q in queues]
for event in received:
assert event == {"id": "C1", "_kind": "claim"}
@pytest.mark.asyncio
async def test_subscriber_only_receives_matching_kinds():
bus = EventBus()
queue = bus.subscribe(["a"])
await bus.publish("b", {"n": 1})
await bus.publish("a", {"n": 2})
event = await asyncio.wait_for(queue.__anext__(), timeout=0.5)
assert event == {"n": 2, "_kind": "a"}
@pytest.mark.asyncio
async def test_slow_subscriber_does_not_block_publish():
bus = EventBus(max_queue_size=2)
queue = bus.subscribe(["e"])
async def emit():
for i in range(3):
await bus.publish("e", {"i": i})
start = time.monotonic()
await emit()
elapsed = time.monotonic() - start
# Publishing must not block even though the subscriber queue is full
# after the second event; the drop-oldest policy keeps publishes non-blocking.
assert elapsed < 0.05, f"publish blocked for {elapsed:.3f}s"
# The slow subscriber retains only the last two events.
seen = []
for _ in range(2):
seen.append(await asyncio.wait_for(queue.__anext__(), timeout=0.5))
assert [e["i"] for e in seen] == [1, 2]
@pytest.mark.asyncio
async def test_queue_overflow_drops_oldest():
bus = EventBus(max_queue_size=2)
queue = bus.subscribe(["e"])
for i in range(3):
await bus.publish("e", {"i": i})
seen = []
for _ in range(2):
seen.append(await asyncio.wait_for(queue.__anext__(), timeout=0.5))
assert [e["i"] for e in seen] == [1, 2]
@pytest.mark.asyncio
async def test_multiple_concurrent_subscribers_each_get_all_events():
bus = EventBus()
queues = [bus.subscribe(["claim"]) for _ in range(2)]
for i in range(5):
await bus.publish("claim", {"i": i})
for q in queues:
seen = []
for _ in range(5):
seen.append(await asyncio.wait_for(q.__anext__(), timeout=0.5))
assert [e["i"] for e in seen] == [0, 1, 2, 3, 4]