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