191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
"""Tests for tools.confirmation_daemon — Human Confirmation Firewall."""
|
|
|
|
import pytest
|
|
import time
|
|
from tools.confirmation_daemon import (
|
|
ConfirmationDaemon,
|
|
ConfirmationRequest,
|
|
ConfirmationStatus,
|
|
RiskLevel,
|
|
classify_action,
|
|
_is_whitelisted,
|
|
_DEFAULT_WHITELIST,
|
|
)
|
|
|
|
|
|
class TestClassifyAction:
|
|
"""Test action risk classification."""
|
|
|
|
def test_crypto_tx_is_critical(self):
|
|
assert classify_action("crypto_tx") == RiskLevel.CRITICAL
|
|
|
|
def test_sign_transaction_is_critical(self):
|
|
assert classify_action("sign_transaction") == RiskLevel.CRITICAL
|
|
|
|
def test_send_email_is_high(self):
|
|
assert classify_action("send_email") == RiskLevel.HIGH
|
|
|
|
def test_send_message_is_medium(self):
|
|
assert classify_action("send_message") == RiskLevel.MEDIUM
|
|
|
|
def test_access_calendar_is_low(self):
|
|
assert classify_action("access_calendar") == RiskLevel.LOW
|
|
|
|
def test_unknown_action_is_medium(self):
|
|
assert classify_action("unknown_action_xyz") == RiskLevel.MEDIUM
|
|
|
|
|
|
class TestWhitelist:
|
|
"""Test whitelist auto-approval."""
|
|
|
|
def test_self_email_is_whitelisted(self):
|
|
whitelist = dict(_DEFAULT_WHITELIST)
|
|
payload = {"from": "me@test.com", "to": "me@test.com"}
|
|
assert _is_whitelisted("send_email", payload, whitelist) is True
|
|
|
|
def test_non_whitelisted_recipient_not_approved(self):
|
|
whitelist = dict(_DEFAULT_WHITELIST)
|
|
payload = {"to": "random@stranger.com"}
|
|
assert _is_whitelisted("send_email", payload, whitelist) is False
|
|
|
|
def test_whitelisted_contact_approved(self):
|
|
whitelist = {
|
|
"send_message": {"targets": ["alice", "bob"]},
|
|
}
|
|
assert _is_whitelisted("send_message", {"to": "alice"}, whitelist) is True
|
|
assert _is_whitelisted("send_message", {"to": "charlie"}, whitelist) is False
|
|
|
|
def test_no_whitelist_entry_means_not_whitelisted(self):
|
|
whitelist = {}
|
|
assert _is_whitelisted("crypto_tx", {"amount": 1.0}, whitelist) is False
|
|
|
|
|
|
class TestConfirmationRequest:
|
|
"""Test the request data model."""
|
|
|
|
def test_defaults(self):
|
|
req = ConfirmationRequest(
|
|
request_id="test-1",
|
|
action="send_email",
|
|
description="Test email",
|
|
risk_level="high",
|
|
payload={},
|
|
)
|
|
assert req.status == ConfirmationStatus.PENDING.value
|
|
assert req.created_at > 0
|
|
assert req.expires_at > req.created_at
|
|
|
|
def test_is_pending(self):
|
|
req = ConfirmationRequest(
|
|
request_id="test-2",
|
|
action="send_email",
|
|
description="Test",
|
|
risk_level="high",
|
|
payload={},
|
|
expires_at=time.time() + 300,
|
|
)
|
|
assert req.is_pending is True
|
|
|
|
def test_is_expired(self):
|
|
req = ConfirmationRequest(
|
|
request_id="test-3",
|
|
action="send_email",
|
|
description="Test",
|
|
risk_level="high",
|
|
payload={},
|
|
expires_at=time.time() - 10,
|
|
)
|
|
assert req.is_expired is True
|
|
assert req.is_pending is False
|
|
|
|
def test_to_dict(self):
|
|
req = ConfirmationRequest(
|
|
request_id="test-4",
|
|
action="send_email",
|
|
description="Test",
|
|
risk_level="medium",
|
|
payload={"to": "a@b.com"},
|
|
)
|
|
d = req.to_dict()
|
|
assert d["request_id"] == "test-4"
|
|
assert d["action"] == "send_email"
|
|
assert "is_pending" in d
|
|
|
|
|
|
class TestConfirmationDaemon:
|
|
"""Test the daemon logic (without HTTP layer)."""
|
|
|
|
def test_auto_approve_low_risk(self):
|
|
daemon = ConfirmationDaemon()
|
|
req = daemon.request(
|
|
action="access_calendar",
|
|
description="Read today's events",
|
|
risk_level="low",
|
|
)
|
|
assert req.status == ConfirmationStatus.AUTO_APPROVED.value
|
|
|
|
def test_whitelisted_auto_approves(self):
|
|
daemon = ConfirmationDaemon()
|
|
daemon._whitelist = {"send_message": {"targets": ["alice"]}}
|
|
req = daemon.request(
|
|
action="send_message",
|
|
description="Message alice",
|
|
payload={"to": "alice"},
|
|
)
|
|
assert req.status == ConfirmationStatus.AUTO_APPROVED.value
|
|
|
|
def test_non_whitelisted_goes_pending(self):
|
|
daemon = ConfirmationDaemon()
|
|
daemon._whitelist = {}
|
|
req = daemon.request(
|
|
action="send_email",
|
|
description="Email to stranger",
|
|
payload={"to": "stranger@test.com"},
|
|
risk_level="high",
|
|
)
|
|
assert req.status == ConfirmationStatus.PENDING.value
|
|
assert req.is_pending is True
|
|
|
|
def test_approve_response(self):
|
|
daemon = ConfirmationDaemon()
|
|
daemon._whitelist = {}
|
|
req = daemon.request(
|
|
action="send_email",
|
|
description="Email test",
|
|
risk_level="high",
|
|
)
|
|
result = daemon.respond(req.request_id, approved=True, decided_by="human")
|
|
assert result.status == ConfirmationStatus.APPROVED.value
|
|
assert result.decided_by == "human"
|
|
|
|
def test_deny_response(self):
|
|
daemon = ConfirmationDaemon()
|
|
daemon._whitelist = {}
|
|
req = daemon.request(
|
|
action="crypto_tx",
|
|
description="Send 1 ETH",
|
|
risk_level="critical",
|
|
)
|
|
result = daemon.respond(
|
|
req.request_id, approved=False, decided_by="human", reason="Too risky"
|
|
)
|
|
assert result.status == ConfirmationStatus.DENIED.value
|
|
assert result.reason == "Too risky"
|
|
|
|
def test_get_pending(self):
|
|
daemon = ConfirmationDaemon()
|
|
daemon._whitelist = {}
|
|
daemon.request(action="send_email", description="Test 1", risk_level="high")
|
|
daemon.request(action="send_email", description="Test 2", risk_level="high")
|
|
pending = daemon.get_pending()
|
|
assert len(pending) >= 2
|
|
|
|
def test_get_history(self):
|
|
daemon = ConfirmationDaemon()
|
|
req = daemon.request(
|
|
action="access_calendar", description="Test", risk_level="low"
|
|
)
|
|
history = daemon.get_history()
|
|
assert len(history) >= 1
|
|
assert history[0]["action"] == "access_calendar"
|