新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
265 lines
10 KiB
Python
265 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.policy.approval import (
|
|
ApprovalAction,
|
|
ApprovalConfig,
|
|
ApprovalState,
|
|
BaseApprovalCapability,
|
|
)
|
|
|
|
|
|
class TestApprovalConfig:
|
|
def test_defaults(self):
|
|
cfg = ApprovalConfig()
|
|
assert cfg.enabled is False
|
|
assert cfg.approvers == []
|
|
assert cfg.agent_filter == []
|
|
assert cfg.session_filter == []
|
|
assert cfg.timeout_s == 300.0
|
|
assert cfg.target == "dm"
|
|
assert cfg.auto_approve_owner is True
|
|
|
|
def test_custom_values(self):
|
|
cfg = ApprovalConfig(
|
|
enabled=True,
|
|
approvers=["admin-1", "admin-2"],
|
|
agent_filter=["agent-critical"],
|
|
session_filter=["dm-session-1"],
|
|
timeout_s=600.0,
|
|
target="both",
|
|
auto_approve_owner=False,
|
|
)
|
|
assert cfg.enabled is True
|
|
assert cfg.approvers == ["admin-1", "admin-2"]
|
|
assert cfg.agent_filter == ["agent-critical"]
|
|
assert cfg.session_filter == ["dm-session-1"]
|
|
assert cfg.timeout_s == 600.0
|
|
assert cfg.target == "both"
|
|
assert cfg.auto_approve_owner is False
|
|
|
|
|
|
class TestBaseApprovalCapability:
|
|
@pytest.fixture
|
|
def disabled_config(self) -> ApprovalConfig:
|
|
return ApprovalConfig(enabled=False)
|
|
|
|
@pytest.fixture
|
|
def enabled_config(self) -> ApprovalConfig:
|
|
return ApprovalConfig(enabled="auto", approvers=["approver-1"])
|
|
|
|
@pytest.fixture
|
|
def filtered_config(self) -> ApprovalConfig:
|
|
return ApprovalConfig(
|
|
enabled=True,
|
|
agent_filter=["agent-1"],
|
|
session_filter=["session-1"],
|
|
approvers=["approver-1"],
|
|
)
|
|
|
|
def test_is_enabled_with_false(self, disabled_config):
|
|
capability = BaseApprovalCapability(disabled_config)
|
|
assert capability.is_enabled() is False
|
|
|
|
def test_is_enabled_with_true(self):
|
|
cfg = ApprovalConfig(enabled=True)
|
|
capability = BaseApprovalCapability(cfg)
|
|
assert capability.is_enabled() is True
|
|
|
|
def test_is_enabled_with_auto(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
assert capability.is_enabled() is True
|
|
|
|
def test_requires_approval_when_disabled(self, disabled_config):
|
|
capability = BaseApprovalCapability(disabled_config)
|
|
assert capability.requires_approval("agent-1") is False
|
|
|
|
def test_requires_approval_without_filters(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
assert capability.requires_approval("agent-1") is True
|
|
|
|
def test_requires_approval_agent_filter_hit(self, filtered_config):
|
|
capability = BaseApprovalCapability(filtered_config)
|
|
assert capability.requires_approval("agent-1", "session-1") is True
|
|
|
|
def test_requires_approval_agent_filter_miss(self, filtered_config):
|
|
capability = BaseApprovalCapability(filtered_config)
|
|
assert capability.requires_approval("agent-2") is False
|
|
|
|
def test_requires_approval_session_filter_miss(self, filtered_config):
|
|
capability = BaseApprovalCapability(filtered_config)
|
|
assert capability.requires_approval("agent-1", "session-2") is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_approval(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
assert len(approval_id) == 8
|
|
assert capability.get_state(approval_id) == ApprovalState.PENDING
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_approve(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
result = await capability.resolve(approval_id, ApprovalAction.APPROVE, "approver-1")
|
|
assert result is True
|
|
assert capability.get_state(approval_id) == ApprovalState.APPROVED
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_allow_once(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
result = await capability.resolve(approval_id, ApprovalAction.ALLOW_ONCE, "approver-1")
|
|
assert result is True
|
|
assert capability.get_state(approval_id) == ApprovalState.APPROVED
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_deny(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
result = await capability.resolve(approval_id, ApprovalAction.DENY, "approver-1")
|
|
assert result is True
|
|
assert capability.get_state(approval_id) == ApprovalState.DENIED
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_nonexistent_approval(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
result = await capability.resolve("nonexistent", ApprovalAction.APPROVE, "approver-1")
|
|
assert result is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_already_resolved_returns_false(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
await capability.resolve(approval_id, ApprovalAction.APPROVE, "approver-1")
|
|
result = await capability.resolve(approval_id, ApprovalAction.DENY, "approver-1")
|
|
assert result is False
|
|
assert capability.get_state(approval_id) == ApprovalState.APPROVED
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_unauthorized_approver(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
result = await capability.resolve(approval_id, ApprovalAction.APPROVE, "stranger")
|
|
assert result is False
|
|
assert capability.get_state(approval_id) == ApprovalState.PENDING
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cancel_approval(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
result = await capability.cancel(approval_id)
|
|
assert result is True
|
|
assert capability.get_state(approval_id) is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cancel_nonexistent(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
result = await capability.cancel("nonexistent")
|
|
assert result is False
|
|
|
|
def test_get_state_nonexistent(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
assert capability.get_state("nonexistent") is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_suppress_reply_pending(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
assert capability.should_suppress_reply(approval_id) is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_suppress_reply_after_approve(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
await capability.resolve(approval_id, ApprovalAction.APPROVE, "approver-1")
|
|
assert capability.should_suppress_reply(approval_id) is False
|
|
|
|
def test_should_suppress_reply_nonexistent(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
assert capability.should_suppress_reply("nonexistent") is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_timeouts(self):
|
|
cfg = ApprovalConfig(enabled=True, approvers=["approver-1"], timeout_s=0.0)
|
|
capability = BaseApprovalCapability(cfg)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
expired = capability.check_timeouts()
|
|
assert approval_id in expired
|
|
assert capability.get_state(approval_id) == ApprovalState.TIMED_OUT
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_timeouts_not_expired(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
expired = capability.check_timeouts()
|
|
assert len(expired) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cleanup_completed(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
approval_id = await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
await capability.resolve(approval_id, ApprovalAction.APPROVE, "approver-1")
|
|
cleaned = capability.cleanup_completed()
|
|
assert cleaned == 1
|
|
assert capability.get_state(approval_id) is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cleanup_does_not_remove_pending(self, enabled_config):
|
|
capability = BaseApprovalCapability(enabled_config)
|
|
await capability.create_approval(
|
|
agent_id="agent-1",
|
|
command="/run test",
|
|
requester_id="user-1",
|
|
)
|
|
cleaned = capability.cleanup_completed()
|
|
assert cleaned == 0 |