feat: finalize matrix platform audit and docs
This commit is contained in:
parent
6422c7db58
commit
4524a6abc8
30 changed files with 3093 additions and 176 deletions
|
|
@ -6,7 +6,11 @@ from unittest.mock import AsyncMock
|
|||
from nio.api import RoomVisibility
|
||||
from nio.responses import RoomCreateError
|
||||
|
||||
from adapter.matrix.handlers.chat import make_handle_archive, make_handle_new_chat, make_handle_rename
|
||||
from adapter.matrix.handlers.chat import (
|
||||
make_handle_archive,
|
||||
make_handle_new_chat,
|
||||
make_handle_rename,
|
||||
)
|
||||
from adapter.matrix.store import get_room_meta, set_user_meta
|
||||
from core.auth import AuthManager
|
||||
from core.chat import ChatManager
|
||||
|
|
@ -28,7 +32,9 @@ async def _setup():
|
|||
|
||||
async def test_mat04_new_chat_calls_room_put_state_with_space_id():
|
||||
platform, store, chat_mgr, auth_mgr, settings_mgr = await _setup()
|
||||
await set_user_meta(store, "@alice:example.org", {"space_id": "!space:ex", "next_chat_index": 2})
|
||||
await set_user_meta(
|
||||
store, "@alice:example.org", {"space_id": "!space:ex", "next_chat_index": 2}
|
||||
)
|
||||
|
||||
client = SimpleNamespace(
|
||||
room_create=AsyncMock(return_value=SimpleNamespace(room_id="!newroom:ex")),
|
||||
|
|
@ -59,7 +65,7 @@ async def test_mat04_new_chat_calls_room_put_state_with_space_id():
|
|||
assert kwargs.get("state_key") == "!newroom:ex"
|
||||
room_meta = await get_room_meta(store, "!newroom:ex")
|
||||
assert room_meta is not None
|
||||
assert room_meta["platform_chat_id"] == "matrix:!newroom:ex"
|
||||
assert room_meta["platform_chat_id"] == "1"
|
||||
assert any(isinstance(item, OutgoingMessage) and "Test" in item.text for item in result)
|
||||
|
||||
|
||||
|
|
@ -169,10 +175,14 @@ async def test_mat11b_rename_from_unregistered_room_returns_error_message():
|
|||
|
||||
async def test_mat12_room_create_error_returns_user_message():
|
||||
platform, store, chat_mgr, auth_mgr, settings_mgr = await _setup()
|
||||
await set_user_meta(store, "@alice:example.org", {"space_id": "!space:ex", "next_chat_index": 2})
|
||||
await set_user_meta(
|
||||
store, "@alice:example.org", {"space_id": "!space:ex", "next_chat_index": 2}
|
||||
)
|
||||
|
||||
client = SimpleNamespace(
|
||||
room_create=AsyncMock(return_value=RoomCreateError(message="rate limited", status_code="429")),
|
||||
room_create=AsyncMock(
|
||||
return_value=RoomCreateError(message="rate limited", status_code="429")
|
||||
),
|
||||
room_put_state=AsyncMock(),
|
||||
room_invite=AsyncMock(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -15,7 +14,6 @@ from adapter.matrix.handlers.context_commands import (
|
|||
)
|
||||
from adapter.matrix.store import (
|
||||
get_load_pending,
|
||||
|
||||
set_load_pending,
|
||||
set_room_meta,
|
||||
)
|
||||
|
|
@ -48,7 +46,7 @@ async def test_save_command_auto_name_records_session():
|
|||
await set_room_meta(
|
||||
store,
|
||||
"!room:example.org",
|
||||
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "matrix:room-1"},
|
||||
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
|
||||
)
|
||||
handler = make_handle_save(
|
||||
agent_api=platform._agent_api,
|
||||
|
|
@ -71,7 +69,7 @@ async def test_save_command_auto_name_records_session():
|
|||
sessions = await platform._prototype_state.list_saved_sessions("u1")
|
||||
assert len(sessions) == 1
|
||||
assert sessions[0]["name"].startswith("context-")
|
||||
assert sessions[0]["source_context_id"] == "matrix:room-1"
|
||||
assert sessions[0]["source_context_id"] == "41"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -81,7 +79,7 @@ async def test_save_command_with_name_uses_given_name():
|
|||
await set_room_meta(
|
||||
store,
|
||||
"!room:example.org",
|
||||
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "matrix:room-1"},
|
||||
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
|
||||
)
|
||||
handler = make_handle_save(
|
||||
agent_api=platform._agent_api,
|
||||
|
|
@ -119,7 +117,13 @@ async def test_load_command_shows_numbered_list_and_sets_pending():
|
|||
handler = make_handle_load(store=runtime.store, prototype_state=platform._prototype_state)
|
||||
event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="load", args=[])
|
||||
|
||||
result = await handler(event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr)
|
||||
result = await handler(
|
||||
event,
|
||||
runtime.auth_mgr,
|
||||
platform,
|
||||
runtime.chat_mgr,
|
||||
runtime.settings_mgr,
|
||||
)
|
||||
|
||||
assert "1. session-a" in result[0].text
|
||||
assert "2. session-b" in result[0].text
|
||||
|
|
@ -150,16 +154,28 @@ async def test_reset_command_assigns_new_platform_chat_id():
|
|||
runtime = build_runtime(platform=platform)
|
||||
store = runtime.store
|
||||
|
||||
await set_room_meta(store, "!room:example.org", {"platform_chat_id": "matrix:!room:example.org"})
|
||||
await set_room_meta(store, "!room:example.org", {"platform_chat_id": "7"})
|
||||
|
||||
handler = make_handle_reset(store=store, prototype_state=prototype_state)
|
||||
event = IncomingCommand(user_id="u1", platform="matrix", chat_id="!room:example.org", command="reset", args=[])
|
||||
event = IncomingCommand(
|
||||
user_id="u1",
|
||||
platform="matrix",
|
||||
chat_id="!room:example.org",
|
||||
command="reset",
|
||||
args=[],
|
||||
)
|
||||
|
||||
result = await handler(event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr)
|
||||
result = await handler(
|
||||
event,
|
||||
runtime.auth_mgr,
|
||||
platform,
|
||||
runtime.chat_mgr,
|
||||
runtime.settings_mgr,
|
||||
)
|
||||
|
||||
new_id = await get_platform_chat_id(store, "!room:example.org")
|
||||
assert new_id != "matrix:!room:example.org"
|
||||
assert new_id.startswith("matrix:!room:example.org#")
|
||||
assert new_id != "7"
|
||||
assert new_id == "1"
|
||||
assert "сброшен" in result[0].text.lower()
|
||||
|
||||
|
||||
|
|
@ -177,17 +193,29 @@ async def test_context_command_shows_current_snapshot():
|
|||
await set_room_meta(
|
||||
runtime.store,
|
||||
"!room:example.org",
|
||||
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "matrix:room-1"},
|
||||
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
|
||||
)
|
||||
await platform._prototype_state.set_current_session("matrix:room-1", "session-a")
|
||||
await platform._prototype_state.set_last_tokens_used("matrix:room-1", 99)
|
||||
await platform._prototype_state.set_current_session("41", "session-a")
|
||||
await platform._prototype_state.set_last_tokens_used("41", 99)
|
||||
await platform._prototype_state.add_saved_session("u1", "session-a")
|
||||
handler = make_handle_context(store=runtime.store, prototype_state=platform._prototype_state)
|
||||
event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="context", args=[])
|
||||
event = IncomingCommand(
|
||||
user_id="u1",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
command="context",
|
||||
args=[],
|
||||
)
|
||||
|
||||
result = await handler(event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr)
|
||||
result = await handler(
|
||||
event,
|
||||
runtime.auth_mgr,
|
||||
platform,
|
||||
runtime.chat_mgr,
|
||||
runtime.settings_mgr,
|
||||
)
|
||||
|
||||
assert "Контекст чата: matrix:room-1" in result[0].text
|
||||
assert "Контекст чата: 41" in result[0].text
|
||||
assert "Сессия: session-a" in result[0].text
|
||||
assert "Токены (последний ответ): 99" in result[0].text
|
||||
assert "session-a" in result[0].text
|
||||
|
|
@ -203,7 +231,7 @@ async def test_bot_intercepts_numeric_load_selection():
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:room-1",
|
||||
"platform_chat_id": "41",
|
||||
},
|
||||
)
|
||||
client = SimpleNamespace(
|
||||
|
|
@ -223,7 +251,7 @@ async def test_bot_intercepts_numeric_load_selection():
|
|||
await bot.on_room_message(room, event)
|
||||
|
||||
platform.send_message.assert_awaited_once()
|
||||
assert await platform._prototype_state.get_current_session("matrix:room-1") == "session-a"
|
||||
assert await platform._prototype_state.get_current_session("41") == "session-a"
|
||||
assert await platform._prototype_state.get_current_session("C1") == "session-a"
|
||||
client.room_send.assert_awaited_once_with(
|
||||
"!room:example.org",
|
||||
|
|
|
|||
|
|
@ -272,10 +272,7 @@ async def test_bot_assigns_platform_chat_id_for_existing_managed_room():
|
|||
|
||||
await bot.on_room_message(room, event)
|
||||
|
||||
assert (
|
||||
await get_platform_chat_id(runtime.store, "!chat1:example.org")
|
||||
== "matrix:!chat1:example.org"
|
||||
)
|
||||
assert await get_platform_chat_id(runtime.store, "!chat1:example.org") == "1"
|
||||
runtime.dispatcher.dispatch.assert_awaited_once()
|
||||
|
||||
|
||||
|
|
@ -287,7 +284,7 @@ async def test_bot_routes_plain_messages_via_platform_chat_id():
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:ctx-1",
|
||||
"platform_chat_id": "41",
|
||||
},
|
||||
)
|
||||
client = SimpleNamespace(user_id="@bot:example.org")
|
||||
|
|
@ -300,7 +297,7 @@ async def test_bot_routes_plain_messages_via_platform_chat_id():
|
|||
await bot.on_room_message(room, event)
|
||||
|
||||
dispatched = runtime.dispatcher.dispatch.await_args.args[0]
|
||||
assert dispatched.chat_id == "matrix:ctx-1"
|
||||
assert dispatched.chat_id == "41"
|
||||
assert dispatched.text == "hello"
|
||||
|
||||
|
||||
|
|
@ -313,7 +310,7 @@ async def test_bot_downloads_matrix_file_to_workspace_before_staging(tmp_path, m
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:ctx-1",
|
||||
"platform_chat_id": "41",
|
||||
},
|
||||
)
|
||||
client = SimpleNamespace(
|
||||
|
|
@ -539,7 +536,7 @@ async def test_next_normal_message_commits_staged_attachments():
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:ctx-1",
|
||||
"platform_chat_id": "41",
|
||||
},
|
||||
)
|
||||
await add_staged_attachment(
|
||||
|
|
@ -584,7 +581,7 @@ async def test_failed_commit_preserves_staged_attachments():
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:ctx-1",
|
||||
"platform_chat_id": "41",
|
||||
},
|
||||
)
|
||||
await add_staged_attachment(
|
||||
|
|
@ -622,7 +619,7 @@ async def test_bot_keeps_commands_on_local_chat_id():
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:ctx-1",
|
||||
"platform_chat_id": "41",
|
||||
},
|
||||
)
|
||||
client = SimpleNamespace(user_id="@bot:example.org")
|
||||
|
|
@ -647,7 +644,7 @@ async def test_bot_leaves_existing_platform_chat_id_unchanged():
|
|||
{
|
||||
"chat_id": "C1",
|
||||
"matrix_user_id": "@alice:example.org",
|
||||
"platform_chat_id": "matrix:existing",
|
||||
"platform_chat_id": "99",
|
||||
},
|
||||
)
|
||||
client = SimpleNamespace(user_id="@bot:example.org")
|
||||
|
|
@ -659,7 +656,7 @@ async def test_bot_leaves_existing_platform_chat_id_unchanged():
|
|||
|
||||
await bot.on_room_message(room, event)
|
||||
|
||||
assert await get_platform_chat_id(runtime.store, "!chat1:example.org") == "matrix:existing"
|
||||
assert await get_platform_chat_id(runtime.store, "!chat1:example.org") == "99"
|
||||
runtime.dispatcher.dispatch.assert_awaited_once()
|
||||
|
||||
|
||||
|
|
@ -686,10 +683,7 @@ async def test_bot_assigns_platform_chat_id_before_load_selection():
|
|||
|
||||
await bot.on_room_message(room, event)
|
||||
|
||||
assert (
|
||||
await get_platform_chat_id(runtime.store, "!chat1:example.org")
|
||||
== "matrix:!chat1:example.org"
|
||||
)
|
||||
assert await get_platform_chat_id(runtime.store, "!chat1:example.org") == "1"
|
||||
client.room_send.assert_awaited_once_with(
|
||||
"!chat1:example.org",
|
||||
"m.room.message",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async def test_mat01_invite_creates_space_and_chat1():
|
|||
assert room_meta is not None
|
||||
assert room_meta["chat_id"] == "C4"
|
||||
assert room_meta["space_id"] == "!space:example.org"
|
||||
assert room_meta["platform_chat_id"] == "matrix:!chat1:example.org"
|
||||
assert room_meta["platform_chat_id"] == "1"
|
||||
assert user_meta["next_chat_index"] == 5
|
||||
|
||||
chats = await runtime.chat_mgr.list_active("@alice:example.org")
|
||||
|
|
@ -120,7 +120,7 @@ async def test_mat03_no_hardcoded_c1():
|
|||
room_meta = await get_room_meta(runtime.store, "!chat1:example.org")
|
||||
assert room_meta is not None
|
||||
assert room_meta["chat_id"] == "C7"
|
||||
assert room_meta["platform_chat_id"] == "matrix:!chat1:example.org"
|
||||
assert room_meta["platform_chat_id"] == "1"
|
||||
|
||||
user_meta = await get_user_meta(runtime.store, "@alice:example.org")
|
||||
assert user_meta is not None
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from adapter.matrix.store import (
|
|||
get_staged_attachments,
|
||||
get_user_meta,
|
||||
next_chat_id,
|
||||
next_platform_chat_id,
|
||||
remove_staged_attachment_at,
|
||||
set_pending_confirm,
|
||||
set_platform_chat_id,
|
||||
|
|
@ -107,6 +108,12 @@ async def test_next_chat_id_increments(store: InMemoryStore):
|
|||
assert await next_chat_id(store, uid) == "C3"
|
||||
|
||||
|
||||
async def test_next_platform_chat_id_increments(store: InMemoryStore):
|
||||
assert await next_platform_chat_id(store) == "1"
|
||||
assert await next_platform_chat_id(store) == "2"
|
||||
assert await next_platform_chat_id(store) == "3"
|
||||
|
||||
|
||||
async def test_skills_message_roundtrip(store: InMemoryStore):
|
||||
await set_skills_message_id(store, "!room", "$event")
|
||||
assert await get_skills_message_id(store, "!room") == "$event"
|
||||
|
|
@ -151,7 +158,8 @@ async def test_staged_attachments_roundtrip(store: InMemoryStore):
|
|||
],
|
||||
)
|
||||
async def test_staged_attachments_invalid_container_state_returns_empty_list(
|
||||
store: InMemoryStore, stored_value,
|
||||
store: InMemoryStore,
|
||||
stored_value,
|
||||
):
|
||||
room_id = "!room:m.org"
|
||||
user_id = "@alice:m.org"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from lambda_agent_api.server import MsgEventEnd, MsgEventTextChunk
|
||||
|
||||
from core.protocol import SettingsAction
|
||||
import sdk.agent_api_wrapper as agent_api_wrapper_module
|
||||
from core.protocol import SettingsAction
|
||||
from sdk.agent_api_wrapper import AgentApiWrapper
|
||||
from sdk.interface import Attachment, MessageChunk, MessageResponse, UserSettings
|
||||
from sdk.interface import Attachment, MessageChunk, MessageResponse, PlatformError, UserSettings
|
||||
from sdk.prototype_state import PrototypeStateStore
|
||||
from sdk.real import RealPlatformClient
|
||||
|
||||
|
|
@ -110,6 +111,23 @@ class AttachmentTrackingChatAgentApi:
|
|||
self.last_tokens_used = 5
|
||||
|
||||
|
||||
class FlakyChatAgentApi:
|
||||
def __init__(self, chat_id: str) -> None:
|
||||
self.chat_id = chat_id
|
||||
self.connect_calls = 0
|
||||
self.close_calls = 0
|
||||
|
||||
async def connect(self) -> None:
|
||||
self.connect_calls += 1
|
||||
|
||||
async def close(self) -> None:
|
||||
self.close_calls += 1
|
||||
|
||||
async def send_message(self, text: str, attachments: list[str] | None = None):
|
||||
raise ConnectionError("Connection closed")
|
||||
yield
|
||||
|
||||
|
||||
class SendFileEvent:
|
||||
def __init__(self, *, workspace_path: str, mime_type: str, filename: str, size: int) -> None:
|
||||
self.type = "AGENT_EVENT_SEND_FILE"
|
||||
|
|
@ -180,6 +198,26 @@ class FakeWebSocket:
|
|||
return self._messages.pop(0)
|
||||
|
||||
|
||||
class QueueFeedingWebSocket:
|
||||
def __init__(self, owner, queued_events: list[object]) -> None:
|
||||
self.owner = owner
|
||||
self.queued_events = list(queued_events)
|
||||
self.sent_payloads: list[str] = []
|
||||
|
||||
async def send_str(self, payload: str) -> None:
|
||||
self.sent_payloads.append(payload)
|
||||
for event in self.queued_events:
|
||||
await self.owner._current_queue.put(event)
|
||||
|
||||
|
||||
class SilentWebSocket:
|
||||
def __init__(self) -> None:
|
||||
self.sent_payloads: list[str] = []
|
||||
|
||||
async def send_str(self, payload: str) -> None:
|
||||
self.sent_payloads.append(payload)
|
||||
|
||||
|
||||
class MessageResponseWithAttachments(MessageResponse):
|
||||
attachments: list[Attachment] = []
|
||||
|
||||
|
|
@ -271,6 +309,68 @@ def test_agent_api_wrapper_falls_back_to_legacy_url_constructor(monkeypatch):
|
|||
assert wrapper.last_tokens_used == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_api_wrapper_recovers_late_text_after_first_end(monkeypatch):
|
||||
def fake_init(self, agent_id, base_url=None, chat_id=0, **kwargs):
|
||||
self.id = agent_id
|
||||
self.url = base_url
|
||||
self.callback = kwargs.get("callback")
|
||||
self.on_disconnect = kwargs.get("on_disconnect")
|
||||
|
||||
monkeypatch.setattr(agent_api_wrapper_module.AgentApi, "__init__", fake_init)
|
||||
|
||||
wrapper = AgentApiWrapper(
|
||||
agent_id="agent-1",
|
||||
base_url="https://agent.example.com/v1/agent_ws",
|
||||
chat_id="chat-1",
|
||||
)
|
||||
wrapper._connected = True
|
||||
wrapper._request_lock = asyncio.Lock()
|
||||
wrapper._current_queue = None
|
||||
wrapper._ws = QueueFeedingWebSocket(
|
||||
wrapper,
|
||||
[
|
||||
MsgEventTextChunk(text="Иллюстра"),
|
||||
MsgEventEnd(tokens_used=5),
|
||||
MsgEventTextChunk(text="ция"),
|
||||
MsgEventEnd(tokens_used=5),
|
||||
],
|
||||
)
|
||||
|
||||
chunks = []
|
||||
async for chunk in wrapper.send_message("hello"):
|
||||
chunks.append(chunk)
|
||||
|
||||
assert [chunk.text for chunk in chunks] == ["Иллюстра", "ция"]
|
||||
assert wrapper.last_tokens_used == 5
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_api_wrapper_times_out_on_idle_stream(monkeypatch):
|
||||
def fake_init(self, agent_id, base_url=None, chat_id=0, **kwargs):
|
||||
self.id = agent_id
|
||||
self.url = base_url
|
||||
self.callback = kwargs.get("callback")
|
||||
self.on_disconnect = kwargs.get("on_disconnect")
|
||||
|
||||
monkeypatch.setattr(agent_api_wrapper_module.AgentApi, "__init__", fake_init)
|
||||
monkeypatch.setattr(agent_api_wrapper_module, "_STREAM_IDLE_TIMEOUT_MS", 10)
|
||||
|
||||
wrapper = AgentApiWrapper(
|
||||
agent_id="agent-1",
|
||||
base_url="https://agent.example.com/v1/agent_ws",
|
||||
chat_id="chat-1",
|
||||
)
|
||||
wrapper._connected = True
|
||||
wrapper._request_lock = asyncio.Lock()
|
||||
wrapper._current_queue = None
|
||||
wrapper._ws = SilentWebSocket()
|
||||
|
||||
with pytest.raises(agent_api_wrapper_module.AgentException, match="Timed out waiting"):
|
||||
async for _ in wrapper.send_message("hello"):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_real_platform_client_get_or_create_user_uses_local_state():
|
||||
client = RealPlatformClient(
|
||||
|
|
@ -418,6 +518,58 @@ async def test_real_platform_client_reuses_cached_chat_client():
|
|||
assert agent_api.created_chat_ids == ["chat-1"]
|
||||
assert agent_api.instances["chat-1"].calls == ["hello", "again"]
|
||||
assert agent_api.instances["chat-1"].connect_calls == 1
|
||||
assert agent_api.instances["chat-1"].close_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_real_platform_client_wraps_connection_closed_as_platform_error():
|
||||
agent_api = FakeAgentApiFactory()
|
||||
agent_api.instances["chat-1"] = FlakyChatAgentApi("chat-1")
|
||||
agent_api.for_chat = lambda chat_id: agent_api.instances.setdefault(
|
||||
chat_id, FlakyChatAgentApi(chat_id)
|
||||
)
|
||||
client = RealPlatformClient(
|
||||
agent_api=agent_api,
|
||||
prototype_state=PrototypeStateStore(),
|
||||
platform="matrix",
|
||||
)
|
||||
|
||||
with pytest.raises(PlatformError, match="Connection closed") as exc_info:
|
||||
await client.send_message("@alice:example.org", "chat-1", "hello")
|
||||
|
||||
assert exc_info.value.code == "PLATFORM_CONNECTION_ERROR"
|
||||
assert "chat-1" not in client._chat_apis
|
||||
assert agent_api.instances["chat-1"].close_calls == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_real_platform_client_reconnects_after_closed_chat_api():
|
||||
agent_api = FakeAgentApiFactory()
|
||||
flaky = FlakyChatAgentApi("chat-1")
|
||||
healthy = AttachmentTrackingChatAgentApi("chat-1")
|
||||
provided = iter([flaky, healthy])
|
||||
|
||||
def for_chat(chat_id: str):
|
||||
chat_api = next(provided)
|
||||
agent_api.created_chat_ids.append(chat_id)
|
||||
agent_api.instances[chat_id] = chat_api
|
||||
return chat_api
|
||||
|
||||
agent_api.for_chat = for_chat
|
||||
client = RealPlatformClient(
|
||||
agent_api=agent_api,
|
||||
prototype_state=PrototypeStateStore(),
|
||||
platform="matrix",
|
||||
)
|
||||
|
||||
with pytest.raises(PlatformError, match="Connection closed"):
|
||||
await client.send_message("@alice:example.org", "chat-1", "hello")
|
||||
|
||||
result = await client.send_message("@alice:example.org", "chat-1", "again")
|
||||
|
||||
assert result.response == "again"
|
||||
assert agent_api.created_chat_ids == ["chat-1", "chat-1"]
|
||||
assert healthy.calls == [("again", None)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -462,7 +614,9 @@ async def test_real_platform_client_creates_distinct_clients_per_chat():
|
|||
async def test_real_platform_client_serializes_same_chat_streams_across_send_paths():
|
||||
agent_api = FakeAgentApiFactory()
|
||||
agent_api.instances["chat-1"] = BlockingChatAgentApi("chat-1")
|
||||
agent_api.for_chat = lambda chat_id: agent_api.instances.setdefault(chat_id, BlockingChatAgentApi(chat_id))
|
||||
agent_api.for_chat = lambda chat_id: agent_api.instances.setdefault(
|
||||
chat_id, BlockingChatAgentApi(chat_id)
|
||||
)
|
||||
client = RealPlatformClient(
|
||||
agent_api=agent_api,
|
||||
prototype_state=PrototypeStateStore(),
|
||||
|
|
@ -587,10 +741,12 @@ async def test_agent_api_wrapper_transparently_surfaces_modern_events(monkeypatc
|
|||
monkeypatch.setattr(
|
||||
agent_api_wrapper_module.AgentApi,
|
||||
"__init__",
|
||||
lambda self, agent_id, base_url=None, chat_id=0, **kwargs: setattr(self, "id", agent_id)
|
||||
or setattr(self, "callback", kwargs.get("callback"))
|
||||
or setattr(self, "on_disconnect", kwargs.get("on_disconnect"))
|
||||
or setattr(self, "_current_queue", None),
|
||||
lambda self, agent_id, base_url=None, chat_id=0, **kwargs: (
|
||||
setattr(self, "id", agent_id)
|
||||
or setattr(self, "callback", kwargs.get("callback"))
|
||||
or setattr(self, "on_disconnect", kwargs.get("on_disconnect"))
|
||||
or setattr(self, "_current_queue", None)
|
||||
),
|
||||
)
|
||||
|
||||
wrapper = AgentApiWrapper(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue