xsecuremcp / tests /reflexive /test_actions.py
svkrishna
🤖 Implement Story 4: Reflexive Core
7b7b417
Raw
History Blame Contribute Delete
17.5 kB
"""Tests for the reflexive actions."""
import pytest
from datetime import datetime
from unittest.mock import Mock
from fastmcp.reflexive.actions import (
HaltAction, EscalateAction, MonitorAction, AllowAction,
ActionFactory, ActionExecutor
)
from fastmcp.reflexive.engine import ReflexiveDecision, ActionContext, DecisionType, RiskLevel
class TestHaltAction:
"""Test the HaltAction class."""
@pytest.fixture
def halt_decision(self):
"""Create a halt decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="admin_access"
)
return ReflexiveDecision(
decision_type=DecisionType.HALT,
risk_level=RiskLevel.HIGH,
action_context=action_context,
reason="Unauthorized admin access attempt"
)
@pytest.fixture
def halt_action(self, halt_decision):
"""Create a halt action for testing."""
return HaltAction(
decision=halt_decision,
halt_reason="Security violation detected",
halt_level="immediate",
affected_operations=["admin_access", "user_management"]
)
def test_halt_action_creation(self, halt_action, halt_decision):
"""Test halt action creation."""
assert halt_action.decision == halt_decision
assert halt_action.halt_reason == "Security violation detected"
assert halt_action.halt_level == "immediate"
assert halt_action.affected_operations == ["admin_access", "user_management"]
assert halt_action.status == "pending"
assert halt_action.result is None
async def test_halt_action_execution(self, halt_action):
"""Test halt action execution."""
result = await halt_action.execute()
assert halt_action.status == "completed"
assert halt_action.result is not None
assert result["halted_operations"] == ["admin_access", "user_management"]
assert result["halt_reason"] == "Security violation detected"
assert result["halt_level"] == "immediate"
assert "halt_timestamp" in result
assert "decision_id" in result
def test_halt_action_type(self, halt_action):
"""Test halt action type."""
assert halt_action.get_action_type() == "halt"
class TestEscalateAction:
"""Test the EscalateAction class."""
@pytest.fixture
def escalate_decision(self):
"""Create an escalate decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="data_access"
)
return ReflexiveDecision(
decision_type=DecisionType.ESCALATE,
risk_level=RiskLevel.MEDIUM,
action_context=action_context,
reason="Suspicious data access pattern"
)
@pytest.fixture
def escalate_action(self, escalate_decision):
"""Create an escalate action for testing."""
return EscalateAction(
decision=escalate_decision,
escalation_target="security_team",
escalation_priority="high",
escalation_context={"alert_level": "medium"},
notification_channels=["email", "slack"]
)
def test_escalate_action_creation(self, escalate_action, escalate_decision):
"""Test escalate action creation."""
assert escalate_action.decision == escalate_decision
assert escalate_action.escalation_target == "security_team"
assert escalate_action.escalation_priority == "high"
assert escalate_action.escalation_context == {"alert_level": "medium"}
assert escalate_action.notification_channels == ["email", "slack"]
assert escalate_action.status == "pending"
assert escalate_action.result is None
async def test_escalate_action_execution(self, escalate_action):
"""Test escalate action execution."""
result = await escalate_action.execute()
assert escalate_action.status == "completed"
assert escalate_action.result is not None
assert result["escalation_target"] == "security_team"
assert result["escalation_priority"] == "high"
assert result["escalation_context"] == {"alert_level": "medium"}
assert result["notification_channels"] == ["email", "slack"]
assert "escalation_timestamp" in result
assert "decision_id" in result
assert "action_context" in result
def test_escalate_action_type(self, escalate_action):
"""Test escalate action type."""
assert escalate_action.get_action_type() == "escalate"
class TestMonitorAction:
"""Test the MonitorAction class."""
@pytest.fixture
def monitor_decision(self):
"""Create a monitor decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="api_call"
)
return ReflexiveDecision(
decision_type=DecisionType.MONITOR,
risk_level=RiskLevel.LOW,
action_context=action_context,
reason="Unusual but not suspicious activity"
)
@pytest.fixture
def monitor_action(self, monitor_decision):
"""Create a monitor action for testing."""
return MonitorAction(
decision=monitor_decision,
monitoring_level="enhanced",
monitoring_duration=1800,
monitoring_scope=["test_user", "api_calls"]
)
def test_monitor_action_creation(self, monitor_action, monitor_decision):
"""Test monitor action creation."""
assert monitor_action.decision == monitor_decision
assert monitor_action.monitoring_level == "enhanced"
assert monitor_action.monitoring_duration == 1800
assert monitor_action.monitoring_scope == ["test_user", "api_calls"]
assert monitor_action.status == "pending"
assert monitor_action.result is None
async def test_monitor_action_execution(self, monitor_action):
"""Test monitor action execution."""
result = await monitor_action.execute()
assert monitor_action.status == "completed"
assert monitor_action.result is not None
assert result["monitoring_level"] == "enhanced"
assert result["monitoring_duration"] == 1800
assert result["monitoring_scope"] == ["test_user", "api_calls"]
assert "monitoring_timestamp" in result
assert "decision_id" in result
assert "action_context" in result
def test_monitor_action_type(self, monitor_action):
"""Test monitor action type."""
assert monitor_action.get_action_type() == "monitor"
class TestAllowAction:
"""Test the AllowAction class."""
@pytest.fixture
def allow_decision(self):
"""Create an allow decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="normal_operation"
)
return ReflexiveDecision(
decision_type=DecisionType.ALLOW,
risk_level=RiskLevel.LOW,
action_context=action_context,
reason="No violations or anomalies detected"
)
@pytest.fixture
def allow_action(self, allow_decision):
"""Create an allow action for testing."""
return AllowAction(
decision=allow_decision,
allow_conditions=["authenticated", "authorized"],
allow_restrictions=["rate_limited"]
)
def test_allow_action_creation(self, allow_action, allow_decision):
"""Test allow action creation."""
assert allow_action.decision == allow_decision
assert allow_action.allow_conditions == ["authenticated", "authorized"]
assert allow_action.allow_restrictions == ["rate_limited"]
assert allow_action.status == "pending"
assert allow_action.result is None
async def test_allow_action_execution(self, allow_action):
"""Test allow action execution."""
result = await allow_action.execute()
assert allow_action.status == "completed"
assert allow_action.result is not None
assert result["allowed"] is True
assert result["allow_conditions"] == ["authenticated", "authorized"]
assert result["allow_restrictions"] == ["rate_limited"]
assert "allow_timestamp" in result
assert "decision_id" in result
assert "action_context" in result
def test_allow_action_type(self, allow_action):
"""Test allow action type."""
assert allow_action.get_action_type() == "allow"
class TestActionFactory:
"""Test the ActionFactory class."""
@pytest.fixture
def halt_decision(self):
"""Create a halt decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="admin_access"
)
return ReflexiveDecision(
decision_type=DecisionType.HALT,
risk_level=RiskLevel.HIGH,
action_context=action_context,
reason="Security violation"
)
@pytest.fixture
def escalate_decision(self):
"""Create an escalate decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="data_access"
)
return ReflexiveDecision(
decision_type=DecisionType.ESCALATE,
risk_level=RiskLevel.MEDIUM,
action_context=action_context,
reason="Suspicious activity"
)
@pytest.fixture
def monitor_decision(self):
"""Create a monitor decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="api_call"
)
return ReflexiveDecision(
decision_type=DecisionType.MONITOR,
risk_level=RiskLevel.LOW,
action_context=action_context,
reason="Unusual activity"
)
@pytest.fixture
def allow_decision(self):
"""Create an allow decision for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="normal_operation"
)
return ReflexiveDecision(
decision_type=DecisionType.ALLOW,
risk_level=RiskLevel.LOW,
action_context=action_context,
reason="No issues detected"
)
def test_create_halt_action(self, halt_decision):
"""Test creating a halt action."""
action = ActionFactory.create_action(halt_decision)
assert isinstance(action, HaltAction)
assert action.decision == halt_decision
assert action.halt_reason == halt_decision.reason
assert action.halt_level == "immediate"
assert halt_decision.action_context.action_id in action.affected_operations
def test_create_escalate_action(self, escalate_decision):
"""Test creating an escalate action."""
action = ActionFactory.create_action(escalate_decision)
assert isinstance(action, EscalateAction)
assert action.decision == escalate_decision
assert action.escalation_target == "default_admin"
assert action.escalation_priority == "normal"
def test_create_monitor_action(self, monitor_decision):
"""Test creating a monitor action."""
action = ActionFactory.create_action(monitor_decision)
assert isinstance(action, MonitorAction)
assert action.decision == monitor_decision
assert action.monitoring_level == "enhanced"
assert action.monitoring_duration == 3600
def test_create_allow_action(self, allow_decision):
"""Test creating an allow action."""
action = ActionFactory.create_action(allow_decision)
assert isinstance(action, AllowAction)
assert action.decision == allow_decision
assert action.allow_conditions == []
assert action.allow_restrictions == []
def test_create_action_with_kwargs(self, halt_decision):
"""Test creating an action with additional kwargs."""
action = ActionFactory.create_action(
halt_decision,
halt_level="graceful",
affected_operations=["operation1", "operation2"]
)
assert isinstance(action, HaltAction)
assert action.halt_level == "graceful"
assert action.affected_operations == ["operation1", "operation2"]
def test_create_action_unknown_type(self, halt_decision):
"""Test creating an action with unknown decision type."""
halt_decision.decision_type = "unknown_type" # type: ignore
with pytest.raises(ValueError, match="Unknown decision type"):
ActionFactory.create_action(halt_decision)
class TestActionExecutor:
"""Test the ActionExecutor class."""
@pytest.fixture
def action_executor(self):
"""Create an action executor for testing."""
return ActionExecutor()
@pytest.fixture
def halt_action(self):
"""Create a halt action for testing."""
action_context = ActionContext(
action_id="test_action",
actor_id="test_user",
action_type="admin_access"
)
decision = ReflexiveDecision(
decision_type=DecisionType.HALT,
risk_level=RiskLevel.HIGH,
action_context=action_context,
reason="Security violation"
)
return HaltAction(
decision=decision,
halt_reason="Unauthorized access",
halt_level="immediate",
affected_operations=[action_context.action_id]
)
async def test_execute_action_success(self, action_executor, halt_action):
"""Test successful action execution."""
result = await action_executor.execute_action(halt_action)
assert result["halted_operations"] == [halt_action.decision.action_context.action_id]
assert result["halt_reason"] == "Unauthorized access"
assert result["halt_level"] == "immediate"
# Check execution history
assert len(action_executor.execution_history) == 1
history_record = action_executor.execution_history[0]
assert history_record["action_id"] == str(halt_action.action_id)
assert history_record["action_type"] == "halt"
assert history_record["status"] == "completed"
assert "end_time" in history_record
assert "result" in history_record
async def test_execute_action_failure(self, action_executor):
"""Test action execution failure."""
# Create a mock action that raises an exception
mock_action = Mock()
mock_action.action_id = "test_action_id"
mock_action.decision = Mock()
mock_action.decision.decision_id = "test_decision_id"
mock_action.execute.side_effect = Exception("Execution failed")
mock_action.get_action_type.return_value = "test_type"
mock_action.timestamp = datetime.utcnow()
mock_action.status = "pending"
with pytest.raises(Exception, match="Execution failed"):
await action_executor.execute_action(mock_action)
# Check execution history
assert len(action_executor.execution_history) == 1
history_record = action_executor.execution_history[0]
assert history_record["action_id"] == "test_action_id"
assert history_record["status"] == "failed"
assert "error" in history_record
assert history_record["error"] == "Execution failed"
def test_get_execution_stats(self, action_executor):
"""Test getting execution statistics."""
# Add some execution history
action_executor.execution_history = [
{"action_id": "1", "status": "completed"},
{"action_id": "2", "status": "completed"},
{"action_id": "3", "status": "failed"},
]
stats = action_executor.get_execution_stats()
assert stats["total_actions"] == 3
assert stats["completed_actions"] == 2
assert stats["failed_actions"] == 1
assert stats["active_actions"] == 0
assert stats["success_rate"] == 2/3
def test_active_actions_tracking(self, action_executor, halt_action):
"""Test active actions tracking."""
# Start execution (this would normally be async)
action_executor.active_actions[str(halt_action.action_id)] = halt_action
assert len(action_executor.active_actions) == 1
assert str(halt_action.action_id) in action_executor.active_actions
# Simulate completion
del action_executor.active_actions[str(halt_action.action_id)]
assert len(action_executor.active_actions) == 0