zapretgui/tests/test_dns_page_build_contract.py
2026-06-14 17:54:43 +03:00

388 lines
15 KiB
Python

from __future__ import annotations
import os
import unittest
from types import SimpleNamespace
from unittest.mock import patch
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtCore import QPoint
from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QVBoxLayout, QWidget
from qfluentwidgets import BodyLabel, IndeterminateProgressBar, LineEdit, PushButton
from dns.ui.page_build import build_network_page_shell
from dns.ui.page_runtime_helpers import build_dns_choices_ui
from ui.fluent_widgets import SettingsCard
class DnsPageBuildContractTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls._app = QApplication.instance() or QApplication([])
def test_network_shell_transient_containers_are_parented_immediately(self) -> None:
content_parent = QWidget()
shell = build_network_page_shell(
parent=content_parent,
content_parent=content_parent,
tr_fn=lambda _key, default: default,
add_section_title_fn=lambda **_kwargs: None,
body_label_cls=BodyLabel,
settings_card_cls=SettingsCard,
qvbox_layout_cls=QVBoxLayout,
qhbox_layout_cls=QHBoxLayout,
qframe_cls=QFrame,
line_edit_cls=LineEdit,
action_button_cls=PushButton,
indeterminate_progress_bar_cls=IndeterminateProgressBar,
setting_card_group_cls=SettingsCard,
quick_actions_bar_cls=lambda: QWidget(),
insert_widget_into_setting_card_group_fn=lambda *_args, **_kwargs: None,
build_custom_dns_ui_fn=lambda **_kwargs: SimpleNamespace(
card=SettingsCard(parent=content_parent),
indicator=QFrame(content_parent),
title_label=BodyLabel(""),
primary_input=LineEdit(content_parent),
secondary_input=LineEdit(content_parent),
apply_button=PushButton("OK", content_parent),
),
build_tools_card_ui_fn=lambda **_kwargs: SimpleNamespace(
section_label=None,
card=SettingsCard(parent=content_parent),
actions_bar=QWidget(content_parent),
test_button=PushButton("test", content_parent),
flush_button=PushButton("flush", content_parent),
),
on_apply_custom_dns=lambda: None,
on_test_connection=lambda: None,
on_flush_dns_cache=lambda: None,
set_tooltip_fn=lambda *_args, **_kwargs: None,
dns_provider_card_cls=SimpleNamespace(indicator_off=lambda: ""),
)
self.assertIs(shell.dns_cards_container.parent(), content_parent)
self.assertIs(shell.adapters_container.parent(), content_parent)
def test_network_shell_uses_compact_adapter_choice_list(self) -> None:
from dns.ui.adapter_list import AdapterChoiceListWidget
content_parent = QWidget()
shell = build_network_page_shell(
parent=content_parent,
content_parent=content_parent,
tr_fn=lambda _key, default: default,
add_section_title_fn=lambda **_kwargs: None,
body_label_cls=BodyLabel,
settings_card_cls=SettingsCard,
qvbox_layout_cls=QVBoxLayout,
qhbox_layout_cls=QHBoxLayout,
qframe_cls=QFrame,
line_edit_cls=LineEdit,
action_button_cls=PushButton,
indeterminate_progress_bar_cls=IndeterminateProgressBar,
setting_card_group_cls=SettingsCard,
quick_actions_bar_cls=lambda: QWidget(),
insert_widget_into_setting_card_group_fn=lambda *_args, **_kwargs: None,
build_custom_dns_ui_fn=lambda **_kwargs: SimpleNamespace(
card=SettingsCard(parent=content_parent),
indicator=QFrame(content_parent),
title_label=BodyLabel(""),
primary_input=LineEdit(content_parent),
secondary_input=LineEdit(content_parent),
apply_button=PushButton("OK", content_parent),
),
build_tools_card_ui_fn=lambda **_kwargs: SimpleNamespace(
section_label=None,
card=SettingsCard(parent=content_parent),
actions_bar=QWidget(content_parent),
test_button=PushButton("test", content_parent),
flush_button=PushButton("flush", content_parent),
),
on_apply_custom_dns=lambda: None,
on_test_connection=lambda: None,
on_flush_dns_cache=lambda: None,
set_tooltip_fn=lambda *_args, **_kwargs: None,
dns_provider_card_cls=SimpleNamespace(indicator_off=lambda: ""),
)
self.assertIsInstance(shell.adapters_container, AdapterChoiceListWidget)
self.assertIs(shell.adapters_layout, shell.adapters_container)
def test_network_page_does_not_place_inline_custom_dns_row_in_choices_list(self) -> None:
from dns.ui.cards import DNSProviderCard
from dns.ui.choice_list import DnsChoiceListWidget
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with patch("dns.ui.page.get_custom_dns_servers", return_value=[]):
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
self.assertIsInstance(page.dns_cards_container, DnsChoiceListWidget)
self.assertIsNone(page.dns_cards_container.custom_item())
self.assertFalse(page.custom_card.isVisible())
self.assertEqual(len(page.dns_cards_container.findChildren(DNSProviderCard)), 0)
self.assertGreater(page.dns_cards_container.count(), len(page.dns_cards))
def test_network_page_refresh_adds_saved_custom_dns_to_choice_list(self) -> None:
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with patch("dns.ui.page.get_custom_dns_servers", return_value=[]):
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
page._custom_dns_servers = [
{
"id": "my-dns",
"name": "Мой DNS",
"ipv4": ["8.8.8.8"],
"ipv6": [],
}
]
page._refresh_custom_dns_providers()
self.assertIn("Мой DNS", page.dns_cards)
def test_network_page_custom_dns_button_adds_one_new_dns(self) -> None:
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with (
patch("dns.ui.page.get_custom_dns_servers", return_value=[]),
patch("dns.ui.page.set_custom_dns_servers", side_effect=lambda value: value),
patch(
"dns.ui.page.CustomDnsDialog",
return_value=_FakeCustomDnsDialog(
{"id": "my-dns", "name": "Мой DNS", "ipv4": ["8.8.8.8"], "ipv6": []}
),
),
):
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
page._open_custom_dns_dialog()
self.assertEqual([server["name"] for server in page._custom_dns_servers], ["Мой DNS"])
self.assertIn("Мой DNS", page.dns_cards)
def test_network_page_custom_dns_dialog_receives_detected_ipv6_availability(self) -> None:
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with (
patch("dns.ui.page.get_custom_dns_servers", return_value=[]),
patch("dns.ui.page.set_custom_dns_servers", side_effect=lambda value: value),
patch(
"dns.ui.page.CustomDnsDialog",
return_value=_FakeCustomDnsDialog(
{"id": "my-dns", "name": "Мой DNS", "ipv4": [], "ipv6": ["2001:4860:4860::8888"]}
),
) as dialog_cls,
):
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
page._ipv6_available = True
page._open_custom_dns_dialog()
self.assertTrue(dialog_cls.call_args.kwargs["ipv6_available"])
self.assertEqual(page._custom_dns_servers[0]["ipv6"], ["2001:4860:4860::8888"])
def test_network_page_custom_dns_row_actions_update_saved_dns(self) -> None:
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with (
patch(
"dns.ui.page.get_custom_dns_servers",
return_value=[
{"id": "my-dns", "name": "Мой DNS", "ipv4": ["8.8.8.8"], "ipv6": []},
],
),
patch("dns.ui.page.set_custom_dns_servers", side_effect=lambda value: value),
patch(
"dns.ui.page.CustomDnsDialog",
return_value=_FakeCustomDnsDialog(
{"id": "my-dns", "name": "Рабочий DNS", "ipv4": ["9.9.9.9"], "ipv6": []}
),
),
):
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
page._edit_custom_dns_server(0)
self.assertEqual(page._custom_dns_servers[0]["name"], "Рабочий DNS")
page._duplicate_custom_dns_server(0)
self.assertEqual([server["name"] for server in page._custom_dns_servers], ["Рабочий DNS", "Рабочий DNS копия"])
with patch("dns.ui.page.InfoBar.success") as info_success:
page._copy_custom_dns_server(0)
self.assertEqual(QApplication.clipboard().text(), "9.9.9.9")
info_success.assert_called_once()
info_text = f"{info_success.call_args.kwargs.get('title', '')} {info_success.call_args.kwargs.get('content', '')}"
self.assertIn("буфер обмена", info_text.lower())
page._delete_custom_dns_server(0)
self.assertEqual([server["name"] for server in page._custom_dns_servers], ["Рабочий DNS копия"])
def test_network_page_copies_ipv6_values_from_custom_dns_row(self) -> None:
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with patch("dns.ui.page.get_custom_dns_servers", return_value=[]):
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
page._custom_dns_servers = [
{
"id": "v6",
"name": "IPv6 DNS",
"ipv4": [],
"ipv6": ["2001:4860:4860::8888", "2001:4860:4860::8844"],
}
]
with patch("dns.ui.page.InfoBar.success"):
page._copy_custom_dns_server(0)
self.assertEqual(
QApplication.clipboard().text(),
"2001:4860:4860::8888, 2001:4860:4860::8844",
)
def test_custom_dns_context_menu_copy_action_mentions_clipboard(self) -> None:
from dns.ui.page import NetworkPage
dns_feature = SimpleNamespace(
normalize_adapter_alias=lambda value: str(value),
consume_warmed_page_data=lambda: None,
create_page_load_worker=lambda request_id, parent=None: None,
)
with (
patch(
"dns.ui.page.get_custom_dns_servers",
return_value=[
{"id": "my-dns", "name": "Мой DNS", "ipv4": ["8.8.8.8"], "ipv6": []},
],
),
patch("dns.ui.page.exec_popup_menu") as exec_popup,
):
menu_labels: list[str] = []
def capture_menu(menu, *_args, **_kwargs):
menu_labels.extend(action.text() for action in menu.actions() if not action.isSeparator())
return None
exec_popup.side_effect = capture_menu
page = NetworkPage(deps=SimpleNamespace(dns_feature=dns_feature))
page._show_custom_dns_provider_menu("Мой DNS", {"custom_id": "my-dns"}, QPoint(1, 1))
self.assertIn("Копировать DNS в буфер обмена", menu_labels)
def test_custom_dns_row_is_not_inserted_or_shown_by_choices_builder(self) -> None:
class _ChoiceList:
def __init__(self):
self.auto_selected = _Signal()
self.custom_was_inserted = False
def show(self):
pass
def add_auto_choice(self, _title):
return object()
def set_custom_choice(self, custom_card):
self.custom_was_inserted = True
custom_card.set_parented()
class _CustomCard:
def __init__(self):
self.was_shown_without_parent = False
self.parented = False
def show(self):
if not self.parented:
self.was_shown_without_parent = True
def set_parented(self):
self.parented = True
custom_card = _CustomCard()
choice_list = _ChoiceList()
build_dns_choices_ui(
cleanup_in_progress=False,
dns_choices_built=False,
tr_fn=lambda _key, default: default,
settings_card_cls=object,
qhbox_layout_cls=object,
qframe_cls=object,
strong_body_label_cls=object,
caption_label_cls=object,
qlabel_cls=object,
qta_module=None,
get_theme_tokens_fn=lambda: SimpleNamespace(fg_faint="#888888"),
build_auto_dns_ui_fn=lambda **_kwargs: None,
build_provider_cards_fn=lambda **_kwargs: SimpleNamespace(dns_cards={}, category_labels=[]),
providers={},
dns_cards_layout=choice_list,
on_auto_selected=lambda: None,
on_provider_selected=lambda _name, _data: None,
ipv6_available=True,
doh_supported=True,
dns_cards_container=SimpleNamespace(show=lambda: None),
custom_card=custom_card,
dns_provider_card_cls=SimpleNamespace(indicator_off=lambda: ""),
apply_inline_theme_styles_fn=lambda _tokens: None,
)
self.assertFalse(choice_list.custom_was_inserted)
self.assertFalse(custom_card.was_shown_without_parent)
class _Signal:
def connect(self, _callback):
pass
class _FakeCustomDnsDialog:
def __init__(self, server):
self._server = dict(server)
def exec(self):
return True
def server(self):
return dict(self._server)
if __name__ == "__main__":
unittest.main()