annator-command-center / tests /test_auto_dev /test_reflection_engine.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 9)
81e3673 verified
Raw
History Blame Contribute Delete
6.99 kB
"""
Test ReflectionEngine pattern detection.
Tests cover:
- Event listening
- Repeated failure pattern detection
- Triggering MementoEngine after threshold
- Pattern frequency tracking
- Resetting patterns after successful promotion
"""
import pytest
from unittest.mock import AsyncMock, MagicMock
from core.auto_dev.reflection_engine import ReflectionEngine
from core.auto_dev.event_hooks import TaskEvent
class TestReflectionEngineEventListening:
"""Test ReflectionEngine listens to events."""
def test_subscribes_to_task_fail_events(self, auto_dev_db_session):
"""Test subscribes to task_fail events."""
engine = ReflectionEngine(db=auto_dev_db_session)
engine.register()
# Should have registered handler
assert len(engine._failure_buffer) == 0
def test_tracks_event_metadata(self, auto_dev_db_session, sample_task_event):
"""Test tracks event metadata."""
engine = ReflectionEngine(db=auto_dev_db_session)
# Mock capability gate to allow processing
engine._should_process_agent = lambda agent_id, tenant_id: True
import asyncio
asyncio.run(engine.process_failure(sample_task_event))
assert len(engine._failure_buffer[sample_task_event.agent_id]) == 1
class TestReflectionEnginePatternDetection:
"""Test detects repeated failure patterns."""
@pytest.mark.asyncio
async def test_identifies_repeated_failure_patterns(self, auto_dev_db_session, sample_task_event):
"""Test identifies repeated failure patterns."""
from unittest.mock import AsyncMock
engine = ReflectionEngine(db=auto_dev_db_session, failure_threshold=2)
# Mock capability gate to allow processing
engine._should_process_agent = lambda agent_id, tenant_id: True
# Mock _trigger_memento to avoid database query
engine._trigger_memento = AsyncMock()
# Process first failure
await engine.process_failure(sample_task_event)
# Check buffer has 1 failure
assert len(engine._failure_buffer[sample_task_event.agent_id]) == 1
# Process second failure (should trigger)
await engine.process_failure(sample_task_event)
# Verify _trigger_memento was called (pattern detected)
engine._trigger_memento.assert_called_once()
# Note: Buffer is cleared after triggering, so we check the call happened
@pytest.mark.asyncio
async def test_groups_by_error_type(self, auto_dev_db_session):
"""Test groups by error type."""
from unittest.mock import AsyncMock
engine = ReflectionEngine(db=auto_dev_db_session, failure_threshold=3)
# Mock capability gate to allow processing
engine._should_process_agent = lambda agent_id, tenant_id: True
# Mock _trigger_memento to avoid database query
engine._trigger_memento = AsyncMock()
event1 = TaskEvent(
episode_id="ep-001",
agent_id="agent-001",
tenant_id="tenant-001",
task_description="Process sales data",
error_trace="ValueError: Invalid format",
outcome="failure",
)
event2 = TaskEvent(
episode_id="ep-002",
agent_id="agent-001",
tenant_id="tenant-001",
task_description="Process sales data again",
error_trace="ValueError: Invalid format",
outcome="failure",
)
await engine.process_failure(event1)
await engine.process_failure(event2)
# Should find similar failures
similar = engine._find_similar_failures("agent-001", "Process sales data")
assert len(similar) >= 1
class TestReflectionEngineTriggerThreshold:
"""Test triggers MementoEngine after threshold."""
@pytest.mark.asyncio
async def test_triggers_after_n_failures(self, auto_dev_db_session, sample_task_event, monkeypatch):
"""Test triggers MementoEngine after N failures."""
engine = ReflectionEngine(db=auto_dev_db_session, failure_threshold=2)
# Mock capability gate to allow processing
engine._should_process_agent = lambda agent_id, tenant_id: True
# Mock MementoEngine to avoid database requirements
mock_memento = MagicMock()
mock_candidate = MagicMock()
mock_candidate.skill_name = "test_skill"
mock_memento.generate_skill_candidate = AsyncMock(return_value=mock_candidate)
import sys
original_module = sys.modules.get("core.auto_dev.memento_engine")
class MockMementoEngine:
def __init__(self, db):
pass
sys.modules["core.auto_dev.memento_engine"] = MockMementoEngine
sys.modules["core.auto_dev.memento_engine"].MementoEngine = lambda db: mock_memento
try:
# Process two failures
await engine.process_failure(sample_task_event)
await engine.process_failure(sample_task_event)
# Should have triggered
mock_memento.generate_skill_candidate.assert_called_once()
finally:
if original_module:
sys.modules["core.auto_dev.memento_engine"] = original_module
else:
sys.modules.pop("core.auto_dev.memento_engine", None)
@pytest.mark.asyncio
async def test_prevents_duplicate_triggers(self, auto_dev_db_session, sample_task_event):
"""Test prevents duplicate triggers."""
engine = ReflectionEngine(db=auto_dev_db_session, failure_threshold=2)
# Process same event multiple times
for _ in range(5):
await engine.process_failure(sample_task_event)
# Buffer should be cleared after trigger
assert len(engine._failure_buffer[sample_task_event.agent_id]) < 5
class TestReflectionEnginePatternTracking:
"""Test pattern frequency tracking."""
@pytest.mark.asyncio
async def test_stores_pattern_metadata(self, auto_dev_db_session, sample_task_event):
"""Test stores pattern metadata."""
engine = ReflectionEngine(db=auto_dev_db_session)
# Mock capability gate to allow processing
engine._should_process_agent = lambda agent_id, tenant_id: True
await engine.process_failure(sample_task_event)
buffer = engine._failure_buffer[sample_task_event.agent_id]
assert len(buffer) == 1
assert buffer[0]["episode_id"] == sample_task_event.episode_id
assert buffer[0]["task_description"] == sample_task_event.task_description
class TestReflectionEngineIntegration:
"""Test ReflectionEngine integration."""
def test_reflection_engine_initialization(self, auto_dev_db_session):
"""Test ReflectionEngine initializes correctly."""
engine = ReflectionEngine(db=auto_dev_db_session)
assert engine.db == auto_dev_db_session
assert engine.failure_threshold == 2 # Default
assert len(engine._failure_buffer) == 0