mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
185 lines
6.7 KiB
Python
185 lines
6.7 KiB
Python
"""
|
|
Tests for ConversationCallbackProcessor and ConversationCallback models.
|
|
"""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from storage.conversation_callback import (
|
|
CallbackStatus,
|
|
ConversationCallback,
|
|
ConversationCallbackProcessor,
|
|
)
|
|
from storage.stored_conversation_metadata import StoredConversationMetadata
|
|
|
|
from openhands.events.observation.agent import AgentStateChangedObservation
|
|
|
|
|
|
class MockConversationCallbackProcessor(ConversationCallbackProcessor):
|
|
"""Mock implementation of ConversationCallbackProcessor for testing."""
|
|
|
|
name: str = 'test'
|
|
config: dict = {}
|
|
|
|
def __init__(self, name: str = 'test', config: dict | None = None, **kwargs):
|
|
super().__init__(name=name, config=config or {}, **kwargs)
|
|
self.call_count = 0
|
|
self.last_conversation_id: str | None = None
|
|
|
|
def __call__(
|
|
self, callback: ConversationCallback, observation: AgentStateChangedObservation
|
|
) -> None:
|
|
"""Mock implementation that tracks calls."""
|
|
self.call_count += 1
|
|
self.last_conversation_id = callback.conversation_id
|
|
|
|
|
|
class TestConversationCallbackProcessor:
|
|
"""Test the ConversationCallbackProcessor abstract base class."""
|
|
|
|
def test_mock_processor_creation(self):
|
|
"""Test that we can create a mock processor."""
|
|
processor = MockConversationCallbackProcessor(
|
|
name='test_processor', config={'key': 'value'}
|
|
)
|
|
assert processor.name == 'test_processor'
|
|
assert processor.config == {'key': 'value'}
|
|
assert processor.call_count == 0
|
|
assert processor.last_conversation_id is None
|
|
|
|
def test_mock_processor_call(self):
|
|
"""Test that the mock processor can be called."""
|
|
callback = ConversationCallback(conversation_id='test_conversation_id')
|
|
processor = MockConversationCallbackProcessor()
|
|
processor(
|
|
callback,
|
|
AgentStateChangedObservation('foobar', 'awaiting_user_input'),
|
|
)
|
|
|
|
assert processor.call_count == 1
|
|
assert processor.last_conversation_id == 'test_conversation_id'
|
|
|
|
def test_processor_serialization(self):
|
|
"""Test that processors can be serialized to JSON."""
|
|
processor = MockConversationCallbackProcessor(
|
|
name='test', config={'setting': 'value'}
|
|
)
|
|
json_data = processor.model_dump_json()
|
|
|
|
# Should be able to parse the JSON
|
|
data = json.loads(json_data)
|
|
assert data['name'] == 'test'
|
|
assert data['config'] == {'setting': 'value'}
|
|
|
|
|
|
class TestConversationCallback:
|
|
"""Test the ConversationCallback SQLAlchemy model."""
|
|
|
|
@pytest.fixture
|
|
def conversation_metadata(self, session_maker):
|
|
"""Create a test conversation metadata record."""
|
|
with session_maker() as session:
|
|
metadata = StoredConversationMetadata(
|
|
conversation_id='test_conversation_123', user_id='test_user_456'
|
|
)
|
|
session.add(metadata)
|
|
session.commit()
|
|
session.refresh(metadata)
|
|
yield metadata
|
|
|
|
# Cleanup
|
|
session.delete(metadata)
|
|
session.commit()
|
|
|
|
def test_callback_creation(self, conversation_metadata, session_maker):
|
|
"""Test creating a conversation callback."""
|
|
processor = MockConversationCallbackProcessor(name='test_processor')
|
|
|
|
with session_maker() as session:
|
|
callback = ConversationCallback(
|
|
conversation_id=conversation_metadata.conversation_id,
|
|
status=CallbackStatus.ACTIVE,
|
|
processor_type='tests.unit.test_conversation_processor.MockConversationCallbackProcessor',
|
|
processor_json=processor.model_dump_json(),
|
|
)
|
|
session.add(callback)
|
|
session.commit()
|
|
session.refresh(callback)
|
|
|
|
assert callback.id is not None
|
|
assert callback.conversation_id == conversation_metadata.conversation_id
|
|
assert callback.status == CallbackStatus.ACTIVE
|
|
assert callback.created_at is not None
|
|
assert callback.updated_at is not None
|
|
|
|
# Cleanup
|
|
session.delete(callback)
|
|
session.commit()
|
|
|
|
def test_set_processor(self, conversation_metadata, session_maker):
|
|
"""Test setting a processor on a callback."""
|
|
processor = MockConversationCallbackProcessor(
|
|
name='test_processor', config={'key': 'value'}
|
|
)
|
|
|
|
callback = ConversationCallback(
|
|
conversation_id=conversation_metadata.conversation_id
|
|
)
|
|
callback.set_processor(processor)
|
|
|
|
assert (
|
|
callback.processor_type
|
|
== 'enterprise.tests.unit.test_conversation_callback_processor.MockConversationCallbackProcessor'
|
|
)
|
|
|
|
# Verify the JSON contains the processor data
|
|
processor_data = json.loads(callback.processor_json)
|
|
assert processor_data['name'] == 'test_processor'
|
|
assert processor_data['config'] == {'key': 'value'}
|
|
|
|
def test_get_processor(self, conversation_metadata, session_maker):
|
|
"""Test getting a processor from a callback."""
|
|
processor = MockConversationCallbackProcessor(
|
|
name='test_processor', config={'key': 'value'}
|
|
)
|
|
|
|
callback = ConversationCallback(
|
|
conversation_id=conversation_metadata.conversation_id
|
|
)
|
|
callback.set_processor(processor)
|
|
|
|
# Get the processor back
|
|
retrieved_processor = callback.get_processor()
|
|
|
|
assert isinstance(retrieved_processor, MockConversationCallbackProcessor)
|
|
assert retrieved_processor.name == 'test_processor'
|
|
assert retrieved_processor.config == {'key': 'value'}
|
|
|
|
def test_callback_status_enum(self):
|
|
"""Test the CallbackStatus enum."""
|
|
assert CallbackStatus.ACTIVE.value == 'ACTIVE'
|
|
assert CallbackStatus.COMPLETED.value == 'COMPLETED'
|
|
assert CallbackStatus.ERROR.value == 'ERROR'
|
|
|
|
def test_callback_foreign_key_constraint(
|
|
self, conversation_metadata, session_maker
|
|
):
|
|
"""Test that the foreign key constraint works."""
|
|
with session_maker() as session:
|
|
# This should work with valid conversation_id
|
|
callback = ConversationCallback(
|
|
conversation_id=conversation_metadata.conversation_id,
|
|
processor_type='test.Processor',
|
|
processor_json='{}',
|
|
)
|
|
session.add(callback)
|
|
session.commit()
|
|
|
|
# Cleanup
|
|
session.delete(callback)
|
|
session.commit()
|
|
|
|
# Note: SQLite doesn't enforce foreign key constraints by default in tests
|
|
# In a real PostgreSQL database, this would raise an integrity error
|
|
# For now, we just test that the callback can be created with valid data
|