test_config.pyโข14.2 kB
"""Tests for configuration module."""
import pytest
from pathlib import Path
import tempfile
from delegation_mcp.config import (
DelegationConfig,
DelegationRule,
OrchestratorConfig,
ConfigValidationError,
)
def test_delegation_rule_creation():
"""Test creating a delegation rule."""
rule = DelegationRule(
pattern="security|audit",
delegate_to="gemini",
priority=5,
requires_approval=False,
description="Security tasks",
)
assert rule.pattern == "security|audit"
assert rule.delegate_to == "gemini"
assert rule.priority == 5
def test_orchestrator_config_creation():
"""Test creating orchestrator configuration."""
config = OrchestratorConfig(
name="claude",
command="claude",
enabled=True,
)
assert config.name == "claude"
assert config.command == "claude"
assert config.enabled is True
assert config.timeout == 300 # default
def test_delegation_config_find_rule():
"""Test finding matching delegation rule."""
config = DelegationConfig(
orchestrator="claude",
rules=[
DelegationRule(
pattern="security|audit",
delegate_to="gemini",
priority=5,
),
DelegationRule(
pattern="refactor",
delegate_to="aider",
priority=3,
),
],
)
# Should match security rule
rule = config.find_delegation_rule("Run a security audit")
assert rule is not None
assert rule.delegate_to == "gemini"
# Should match refactor rule
rule = config.find_delegation_rule("Refactor the code")
assert rule is not None
assert rule.delegate_to == "aider"
# Should not match any rule
rule = config.find_delegation_rule("Explain Python")
assert rule is None
def test_config_save_and_load():
"""Test saving and loading configuration."""
with tempfile.TemporaryDirectory() as tmpdir:
config_path = Path(tmpdir) / "test_config.yaml"
# Create and save config
config = DelegationConfig(
orchestrator="claude",
rules=[
DelegationRule(
pattern="test",
delegate_to="gemini",
priority=1,
),
],
)
config.to_yaml(config_path)
# Load config
loaded_config = DelegationConfig.from_yaml(config_path, validate=False)
assert loaded_config.orchestrator == "claude"
assert len(loaded_config.rules) == 1
assert loaded_config.rules[0].pattern == "test"
# ============================================================================
# Validation Tests
# ============================================================================
def test_validate_minimum_agents_success():
"""Test validation passes with 2 or more enabled agents."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
"aider": OrchestratorConfig(name="aider", command="aider", enabled=False),
},
rules=[],
)
# Should not raise exception
config.validate()
def test_validate_minimum_agents_failure_zero():
"""Test validation fails with no enabled agents."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=False),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=False),
},
rules=[],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
assert "At least 2 agents must be enabled" in str(exc_info.value)
assert "only 0 are enabled" in str(exc_info.value)
def test_validate_minimum_agents_failure_one():
"""Test validation fails with only one enabled agent."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=False),
},
rules=[],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
assert "At least 2 agents must be enabled" in str(exc_info.value)
assert "only 1 is enabled" in str(exc_info.value)
def test_validate_regex_patterns_success():
"""Test validation passes with valid regex patterns."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
},
rules=[
DelegationRule(pattern="security|audit", delegate_to="gemini", priority=5),
DelegationRule(pattern="refactor.*code", delegate_to="claude", priority=3),
DelegationRule(pattern="^test", delegate_to="gemini", priority=2),
],
)
# Should not raise exception
config.validate()
def test_validate_regex_patterns_failure():
"""Test validation fails with invalid regex patterns."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
},
rules=[
DelegationRule(
pattern="valid_pattern", delegate_to="gemini", priority=5
),
DelegationRule(
pattern="[invalid(regex", delegate_to="claude", priority=3
), # Invalid regex
DelegationRule(
pattern="(?P<incomplete", delegate_to="gemini", priority=2
), # Invalid regex
],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
error_msg = str(exc_info.value)
assert "Invalid regex pattern" in error_msg
assert "[invalid(regex" in error_msg or "(?P<incomplete" in error_msg
def test_validate_agent_references_success():
"""Test validation passes when all referenced agents exist."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
"aider": OrchestratorConfig(name="aider", command="aider", enabled=True),
},
rules=[
DelegationRule(pattern="security", delegate_to="gemini", priority=5),
DelegationRule(pattern="refactor", delegate_to="claude", priority=3),
DelegationRule(pattern="git", delegate_to="aider", priority=4),
],
)
# Should not raise exception
config.validate()
def test_validate_agent_references_failure_rule_target():
"""Test validation fails when rule references non-existent agent."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
},
rules=[
DelegationRule(pattern="security", delegate_to="gemini", priority=5),
DelegationRule(
pattern="git", delegate_to="nonexistent_agent", priority=3
), # Invalid reference
],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
error_msg = str(exc_info.value)
assert "Target orchestrator 'nonexistent_agent' is not defined" in error_msg
assert "pattern: 'git'" in error_msg
def test_validate_no_circular_delegation_success():
"""Test validation passes with normal delegation rules."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
"aider": OrchestratorConfig(name="aider", command="aider", enabled=True),
},
rules=[
DelegationRule(pattern="security", delegate_to="gemini", priority=5),
DelegationRule(pattern="refactor", delegate_to="claude", priority=3),
DelegationRule(pattern="git", delegate_to="aider", priority=4),
],
)
# Should not raise exception - multiple different patterns to different targets is fine
config.validate()
def test_validate_no_ambiguous_delegation():
"""Test validation detects ambiguous delegation patterns."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
"aider": OrchestratorConfig(name="aider", command="aider", enabled=True),
},
rules=[
# Same pattern, same priority, different targets - ambiguous!
DelegationRule(pattern="security", delegate_to="claude", priority=5),
DelegationRule(pattern="security", delegate_to="gemini", priority=5),
],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
error_msg = str(exc_info.value)
assert "ambiguous delegation" in error_msg.lower()
assert "security" in error_msg.lower()
def test_validate_same_pattern_different_priority_ok():
"""Test validation allows same pattern with different priorities."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
"aider": OrchestratorConfig(name="aider", command="aider", enabled=True),
},
rules=[
# Same pattern but different priorities - OK (higher priority wins)
DelegationRule(pattern="security", delegate_to="gemini", priority=5),
DelegationRule(pattern="security", delegate_to="claude", priority=3),
],
)
# Should not raise exception - different priorities resolve ambiguity
config.validate()
def test_validate_multiple_errors():
"""Test validation reports multiple errors at once."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=False),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=False),
# Error 1: less than 2 enabled agents
},
rules=[
DelegationRule(
pattern="[invalid(", delegate_to="claude", priority=5
), # Error 2: invalid regex
DelegationRule(
pattern="test", delegate_to="missing_agent", priority=3
), # Error 3: invalid reference
],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
error_msg = str(exc_info.value)
# Should contain multiple errors
assert "At least 2 agents must be enabled" in error_msg
assert "Invalid regex pattern" in error_msg
assert "Target orchestrator 'missing_agent' is not defined" in error_msg
def test_from_yaml_with_validation():
"""Test loading config from YAML with validation enabled."""
with tempfile.TemporaryDirectory() as tmpdir:
config_path = Path(tmpdir) / "test_config.yaml"
# Create invalid config
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(
name="claude", command="claude", enabled=False
),
"gemini": OrchestratorConfig(
name="gemini", command="gemini", enabled=False
),
},
rules=[],
)
config.to_yaml(config_path)
# Try to load with validation - should fail
with pytest.raises(ConfigValidationError):
DelegationConfig.from_yaml(config_path, validate=True)
# Load without validation - should succeed
loaded = DelegationConfig.from_yaml(config_path, validate=False)
assert loaded.orchestrator == "claude"
def test_validate_empty_orchestrators():
"""Test validation fails with no orchestrators defined."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={}, # Empty orchestrators
rules=[],
)
with pytest.raises(ConfigValidationError) as exc_info:
config.validate()
error_msg = str(exc_info.value)
assert "At least 2 agents must be enabled" in error_msg
assert "Primary orchestrator 'claude' is not defined" in error_msg
def test_validate_with_three_enabled_agents():
"""Test validation passes with more than minimum enabled agents."""
config = DelegationConfig(
orchestrator="claude",
orchestrators={
"claude": OrchestratorConfig(name="claude", command="claude", enabled=True),
"gemini": OrchestratorConfig(name="gemini", command="gemini", enabled=True),
"aider": OrchestratorConfig(name="aider", command="aider", enabled=True),
"copilot": OrchestratorConfig(
name="copilot", command="copilot", enabled=False
),
},
rules=[
DelegationRule(pattern="security", delegate_to="gemini", priority=5),
],
)
# Should not raise exception
config.validate()