test(01-04): add matrix space regression coverage
- add MAT-01..MAT-07 and MAT-09..MAT-12 regression tests for matrix adapter - extend store and dispatcher coverage for pending confirmations and settings dashboard - verify matrix adapter suite and full pytest suite stay green
This commit is contained in:
parent
6f1bdb4077
commit
97a3dc35ea
6 changed files with 382 additions and 0 deletions
125
tests/adapter/matrix/test_chat_space.py
Normal file
125
tests/adapter/matrix/test_chat_space.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from nio.responses import RoomCreateError
|
||||
|
||||
from adapter.matrix.handlers.chat import make_handle_archive, make_handle_new_chat
|
||||
from adapter.matrix.store import set_user_meta
|
||||
from core.auth import AuthManager
|
||||
from core.chat import ChatManager
|
||||
from core.protocol import IncomingCommand, OutgoingMessage
|
||||
from core.settings import SettingsManager
|
||||
from core.store import InMemoryStore
|
||||
from sdk.mock import MockPlatformClient
|
||||
|
||||
|
||||
async def _setup():
|
||||
platform = MockPlatformClient()
|
||||
store = InMemoryStore()
|
||||
chat_mgr = ChatManager(platform, store)
|
||||
auth_mgr = AuthManager(platform, store)
|
||||
settings_mgr = SettingsManager(platform, store)
|
||||
await auth_mgr.confirm("@alice:example.org")
|
||||
return platform, store, chat_mgr, auth_mgr, settings_mgr
|
||||
|
||||
|
||||
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})
|
||||
|
||||
client = SimpleNamespace(
|
||||
room_create=AsyncMock(return_value=SimpleNamespace(room_id="!newroom:ex")),
|
||||
room_put_state=AsyncMock(),
|
||||
room_invite=AsyncMock(),
|
||||
)
|
||||
handler = make_handle_new_chat(client, store)
|
||||
event = IncomingCommand(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
command="new",
|
||||
args=["Test"],
|
||||
)
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
client.room_put_state.assert_awaited_once()
|
||||
kwargs = client.room_put_state.call_args.kwargs
|
||||
assert kwargs.get("room_id") == "!space:ex"
|
||||
assert kwargs.get("event_type") == "m.space.child"
|
||||
assert kwargs.get("state_key") == "!newroom:ex"
|
||||
assert any(isinstance(item, OutgoingMessage) and "Test" in item.text for item in result)
|
||||
|
||||
|
||||
async def test_mat05_new_chat_without_space_id_returns_error():
|
||||
platform, store, chat_mgr, auth_mgr, settings_mgr = await _setup()
|
||||
await set_user_meta(store, "@alice:example.org", {"next_chat_index": 1})
|
||||
|
||||
client = SimpleNamespace(
|
||||
room_create=AsyncMock(),
|
||||
room_put_state=AsyncMock(),
|
||||
room_invite=AsyncMock(),
|
||||
)
|
||||
handler = make_handle_new_chat(client, store)
|
||||
event = IncomingCommand(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
command="new",
|
||||
)
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], OutgoingMessage)
|
||||
assert "Space" in result[0].text or "ошибка" in result[0].text.lower()
|
||||
client.room_create.assert_not_awaited()
|
||||
|
||||
|
||||
async def test_mat10_archive_calls_chat_mgr_archive():
|
||||
platform, store, chat_mgr, auth_mgr, settings_mgr = await _setup()
|
||||
|
||||
handler = make_handle_archive(None, store)
|
||||
event = IncomingCommand(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
command="archive",
|
||||
)
|
||||
await chat_mgr.get_or_create(
|
||||
user_id="@alice:example.org",
|
||||
chat_id="C1",
|
||||
platform="matrix",
|
||||
surface_ref="!room:ex",
|
||||
name="Test",
|
||||
)
|
||||
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
assert len(result) == 1
|
||||
assert "архивирован" in result[0].text
|
||||
|
||||
|
||||
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})
|
||||
|
||||
client = SimpleNamespace(
|
||||
room_create=AsyncMock(return_value=RoomCreateError(message="rate limited", status_code="429")),
|
||||
room_put_state=AsyncMock(),
|
||||
room_invite=AsyncMock(),
|
||||
)
|
||||
handler = make_handle_new_chat(client, store)
|
||||
event = IncomingCommand(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
command="new",
|
||||
args=["Fail"],
|
||||
)
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], OutgoingMessage)
|
||||
assert "Не удалось" in result[0].text or "не удалось" in result[0].text
|
||||
client.room_put_state.assert_not_awaited()
|
||||
96
tests/adapter/matrix/test_confirm.py
Normal file
96
tests/adapter/matrix/test_confirm.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from adapter.matrix.handlers.confirm import make_handle_cancel, make_handle_confirm
|
||||
from adapter.matrix.store import get_pending_confirm, set_pending_confirm
|
||||
from core.auth import AuthManager
|
||||
from core.chat import ChatManager
|
||||
from core.protocol import IncomingCallback, OutgoingMessage
|
||||
from core.settings import SettingsManager
|
||||
from core.store import InMemoryStore
|
||||
from sdk.mock import MockPlatformClient
|
||||
|
||||
|
||||
async def test_mat09_yes_reads_pending_confirm():
|
||||
store = InMemoryStore()
|
||||
platform = MockPlatformClient()
|
||||
chat_mgr = ChatManager(platform, store)
|
||||
auth_mgr = AuthManager(platform, store)
|
||||
settings_mgr = SettingsManager(platform, store)
|
||||
|
||||
await set_pending_confirm(
|
||||
store,
|
||||
"C1",
|
||||
{
|
||||
"action_id": "delete_file",
|
||||
"description": "Удалить файл config.yaml",
|
||||
"payload": {},
|
||||
},
|
||||
)
|
||||
|
||||
handler = make_handle_confirm(store)
|
||||
event = IncomingCallback(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
action="confirm",
|
||||
payload={"source": "command", "command": "yes"},
|
||||
)
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], OutgoingMessage)
|
||||
assert "Удалить файл config.yaml" in result[0].text
|
||||
assert await get_pending_confirm(store, "C1") is None
|
||||
|
||||
|
||||
async def test_no_clears_pending_confirm():
|
||||
store = InMemoryStore()
|
||||
platform = MockPlatformClient()
|
||||
chat_mgr = ChatManager(platform, store)
|
||||
auth_mgr = AuthManager(platform, store)
|
||||
settings_mgr = SettingsManager(platform, store)
|
||||
|
||||
await set_pending_confirm(
|
||||
store,
|
||||
"C1",
|
||||
{
|
||||
"action_id": "delete_file",
|
||||
"description": "Удалить файл",
|
||||
"payload": {},
|
||||
},
|
||||
)
|
||||
|
||||
handler = make_handle_cancel(store)
|
||||
event = IncomingCallback(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
action="cancel",
|
||||
payload={"source": "command", "command": "no"},
|
||||
)
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
assert len(result) == 1
|
||||
assert "отменено" in result[0].text.lower()
|
||||
assert await get_pending_confirm(store, "C1") is None
|
||||
|
||||
|
||||
async def test_yes_without_pending_returns_no_pending():
|
||||
store = InMemoryStore()
|
||||
platform = MockPlatformClient()
|
||||
chat_mgr = ChatManager(platform, store)
|
||||
auth_mgr = AuthManager(platform, store)
|
||||
settings_mgr = SettingsManager(platform, store)
|
||||
|
||||
handler = make_handle_confirm(store)
|
||||
event = IncomingCallback(
|
||||
user_id="@alice:example.org",
|
||||
platform="matrix",
|
||||
chat_id="C1",
|
||||
action="confirm",
|
||||
payload={},
|
||||
)
|
||||
result = await handler(event, auth_mgr, platform, chat_mgr, settings_mgr)
|
||||
|
||||
assert len(result) == 1
|
||||
assert "Нет ожидающих" in result[0].text
|
||||
|
|
@ -151,3 +151,20 @@ async def test_bot_ignores_its_own_messages():
|
|||
|
||||
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 or "!skills" in text
|
||||
assert "!connectors" not in text
|
||||
assert "!whoami" not in text
|
||||
|
|
|
|||
78
tests/adapter/matrix/test_invite_space.py
Normal file
78
tests/adapter/matrix/test_invite_space.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from adapter.matrix.bot import build_runtime
|
||||
from adapter.matrix.handlers.auth import handle_invite
|
||||
from adapter.matrix.store import get_room_meta, get_user_meta
|
||||
from sdk.mock import MockPlatformClient
|
||||
|
||||
|
||||
def _make_client():
|
||||
space_resp = SimpleNamespace(room_id="!space:example.org")
|
||||
chat_resp = SimpleNamespace(room_id="!chat1:example.org")
|
||||
return SimpleNamespace(
|
||||
join=AsyncMock(),
|
||||
room_create=AsyncMock(side_effect=[space_resp, chat_resp]),
|
||||
room_put_state=AsyncMock(),
|
||||
room_invite=AsyncMock(),
|
||||
room_send=AsyncMock(),
|
||||
)
|
||||
|
||||
|
||||
async def test_mat01_invite_creates_space_and_chat1():
|
||||
runtime = build_runtime(platform=MockPlatformClient())
|
||||
client = _make_client()
|
||||
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)
|
||||
|
||||
first_call = client.room_create.call_args_list[0]
|
||||
assert first_call.kwargs.get("space") is True
|
||||
assert client.room_create.await_count == 2
|
||||
|
||||
client.room_put_state.assert_awaited_once()
|
||||
kwargs = client.room_put_state.call_args.kwargs
|
||||
assert kwargs.get("event_type") == "m.space.child"
|
||||
assert kwargs.get("state_key") == "!chat1:example.org"
|
||||
assert kwargs.get("room_id") == "!space:example.org"
|
||||
|
||||
user_meta = await get_user_meta(runtime.store, "@alice:example.org")
|
||||
assert user_meta is not None
|
||||
assert user_meta["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"
|
||||
|
||||
|
||||
async def test_mat02_invite_idempotent():
|
||||
runtime = build_runtime(platform=MockPlatformClient())
|
||||
client = _make_client()
|
||||
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_mat03_no_hardcoded_c1():
|
||||
runtime = build_runtime(platform=MockPlatformClient())
|
||||
client = _make_client()
|
||||
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)
|
||||
|
||||
room_meta = await get_room_meta(runtime.store, "!chat1:example.org")
|
||||
assert room_meta is not None
|
||||
assert room_meta["chat_id"] == "C1"
|
||||
|
||||
user_meta = await get_user_meta(runtime.store, "@alice:example.org")
|
||||
assert user_meta is not None
|
||||
assert user_meta["next_chat_index"] == 2
|
||||
52
tests/adapter/matrix/test_send_outgoing.py
Normal file
52
tests/adapter/matrix/test_send_outgoing.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from adapter.matrix.bot import send_outgoing
|
||||
from adapter.matrix.store import get_pending_confirm
|
||||
from core.protocol import OutgoingUI, UIButton
|
||||
from core.store import InMemoryStore
|
||||
|
||||
|
||||
async def test_mat06_outgoing_ui_renders_text_with_yes_no():
|
||||
client = SimpleNamespace(room_send=AsyncMock())
|
||||
store = InMemoryStore()
|
||||
event = OutgoingUI(
|
||||
chat_id="C1",
|
||||
text="Удалить файл?",
|
||||
buttons=[UIButton(label="Подтвердить", action="confirm")],
|
||||
)
|
||||
|
||||
await send_outgoing(client, "!room:ex", event, store=store)
|
||||
|
||||
client.room_send.assert_awaited_once()
|
||||
body = client.room_send.call_args.args[2]["body"]
|
||||
assert "Удалить файл?" in body
|
||||
assert "!yes" in body
|
||||
assert "!no" in body
|
||||
assert "Подтвердить" in body
|
||||
|
||||
|
||||
async def test_mat07_outgoing_ui_no_reaction_sent():
|
||||
client = SimpleNamespace(room_send=AsyncMock())
|
||||
store = InMemoryStore()
|
||||
event = OutgoingUI(
|
||||
chat_id="C1",
|
||||
text="Confirm action?",
|
||||
buttons=[UIButton(label="OK", action="confirm", payload={"id": 1})],
|
||||
)
|
||||
|
||||
await send_outgoing(client, "!room:ex", event, store=store)
|
||||
|
||||
assert client.room_send.await_count == 1
|
||||
assert client.room_send.call_args.args[1] == "m.room.message"
|
||||
for call in client.room_send.call_args_list:
|
||||
assert call.args[1] != "m.reaction"
|
||||
|
||||
pending = await get_pending_confirm(store, "!room:ex")
|
||||
assert pending == {
|
||||
"action_id": "confirm",
|
||||
"description": "Confirm action?",
|
||||
"payload": {"id": 1},
|
||||
}
|
||||
|
|
@ -3,11 +3,14 @@ from __future__ import annotations
|
|||
import pytest
|
||||
|
||||
from adapter.matrix.store import (
|
||||
clear_pending_confirm,
|
||||
get_pending_confirm,
|
||||
get_room_meta,
|
||||
get_room_state,
|
||||
get_skills_message_id,
|
||||
get_user_meta,
|
||||
next_chat_id,
|
||||
set_pending_confirm,
|
||||
set_room_meta,
|
||||
set_room_state,
|
||||
set_skills_message_id,
|
||||
|
|
@ -70,3 +73,14 @@ async def test_next_chat_id_increments(store: InMemoryStore):
|
|||
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"
|
||||
|
||||
|
||||
async def test_pending_confirm_roundtrip(store: InMemoryStore):
|
||||
assert await get_pending_confirm(store, "!room:m.org") is None
|
||||
|
||||
meta = {"action_id": "test", "description": "Do thing"}
|
||||
await set_pending_confirm(store, "!room:m.org", meta)
|
||||
assert await get_pending_confirm(store, "!room:m.org") == meta
|
||||
|
||||
await clear_pending_confirm(store, "!room:m.org")
|
||||
assert await get_pending_confirm(store, "!room:m.org") is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue