import types import pytest from unittest.mock import AsyncMock, patch from gateway.config import PlatformConfig class TestMatrixExecApprovalReactions: @pytest.mark.asyncio async def test_send_exec_approval_registers_prompt_and_seeds_reactions(self, monkeypatch): monkeypatch.setenv("MATRIX_ALLOWED_USERS", "@liizfq:liizfq.top") from plugins.platforms.matrix.adapter import MatrixAdapter adapter = MatrixAdapter(PlatformConfig(enabled=True, token="tok", extra={"homeserver": "https://matrix.example.org"})) adapter._client = types.SimpleNamespace() adapter.send = AsyncMock(return_value=types.SimpleNamespace(success=True, message_id="$evt1")) adapter._send_reaction = AsyncMock(return_value="$r") result = await adapter.send_exec_approval( chat_id="!room:example.org", command="rm -rf /tmp/test", session_key="sess-1", description="dangerous", ) assert result.success is True assert adapter._approval_prompt_by_session["sess-1"] == "$evt1" assert adapter._approval_prompts_by_event["$evt1"].session_key == "sess-1" assert adapter._send_reaction.await_count == 4 emojis = [call.args[2] for call in adapter._send_reaction.await_args_list] assert emojis == ["βœ…", "πŸŒ€", "♾️", "❌"] @pytest.mark.asyncio async def test_send_exec_approval_tirith_seeds_session_but_not_always(self, monkeypatch): """allow_permanent=False (tirith-only prompt) keeps the session tier but drops the permanent reaction.""" monkeypatch.setenv("MATRIX_ALLOWED_USERS", "@liizfq:liizfq.top") from plugins.platforms.matrix.adapter import MatrixAdapter adapter = MatrixAdapter(PlatformConfig(enabled=True, token="tok", extra={"homeserver": "https://matrix.example.org"})) adapter._client = types.SimpleNamespace() adapter.send = AsyncMock(return_value=types.SimpleNamespace(success=True, message_id="$evt2")) adapter._send_reaction = AsyncMock(return_value="$r") result = await adapter.send_exec_approval( chat_id="!room:example.org", command="curl https://bit.ly/abc", session_key="sess-2", description="shortened URL", allow_permanent=False, ) assert result.success is True emojis = [call.args[2] for call in adapter._send_reaction.await_args_list] assert emojis == ["βœ…", "πŸŒ€", "❌"] @pytest.mark.asyncio async def test_send_exec_approval_no_session_seeds_once_deny_only(self, monkeypatch): """allow_session=False (Smart-DENY-style) collapses to once/deny.""" monkeypatch.setenv("MATRIX_ALLOWED_USERS", "@liizfq:liizfq.top") from plugins.platforms.matrix.adapter import MatrixAdapter adapter = MatrixAdapter(PlatformConfig(enabled=True, token="tok", extra={"homeserver": "https://matrix.example.org"})) adapter._client = types.SimpleNamespace() adapter.send = AsyncMock(return_value=types.SimpleNamespace(success=True, message_id="$evt3")) adapter._send_reaction = AsyncMock(return_value="$r") result = await adapter.send_exec_approval( chat_id="!room:example.org", command="rm -rf /tmp/x", session_key="sess-3", description="dangerous", allow_session=False, ) assert result.success is True emojis = [call.args[2] for call in adapter._send_reaction.await_args_list] assert emojis == ["βœ…", "❌"] @pytest.mark.asyncio async def test_reaction_resolves_pending_approval(self, monkeypatch): monkeypatch.setenv("MATRIX_ALLOWED_USERS", "@liizfq:liizfq.top") from plugins.platforms.matrix.adapter import MatrixAdapter, _MatrixApprovalPrompt adapter = MatrixAdapter(PlatformConfig(enabled=True, token="tok", extra={"homeserver": "https://matrix.example.org"})) # Resolve user_id so _is_self_sender doesn't defensively drop all traffic (#15763). adapter._user_id = "@bot:example.org" adapter._approval_prompts_by_event["$target"] = _MatrixApprovalPrompt( session_key="sess-1", chat_id="!room:example.org", message_id="$target" ) adapter._approval_prompt_by_session["sess-1"] = "$target" content = {"m.relates_to": {"event_id": "$target", "key": "βœ…"}} event = types.SimpleNamespace( sender="@liizfq:liizfq.top", event_id="$react1", room_id="!room:example.org", content=content, ) with patch("tools.approval.resolve_gateway_approval", return_value=1) as mock_resolve: await adapter._on_reaction(event) mock_resolve.assert_called_once_with("sess-1", "once") assert "$target" not in adapter._approval_prompts_by_event assert "sess-1" not in adapter._approval_prompt_by_session