mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
Update the Matrix reaction-seeding contract to the four-reaction default (once/session/always/deny), add tirith-tier (session without always) and no-session-tier cases, and assert allow_session=True in the tirith gateway payload.
107 lines
4.9 KiB
Python
107 lines
4.9 KiB
Python
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
|