AI-Trend-Scout/tests/bot/test_hottest_command.py
Artur Mukhamadiev 65fccbc614 feat(storage): implement hybrid search and fix async chroma i/o
- Add ADR 001 for Hybrid Search Architecture
- Implement Phase 1 (Exact Match) and Phase 2 (Semantic Fallback) in ChromaStore
- Wrap blocking ChromaDB calls in asyncio.to_thread
- Update IVectorStore interface to support category filtering and thresholds
- Add comprehensive tests for hybrid search logic
2026-03-16 00:11:07 +03:00

169 lines
5.4 KiB
Python

import uuid
import pytest
from unittest.mock import AsyncMock, MagicMock
from aiogram.types import Message, InlineKeyboardMarkup
from aiogram.filters import CommandObject
from datetime import datetime
from src.bot.handlers import get_router
from src.processor.dto import EnrichedNewsItemDTO
@pytest.fixture
def mock_storage():
return AsyncMock()
@pytest.fixture
def mock_processor():
processor = MagicMock()
processor.get_info.return_value = {"model": "test-model"}
return processor
@pytest.fixture
def allowed_chat_id():
return "123456789"
@pytest.fixture
def router(mock_storage, mock_processor, allowed_chat_id):
return get_router(mock_storage, mock_processor, allowed_chat_id)
def get_handler(router, callback_name):
for handler in router.message.handlers:
if handler.callback.__name__ == callback_name:
return handler.callback
raise ValueError(f"Handler {callback_name} not found")
@pytest.mark.asyncio
async def test_command_hottest_handler_success(router, mock_storage, allowed_chat_id):
"""
Test that /hottest command calls get_top_ranked and returns a list of items.
"""
# 1. Arrange
handler = get_handler(router, "command_hottest_handler")
message = AsyncMock()
message.chat = MagicMock()
message.chat.id = int(allowed_chat_id)
message.answer = AsyncMock()
mock_items = [
EnrichedNewsItemDTO(
title=f"Hot News {i}",
url=f"https://example.com/{i}",
content_text=f"Content {i}",
source="Source",
timestamp=datetime.now(),
relevance_score=10-i,
summary_ru=f"Сводка {i}",
anomalies_detected=[],
category="Tech"
) for i in range(3)
]
mock_storage.get_top_ranked.return_value = mock_items
# 2. Act
command = CommandObject(prefix='/', command='hottest', args=None)
await handler(message=message, command=command)
# 3. Assert
mock_storage.get_top_ranked.assert_called_once_with(limit=10, category=None)
message.answer.assert_called_once()
args, kwargs = message.answer.call_args
assert "Top 3 Hottest Trends:" in args[0]
assert "reply_markup" in kwargs
assert isinstance(kwargs["reply_markup"], InlineKeyboardMarkup)
# Check if all 3 items are in the markup
markup = kwargs["reply_markup"]
assert len(markup.inline_keyboard) == 3
# Check if icons and scores are present
button_text = markup.inline_keyboard[0][0].text
assert "🔥" in button_text
assert "[10/10]" in button_text
assert "Hot News 0" in button_text
@pytest.mark.asyncio
async def test_command_hottest_handler_empty(router, mock_storage, allowed_chat_id):
"""
Test that /hottest command handles empty results correctly.
"""
# 1. Arrange
handler = get_handler(router, "command_hottest_handler")
message = AsyncMock()
message.chat = MagicMock()
message.chat.id = int(allowed_chat_id)
message.answer = AsyncMock()
mock_storage.get_top_ranked.return_value = []
# 2. Act
command = CommandObject(prefix='/', command='hottest', args=None)
await handler(message=message, command=command)
# 3. Assert
mock_storage.get_top_ranked.assert_called_once_with(limit=10, category=None)
message.answer.assert_called_once_with("No hot trends found yet.")
@pytest.mark.asyncio
async def test_command_hottest_handler_custom_limit(router, mock_storage, allowed_chat_id):
"""
Test that /hottest command with custom limit correctly passes it to storage.
"""
# 1. Arrange
handler = get_handler(router, "command_hottest_handler")
message = AsyncMock()
message.chat = MagicMock()
message.chat.id = int(allowed_chat_id)
message.answer = AsyncMock()
mock_storage.get_top_ranked.return_value = []
# 2. Act
command = CommandObject(prefix='/', command='hottest', args='25')
await handler(message=message, command=command)
# 3. Assert
mock_storage.get_top_ranked.assert_called_once_with(limit=25, category=None)
@pytest.mark.asyncio
async def test_command_hottest_handler_max_limit(router, mock_storage, allowed_chat_id):
"""
Test that /hottest command enforces maximum limit.
"""
# 1. Arrange
handler = get_handler(router, "command_hottest_handler")
message = AsyncMock()
message.chat = MagicMock()
message.chat.id = int(allowed_chat_id)
message.answer = AsyncMock()
mock_storage.get_top_ranked.return_value = []
# 2. Act
command = CommandObject(prefix='/', command='hottest', args='1000')
await handler(message=message, command=command)
# 3. Assert
mock_storage.get_top_ranked.assert_called_once_with(limit=50, category=None)
@pytest.mark.asyncio
async def test_command_hottest_handler_invalid_limit(router, mock_storage, allowed_chat_id):
"""
Test that /hottest command handles invalid limit by falling back to default.
"""
# 1. Arrange
handler = get_handler(router, "command_hottest_handler")
message = AsyncMock()
message.chat = MagicMock()
message.chat.id = int(allowed_chat_id)
message.answer = AsyncMock()
mock_storage.get_top_ranked.return_value = []
# 2. Act
command = CommandObject(prefix='/', command='hottest', args='invalid')
await handler(message=message, command=command)
# 3. Assert
mock_storage.get_top_ranked.assert_called_once_with(limit=10, category='invalid')