Листы-продолжения форм (STATEMENT_CONTINUATION) идут в конвейер форм, а не в разбор ОСВ (R-5). Refs #14 Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
74 lines
3.8 KiB
Python
74 lines
3.8 KiB
Python
"""Покрытие словарей `rules/*.yaml` (design D-10, ruling R-22): каждая запись распознавания срабатывает хотя бы раз на
|
||
реальных ОСВ и синтетике; мёртвый синоним — повод удалить его или добавить файл синтетики `s14_*`.
|
||
|
||
Запись — один `Pattern` словаря (`sheet_markers`, `statement_markers`, `groups`, `debit`, `credit`, `service_columns`,
|
||
`total_row`, `header_markers`, `mapping`, правила периодов, `year`, `end`, `header`/`row` типов данных,
|
||
`currency_aliases`). Перечень кодов `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, synthetic_files
|
||
from rules_coverage import DICTIONARIES, MatchCounter, iter_patterns, uncovered
|
||
|
||
from src.parser import get_parser
|
||
from src.parser.book import BookParser, SkippingStatementParser
|
||
from src.parser.osv import OsvParser
|
||
from src.parser.rules import DEFAULT_RULES_DIR, Rules, load_rules
|
||
|
||
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)
|
||
parser = BookParser(
|
||
rules,
|
||
osv=OsvParser(rules),
|
||
statements=SkippingStatementParser(),
|
||
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)]
|
||
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())
|
||
assert sum(counter.hits.values()) > len(entries)
|
||
assert files >= 72 # noqa: PLR2004 - реальные ОСВ из fixtures
|
||
assert not any(path.startswith("currency_codes") for _, path, _ in entries)
|