"""Доступ к реальным данным из `../fixtures` (отдельный репозиторий, внутренний доступ).""" import os from pathlib import Path FIXTURES_ENV = "ARM_KA_FIXTURES" OSV_EXTENSIONS = frozenset({".xls", ".xlsx", ".xlsb"}) _SYNTHETIC = "synthetic" _OSV_DKB = "osv-dkb" # Golden-снимок делается для файла пакета ДКБ не больше 1 МиБ: у больших (Агрокомплекс, 9–14 МБ, ~50 тыс. строк) # снимок — десятки МБ JSON, для них остаются property-тесты инвариантов GOLDEN_MAX_BYTES = 1024 * 1024 _ANALYSIS_REPORTS = "analysis-reports" # Не фикстуры: скрытые файлы и каталоги («.DS_Store», «.cache/») и lock-файлы Excel («~$книга.xlsx», пока она открыта) _IGNORED_PREFIXES = (".", "~$") def _is_workbook(path: Path, base: Path) -> bool: """Файл книги Excel под `base`: расширение xls/xlsx/xlsb, ни файл, ни каталоги на пути — не скрытые и не lock.""" if not path.is_file() or path.suffix.lower() not in OSV_EXTENSIONS: return False return not any(part.startswith(_IGNORED_PREFIXES) for part in path.relative_to(base).parts) def resolve_fixtures_dir() -> Path: """Каталог фикстур: `ARM_KA_FIXTURES` или соседний с репозиторием `fixtures/` (workspace).""" override = os.environ.get(FIXTURES_ENV) if override: return Path(override) return Path(__file__).resolve().parents[3] / "fixtures" def fixtures_skip_reason() -> str | None: """Причина пропуска тестов на фикстурах (None — каталог доступен). Текст называет переменную и путь (OQ-81).""" root = resolve_fixtures_dir() if (root / "osv").is_dir(): return None return f"каталог фикстур не найден: {root} (задайте {FIXTURES_ENV}=<путь к репозиторию fixtures>)" def synthetic_skip_reason() -> str | None: """Причина пропуска тестов на синтетике: каталог fixtures есть, но `osv/synthetic` отсутствует или пуст (например, fixtures на main до слияния ветки с синтетикой). `None` — файлы есть или нет самих fixtures (тогда причину даёт `fixtures_skip_reason`).""" root = resolve_fixtures_dir() if not (root / "osv").is_dir() or synthetic_files(root): return None return f"нет синтетики: {root / 'osv' / _SYNTHETIC} отсутствует или пуст (сгенерируйте: см. README fixtures)" def osv_fixture_files(root: Path) -> list[Path]: """Все `*.xls/*.xlsx/*.xlsb` под `root/osv`, кроме каталога synthetic, скрытых и lock-файлов; отсортированы.""" osv = root / "osv" if not osv.is_dir(): return [] synthetic = osv / _SYNTHETIC return sorted(path for path in osv.rglob("*") if _is_workbook(path, osv) and synthetic not in path.parents) def osv_dkb_files(root: Path) -> list[Path]: """Файлы Excel пакета ДКБ `root/osv-dkb/**` (многолистовые книги, статусы `needs_check`), кроме отчётов «Анализ счета» (`analysis-reports/`: не ОСВ); отсортированы. Нет каталога — пусто.""" directory = root / _OSV_DKB if not directory.is_dir(): return [] reports = directory / _ANALYSIS_REPORTS found = (path for path in directory.rglob("*") if _is_workbook(path, directory)) return sorted(path for path in found if reports not in path.parents) def osv_dkb_golden_files(root: Path) -> list[Path]: """Файлы пакета ДКБ, для которых хранится golden-снимок: размер не больше `GOLDEN_MAX_BYTES`.""" return [path for path in osv_dkb_files(root) if path.stat().st_size <= GOLDEN_MAX_BYTES] def synthetic_files(root: Path) -> list[Path]: """Файлы Excel `root/osv/synthetic/*.xls|xlsx|xlsb` (скрытые, lock-файлы `~$…` и пояснения вроде `README.md` не считаются); отсортированы.""" directory = root / "osv" / _SYNTHETIC if not directory.is_dir(): return [] return sorted(path for path in directory.iterdir() if _is_workbook(path, directory))