| | import json
|
| | from contextlib import contextmanager
|
| | from datetime import datetime
|
| | from unittest.mock import MagicMock, patch
|
| |
|
| | import pytest
|
| |
|
| | from openhands.server.routes.manage_conversations import (
|
| | delete_conversation,
|
| | get_conversation,
|
| | search_conversations,
|
| | update_conversation,
|
| | )
|
| | from openhands.storage.data_models.conversation_info import ConversationInfo
|
| | from openhands.storage.data_models.conversation_info_result_set import (
|
| | ConversationInfoResultSet,
|
| | )
|
| | from openhands.storage.data_models.conversation_status import ConversationStatus
|
| | from openhands.storage.memory import InMemoryFileStore
|
| |
|
| |
|
| | @contextmanager
|
| | def _patch_store():
|
| | file_store = InMemoryFileStore()
|
| | file_store.write(
|
| | 'sessions/some_conversation_id/metadata.json',
|
| | json.dumps(
|
| | {
|
| | 'title': 'Some Conversation',
|
| | 'selected_repository': 'foobar',
|
| | 'conversation_id': 'some_conversation_id',
|
| | 'github_user_id': '12345',
|
| | 'created_at': '2025-01-01T00:00:00',
|
| | 'last_updated_at': '2025-01-01T00:01:00',
|
| | }
|
| | ),
|
| | )
|
| | with patch(
|
| | 'openhands.storage.conversation.file_conversation_store.get_file_store',
|
| | MagicMock(return_value=file_store),
|
| | ):
|
| | with patch(
|
| | 'openhands.server.routes.manage_conversations.session_manager.file_store',
|
| | file_store,
|
| | ):
|
| | yield
|
| |
|
| |
|
| | @pytest.mark.asyncio
|
| | async def test_search_conversations():
|
| | with _patch_store():
|
| | result_set = await search_conversations(
|
| | MagicMock(state=MagicMock(github_token=''))
|
| | )
|
| | expected = ConversationInfoResultSet(
|
| | results=[
|
| | ConversationInfo(
|
| | conversation_id='some_conversation_id',
|
| | title='Some Conversation',
|
| | created_at=datetime.fromisoformat('2025-01-01T00:00:00'),
|
| | last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00'),
|
| | status=ConversationStatus.STOPPED,
|
| | selected_repository='foobar',
|
| | )
|
| | ]
|
| | )
|
| | assert result_set == expected
|
| |
|
| |
|
| | @pytest.mark.asyncio
|
| | async def test_get_conversation():
|
| | with _patch_store():
|
| | conversation = await get_conversation(
|
| | 'some_conversation_id', MagicMock(state=MagicMock(github_token=''))
|
| | )
|
| | expected = ConversationInfo(
|
| | conversation_id='some_conversation_id',
|
| | title='Some Conversation',
|
| | created_at=datetime.fromisoformat('2025-01-01T00:00:00'),
|
| | last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00'),
|
| | status=ConversationStatus.STOPPED,
|
| | selected_repository='foobar',
|
| | )
|
| | assert conversation == expected
|
| |
|
| |
|
| | @pytest.mark.asyncio
|
| | async def test_get_missing_conversation():
|
| | with _patch_store():
|
| | assert (
|
| | await get_conversation(
|
| | 'no_such_conversation', MagicMock(state=MagicMock(github_token=''))
|
| | )
|
| | is None
|
| | )
|
| |
|
| |
|
| | @pytest.mark.asyncio
|
| | async def test_update_conversation():
|
| | with _patch_store():
|
| | await update_conversation(
|
| | MagicMock(state=MagicMock(github_token='')),
|
| | 'some_conversation_id',
|
| | 'New Title',
|
| | )
|
| | conversation = await get_conversation(
|
| | 'some_conversation_id', MagicMock(state=MagicMock(github_token=''))
|
| | )
|
| | expected = ConversationInfo(
|
| | conversation_id='some_conversation_id',
|
| | title='New Title',
|
| | created_at=datetime.fromisoformat('2025-01-01T00:00:00'),
|
| | last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00'),
|
| | status=ConversationStatus.STOPPED,
|
| | selected_repository='foobar',
|
| | )
|
| | assert conversation == expected
|
| |
|
| |
|
| | @pytest.mark.asyncio
|
| | async def test_delete_conversation():
|
| | with _patch_store():
|
| | await delete_conversation(
|
| | 'some_conversation_id',
|
| | MagicMock(state=MagicMock(github_token='')),
|
| | )
|
| | conversation = await get_conversation(
|
| | 'some_conversation_id', MagicMock(state=MagicMock(github_token=''))
|
| | )
|
| | assert conversation is None
|
| |
|