ruwiki-test/tests/conftest.py

191 lines
5.6 KiB
Python
Raw Permalink Normal View History

2025-07-11 21:28:58 +02:00
import asyncio
import tempfile
from collections.abc import Generator
2025-07-12 10:43:02 +02:00
from datetime import datetime, timezone
2025-07-11 21:28:58 +02:00
from pathlib import Path
2025-07-12 10:43:02 +02:00
from typing import AsyncGenerator
from unittest.mock import MagicMock
2025-07-11 21:28:58 +02:00
import pytest
2025-07-12 10:43:02 +02:00
import pytest_asyncio
import structlog
import logging
from src.models import AppConfig
from src.models.article_dto import ArticleDTO, ArticleStatus
from src.services import ArticleRepository, DatabaseService
def level_to_int(logger, method_name, event_dict):
if isinstance(event_dict.get("level"), str):
try:
event_dict["level"] = getattr(logging, event_dict["level"].upper())
except Exception:
pass
return event_dict
2025-07-12 10:47:31 +02:00
2025-07-12 10:43:02 +02:00
@pytest.fixture(autouse=True, scope="session")
def configure_structlog():
import tenacity
2025-07-12 10:47:31 +02:00
2025-07-12 10:43:02 +02:00
logging.basicConfig(level=logging.DEBUG)
structlog.configure(
processors=[
level_to_int,
structlog.processors.TimeStamper(fmt="iso"),
2025-07-12 10:47:31 +02:00
structlog.dev.ConsoleRenderer(),
2025-07-12 10:43:02 +02:00
],
2025-07-12 10:47:31 +02:00
wrapper_class=structlog.make_filtering_bound_logger(logging.DEBUG),
2025-07-12 10:43:02 +02:00
)
tenacity.logger = structlog.get_logger("tenacity")
@pytest.fixture(autouse=True, scope="session")
def patch_tenacity_before_sleep_log():
import logging
import tenacity
from tenacity.before_sleep import before_sleep_log
original_before_sleep_log = tenacity.before_sleep_log
2025-07-11 21:28:58 +02:00
2025-07-12 10:43:02 +02:00
def patched_before_sleep_log(logger, log_level):
if isinstance(log_level, str):
log_level = getattr(logging, log_level.upper(), logging.WARNING)
return original_before_sleep_log(logger, log_level)
tenacity.before_sleep_log = patched_before_sleep_log
2025-07-11 21:28:58 +02:00
@pytest.fixture(scope="session")
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
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
2025-07-12 10:43:02 +02:00
def mock_openai_response():
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = "Упрощённый текст для школьников"
mock_response.usage.prompt_tokens = 100
mock_response.usage.completion_tokens = 50
mock_response.__await__ = lambda: iter([mock_response])
return mock_response
@pytest_asyncio.fixture
async def database_service(test_config: AppConfig) -> AsyncGenerator[DatabaseService, None]:
service = DatabaseService(test_config)
await service.initialize_database()
yield service
@pytest_asyncio.fixture
async def repository(database_service: DatabaseService) -> AsyncGenerator[ArticleRepository, None]:
repo = ArticleRepository(database_service)
yield repo
2025-07-11 21:28:58 +02:00
@pytest.fixture
2025-07-12 10:43:02 +02:00
def sample_wiki_urls() -> list[str]:
2025-07-11 21:28:58 +02:00
return [
2025-07-12 10:43:02 +02:00
"https://ru.ruwiki.ru/wiki/Тест",
"https://ru.ruwiki.ru/wiki/Пример",
"https://ru.ruwiki.ru/wiki/Образец",
2025-07-11 21:28:58 +02:00
]
@pytest.fixture
def sample_wikitext() -> str:
return """'''Тест''' — это проверка чего-либо.
== Определение ==
Тест может проводиться для различных целей:
* Проверка знаний
* Проверка работоспособности
* Проверка качества
== История ==
2025-07-12 10:43:02 +02:00
Тесты использовались с древних времён."""
2025-07-11 21:28:58 +02:00
@pytest.fixture
def simplified_text() -> str:
2025-07-12 10:43:02 +02:00
return """Тест — это проверка чего-либо для школьников.
2025-07-11 21:28:58 +02:00
2025-07-12 10:43:02 +02:00
Что такое тест
2025-07-11 21:28:58 +02:00
Тест помогает проверить:
* Знания учеников
* Как работают устройства
* Качество продуктов
2025-07-12 10:43:02 +02:00
Когда появились тесты
Люди проверяли друг друга очень давно."""
2025-07-11 21:28:58 +02:00
@pytest.fixture
2025-07-12 10:43:02 +02:00
def sample_article_dto() -> ArticleDTO:
return ArticleDTO(
url="https://ru.ruwiki.ru/wiki/Тест",
2025-07-11 21:28:58 +02:00
title="Тест",
raw_text="Тестовый wiki-текст",
2025-07-12 10:43:02 +02:00
status=ArticleStatus.PENDING,
created_at=datetime.now(timezone.utc),
2025-07-11 21:28:58 +02:00
)
2025-07-12 10:43:02 +02:00
@pytest_asyncio.fixture
async def sample_article_in_db(
repository: ArticleRepository, sample_article_dto: ArticleDTO
) -> AsyncGenerator[ArticleDTO, None]:
article = await repository.create_article(
url=sample_article_dto.url,
title=sample_article_dto.title,
raw_text=sample_article_dto.raw_text,
2025-07-11 21:28:58 +02:00
)
2025-07-12 10:43:02 +02:00
yield article
2025-07-11 21:28:58 +02:00
@pytest.fixture
def temp_input_file(sample_wiki_urls: list[str]) -> Generator[str, None, None]:
2025-07-12 10:43:02 +02:00
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
2025-07-11 21:28:58 +02:00
for url in sample_wiki_urls:
f.write(f"{url}\n")
temp_path = f.name
yield temp_path
Path(temp_path).unlink(missing_ok=True)
2025-07-12 10:43:02 +02:00
@pytest_asyncio.fixture
async def multiple_articles_in_db(
repository: ArticleRepository, sample_wiki_urls: list[str]
) -> AsyncGenerator[list[ArticleDTO], None]:
articles = []
for i, url in enumerate(sample_wiki_urls):
article = await repository.create_article(
url=url,
title=f"Test Article {i+1}",
raw_text=f"Content for article {i+1}",
)
articles.append(article)
yield articles