1d30c3f6ce
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
129 lines
4.4 KiB
Python
129 lines
4.4 KiB
Python
"""End-to-end integration test for hooks → bus → correlation → response."""
|
|
|
|
import asyncio
|
|
import pytest
|
|
from aiohttp.test_utils import TestClient, TestServer
|
|
from nanobot.hooks.server import HooksServer
|
|
from nanobot.bus.queue import MessageBus
|
|
from nanobot.bus.events import InboundMessage, OutboundMessage
|
|
from nanobot.config.schema import HooksConfig
|
|
from nanobot.channels.hook import HookChannel
|
|
|
|
|
|
@pytest.fixture
|
|
def bus():
|
|
return MessageBus()
|
|
|
|
|
|
@pytest.fixture
|
|
def config():
|
|
return HooksConfig(
|
|
enabled=True,
|
|
tokens={"gitea": "gitea-secret", "ha": "ha-secret"},
|
|
timeout_seconds=5,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def server(bus, config):
|
|
return HooksServer(host="127.0.0.1", port=0, config=config, bus=bus)
|
|
|
|
|
|
async def fake_agent_loop(bus: MessageBus):
|
|
"""Simulate agent loop: consume inbound, process, publish outbound."""
|
|
msg = await asyncio.wait_for(bus.consume_inbound(), timeout=3.0)
|
|
response_content = f"Processed: {msg.content}"
|
|
await bus.publish_outbound(OutboundMessage(
|
|
channel=msg.channel,
|
|
chat_id=msg.chat_id,
|
|
content=response_content,
|
|
metadata=msg.metadata or {},
|
|
))
|
|
|
|
|
|
async def fake_dispatch_loop(bus: MessageBus, hook_channel: HookChannel):
|
|
"""Simulate outbound dispatcher: consume outbound, resolve correlation, dispatch."""
|
|
msg = await asyncio.wait_for(bus.consume_outbound(), timeout=3.0)
|
|
bus.resolve_correlation(msg)
|
|
if msg.channel == "hook":
|
|
await hook_channel.send(msg)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_full_hook_flow_default_channel(server, bus):
|
|
"""Hook with default channel: message goes through bus, response returned to HTTP caller."""
|
|
hook_channel = HookChannel(bus)
|
|
client = TestClient(TestServer(server._app))
|
|
async with client:
|
|
async def do_request():
|
|
return await client.post(
|
|
"/hooks",
|
|
json={"message": "deploy started"},
|
|
headers={"Authorization": "Bearer gitea-secret"},
|
|
)
|
|
|
|
# Run request + fake agent + fake dispatcher concurrently
|
|
request_task = asyncio.create_task(do_request())
|
|
agent_task = asyncio.create_task(fake_agent_loop(bus))
|
|
dispatch_task = asyncio.create_task(fake_dispatch_loop(bus, hook_channel))
|
|
|
|
resp = await asyncio.wait_for(request_task, timeout=5.0)
|
|
await agent_task
|
|
await dispatch_task
|
|
|
|
assert resp.status == 200
|
|
data = await resp.json()
|
|
assert data["ok"] is True
|
|
assert "deploy started" in data["response"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_full_hook_flow_telegram_channel(server, bus):
|
|
"""Hook targeting telegram: uses telegram session, response still returned to HTTP caller."""
|
|
hook_channel = HookChannel(bus)
|
|
client = TestClient(TestServer(server._app))
|
|
async with client:
|
|
async def do_request():
|
|
return await client.post(
|
|
"/hooks",
|
|
json={"message": "doorbell rang", "channel": "telegram", "chat_id": "239824268"},
|
|
headers={"Authorization": "Bearer ha-secret"},
|
|
)
|
|
|
|
request_task = asyncio.create_task(do_request())
|
|
agent_task = asyncio.create_task(fake_agent_loop(bus))
|
|
dispatch_task = asyncio.create_task(fake_dispatch_loop(bus, hook_channel))
|
|
|
|
resp = await asyncio.wait_for(request_task, timeout=5.0)
|
|
await agent_task
|
|
await dispatch_task
|
|
|
|
assert resp.status == 200
|
|
data = await resp.json()
|
|
assert data["ok"] is True
|
|
assert "doorbell rang" in data["response"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_named_token_identification(server, bus):
|
|
"""Different tokens should produce different hook_source in metadata."""
|
|
client = TestClient(TestServer(server._app))
|
|
async with client:
|
|
asyncio.create_task(client.post(
|
|
"/hooks",
|
|
json={"message": "from gitea"},
|
|
headers={"Authorization": "Bearer gitea-secret"},
|
|
))
|
|
msg1 = await asyncio.wait_for(bus.consume_inbound(), timeout=2.0)
|
|
assert msg1.metadata["hook_source"] == "gitea"
|
|
assert msg1.chat_id == "gitea"
|
|
|
|
asyncio.create_task(client.post(
|
|
"/hooks",
|
|
json={"message": "from ha"},
|
|
headers={"Authorization": "Bearer ha-secret"},
|
|
))
|
|
msg2 = await asyncio.wait_for(bus.consume_inbound(), timeout=2.0)
|
|
assert msg2.metadata["hook_source"] == "ha"
|
|
assert msg2.chat_id == "ha"
|