104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
|
|
"""Unit tests for Signal daemon lifecycle and SSE reconnect."""
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channels.adapters.signal.channel import SignalChannel
|
||
|
|
from yuxi.channels.adapters.signal.daemon import SignalDaemonManager
|
||
|
|
|
||
|
|
|
||
|
|
class TestDaemonLifecycle:
|
||
|
|
def test_daemon_constructor_defaults(self):
|
||
|
|
daemon = SignalDaemonManager(
|
||
|
|
cli_path="signal-cli",
|
||
|
|
account="+1234",
|
||
|
|
http_listen="127.0.0.1:8090",
|
||
|
|
)
|
||
|
|
assert daemon._account == "+1234"
|
||
|
|
assert daemon._http_listen == "127.0.0.1:8090"
|
||
|
|
assert daemon._process is None
|
||
|
|
assert daemon._on_crash is None
|
||
|
|
|
||
|
|
def test_crash_triggers_handler(self):
|
||
|
|
handler_called = False
|
||
|
|
|
||
|
|
async def crash_handler(code, msg):
|
||
|
|
nonlocal handler_called
|
||
|
|
handler_called = True
|
||
|
|
|
||
|
|
daemon = SignalDaemonManager(
|
||
|
|
cli_path="signal-cli",
|
||
|
|
account="+1234",
|
||
|
|
http_listen="127.0.0.1:8091",
|
||
|
|
)
|
||
|
|
daemon.on_crash(crash_handler)
|
||
|
|
assert daemon._crash_handler is not None
|
||
|
|
|
||
|
|
async def _test():
|
||
|
|
await daemon._crash_handler(1, "test crash")
|
||
|
|
|
||
|
|
asyncio.run(_test())
|
||
|
|
assert handler_called
|
||
|
|
|
||
|
|
def test_stop_no_process(self):
|
||
|
|
daemon = SignalDaemonManager(
|
||
|
|
cli_path="signal-cli",
|
||
|
|
account="+1234",
|
||
|
|
http_listen="127.0.0.1:8092",
|
||
|
|
)
|
||
|
|
|
||
|
|
async def _test():
|
||
|
|
await daemon.stop()
|
||
|
|
|
||
|
|
asyncio.run(_test())
|
||
|
|
|
||
|
|
|
||
|
|
class TestSSEReconnectConstants:
|
||
|
|
def test_max_reconnect_delay(self):
|
||
|
|
from yuxi.channels.adapters.signal.sse_reconnect import MAX_RECONNECT_DELAY, INITIAL_RECONNECT_DELAY
|
||
|
|
|
||
|
|
assert MAX_RECONNECT_DELAY > INITIAL_RECONNECT_DELAY
|
||
|
|
assert MAX_RECONNECT_DELAY == 60.0
|
||
|
|
assert INITIAL_RECONNECT_DELAY == 1.0
|
||
|
|
|
||
|
|
def test_max_reconnect_count(self):
|
||
|
|
from yuxi.channels.adapters.signal.sse_reconnect import MAX_RECONNECT_COUNT
|
||
|
|
|
||
|
|
assert MAX_RECONNECT_COUNT > 0
|
||
|
|
|
||
|
|
def test_exponential_backoff_formula(self):
|
||
|
|
from yuxi.channels.adapters.signal.sse_reconnect import INITIAL_RECONNECT_DELAY
|
||
|
|
|
||
|
|
delay = INITIAL_RECONNECT_DELAY
|
||
|
|
for i in range(5):
|
||
|
|
delay = min(delay * 2, 60.0)
|
||
|
|
assert delay <= 60.0
|
||
|
|
|
||
|
|
|
||
|
|
class TestSignalChannelDaemonIntegration:
|
||
|
|
def test_connect_sets_attributes(self):
|
||
|
|
channel = SignalChannel({"enabled": False})
|
||
|
|
assert channel._daemon is None
|
||
|
|
assert channel._monitor is None
|
||
|
|
assert channel._status.value == "disconnected"
|
||
|
|
|
||
|
|
def test_disconnect_cleans_stream_buffer(self):
|
||
|
|
channel = SignalChannel()
|
||
|
|
channel._stream_buffer.set("chat1", "some content")
|
||
|
|
channel._stream_buffer.set("chat2", "more content")
|
||
|
|
assert channel._stream_buffer.get("chat1") == "some content"
|
||
|
|
|
||
|
|
channel._stream_buffer.clear()
|
||
|
|
assert channel._stream_buffer.get("chat1") == ""
|
||
|
|
assert channel._stream_buffer.get("chat2") == ""
|
||
|
|
|
||
|
|
def test_health_check_not_initialized(self):
|
||
|
|
async def _test():
|
||
|
|
channel = SignalChannel()
|
||
|
|
result = await channel.health_check()
|
||
|
|
assert result.status == "unhealthy"
|
||
|
|
assert "not initialized" in result.last_error.lower()
|
||
|
|
|
||
|
|
asyncio.run(_test())
|