ForcePilot/backend/test/unit/channels/test_thread_binding_manager.py

161 lines
5.3 KiB
Python
Raw Normal View History

from __future__ import annotations
from datetime import datetime
import pytest
from yuxi.channels.thread_binding_manager import (
BindingType,
ThreadBinding,
ThreadBindingManager,
)
class TestBindingType:
def test_agent(self):
assert BindingType.AGENT == "agent"
def test_subagent(self):
assert BindingType.SUBAGENT == "subagent"
def test_acp(self):
assert BindingType.ACP == "acp"
class TestThreadBinding:
def test_creation(self):
binding = ThreadBinding(
thread_id="thread_1",
binding_type=BindingType.AGENT,
target_id="agent_1",
)
assert binding.thread_id == "thread_1"
assert binding.binding_type == BindingType.AGENT
assert binding.target_id == "agent_1"
assert binding.expires_at is None
assert binding.is_expired is False
def test_not_expired_without_expiry(self):
binding = ThreadBinding(
thread_id="t1",
binding_type=BindingType.AGENT,
target_id="a1",
)
assert binding.is_expired is False
def test_expired(self):
from datetime import timedelta
from yuxi.utils.datetime_utils import utc_now_naive
binding = ThreadBinding(
thread_id="t1",
binding_type=BindingType.AGENT,
target_id="a1",
expires_at=utc_now_naive() - timedelta(hours=1),
)
assert binding.is_expired is True
def test_default_metadata(self):
binding = ThreadBinding(
thread_id="t1",
binding_type=BindingType.AGENT,
target_id="a1",
)
assert binding.metadata == {}
class TestThreadBindingManager:
def test_bind_success(self):
mgr = ThreadBindingManager(default_ttl_hours=None)
binding = mgr.bind("thread_1", BindingType.AGENT, "agent_1")
assert binding.thread_id == "thread_1"
assert binding.target_id == "agent_1"
def test_bind_and_get(self):
mgr = ThreadBindingManager(default_ttl_hours=None)
mgr.bind("thread_1", BindingType.AGENT, "agent_1")
result = mgr.get_binding("thread_1", BindingType.AGENT)
assert result is not None
assert result.target_id == "agent_1"
def test_get_nonexistent(self):
mgr = ThreadBindingManager()
result = mgr.get_binding("nonexistent", BindingType.AGENT)
assert result is None
def test_unbind(self):
mgr = ThreadBindingManager(default_ttl_hours=None)
mgr.bind("thread_1", BindingType.AGENT, "agent_1")
assert mgr.unbind("thread_1", BindingType.AGENT) is True
assert mgr.get_binding("thread_1", BindingType.AGENT) is None
def test_unbind_nonexistent(self):
mgr = ThreadBindingManager()
assert mgr.unbind("nonexistent", BindingType.AGENT) is False
def test_list_bindings(self):
mgr = ThreadBindingManager(default_ttl_hours=None)
mgr.bind("thread_1", BindingType.AGENT, "agent_1")
mgr.bind("thread_1", BindingType.SUBAGENT, "sub_1")
mgr.bind("thread_2", BindingType.AGENT, "agent_2")
all_bindings = mgr.list_bindings()
assert len(all_bindings) == 3
thread1_bindings = mgr.list_bindings(thread_id="thread_1")
assert len(thread1_bindings) == 2
agent_bindings = mgr.list_bindings(binding_type=BindingType.AGENT)
assert len(agent_bindings) == 2
def test_bind_with_ttl(self):
mgr = ThreadBindingManager(default_ttl_hours=None)
binding = mgr.bind("thread_1", BindingType.AGENT, "agent_1", ttl_hours=24)
assert binding.expires_at is not None
def test_bind_with_default_ttl(self):
mgr = ThreadBindingManager(default_ttl_hours=24)
binding = mgr.bind("thread_1", BindingType.AGENT, "agent_1")
assert binding.expires_at is not None
def test_bind_with_metadata(self):
mgr = ThreadBindingManager(default_ttl_hours=None)
binding = mgr.bind("thread_1", BindingType.AGENT, "agent_1", metadata={"key": "val"})
assert binding.metadata["key"] == "val"
def test_listener_notification(self):
events: list[tuple[str, str]] = []
def listener(event: str, binding: ThreadBinding):
events.append((event, binding.thread_id))
mgr = ThreadBindingManager(default_ttl_hours=None)
mgr.add_listener(listener)
mgr.bind("thread_1", BindingType.AGENT, "agent_1")
mgr.unbind("thread_1", BindingType.AGENT)
assert len(events) == 2
assert events[0][0] == "bind"
assert events[1][0] == "unbind"
def test_remove_listener(self):
events: list[tuple[str, str]] = []
def listener(event: str, binding: ThreadBinding):
events.append((event, binding.thread_id))
mgr = ThreadBindingManager(default_ttl_hours=None)
mgr.add_listener(listener)
mgr.remove_listener(listener)
mgr.bind("thread_1", BindingType.AGENT, "agent_1")
assert len(events) == 0
def test_listener_exception_does_not_crash(self):
def faulty_listener(event: str, binding: ThreadBinding):
raise RuntimeError("test error")
mgr = ThreadBindingManager(default_ttl_hours=None)
mgr.add_listener(faulty_listener)
binding = mgr.bind("thread_1", BindingType.AGENT, "agent_1")
assert binding is not None