All checks were successful
Zapret source guards / validate (push) Successful in 10s
Группировка проблем, привязка строки итога к карточке (site/section), имя и пояснение сайта, предупреждения о программах обхода и строки «Прошлых проверок» переехали без изменений из check_results.py (виджеты, 1750 строк) в blockcheck/ui/summary_model.py. Виджеты берут их готовыми, тесты чистой логики импортируют модель без окна; тест слоёв следит, что модель не тянет PyQt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
394 lines
24 KiB
Python
394 lines
24 KiB
Python
"""Отчёт BlockCheck вглубь: страница одного сервера, выводы DNS по частям, сводка серверов, общие действия группы."""
|
||
|
||
import os
|
||
import unittest
|
||
|
||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||
|
||
from PyQt6.QtCore import QPoint, Qt
|
||
from PyQt6.QtTest import QTest
|
||
from PyQt6.QtWidgets import QApplication, QScrollArea
|
||
|
||
from blockcheck.ui.check_results import BlockcheckSummaryPanel
|
||
from blockcheck.ui.finding_parts import FindingCard, split_finding
|
||
from blockcheck.ui.result_cards import ResultDetailView, finding_detail_card, server_card, wants_findings
|
||
from blockcheck.ui.result_cards_model import Card, FindingParts, Line, Section, build_cards
|
||
from blockcheck.ui.server_matrix import cell_state, count_state, parse_server_table, summarize_servers
|
||
|
||
TABLE = "\n".join(
|
||
[
|
||
"ПРОВЕРКА DNS-СЕРВЕРОВ",
|
||
"",
|
||
"Итог:",
|
||
" ✗ что-то",
|
||
"",
|
||
"Сервер Адрес Пинг UDP 53 TCP 53 DoH 443",
|
||
"Cloudflare 1.1.1.1 4 мс 16 мс 21 мс 487 мс",
|
||
"Cloudflare 2606:4700:4700::1111 4 мс 41 мс · 2 из 3 37 мс обрыв",
|
||
"Quad9 9.9.9.9 36 мс молчит молчит молчит",
|
||
"",
|
||
"Подробности:",
|
||
"Cloudflare (1.1.1.1)",
|
||
]
|
||
)
|
||
|
||
AKAMAI = Section(
|
||
"Akamai — обрыв у 1 из 2",
|
||
(
|
||
Line("ok", "SE.AKM-01 · media.miele.com", "получено 32 КБ без обрыва · 5.1 с"),
|
||
Line("fail", "US.AKM-02 · www.roxio.com", "загрузка оборвалась на 16 КБ · 4.8 с"),
|
||
),
|
||
)
|
||
HOSTINGS = Card(key="hostings", icon="fa5s.server", title="Зарубежные хостинги", level="fail", status="Обрыв у 1 из 2", sections=(AKAMAI,))
|
||
|
||
|
||
class ServerTableTests(unittest.TestCase):
|
||
def test_table_is_read_by_the_header_columns(self) -> None:
|
||
columns, rows = parse_server_table(TABLE)
|
||
self.assertEqual(columns, ["Пинг", "UDP 53", "TCP 53", "DoH 443"])
|
||
self.assertEqual([(row.server, row.address) for row in rows], [("Cloudflare", "1.1.1.1"), ("Cloudflare", "2606:4700:4700::1111"), ("Quad9", "9.9.9.9")])
|
||
self.assertEqual(rows[1].cells, ("4 мс", "41 мс · 2 из 3", "37 мс", "обрыв"))
|
||
# Текста без таблицы сводка не касается.
|
||
self.assertEqual(parse_server_table("узел 1 10.0.0.1"), ([], []))
|
||
|
||
def test_cells_are_counted_per_service(self) -> None:
|
||
self.assertEqual([cell_state(text) for text in ("16 мс", "41 мс · 2 из 3", "молчит", "", "—")], ["ok", "warn", "fail", "none", "none"])
|
||
columns, rows = parse_server_table(TABLE)
|
||
cloudflare, quad9 = summarize_servers(rows, len(columns))
|
||
self.assertEqual((cloudflare.name, len(cloudflare.rows)), ("Cloudflare", 2))
|
||
# UDP: один адрес отвечает, второй с потерями; DoH: один работает, один нет.
|
||
self.assertEqual(cloudflare.counts, ((2, 0, 0), (1, 1, 0), (2, 0, 0), (1, 0, 1)))
|
||
self.assertEqual([count_state(*count) for count in cloudflare.counts], ["ok", "warn", "ok", "warn"])
|
||
self.assertEqual([count_state(*count) for count in quad9.counts], ["ok", "fail", "fail", "fail"])
|
||
|
||
|
||
class FindingTests(unittest.TestCase):
|
||
def test_finding_is_split_into_title_and_details(self) -> None:
|
||
self.assertEqual(split_finding("Отвечают через раз: OpenDNS (1.2.3.4) и ещё 3. Так бывает"), ("Отвечают через раз", "OpenDNS (1.2.3.4) и ещё 3. Так бывает."))
|
||
self.assertEqual(split_finding("Потеряли по одному запросу. Похоже на потери в сети"), ("Потеряли по одному запросу", "Похоже на потери в сети."))
|
||
self.assertEqual(split_finding("Всё хорошо"), ("Всё хорошо", ""))
|
||
|
||
def test_only_dns_conclusions_are_shown_in_parts(self) -> None:
|
||
conclusions = Section("Выводы", (Line("fail", "Обычные ответы подменяются у серверов: Cloudflare (1.1.1.1)"),))
|
||
self.assertTrue(wants_findings(conclusions, Card(key="dns_servers", icon="", title="", level="fail", status="")))
|
||
self.assertFalse(wants_findings(conclusions, Card(key="system", icon="", title="", level="fail", status="")))
|
||
|
||
|
||
class ReportOnScreenTests(unittest.TestCase):
|
||
@classmethod
|
||
def setUpClass(cls) -> None:
|
||
cls.app = QApplication.instance() or QApplication([])
|
||
|
||
def _view(self) -> ResultDetailView:
|
||
area = QScrollArea()
|
||
self.addCleanup(area.deleteLater)
|
||
area.setWidgetResizable(True)
|
||
view = ResultDetailView()
|
||
area.setWidget(view)
|
||
area.resize(1000, 400)
|
||
area.show()
|
||
self.app.processEvents()
|
||
return view
|
||
|
||
def test_dns_report_shows_chips_and_a_service_matrix_instead_of_raw_text(self) -> None:
|
||
card = Card(
|
||
key="dns_servers",
|
||
icon="fa5s.network-wired",
|
||
title="DNS-серверы",
|
||
level="fail",
|
||
status="Есть проблемы",
|
||
sections=(
|
||
Section("Выводы", (Line("warn", "Отвечают через раз: Quad9 (9.9.9.9), Quad9 (149.112.112.112) и ещё 3. Так бывает."),)),
|
||
Section("Все серверы и способы связи", text=TABLE),
|
||
),
|
||
)
|
||
view = self._view()
|
||
view.show_card(card)
|
||
self.app.processEvents()
|
||
|
||
conclusions, table = view.blocks
|
||
# Вывод — карточка: заголовок и серверы метками; пояснение целиком — в подсказке.
|
||
[card] = conclusions.rows
|
||
self.assertEqual(conclusions.findings_flow.cards(), [card])
|
||
self.assertEqual(card.title, "Отвечают через раз")
|
||
self.assertEqual([chip.text for chip in card.server_chips], ["Quad9 ×2"])
|
||
self.assertEqual(card.more_label.text(), "и ещё 3")
|
||
self.assertIn("Так бывает.", card.hint_text)
|
||
# Отчёт прошлой проверки хранит только фразу: на странице находки перечень остаётся обрезанным.
|
||
past = finding_detail_card(conclusions.section.lines[0].name, "warn")
|
||
self.assertEqual([line.name for line in past.sections[1].lines], ["Quad9", "и ещё 3"])
|
||
# Таблица — сводкой по сервисам; сырой текст открывается кнопкой, а не лежит на странице.
|
||
self.assertEqual([service.name for service in table.matrix.services()], ["Cloudflare", "Quad9"])
|
||
self.assertIsNone(table.editor)
|
||
self.assertEqual(table.open_text_button.text(), "Подробный текст")
|
||
# Строка сводки открывает страницу сервиса: по разделу на адрес, по строке на способ связи.
|
||
table.matrix.opened.emit(0)
|
||
self.assertEqual((view.card().title, view.card().status), ("Cloudflare", "Отвечает 2 из 2 адресов"))
|
||
self.assertEqual([section.title for section in view.card().sections], ["1.1.1.1", "2606:4700:4700::1111"])
|
||
self.assertEqual([(line.name, line.state) for line in view.card().sections[1].lines][-1], ("DoH 443", "fail"))
|
||
self.assertTrue(view.go_back())
|
||
# Карточка вывода открывает страницу находки с полным текстом.
|
||
QTest.mouseClick(view.blocks[0].rows[0], Qt.MouseButton.LeftButton)
|
||
self.assertEqual(view.card().title, "Отвечают через раз")
|
||
self.assertEqual([section.title for section in view.card().sections], ["Что найдено", "Серверы", "Что это значит"])
|
||
self.assertTrue(view.go_back())
|
||
self.assertIn("2606:4700:4700::1111: Пинг — 4 мс, UDP 53 — 41 мс · 2 из 3", table.matrix.hint(0))
|
||
|
||
def test_ready_parts_give_the_whole_server_list_without_cutting_the_phrase(self) -> None:
|
||
servers = [["Quad9", "9.9.9.9"], ["Quad9", "149.112.112.112"], ["Cloudflare", "1.1.1.1"], ["AdGuard", "94.140.14.14"]]
|
||
servers += [["Google DNS", "8.8.8.8"], ["Google DNS", "8.8.4.4"]]
|
||
report = {
|
||
"dns_servers": {
|
||
"level": "warn",
|
||
"findings": [
|
||
{
|
||
"level": "warn",
|
||
"text": "Отвечают через раз: Quad9 (9.9.9.9), Quad9 (149.112.112.112), Cloudflare (1.1.1.1), AdGuard (94.140.14.14) и ещё 2. Так бывает.",
|
||
"title": "Отвечают через раз",
|
||
"servers": servers,
|
||
"note": "Так бывает.",
|
||
},
|
||
# Вторая находка — как в отчёте прошлой проверки: только фраза.
|
||
{"level": "info", "text": "Не отвечают совсем: Xbox DNS (111.88.96.50) и ещё 5."},
|
||
],
|
||
"text": "",
|
||
}
|
||
}
|
||
[card] = [item for item in build_cards(report) if item.key == "dns_servers"]
|
||
ready, past = card.sections[0].lines
|
||
self.assertEqual(ready.parts, FindingParts("Отвечают через раз", tuple((name, address) for name, address in servers), "Так бывает."))
|
||
self.assertIsNone(past.parts)
|
||
# Фраза целиком остаётся как была — она идёт в текст для копирования.
|
||
self.assertTrue(ready.name.endswith("и ещё 2. Так бывает."))
|
||
|
||
view = self._view()
|
||
view.show_card(card)
|
||
self.app.processEvents()
|
||
shown, old = view.blocks[0].rows
|
||
self.assertEqual(shown.title, "Отвечают через раз")
|
||
# Метки — по сервисам со счётчиком; «и ещё» считает не поместившиеся сервисы, а не обрезку фразы.
|
||
self.assertEqual([chip.text for chip in shown.server_chips], ["Quad9 ×2", "Cloudflare", "AdGuard"])
|
||
self.assertEqual(shown.more_label.text(), "и ещё 1")
|
||
self.assertEqual(
|
||
shown.hint_text,
|
||
"Отвечают через раз\nQuad9: 9.9.9.9, 149.112.112.112\nCloudflare: 1.1.1.1\nAdGuard: 94.140.14.14\n"
|
||
"Google DNS: 8.8.8.8, 8.8.4.4\nТак бывает.",
|
||
)
|
||
# Старая находка показана как раньше: фраза делится на экране.
|
||
self.assertEqual(old.title, "Не отвечают совсем")
|
||
self.assertEqual([chip.text for chip in old.server_chips], ["Xbox DNS"])
|
||
self.assertEqual(old.more_label.text(), "и ещё 5")
|
||
|
||
page = finding_detail_card(ready.name, ready.state, ready.parts)
|
||
self.assertEqual(page.title, "Отвечают через раз")
|
||
self.assertEqual(
|
||
[(line.name, line.text) for line in page.sections[1].lines],
|
||
[
|
||
("Quad9", "9.9.9.9, 149.112.112.112"),
|
||
("Cloudflare", "1.1.1.1"),
|
||
("AdGuard", "94.140.14.14"),
|
||
("Google DNS", "8.8.8.8, 8.8.4.4"),
|
||
],
|
||
)
|
||
self.assertEqual(page.sections[2].lines[0].name, "Так бывает.")
|
||
|
||
def test_server_page_tells_what_was_checked_and_what_it_means(self) -> None:
|
||
card = server_card(AKAMAI.lines[1], AKAMAI)
|
||
self.assertEqual((card.title, card.level, card.status), ("www.roxio.com", "fail", "Загрузка оборвалась на 16 КБ"))
|
||
facts, result, meaning = card.sections
|
||
self.assertEqual([(line.name, line.text) for line in facts.lines], [("Провайдер", "Akamai"), ("Адрес", "www.roxio.com"), ("Сервер", "US.AKM-02"), ("Время проверки", "4.8 с")])
|
||
self.assertEqual(result.lines[0].text, "загрузка оборвалась на 16 КБ")
|
||
self.assertIn("фильтр провайдера", meaning.lines[0].name)
|
||
|
||
def test_tile_opens_the_server_page_and_back_returns_to_the_list(self) -> None:
|
||
view = self._view()
|
||
closed = []
|
||
view.closed.connect(lambda: closed.append(True))
|
||
view.show_card(HOSTINGS)
|
||
self.app.processEvents()
|
||
|
||
grid = view.blocks[0].grid
|
||
QTest.mouseClick(grid, Qt.MouseButton.LeftButton, pos=grid.tile_rect(1).center().toPoint())
|
||
self.assertEqual(view.card().title, "www.roxio.com")
|
||
# Строка пути: BlockCheck → Зарубежные хостинги → сервер.
|
||
self.assertEqual(view.breadcrumb.count(), 3)
|
||
self.assertTrue(view.go_back())
|
||
self.assertEqual(view.card().key, "hostings")
|
||
self.assertEqual(view.breadcrumb.count(), 2)
|
||
# Выше списка хостингов шагов внутри отчёта нет — дальше отчёт закрывают.
|
||
self.assertFalse(view.go_back())
|
||
|
||
QTest.mouseClick(grid if False else view.blocks[0].grid, Qt.MouseButton.LeftButton, pos=QPoint(20, 20))
|
||
self.assertEqual(view.card().title, "media.miele.com")
|
||
view._on_breadcrumb(f"{view.LEVEL_KEY}0")
|
||
self.assertEqual(view.card().key, "hostings")
|
||
# Esc со страницы сервера — шаг назад, а не выход из отчёта.
|
||
view.open_child(server_card(AKAMAI.lines[0], AKAMAI))
|
||
QTest.keyClick(view, Qt.Key.Key_Escape)
|
||
self.assertEqual((view.card().key, closed), ("hostings", []))
|
||
QTest.keyClick(view, Qt.Key.Key_Escape)
|
||
self.assertEqual(closed, [True])
|
||
|
||
|
||
class GroupActionsTests(unittest.TestCase):
|
||
@classmethod
|
||
def setUpClass(cls) -> None:
|
||
cls.app = QApplication.instance() or QApplication([])
|
||
|
||
@staticmethod
|
||
def _dns(text: str) -> dict:
|
||
return {"level": "warn", "kind": "dns", "text": text, "title": "", "target": "", "advice": [], "evidence": [], "action": "dns"}
|
||
|
||
def test_one_action_and_one_report_button_stand_in_the_group_header(self) -> None:
|
||
opened, acted = [], []
|
||
panel = BlockcheckSummaryPanel(on_action=lambda *args: acted.append(args), on_open=opened.append)
|
||
self.addCleanup(panel.deleteLater)
|
||
report = {
|
||
"dns_servers": {"level": "fail", "findings": [{"level": "fail", "text": "x"}], "text": ""},
|
||
"problems": [self._dns("Обычные ответы подменяются у серверов: Cloudflare (1.1.1.1)"), self._dns("Отвечают через раз: Quad9 (9.9.9.9)")],
|
||
}
|
||
panel.show_report(report)
|
||
|
||
[group] = panel.problem_groups()
|
||
self.assertEqual(group.shared_action_button.text(), "Настройка DNS")
|
||
# Строки не повторяют кнопку и не открывают один и тот же отчёт каждая.
|
||
self.assertEqual([(row.action_button, row.card_key) for row in group.rows], [(None, ""), (None, "")])
|
||
# Находки — карточки одной сетки: заголовок и метки серверов, без абзацев пояснений.
|
||
self.assertEqual(group.findings_flow.cards(), group.rows)
|
||
self.assertEqual([card.title for card in group.rows], ["Обычные ответы подменяются у серверов", "Отвечают через раз"])
|
||
self.assertEqual([chip.text for chip in group.rows[0].server_chips], ["Cloudflare"])
|
||
group.shared_action_button.click()
|
||
group.report_button.click()
|
||
self.assertEqual((acted, opened), ([("dns", "")], ["dns_servers"]))
|
||
|
||
def test_group_without_a_kind_goes_last(self) -> None:
|
||
from blockcheck.ui.summary_model import group_problems
|
||
|
||
problems = [
|
||
{"level": "fail", "kind": "other", "text": "WhatsApp не открывается"},
|
||
{"level": "warn", "kind": "dns", "text": "Отвечают через раз"},
|
||
{"level": "fail", "kind": "ip", "text": "Telegram не открывается"},
|
||
]
|
||
self.assertEqual([kind for kind, _rows in group_problems(problems)], ["ip", "dns", "other"])
|
||
|
||
def test_wide_card_goes_first_so_no_card_is_left_alone_in_a_row(self) -> None:
|
||
from blockcheck.ui.result_cards import ResultCardsView
|
||
|
||
view = ResultCardsView()
|
||
self.addCleanup(view.deleteLater)
|
||
report = {
|
||
"services": [],
|
||
"ipv6": {"state": "absent", "text": "в этой сети его нет"},
|
||
"freeze": {"level": "ok", "headline": "", "advice": [], "items": [], "servers": [{"provider": "AWS", "id": "A", "host": "a", "state": "ok", "text": "ок", "direction": "download", "seconds": 1.0}]},
|
||
}
|
||
view.show_report(report, animate=False)
|
||
keys = [card.card.key for card in view.checks_grid.cards()]
|
||
self.assertEqual(keys[0], "hostings")
|
||
self.assertIn("ipv6", keys[1:])
|
||
|
||
def test_finding_card_is_short_and_opens_its_own_page(self) -> None:
|
||
from blockcheck.ui.finding_parts import short_title
|
||
|
||
self.assertEqual(
|
||
short_title("DNS подменяет ответы для www.youtube.com, rutracker.org и ещё 9"),
|
||
("DNS подменяет ответы", ["www.youtube.com", "rutracker.org"], 9),
|
||
)
|
||
self.assertEqual(short_title("Шифрованный DNS AdGuard (94.140.14.140) недоступен"), ("Шифрованный DNS AdGuard недоступен", [], 0))
|
||
|
||
children = []
|
||
panel = BlockcheckSummaryPanel(
|
||
on_action=lambda *_args: None, on_open=lambda _key: None, on_open_page=children.append
|
||
)
|
||
self.addCleanup(panel.deleteLater)
|
||
long = "Обычные DNS-запросы перехватываются по дороге (провайдером или роутером): запросы выполняет одна сеть. Отвечает перехватчик"
|
||
panel.show_report(
|
||
{
|
||
"dns_servers": {"level": "fail", "findings": [{"level": "fail", "text": "x"}], "text": ""},
|
||
"problems": [{**self._dns(long), "level": "fail"}, self._dns("DNS подменяет ответы для www.youtube.com, rutracker.org и ещё 9. Браузер этого не замечает")],
|
||
}
|
||
)
|
||
[group] = panel.problem_groups()
|
||
first, second = group.rows
|
||
# На карточке — коротко; полный заголовок и текст — в подсказке и на странице находки.
|
||
self.assertEqual(first.title, "Обычные DNS-запросы перехватываются по дороге")
|
||
self.assertIn("(провайдером или роутером)", first.hint_text)
|
||
self.assertEqual((second.title, [chip.text for chip in second.server_chips], second.more_label.text()), ("DNS подменяет ответы", ["www.youtube.com", "rutracker.org"], "и ещё 9"))
|
||
QTest.mouseClick(first, Qt.MouseButton.LeftButton)
|
||
# Карточка открывает одну страницу — свою, без отчёта раздела перед ней.
|
||
[child] = children
|
||
self.assertEqual(child.title, "Обычные DNS-запросы перехватываются по дороге (провайдером или роутером)")
|
||
self.assertEqual(child.sections[0].lines[0].name, long)
|
||
self.assertEqual([section.title for section in child.sections[1:]], ["Что это значит"])
|
||
|
||
def test_finding_opens_its_own_page_even_without_a_section_report(self) -> None:
|
||
from blockcheck.ui.check_results import problem_detail_card
|
||
|
||
opened = []
|
||
panel = BlockcheckSummaryPanel(on_action=lambda *_args: None, on_open_page=opened.append)
|
||
self.addCleanup(panel.deleteLater)
|
||
cut = {
|
||
"level": "fail",
|
||
"kind": "cut16",
|
||
"action": "strategy",
|
||
"text": "Обрывается загрузка с зарубежных серверов на 16–20 КБ",
|
||
"advice": ["Подберите стратегию"],
|
||
"chips": [("Akamai", ["US.AKM-01"]), ("Contabo", ["DE.CNT-01", "DE.CNT-02"])],
|
||
}
|
||
panel.show_report({"problems": [cut]})
|
||
[group] = panel.problem_groups()
|
||
# Отчёта раздела «Зарубежные хостинги» в этом итоге нет — страница находки открывается всё равно.
|
||
self.assertIsNone(group.report_button)
|
||
QTest.mouseClick(group.rows[0], Qt.MouseButton.LeftButton)
|
||
[page] = opened
|
||
self.assertEqual(page, problem_detail_card({**cut, "chips": cut["chips"]}))
|
||
self.assertEqual(page.title, "Обрывается загрузка с зарубежных серверов на 16–20 КБ")
|
||
# На странице — те же хостинги, что метками на карточке, со всеми серверами, и совет.
|
||
servers = next(section for section in page.sections if section.title == "Серверы")
|
||
self.assertEqual([(line.name, line.text) for line in servers.lines], [("Akamai", "US.AKM-01"), ("Contabo", "DE.CNT-01, DE.CNT-02")])
|
||
self.assertEqual(page.sections[-1].lines[0].name, "Подберите стратегию")
|
||
# Серверы — плитками с логотипами, значок страницы — по виду проблемы, хвост фразы — итогом в шапке.
|
||
self.assertTrue(servers.tiles)
|
||
self.assertEqual(page.icon, "fa5s.cut")
|
||
tail = problem_detail_card({**cut, "text": cut["text"] + ": 13 из 31 проверенных серверов"})
|
||
self.assertEqual(tail.status, "13 из 31 проверенных серверов")
|
||
self.assertNotIn("Что это значит", [section.title for section in tail.sections])
|
||
|
||
def test_tile_scrolls_the_page_to_its_group(self) -> None:
|
||
area = QScrollArea()
|
||
self.addCleanup(area.deleteLater)
|
||
area.setWidgetResizable(True)
|
||
panel = BlockcheckSummaryPanel()
|
||
area.setWidget(panel)
|
||
area.resize(900, 260)
|
||
area.show()
|
||
problems = [
|
||
{"level": "fail", "kind": "ip", "title": f"Сайт {index}", "text": f"Сайт {index} не открывается", "target": f"s{index}.example", "advice": [], "evidence": [], "action": ""}
|
||
for index in range(40)
|
||
] + [self._dns("Отвечают через раз: Quad9 (9.9.9.9)")]
|
||
panel.show_report({"problems": problems, "services": [{"key": "a", "label": "Сайт 0", "level": "fail", "kind": "ip", "targets": []}]})
|
||
self.app.processEvents()
|
||
|
||
self.assertTrue(panel.scroll_to_group("dns"))
|
||
self.assertGreater(area.verticalScrollBar().value(), 0)
|
||
self.assertFalse(panel.scroll_to_group("нет такой"))
|
||
# Сигнал плитки ведёт туда же.
|
||
area.verticalScrollBar().setValue(0)
|
||
panel.overview.tiles()[0].opened.emit("dns")
|
||
self.assertGreater(area.verticalScrollBar().value(), 0)
|
||
|
||
def test_single_finding_is_a_card_with_the_button_in_the_group_header(self) -> None:
|
||
panel = BlockcheckSummaryPanel(on_action=lambda *_args: None, on_open=lambda _key: None)
|
||
self.addCleanup(panel.deleteLater)
|
||
panel.show_report({"dns_servers": {"level": "fail", "findings": [], "text": ""}, "problems": [self._dns("Отвечают через раз: Quad9 (9.9.9.9)")]})
|
||
|
||
[group] = panel.problem_groups()
|
||
# Одна находка — такая же карточка ограниченной ширины, как несколько, а не строка во всё окно.
|
||
self.assertIsNotNone(group.shared_action_button)
|
||
self.assertIsNotNone(group.report_button)
|
||
[card] = group.findings_flow.cards()
|
||
self.assertIsInstance(card, FindingCard)
|
||
self.assertEqual(card.title, "Отвечают через раз")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|