zapretgui/tests/test_report_contract.py
loop-uh 245ccca26e
All checks were successful
Zapret source guards / validate (push) Successful in 14s
BlockCheck: договор об отчёте с тестами-сторожами, карточка «Ход проверки» и время шагов на экране
Отчёт — единственный источник истины, но новое поле можно было положить в
него и забыть вывести: так было с временем шагов, условиями проверки и
сводкой по списку реестра РКН.

- общий модуль diagnostics.report_contract: у каждого поля отчёта одно из трёх
  мест — карточки, панель итога или «служебное» с объяснением;
- тесты tests/test_report_contract.py на обе стороны: движок и рабочий поток
  не кладут в отчёт поле мимо списка, а каждое показываемое поле на образце
  отчёта действительно меняет карточки (без него они получаются другими);
- сторож сразу нашёл пробел — сводка списка реестра не показывалась;
- новая карточка «Ход проверки»: время по шагам, условия проверки (Windows,
  DNS системы, запущен ли Zapret, другие программы обхода) и состояние списка
  реестра РКН;
- время шага видно и по ходу проверки, в его строке: «45 / 45 · 23 с»,
  «готово · 9 с»; секунды берутся из отчёта (step_seconds теперь в каждой
  публикации), а не считаются экраном;
- движок снова меньше 1000 строк: печать разделов текста и перевод находок
  DNS-серверов в строки итога вынесены в sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:52:25 +03:00

83 lines
4.3 KiB
Python
Raw Permalink 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.

"""Отчёт BlockCheck и экран не расходятся: у каждого поля отчёта есть место.
Список полей — в ``diagnostics.report_contract``. Здесь две проверки:
- движок не кладёт в отчёт поле мимо списка (новое поле нельзя добавить молча);
- каждое показываемое поле действительно меняет экран: без него карточки
получаются другими, а поля панели итога названы в её коде.
"""
from __future__ import annotations
import json
import unittest
from pathlib import Path
from test_diagnostics_verdict import _Net
from blockcheck.ui.result_cards_model import build_cards
from diagnostics import engine
from diagnostics.report_contract import CARD_FIELDS, SERVICE_FIELDS, SUMMARY_FIELDS, declared_fields
ROOT = Path(__file__).resolve().parents[1]
SAMPLE = ROOT / "tests" / "fixtures" / "blockcheck_report_sample.json"
# Что читает панель итога и страница.
SUMMARY_SOURCES = ("src/blockcheck/ui/check_results.py", "src/blockcheck/ui/page.py", "src/diagnostics/history.py")
# Что рабочий поток проверки дописывает к отчёту движка.
WORKER_SOURCE = "src/blockcheck/worker.py"
def _sample() -> dict:
return json.loads(SAMPLE.read_text("utf-8"))
class ReportContractTests(unittest.TestCase):
def test_every_field_has_exactly_one_place(self) -> None:
places = [*CARD_FIELDS, *SUMMARY_FIELDS, *SERVICE_FIELDS]
self.assertEqual(sorted(places), sorted(set(places)), "поле названо в двух местах")
self.assertTrue(all(reason.strip() for reason in SERVICE_FIELDS.values()), "у служебного поля нет объяснения")
def test_engine_report_has_no_field_outside_the_contract(self) -> None:
for scope in ("main", "full"):
partials: list[dict] = []
report = _Net().run(engine.run_blockcheck, scope, emit=lambda _line: None, partial=partials.append)
for item in (report, *partials):
self.assertEqual(set(item) - declared_fields(), set(), f"{scope}: поле отчёта не внесено в договор")
def test_fields_added_by_the_worker_are_in_the_contract(self) -> None:
import re
source = (ROOT / WORKER_SOURCE).read_text("utf-8")
added = set(re.findall(r"""report\[["'](\w+)["']\]\s*=""", source))
self.assertTrue(added, "не нашли, что рабочий поток дописывает к отчёту")
self.assertEqual(added - declared_fields(), set())
def test_sample_report_covers_every_shown_field(self) -> None:
sample = _sample()
self.assertEqual(set(sample) - declared_fields(), set())
empty = [name for name in (*CARD_FIELDS, *SUMMARY_FIELDS) if name not in sample]
self.assertEqual(empty, [], "в образце отчёта нет показываемого поля")
# Пустое значение ничего не доказывает: карточка обязана быть построена из данных.
self.assertEqual([name for name in CARD_FIELDS if not sample[name]], [])
def test_each_card_field_really_changes_the_cards(self) -> None:
sample = _sample()
full = build_cards(sample)
for name in CARD_FIELDS:
with self.subTest(field=name):
without = dict(sample)
without[name] = None
self.assertNotEqual(build_cards(without), full, f"поле {name} есть в отчёте, но на карточках его не видно")
def test_each_summary_field_is_read_by_the_summary_code(self) -> None:
source = "\n".join((ROOT / path).read_text("utf-8") for path in SUMMARY_SOURCES)
for name in SUMMARY_FIELDS:
with self.subTest(field=name):
self.assertTrue(
f'"{name}"' in source or f"'{name}'" in source,
f"поле {name} объявлено показываемым, но панель итога его не читает",
)
if __name__ == "__main__":
unittest.main()