from __future__ import annotations from types import SimpleNamespace from unittest.mock import AsyncMock from adapter.matrix.bot import send_outgoing from adapter.matrix.converter import from_room_event from adapter.matrix.handlers.confirm import make_handle_cancel, make_handle_confirm from adapter.matrix.store import get_pending_confirm, set_room_meta from core.auth import AuthManager from core.chat import ChatManager from core.protocol import Attachment, OutgoingMessage, OutgoingUI, UIButton from core.settings import SettingsManager from core.store import InMemoryStore from sdk.mock import MockPlatformClient async def test_mat06_outgoing_ui_renders_text_with_yes_no(): client = SimpleNamespace(room_send=AsyncMock()) store = InMemoryStore() await set_room_meta(store, "!confirm:example.org", {"matrix_user_id": "@alice:example.org"}) event = OutgoingUI( chat_id="C7", text="Удалить файл?", buttons=[UIButton(label="Подтвердить", action="confirm")], ) await send_outgoing(client, "!confirm:example.org", 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() await set_room_meta(store, "!confirm:example.org", {"matrix_user_id": "@alice:example.org"}) event = OutgoingUI( chat_id="C7", text="Confirm action?", buttons=[UIButton(label="OK", action="confirm", payload={"id": 1})], ) await send_outgoing(client, "!confirm:example.org", 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, "@alice:example.org", "!confirm:example.org") assert pending == { "action_id": "confirm", "description": "Confirm action?", "payload": {"id": 1}, } async def test_outgoing_ui_yes_round_trip_uses_user_and_room_scope(): client = SimpleNamespace(room_send=AsyncMock()) store = InMemoryStore() platform = MockPlatformClient() chat_mgr = ChatManager(platform, store) auth_mgr = AuthManager(platform, store) settings_mgr = SettingsManager(platform, store) await set_room_meta(store, "!confirm:example.org", {"matrix_user_id": "@alice:example.org"}) await set_room_meta(store, "!other:example.org", {"matrix_user_id": "@bob:example.org"}) await send_outgoing( client, "!confirm:example.org", OutgoingUI( chat_id="C7", text="Archive room", buttons=[UIButton(label="Confirm", action="archive", payload={"id": 7})], ), store=store, ) await send_outgoing( client, "!other:example.org", OutgoingUI( chat_id="C8", text="Keep other room", buttons=[UIButton(label="Confirm", action="archive", payload={"id": 8})], ), store=store, ) callback = from_room_event( SimpleNamespace( sender="@alice:example.org", body="!yes", event_id="$yes", msgtype="m.text", replyto_event_id=None, ), room_id="!confirm:example.org", chat_id="C7", ) result = await make_handle_confirm(store)(callback, auth_mgr, platform, chat_mgr, settings_mgr) assert "Archive room" in result[0].text assert await get_pending_confirm(store, "@alice:example.org", "!confirm:example.org") is None assert await get_pending_confirm(store, "@bob:example.org", "!other:example.org") is not None async def test_outgoing_ui_no_round_trip_uses_user_and_room_scope(): client = SimpleNamespace(room_send=AsyncMock()) store = InMemoryStore() platform = MockPlatformClient() chat_mgr = ChatManager(platform, store) auth_mgr = AuthManager(platform, store) settings_mgr = SettingsManager(platform, store) await set_room_meta(store, "!confirm:example.org", {"matrix_user_id": "@alice:example.org"}) await set_room_meta(store, "!other:example.org", {"matrix_user_id": "@bob:example.org"}) await send_outgoing( client, "!confirm:example.org", OutgoingUI( chat_id="C7", text="Delete room", buttons=[UIButton(label="Confirm", action="delete", payload={"id": 7})], ), store=store, ) await send_outgoing( client, "!other:example.org", OutgoingUI( chat_id="C8", text="Keep other room", buttons=[UIButton(label="Confirm", action="archive", payload={"id": 8})], ), store=store, ) callback = from_room_event( SimpleNamespace( sender="@alice:example.org", body="!no", event_id="$no", msgtype="m.text", replyto_event_id=None, ), room_id="!confirm:example.org", chat_id="C7", ) result = await make_handle_cancel(store)(callback, auth_mgr, platform, chat_mgr, settings_mgr) assert "отменено" in result[0].text.lower() assert await get_pending_confirm(store, "@alice:example.org", "!confirm:example.org") is None assert await get_pending_confirm(store, "@bob:example.org", "!other:example.org") is not None async def test_send_outgoing_uploads_workspace_file_attachment(tmp_path, monkeypatch): workspace_file = tmp_path / "surfaces" / "matrix" / "alice" / "room" / "inbox" / "result.txt" workspace_file.parent.mkdir(parents=True, exist_ok=True) workspace_file.write_text("ready") monkeypatch.setenv("SURFACES_WORKSPACE_DIR", str(tmp_path)) client = SimpleNamespace( upload=AsyncMock(return_value=(SimpleNamespace(content_uri="mxc://server/file"), {})), room_send=AsyncMock(), ) await send_outgoing( client, "!room:example.org", OutgoingMessage( chat_id="!room:example.org", text="Файл готов", attachments=[ Attachment( type="document", filename="result.txt", mime_type="text/plain", workspace_path="surfaces/matrix/alice/room/inbox/result.txt", ) ], ), ) client.upload.assert_awaited_once() client.room_send.assert_awaited() assert client.room_send.await_args_list[0].args[2]["body"] == "Файл готов" file_call = client.room_send.await_args_list[1] assert file_call.args[2]["msgtype"] == "m.file" assert file_call.args[2]["url"] == "mxc://server/file"