"""Tests for core.bus — EventBus pub/sub system.""" import time import threading import pytest from core.bus import EventBus, Event class TestEventBus: def test_subscribe_and_publish(self, mock_bus): """A subscriber receives an event that was published.""" received = [] def handler(event): received.append(event) mock_bus.subscribe(handler, event_type="HOST_DISCOVERED") mock_bus.publish(Event( event_type="HOST_DISCOVERED", payload={"ip": "10.0.0.1"}, source_module="test", )) # Wait for dispatch deadline = time.time() + 3.0 while not received and time.time() < deadline: time.sleep(0.05) assert len(received) == 1 assert received[0].event_type == "HOST_DISCOVERED" assert received[0].payload["ip"] == "10.0.0.1" def test_publish_no_subscribers(self, mock_bus): """Publishing with no subscribers does not raise an error.""" mock_bus.publish(Event( event_type="CREDENTIAL_FOUND", payload={"user": "admin"}, source_module="test", )) # Give the dispatcher a moment to process time.sleep(0.2) # No crash = pass def test_multiple_subscribers(self, mock_bus): """Multiple subscribers for the same event type all receive the event.""" results_a = [] results_b = [] mock_bus.subscribe(lambda e: results_a.append(e), event_type="PCAP_ROTATED") mock_bus.subscribe(lambda e: results_b.append(e), event_type="PCAP_ROTATED") mock_bus.publish(Event( event_type="PCAP_ROTATED", payload={"file": "/tmp/test.pcap"}, source_module="test", )) deadline = time.time() + 3.0 while (not results_a or not results_b) and time.time() < deadline: time.sleep(0.05) assert len(results_a) == 1 assert len(results_b) == 1 def test_event_type_filtering(self, mock_bus): """A subscriber only receives events matching its registered type.""" received = [] mock_bus.subscribe(lambda e: received.append(e), event_type="CREDENTIAL_FOUND") # Publish a different event type mock_bus.publish(Event( event_type="HOST_DISCOVERED", payload={"ip": "10.0.0.2"}, source_module="test", )) # And the matching type mock_bus.publish(Event( event_type="CREDENTIAL_FOUND", payload={"user": "root"}, source_module="test", )) deadline = time.time() + 3.0 while len(received) < 1 and time.time() < deadline: time.sleep(0.05) # Allow a little extra time for any misdelivered events time.sleep(0.2) assert len(received) == 1 assert received[0].event_type == "CREDENTIAL_FOUND" def test_wildcard_subscriber(self, mock_bus): """A wildcard subscriber (event_type=None) receives all events.""" received = [] mock_bus.subscribe(lambda e: received.append(e), event_type=None) mock_bus.publish(Event(event_type="HOST_DISCOVERED", payload={}, source_module="test")) mock_bus.publish(Event(event_type="CREDENTIAL_FOUND", payload={}, source_module="test")) deadline = time.time() + 3.0 while len(received) < 2 and time.time() < deadline: time.sleep(0.05) assert len(received) == 2 types = {e.event_type for e in received} assert "HOST_DISCOVERED" in types assert "CREDENTIAL_FOUND" in types def test_event_dataclass(self): """Event dataclass fields and to_dict() work correctly.""" before = time.time() event = Event( event_type="MODULE_STARTED", payload={"module": "dns_logger", "pid": 1234}, source_module="engine", ) after = time.time() assert event.event_type == "MODULE_STARTED" assert event.payload["module"] == "dns_logger" assert event.source_module == "engine" assert before <= event.timestamp <= after d = event.to_dict() assert isinstance(d, dict) assert d["event_type"] == "MODULE_STARTED" assert d["payload"]["pid"] == 1234