mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
233 lines
8.7 KiB
Python
233 lines
8.7 KiB
Python
"""
|
|
Tests for the GitlabCallbackProcessor.
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
from integrations.gitlab.gitlab_view import GitlabIssueComment
|
|
from integrations.types import UserData
|
|
from server.conversation_callback_processor.gitlab_callback_processor import (
|
|
GitlabCallbackProcessor,
|
|
)
|
|
from storage.conversation_callback import CallbackStatus, ConversationCallback
|
|
|
|
from openhands.core.schema.agent import AgentState
|
|
from openhands.events.observation.agent import AgentStateChangedObservation
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_gitlab_view():
|
|
"""Create a mock GitlabViewType for testing."""
|
|
# Use a simple dict that matches GitlabIssue structure
|
|
return GitlabIssueComment(
|
|
installation_id='test_installation',
|
|
issue_number=789,
|
|
project_id=456,
|
|
full_repo_name='test/repo',
|
|
is_public_repo=True,
|
|
user_info=UserData(
|
|
user_id=123, username='test_user', keycloak_user_id='test_keycloak_id'
|
|
),
|
|
raw_payload={'source': 'gitlab', 'message': {'test': 'data'}},
|
|
conversation_id='test_conversation',
|
|
should_extract=True,
|
|
send_summary_instruction=True,
|
|
title='',
|
|
description='',
|
|
previous_comments=[],
|
|
is_mr=False,
|
|
comment_body='sdfs',
|
|
discussion_id='test_discussion',
|
|
confidential=False,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def gitlab_callback_processor(mock_gitlab_view):
|
|
"""Create a GitlabCallbackProcessor instance for testing."""
|
|
return GitlabCallbackProcessor(
|
|
gitlab_view=mock_gitlab_view,
|
|
send_summary_instruction=True,
|
|
)
|
|
|
|
|
|
class TestGitlabCallbackProcessor:
|
|
"""Test the GitlabCallbackProcessor class."""
|
|
|
|
def test_model_validation(self, mock_gitlab_view):
|
|
"""Test the model validation of GitlabCallbackProcessor."""
|
|
# Test with all required fields
|
|
processor = GitlabCallbackProcessor(
|
|
gitlab_view=mock_gitlab_view,
|
|
)
|
|
# Check that gitlab_view was converted to a GitlabIssue object
|
|
assert hasattr(processor.gitlab_view, 'issue_number')
|
|
assert processor.gitlab_view.issue_number == 789
|
|
assert processor.gitlab_view.full_repo_name == 'test/repo'
|
|
assert processor.send_summary_instruction is True
|
|
|
|
# Test with custom send_summary_instruction
|
|
processor = GitlabCallbackProcessor(
|
|
gitlab_view=mock_gitlab_view,
|
|
send_summary_instruction=False,
|
|
)
|
|
assert hasattr(processor.gitlab_view, 'issue_number')
|
|
assert processor.gitlab_view.issue_number == 789
|
|
assert processor.send_summary_instruction is False
|
|
|
|
def test_serialization(self, mock_gitlab_view):
|
|
"""Test serialization and deserialization of GitlabCallbackProcessor."""
|
|
original_processor = GitlabCallbackProcessor(
|
|
gitlab_view=mock_gitlab_view,
|
|
send_summary_instruction=True,
|
|
)
|
|
|
|
# Serialize to JSON
|
|
json_data = original_processor.model_dump_json()
|
|
assert isinstance(json_data, str)
|
|
|
|
# Deserialize from JSON
|
|
deserialized_processor = GitlabCallbackProcessor.model_validate_json(json_data)
|
|
assert (
|
|
deserialized_processor.send_summary_instruction
|
|
== original_processor.send_summary_instruction
|
|
)
|
|
assert (
|
|
deserialized_processor.gitlab_view.issue_number
|
|
== original_processor.gitlab_view.issue_number
|
|
)
|
|
|
|
assert isinstance(
|
|
deserialized_processor.gitlab_view.issue_number,
|
|
type(original_processor.gitlab_view.issue_number),
|
|
)
|
|
# Note: gitlab_view will be serialized as a dict, so we can't directly compare objects
|
|
|
|
@pytest.mark.asyncio
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.get_summary_instruction'
|
|
)
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.conversation_manager'
|
|
)
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.session_maker'
|
|
)
|
|
async def test_call_with_send_summary_instruction(
|
|
self,
|
|
mock_session_maker,
|
|
mock_conversation_manager,
|
|
mock_get_summary_instruction,
|
|
gitlab_callback_processor,
|
|
):
|
|
"""Test the __call__ method when send_summary_instruction is True."""
|
|
# Setup mocks
|
|
mock_session = MagicMock()
|
|
mock_session_maker.return_value.__enter__.return_value = mock_session
|
|
mock_conversation_manager.send_event_to_conversation = AsyncMock()
|
|
mock_get_summary_instruction.return_value = (
|
|
"I'm a man of few words. Any questions?"
|
|
)
|
|
|
|
# Create a callback and observation
|
|
callback = ConversationCallback(
|
|
conversation_id='conv123',
|
|
status=CallbackStatus.ACTIVE,
|
|
processor_type=f'{GitlabCallbackProcessor.__module__}.{GitlabCallbackProcessor.__name__}',
|
|
processor_json=gitlab_callback_processor.model_dump_json(),
|
|
)
|
|
observation = AgentStateChangedObservation(
|
|
content='', agent_state=AgentState.AWAITING_USER_INPUT
|
|
)
|
|
|
|
# Call the processor
|
|
await gitlab_callback_processor(callback, observation)
|
|
|
|
# Verify that send_event_to_conversation was called
|
|
mock_conversation_manager.send_event_to_conversation.assert_called_once()
|
|
|
|
# Verify that the processor state was updated
|
|
assert gitlab_callback_processor.send_summary_instruction is False
|
|
mock_session.merge.assert_called_once_with(callback)
|
|
mock_session.commit.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.conversation_manager'
|
|
)
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.extract_summary_from_conversation_manager'
|
|
)
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.asyncio.create_task'
|
|
)
|
|
@patch(
|
|
'server.conversation_callback_processor.gitlab_callback_processor.session_maker'
|
|
)
|
|
async def test_call_with_extract_summary(
|
|
self,
|
|
mock_session_maker,
|
|
mock_create_task,
|
|
mock_extract_summary,
|
|
mock_conversation_manager,
|
|
gitlab_callback_processor,
|
|
):
|
|
"""Test the __call__ method when send_summary_instruction is False."""
|
|
# Setup mocks
|
|
mock_session = MagicMock()
|
|
mock_session_maker.return_value.__enter__.return_value = mock_session
|
|
mock_extract_summary.return_value = 'Test summary'
|
|
# Ensure we don't leak an un-awaited coroutine when create_task is mocked
|
|
mock_create_task.side_effect = lambda coro: (coro.close(), None)[1]
|
|
|
|
# Set send_summary_instruction to False
|
|
gitlab_callback_processor.send_summary_instruction = False
|
|
|
|
# Create a callback and observation
|
|
callback = ConversationCallback(
|
|
conversation_id='conv123',
|
|
status=CallbackStatus.ACTIVE,
|
|
processor_type=f'{GitlabCallbackProcessor.__module__}.{GitlabCallbackProcessor.__name__}',
|
|
processor_json=gitlab_callback_processor.model_dump_json(),
|
|
)
|
|
observation = AgentStateChangedObservation(
|
|
content='', agent_state=AgentState.FINISHED
|
|
)
|
|
|
|
# Call the processor
|
|
await gitlab_callback_processor(callback, observation)
|
|
|
|
# Verify that extract_summary_from_conversation_manager was called
|
|
mock_extract_summary.assert_called_once_with(
|
|
mock_conversation_manager, 'conv123'
|
|
)
|
|
|
|
# Verify that create_task was called to send the message
|
|
mock_create_task.assert_called_once()
|
|
|
|
# Verify that the callback status was updated
|
|
assert callback.status == CallbackStatus.COMPLETED
|
|
mock_session.merge.assert_called_once_with(callback)
|
|
mock_session.commit.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_call_with_non_terminal_state(self, gitlab_callback_processor):
|
|
"""Test the __call__ method with a non-terminal agent state."""
|
|
# Create a callback and observation with a non-terminal state
|
|
callback = ConversationCallback(
|
|
conversation_id='conv123',
|
|
status=CallbackStatus.ACTIVE,
|
|
processor_type=f'{GitlabCallbackProcessor.__module__}.{GitlabCallbackProcessor.__name__}',
|
|
processor_json=gitlab_callback_processor.model_dump_json(),
|
|
)
|
|
observation = AgentStateChangedObservation(
|
|
content='', agent_state=AgentState.RUNNING
|
|
)
|
|
|
|
# Call the processor
|
|
await gitlab_callback_processor(callback, observation)
|
|
|
|
# Verify that nothing happened (early return)
|
|
assert gitlab_callback_processor.send_summary_instruction is True
|