- Assert skills text no longer includes reaction-era labels - Require converter to drop reaction callback support - Lock !settings dashboard to read-only snapshot copy
173 lines
6.9 KiB
Python
173 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock
|
|
|
|
from adapter.matrix.bot import MatrixBot, build_runtime
|
|
from adapter.matrix.handlers.auth import handle_invite
|
|
from adapter.matrix.store import get_room_meta, get_user_meta, set_user_meta
|
|
from core.protocol import IncomingCallback, IncomingCommand, OutgoingMessage
|
|
from sdk.mock import MockPlatformClient
|
|
|
|
|
|
async def test_matrix_dispatcher_registers_custom_handlers():
|
|
runtime = build_runtime(platform=MockPlatformClient())
|
|
|
|
start = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="start")
|
|
await runtime.dispatcher.dispatch(start)
|
|
|
|
new = IncomingCommand(
|
|
user_id="u1", platform="matrix", chat_id="C1", command="new", args=["Research"]
|
|
)
|
|
result = await runtime.dispatcher.dispatch(new)
|
|
assert any(isinstance(r, OutgoingMessage) and "Research" in r.text for r in result)
|
|
|
|
chats = await runtime.chat_mgr.list_active("u1")
|
|
assert [c.chat_id for c in chats] == ["C1"]
|
|
|
|
new2 = IncomingCommand(
|
|
user_id="u1", platform="matrix", chat_id="C1", command="new", args=["Ops"]
|
|
)
|
|
await runtime.dispatcher.dispatch(new2)
|
|
chats = await runtime.chat_mgr.list_active("u1")
|
|
assert [c.chat_id for c in chats] == ["C1", "C2"]
|
|
|
|
skills = IncomingCommand(
|
|
user_id="u1", platform="matrix", chat_id="C1", command="settings_skills"
|
|
)
|
|
result = await runtime.dispatcher.dispatch(skills)
|
|
assert any(isinstance(r, OutgoingMessage) and "!skill on/off" in r.text for r in result)
|
|
|
|
toggle = IncomingCallback(
|
|
user_id="u1",
|
|
platform="matrix",
|
|
chat_id="C1",
|
|
action="toggle_skill",
|
|
payload={"skill_index": 2},
|
|
)
|
|
result = await runtime.dispatcher.dispatch(toggle)
|
|
assert any(isinstance(r, OutgoingMessage) and "fetch-url" in r.text for r in result)
|
|
|
|
|
|
async def test_new_chat_creates_real_matrix_room_when_client_available():
|
|
client = SimpleNamespace(
|
|
room_create=AsyncMock(return_value=SimpleNamespace(room_id="!r2:example")),
|
|
room_put_state=AsyncMock(),
|
|
room_invite=AsyncMock(),
|
|
)
|
|
runtime = build_runtime(platform=MockPlatformClient(), client=client)
|
|
await set_user_meta(runtime.store, "u1", {"space_id": "!space:example", "next_chat_index": 1})
|
|
|
|
start = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="start")
|
|
await runtime.dispatcher.dispatch(start)
|
|
|
|
new = IncomingCommand(
|
|
user_id="u1",
|
|
platform="matrix",
|
|
chat_id="C1",
|
|
command="new",
|
|
args=["Research"],
|
|
)
|
|
result = await runtime.dispatcher.dispatch(new)
|
|
|
|
client.room_create.assert_awaited_once_with(name="Research", visibility="private", is_direct=False)
|
|
client.room_put_state.assert_awaited_once()
|
|
put_call = client.room_put_state.call_args
|
|
assert put_call.kwargs.get("room_id") == "!space:example" or put_call.args[0] == "!space:example"
|
|
chats = await runtime.chat_mgr.list_active("u1")
|
|
assert [c.surface_ref for c in chats] == ["!r2:example"]
|
|
assert any(isinstance(r, OutgoingMessage) and "Research" in r.text for r in result)
|
|
|
|
|
|
async def test_invite_event_creates_space_and_chat_room():
|
|
runtime = build_runtime(platform=MockPlatformClient())
|
|
space_resp = SimpleNamespace(room_id="!space:example.org")
|
|
chat_resp = SimpleNamespace(room_id="!chat1:example.org")
|
|
client = SimpleNamespace(
|
|
join=AsyncMock(),
|
|
room_create=AsyncMock(side_effect=[space_resp, chat_resp]),
|
|
room_put_state=AsyncMock(),
|
|
room_invite=AsyncMock(),
|
|
room_send=AsyncMock(),
|
|
)
|
|
room = SimpleNamespace(room_id="!dm:example.org", display_name="Alice")
|
|
event = SimpleNamespace(sender="@alice:example.org", membership="invite")
|
|
|
|
await handle_invite(client, room, event, runtime.platform, runtime.store, runtime.auth_mgr)
|
|
|
|
assert client.room_create.await_count == 2
|
|
first_call = client.room_create.call_args_list[0]
|
|
assert first_call.kwargs.get("space") is True or (
|
|
len(first_call.args) > 0 and first_call.kwargs.get("space") is True
|
|
)
|
|
|
|
client.room_put_state.assert_awaited_once()
|
|
put_state_call = client.room_put_state.call_args
|
|
assert put_state_call.kwargs.get("event_type") == "m.space.child" or put_state_call.args[1] == "m.space.child"
|
|
|
|
user_meta = await get_user_meta(runtime.store, "@alice:example.org")
|
|
assert user_meta is not None
|
|
assert user_meta.get("space_id") == "!space:example.org"
|
|
|
|
room_meta = await get_room_meta(runtime.store, "!chat1:example.org")
|
|
assert room_meta is not None
|
|
assert room_meta["chat_id"] == "C1"
|
|
assert room_meta["space_id"] == "!space:example.org"
|
|
|
|
assert await runtime.auth_mgr.is_authenticated("@alice:example.org") is True
|
|
client.room_send.assert_awaited_once()
|
|
|
|
|
|
async def test_invite_event_is_idempotent_per_user():
|
|
runtime = build_runtime(platform=MockPlatformClient())
|
|
space_resp = SimpleNamespace(room_id="!space:example.org")
|
|
chat_resp = SimpleNamespace(room_id="!chat1:example.org")
|
|
client = SimpleNamespace(
|
|
join=AsyncMock(),
|
|
room_create=AsyncMock(side_effect=[space_resp, chat_resp]),
|
|
room_put_state=AsyncMock(),
|
|
room_invite=AsyncMock(),
|
|
room_send=AsyncMock(),
|
|
)
|
|
room = SimpleNamespace(room_id="!dm:example.org", display_name="Alice")
|
|
event = SimpleNamespace(sender="@alice:example.org", membership="invite")
|
|
|
|
await handle_invite(client, room, event, runtime.platform, runtime.store, runtime.auth_mgr)
|
|
await handle_invite(client, room, event, runtime.platform, runtime.store, runtime.auth_mgr)
|
|
|
|
assert client.room_create.await_count == 2
|
|
|
|
|
|
async def test_bot_ignores_its_own_messages():
|
|
runtime = build_runtime(platform=MockPlatformClient())
|
|
client = SimpleNamespace(user_id="@bot:example.org")
|
|
bot = MatrixBot(client, runtime)
|
|
bot._send_all = AsyncMock()
|
|
runtime.dispatcher.dispatch = AsyncMock()
|
|
room = SimpleNamespace(room_id="!dm:example.org")
|
|
event = SimpleNamespace(sender="@bot:example.org", body="hello")
|
|
|
|
await bot.on_room_message(room, event)
|
|
|
|
runtime.dispatcher.dispatch.assert_not_awaited()
|
|
bot._send_all.assert_not_awaited()
|
|
|
|
|
|
async def test_mat11_settings_returns_dashboard():
|
|
runtime = build_runtime(platform=MockPlatformClient())
|
|
|
|
start = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="start")
|
|
await runtime.dispatcher.dispatch(start)
|
|
|
|
settings_cmd = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="settings")
|
|
result = await runtime.dispatcher.dispatch(settings_cmd)
|
|
|
|
assert len(result) >= 1
|
|
text = result[0].text
|
|
assert "Скиллы" in text or "скиллы" in text.lower()
|
|
assert "Личность" in text
|
|
assert "Безопасность" in text
|
|
assert "Активные чаты" in text
|
|
assert "Изменить" not in text
|
|
assert "!connectors" not in text
|
|
assert "!whoami" not in text
|