"""Механизм покрытия словарей (R-22) без каталога fixtures: обход записей, счётчик срабатываний, сообщение о мёртвых.""" import shutil from pathlib import Path import pytest from rules_coverage import DICTIONARIES, PATTERNLESS, MatchCounter, iter_patterns, uncovered from src.parser.rules import DEFAULT_RULES_DIR, Pattern, load_rules def test_every_pattern_of_every_dictionary_is_enumerated_with_file_and_key() -> None: rules = load_rules() entries = list(iter_patterns(rules)) assert {file_name for file_name, _, _ in entries} == set(DICTIONARIES.values()) - PATTERNLESS keys = {(file_name, path) for file_name, path, _ in entries} assert ("osv_headers.yaml", "sheet_markers[0]") in keys assert ("osv_headers.yaml", "groups.opening[0]") in keys assert ("osv_groups.yaml", "mapping[0][0]") in keys assert ("periods.yaml", "rules[0].pattern") in keys assert ("periods.yaml", "year") in keys assert ("data_types.yaml", "currency_aliases.RUB[0]") in keys # R-22: перечень кодов ISO — значения, не синонимы assert not any("currency_codes" in path for _, path, _ in entries) assert all(isinstance(pattern, Pattern) for _, _, pattern in entries) def test_counter_counts_matches_of_search_and_search_normalized_only(monkeypatch: pytest.MonkeyPatch) -> None: rules = load_rules() counter = MatchCounter() counter.install(monkeypatch) total = rules.headers.total_row[0] inn = rules.headers.service_columns["inn"][0] assert total.search("Итого") is not None assert inn.search_normalized("инн") is not None assert inn.search_normalized("нет такого") is None dead = uncovered(rules, counter) assert not any("total_row[0]" in line or "service_columns.inn[0]" in line for line in dead) assert any("sheet_markers[0]" in line for line in dead) def test_message_lists_file_key_and_pattern_of_a_dead_entry(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: rules_dir = tmp_path / "rules" shutil.copytree(DEFAULT_RULES_DIR, rules_dir) headers = rules_dir / "osv_headers.yaml" headers.write_text( headers.read_text(encoding="utf-8").replace( "total_row:\n - '^итого\\b'", "total_row:\n - '^итого\\b'\n - '^никогда не встречается$'" ), encoding="utf-8", ) rules = load_rules(rules_dir) counter = MatchCounter() counter.install(monkeypatch) assert rules.headers.total_row[0].search("Итого по счету") is not None assert rules.headers.total_row[1].search("Итого по счету") is None dead = uncovered(rules, counter) assert "osv_headers.yaml: total_row[1]: '^никогда не встречается$'" in dead assert not any("total_row[0]" in line for line in dead) def test_counter_does_not_change_matching_results(monkeypatch: pytest.MonkeyPatch) -> None: pattern = Pattern.compile(r"\bбу\b") before = [pattern.search("данные БУ"), pattern.search("нет")] MatchCounter().install(monkeypatch) after = [pattern.search("данные БУ"), pattern.search("нет")] assert [m.group(0) if m else None for m in after] == [m.group(0) if m else None for m in before]