87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
|
|
from opc.channels.manager import ChannelManager
|
|
from opc.channels.provider_base import PollingChannel, SocketChannel
|
|
from opc.channels.provider_registry import PROVIDER_SPECS, ordered_provider_specs
|
|
from opc.core.config import OPCConfig
|
|
from opc.layer0_interaction.message_bus import MessageBus
|
|
|
|
|
|
class _PollingProbe(PollingChannel):
|
|
name = "poll-probe"
|
|
required_package = None
|
|
|
|
def __init__(self, config, bus):
|
|
super().__init__(config, bus)
|
|
self.calls = 0
|
|
|
|
def get_poll_interval_seconds(self) -> float:
|
|
return 0.01
|
|
|
|
async def poll_once(self) -> None:
|
|
self.calls += 1
|
|
if self.calls >= 2:
|
|
self.mark_stopped()
|
|
|
|
|
|
class _SocketProbe(SocketChannel):
|
|
name = "socket-probe"
|
|
required_package = None
|
|
|
|
def __init__(self, config, bus):
|
|
super().__init__(config, bus)
|
|
self.calls = 0
|
|
|
|
async def run_socket_forever(self) -> None:
|
|
self.calls += 1
|
|
if self.calls == 1:
|
|
raise RuntimeError("boom")
|
|
self.mark_stopped()
|
|
|
|
|
|
class ChannelProviderRegistryTests(unittest.TestCase):
|
|
def test_provider_registry_covers_all_builtin_channels(self) -> None:
|
|
self.assertEqual(
|
|
[spec.name for spec in ordered_provider_specs()],
|
|
["telegram", "whatsapp", "discord", "feishu", "mochat", "dingtalk", "email", "slack", "qq", "matrix"],
|
|
)
|
|
self.assertTrue(PROVIDER_SPECS["whatsapp"].bridge_required)
|
|
self.assertEqual(PROVIDER_SPECS["slack"].extra_name, "channels-slack")
|
|
|
|
|
|
class ChannelRuntimeScaffoldTests(unittest.IsolatedAsyncioTestCase):
|
|
async def test_polling_channel_runs_loop_until_stopped(self) -> None:
|
|
channel = _PollingProbe(SimpleNamespace(allow_from=["*"]), MessageBus())
|
|
await channel.start()
|
|
await asyncio.sleep(0.05)
|
|
await channel.stop()
|
|
self.assertGreaterEqual(channel.calls, 2)
|
|
|
|
async def test_socket_channel_retries_after_failure(self) -> None:
|
|
channel = _SocketProbe(SimpleNamespace(allow_from=["*"]), MessageBus())
|
|
channel.reconnect_delay_seconds = 0.01
|
|
await channel.start()
|
|
await asyncio.sleep(0.05)
|
|
await channel.stop()
|
|
self.assertGreaterEqual(channel.calls, 2)
|
|
self.assertEqual(channel.last_error, "boom")
|
|
|
|
async def test_manager_status_reports_bridge_and_missing_config(self) -> None:
|
|
config = OPCConfig()
|
|
config.channels.slack.enabled = True
|
|
config.channels.slack.allow_from = ["*"]
|
|
config.channels.whatsapp.enabled = True
|
|
manager = ChannelManager(config, MessageBus())
|
|
slack_status = manager.get_status("slack")
|
|
whatsapp_status = manager.get_status("whatsapp")
|
|
self.assertTrue(slack_status["enabled"])
|
|
self.assertFalse(slack_status["configured"])
|
|
self.assertIn("bot_token", slack_status["missing_config"])
|
|
self.assertTrue(whatsapp_status["bridge_required"])
|
|
self.assertTrue(whatsapp_status["enabled"])
|
|
self.assertTrue(whatsapp_status["configured"])
|