arm-tabular-parser/api/tests/fixtures/test_rules_coverage.py
Andrey Epifancev be5ad8ce86 refactor: удалён собственный конвейер форм и словари форм — шаги 2–8 в arm_statements (#54)
Удалены модули statements/{checks,columns,detect,header,lines,merge,statement,legacy_models}.py, словари
rules/statement_{headers,periods,checks}.yaml и их тесты. Словари форм и шаги 2–8 — в пакете arm_statements.
Rules без полей statement_*; покрытие словарей ОСВ (DICTIONARIES) без словарей форм. Документация сервиса
переписана под адаптер форм, контракт 2.0 и коды OSV_*.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
2026-10-18 21:53:29 +04:00

91 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Покрытие словарей `rules/*.yaml` (design D-10, ruling R-22): каждая запись распознавания срабатывает хотя бы раз на
реальных ОСВ и синтетике; мёртвый синоним — повод удалить его или добавить файл синтетики `s14_*`.
Запись — один `Pattern` словаря ОСВ (`sheet_markers`, `groups`, `debit`, `credit`, `service_columns`,
`total_row`, `header_markers`, `mapping`, правила периодов, `year`, `end`, `header`/`row` типов данных,
`currency_aliases`). Словари форм ББ/ОФР покрываются тестами пакета `arm_statements`, не этим.
Перечень кодов `currency_codes` — список значений, не синонимы: в покрытие не входит. Записи находятся обходом
моделей `Rules`, поэтому новый паттерн в любом словаре попадает под проверку автоматически.
Файлы — реальные ОСВ `osv/**`, пакет ДКБ `osv-dkb/**` (диапазон месяцев, «Итого развернутое», шапка без меток) и
синтетика.
«Срабатывание» — `search`/`search_normalized` вернул совпадение. Счётчик — обёртка вокруг методов `Pattern` в тесте,
боевой код не меняется.
"""
from pathlib import Path
import pytest
from fixtures_helpers import (
osv_dkb_files,
osv_fixture_files,
statement_files,
statement_synthetic_files,
synthetic_files,
)
from rules_coverage import DICTIONARIES, PATTERNLESS, MatchCounter, iter_patterns, uncovered
from arm_statements import default_rules
from src.parser import get_parser
from src.parser.book import BookParser
from src.parser.osv import OsvParser
from src.parser.rules import DEFAULT_RULES_DIR, Rules, load_rules
from src.parser.statements.pipeline import StatementPipeline
pytestmark = pytest.mark.fixtures
@pytest.fixture(scope="module")
def coverage(fixtures_dir: Path) -> tuple[Rules, MatchCounter, int]:
monkeypatch = pytest.MonkeyPatch()
try:
counter = MatchCounter()
counter.install(monkeypatch)
rules = load_rules(DEFAULT_RULES_DIR)
base = get_parser()
assert isinstance(base, BookParser)
statement_rules = default_rules()
parser = BookParser(
rules,
osv=OsvParser(rules),
statements=StatementPipeline(statement_rules),
statement_rules=statement_rules,
max_bytes=base.max_bytes,
timeout_seconds=base.timeout_seconds,
)
files = [
*osv_fixture_files(fixtures_dir),
*osv_dkb_files(fixtures_dir),
*synthetic_files(fixtures_dir),
*statement_files(fixtures_dir),
*statement_synthetic_files(fixtures_dir),
]
for file in files:
parser.parse(file.read_bytes(), file.suffix.lstrip(".").upper()) # type: ignore[arg-type]
return rules, counter, len(files)
finally:
monkeypatch.undo()
def test_every_dictionary_entry_fires_on_fixtures_and_synthetic(coverage: tuple[Rules, MatchCounter, int]) -> None:
rules, counter, files = coverage
dead = uncovered(rules, counter)
assert dead == [], (
f"записи словарей, не сработавшие ни на одном из {files} файлов (удалите синоним или добавьте синтетику "
"s14_*):\n" + "\n".join(dead)
)
def test_coverage_sees_every_dictionary_entry_and_all_files(coverage: tuple[Rules, MatchCounter, int]) -> None:
"""Счётчик не пуст и обход видит записи всех пяти словарей ОСВ (защита от «покрытия» на пустом месте)."""
rules, counter, files = coverage
entries = list(iter_patterns(rules))
assert {file_name for file_name, _, _ in entries} == set(DICTIONARIES.values()) - PATTERNLESS
assert sum(counter.hits.values()) > len(entries)
assert files >= 95 # noqa: PLR2004 - реальные ОСВ, формы и синтетика из fixtures
assert not any(path.startswith("currency_codes") for _, path, _ in entries)