"""Поиск каталога реальных данных `fixtures` (соседний репозиторий workspace) для тестов с маркером `fixtures`.""" import json import os import uuid from datetime import UTC, datetime from pathlib import Path from typing import TYPE_CHECKING from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession if TYPE_CHECKING: from src.contract.result import RecognitionResult from src.models.directories import LegalEntity from src.models.files import Document FIXTURES_ENV = "ARM_KA_FIXTURES" def resolve_fixtures_dir() -> Path: """Каталог fixtures: `ARM_KA_FIXTURES`, иначе соседний `../../fixtures` относительно `arm-core/api`. Не проверяет существование: это делает вызывающий код (пропуск теста с причиной). """ explicit = os.environ.get(FIXTURES_ENV) if explicit: return Path(explicit) return Path(__file__).resolve().parents[3] / "fixtures" def fixtures_skip_reason() -> str | None: """Причина пропуска тестов с маркером `fixtures` или `None`, если каталог доступен.""" path = resolve_fixtures_dir() if path.is_dir(): return None return ( f"ARM_KA_FIXTURES недоступен: нет каталога {path} " f"(задайте {FIXTURES_ENV} или положите fixtures рядом с arm-core)" ) def golden_result(path: Path, *, job_id: uuid.UUID, file_id: uuid.UUID) -> "RecognitionResult": """Golden-снимок парсера как `RecognitionResult`: подставляет `job_id`/`file_id` и версию сервиса.""" from src.contract.result import RecognitionResult # noqa: PLC0415 - тяжёлые импорты лениво: цикл с conftest data = json.loads(path.read_text("utf-8")) data["service"] = {"version": "golden", **data["service"]} return RecognitionResult.model_validate(data | {"job_id": str(job_id), "file_id": str(file_id)}) async def map_golden_snapshot( session: AsyncSession, *, entity: "LegalEntity", path: Path, uploaded_by: uuid.UUID | None ) -> "list[Document]": """Golden-снимок `path` → файл ЮЛ `entity` и его документы ОСВ в БД теста (`flush`, без commit). Как маппер: файл `PROCESSING` с текущим заданием `done`, затем боевой `map_file` с планом счетов seed и настоящим `CounterpartyIndex` (справочник читается на момент вызова — ЮЛ и алиасы должны быть созданы до маппинга). Возвращает документы файла (после автозамены прежних версий — только документы этого файла). """ # ленивые импорты: `conftest` импортирует этот модуль раньше `tests.factories` (цикл) from src.models.accounting import ChartOfAccount # noqa: PLC0415 from src.models.enums import FileStatus, RecognitionStatus # noqa: PLC0415 from src.models.files import Document # noqa: PLC0415 from src.repositories.counterparties import CounterpartyIndex # noqa: PLC0415 from src.services.result_mapping import map_file # noqa: PLC0415 from tests.factories import make_stored_file # noqa: PLC0415 file, job = await make_stored_file( session, entity=entity, status=FileStatus.PROCESSING, job_status=RecognitionStatus.done, original_name=path.name, uploaded_by=uploaded_by, ) chart = { code: account_id for code, account_id in await session.execute(select(ChartOfAccount.code, ChartOfAccount.id)) } index = await CounterpartyIndex.load(session) result = golden_result(path, job_id=job.id, file_id=file.id) await map_file(session, job=job, file=file, result=result, chart=chart, index=index, now=datetime.now(UTC)) documents = await session.execute( select(Document).where(Document.stored_file_id == file.id).order_by(Document.osv_account_code) ) return list(documents.scalars())