import asyncio import tempfile from collections.abc import Generator from pathlib import Path from unittest.mock import AsyncMock, MagicMock import pytest from openai.types.chat import ChatCompletion, ChatCompletionMessage from openai.types.chat.chat_completion import Choice from src.models import AppConfig, Article, ArticleCreate, ProcessingStatus @pytest.fixture(scope="session") def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]: """Создать event loop для всей сессии тестов.""" loop = asyncio.new_event_loop() yield loop loop.close() @pytest.fixture def test_config() -> AppConfig: """Тестовая конфигурация.""" with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" return AppConfig( openai_api_key="test_key", openai_model="gpt-4o-mini", db_path=str(db_path), max_concurrent_llm=2, openai_rpm=10, max_concurrent_wiki=5, prompt_template_path="src/prompt.txt", log_level="DEBUG", ) @pytest.fixture def sample_wiki_urls() -> list[str]: """Список тестовых URL википедии.""" return [ "https://ru.wikipedia.org/wiki/Тест", "https://ru.wikipedia.org/wiki/Пример", "https://ru.wikipedia.org/wiki/Образец", ] @pytest.fixture def invalid_urls() -> list[str]: """Список невалидных URL.""" return [ "https://example.com/invalid", "https://en.wikipedia.org/wiki/English", "not_a_url", "", "https://ru.wikipedia.org/wiki/", ] @pytest.fixture def sample_wikitext() -> str: return """'''Тест''' — это проверка чего-либо. == Определение == Тест может проводиться для различных целей: * Проверка знаний * Проверка работоспособности * Проверка качества == История == Тесты использовались с древних времён. {{навигация|тема=Тестирование}} [[Категория:Тестирование]]""" @pytest.fixture def simplified_text() -> str: return """'''Тест''' — это проверка чего-либо для школьников. == Что такое тест == Тест помогает проверить: * Знания учеников * Как работают устройства * Качество продуктов == Когда появились тесты == Люди проверяли друг друга очень давно. ###END###""" @pytest.fixture def sample_article_data() -> ArticleCreate: return ArticleCreate( url="https://ru.wikipedia.org/wiki/Тест", title="Тест", raw_text="Тестовый wiki-текст", ) @pytest.fixture def sample_article(sample_article_data: ArticleCreate) -> Article: return Article( id=1, url=sample_article_data.url, title=sample_article_data.title, raw_text=sample_article_data.raw_text, status=ProcessingStatus.PENDING, ) @pytest.fixture def completed_article(sample_article: Article, simplified_text: str) -> Article: article = sample_article.model_copy() article.mark_completed( simplified_text=simplified_text, token_count_raw=100, token_count_simplified=50, processing_time=2.5, ) return article @pytest.fixture def mock_openai_response() -> ChatCompletion: return ChatCompletion( id="test_completion", object="chat.completion", created=1234567890, model="gpt-4o-mini", choices=[ Choice( index=0, message=ChatCompletionMessage( role="assistant", content="Упрощённый текст для школьников.\n\n###END###", ), finish_reason="stop", ) ], usage=None, ) @pytest.fixture def temp_input_file(sample_wiki_urls: list[str]) -> Generator[str, None, None]: with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f: for url in sample_wiki_urls: f.write(f"{url}\n") f.write("# Комментарий\n") f.write("\n") f.write("https://ru.wikipedia.org/wiki/Дубликат\n") f.write("https://ru.wikipedia.org/wiki/Дубликат\n") temp_path = f.name yield temp_path Path(temp_path).unlink(missing_ok=True) @pytest.fixture async def mock_wiki_client() -> AsyncMock: mock_client = AsyncMock() mock_page = MagicMock() mock_page.exists = True mock_page.redirect = False mock_page.text.return_value = "Тестовый wiki-текст" mock_client.pages = {"Тест": mock_page} return mock_client @pytest.fixture async def mock_openai_client() -> AsyncMock: mock_client = AsyncMock() return mock_client