All checks were successful
Zapret source guards / validate (push) Successful in 4s
Новый переиспользуемый пакет ui/code_editor: CodeEditor с колонкой номеров строк, подсветкой текущей строки, моноширинным шрифтом и зумом (Ctrl+колесо, Ctrl+/-/0); панель Find/Replace (Ctrl+F, Ctrl+H) с подсветкой всех совпадений, счётчиком, опциями регистра/целого слова/regex; построчные операции (Ctrl+D, Ctrl+L, Ctrl+Shift+K, Alt+стрелки, Ctrl+/, Tab только при выделении нескольких строк) одной undo-транзакцией; Ctrl+G — переход к строке; подсветка синтаксиса winws2. Позиция курсора выводится справа в статус-баре пресета. Перекраска синтаксиса при смене темы больше не помечает пресет изменённым: rehighlight() эмитит textChanged без правки текста, поэтому наружу отдаётся собственный сигнал contentEdited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5086 lines
269 KiB
Python
5086 lines
269 KiB
Python
from __future__ import annotations
|
||
|
||
import inspect
|
||
import importlib
|
||
import importlib.util
|
||
import time
|
||
from types import SimpleNamespace
|
||
import unittest
|
||
from unittest.mock import Mock, patch
|
||
|
||
from profile import commands as profile_commands
|
||
import profile.additional_settings_loader as profile_additional_settings_loader
|
||
import profile.profile_setup_loader as profile_setup_loader
|
||
from profile.profile_list_loader import ProfileListLoadWorker
|
||
from profile.service import ProfilePresetService
|
||
from profile.ui.profile_list_model import ProfileListModel as ProfileSetupListModel
|
||
from profile.ui.profiles_list import ProfilesList
|
||
from profile.ui.profile_setup_page import ProfileSetupPageBase
|
||
from profile.ui.preset_setup_page import PresetSetupPageBase
|
||
from profile.ui.profile_payload_controller import ProfilePayloadController
|
||
from profile.ui.preset_write_queue import PresetWriteQueue
|
||
from profile.ui.profile_folder_controller import ProfileFolderController
|
||
from presets import display_state
|
||
from presets import commands as preset_commands
|
||
from presets.ui.common.preset_subpage_base import PresetRawEditorPage
|
||
from presets.ui.common.user_presets_page import UserPresetsPageBase
|
||
from presets.raw_preset_loader import RawPresetActionWorker, RawPresetActivateWorker, RawPresetLoadWorker, RawPresetSaveWorker
|
||
from presets.user_presets_action_workers import UserPresetActivateWorker, UserPresetItemActionWorker
|
||
from presets.user_presets_page_plans import build_preset_rows_plan
|
||
from ui.presets_menu.model import PresetListModel
|
||
import presets.user_presets_action_workers as user_presets_action_workers
|
||
import presets.ui.common.user_presets_page_runtime as user_presets_page_runtime
|
||
import app.feature_facades.presets as presets_feature_facade
|
||
import presets.ui.control.additional_settings_runtime as control_additional_settings_runtime
|
||
import presets.ui.control.control_page_shared as control_page_shared
|
||
import presets.ui.control.windows_features.runtime as windows_features_runtime
|
||
import program_settings.runtime as program_settings_runtime
|
||
import program_settings.workers as program_settings_workers
|
||
import presets.ui.common.preset_folder_menu as preset_folder_menu
|
||
import presets.ui.common.preset_rating_menu as preset_rating_menu
|
||
import profile.ui.profile_folder_menu as profile_folder_menu
|
||
import presets.ui.control.zapret1.runtime_helpers as zapret1_runtime_helpers
|
||
from presets.ui.control.zapret1.page import Zapret1ModeControlPage
|
||
import presets.ui.control.zapret2.page_runtime as zapret2_page_runtime
|
||
from presets.ui.control.zapret2.page import Zapret2ModeControlPage
|
||
from presets.user_presets_runtime_service import (
|
||
UserPresetsMetadataLoadWorker,
|
||
UserPresetsRuntimeService,
|
||
)
|
||
from hosts.ui.page import HostsPage
|
||
import hosts.ui.page_runtime as hosts_page_runtime
|
||
import hosts.commands as hosts_commands
|
||
import log.commands as log_commands
|
||
from log.ui.page import LogsPage
|
||
import blockcheck.page_runtime as blockcheck_page_runtime
|
||
import blockcheck.page_run_workflow as blockcheck_page_run_workflow
|
||
import blockcheck.worker as blockcheck_worker
|
||
from blockcheck.ui.page import BlockcheckPage
|
||
from blockcheck.ui.strategy_scan_page import StrategyScanPage
|
||
import blockcheck.ui.helpers as blockcheck_ui_helpers
|
||
from app.feature_facades.blockcheck import BlockcheckFeature
|
||
from updater.ui.page import ServersPage
|
||
import donater.pairing_workflow as premium_pairing_workflow
|
||
import donater.ui.pairing_workflow as premium_ui_pairing_workflow
|
||
import donater.ui.page_plans as premium_page_plans
|
||
from donater.ui.page import PremiumPage
|
||
import donater.ui.page_lifecycle as premium_page_lifecycle
|
||
from settings.dpi.page import DpiSettingsPage
|
||
from ui.pages.about_page import AboutPage
|
||
from ui.pages.appearance_page import AppearancePage
|
||
from ui.pages.base_page import BasePage
|
||
from ui.pages.support_page import SupportPage
|
||
import settings.appearance as appearance_settings
|
||
import settings.appearance_workers as appearance_workers
|
||
from orchestra.ui.page import OrchestraPage
|
||
from orchestra.ui.blocked_page import OrchestraBlockedPage
|
||
from orchestra.ui.locked_page import OrchestraLockedPage
|
||
from orchestra.ui.ratings_page import OrchestraRatingsPage
|
||
from orchestra.ui.settings_page import OrchestraSettingsPage
|
||
from orchestra.ui.whitelist_page import OrchestraWhitelistPage
|
||
import app.feature_facades.orchestra as orchestra_feature_facade
|
||
import dns.page_diagnostics_warning_workflow as dns_diag_workflow
|
||
import dns.page_load_workflow as dns_load_workflow
|
||
import dns.ui.page as dns_page
|
||
import dns.ui.dns_check_page as dns_check_page
|
||
import dns.commands as dns_commands
|
||
import dns.dns_check_plans as dns_check_page_plans
|
||
import dns.dns_check_worker as dns_check_worker
|
||
from dns.page_workers import DnsPageLoadWorker
|
||
import telegram_proxy.ui.diagnostics_workflow as telegram_diag_workflow
|
||
import telegram_proxy.ui.proxy_runtime_workflow as telegram_runtime_workflow
|
||
import telegram_proxy.ui.page as telegram_page
|
||
import telegram_proxy.runtime.commands as telegram_proxy_commands
|
||
import telegram_proxy.config.settings as telegram_proxy_settings
|
||
import telegram_proxy.ui.settings_build as telegram_proxy_settings_build
|
||
from app.feature_facades.telegram_proxy import TelegramProxyFeature
|
||
import telegram_proxy.ui.upstream_workflow as telegram_upstream_workflow
|
||
import telegram_proxy.runtime.workers as telegram_proxy_workers
|
||
from telegram_proxy.ui.page import TelegramProxyPage
|
||
from telegram_proxy.ui.worker_state import TelegramProxyPageQueuedWorkerState
|
||
from telegram_proxy.runtime.workers import TelegramProxyDiagnosticsWorker
|
||
from diagnostics.ui.page import ConnectionTestPage
|
||
import diagnostics.ui.runtime_helpers as diagnostics_runtime_helpers
|
||
import app.feature_facades.diagnostics as diagnostics_feature_facade
|
||
from app.feature_facades.diagnostics import DiagnosticsFeature
|
||
import ui.navigation.text_sync as navigation_text_sync
|
||
import ui.theme as ui_theme
|
||
import ui.window_appearance_bindings as window_appearance_bindings
|
||
import ui.window_appearance_state as window_appearance_state
|
||
import ui.smooth_scroll as smooth_scroll
|
||
import ui.animation_policy as animation_policy
|
||
import main.entry as main_entry
|
||
from app.feature_facades.profile import ProfileFeature
|
||
from app.page_names import PageName
|
||
from ui.widgets.win11_controls import Win11RadioOption
|
||
from ui.page_host import WindowPageHost
|
||
|
||
|
||
class PresetProfileAsyncArchitectureTests(unittest.TestCase):
|
||
def test_preset_setup_page_loads_profiles_through_worker(self) -> None:
|
||
refresh_source = inspect.getsource(PresetSetupPageBase.refresh_from_preset_switch)
|
||
activated_source = inspect.getsource(PresetSetupPageBase.on_page_activated)
|
||
init_source = inspect.getsource(PresetSetupPageBase.__init__)
|
||
request_source = inspect.getsource(ProfilePayloadController._request_profiles_payload)
|
||
run_source = inspect.getsource(ProfilePayloadController._run_scheduled_profiles_payload_request)
|
||
finished_source = inspect.getsource(ProfilePayloadController._on_profile_worker_finished)
|
||
cleanup_source = inspect.getsource(PresetSetupPageBase.cleanup)
|
||
|
||
self.assertNotIn(".list_profiles(", refresh_source)
|
||
self.assertIn("_schedule_profiles_payload_request(force=True)", refresh_source)
|
||
self.assertNotIn("_request_profiles_payload(force=True)", refresh_source)
|
||
self.assertIn("_schedule_profiles_payload_request", activated_source)
|
||
self.assertNotIn("QTimer.singleShot(0, self._request_profiles_payload)", init_source)
|
||
self.assertIn("_run_scheduled_profiles_payload_request", inspect.getsource(ProfilePayloadController._schedule_profiles_payload_request))
|
||
self.assertIn("_request_profiles_payload", run_source)
|
||
self.assertIn("_profile_payload_dirty", request_source)
|
||
self.assertIn("_profile_payload_loaded_once", request_source)
|
||
self.assertIn("_profile_load_runtime_request_id", request_source)
|
||
self.assertNotIn("_profile_load_runtime_worker", init_source)
|
||
self.assertNotIn("_profile_load_runtime_worker", request_source)
|
||
self.assertNotIn("_profile_load_runtime_worker", finished_source)
|
||
self.assertNotIn("_profile_load_runtime_worker", cleanup_source)
|
||
|
||
def test_preset_setup_clean_activation_skips_payload_request_timer(self) -> None:
|
||
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
|
||
page._profile_payload_loaded_once = True
|
||
page._profile_payload_dirty = False
|
||
page._schedule_profiles_payload_request = Mock(
|
||
side_effect=AssertionError("clean activation must not schedule profile payload request")
|
||
)
|
||
|
||
PresetSetupPageBase.on_page_activated(page)
|
||
|
||
page._schedule_profiles_payload_request.assert_not_called()
|
||
|
||
def test_preset_setup_clean_activation_keeps_ready_list_attached(self) -> None:
|
||
class _List:
|
||
def __init__(self) -> None:
|
||
self.visible_calls: list[bool] = []
|
||
|
||
def setVisible(self, value: bool) -> None: # noqa: N802
|
||
self.visible_calls.append(bool(value))
|
||
|
||
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
|
||
profile_list = _List()
|
||
page._profiles_list = profile_list
|
||
page._profile_payload_loaded_once = True
|
||
page._profile_payload_dirty = False
|
||
page._profiles_list_show_scheduled = False
|
||
page._cleanup_in_progress = False
|
||
page._mark_profiles_list_ready_after_page_switch = Mock()
|
||
page._schedule_profiles_payload_request = Mock(
|
||
side_effect=AssertionError("clean activation must not reload profile payload")
|
||
)
|
||
|
||
PresetSetupPageBase.on_page_hidden(page)
|
||
PresetSetupPageBase.on_page_activated(page)
|
||
|
||
self.assertEqual(profile_list.visible_calls, [])
|
||
page._schedule_profiles_payload_request.assert_not_called()
|
||
page._mark_profiles_list_ready_after_page_switch.assert_called_once_with()
|
||
|
||
def test_preset_setup_page_refreshes_after_active_preset_content_change_signal(self) -> None:
|
||
init_source = inspect.getsource(PresetSetupPageBase.__init__)
|
||
bind_source = inspect.getsource(PresetSetupPageBase.bind_ui_state_store)
|
||
handler_source = inspect.getsource(PresetSetupPageBase._on_ui_state_changed)
|
||
cleanup_source = inspect.getsource(PresetSetupPageBase.cleanup)
|
||
|
||
self.assertIn("ui_state_store", init_source)
|
||
self.assertIn("bind_ui_state_store", init_source)
|
||
self.assertIn("preset_content_revision", bind_source)
|
||
self.assertIn("active_preset_revision", bind_source)
|
||
self.assertIn("preset_content_change_kind", handler_source)
|
||
self.assertIn('"strategy_only"', handler_source)
|
||
self.assertIn("_profile_payload_dirty = True", handler_source)
|
||
self.assertIn("_schedule_profiles_payload_request(force=True)", handler_source)
|
||
self.assertNotIn("_request_profiles_payload(force=True)", handler_source)
|
||
self.assertIn("_ui_state_unsubscribe", cleanup_source)
|
||
|
||
def test_preset_setup_page_ignores_stale_profile_worker_after_preset_switch(self) -> None:
|
||
request_source = inspect.getsource(ProfilePayloadController._request_profiles_payload)
|
||
finished_source = inspect.getsource(ProfilePayloadController._on_profile_worker_finished)
|
||
scheduled_source = inspect.getsource(ProfilePayloadController._run_scheduled_profile_load_refresh_start)
|
||
|
||
self.assertIn("_profile_load_refresh_state_obj()", request_source)
|
||
self.assertIn("runtime.is_running() or refresh_state.start_scheduled", request_source)
|
||
self.assertIn("if force:", request_source)
|
||
self.assertIn("refresh_state.pending = True", request_source)
|
||
self.assertIn("_profile_load_request_id += 1", request_source)
|
||
self.assertIn("_profile_payload_dirty = True", request_source)
|
||
self.assertIn("schedule_pending_after_finish", finished_source)
|
||
self.assertIn("_accept_current_profile_load_worker_finished", finished_source)
|
||
self.assertIn("_schedule_profiles_payload_request(force=True)", scheduled_source)
|
||
|
||
def test_profile_setup_pending_load_restarts_use_shared_finish_guard(self) -> None:
|
||
finish_sources = (
|
||
inspect.getsource(ProfileSetupPageBase._on_profile_setup_worker_finished),
|
||
inspect.getsource(ProfileSetupPageBase._on_list_file_worker_finished),
|
||
inspect.getsource(ProfileSetupPageBase._on_list_file_validation_worker_finished),
|
||
)
|
||
|
||
for source in finish_sources:
|
||
self.assertIn("schedule_pending_after_finish", source)
|
||
self.assertIn("is_current_worker_finish", source)
|
||
self.assertNotIn("if self._", source)
|
||
|
||
def test_profile_folder_worker_returns_folder_state_after_write_action(self) -> None:
|
||
worker_source = inspect.getsource(profile_setup_loader.ProfileFolderActionWorker.run)
|
||
|
||
self.assertIn('context["folder_state"]', worker_source)
|
||
self.assertIn('self._action != "load_state"', worker_source)
|
||
|
||
def test_profile_folder_action_updates_visible_list_without_reload(self) -> None:
|
||
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
|
||
page._profile_folder_action_request_id = 7
|
||
page._profile_folder_action_refresh_by_request = {7: True}
|
||
page._profiles_list = Mock()
|
||
page._profiles_list.apply_profile_folder_state.return_value = True
|
||
page._profile_payload_dirty = False
|
||
page.refresh_from_preset_switch = Mock(
|
||
side_effect=AssertionError("folder action must not reload the whole profile list")
|
||
)
|
||
|
||
page._folder_controller_obj()._on_profile_folder_action_finished(
|
||
7,
|
||
"rename",
|
||
True,
|
||
{"folder_state": {"folders": {}, "items": {}}},
|
||
)
|
||
|
||
page._profiles_list.apply_profile_folder_state.assert_called_once_with({"folders": {}, "items": {}})
|
||
page.refresh_from_preset_switch.assert_not_called()
|
||
self.assertTrue(page._profile_payload_dirty)
|
||
|
||
def test_profile_folder_action_result_ignored_when_new_action_is_pending(self) -> None:
|
||
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
|
||
page._profile_folder_action_request_id = 8
|
||
page._profile_folder_action_refresh_by_request = {8: True}
|
||
page._profile_folder_action_pending = [
|
||
{
|
||
"action": "rename",
|
||
"folder_key": "games",
|
||
"name": "Games",
|
||
"direction": 0,
|
||
"collapsed": False,
|
||
"refresh": True,
|
||
"context_extra": {},
|
||
}
|
||
]
|
||
page._profiles_list = Mock()
|
||
page._profiles_list.apply_profile_folder_state.return_value = True
|
||
page._show_folder_menu_with_state = Mock()
|
||
page.refresh_from_preset_switch = Mock()
|
||
page._profile_payload_dirty = False
|
||
|
||
page._folder_controller_obj()._on_profile_folder_action_finished(
|
||
8,
|
||
"rename",
|
||
True,
|
||
{"folder_state": {"folders": {}, "items": {}}},
|
||
)
|
||
|
||
page._profiles_list.apply_profile_folder_state.assert_not_called()
|
||
page._show_folder_menu_with_state.assert_not_called()
|
||
page.refresh_from_preset_switch.assert_not_called()
|
||
self.assertFalse(page._profile_payload_dirty)
|
||
self.assertNotIn(8, page._profile_folder_action_refresh_by_request)
|
||
|
||
def test_profile_folder_action_error_ignored_when_new_action_is_pending(self) -> None:
|
||
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
|
||
page._profile_folder_action_request_id = 9
|
||
page._profile_folder_action_refresh_by_request = {9: True}
|
||
page._profile_folder_action_pending = [
|
||
{
|
||
"action": "set_collapsed",
|
||
"folder_key": "video",
|
||
"name": "",
|
||
"direction": 0,
|
||
"collapsed": True,
|
||
"refresh": False,
|
||
"context_extra": {},
|
||
}
|
||
]
|
||
|
||
with patch("profile.ui.preset_setup_page.log") as log_mock:
|
||
PresetSetupPageBase._on_profile_folder_action_failed(page, 9, "rename", "old error", {})
|
||
|
||
log_mock.assert_not_called()
|
||
self.assertNotIn(9, page._profile_folder_action_refresh_by_request)
|
||
|
||
def test_profile_model_applies_folder_state_without_loading_profiles_again(self) -> None:
|
||
model = ProfileSetupListModel()
|
||
model.set_profiles((
|
||
SimpleNamespace(
|
||
key="profile-1",
|
||
persistent_key="persistent-1",
|
||
profile_index=0,
|
||
profile_name="Discord",
|
||
enabled=True,
|
||
in_preset=True,
|
||
strategy_id="none",
|
||
strategy_name="Стратегия не выбрана",
|
||
match_lines=("--filter-tcp=443",),
|
||
list_type="hostlist",
|
||
rating="",
|
||
favorite=False,
|
||
group="common",
|
||
group_name="Общие",
|
||
order=0,
|
||
order_is_manual=False,
|
||
group_collapsed=False,
|
||
user_profile_id="",
|
||
),
|
||
))
|
||
model.beginResetModel = Mock(side_effect=AssertionError("folder rename must not reload profiles"))
|
||
|
||
self.assertTrue(model.apply_folder_state({
|
||
"folders": {
|
||
"common": {"name": "Новая папка", "order": 0, "collapsed": False},
|
||
},
|
||
"items": {
|
||
"persistent-1": {"folder_key": "common", "order": 0},
|
||
},
|
||
}))
|
||
|
||
self.assertEqual(model.index(0, 0).data(ProfileSetupListModel.GroupNameRole), "Новая папка")
|
||
|
||
def test_raw_preset_editor_saves_without_runtime_publish_until_editor_is_left(self) -> None:
|
||
from presets.ui.common.raw_preset_text_editor import RawPresetTextEditor
|
||
|
||
save_source = inspect.getsource(PresetRawEditorPage._save_file)
|
||
text_changed_source = inspect.getsource(RawPresetTextEditor.on_text_changed)
|
||
commit_source = inspect.getsource(RawPresetTextEditor.commit_pending_content_change)
|
||
event_source = inspect.getsource(RawPresetTextEditor.handle_event)
|
||
controller_source = inspect.getsource(PresetRawEditorPage.__init__) + inspect.getsource(
|
||
RawPresetTextEditor.__init__
|
||
)
|
||
|
||
self.assertIn("publish_content_changed: bool = False", save_source)
|
||
self.assertIn("publish_content_changed", save_source)
|
||
self.assertIn("content_publish_pending", text_changed_source)
|
||
self.assertIn("publish_content_changed=True", commit_source)
|
||
self.assertIn("QEvent.Type.FocusOut", event_source)
|
||
self.assertIn("QEvent.Type.Leave", event_source)
|
||
self.assertIn("QEvent.Type.MouseButtonPress", event_source)
|
||
self.assertIn("installEventFilter", controller_source)
|
||
|
||
def test_raw_preset_editor_has_inline_text_search(self) -> None:
|
||
from presets.ui.common.raw_preset_text_editor import RawPresetTextEditor
|
||
|
||
build_source = inspect.getsource(PresetRawEditorPage._build_ui)
|
||
editor_init_source = inspect.getsource(RawPresetTextEditor.__init__)
|
||
search_source = inspect.getsource(RawPresetTextEditor.search_text)
|
||
find_source = inspect.getsource(RawPresetTextEditor.find_next)
|
||
|
||
self.assertTrue(hasattr(RawPresetTextEditor, "search_text"))
|
||
self.assertTrue(hasattr(RawPresetTextEditor, "find_next"))
|
||
# Поиск живёт в общем ui.code_editor: панель Find/Replace плюс
|
||
# контроллер, владеющий состоянием совпадений.
|
||
self.assertIn("FindReplaceBar(parent)", editor_init_source)
|
||
self.assertIn("FindController(self.editor, self.find_bar", editor_init_source)
|
||
self.assertIn("self.search_input = self.find_bar.search_input", editor_init_source)
|
||
self.assertIn("actions_layout.addStretch(1)", build_source)
|
||
self.assertIn("self.add_widget(self.findBar)", build_source)
|
||
self.assertIn("self.find_controller.search_text(query)", search_source)
|
||
self.assertIn("self.find_controller.find_next(reverse=", find_source)
|
||
|
||
def test_refresh_after_switch_uses_profile_snapshot_not_full_list(self) -> None:
|
||
source = inspect.getsource(display_state.resolve_profile_strategy_display_state)
|
||
|
||
self.assertNotIn(".list_profiles(", source)
|
||
self.assertIn("get_profile_strategy_display_state", source)
|
||
|
||
def test_preset_switch_summary_refresh_runs_through_worker_runtime(self) -> None:
|
||
import ui.window_bootstrap_runtime as bootstrap_runtime
|
||
|
||
source = inspect.getsource(bootstrap_runtime.create_preset_runtime_coordinator)
|
||
|
||
self.assertIn("PresetProfileStrategySummaryRefreshRuntime", source)
|
||
self.assertIn("summary_refresh_runtime.request_refresh", source)
|
||
self.assertNotIn(
|
||
"refresh_after_switch=lambda: runtime_deps.presets_feature.refresh_profile_strategy_summary_in_store",
|
||
source,
|
||
)
|
||
|
||
def test_preset_switch_summary_worker_does_not_touch_ui_state_store(self) -> None:
|
||
import presets.display_state_refresh as display_state_refresh
|
||
|
||
worker_source = inspect.getsource(display_state_refresh.PresetProfileStrategySummaryWorker.run)
|
||
runtime_source = inspect.getsource(
|
||
display_state_refresh.PresetProfileStrategySummaryRefreshRuntime._on_summary_loaded,
|
||
)
|
||
publish_source = inspect.getsource(display_state.publish_profile_strategy_summary_in_store)
|
||
|
||
self.assertIn("resolve_profile_strategy_display_state", worker_source)
|
||
self.assertNotIn("ui_state_store", worker_source)
|
||
self.assertIn("publish_profile_strategy_summary_in_store", runtime_source)
|
||
self.assertIn("set_current_strategy_summary", publish_source)
|
||
|
||
def test_user_presets_full_metadata_loading_is_worker_only(self) -> None:
|
||
load_source = inspect.getsource(UserPresetsRuntimeService.load_presets)
|
||
watcher_source = inspect.getsource(UserPresetsRuntimeService.reload_presets_from_watcher)
|
||
|
||
self.assertNotIn("adapter.load_all_metadata()", load_source)
|
||
self.assertNotIn("adapter.load_all_metadata()", watcher_source)
|
||
self.assertIn("UserPresetsMetadataLoadWorker", load_source)
|
||
|
||
def test_user_presets_single_metadata_refresh_is_worker_only(self) -> None:
|
||
import presets.user_presets_runtime_service as runtime_service
|
||
|
||
self.assertTrue(hasattr(runtime_service, "UserPresetsSingleMetadataWorker"))
|
||
changed_source = inspect.getsource(UserPresetsRuntimeService.on_store_content_changed)
|
||
request_source = inspect.getsource(UserPresetsRuntimeService._request_single_metadata_refresh)
|
||
start_source = inspect.getsource(UserPresetsRuntimeService._start_single_metadata_refresh_worker)
|
||
loaded_source = inspect.getsource(UserPresetsRuntimeService._on_single_metadata_loaded)
|
||
worker_source = inspect.getsource(runtime_service.UserPresetsSingleMetadataWorker.run)
|
||
|
||
self.assertNotIn("adapter.read_single_metadata(file_name)", changed_source)
|
||
self.assertIn("_request_single_metadata_refresh", changed_source)
|
||
self.assertIn("_single_metadata_state_obj", request_source)
|
||
self.assertIn("_start_single_metadata_refresh_worker", request_source)
|
||
self.assertIn("UserPresetsSingleMetadataWorker", start_source)
|
||
self.assertIn("_read_single_metadata", worker_source)
|
||
self.assertIn("try_apply_single_preset_metadata_update", loaded_source)
|
||
|
||
def test_user_presets_folder_state_for_rows_is_worker_loaded(self) -> None:
|
||
worker_source = inspect.getsource(UserPresetsMetadataLoadWorker.run)
|
||
load_source = inspect.getsource(UserPresetsRuntimeService.load_presets)
|
||
loaded_source = inspect.getsource(UserPresetsRuntimeService._on_metadata_loaded)
|
||
cache_refresh_source = inspect.getsource(UserPresetsRuntimeService.refresh_presets_view_from_cache)
|
||
request_rows_source = inspect.getsource(UserPresetsRuntimeService._request_rows_plan_refresh)
|
||
plan_source = inspect.getsource(build_preset_rows_plan)
|
||
|
||
self.assertIn("_load_folder_state", worker_source)
|
||
self.assertIn("folder_state", load_source)
|
||
self.assertIn("_cached_folder_state", loaded_source)
|
||
self.assertIn("_cached_folder_state", cache_refresh_source)
|
||
self.assertIn("folder_state=folder_state", request_rows_source)
|
||
fallback_branch = plan_source.split("effective_folder_state", 1)[1]
|
||
self.assertNotIn("load_preset_folder_state", fallback_branch)
|
||
|
||
def test_user_presets_rows_plan_apply_is_deferred_after_worker_signal(self) -> None:
|
||
service = UserPresetsRuntimeService.__new__(UserPresetsRuntimeService)
|
||
service._rows_plan_request_id = 3
|
||
page = SimpleNamespace()
|
||
adapter = SimpleNamespace(apply_rows_plan=Mock())
|
||
service._resolve_page = Mock(return_value=page)
|
||
service._resolve_adapter = Mock(return_value=adapter)
|
||
|
||
with patch("presets.user_presets_runtime_service.QTimer.singleShot") as single_shot:
|
||
UserPresetsRuntimeService._on_rows_plan_loaded(service, 3, "plan", 12.5, page)
|
||
|
||
adapter.apply_rows_plan.assert_not_called()
|
||
single_shot.assert_called_once()
|
||
|
||
UserPresetsRuntimeService._run_scheduled_rows_plan_apply(service)
|
||
|
||
adapter.apply_rows_plan.assert_called_once_with("plan", 12.5)
|
||
|
||
def test_user_presets_active_marker_uses_model_signals_without_full_viewport_update(self) -> None:
|
||
source = inspect.getsource(UserPresetsRuntimeService.apply_active_preset_marker_for_file)
|
||
|
||
self.assertIn("set_active_preset", source)
|
||
self.assertIn("schedule_current_preset_index", source)
|
||
self.assertNotIn("viewport().update()", source)
|
||
self.assertNotIn("viewport().repaint()", source)
|
||
|
||
def test_user_presets_single_metadata_update_uses_model_signals_without_full_viewport_update(self) -> None:
|
||
source = inspect.getsource(UserPresetsRuntimeService.try_apply_single_preset_metadata_update)
|
||
|
||
self.assertIn("update_preset_row", source)
|
||
self.assertNotIn("viewport().update()", source)
|
||
self.assertNotIn("viewport().repaint()", source)
|
||
|
||
def test_user_presets_switched_signal_uses_delivered_file_name(self) -> None:
|
||
source = inspect.getsource(UserPresetsRuntimeService.on_store_switched)
|
||
|
||
self.assertIn("apply_active_preset_marker_for_file", source)
|
||
self.assertNotIn("apply_active_preset_marker(page)", source)
|
||
|
||
def test_user_presets_page_uses_warmed_smooth_scroll_preference(self) -> None:
|
||
source = inspect.getsource(UserPresetsPageBase._build_ui)
|
||
|
||
self.assertIn("get_page_smooth_scroll_enabled", source)
|
||
self.assertNotIn("load_smooth_scroll_enabled", source)
|
||
|
||
def test_user_presets_page_uses_narrow_preset_dependencies(self) -> None:
|
||
after_ui_source = inspect.getsource(UserPresetsPageBase._after_ui_built)
|
||
open_folder_source = inspect.getsource(UserPresetsPageBase._open_presets_folder)
|
||
page_source = inspect.getsource(UserPresetsPageBase)
|
||
|
||
self.assertIn("self._connect_preset_signals", after_ui_source)
|
||
self.assertIn("_request_preset_open_folder_action", open_folder_source)
|
||
self.assertNotIn("open_presets_folder_action", open_folder_source)
|
||
self.assertNotIn("open_user_presets_folder", open_folder_source)
|
||
self.assertIn("create_preset_open_folder_worker", page_source)
|
||
self.assertIn("_preset_open_folder_worker", page_source)
|
||
self.assertNotIn("self._presets_feature", page_source)
|
||
self.assertNotIn("self._presets.", after_ui_source)
|
||
self.assertNotIn("self._presets.", open_folder_source)
|
||
|
||
self.assertTrue(hasattr(user_presets_action_workers, "UserPresetOpenFolderWorker"))
|
||
create_worker_source = inspect.getsource(UserPresetsPageBase.create_preset_open_folder_worker)
|
||
worker_source = inspect.getsource(user_presets_action_workers.UserPresetOpenFolderWorker.run)
|
||
worker_init_source = inspect.getsource(user_presets_action_workers.UserPresetOpenFolderWorker.__init__)
|
||
feature_source = inspect.getsource(presets_feature_facade.PresetsFeature.create_user_presets_open_folder_worker)
|
||
self.assertIn("_create_user_presets_open_folder_worker", create_worker_source)
|
||
self.assertNotIn("UserPresetOpenFolderWorker(", create_worker_source)
|
||
self.assertNotIn("_presets_feature", worker_init_source)
|
||
self.assertIn("open_folder", worker_init_source)
|
||
self.assertIn("self._open_folder", worker_init_source)
|
||
self.assertIn("self._open_folder()", worker_source)
|
||
self.assertNotIn("preset_commands.open_user_presets_folder", worker_source)
|
||
self.assertNotIn("_preset_services", worker_init_source)
|
||
self.assertIn("open_user_presets_folder", feature_source)
|
||
|
||
def test_preset_list_active_marker_updates_indexed_rows_only(self) -> None:
|
||
class CountingRow(dict):
|
||
def __init__(self, *args, **kwargs) -> None:
|
||
super().__init__(*args, **kwargs)
|
||
self.get_count = 0
|
||
|
||
def get(self, key, default=None):
|
||
self.get_count += 1
|
||
return super().get(key, default)
|
||
|
||
rows = [
|
||
CountingRow(
|
||
{
|
||
"kind": "preset",
|
||
"file_name": f"preset-{index}.txt",
|
||
"is_active": index == 3,
|
||
}
|
||
)
|
||
for index in range(100)
|
||
]
|
||
model = PresetListModel()
|
||
model.set_rows(rows)
|
||
for row in rows:
|
||
row.get_count = 0
|
||
|
||
self.assertTrue(model.set_active_preset("preset-70.txt"))
|
||
|
||
touched_rows = [row for row in rows if row.get_count]
|
||
self.assertLessEqual(len(touched_rows), 2)
|
||
self.assertFalse(rows[3]["is_active"])
|
||
self.assertTrue(rows[70]["is_active"])
|
||
|
||
def test_user_presets_current_index_uses_model_row_index(self) -> None:
|
||
source = inspect.getsource(UserPresetsRuntimeService.set_current_preset_index)
|
||
|
||
self.assertIn("find_preset_row", source)
|
||
self.assertNotIn("for row in range", source)
|
||
|
||
def test_user_presets_ensure_current_index_uses_model_first_preset_row(self) -> None:
|
||
source = inspect.getsource(UserPresetsRuntimeService.ensure_preset_list_current_index)
|
||
|
||
self.assertIn("first_preset_row", source)
|
||
self.assertNotIn("for row in range", source)
|
||
|
||
def test_user_presets_current_index_skips_already_selected_row(self) -> None:
|
||
class _Index:
|
||
def __init__(self, row: int) -> None:
|
||
self.row = row
|
||
|
||
def isValid(self) -> bool:
|
||
return True
|
||
|
||
def __eq__(self, other) -> bool:
|
||
return isinstance(other, _Index) and self.row == other.row
|
||
|
||
target_index = _Index(4)
|
||
model = Mock()
|
||
model.find_preset_row.return_value = 4
|
||
model.index.return_value = target_index
|
||
presets_list = Mock()
|
||
presets_list.currentIndex.return_value = target_index
|
||
page = SimpleNamespace(_presets_model=model, presets_list=presets_list)
|
||
service = UserPresetsRuntimeService.__new__(UserPresetsRuntimeService)
|
||
service._resolve_page = Mock(return_value=page)
|
||
|
||
UserPresetsRuntimeService.set_current_preset_index(service, "Default.txt")
|
||
|
||
presets_list.setCurrentIndex.assert_not_called()
|
||
|
||
def test_user_presets_restore_view_state_skips_same_scroll_value(self) -> None:
|
||
scrollbar = Mock()
|
||
scrollbar.value.return_value = 42
|
||
presets_list = Mock()
|
||
presets_list.verticalScrollBar.return_value = scrollbar
|
||
page = SimpleNamespace(presets_list=presets_list)
|
||
service = UserPresetsRuntimeService.__new__(UserPresetsRuntimeService)
|
||
service._resolve_page = Mock(return_value=page)
|
||
|
||
UserPresetsRuntimeService.restore_presets_view_state(
|
||
service,
|
||
{"current_file_name": "", "scroll_value": 42},
|
||
)
|
||
|
||
scrollbar.setValue.assert_not_called()
|
||
|
||
def test_user_presets_activation_runs_through_worker(self) -> None:
|
||
handler_source = inspect.getsource(UserPresetsPageBase._on_activate_preset)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_activation)
|
||
start_source = inspect.getsource(UserPresetsPageBase._start_preset_activation_worker)
|
||
worker_source = inspect.getsource(UserPresetActivateWorker.run)
|
||
|
||
self.assertNotIn("activate_preset_action", handler_source)
|
||
self.assertNotIn(".activate_preset(", handler_source)
|
||
self.assertIn("_request_preset_activation", handler_source)
|
||
self.assertIn("apply_active_preset_marker_for_file", request_source + start_source)
|
||
self.assertIn("create_preset_activate_worker", request_source + start_source)
|
||
self.assertIn("self._activate_preset", worker_source)
|
||
self.assertNotIn("actions_api.activate_preset", worker_source)
|
||
|
||
def test_user_presets_activation_error_restores_marker_without_list_reload(self) -> None:
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_activate_request_id = 4
|
||
page._pending_preset_activation = None
|
||
page._restore_preset_activation_marker_file_name = "Before.txt"
|
||
page._runtime_service = Mock()
|
||
page._refresh_presets_view_from_cache = Mock(
|
||
side_effect=AssertionError("activation error must not reload the whole preset list")
|
||
)
|
||
page._tr = Mock(side_effect=lambda _key, default, **_kwargs: default)
|
||
page.window = Mock(return_value=None)
|
||
result = SimpleNamespace(
|
||
ok=False,
|
||
log_message="Ошибка активации",
|
||
log_level="ERROR",
|
||
infobar_level="error",
|
||
infobar_title="Ошибка",
|
||
infobar_content="Не удалось",
|
||
activated_file_name=None,
|
||
)
|
||
|
||
with patch("presets.ui.common.user_presets_page.InfoBar.error"):
|
||
UserPresetsPageBase._on_preset_activation_finished(page, 4, result)
|
||
|
||
page._runtime_service.apply_active_preset_marker.assert_not_called()
|
||
page._runtime_service.apply_active_preset_marker_for_file.assert_called_once_with("Before.txt")
|
||
|
||
def test_user_presets_activation_failure_restores_marker_without_list_reload(self) -> None:
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_activate_request_id = 5
|
||
page._pending_preset_activation = None
|
||
page._restore_preset_activation_marker_file_name = "Before.txt"
|
||
page._runtime_service = Mock()
|
||
page._refresh_presets_view_from_cache = Mock(
|
||
side_effect=AssertionError("activation failure must not reload the whole preset list")
|
||
)
|
||
page._tr = Mock(side_effect=lambda _key, default, **_kwargs: default)
|
||
page.window = Mock(return_value=None)
|
||
|
||
with patch("presets.ui.common.user_presets_page.InfoBar.error"):
|
||
UserPresetsPageBase._on_preset_activation_failed(page, 5, "bad")
|
||
|
||
page._runtime_service.apply_active_preset_marker.assert_not_called()
|
||
page._runtime_service.apply_active_preset_marker_for_file.assert_called_once_with("Before.txt")
|
||
|
||
def test_user_presets_pending_activation_uses_shared_write_queue_after_worker_signal(self) -> None:
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_activate_request_id = 5
|
||
page._preset_activate_runtime = SimpleNamespace(is_running=Mock(return_value=False))
|
||
page._preset_item_action_runtime = SimpleNamespace(is_running=Mock(return_value=False))
|
||
page._preset_bulk_action_runtime = SimpleNamespace(is_running=Mock(return_value=False))
|
||
page._preset_edit_action_runtime = SimpleNamespace(is_running=Mock(return_value=False))
|
||
page._preset_storage_action_runtime = SimpleNamespace(is_running=Mock(return_value=False))
|
||
page._preset_folder_action_runtime = SimpleNamespace(is_running=Mock(return_value=False))
|
||
page._preset_folder_action_pending = []
|
||
page._pending_preset_write_actions = []
|
||
page._pending_preset_activation = ("Next.txt", "Next")
|
||
page._cleanup_in_progress = False
|
||
page._start_preset_activation_worker = Mock()
|
||
callbacks = []
|
||
|
||
with patch(
|
||
"presets.ui.common.user_presets_page.QTimer.singleShot",
|
||
side_effect=lambda _delay, callback: callbacks.append(callback),
|
||
):
|
||
UserPresetsPageBase._on_preset_activate_worker_finished(page, SimpleNamespace(_request_id=5))
|
||
|
||
page._start_preset_activation_worker.assert_not_called()
|
||
self.assertEqual(len(callbacks), 1)
|
||
|
||
callbacks[0]()
|
||
|
||
page._start_preset_activation_worker.assert_called_once_with("Next.txt", "Next")
|
||
|
||
def test_preset_model_removes_visible_preset_without_full_reset(self) -> None:
|
||
model = PresetListModel()
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 2,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "second.txt",
|
||
"name": "Second",
|
||
"folder_key": "common",
|
||
},
|
||
])
|
||
model.beginResetModel = Mock(side_effect=AssertionError("delete must not reset the whole preset list"))
|
||
|
||
self.assertTrue(model.remove_preset("first.txt"))
|
||
|
||
self.assertEqual(model.rowCount(), 2)
|
||
self.assertEqual(model.find_preset_row("first.txt"), -1)
|
||
self.assertEqual(model.find_preset_row("second.txt"), 1)
|
||
self.assertEqual(model.index(0, 0).data(PresetListModel.CountRole), 1)
|
||
|
||
def test_preset_model_skips_identical_rows_without_full_reset(self) -> None:
|
||
rows = [
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 1,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
},
|
||
]
|
||
model = PresetListModel()
|
||
model.set_rows(rows)
|
||
model.beginResetModel = Mock(side_effect=AssertionError("same rows must not reset the preset list"))
|
||
|
||
model.set_rows([dict(row) for row in rows])
|
||
|
||
self.assertEqual(model.rowCount(), 2)
|
||
self.assertEqual(model.find_preset_row("first.txt"), 1)
|
||
|
||
def test_preset_model_reports_whether_set_rows_changed_model(self) -> None:
|
||
rows = [
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
},
|
||
]
|
||
model = PresetListModel()
|
||
|
||
self.assertTrue(model.set_rows(rows))
|
||
self.assertFalse(model.set_rows([dict(row) for row in rows]))
|
||
self.assertTrue(model.set_rows([{**rows[0], "name": "First updated"}]))
|
||
|
||
def test_preset_model_tracks_first_visible_preset_row(self) -> None:
|
||
model = PresetListModel()
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 2,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "second.txt",
|
||
"name": "Second",
|
||
"folder_key": "common",
|
||
},
|
||
])
|
||
|
||
self.assertEqual(model.first_preset_row(), 1)
|
||
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "empty",
|
||
"text": "Нет",
|
||
"count": 0,
|
||
"is_collapsed": False,
|
||
}
|
||
])
|
||
|
||
self.assertEqual(model.first_preset_row(), -1)
|
||
|
||
def test_preset_model_updates_stable_rows_without_full_reset(self) -> None:
|
||
model = PresetListModel()
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 1,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
"description": "old",
|
||
"rating": 1,
|
||
},
|
||
])
|
||
model.beginResetModel = Mock(side_effect=AssertionError("stable rows must not reset the preset list"))
|
||
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 1,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First updated",
|
||
"folder_key": "common",
|
||
"description": "new",
|
||
"rating": 5,
|
||
},
|
||
])
|
||
|
||
self.assertEqual(model.rowCount(), 2)
|
||
self.assertEqual(model.find_preset_row("first.txt"), 1)
|
||
self.assertEqual(model.index(1, 0).data(PresetListModel.NameRole), "First updated")
|
||
self.assertEqual(model.index(1, 0).data(PresetListModel.DescriptionRole), "new")
|
||
self.assertEqual(model.index(1, 0).data(PresetListModel.RatingRole), 5)
|
||
|
||
def test_preset_model_moves_single_reordered_row_without_full_reset(self) -> None:
|
||
model = PresetListModel()
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 3,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
"rating": 0,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "second.txt",
|
||
"name": "Second",
|
||
"folder_key": "common",
|
||
"rating": 0,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "third.txt",
|
||
"name": "Third",
|
||
"folder_key": "common",
|
||
"rating": 0,
|
||
},
|
||
])
|
||
model.beginResetModel = Mock(side_effect=AssertionError("single-row reorder must not reset the whole preset list"))
|
||
|
||
changed = model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 3,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "second.txt",
|
||
"name": "Second",
|
||
"folder_key": "common",
|
||
"rating": 0,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
"rating": 5,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "third.txt",
|
||
"name": "Third",
|
||
"folder_key": "common",
|
||
"rating": 0,
|
||
},
|
||
])
|
||
|
||
self.assertTrue(changed)
|
||
self.assertEqual(model.find_preset_row("first.txt"), 2)
|
||
self.assertEqual(model.index(2, 0).data(PresetListModel.FileNameRole), "first.txt")
|
||
self.assertEqual(model.index(2, 0).data(PresetListModel.RatingRole), 5)
|
||
|
||
def test_preset_model_single_move_detection_is_not_bruteforce(self) -> None:
|
||
helper_source = inspect.getsource(__import__("ui.presets_menu.model", fromlist=["_single_row_move"])._single_row_move)
|
||
|
||
self.assertNotIn("for insert_index in range", helper_source)
|
||
self.assertNotIn("candidate =", helper_source)
|
||
self.assertIn("current_positions", helper_source)
|
||
|
||
def test_preset_rows_plan_apply_skips_layout_when_rows_do_not_change(self) -> None:
|
||
from presets.ui.common.user_presets_page_runtime import apply_presets_rows_plan
|
||
|
||
runtime_service = Mock()
|
||
runtime_service.capture_presets_view_state.return_value = {}
|
||
presets_model = Mock()
|
||
presets_model.set_rows.return_value = False
|
||
plan = SimpleNamespace(
|
||
rows=[
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
}
|
||
],
|
||
total_presets=1,
|
||
)
|
||
update_height = Mock(side_effect=AssertionError("unchanged rows must not recalculate list height"))
|
||
schedule_resync = Mock(side_effect=AssertionError("unchanged rows must not resync layout"))
|
||
presets_delegate = Mock()
|
||
|
||
apply_presets_rows_plan(
|
||
runtime_service=runtime_service,
|
||
presets_delegate=presets_delegate,
|
||
presets_model=presets_model,
|
||
presets_list=object(),
|
||
schedule_layout_resync_fn=schedule_resync,
|
||
update_presets_view_height_fn=update_height,
|
||
log_fn=Mock(),
|
||
plan=plan,
|
||
)
|
||
|
||
presets_delegate.reset_interaction_state.assert_not_called()
|
||
runtime_service.ensure_preset_list_current_index.assert_not_called()
|
||
runtime_service.restore_presets_view_state.assert_not_called()
|
||
|
||
def test_preset_rows_plan_apply_skips_layout_when_row_count_is_unchanged(self) -> None:
|
||
from presets.ui.common.user_presets_page_runtime import apply_presets_rows_plan
|
||
|
||
runtime_service = Mock()
|
||
runtime_service.capture_presets_view_state.return_value = {}
|
||
presets_model = PresetListModel()
|
||
presets_model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 2,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "second.txt",
|
||
"name": "Second",
|
||
"folder_key": "common",
|
||
},
|
||
])
|
||
plan = SimpleNamespace(
|
||
rows=[
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"text": "Общие",
|
||
"count": 2,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "second.txt",
|
||
"name": "Second",
|
||
"folder_key": "common",
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "first.txt",
|
||
"name": "First",
|
||
"folder_key": "common",
|
||
"rating": 5,
|
||
},
|
||
],
|
||
total_presets=2,
|
||
)
|
||
update_height = Mock()
|
||
schedule_resync = Mock()
|
||
|
||
apply_presets_rows_plan(
|
||
runtime_service=runtime_service,
|
||
presets_delegate=Mock(),
|
||
presets_model=presets_model,
|
||
presets_list=object(),
|
||
schedule_layout_resync_fn=schedule_resync,
|
||
update_presets_view_height_fn=update_height,
|
||
log_fn=Mock(),
|
||
plan=plan,
|
||
)
|
||
|
||
update_height.assert_not_called()
|
||
schedule_resync.assert_not_called()
|
||
runtime_service.ensure_preset_list_current_index.assert_called_once_with()
|
||
|
||
def test_user_presets_clean_activation_skips_full_layout_resync(self) -> None:
|
||
from presets.ui.common.user_presets_page_lifecycle import activate_user_presets_page
|
||
|
||
runtime_service = Mock()
|
||
runtime_service.is_ui_dirty.return_value = False
|
||
start_watching = Mock()
|
||
apply_mode_labels = Mock()
|
||
resync_layout = Mock(side_effect=AssertionError("clean activation must not fully resync layout"))
|
||
refresh_view = Mock(side_effect=AssertionError("clean activation must not reload preset rows"))
|
||
update_height = Mock()
|
||
schedule_resync = Mock(side_effect=AssertionError("clean activation must not schedule delayed layout resync"))
|
||
|
||
activate_user_presets_page(
|
||
cleanup_in_progress=False,
|
||
apply_mode_labels_fn=apply_mode_labels,
|
||
resync_layout_metrics_fn=resync_layout,
|
||
start_watching_presets_fn=start_watching,
|
||
runtime_service=runtime_service,
|
||
refresh_presets_view_if_possible_fn=refresh_view,
|
||
update_presets_view_height_fn=update_height,
|
||
schedule_layout_resync_fn=schedule_resync,
|
||
)
|
||
|
||
start_watching.assert_called_once_with()
|
||
apply_mode_labels.assert_called_once_with()
|
||
update_height.assert_called_once_with()
|
||
|
||
def test_user_presets_delete_updates_visible_row_without_reload(self) -> None:
|
||
result = SimpleNamespace(
|
||
ok=True,
|
||
structure_changed=True,
|
||
log_message="Удалён пресет",
|
||
log_level="INFO",
|
||
infobar_level="",
|
||
infobar_title="",
|
||
infobar_content="",
|
||
error_code="",
|
||
)
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_item_action_request_id = 4
|
||
page._runtime_service = Mock()
|
||
page._runtime_service.remove_deleted_preset_locally.return_value = True
|
||
page._tr = Mock(side_effect=lambda _key, default, **_kwargs: default)
|
||
page.window = Mock(return_value=None)
|
||
|
||
with patch("presets.ui.common.user_presets_page.InfoBar.success"):
|
||
UserPresetsPageBase._on_preset_item_action_finished(
|
||
page,
|
||
4,
|
||
"delete",
|
||
result,
|
||
{"file_name": "first.txt"},
|
||
)
|
||
|
||
page._runtime_service.remove_deleted_preset_locally.assert_called_once_with("first.txt")
|
||
page._runtime_service.mark_presets_structure_changed.assert_not_called()
|
||
|
||
def test_preset_model_renames_visible_preset_without_full_reset(self) -> None:
|
||
model = PresetListModel()
|
||
model.set_rows([
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "old.txt",
|
||
"name": "Old",
|
||
"folder_key": "common",
|
||
"is_active": True,
|
||
},
|
||
])
|
||
model.beginResetModel = Mock(side_effect=AssertionError("rename must not reset the whole preset list"))
|
||
|
||
self.assertTrue(model.rename_preset("old.txt", "new.txt", name="New"))
|
||
|
||
self.assertEqual(model.find_preset_row("old.txt"), -1)
|
||
self.assertEqual(model.find_preset_row("new.txt"), 0)
|
||
self.assertEqual(model.index(0, 0).data(PresetListModel.FileNameRole), "new.txt")
|
||
self.assertEqual(model.index(0, 0).data(PresetListModel.NameRole), "New")
|
||
self.assertEqual(model.active_preset_file_name(), "new.txt")
|
||
|
||
def test_user_presets_rename_updates_visible_row_without_reload(self) -> None:
|
||
result = SimpleNamespace(
|
||
ok=True,
|
||
structure_changed=True,
|
||
log_message="Переименован",
|
||
log_level="INFO",
|
||
infobar_level="",
|
||
infobar_title="",
|
||
infobar_content="",
|
||
error_code="",
|
||
preset_file_name="new.txt",
|
||
preset_display_name="New",
|
||
)
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_edit_action_request_id = 5
|
||
page._runtime_service = Mock()
|
||
page._runtime_service.rename_preset_locally.return_value = True
|
||
|
||
UserPresetsPageBase._on_preset_edit_action_finished(
|
||
page,
|
||
5,
|
||
"rename",
|
||
result,
|
||
{"current_name": "old.txt", "new_name": "New"},
|
||
)
|
||
|
||
page._runtime_service.rename_preset_locally.assert_called_once_with(
|
||
"old.txt",
|
||
"new.txt",
|
||
"New",
|
||
)
|
||
page._runtime_service.mark_presets_structure_changed.assert_not_called()
|
||
|
||
def test_preset_model_inserts_created_preset_without_full_reset(self) -> None:
|
||
model = PresetListModel()
|
||
model.set_rows([
|
||
{
|
||
"kind": "folder",
|
||
"folder_key": "common",
|
||
"name": "Общие",
|
||
"text": "Общие",
|
||
"count": 1,
|
||
"is_collapsed": False,
|
||
},
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "old.txt",
|
||
"name": "Old",
|
||
"folder_key": "common",
|
||
"is_active": False,
|
||
},
|
||
])
|
||
model.beginResetModel = Mock(side_effect=AssertionError("create must not reset the whole preset list"))
|
||
|
||
self.assertTrue(model.insert_preset({
|
||
"kind": "preset",
|
||
"file_name": "new.txt",
|
||
"name": "New",
|
||
"folder_key": "common",
|
||
"is_active": False,
|
||
}))
|
||
|
||
self.assertEqual(model.find_preset_row("new.txt"), 2)
|
||
self.assertEqual(model.index(0, 0).data(PresetListModel.CountRole), 2)
|
||
|
||
def test_user_presets_create_updates_visible_row_without_reload(self) -> None:
|
||
result = SimpleNamespace(
|
||
ok=True,
|
||
structure_changed=True,
|
||
log_message="Создан",
|
||
log_level="INFO",
|
||
infobar_level="",
|
||
infobar_title="",
|
||
infobar_content="",
|
||
error_code="",
|
||
preset_file_name="new.txt",
|
||
preset_display_name="New",
|
||
)
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_edit_action_request_id = 6
|
||
page._runtime_service = Mock()
|
||
page._runtime_service.add_created_preset_locally.return_value = True
|
||
|
||
UserPresetsPageBase._on_preset_edit_action_finished(
|
||
page,
|
||
6,
|
||
"create",
|
||
result,
|
||
{"name": "New"},
|
||
)
|
||
|
||
page._runtime_service.add_created_preset_locally.assert_called_once_with(
|
||
"new.txt",
|
||
"New",
|
||
)
|
||
page._runtime_service.mark_presets_structure_changed.assert_not_called()
|
||
|
||
def test_user_presets_duplicate_updates_visible_row_without_reload(self) -> None:
|
||
result = SimpleNamespace(
|
||
ok=True,
|
||
structure_changed=True,
|
||
log_message="Дублирован",
|
||
log_level="INFO",
|
||
infobar_level="",
|
||
infobar_title="",
|
||
infobar_content="",
|
||
error_code="",
|
||
preset_file_name="copy.txt",
|
||
preset_display_name="Copy",
|
||
)
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_item_action_request_id = 7
|
||
page._runtime_service = Mock()
|
||
page._runtime_service.add_created_preset_locally.return_value = True
|
||
page._tr = Mock(side_effect=lambda _key, default, **_kwargs: default)
|
||
page.window = Mock(return_value=None)
|
||
|
||
UserPresetsPageBase._on_preset_item_action_finished(
|
||
page,
|
||
7,
|
||
"duplicate",
|
||
result,
|
||
{"file_name": "source.txt", "display_name": "Source"},
|
||
)
|
||
|
||
page._runtime_service.add_created_preset_locally.assert_called_once_with(
|
||
"copy.txt",
|
||
"Copy",
|
||
)
|
||
page._runtime_service.mark_presets_structure_changed.assert_not_called()
|
||
|
||
def test_user_presets_import_updates_visible_row_without_reload(self) -> None:
|
||
result = SimpleNamespace(
|
||
ok=True,
|
||
structure_changed=True,
|
||
log_message="Импортирован",
|
||
log_level="INFO",
|
||
infobar_level="success",
|
||
infobar_title="Пресет импортирован",
|
||
infobar_content="Готово",
|
||
actual_file_name="imported.txt",
|
||
actual_name="Imported",
|
||
)
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_bulk_action_request_id = 8
|
||
page._runtime_service = Mock()
|
||
page._runtime_service.add_created_preset_locally.return_value = True
|
||
page.window = Mock(return_value=None)
|
||
|
||
with patch("presets.ui.common.user_presets_page.InfoBar.success"):
|
||
UserPresetsPageBase._on_preset_bulk_action_finished(
|
||
page,
|
||
8,
|
||
"import",
|
||
result,
|
||
{},
|
||
)
|
||
|
||
page._runtime_service.add_created_preset_locally.assert_called_once_with(
|
||
"imported.txt",
|
||
"Imported",
|
||
)
|
||
page._runtime_service.mark_presets_structure_changed.assert_not_called()
|
||
|
||
def test_user_presets_display_name_uses_visible_cache_not_backend_manifest(self) -> None:
|
||
class _ListingApi:
|
||
def resolve_display_name(self, _reference: str) -> str:
|
||
raise AssertionError("display name must not be resolved from backend in GUI path")
|
||
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._page_api = type("_PageApi", (), {"listing": _ListingApi()})()
|
||
page._runtime_service = UserPresetsRuntimeService()
|
||
page._runtime_service._cached_presets_metadata = {
|
||
"cached.txt": {"display_name": "Cached Preset"},
|
||
}
|
||
page._presets_model = PresetListModel()
|
||
page._presets_model.set_rows(
|
||
[
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "visible.txt",
|
||
"name": "Visible Preset",
|
||
}
|
||
]
|
||
)
|
||
page._presets_model.find_preset_row = Mock(
|
||
side_effect=AssertionError("display name should use model display cache")
|
||
)
|
||
|
||
self.assertEqual(page._resolve_display_name("visible.txt"), "Visible Preset")
|
||
self.assertEqual(page._resolve_display_name("cached.txt"), "Cached Preset")
|
||
self.assertEqual(page._resolve_display_name("fallback.txt"), "fallback")
|
||
|
||
def test_user_presets_builtin_check_uses_visible_cache_not_backend_storage(self) -> None:
|
||
class _StorageApi:
|
||
def is_builtin_preset_file_with_cache(self, _name: str, _cached_metadata) -> bool:
|
||
raise AssertionError("builtin check must not be resolved from backend in GUI path")
|
||
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._page_api = type("_PageApi", (), {"storage": _StorageApi()})()
|
||
page._runtime_service = UserPresetsRuntimeService()
|
||
page._runtime_service._cached_presets_metadata = {
|
||
"cached.txt": {"is_builtin": True},
|
||
}
|
||
page._presets_model = PresetListModel()
|
||
page._presets_model.set_rows(
|
||
[
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "visible.txt",
|
||
"name": "Visible Preset",
|
||
"is_builtin": True,
|
||
}
|
||
]
|
||
)
|
||
page._presets_model.find_preset_row = Mock(
|
||
side_effect=AssertionError("builtin check should use model builtin cache")
|
||
)
|
||
|
||
self.assertTrue(page._is_builtin_preset_file("visible.txt"))
|
||
self.assertTrue(page._is_builtin_preset_file("cached.txt"))
|
||
self.assertFalse(page._is_builtin_preset_file("fallback.txt"))
|
||
|
||
def test_user_presets_rating_menu_uses_visible_cache_not_backend_metadata(self) -> None:
|
||
class _RuntimeService:
|
||
def cached_presets_metadata(self):
|
||
raise AssertionError("rating menu should use model rating cache")
|
||
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._runtime_service = _RuntimeService()
|
||
page._config = SimpleNamespace(tr_prefix="presets")
|
||
page._tr = Mock(side_effect=lambda _key, fallback="": fallback)
|
||
page._request_preset_storage_action = Mock()
|
||
page._presets_model = PresetListModel()
|
||
page._presets_model.set_rows(
|
||
[
|
||
{
|
||
"kind": "preset",
|
||
"file_name": "visible.txt",
|
||
"name": "Visible Preset",
|
||
"rating": 6,
|
||
}
|
||
]
|
||
)
|
||
|
||
with patch(
|
||
"presets.ui.common.user_presets_page.show_preset_rating_menu",
|
||
return_value=8,
|
||
) as menu_mock:
|
||
page._show_rating_menu("visible.txt")
|
||
|
||
menu_mock.assert_called_once()
|
||
self.assertEqual(menu_mock.call_args.kwargs["current_rating"], 6)
|
||
page._request_preset_storage_action.assert_called_once_with(
|
||
"rating",
|
||
name="visible.txt",
|
||
display_name="Visible Preset",
|
||
rating=8,
|
||
)
|
||
|
||
def test_user_presets_cleanup_stops_action_workers_and_pending_requests(self) -> None:
|
||
class _Runtime:
|
||
def __init__(self) -> None:
|
||
self.stop = Mock()
|
||
self.cancel = Mock()
|
||
|
||
class _Timer:
|
||
def __init__(self) -> None:
|
||
self.stop = Mock()
|
||
|
||
runtime_attrs = (
|
||
"_preset_activate_runtime",
|
||
"_preset_item_action_runtime",
|
||
"_preset_bulk_action_runtime",
|
||
"_preset_edit_action_runtime",
|
||
"_preset_storage_action_runtime",
|
||
"_preset_folder_action_runtime",
|
||
"_preset_open_folder_runtime",
|
||
"_preset_link_action_runtime",
|
||
)
|
||
request_attrs = (
|
||
"_preset_activate_request_id",
|
||
"_preset_item_action_request_id",
|
||
"_preset_bulk_action_request_id",
|
||
"_preset_edit_action_request_id",
|
||
"_preset_storage_action_request_id",
|
||
"_preset_folder_action_request_id",
|
||
"_preset_open_folder_request_id",
|
||
"_preset_link_action_request_id",
|
||
)
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
runtimes = {attr: _Runtime() for attr in runtime_attrs}
|
||
for attr, runtime in runtimes.items():
|
||
setattr(page, attr, runtime)
|
||
for attr in request_attrs:
|
||
setattr(page, attr, 7)
|
||
page._pending_preset_activation = ("next.txt", "Next")
|
||
page._preset_folder_action_pending = [{"action": "load_state"}]
|
||
page._preset_open_folder_pending = True
|
||
page._preset_link_action_pending = ["info"]
|
||
page._preset_bulk_action_kind = "reset_all"
|
||
page._bulk_reset_running = True
|
||
page._layout_resync_timer = _Timer()
|
||
page._layout_resync_delayed_timer = _Timer()
|
||
page._preset_search_timer = _Timer()
|
||
page._ui_state_unsubscribe = Mock()
|
||
page._ui_state_store = object()
|
||
page._runtime_service = Mock()
|
||
|
||
UserPresetsPageBase.cleanup(page)
|
||
|
||
for runtime in runtimes.values():
|
||
runtime.stop.assert_called_once()
|
||
runtime.cancel.assert_called_once()
|
||
for attr in request_attrs:
|
||
self.assertEqual(getattr(page, attr), 8)
|
||
self.assertIsNone(page._pending_preset_activation)
|
||
self.assertEqual(page._preset_folder_action_pending, [])
|
||
self.assertFalse(page._preset_open_folder_pending)
|
||
self.assertEqual(page._preset_link_action_pending, [])
|
||
self.assertEqual(page._preset_bulk_action_kind, "")
|
||
self.assertFalse(page._bulk_reset_running)
|
||
page._runtime_service.stop_watching_presets.assert_called_once()
|
||
|
||
def test_user_presets_menu_selected_check_uses_runtime_marker(self) -> None:
|
||
source = inspect.getsource(UserPresetsPageBase._is_selected_source_preset_file)
|
||
|
||
self.assertIn("active_preset_file_name", source)
|
||
self.assertNotIn("_get_selected_source_preset_file_name_light", source)
|
||
|
||
def test_user_preset_activation_worker_is_created_through_feature(self) -> None:
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_activation)
|
||
start_source = inspect.getsource(UserPresetsPageBase._start_preset_activation_worker)
|
||
create_worker_source = inspect.getsource(UserPresetsPageBase.create_preset_activate_worker)
|
||
|
||
self.assertIn("_start_preset_activation_worker", request_source)
|
||
self.assertIn("create_preset_activate_worker", start_source)
|
||
self.assertNotIn("UserPresetActivateWorker", create_worker_source)
|
||
self.assertIn("_create_preset_activate_worker_fn", create_worker_source)
|
||
|
||
def test_user_presets_runtime_does_not_keep_file_action_fallbacks(self) -> None:
|
||
page_source = inspect.getsource(UserPresetsPageBase)
|
||
runtime_source = inspect.getsource(user_presets_page_runtime)
|
||
|
||
self.assertNotIn("def _actions_api", page_source)
|
||
self.assertNotIn("class UserPresetsActionsApi", runtime_source)
|
||
self.assertNotIn("class _UserPresetsActionsApiImpl", runtime_source)
|
||
self.assertNotIn("def create_preset(self, *, name", runtime_source)
|
||
self.assertNotIn("def import_preset_from_file(self, *, file_path", runtime_source)
|
||
self.assertNotIn("def activate_preset(self, *, file_name", runtime_source)
|
||
self.assertNotIn("def duplicate_preset(self, *, file_name", runtime_source)
|
||
self.assertNotIn("def open_presets_info(self)", runtime_source)
|
||
self.assertNotIn(
|
||
"presets.ui.common.user_presets_page_runtime",
|
||
inspect.getsource(presets_feature_facade),
|
||
)
|
||
|
||
def test_user_presets_item_file_actions_run_through_worker(self) -> None:
|
||
worker_source = inspect.getsource(UserPresetItemActionWorker.run)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_item_action)
|
||
start_source = inspect.getsource(UserPresetsPageBase._start_preset_item_action_worker)
|
||
create_worker_source = inspect.getsource(UserPresetsPageBase.create_preset_item_action_worker)
|
||
|
||
for method in (
|
||
UserPresetsPageBase._on_duplicate_preset,
|
||
UserPresetsPageBase._on_reset_preset,
|
||
UserPresetsPageBase._on_delete_preset,
|
||
UserPresetsPageBase._on_export_preset,
|
||
):
|
||
source = inspect.getsource(method)
|
||
self.assertIn("_request_preset_item_action", source)
|
||
self.assertNotIn("duplicate_preset_action", source)
|
||
self.assertNotIn("reset_preset_action", source)
|
||
self.assertNotIn("delete_preset_action", source)
|
||
self.assertNotIn("export_preset_action", source)
|
||
self.assertNotIn(".duplicate_preset(", source)
|
||
self.assertNotIn(".reset_preset_to_builtin(", source)
|
||
self.assertNotIn(".delete_preset(", source)
|
||
self.assertNotIn(".export_preset(", source)
|
||
|
||
delete_source = inspect.getsource(UserPresetsPageBase._on_delete_preset)
|
||
self.assertIn("_is_builtin_preset_file", delete_source)
|
||
self.assertNotIn("_storage_api().is_builtin_preset_file", delete_source)
|
||
self.assertIn("_start_preset_item_action_worker", request_source)
|
||
self.assertIn("create_preset_item_action_worker", start_source)
|
||
self.assertNotIn("UserPresetItemActionWorker", create_worker_source)
|
||
self.assertIn("_create_preset_item_action_worker_fn", create_worker_source)
|
||
self.assertIn("self._duplicate_preset", worker_source)
|
||
self.assertIn("self._reset_preset_to_builtin", worker_source)
|
||
self.assertIn("self._delete_preset", worker_source)
|
||
self.assertIn("self._export_preset", worker_source)
|
||
self.assertNotIn("actions_api.", worker_source)
|
||
|
||
def test_user_presets_import_and_reset_all_run_through_worker(self) -> None:
|
||
import_source = inspect.getsource(UserPresetsPageBase._on_import_clicked)
|
||
reset_source = inspect.getsource(UserPresetsPageBase._on_reset_all_presets_clicked)
|
||
|
||
self.assertTrue(hasattr(user_presets_action_workers, "UserPresetBulkActionWorker"))
|
||
worker_source = inspect.getsource(user_presets_action_workers.UserPresetBulkActionWorker.run)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_bulk_action)
|
||
start_source = inspect.getsource(UserPresetsPageBase._start_preset_bulk_action_worker)
|
||
create_worker_source = inspect.getsource(UserPresetsPageBase.create_preset_bulk_action_worker)
|
||
|
||
for source in (import_source, reset_source):
|
||
self.assertIn("_request_preset_bulk_action", source)
|
||
self.assertNotIn("import_preset_action", source)
|
||
self.assertNotIn("run_reset_all_presets_action", source)
|
||
self.assertNotIn(".import_preset_from_file(", source)
|
||
self.assertNotIn(".reset_all_presets(", source)
|
||
|
||
self.assertIn("_start_preset_bulk_action_worker", request_source)
|
||
self.assertIn("create_preset_bulk_action_worker", start_source)
|
||
self.assertNotIn("UserPresetBulkActionWorker", create_worker_source)
|
||
self.assertIn("_create_preset_bulk_action_worker_fn", create_worker_source)
|
||
self.assertIn("self._import_preset_from_file", worker_source)
|
||
self.assertIn("self._reset_all_presets", worker_source)
|
||
self.assertNotIn("actions_api.", worker_source)
|
||
|
||
def test_user_presets_create_and_rename_run_through_worker(self) -> None:
|
||
create_source = inspect.getsource(UserPresetsPageBase._show_inline_action_create)
|
||
rename_source = inspect.getsource(UserPresetsPageBase._show_inline_action_rename)
|
||
|
||
self.assertTrue(hasattr(user_presets_action_workers, "UserPresetEditActionWorker"))
|
||
worker_source = inspect.getsource(user_presets_action_workers.UserPresetEditActionWorker.run)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_edit_action)
|
||
start_source = inspect.getsource(UserPresetsPageBase._start_preset_edit_action_worker)
|
||
create_worker_source = inspect.getsource(UserPresetsPageBase.create_preset_edit_action_worker)
|
||
|
||
for source in (create_source, rename_source):
|
||
body_source = "\n".join(source.splitlines()[1:])
|
||
self.assertIn("_request_preset_edit_action", source)
|
||
self.assertNotIn("show_inline_action_create(", body_source)
|
||
self.assertNotIn("show_inline_action_rename(", body_source)
|
||
self.assertNotIn(".create_preset(", source)
|
||
self.assertNotIn(".rename_preset(", source)
|
||
|
||
self.assertIn("_start_preset_edit_action_worker", request_source)
|
||
self.assertIn("create_preset_edit_action_worker", start_source)
|
||
self.assertNotIn("UserPresetEditActionWorker", create_worker_source)
|
||
self.assertIn("_create_preset_edit_action_worker_fn", create_worker_source)
|
||
self.assertIn("self._create_preset", worker_source)
|
||
self.assertIn("self._rename_preset", worker_source)
|
||
self.assertNotIn("actions_api.", worker_source)
|
||
|
||
def test_user_presets_info_links_open_through_worker(self) -> None:
|
||
from presets.ui.common import user_presets_item_actions_workflow
|
||
|
||
info_source = inspect.getsource(UserPresetsPageBase._open_presets_info)
|
||
post_source = inspect.getsource(UserPresetsPageBase._open_new_configs_post)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_link_action)
|
||
cleanup_source = inspect.getsource(UserPresetsPageBase._stop_action_workers_for_cleanup)
|
||
item_actions_source = inspect.getsource(user_presets_item_actions_workflow)
|
||
|
||
self.assertTrue(hasattr(user_presets_action_workers, "UserPresetLinkActionWorker"))
|
||
worker_source = inspect.getsource(user_presets_action_workers.UserPresetLinkActionWorker.run)
|
||
create_worker_source = inspect.getsource(UserPresetsPageBase.create_preset_link_action_worker)
|
||
|
||
for source in (info_source, post_source):
|
||
self.assertIn("_request_preset_link_action", source)
|
||
self.assertNotIn("open_presets_info_action", source)
|
||
self.assertNotIn("open_new_configs_post_action", source)
|
||
|
||
self.assertIn("create_preset_link_action_worker", request_source)
|
||
self.assertNotIn("UserPresetLinkActionWorker", create_worker_source)
|
||
self.assertIn("_create_preset_link_action_worker_fn", create_worker_source)
|
||
self.assertIn("self._open_presets_info", worker_source)
|
||
self.assertIn("self._open_new_configs_post", worker_source)
|
||
self.assertNotIn("actions_api.", worker_source)
|
||
self.assertNotIn("open_presets_info_action", item_actions_source)
|
||
self.assertNotIn("open_new_configs_post_action", item_actions_source)
|
||
self.assertNotIn("actions_api.open_presets_info", item_actions_source)
|
||
self.assertNotIn("actions_api.open_new_configs_post", item_actions_source)
|
||
self.assertIn("_preset_link_action_runtime", cleanup_source)
|
||
self.assertIn(".stop(", cleanup_source)
|
||
self.assertIn(".cancel()", cleanup_source)
|
||
|
||
def test_user_presets_storage_actions_run_through_worker(self) -> None:
|
||
pin_source = inspect.getsource(UserPresetsPageBase._on_toggle_pin_preset)
|
||
rating_source = inspect.getsource(UserPresetsPageBase._show_rating_menu)
|
||
rating_menu_source = inspect.getsource(preset_rating_menu.show_preset_rating_menu)
|
||
move_source = inspect.getsource(UserPresetsPageBase._move_preset_by_step)
|
||
drop_source = inspect.getsource(UserPresetsPageBase._on_item_dropped)
|
||
finished_source = inspect.getsource(UserPresetsPageBase._on_preset_storage_action_finished)
|
||
|
||
self.assertTrue(hasattr(user_presets_action_workers, "UserPresetStorageActionWorker"))
|
||
worker_source = inspect.getsource(user_presets_action_workers.UserPresetStorageActionWorker.run)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_storage_action)
|
||
start_source = inspect.getsource(UserPresetsPageBase._start_preset_storage_action_worker)
|
||
create_source = inspect.getsource(UserPresetsPageBase.create_preset_storage_action_worker)
|
||
runtime_source = inspect.getsource(user_presets_page_runtime.UserPresetsPageRuntime)
|
||
|
||
for source in (pin_source, rating_source, move_source, drop_source):
|
||
self.assertIn("_request_preset_storage_action", source)
|
||
self.assertNotIn("toggle_pin_preset_action", source)
|
||
self.assertNotIn("move_preset_by_step_action", source)
|
||
self.assertNotIn("handle_item_dropped_action", source)
|
||
self.assertNotIn("set_preset_rating(", source)
|
||
self.assertNotIn(".toggle_preset_pin(", source)
|
||
self.assertNotIn(".move_preset_by_step(", source)
|
||
self.assertNotIn(".move_preset_on_drop(", source)
|
||
|
||
self.assertNotIn("set_preset_rating(", rating_menu_source)
|
||
self.assertNotIn("get_preset_item_meta", rating_menu_source)
|
||
self.assertNotIn("folder_scope", rating_menu_source)
|
||
self.assertIn("current_rating=", rating_source)
|
||
self.assertIn("cached_presets_metadata", rating_source)
|
||
self.assertIn("_update_cached_preset_rating", finished_source)
|
||
self.assertIn("_start_preset_storage_action_worker", request_source)
|
||
self.assertIn("create_preset_storage_action_worker", start_source)
|
||
self.assertNotIn("UserPresetStorageActionWorker", create_source)
|
||
self.assertIn("_create_preset_storage_action_worker_fn", create_source)
|
||
self.assertNotIn("from presets.folders import toggle_preset_pin", runtime_source)
|
||
self.assertNotIn("from presets.folders import set_preset_rating", runtime_source)
|
||
self.assertNotIn("from presets.folders import move_preset_by_step", runtime_source)
|
||
self.assertNotIn("from presets.folders import move_preset_after", runtime_source)
|
||
self.assertIn("self._toggle_preset_pin", worker_source)
|
||
self.assertIn("self._set_preset_rating", worker_source)
|
||
self.assertIn("self._move_preset_by_step", worker_source)
|
||
self.assertIn("self._move_preset_on_drop", worker_source)
|
||
self.assertIn("self._load_folder_state", worker_source)
|
||
self.assertNotIn("storage_api.", worker_source)
|
||
self.assertIn('self._action == "pin"', worker_source)
|
||
self.assertIn("destination_kind", worker_source)
|
||
self.assertIn("destination_folder_key", worker_source)
|
||
self.assertIn('context["folder_state"]', worker_source)
|
||
self.assertIn("update_cached_folder_state", finished_source)
|
||
|
||
def test_user_presets_queued_write_action_restarts_after_worker_signal(self) -> None:
|
||
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
|
||
page._preset_write_action_running = Mock(return_value=False)
|
||
page._pending_preset_write_actions = [
|
||
{
|
||
"kind": "storage",
|
||
"action": "rating",
|
||
"name": "Default.txt",
|
||
"display_name": "Default",
|
||
"rating": 5,
|
||
"direction": 0,
|
||
"cached_metadata": None,
|
||
"source_kind": "",
|
||
"source_id": "",
|
||
"destination_kind": "",
|
||
"destination_id": "",
|
||
"destination_folder_key": "",
|
||
"file_name": "",
|
||
"file_path": "",
|
||
}
|
||
]
|
||
page._pending_preset_storage_actions = [{"action": "rating"}]
|
||
page._start_preset_storage_action_worker = Mock()
|
||
callbacks = []
|
||
|
||
with patch(
|
||
"presets.ui.common.user_presets_page.QTimer.singleShot",
|
||
side_effect=lambda _delay, callback: callbacks.append(callback),
|
||
):
|
||
self.assertTrue(UserPresetsPageBase._start_next_preset_write_action(page))
|
||
|
||
page._start_preset_storage_action_worker.assert_not_called()
|
||
self.assertEqual(len(callbacks), 1)
|
||
|
||
callbacks[0]()
|
||
|
||
page._start_preset_storage_action_worker.assert_called_once_with(
|
||
"rating",
|
||
name="Default.txt",
|
||
display_name="Default",
|
||
rating=5,
|
||
direction=0,
|
||
cached_metadata=None,
|
||
source_kind="",
|
||
source_id="",
|
||
destination_kind="",
|
||
destination_id="",
|
||
destination_folder_key="",
|
||
)
|
||
|
||
def test_user_presets_folder_actions_run_through_worker(self) -> None:
|
||
toggle_source = inspect.getsource(UserPresetsPageBase._on_toggle_folder)
|
||
menu_source = inspect.getsource(UserPresetsPageBase._show_folder_menu)
|
||
folder_menu_source = inspect.getsource(preset_folder_menu.show_preset_folder_menu)
|
||
|
||
self.assertTrue(hasattr(user_presets_action_workers, "UserPresetFolderActionWorker"))
|
||
worker_source = inspect.getsource(user_presets_action_workers.UserPresetFolderActionWorker.run)
|
||
request_source = inspect.getsource(UserPresetsPageBase._request_preset_folder_action)
|
||
action_finished_source = inspect.getsource(UserPresetsPageBase._on_preset_folder_action_finished)
|
||
finished_source = inspect.getsource(UserPresetsPageBase._on_preset_folder_action_worker_finished)
|
||
next_write_source = inspect.getsource(UserPresetsPageBase._start_next_preset_write_action)
|
||
show_source = inspect.getsource(UserPresetsPageBase._show_folder_menu)
|
||
|
||
for source in (toggle_source, menu_source):
|
||
self.assertIn("_request_preset_folder_action", source)
|
||
self.assertNotIn("set_preset_folder_collapsed(", source)
|
||
self.assertNotIn("create_preset_folder(", source)
|
||
self.assertNotIn("rename_preset_folder(", source)
|
||
self.assertNotIn("delete_preset_folder(", source)
|
||
self.assertNotIn("reset_preset_folders(", source)
|
||
|
||
for forbidden in (
|
||
"create_preset_folder(",
|
||
"rename_preset_folder(",
|
||
"delete_preset_folder(",
|
||
"move_preset_folder_by_step(",
|
||
"set_preset_folder_collapsed(",
|
||
"reset_preset_folders(",
|
||
):
|
||
self.assertNotIn(forbidden, folder_menu_source)
|
||
|
||
self.assertNotIn("load_preset_folder_state", folder_menu_source)
|
||
self.assertIn("folder_state", folder_menu_source)
|
||
self.assertIn('"load_state"', show_source)
|
||
self.assertIn("self._create_preset_folder", worker_source)
|
||
self.assertIn("self._rename_preset_folder", worker_source)
|
||
self.assertIn("self._delete_preset_folder", worker_source)
|
||
self.assertIn("self._move_preset_folder_by_step", worker_source)
|
||
self.assertIn("self._set_preset_folder_collapsed", worker_source)
|
||
self.assertIn("self._reset_preset_folders", worker_source)
|
||
self.assertIn("self._load_preset_folder_state", worker_source)
|
||
self.assertNotIn("from presets.folders", worker_source)
|
||
self.assertNotIn("from presets.folders import", inspect.getsource(UserPresetsPageBase))
|
||
self.assertIn('context["folder_state"]', worker_source)
|
||
self.assertIn("_queue_preset_folder_action", request_source)
|
||
self.assertIn(
|
||
"_preset_folder_action_state_obj()",
|
||
inspect.getsource(UserPresetsPageBase._queue_preset_folder_action),
|
||
)
|
||
self.assertIn("_schedule_next_preset_write_action_after_finish", finished_source)
|
||
self.assertIn("schedule_next_after_finish", inspect.getsource(UserPresetsPageBase._schedule_next_preset_write_action_after_finish))
|
||
self.assertIn("_preset_folder_action_state_obj().pop_next()", next_write_source)
|
||
self.assertIn("update_cached_folder_state", action_finished_source)
|
||
self.assertIn("show_menu", action_finished_source)
|
||
self.assertIn("create_preset_folder_action_worker", request_source)
|
||
create_source = inspect.getsource(UserPresetsPageBase.create_preset_folder_action_worker)
|
||
self.assertNotIn("UserPresetFolderActionWorker", create_source)
|
||
self.assertIn("_create_preset_folder_action_worker_fn", create_source)
|
||
|
||
def test_profile_folder_actions_run_through_worker(self) -> None:
|
||
toggle_source = inspect.getsource(PresetSetupPageBase._on_folder_toggled)
|
||
menu_source = inspect.getsource(PresetSetupPageBase._on_folder_context_requested)
|
||
folder_menu_source = inspect.getsource(profile_folder_menu.show_profile_folder_menu)
|
||
|
||
self.assertTrue(hasattr(profile_setup_loader, "ProfileFolderActionWorker"))
|
||
worker_source = inspect.getsource(profile_setup_loader.ProfileFolderActionWorker.run)
|
||
request_source = inspect.getsource(ProfileFolderController._request_profile_folder_action)
|
||
action_finished_source = inspect.getsource(ProfileFolderController._on_profile_folder_action_finished)
|
||
finished_source = inspect.getsource(ProfileFolderController._on_profile_folder_action_worker_finished)
|
||
show_source = inspect.getsource(PresetSetupPageBase._on_folder_context_requested)
|
||
|
||
for source in (toggle_source, menu_source):
|
||
self.assertIn("_request_profile_folder_action", source)
|
||
self.assertNotIn("set_profile_folder_collapsed(", source)
|
||
self.assertNotIn("create_profile_folder(", source)
|
||
self.assertNotIn("rename_profile_folder(", source)
|
||
self.assertNotIn("delete_profile_folder(", source)
|
||
self.assertNotIn("reset_profile_folders(", source)
|
||
|
||
for forbidden in (
|
||
"create_profile_folder(",
|
||
"rename_profile_folder(",
|
||
"delete_profile_folder(",
|
||
"move_profile_folder_by_step(",
|
||
"set_profile_folder_collapsed(",
|
||
"reset_profile_folders(",
|
||
):
|
||
self.assertNotIn(forbidden, folder_menu_source)
|
||
|
||
self.assertNotIn("load_profile_folder_state", folder_menu_source)
|
||
self.assertIn("folder_state", folder_menu_source)
|
||
self.assertIn('"load_state"', show_source)
|
||
self.assertIn("self._create_profile_folder", worker_source)
|
||
self.assertIn("self._rename_profile_folder", worker_source)
|
||
self.assertIn("self._delete_profile_folder", worker_source)
|
||
self.assertIn("self._move_profile_folder_by_step", worker_source)
|
||
self.assertIn("self._set_profile_folder_collapsed", worker_source)
|
||
self.assertIn("self._reset_profile_folders", worker_source)
|
||
self.assertIn("self._load_profile_folder_state", worker_source)
|
||
self.assertNotIn("from profile.folders", worker_source)
|
||
self.assertNotIn("from profile.folders import", inspect.getsource(PresetSetupPageBase))
|
||
self.assertIn("_queue_profile_folder_action", request_source)
|
||
self.assertIn(
|
||
"_profile_folder_action_state_obj()",
|
||
inspect.getsource(ProfileFolderController._queue_profile_folder_action),
|
||
)
|
||
self.assertIn("_profile_folder_action_state_obj().pop_next()", finished_source)
|
||
self.assertIn("show_menu", action_finished_source)
|
||
self.assertIn("_create_profile_folder_action_worker", request_source)
|
||
create_source = inspect.getsource(PresetSetupPageBase._create_profile_folder_action_worker)
|
||
self.assertNotIn("ProfileFolderActionWorker", create_source)
|
||
self.assertIn("_create_profile_folder_action_worker_fn", create_source)
|
||
|
||
def test_profile_preset_write_queue_restarts_after_worker_signal_returns(self) -> None:
|
||
context_finished = inspect.getsource(PresetWriteQueue._on_profile_context_action_worker_finished)
|
||
move_finished = inspect.getsource(PresetWriteQueue._on_profile_move_worker_finished)
|
||
create_finished = inspect.getsource(PresetWriteQueue._on_user_profile_create_worker_finished)
|
||
update_finished = inspect.getsource(PresetWriteQueue._on_user_profile_update_worker_finished)
|
||
delete_finished = inspect.getsource(PresetWriteQueue._on_user_profile_delete_worker_finished)
|
||
helper_source = inspect.getsource(PresetWriteQueue._schedule_next_profile_preset_write_operation_after_finish)
|
||
schedule_source = inspect.getsource(PresetWriteQueue._schedule_next_profile_preset_write_operation_start)
|
||
run_source = inspect.getsource(PresetWriteQueue._run_scheduled_profile_preset_write_operation_start)
|
||
|
||
for source in (context_finished, move_finished, create_finished, update_finished, delete_finished):
|
||
self.assertIn("_schedule_next_profile_preset_write_operation_after_finish", source)
|
||
self.assertNotIn("_start_next_profile_preset_write_operation()", source)
|
||
|
||
self.assertIn("schedule_next_after_finish", helper_source)
|
||
self.assertIn("_accept_current_preset_setup_worker_finished", helper_source)
|
||
self.assertIn("QTimer.singleShot", schedule_source)
|
||
self.assertIn("_run_scheduled_profile_preset_write_operation_start", schedule_source)
|
||
self.assertIn("_start_next_profile_preset_write_operation", run_source)
|
||
|
||
def test_profile_folder_action_queue_restarts_after_worker_signal_returns(self) -> None:
|
||
finished_source = inspect.getsource(ProfileFolderController._on_profile_folder_action_worker_finished)
|
||
schedule_source = inspect.getsource(ProfileFolderController._schedule_profile_folder_action_start)
|
||
run_source = inspect.getsource(ProfileFolderController._run_scheduled_profile_folder_action_start)
|
||
|
||
self.assertIn("_profile_folder_action_state_obj().pop_next()", finished_source)
|
||
self.assertIn("_schedule_profile_folder_action_start", finished_source)
|
||
self.assertNotIn("_request_profile_folder_action(", finished_source)
|
||
self.assertIn("QTimer.singleShot", schedule_source)
|
||
self.assertIn("_run_scheduled_profile_folder_action_start", schedule_source)
|
||
self.assertIn("_request_profile_folder_action", run_source)
|
||
|
||
def test_profile_folder_action_queue_uses_shared_queued_worker_state(self) -> None:
|
||
from types import SimpleNamespace
|
||
|
||
from ui.queued_worker_state import QueuedWorkerState
|
||
|
||
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
|
||
page._profile_folder_action_runtime = SimpleNamespace(is_running=lambda: False)
|
||
|
||
init_source = inspect.getsource(PresetSetupPageBase.__init__)
|
||
queue_source = inspect.getsource(ProfileFolderController._queue_profile_folder_action)
|
||
cleanup_source = inspect.getsource(PresetSetupPageBase.cleanup)
|
||
|
||
self.assertTrue(hasattr(PresetSetupPageBase, "_profile_folder_action_state_obj"))
|
||
self.assertIsInstance(page._profile_folder_action_state_obj(), QueuedWorkerState)
|
||
self.assertIn("_profile_folder_action_state = QueuedWorkerState", init_source)
|
||
self.assertIn("_profile_folder_action_state_obj()", queue_source)
|
||
self.assertIn("_profile_folder_action_state_obj().reset()", cleanup_source)
|
||
self.assertNotIn("self._profile_folder_action_pending: list", init_source)
|
||
self.assertNotIn("self._profile_folder_action_start_scheduled = False", init_source)
|
||
|
||
def test_profile_move_updates_visible_list_locally_after_worker(self) -> None:
|
||
finished_source = inspect.getsource(PresetWriteQueue._on_profile_move_finished)
|
||
local_source = inspect.getsource(PresetSetupPageBase._apply_profile_move_locally)
|
||
list_source = inspect.getsource(ProfilesList)
|
||
|
||
self.assertIn("_apply_profile_move_locally", finished_source)
|
||
self.assertIn("refresh_from_preset_switch", finished_source)
|
||
self.assertLess(finished_source.index("_apply_profile_move_locally"), finished_source.index("refresh_from_preset_switch"))
|
||
self.assertIn("move_profile_item", list_source)
|
||
self.assertIn("move_profile_item", local_source)
|
||
|
||
def test_profile_commands_reuse_service_cache(self) -> None:
|
||
source = inspect.getsource(profile_commands._profile_preset_service)
|
||
|
||
self.assertIn("_preset_service_cache", source)
|
||
self.assertIn("cache[key]", source)
|
||
|
||
def test_profile_service_exposes_cached_profile_payload_without_rebuilding(self) -> None:
|
||
service_source = inspect.getsource(ProfilePresetService.get_cached_profile_list_entry)
|
||
payload_source = inspect.getsource(ProfilePresetService.get_cached_profile_list)
|
||
revision_source = inspect.getsource(ProfilePresetService._current_profile_list_revision)
|
||
command_source = inspect.getsource(profile_commands.get_cached_profile_list)
|
||
|
||
self.assertIn("_profile_list_lock", service_source)
|
||
self.assertIn("acquire(blocking=False)", service_source)
|
||
self.assertIn("_profile_list_snapshot_revision", service_source)
|
||
self.assertIn("return list_revision, snapshot", service_source)
|
||
self.assertNotIn("_list_profiles_locked(", service_source)
|
||
self.assertIn("get_cached_profile_list_entry", payload_source)
|
||
self.assertIn("return payload", payload_source)
|
||
self.assertIn("_selected_preset_revision", revision_source)
|
||
self.assertIn("load_profile_folder_state", revision_source)
|
||
self.assertIn("get_cached_profile_list", command_source)
|
||
|
||
def test_preset_setup_page_always_uses_worker_for_profile_payload(self) -> None:
|
||
source = inspect.getsource(ProfilePayloadController._request_profiles_payload)
|
||
|
||
self.assertNotIn("get_cached_profile_list", source)
|
||
self.assertNotIn("_apply_cached_profile_payload", source)
|
||
self.assertIn("create_profile_list_load_worker", source)
|
||
self.assertNotIn("_show_loading_skeleton", source)
|
||
|
||
def test_preset_setup_force_refresh_still_uses_worker_path(self) -> None:
|
||
source = inspect.getsource(ProfilePayloadController._request_profiles_payload)
|
||
|
||
before_worker = source.split("worker =", 1)[0]
|
||
|
||
self.assertNotIn("get_cached_profile_list", before_worker)
|
||
self.assertNotIn("if not force:", before_worker)
|
||
self.assertNotIn("_apply_cached_profile_payload", before_worker)
|
||
|
||
def test_profile_service_has_selected_preset_snapshot(self) -> None:
|
||
source = inspect.getsource(ProfilePresetService.load_selected_preset)
|
||
helper_source = inspect.getsource(ProfilePresetService._load_selected_preset_for_revision)
|
||
|
||
self.assertIn("_selected_preset_revision", source)
|
||
self.assertIn("_selected_preset_snapshot", helper_source)
|
||
|
||
def test_profile_list_worker_logs_full_profile_payload_duration(self) -> None:
|
||
source = inspect.getsource(ProfileListLoadWorker.run)
|
||
module_source = inspect.getsource(importlib.import_module("profile.profile_list_loader"))
|
||
|
||
self.assertIn("time.perf_counter", source)
|
||
self.assertIn("profile_feature.worker.list_profiles.total", source)
|
||
self.assertIn("log_ui_timing_since", module_source)
|
||
self.assertNotIn("PROFILE_TIMING_LOG_LEVEL", module_source)
|
||
|
||
def test_profile_list_worker_accepts_warmed_load_result(self) -> None:
|
||
source = inspect.getsource(ProfileListLoadWorker.run)
|
||
|
||
self.assertIn("ProfileListLoadResult", source)
|
||
self.assertIn("isinstance(payload, ProfileListLoadResult)", source)
|
||
|
||
def test_profile_list_worker_receives_loader_function(self) -> None:
|
||
init_source = inspect.getsource(ProfileListLoadWorker.__init__)
|
||
run_source = inspect.getsource(ProfileListLoadWorker.run)
|
||
|
||
self.assertIn("load_profiles", init_source)
|
||
self.assertIn("self._load_profiles", init_source)
|
||
self.assertNotIn("self._service", init_source)
|
||
self.assertNotIn("self._profile", init_source)
|
||
self.assertNotIn("launch_method", init_source)
|
||
self.assertIn("self._load_profiles()", run_source)
|
||
self.assertNotIn("self._service.list_profiles", run_source)
|
||
self.assertNotIn("self._profile.list_profiles", run_source)
|
||
|
||
def test_profile_list_filter_rebuild_runs_through_worker_runtime(self) -> None:
|
||
import profile.ui.profiles_list as profiles_list_module
|
||
from ui.latest_value_worker_state import LatestValueWorkerState
|
||
|
||
page = ProfilesList.__new__(ProfilesList)
|
||
page._view_state_runtime = Mock()
|
||
list_source = inspect.getsource(ProfilesList)
|
||
init_source = inspect.getsource(ProfilesList.__init__)
|
||
build_source = inspect.getsource(ProfilesList.build_profiles)
|
||
update_source = inspect.getsource(ProfilesList.update_profiles)
|
||
search_source = inspect.getsource(ProfilesList.set_search_query)
|
||
request_source = inspect.getsource(ProfilesList._request_view_state_rebuild)
|
||
finished_source = inspect.getsource(ProfilesList._on_view_state_worker_finished)
|
||
type_source = inspect.getsource(ProfilesList._apply_profile_type_filter)
|
||
toggle_source = inspect.getsource(ProfilesList._on_delegate_action)
|
||
expand_source = inspect.getsource(ProfilesList.expand_all)
|
||
collapse_source = inspect.getsource(ProfilesList.collapse_all)
|
||
all_groups_source = inspect.getsource(ProfilesList._request_all_groups_expanded)
|
||
folder_state_source = inspect.getsource(ProfilesList.apply_profile_folder_state)
|
||
replace_item_source = inspect.getsource(ProfilesList.replace_profile_item)
|
||
add_item_source = inspect.getsource(ProfilesList.add_profile_item)
|
||
remove_item_source = inspect.getsource(ProfilesList.remove_profile_item)
|
||
move_item_source = inspect.getsource(ProfilesList.move_profile_item)
|
||
worker_source = inspect.getsource(profiles_list_module.ProfileListViewStateWorker.run)
|
||
|
||
self.assertIn("OneShotWorkerRuntime", list_source)
|
||
self.assertIn("LatestValueWorkerState", list_source)
|
||
self.assertTrue(hasattr(ProfilesList, "_view_state_state_obj"))
|
||
self.assertIsInstance(page._view_state_state_obj(), LatestValueWorkerState)
|
||
self.assertIn("_view_state_state = LatestValueWorkerState", init_source)
|
||
self.assertNotIn("_view_state_runtime_worker = None", init_source)
|
||
self.assertNotIn("_view_state_rebuild_pending = False", init_source)
|
||
self.assertIn("_view_state_state_obj()", request_source)
|
||
self.assertIn("_view_state_state_obj()", finished_source)
|
||
self.assertIn("_request_view_state_rebuild", build_source)
|
||
self.assertIn("_request_view_state_rebuild", update_source)
|
||
self.assertIn("_request_view_state_rebuild", search_source)
|
||
self.assertIn("_request_view_state_rebuild", type_source)
|
||
self.assertIn("_request_view_state_rebuild", toggle_source)
|
||
self.assertIn("_request_all_groups_expanded", expand_source)
|
||
self.assertIn("_request_all_groups_expanded", collapse_source)
|
||
self.assertIn("_request_view_state_rebuild", all_groups_source)
|
||
self.assertIn("_request_view_state_rebuild", folder_state_source)
|
||
self.assertIn("_request_view_state_rebuild", replace_item_source)
|
||
self.assertIn("_request_view_state_rebuild", add_item_source)
|
||
self.assertIn("_request_view_state_rebuild", remove_item_source)
|
||
self.assertIn("_request_view_state_rebuild", move_item_source)
|
||
self.assertNotIn("self._model.set_profiles", build_source)
|
||
self.assertNotIn("self._model.update_profiles", update_source)
|
||
self.assertNotIn("self._model.set_search_query", search_source)
|
||
self.assertNotIn("self._model.set_active_profile_types", type_source)
|
||
self.assertNotIn("self._model.set_group_expanded", toggle_source)
|
||
self.assertNotIn("self._model.set_all_groups_expanded", expand_source)
|
||
self.assertNotIn("self._model.set_all_groups_expanded", collapse_source)
|
||
self.assertNotIn("self._model.apply_folder_state", folder_state_source)
|
||
self.assertNotIn("self._model.replace_profile", replace_item_source)
|
||
self.assertNotIn("self._model.add_profile", add_item_source)
|
||
self.assertNotIn("self._model.remove_profile", remove_item_source)
|
||
self.assertNotIn("self._model.move_profile", move_item_source)
|
||
self.assertIn("build_profile_list_view_state", worker_source)
|
||
self.assertIn("folder_state", worker_source)
|
||
|
||
def test_profile_feature_warms_profile_list_without_mass_setup_warm(self) -> None:
|
||
source = inspect.getsource(ProfileFeature.warm_profile_list)
|
||
class_source = inspect.getsource(ProfileFeature)
|
||
|
||
self.assertIn("service.list_profiles", source)
|
||
self.assertIn("build_profile_list_view_state", source)
|
||
self.assertIn("ProfileListLoadResult", source)
|
||
self.assertIn("profile_warmup.list_profiles", source)
|
||
self.assertIn("profile_warmup.view_state", source)
|
||
self.assertNotIn("warm_profile_setups", source)
|
||
self.assertNotIn("profile_warmup.setup_payloads", source)
|
||
self.assertNotIn("_profile_list_load_result_cache", class_source)
|
||
|
||
def test_profile_feature_profile_list_worker_reads_service_cache(self) -> None:
|
||
source = inspect.getsource(ProfileFeature.create_profile_list_load_worker)
|
||
|
||
self.assertIn("view_state_options", source)
|
||
self.assertIn("active_profile_types", source)
|
||
self.assertIn("search_query", source)
|
||
self.assertIn("group_expanded", source)
|
||
self.assertIn("build_profile_list_view_state", source)
|
||
self.assertIn("service.list_profiles()", source)
|
||
self.assertNotIn("_profile_list_load_result", source)
|
||
|
||
def test_profile_service_logs_profile_payload_stages(self) -> None:
|
||
source = inspect.getsource(ProfilePresetService._list_profiles_locked)
|
||
timing_source = inspect.getsource(ProfilePresetService._log_timing)
|
||
|
||
self.assertIn("profile_feature.strategy_catalogs.load", source)
|
||
self.assertIn("profile_feature.templates.load", source)
|
||
self.assertIn("profile_feature.folder_state.load", source)
|
||
self.assertIn("profile_feature.sources.build", source)
|
||
self.assertIn("profile_feature.profile_list_item.build", source)
|
||
self.assertIn("log_ui_timing_since", timing_source)
|
||
self.assertIn("important=True", timing_source)
|
||
|
||
def test_profile_worker_yields_during_large_payload_builds(self) -> None:
|
||
source = inspect.getsource(ProfilePresetService._list_profiles_locked)
|
||
helper_source = inspect.getsource(ProfilePresetService._yield_profile_payload_worker)
|
||
|
||
self.assertIn("_yield_profile_payload_worker", source)
|
||
self.assertIn("time.sleep(0)", helper_source)
|
||
|
||
def test_profile_service_serializes_profile_list_snapshot_builds(self) -> None:
|
||
source = inspect.getsource(ProfilePresetService.list_profiles)
|
||
|
||
self.assertIn("_profile_list_lock", source)
|
||
self.assertIn("_list_profiles_locked", source)
|
||
|
||
def test_preset_setup_page_logs_profile_list_ui_apply_stages(self) -> None:
|
||
source = inspect.getsource(PresetSetupPageBase._apply_payload)
|
||
timing_source = inspect.getsource(PresetSetupPageBase._log_ui_timing)
|
||
|
||
self.assertIn("profile_ui.apply_payload.total", source)
|
||
self.assertIn("profile_ui.profile_list.create", source)
|
||
self.assertIn("profile_ui.profile_list.build", source)
|
||
self.assertIn("profile_ui.profile_list.attach", source)
|
||
self.assertIn("log_ui_timing_since", timing_source)
|
||
self.assertIn("important=label in", timing_source)
|
||
|
||
def test_slow_navigation_pages_log_local_timing_stages(self) -> None:
|
||
appearance_source = inspect.getsource(AppearancePage._build_ui)
|
||
appearance_lower_source = inspect.getsource(AppearancePage._ensure_lower_sections_built)
|
||
logs_refresh_source = inspect.getsource(LogsPage._refresh_logs_list)
|
||
logs_stats_source = inspect.getsource(LogsPage._update_stats)
|
||
telegram_init_source = inspect.getsource(TelegramProxyPage.__init__)
|
||
telegram_loaded_source = inspect.getsource(TelegramProxyPage._on_initial_state_loaded)
|
||
telegram_apply_source = inspect.getsource(TelegramProxyPage._apply_initial_settings_state)
|
||
blockcheck_source = inspect.getsource(BlockcheckPage._build_ui)
|
||
blockcheck_initial_source = inspect.getsource(BlockcheckPage._on_initial_state_loaded)
|
||
hosts_activation_source = inspect.getsource(HostsPage.on_page_activated)
|
||
hosts_rebuild_source = inspect.getsource(HostsPage._rebuild_services_selectors)
|
||
|
||
self.assertIn("appearance_ui.build.total", appearance_source)
|
||
self.assertIn("appearance_ui.accent_section.build", appearance_lower_source)
|
||
self.assertIn("appearance_ui.lower_sections.build", appearance_lower_source)
|
||
self.assertIn("logs_ui.refresh_logs_list.total", logs_refresh_source)
|
||
self.assertIn("logs_ui.update_stats.total", logs_stats_source)
|
||
self.assertIn("_request_initial_state_load", telegram_init_source)
|
||
self.assertIn("telegram_proxy_ui.initial_state.load", telegram_loaded_source)
|
||
self.assertIn("telegram_proxy_ui.settings.apply", telegram_apply_source)
|
||
self.assertIn("blockcheck_ui.initial_state.load", blockcheck_initial_source)
|
||
self.assertIn("blockcheck_ui.build.total", blockcheck_source)
|
||
self.assertIn("blockcheck_ui.domain_chips.apply", blockcheck_source)
|
||
self.assertIn("hosts_ui.activation.total", hosts_activation_source)
|
||
self.assertIn("hosts_ui.services.rebuild", hosts_rebuild_source)
|
||
|
||
def test_appearance_page_initial_state_is_single_backend_plan(self) -> None:
|
||
build_source = inspect.getsource(AppearancePage._build_ui)
|
||
ensure_source = inspect.getsource(AppearancePage._ensure_lower_sections_built)
|
||
settings_source = inspect.getsource(appearance_settings.load_page_initial_state)
|
||
|
||
self.assertIn("appearance_settings.load_page_initial_state()", build_source)
|
||
self.assertNotIn("load_page_initial_state", ensure_source)
|
||
self.assertNotIn("_request_initial_state_load", build_source)
|
||
self.assertIn("self._initial_state_plan = initial_state", build_source)
|
||
self.assertIn("initial_state.ui_language = self._ui_language", build_source)
|
||
self.assertNotIn("appearance_settings.load_ui_language()", build_source)
|
||
self.assertNotIn("appearance_settings.load_window_opacity()", build_source)
|
||
self.assertNotIn("self._load_accent_color()", build_source)
|
||
self.assertNotIn("self._load_extra_accent_settings()", build_source)
|
||
self.assertNotIn("self._load_performance_settings()", build_source)
|
||
self.assertNotIn("self._load_display_mode()", build_source)
|
||
self.assertNotIn("self._load_bg_preset()", build_source)
|
||
|
||
self.assertIn("read_settings", settings_source)
|
||
self.assertNotIn("get_display_mode", settings_source)
|
||
self.assertNotIn("get_window_opacity", settings_source)
|
||
self.assertNotIn("get_animations_enabled", settings_source)
|
||
|
||
def test_appearance_first_render_uses_direct_initial_state_plan(self) -> None:
|
||
self.assertTrue(hasattr(appearance_workers, "AppearanceInitialStateLoadWorker"))
|
||
appearance_feature = importlib.import_module("app.feature_facades.appearance")
|
||
worker_source = inspect.getsource(appearance_workers.AppearanceInitialStateLoadWorker.run)
|
||
worker_init_source = inspect.getsource(appearance_workers.AppearanceInitialStateLoadWorker.__init__)
|
||
page_source = inspect.getsource(AppearancePage)
|
||
page_init_source = inspect.getsource(AppearancePage.__init__)
|
||
create_source = inspect.getsource(AppearancePage.create_initial_state_load_worker)
|
||
feature_source = inspect.getsource(appearance_feature.AppearanceFeature)
|
||
build_source = inspect.getsource(AppearancePage._build_ui)
|
||
start_source = inspect.getsource(AppearancePage._start_initial_state_load_worker)
|
||
|
||
self.assertIn("load_page_initial_state", worker_source)
|
||
self.assertIn("load_page_initial_state", worker_init_source)
|
||
self.assertIn("appearance_feature", page_init_source)
|
||
self.assertIn("create_initial_state_load_worker", page_source)
|
||
self.assertIn("create_initial_state_load_worker", feature_source)
|
||
self.assertNotIn("AppearanceInitialStateLoadWorker", create_source)
|
||
self.assertIn("self._appearance.create_initial_state_load_worker", create_source)
|
||
self.assertIn("OneShotWorkerRuntime", page_source)
|
||
self.assertIn("_initial_state_load_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("appearance_settings.load_page_initial_state()", build_source)
|
||
self.assertNotIn("_request_initial_state_load", build_source)
|
||
self.assertNotIn("settings.appearance", worker_source)
|
||
|
||
def test_base_page_language_uses_warmed_cache_not_settings_read(self) -> None:
|
||
base_source = inspect.getsource(BasePage._resolve_ui_language)
|
||
navigation_source = inspect.getsource(navigation_text_sync.resolve_ui_language)
|
||
|
||
self.assertIn("peek_warmed_ui_language", base_source)
|
||
self.assertIn("peek_warmed_ui_language", navigation_source)
|
||
self.assertNotIn("load_ui_language", base_source)
|
||
self.assertNotIn("load_ui_language", navigation_source)
|
||
|
||
def test_appearance_page_builds_all_sections_in_initial_pass(self) -> None:
|
||
build_source = inspect.getsource(AppearancePage._build_ui)
|
||
activated_source = inspect.getsource(AppearancePage.on_page_activated)
|
||
schedule_source = inspect.getsource(AppearancePage._schedule_lower_sections_build)
|
||
ensure_source = inspect.getsource(AppearancePage._ensure_lower_sections_built)
|
||
|
||
self.assertIn("_ensure_lower_sections_built(require_visible=False)", build_source)
|
||
self.assertIn("build_holiday_sections", ensure_source)
|
||
self.assertIn("build_opacity_section", ensure_source)
|
||
self.assertIn("build_performance_section", ensure_source)
|
||
self.assertIn("_schedule_lower_sections_build", activated_source)
|
||
self.assertIn("QTimer.singleShot", schedule_source)
|
||
self.assertIn("require_visible", ensure_source)
|
||
|
||
def test_appearance_settings_save_runs_through_worker(self) -> None:
|
||
appearance_feature = importlib.import_module("app.feature_facades.appearance")
|
||
page_source = inspect.getsource(AppearancePage)
|
||
|
||
self.assertTrue(hasattr(appearance_workers, "AppearanceSettingsSaveWorker"))
|
||
worker_source = inspect.getsource(appearance_workers.AppearanceSettingsSaveWorker.run)
|
||
worker_init_source = inspect.getsource(appearance_workers.AppearanceSettingsSaveWorker.__init__)
|
||
create_source = inspect.getsource(AppearancePage.create_appearance_save_worker)
|
||
feature_source = inspect.getsource(appearance_feature.AppearanceFeature)
|
||
request_source = inspect.getsource(AppearancePage._request_appearance_save)
|
||
start_source = inspect.getsource(AppearancePage._start_appearance_save_worker)
|
||
finished_source = inspect.getsource(AppearancePage._on_appearance_save_worker_finished)
|
||
run_scheduled_source = inspect.getsource(AppearancePage._run_scheduled_appearance_save_worker_start)
|
||
|
||
for method_name in (
|
||
"_on_display_mode_changed",
|
||
"_on_ui_language_changed",
|
||
"_on_rkn_background_changed",
|
||
"_on_bg_preset_toggled",
|
||
"_on_mica_changed",
|
||
"_on_opacity_changed",
|
||
"_on_snowflakes_changed",
|
||
"_on_garland_changed",
|
||
"_on_accent_color_changed",
|
||
"_on_follow_windows_accent_changed",
|
||
"_on_tinted_bg_changed",
|
||
"_on_tinted_intensity_changed",
|
||
"set_premium_status",
|
||
"_on_animations_changed",
|
||
"_on_smooth_scroll_changed",
|
||
"_on_editor_smooth_scroll_changed",
|
||
):
|
||
source = inspect.getsource(getattr(AppearancePage, method_name))
|
||
self.assertIn("_request_appearance_save", source)
|
||
self.assertNotIn("appearance_settings.save_", source)
|
||
|
||
self.assertIn("create_appearance_save_worker", page_source)
|
||
self.assertIn("create_appearance_save_worker", feature_source)
|
||
self.assertNotIn("AppearanceSettingsSaveWorker", create_source)
|
||
self.assertIn("self._appearance.create_appearance_save_worker", create_source)
|
||
self.assertIn("_appearance_save_runtime", page_source)
|
||
self.assertIn("QueuedWorkerState", page_source)
|
||
self.assertIn("_appearance_save_state = QueuedWorkerState", page_source)
|
||
self.assertIn("_appearance_save_state_obj", request_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("start_or_queue", request_source)
|
||
self.assertNotIn("_appearance_save_pending.append", page_source)
|
||
self.assertIn("_schedule_appearance_save_worker_start", finished_source)
|
||
self.assertIn("_coalesce_pending_appearance_saves", run_scheduled_source)
|
||
self.assertIn("state.pop_next()", run_scheduled_source)
|
||
self.assertIn("save_display_mode", worker_init_source)
|
||
self.assertIn("save_ui_language", worker_init_source)
|
||
self.assertIn("save_background_preset", worker_init_source)
|
||
self.assertIn("save_mica_enabled", worker_init_source)
|
||
self.assertIn("save_window_opacity", worker_init_source)
|
||
self.assertIn("save_accent_color", worker_init_source)
|
||
self.assertIn("save_animations_enabled", worker_init_source)
|
||
self.assertIn("save_display_mode", worker_source)
|
||
self.assertIn("save_ui_language", worker_source)
|
||
self.assertIn("save_background_preset", worker_source)
|
||
self.assertIn("save_mica_enabled", worker_source)
|
||
self.assertIn("save_window_opacity", worker_source)
|
||
self.assertIn("save_accent_color", worker_source)
|
||
self.assertIn("save_animations_enabled", worker_source)
|
||
self.assertNotIn("settings.appearance", worker_source)
|
||
|
||
def test_telegram_proxy_restart_request_survives_queued_settings_saves(self) -> None:
|
||
from telegram_proxy.ui.settings_save_flow import merge_restart_request
|
||
|
||
self.assertEqual(merge_restart_request("", "schedule"), "schedule")
|
||
self.assertEqual(merge_restart_request("schedule", ""), "schedule")
|
||
self.assertEqual(merge_restart_request("schedule", "now"), "now")
|
||
self.assertEqual(merge_restart_request("now", "schedule"), "now")
|
||
|
||
init_source = inspect.getsource(TelegramProxyPage.__init__)
|
||
finished_source = inspect.getsource(TelegramProxyPage._on_settings_save_finished)
|
||
|
||
self.assertIn("_settings_save_restart_pending", init_source)
|
||
self.assertIn("merge_restart_request", finished_source)
|
||
self.assertIn("_settings_save_restart_pending", finished_source)
|
||
|
||
def test_appearance_rkn_background_options_load_through_worker(self) -> None:
|
||
appearance_feature = importlib.import_module("app.feature_facades.appearance")
|
||
page_source = inspect.getsource(AppearancePage)
|
||
create_source = inspect.getsource(AppearancePage.create_rkn_background_options_load_worker)
|
||
reload_source = inspect.getsource(AppearancePage._reload_rkn_background_options)
|
||
start_source = inspect.getsource(AppearancePage._start_rkn_background_options_load_worker)
|
||
|
||
self.assertTrue(hasattr(appearance_workers, "AppearanceRknBackgroundOptionsLoadWorker"))
|
||
worker_source = inspect.getsource(appearance_workers.AppearanceRknBackgroundOptionsLoadWorker.run)
|
||
worker_init_source = inspect.getsource(appearance_workers.AppearanceRknBackgroundOptionsLoadWorker.__init__)
|
||
feature_source = inspect.getsource(appearance_feature.AppearanceFeature)
|
||
|
||
self.assertIn("_rkn_background_options_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("create_rkn_background_options_load_worker", page_source)
|
||
self.assertIn("create_rkn_background_options_load_worker", feature_source)
|
||
self.assertIn("settings.appearance_backgrounds", feature_source)
|
||
self.assertNotIn("ui.theme", feature_source)
|
||
self.assertNotIn("AppearanceRknBackgroundOptionsLoadWorker", create_source)
|
||
self.assertIn("self._appearance.create_rkn_background_options_load_worker", create_source)
|
||
self.assertIn("_request_rkn_background_options_load", reload_source)
|
||
self.assertNotIn("appearance_settings.load_rkn_background()", reload_source)
|
||
self.assertNotIn("get_rkn_background_options()", reload_source)
|
||
self.assertIn("load_rkn_background", worker_init_source)
|
||
self.assertIn("get_rkn_background_options", worker_init_source)
|
||
self.assertIn("load_rkn_background", worker_source)
|
||
self.assertIn("get_rkn_background_options", worker_source)
|
||
self.assertNotIn("settings.appearance", worker_source)
|
||
|
||
def test_window_background_uses_warmed_rkn_background_without_folder_scan(self) -> None:
|
||
apply_source = inspect.getsource(ui_theme.apply_window_background)
|
||
resolve_source = inspect.getsource(ui_theme.resolve_rkn_background_path)
|
||
|
||
self.assertIn("peek_warmed_rkn_background", apply_source)
|
||
self.assertNotIn("load_rkn_background", apply_source)
|
||
self.assertNotIn("get_rkn_background_options", resolve_source)
|
||
self.assertIn("_RKN_BG_PREFERRED", resolve_source)
|
||
|
||
def test_theme_uses_warmed_accent_and_tinted_settings(self) -> None:
|
||
tint_source = inspect.getsource(ui_theme._compute_tint_color)
|
||
accent_source = inspect.getsource(ui_theme._sync_theme_accent_to_qfluent)
|
||
|
||
self.assertIn("peek_warmed_tinted_settings", tint_source)
|
||
self.assertIn("peek_warmed_accent_color", accent_source)
|
||
self.assertNotIn("load_tinted_settings", tint_source)
|
||
self.assertNotIn("load_accent_color", accent_source)
|
||
|
||
def test_window_appearance_uses_warmed_state_without_settings_reads(self) -> None:
|
||
background_source = inspect.getsource(ui_theme.apply_window_background)
|
||
opacity_source = inspect.getsource(window_appearance_state.apply_window_opacity_value)
|
||
startup_source = inspect.getsource(main_entry._configure_window_appearance)
|
||
|
||
combined = "\n".join((background_source, opacity_source, startup_source))
|
||
self.assertIn("peek_warmed_background_preset", combined)
|
||
self.assertIn("peek_warmed_mica_enabled", combined)
|
||
self.assertIn("peek_warmed_window_opacity", combined)
|
||
self.assertNotIn("load_background_preset", combined)
|
||
self.assertNotIn("load_mica_enabled", combined)
|
||
self.assertNotIn("load_window_opacity", combined)
|
||
|
||
def test_window_appearance_bindings_use_warmed_state_without_settings_reads(self) -> None:
|
||
bindings_source = inspect.getsource(window_appearance_bindings.initialize_window_appearance_bindings)
|
||
holiday_source = inspect.getsource(window_appearance_bindings.initialize_window_holiday_effects)
|
||
smooth_source = "\n".join(
|
||
(
|
||
inspect.getsource(smooth_scroll.get_page_smooth_scroll_enabled),
|
||
inspect.getsource(smooth_scroll.get_editor_smooth_scroll_enabled),
|
||
inspect.getsource(smooth_scroll.get_effective_editor_smooth_scroll_enabled),
|
||
)
|
||
)
|
||
animation_source = "\n".join(
|
||
(
|
||
inspect.getsource(animation_policy.are_animations_enabled),
|
||
inspect.getsource(animation_policy.apply_window_animation_policy),
|
||
)
|
||
)
|
||
combined = "\n".join((bindings_source, holiday_source, smooth_source, animation_source))
|
||
|
||
self.assertIn("peek_warmed_animations_enabled", combined)
|
||
self.assertIn("peek_warmed_smooth_scroll_enabled", combined)
|
||
self.assertIn("peek_warmed_editor_smooth_scroll_enabled", combined)
|
||
self.assertIn("peek_warmed_premium_effects", combined)
|
||
self.assertNotIn("load_animations_enabled", combined)
|
||
self.assertNotIn("load_smooth_scroll_enabled", combined)
|
||
self.assertNotIn("load_editor_smooth_scroll_enabled", combined)
|
||
self.assertNotIn("load_premium_effects", combined)
|
||
self.assertNotIn("load_window_opacity", combined)
|
||
|
||
def test_appearance_windows_accent_loads_through_worker(self) -> None:
|
||
appearance_feature = importlib.import_module("app.feature_facades.appearance")
|
||
self.assertTrue(hasattr(appearance_workers, "AppearanceWindowsAccentLoadWorker"))
|
||
worker_source = inspect.getsource(appearance_workers.AppearanceWindowsAccentLoadWorker.run)
|
||
worker_init_source = inspect.getsource(appearance_workers.AppearanceWindowsAccentLoadWorker.__init__)
|
||
page_source = inspect.getsource(AppearancePage)
|
||
create_source = inspect.getsource(AppearancePage.create_windows_accent_load_worker)
|
||
handler_source = inspect.getsource(AppearancePage._on_follow_windows_accent_changed)
|
||
start_source = inspect.getsource(AppearancePage._start_windows_accent_load_worker)
|
||
apply_source = inspect.getsource(AppearancePage._apply_windows_accent)
|
||
feature_source = inspect.getsource(appearance_feature.AppearanceFeature)
|
||
|
||
self.assertIn("load_windows_system_accent", worker_source)
|
||
self.assertIn("load_windows_system_accent", worker_init_source)
|
||
self.assertIn("create_windows_accent_load_worker", page_source)
|
||
self.assertIn("create_windows_accent_load_worker", feature_source)
|
||
self.assertNotIn("AppearanceWindowsAccentLoadWorker", create_source)
|
||
self.assertIn("self._appearance.create_windows_accent_load_worker", create_source)
|
||
self.assertIn("_windows_accent_load_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("_request_windows_accent_load", handler_source)
|
||
self.assertNotIn("load_windows_system_accent", apply_source)
|
||
self.assertNotIn("settings.appearance", worker_source)
|
||
|
||
def test_premium_navigation_does_not_read_device_info_during_language_refresh(self) -> None:
|
||
language_source = inspect.getsource(premium_page_lifecycle.apply_premium_language)
|
||
|
||
self.assertNotIn("update_device_info_fn()", language_source)
|
||
self.assertNotIn("update_device_info_fn", inspect.signature(premium_page_lifecycle.apply_premium_language).parameters)
|
||
|
||
def test_premium_pairing_autopoll_does_not_initialize_checker_when_storage_is_not_ready(self) -> None:
|
||
class _PremiumFeature:
|
||
def is_checker_ready(self) -> bool:
|
||
return False
|
||
|
||
def is_storage_ready(self) -> bool:
|
||
return False
|
||
|
||
def read_pairing_snapshot(self, *, current_time: int):
|
||
raise AssertionError("read_pairing_snapshot must not run before storage is ready")
|
||
|
||
self.assertFalse(
|
||
premium_pairing_workflow.can_poll_pairing_status(
|
||
premium_feature=_PremiumFeature(),
|
||
page_visible=True,
|
||
activation_in_progress=False,
|
||
connection_test_in_progress=False,
|
||
worker_running=False,
|
||
current_time=1,
|
||
)
|
||
)
|
||
|
||
def test_premium_pairing_autopoll_uses_cached_snapshot_when_storage_is_ready(self) -> None:
|
||
class _PremiumFeature:
|
||
def is_checker_ready(self) -> bool:
|
||
return True
|
||
|
||
def is_storage_ready(self) -> bool:
|
||
return True
|
||
|
||
def read_pairing_snapshot(self, *, current_time: int):
|
||
raise AssertionError("activation must not read pairing snapshot when cached snapshot is available")
|
||
|
||
self.assertTrue(
|
||
premium_pairing_workflow.can_poll_pairing_status(
|
||
premium_feature=_PremiumFeature(),
|
||
page_visible=True,
|
||
activation_in_progress=False,
|
||
connection_test_in_progress=False,
|
||
worker_running=False,
|
||
current_time=1,
|
||
pairing_snapshot={
|
||
"has_device_token": False,
|
||
"has_pending_pair_code": True,
|
||
},
|
||
)
|
||
)
|
||
|
||
def test_premium_page_passes_cached_pairing_snapshot_to_autopoll(self) -> None:
|
||
page_source = inspect.getsource(PremiumPage)
|
||
sync_source = inspect.getsource(PremiumPage._sync_pairing_status_autopoll)
|
||
device_info_loaded_source = inspect.getsource(PremiumPage._on_device_info_loaded)
|
||
|
||
self.assertNotIn("def _has_pending_pair_code", page_source)
|
||
self.assertNotIn("has_pending_pair_code(", page_source)
|
||
self.assertIn("pairing_snapshot=self._pairing_autopoll_snapshot", sync_source)
|
||
self.assertIn("_set_pairing_autopoll_snapshot_from_device_info", device_info_loaded_source)
|
||
|
||
def test_premium_device_info_refresh_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("donater.device_info_worker")
|
||
self.assertIsNotNone(spec)
|
||
device_info_worker = importlib.import_module("donater.device_info_worker")
|
||
premium_feature_cls = __import__("app.feature_facades.premium", fromlist=["PremiumFeature"]).PremiumFeature
|
||
|
||
page_source = inspect.getsource(PremiumPage)
|
||
handler_source = inspect.getsource(PremiumPage._update_device_info)
|
||
feature_source = inspect.getsource(premium_feature_cls)
|
||
|
||
self.assertTrue(hasattr(device_info_worker, "PremiumDeviceInfoLoadWorker"))
|
||
worker_source = inspect.getsource(device_info_worker.PremiumDeviceInfoLoadWorker.run)
|
||
|
||
self.assertIn("_request_device_info_load", handler_source)
|
||
self.assertNotIn("update_device_info_labels", handler_source)
|
||
self.assertNotIn("read_device_info_snapshot", handler_source)
|
||
self.assertIn("create_device_info_load_worker", page_source)
|
||
self.assertIn("_device_info_runtime", page_source)
|
||
self.assertIn("create_device_info_load_worker", feature_source)
|
||
self.assertIn("read_device_info_snapshot=self.read_device_info_snapshot", feature_source)
|
||
self.assertNotIn("premium_feature=self", feature_source)
|
||
self.assertNotIn("self._premium", inspect.getsource(device_info_worker.PremiumDeviceInfoLoadWorker))
|
||
self.assertIn("read_device_info_snapshot", worker_source)
|
||
self.assertFalse(hasattr(premium_ui_pairing_workflow, "update_device_info_labels"))
|
||
|
||
def test_premium_click_actions_initialize_checker_through_worker(self) -> None:
|
||
create_source = inspect.getsource(PremiumPage._create_pair_code)
|
||
status_source = inspect.getsource(PremiumPage._check_status)
|
||
connection_source = inspect.getsource(PremiumPage._test_connection)
|
||
init_request_source = inspect.getsource(PremiumPage._request_checker_init)
|
||
|
||
for source in (create_source, status_source, connection_source):
|
||
self.assertIn("_request_checker_init", source)
|
||
self.assertNotIn("_init_checker()", source)
|
||
self.assertNotIn("ensure_checker_ready", source)
|
||
|
||
self.assertIn("_start_premium_init_worker", init_request_source)
|
||
self.assertNotIn("ensure_checker_ready", init_request_source)
|
||
|
||
def test_premium_runtime_init_starts_checker_in_worker(self) -> None:
|
||
calls: list[str] = []
|
||
|
||
premium_page_lifecycle.run_premium_runtime_init_once(
|
||
runtime_initialized=False,
|
||
build_page_init_plan_fn=premium_page_plans.build_page_init_plan,
|
||
set_runtime_initialized_fn=lambda value: calls.append(f"runtime={value}"),
|
||
start_init_worker_fn=lambda: calls.append("worker"),
|
||
set_server_status_mode_fn=lambda value: calls.append(f"mode={value}"),
|
||
set_server_status_message_fn=lambda value: calls.append(f"message={value}"),
|
||
set_server_status_success_fn=lambda value: calls.append(f"success={value}"),
|
||
render_server_status_fn=lambda: calls.append("render"),
|
||
)
|
||
|
||
self.assertIn("runtime=True", calls)
|
||
self.assertIn("worker", calls)
|
||
self.assertNotIn("init_checker", inspect.getsource(premium_page_lifecycle.run_premium_runtime_init_once))
|
||
|
||
def test_premium_open_bot_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("donater.open_bot_worker")
|
||
self.assertIsNotNone(spec)
|
||
open_bot_worker = importlib.import_module("donater.open_bot_worker")
|
||
|
||
page_source = inspect.getsource(PremiumPage)
|
||
handler_source = inspect.getsource(PremiumPage._open_extend_bot)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.premium", fromlist=["PremiumFeature"]).PremiumFeature)
|
||
|
||
self.assertTrue(hasattr(open_bot_worker, "PremiumOpenBotWorker"))
|
||
worker_source = inspect.getsource(open_bot_worker.PremiumOpenBotWorker.run)
|
||
|
||
self.assertIn("_request_open_extend_bot", handler_source)
|
||
self.assertNotIn(".open_extend_bot(", handler_source)
|
||
self.assertIn("create_open_extend_bot_worker", page_source)
|
||
self.assertIn("_open_bot_runtime", page_source)
|
||
self.assertIn("create_open_extend_bot_worker", feature_source)
|
||
self.assertIn("open_extend_bot=self.open_extend_bot", feature_source)
|
||
self.assertIn("_open_extend_bot", worker_source)
|
||
self.assertNotIn("premium_commands", worker_source)
|
||
self.assertIn("open_extend_bot", worker_source)
|
||
|
||
def test_premium_reset_storage_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("donater.reset_worker")
|
||
self.assertIsNotNone(spec)
|
||
reset_worker = importlib.import_module("donater.reset_worker")
|
||
|
||
page_source = inspect.getsource(PremiumPage)
|
||
handler_source = inspect.getsource(PremiumPage._change_key)
|
||
workflow_source = inspect.getsource(__import__("donater.ui.status_workflow", fromlist=["apply_reset_plan_ui"]).apply_reset_plan_ui)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.premium", fromlist=["PremiumFeature"]).PremiumFeature)
|
||
|
||
self.assertTrue(hasattr(reset_worker, "PremiumResetStorageWorker"))
|
||
worker_source = inspect.getsource(reset_worker.PremiumResetStorageWorker.run)
|
||
|
||
self.assertIn("_request_reset_storage", handler_source)
|
||
self.assertNotIn("apply_reset_plan_ui(", handler_source)
|
||
self.assertNotIn("reset_premium_storage", workflow_source)
|
||
self.assertIn("create_reset_storage_worker", page_source)
|
||
self.assertIn("_reset_storage_runtime", page_source)
|
||
self.assertIn("create_reset_storage_worker", feature_source)
|
||
self.assertIn("reset_storage=self.reset_premium_storage", feature_source)
|
||
self.assertNotIn("premium_feature=self", feature_source)
|
||
self.assertNotIn("self._premium", inspect.getsource(reset_worker.PremiumResetStorageWorker))
|
||
self.assertIn("_reset_storage", worker_source)
|
||
|
||
def test_support_page_external_links_run_through_worker(self) -> None:
|
||
from app.feature_facades.external import ExternalActionsFeature
|
||
import app.external_workers as external_workers
|
||
|
||
page_source = inspect.getsource(SupportPage)
|
||
feature_source = inspect.getsource(ExternalActionsFeature)
|
||
worker_source = inspect.getsource(external_workers.ExternalActionWorker.run)
|
||
|
||
self.assertTrue(hasattr(external_workers, "ExternalActionWorker"))
|
||
self.assertIn("_support_open_runtime", page_source)
|
||
self.assertIn("create_support_open_action_worker", page_source)
|
||
self.assertIn("_create_support_open_action_worker", page_source)
|
||
self.assertIn("create_external_action_worker", feature_source)
|
||
self.assertNotIn("ui.pages.support_open_worker", page_source)
|
||
for method_name in (
|
||
"_open_support_discussions",
|
||
"_open_telegram_support",
|
||
"_open_discord",
|
||
):
|
||
source = inspect.getsource(getattr(SupportPage, method_name))
|
||
self.assertIn("_request_support_open_action", source)
|
||
self.assertNotIn("_open_discussions_action()", source)
|
||
self.assertNotIn("_open_telegram_action()", source)
|
||
self.assertNotIn("_open_discord_action()", source)
|
||
|
||
self.assertIn("action_fn", worker_source)
|
||
|
||
def test_about_page_external_actions_run_through_worker(self) -> None:
|
||
from app.feature_facades.external import ExternalActionsFeature
|
||
import app.external_workers as external_workers
|
||
|
||
page_source = inspect.getsource(AboutPage)
|
||
feature_source = inspect.getsource(ExternalActionsFeature)
|
||
worker_source = inspect.getsource(external_workers.ExternalActionWorker.run)
|
||
|
||
self.assertTrue(hasattr(external_workers, "ExternalActionWorker"))
|
||
self.assertIn("_about_open_runtime", page_source)
|
||
self.assertIn("create_about_open_action_worker", page_source)
|
||
self.assertIn("_create_about_open_action_worker", page_source)
|
||
self.assertIn("create_external_action_worker", feature_source)
|
||
self.assertNotIn("ui.pages.about_open_worker", page_source)
|
||
for method_name in (
|
||
"_open_support_discussions",
|
||
"_open_telegram_support",
|
||
"_open_discord",
|
||
"_open_forum_for_beginners",
|
||
"_open_telegram_news",
|
||
"_open_kvn_channel",
|
||
"_open_kvn_bot",
|
||
"_open_kvn_bypass",
|
||
"_open_kvn_github",
|
||
):
|
||
source = inspect.getsource(getattr(AboutPage, method_name))
|
||
self.assertIn("_request_about_open_action", source)
|
||
self.assertNotIn("_action()", source)
|
||
|
||
self.assertIn("action_fn", worker_source)
|
||
|
||
def test_page_language_is_not_reapplied_on_every_repeat_navigation(self) -> None:
|
||
class _Page:
|
||
def __init__(self) -> None:
|
||
self.calls: list[str] = []
|
||
|
||
def set_ui_language(self, language: str) -> None:
|
||
self.calls.append(language)
|
||
|
||
window = type("_Window", (), {})()
|
||
window.ui_session = type("_Session", (), {"ui_language": "ru"})()
|
||
page = _Page()
|
||
|
||
navigation_text_sync.apply_ui_language_to_page(window, page)
|
||
navigation_text_sync.apply_ui_language_to_page(window, page)
|
||
window.ui_session.ui_language = "en"
|
||
navigation_text_sync.apply_ui_language_to_page(window, page)
|
||
|
||
self.assertEqual(page.calls, ["ru", "en"])
|
||
|
||
def test_page_language_skips_initial_reapply_when_page_already_matches_window(self) -> None:
|
||
class _Page:
|
||
def __init__(self) -> None:
|
||
self._ui_language = "ru"
|
||
self.calls: list[str] = []
|
||
|
||
def set_ui_language(self, language: str) -> None:
|
||
self.calls.append(language)
|
||
|
||
window = type("_Window", (), {})()
|
||
window.ui_session = type("_Session", (), {"ui_language": "ru"})()
|
||
page = _Page()
|
||
|
||
navigation_text_sync.apply_ui_language_to_page(window, page)
|
||
|
||
self.assertEqual(page.calls, [])
|
||
self.assertEqual(page._last_applied_ui_language, "ru")
|
||
|
||
def test_page_language_still_applies_when_page_language_is_unknown(self) -> None:
|
||
class _Page:
|
||
def __init__(self) -> None:
|
||
self.calls: list[str] = []
|
||
|
||
def set_ui_language(self, language: str) -> None:
|
||
self.calls.append(language)
|
||
|
||
window = type("_Window", (), {})()
|
||
window.ui_session = type("_Session", (), {"ui_language": "ru"})()
|
||
page = _Page()
|
||
|
||
navigation_text_sync.apply_ui_language_to_page(window, page)
|
||
|
||
self.assertEqual(page.calls, ["ru"])
|
||
|
||
def test_appearance_initial_state_plan_reads_settings_once(self) -> None:
|
||
data = {
|
||
"appearance": {
|
||
"display_mode": "light",
|
||
"ui_language": "en",
|
||
"background_preset": "rkn_chan",
|
||
"rkn_background": "rkn_tyan/bg.webp",
|
||
"mica_enabled": False,
|
||
"accent_color": "#112233",
|
||
"follow_windows_accent": True,
|
||
"tinted_background": True,
|
||
"tinted_background_intensity": 17,
|
||
"animations_enabled": True,
|
||
"smooth_scroll_enabled": True,
|
||
"editor_smooth_scroll_enabled": False,
|
||
"garland_enabled": True,
|
||
"snowflakes_enabled": False,
|
||
},
|
||
"window": {
|
||
"opacity": 73,
|
||
},
|
||
}
|
||
|
||
with patch("settings.store.read_settings", return_value=data) as read_settings:
|
||
plan = appearance_settings.load_page_initial_state()
|
||
|
||
self.assertEqual(read_settings.call_count, 1)
|
||
self.assertEqual(plan.display_mode, "light")
|
||
self.assertEqual(plan.ui_language, "en")
|
||
self.assertEqual(plan.background_preset, "rkn_chan")
|
||
self.assertEqual(plan.rkn_background, "rkn_tyan/bg.webp")
|
||
self.assertFalse(plan.mica_enabled)
|
||
self.assertEqual(plan.window_opacity, 73)
|
||
self.assertEqual(plan.accent_color, "#112233")
|
||
self.assertTrue(plan.follow_windows_accent)
|
||
self.assertTrue(plan.tinted_background)
|
||
self.assertEqual(plan.tinted_intensity, 17)
|
||
self.assertTrue(plan.animations_enabled)
|
||
self.assertTrue(plan.smooth_scroll_enabled)
|
||
self.assertFalse(plan.editor_smooth_scroll_enabled)
|
||
self.assertTrue(plan.garland_enabled)
|
||
self.assertFalse(plan.snowflakes_enabled)
|
||
|
||
def test_telegram_proxy_initial_state_is_backend_plan_not_ui_loading(self) -> None:
|
||
telegram_proxy_workers = importlib.import_module("telegram_proxy.runtime.workers")
|
||
|
||
init_source = inspect.getsource(TelegramProxyPage.__init__)
|
||
after_source = inspect.getsource(TelegramProxyPage._after_ui_built)
|
||
request_source = inspect.getsource(TelegramProxyPage._request_initial_state_load)
|
||
loaded_source = inspect.getsource(TelegramProxyPage._on_initial_state_loaded)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
settings_build_source = inspect.getsource(telegram_proxy_settings_build.build_telegram_proxy_settings_panel)
|
||
settings_source = inspect.getsource(telegram_proxy_settings.load_page_initial_state)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyInitialStateWorker.run)
|
||
|
||
self.assertIn("_request_initial_state_load", init_source)
|
||
self.assertIn("_initial_state_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", request_source)
|
||
self.assertIn("bind_worker", request_source)
|
||
self.assertIn("worker.completed.connect(self._on_initial_state_loaded)", request_source)
|
||
self.assertIn("worker.failed.connect(self._on_initial_state_failed)", request_source)
|
||
self.assertIn("_initial_state_runtime.is_current", loaded_source)
|
||
self.assertIn("_initial_state_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_initial_state_worker =", page_source)
|
||
self.assertNotIn("worker.start()", request_source)
|
||
self.assertNotIn("self._telegram_proxy.load_page_initial_state()", init_source)
|
||
self.assertIn("_apply_initial_settings_state", page_source)
|
||
self.assertNotIn("self._load_settings()", after_source)
|
||
self.assertNotIn("load_settings_into_ui", after_source)
|
||
self.assertNotIn("UpstreamCatalog.load_from_runtime", settings_build_source)
|
||
self.assertIn("create_initial_state_worker", page_source)
|
||
self.assertIn("create_page_initial_state_worker", feature_source)
|
||
self.assertNotIn("def load_page_initial_state", feature_source)
|
||
|
||
self.assertIn("read_settings", settings_source)
|
||
self.assertNotIn("get_tg_proxy_host", settings_source)
|
||
self.assertNotIn("get_tg_proxy_port", settings_source)
|
||
self.assertNotIn("get_tg_proxy_upstream_enabled", settings_source)
|
||
self.assertIn("load_page_initial_state=self.load_page_initial_state", feature_source)
|
||
self.assertIn("_load_page_initial_state", worker_source)
|
||
self.assertNotIn("telegram_proxy.runtime.commands", worker_source)
|
||
self.assertNotIn("telegram_proxy.settings", worker_source)
|
||
self.assertIn("load_page_initial_state", worker_source)
|
||
|
||
def test_telegram_proxy_initial_state_plan_reads_settings_once(self) -> None:
|
||
from telegram_proxy.config.upstream_catalog import UpstreamCatalog
|
||
|
||
catalog = UpstreamCatalog(build_presets=[
|
||
{
|
||
"id": "build:test",
|
||
"name": "Test",
|
||
"type": "socks5",
|
||
"source": "build",
|
||
"host": "10.0.0.2",
|
||
"port": 1081,
|
||
"username": "u",
|
||
"password": "p",
|
||
}
|
||
])
|
||
data = {
|
||
"telegram_proxy": {
|
||
"host": "0.0.0.0",
|
||
"port": 1453,
|
||
"upstream_enabled": True,
|
||
"upstream_host": "10.0.0.2",
|
||
"upstream_port": 1081,
|
||
"upstream_user": "u",
|
||
"upstream_pass": "p",
|
||
"upstream_mode": "always",
|
||
},
|
||
}
|
||
|
||
with (
|
||
patch("telegram_proxy.config.upstream_catalog.UpstreamCatalog.load_from_runtime", return_value=catalog),
|
||
patch("settings.store.read_settings", return_value=data) as read_settings,
|
||
):
|
||
plan = telegram_proxy_settings.load_page_initial_state()
|
||
|
||
self.assertEqual(read_settings.call_count, 1)
|
||
self.assertIs(plan.upstream_catalog, catalog)
|
||
self.assertEqual(plan.settings.host, "0.0.0.0")
|
||
self.assertEqual(plan.settings.port, 1453)
|
||
self.assertTrue(plan.settings.upstream_enabled)
|
||
self.assertEqual(plan.settings.upstream_host, "10.0.0.2")
|
||
self.assertEqual(plan.settings.upstream_port, 1081)
|
||
self.assertEqual(plan.settings.upstream_user, "u")
|
||
self.assertEqual(plan.settings.upstream_password, "p")
|
||
self.assertEqual(plan.settings.upstream_mode, "always")
|
||
self.assertEqual(plan.settings.upstream_preset_index, 1)
|
||
|
||
def test_telegram_proxy_log_lines_write_through_worker(self) -> None:
|
||
append_source = inspect.getsource(TelegramProxyPage._append_log_line)
|
||
request_source = inspect.getsource(TelegramProxyPage._request_log_line_append)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_log_line_worker)
|
||
failed_source = inspect.getsource(TelegramProxyPage._on_log_line_worker_failed)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_commands, "append_log_line"))
|
||
command_source = inspect.getsource(telegram_proxy_commands.append_log_line)
|
||
self.assertTrue(hasattr(telegram_proxy_workers, "TelegramProxyLogLineWorker"))
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyLogLineWorker.run)
|
||
|
||
self.assertIn("_request_log_line_append", append_source)
|
||
self.assertNotIn("proxy_logger.log", append_source)
|
||
self.assertIn("_log_line_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertIn("bind_worker", start_source)
|
||
self.assertIn("worker.completed.connect(self._on_log_line_worker_completed)", start_source)
|
||
self.assertIn("worker.failed.connect(self._on_log_line_worker_failed)", start_source)
|
||
self.assertIn('_queued_worker_state("_log_line_state", "_log_line_runtime")', request_source)
|
||
self.assertIn("state.start_or_queue", request_source)
|
||
self.assertIn("_log_line_runtime.is_current", failed_source)
|
||
self.assertIn("_log_line_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_log_line_worker =", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("create_log_line_worker", feature_source)
|
||
self.assertIn("append_log_line", command_source)
|
||
self.assertIn("append_log_line", worker_source)
|
||
|
||
def test_telegram_proxy_auto_deeplink_check_runs_through_worker(self) -> None:
|
||
try_source = inspect.getsource(TelegramProxyPage._try_auto_deeplink)
|
||
request_source = inspect.getsource(TelegramProxyPage._request_auto_deeplink_check)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_auto_deeplink_worker)
|
||
checked_source = inspect.getsource(TelegramProxyPage._on_auto_deeplink_checked)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_commands, "consume_auto_deeplink_request"))
|
||
command_source = inspect.getsource(telegram_proxy_commands.consume_auto_deeplink_request)
|
||
self.assertTrue(hasattr(telegram_proxy_workers, "TelegramProxyAutoDeeplinkWorker"))
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyAutoDeeplinkWorker.run)
|
||
|
||
self.assertIn("_request_auto_deeplink_check", try_source)
|
||
self.assertNotIn("telegram_proxy_settings.consume_auto_deeplink_request", try_source)
|
||
self.assertIn("_auto_deeplink_runtime", page_source)
|
||
self.assertIn("_start_auto_deeplink_worker", request_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertIn("bind_worker", start_source)
|
||
self.assertIn("worker.completed.connect(self._on_auto_deeplink_checked)", start_source)
|
||
self.assertIn("worker.failed.connect(self._on_auto_deeplink_failed)", start_source)
|
||
self.assertIn("_auto_deeplink_runtime.is_current", checked_source)
|
||
self.assertIn("_auto_deeplink_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_auto_deeplink_worker =", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("create_auto_deeplink_worker", feature_source)
|
||
self.assertIn("telegram_proxy.config.settings", command_source)
|
||
self.assertIn("consume_auto_deeplink_request", worker_source)
|
||
|
||
def test_dns_check_save_runs_through_worker(self) -> None:
|
||
page_source = inspect.getsource(dns_check_page.DNSCheckPage)
|
||
save_source = inspect.getsource(dns_check_page.DNSCheckPage.save_results)
|
||
|
||
self.assertTrue(hasattr(dns_check_worker, "DNSCheckSaveWorker"))
|
||
self.assertTrue(hasattr(dns_commands, "save_dns_check_results"))
|
||
worker_source = inspect.getsource(dns_check_worker.DNSCheckSaveWorker.run)
|
||
commands_source = inspect.getsource(dns_commands.save_dns_check_results)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["build_dns_feature"]).build_dns_feature)
|
||
|
||
self.assertIn("create_dns_check_save_worker", page_source)
|
||
self.assertIn("_start_save_results_worker", save_source)
|
||
self.assertNotIn("save_results_text(", save_source)
|
||
self.assertIn("save_dns_check_results=save_dns_check_results", feature_source)
|
||
self.assertIn("_save_dns_check_results", worker_source)
|
||
self.assertNotIn("dns_commands", worker_source)
|
||
self.assertNotIn("dns.dns_check_plans", worker_source)
|
||
self.assertIn("open(", commands_source)
|
||
self.assertIn("os.startfile", commands_source)
|
||
|
||
def test_dns_check_worker_runs_poisoning_check_through_commands(self) -> None:
|
||
page_source = inspect.getsource(dns_check_page.DNSCheckPage)
|
||
|
||
self.assertTrue(hasattr(dns_commands, "run_dns_poisoning_check"))
|
||
worker_source = inspect.getsource(dns_check_worker.DNSCheckWorker.run)
|
||
commands_source = inspect.getsource(dns_commands.run_dns_poisoning_check)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["build_dns_feature"]).build_dns_feature)
|
||
|
||
self.assertIn("create_dns_check_worker", page_source)
|
||
self.assertIn("run_dns_poisoning_check=run_dns_poisoning_check", feature_source)
|
||
self.assertIn("_run_dns_poisoning_check", worker_source)
|
||
self.assertNotIn("dns_commands", worker_source)
|
||
self.assertNotIn("dns_checker", worker_source)
|
||
self.assertIn("DNSChecker", commands_source)
|
||
|
||
def test_dns_quick_check_runs_through_worker(self) -> None:
|
||
page_source = inspect.getsource(dns_check_page.DNSCheckPage)
|
||
quick_source = inspect.getsource(dns_check_page.DNSCheckPage.quick_dns_check)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["build_dns_feature"]).build_dns_feature)
|
||
plans_source = inspect.getsource(dns_check_page_plans)
|
||
commands_source = inspect.getsource(__import__("dns.commands", fromlist=["run_quick_dns_check"]).run_quick_dns_check)
|
||
|
||
self.assertTrue(hasattr(dns_check_worker, "DNSQuickCheckWorker"))
|
||
worker_source = inspect.getsource(dns_check_worker.DNSQuickCheckWorker.run)
|
||
|
||
self.assertIn("create_dns_quick_check_worker", page_source)
|
||
self.assertIn("_start_quick_dns_check_worker", quick_source)
|
||
self.assertNotIn("run_quick_dns_check(", quick_source)
|
||
self.assertIn("create_dns_quick_check_worker", feature_source)
|
||
self.assertIn("run_quick_dns_check=run_quick_dns_check", feature_source)
|
||
self.assertIn("_run_quick_dns_check", worker_source)
|
||
self.assertNotIn("dns.commands", worker_source)
|
||
self.assertNotIn("socket.", plans_source)
|
||
# Резолв живёт в commands, а не в plans. Через net_resolve, а не
|
||
# socket.gethostbyname: тот не имеет таймаута и подвешивал страницу.
|
||
self.assertIn("resolve_ipv4", commands_source)
|
||
self.assertNotIn("socket.gethostbyname", commands_source)
|
||
|
||
def test_telegram_proxy_settings_save_runs_through_worker(self) -> None:
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
upstream_source = inspect.getsource(telegram_upstream_workflow)
|
||
runtime_source = inspect.getsource(telegram_runtime_workflow)
|
||
request_source = inspect.getsource(TelegramProxyPage._request_settings_save)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_settings_save_worker)
|
||
completed_source = inspect.getsource(TelegramProxyPage._on_settings_save_finished)
|
||
failed_source = inspect.getsource(TelegramProxyPage._on_settings_save_failed)
|
||
finished_source = inspect.getsource(TelegramProxyPage._on_settings_save_worker_finished)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
command_source = inspect.getsource(telegram_proxy_commands)
|
||
queued_state_source = inspect.getsource(TelegramProxyPageQueuedWorkerState)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_workers, "TelegramProxySettingsSaveWorker"))
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxySettingsSaveWorker.run)
|
||
|
||
for handler_name in (
|
||
"_on_port_changed",
|
||
"_on_host_changed",
|
||
"_on_upstream_changed",
|
||
"_on_upstream_preset_changed",
|
||
"_on_upstream_host_changed",
|
||
"_on_upstream_port_changed",
|
||
"_on_upstream_user_changed",
|
||
"_on_upstream_pass_changed",
|
||
"_on_upstream_mode_changed",
|
||
):
|
||
source = inspect.getsource(getattr(TelegramProxyPage, handler_name))
|
||
self.assertIn("_request_settings_save", source)
|
||
self.assertNotIn("telegram_proxy_settings.set_", source)
|
||
self.assertNotIn("save_upstream_fields(", source)
|
||
self.assertNotIn("save_upstream_mode(", source)
|
||
|
||
self.assertIn("create_settings_save_worker", page_source)
|
||
self.assertIn("_settings_save_runtime", page_source)
|
||
self.assertIn("_queue_settings_save_payload", request_source)
|
||
self.assertIn(
|
||
'_queued_worker_state("_settings_save_state", "_settings_save_runtime")',
|
||
inspect.getsource(TelegramProxyPage._queue_settings_save_payload),
|
||
)
|
||
self.assertIn("state.start_or_queue", request_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertIn("bind_worker", start_source)
|
||
self.assertIn("worker.completed.connect(self._on_settings_save_finished)", start_source)
|
||
self.assertIn("worker.failed.connect(self._on_settings_save_failed)", start_source)
|
||
self.assertIn("_settings_save_runtime.is_current", completed_source)
|
||
self.assertIn("_settings_save_runtime.is_current", failed_source)
|
||
self.assertIn("schedule_next_after_finish", finished_source)
|
||
self.assertIn("pop_next_after_finish", queued_state_source)
|
||
self.assertIn("_settings_save_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_settings_save_worker =", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertNotIn("import telegram_proxy.settings", upstream_source)
|
||
self.assertNotIn("telegram_proxy_settings.set_", upstream_source)
|
||
self.assertNotIn("telegram_proxy_settings.set_proxy_enabled", runtime_source)
|
||
self.assertIn("request_proxy_enabled_save", runtime_source)
|
||
self.assertIn('"proxy_enabled"', page_source)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
self.assertIn("save_settings_action=self.save_settings_action", feature_source)
|
||
self.assertIn("_save_settings_action", worker_source)
|
||
self.assertNotIn("telegram_proxy.runtime.commands", worker_source)
|
||
self.assertNotIn("telegram_proxy.settings", worker_source)
|
||
self.assertIn("save_settings_action", worker_source)
|
||
self.assertIn("set_host", command_source)
|
||
self.assertIn("set_port", command_source)
|
||
self.assertIn("set_proxy_enabled", command_source)
|
||
self.assertIn("set_upstream_enabled", command_source)
|
||
self.assertIn("set_upstream_preset", command_source)
|
||
self.assertIn("set_manual_upstream", command_source)
|
||
self.assertIn("set_upstream_mode", command_source)
|
||
|
||
def test_telegram_proxy_relay_http_probe_is_command_not_ui_runtime(self) -> None:
|
||
page_runtime_source = inspect.getsource(telegram_page.telegram_proxy_page_runtime)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyRelayCheckWorker.run)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_commands, "check_relay_http"))
|
||
command_source = inspect.getsource(telegram_proxy_commands.check_relay_http)
|
||
|
||
self.assertNotIn("socket.", page_runtime_source)
|
||
self.assertIn("check_relay_reachable=self.check_relay_reachable", feature_source)
|
||
self.assertIn("check_relay_http=self.check_relay_http", feature_source)
|
||
self.assertIn("_check_relay_reachable", worker_source)
|
||
self.assertIn("_check_relay_http", worker_source)
|
||
self.assertNotIn("telegram_proxy.runtime.commands", worker_source)
|
||
self.assertIn("check_relay_http", worker_source)
|
||
self.assertIn("socket.create_connection", command_source)
|
||
|
||
def test_telegram_proxy_open_log_file_runs_through_worker(self) -> None:
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
handler_source = inspect.getsource(TelegramProxyPage._on_open_log_file)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_open_log_file_worker)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_workers, "TelegramProxyOpenLogFileWorker"))
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyOpenLogFileWorker.run)
|
||
|
||
self.assertIn("_start_open_log_file_worker", handler_source)
|
||
self.assertNotIn(".open_log_file(", handler_source)
|
||
self.assertIn("create_open_log_file_worker", page_source)
|
||
self.assertIn("_open_log_file_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertIn("bind_worker", start_source)
|
||
self.assertIn("worker.completed.connect(self._on_open_log_file_finished)", start_source)
|
||
self.assertIn("worker.failed.connect(self._on_open_log_file_failed)", start_source)
|
||
self.assertIn('_queued_worker_state("_open_log_file_state", "_open_log_file_runtime")', start_source)
|
||
self.assertIn("state.is_busy()", start_source)
|
||
self.assertIn("_open_log_file_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_open_log_file_worker =", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("create_open_log_file_worker", feature_source)
|
||
self.assertIn("open_log_file", worker_source)
|
||
|
||
def test_telegram_proxy_external_links_run_through_worker(self) -> None:
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
mtproxy_source = inspect.getsource(TelegramProxyPage._on_open_mtproxy)
|
||
telegram_source = inspect.getsource(TelegramProxyPage._on_open_in_telegram)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_external_link_worker)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_workers, "TelegramProxyExternalLinkWorker"))
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyExternalLinkWorker.run)
|
||
|
||
for source in (mtproxy_source, telegram_source):
|
||
self.assertIn("_start_external_link_worker", source)
|
||
self.assertNotIn(".open_external_link(", source)
|
||
|
||
self.assertIn("create_external_link_worker", page_source)
|
||
self.assertIn("_external_link_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertIn("bind_worker", start_source)
|
||
self.assertIn("worker.completed.connect(self._on_external_link_finished)", start_source)
|
||
self.assertIn("worker.failed.connect(self._on_external_link_failed)", start_source)
|
||
self.assertIn('_queued_worker_state("_external_link_state", "_external_link_runtime")', start_source)
|
||
self.assertIn("state.is_busy()", start_source)
|
||
self.assertIn("_external_link_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_external_link_worker =", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("create_external_link_worker", feature_source)
|
||
self.assertIn("open_external_link", worker_source)
|
||
|
||
def test_telegram_proxy_stop_runs_through_worker(self) -> None:
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
stop_source = inspect.getsource(TelegramProxyPage._stop_proxy)
|
||
request_source = inspect.getsource(TelegramProxyPage._request_proxy_stop)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_proxy_stop_worker)
|
||
runtime_source = inspect.getsource(telegram_runtime_workflow.stop_proxy_runtime)
|
||
|
||
self.assertTrue(hasattr(telegram_proxy_workers, "TelegramProxyStopRuntimeWorker"))
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramProxyStopRuntimeWorker.run)
|
||
|
||
self.assertIn("_request_proxy_stop", stop_source)
|
||
self.assertIn("_start_proxy_stop_worker", request_source)
|
||
self.assertIn("stop_proxy_runtime", start_source)
|
||
self.assertNotIn("manager.stop_proxy()", stop_source)
|
||
self.assertNotIn("manager.stop_proxy()", request_source)
|
||
self.assertNotIn("manager.stop_proxy()", start_source)
|
||
self.assertNotIn("manager.stop_proxy()", runtime_source)
|
||
self.assertIn("create_stop_runtime_worker", runtime_source)
|
||
self.assertIn("_proxy_stop_runtime", page_source)
|
||
self.assertIn("_finish_stop_proxy", runtime_source)
|
||
self.assertIn("QMetaObject.invokeMethod", runtime_source)
|
||
self.assertIn('_request_settings_save("proxy_enabled", enabled=False)', page_source)
|
||
self.assertIn("stop_proxy", worker_source)
|
||
|
||
def test_blockcheck_initial_state_is_backend_plan_not_ui_loading(self) -> None:
|
||
spec = importlib.util.find_spec("blockcheck.workers")
|
||
self.assertIsNotNone(spec)
|
||
blockcheck_workers = importlib.import_module("blockcheck.workers")
|
||
|
||
init_source = inspect.getsource(BlockcheckPage.__init__)
|
||
build_source = inspect.getsource(BlockcheckPage._build_ui)
|
||
request_source = inspect.getsource(BlockcheckPage._request_page_initial_state_load)
|
||
loaded_source = inspect.getsource(BlockcheckPage._on_initial_state_loaded)
|
||
cleanup_source = inspect.getsource(BlockcheckPage.cleanup)
|
||
page_source = inspect.getsource(BlockcheckPage)
|
||
helper_source = inspect.getsource(blockcheck_ui_helpers)
|
||
runtime_source = inspect.getsource(blockcheck_page_runtime.load_page_initial_state)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
worker_source = inspect.getsource(blockcheck_workers.BlockcheckInitialStateWorker.run)
|
||
|
||
self.assertIn("_request_page_initial_state_load", init_source)
|
||
self.assertIn("_initial_state_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", request_source)
|
||
self.assertIn("bind_worker", request_source)
|
||
self.assertIn("worker.completed.connect(self._on_initial_state_loaded)", request_source)
|
||
self.assertIn("worker.failed.connect(self._on_initial_state_failed)", request_source)
|
||
self.assertIn("_initial_state_runtime.is_current", loaded_source)
|
||
self.assertIn("_initial_state_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_initial_state_worker =", page_source)
|
||
self.assertNotIn("worker.start()", request_source)
|
||
self.assertNotIn("self._blockcheck.load_page_initial_state()", init_source)
|
||
self.assertIn("_apply_initial_domain_chips", build_source)
|
||
self.assertNotIn("self._load_domain_chips()", build_source)
|
||
self.assertNotIn("load_domain_chips", helper_source)
|
||
self.assertIn("read_settings", runtime_source)
|
||
self.assertNotIn("get_blockcheck_settings", runtime_source)
|
||
self.assertIn("create_initial_state_worker", page_source)
|
||
self.assertIn("create_page_initial_state_worker", feature_source)
|
||
self.assertIn("load_page_initial_state=self.load_page_initial_state", feature_source)
|
||
self.assertIn("_load_page_initial_state", worker_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.page_runtime", worker_source)
|
||
self.assertIn("load_page_initial_state", worker_source)
|
||
|
||
def test_blockcheck_initial_state_plan_reads_settings_once(self) -> None:
|
||
data = {
|
||
"blockcheck": {
|
||
"user_domains": [" Example.COM ", "", "discord.com"],
|
||
},
|
||
}
|
||
|
||
with patch("settings.store.read_settings", return_value=data) as read_settings:
|
||
plan = blockcheck_page_runtime.load_page_initial_state()
|
||
|
||
self.assertEqual(read_settings.call_count, 1)
|
||
self.assertEqual(plan.user_domains, ("example.com", "discord.com"))
|
||
|
||
def test_blockcheck_run_log_writes_are_owned_by_worker(self) -> None:
|
||
start_source = inspect.getsource(blockcheck_page_run_workflow.start_blockcheck_page_run)
|
||
page_source = inspect.getsource(BlockcheckPage)
|
||
worker_source = inspect.getsource(blockcheck_worker.BlockcheckWorker)
|
||
|
||
self.assertNotIn("blockcheck_page_runtime.start_run_log", start_source)
|
||
self.assertNotIn("blockcheck_page_runtime.append_run_log", page_source)
|
||
self.assertNotIn("_append_run_log", page_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.page_runtime", worker_source)
|
||
self.assertIn("run_log_started", worker_source)
|
||
self.assertIn("_start_run_log", worker_source)
|
||
self.assertIn("_append_run_log_action", worker_source)
|
||
|
||
def test_strategy_scan_run_log_writes_are_owned_by_worker(self) -> None:
|
||
strategy_scan_run_workflow = importlib.import_module("blockcheck.strategy_scan_run_workflow")
|
||
strategy_scan_results_workflow = importlib.import_module("blockcheck.ui.strategy_scan_page_results_workflow")
|
||
strategy_scan_worker = importlib.import_module("blockcheck.strategy_scan_worker")
|
||
|
||
start_source = inspect.getsource(strategy_scan_run_workflow.start_strategy_scan_run)
|
||
run_workflow_source = inspect.getsource(strategy_scan_run_workflow)
|
||
results_workflow_source = inspect.getsource(strategy_scan_results_workflow)
|
||
worker_source = inspect.getsource(strategy_scan_worker.StrategyScanWorker)
|
||
|
||
self.assertNotIn(".start_run_log", start_source)
|
||
self.assertNotIn(".append_run_log", run_workflow_source)
|
||
self.assertNotIn("append_strategy_scan_log", results_workflow_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.strategy_scan_logs", worker_source)
|
||
self.assertIn("run_log_started", worker_source)
|
||
self.assertIn("_start_run_log_action", worker_source)
|
||
self.assertIn("_append_run_log_action", worker_source)
|
||
|
||
def test_strategy_scan_page_receives_worker_factory_not_runtime_feature(self) -> None:
|
||
from app.page_names import PageName
|
||
from ui.page_deps.system import build_blockcheck_page_kwargs
|
||
|
||
blockcheck_page_init = inspect.getsource(BlockcheckPage.__init__)
|
||
blockcheck_page_source = inspect.getsource(BlockcheckPage)
|
||
strategy_page_init = inspect.getsource(StrategyScanPage.__init__)
|
||
strategy_start_source = inspect.getsource(StrategyScanPage._on_start)
|
||
workflow_start_source = inspect.getsource(blockcheck_page_run_workflow.start_blockcheck_page_run)
|
||
strategy_workflow = importlib.import_module("blockcheck.strategy_scan_run_workflow")
|
||
strategy_workflow_source = inspect.getsource(strategy_workflow.start_strategy_scan_run)
|
||
|
||
self.assertNotIn("runtime_feature", blockcheck_page_init)
|
||
self.assertNotIn("self._runtime_feature", blockcheck_page_source)
|
||
self.assertNotIn("runtime_feature", strategy_page_init)
|
||
self.assertNotIn("self._runtime_feature", strategy_start_source)
|
||
self.assertIn("create_strategy_scan_worker", strategy_page_init)
|
||
self.assertIn("create_strategy_scan_worker", strategy_workflow_source)
|
||
self.assertNotIn("runtime_feature=runtime_feature", strategy_workflow_source)
|
||
self.assertNotIn("runtime_feature", workflow_start_source)
|
||
|
||
blockcheck_feature = Mock()
|
||
runtime_feature = Mock()
|
||
kwargs = build_blockcheck_page_kwargs(
|
||
page_name=PageName.BLOCKCHECK,
|
||
blockcheck_feature=blockcheck_feature,
|
||
diagnostics_feature=Mock(),
|
||
dns_feature=Mock(),
|
||
runtime_feature=runtime_feature,
|
||
)
|
||
|
||
self.assertNotIn("runtime_feature", kwargs)
|
||
self.assertIn("create_strategy_scan_worker", kwargs)
|
||
kwargs["create_strategy_scan_worker"](target="example.org", mode="quick", parent=object())
|
||
blockcheck_feature.create_strategy_scan_worker.assert_called_once()
|
||
_, call_kwargs = blockcheck_feature.create_strategy_scan_worker.call_args
|
||
# Сканер работает в своём QThread: остановка идёт через worker-вариант,
|
||
# который применяет runtime-state (и UI-подписчиков) в GUI-потоке.
|
||
self.assertIs(call_kwargs["shutdown_sync"], runtime_feature.shutdown_sync_from_worker)
|
||
|
||
def test_blockcheck_support_bundle_prepares_through_worker(self) -> None:
|
||
blockcheck_workers = importlib.import_module("blockcheck.workers")
|
||
page_source = inspect.getsource(BlockcheckPage)
|
||
handler_source = inspect.getsource(BlockcheckPage._prepare_support_from_blockcheck)
|
||
worker_source = inspect.getsource(blockcheck_workers.BlockcheckSupportPrepareWorker.run)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
|
||
self.assertIn("_request_support_prepare", handler_source)
|
||
self.assertNotIn("blockcheck_page_runtime.prepare_support", handler_source)
|
||
self.assertIn("create_support_prepare_worker", page_source)
|
||
self.assertIn("_support_prepare_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", page_source)
|
||
self.assertIn("bind_worker", page_source)
|
||
self.assertIn("_on_support_prepare_finished", page_source)
|
||
self.assertIn("_on_support_prepare_failed", page_source)
|
||
self.assertIn("_support_prepare_runtime.is_current", page_source)
|
||
self.assertIn("_support_prepare_runtime.stop", page_source)
|
||
self.assertNotIn("_support_prepare_worker =", page_source)
|
||
self.assertNotIn("worker.start()", inspect.getsource(BlockcheckPage._request_support_prepare))
|
||
self.assertIn("create_blockcheck_support_prepare_worker", feature_source)
|
||
self.assertIn("prepare_support=self.prepare_support", feature_source)
|
||
self.assertIn("_prepare_support", worker_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.page_runtime", worker_source)
|
||
self.assertIn("prepare_support", worker_source)
|
||
|
||
def test_blockcheck_user_domain_actions_run_through_commands(self) -> None:
|
||
blockcheck_workers = importlib.import_module("blockcheck.workers")
|
||
page_source = inspect.getsource(BlockcheckPage)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
worker_source = inspect.getsource(blockcheck_workers.BlockcheckUserDomainActionWorker.run)
|
||
|
||
self.assertIn("create_user_domain_action_worker", page_source)
|
||
self.assertIn("create_user_domain_action_worker", feature_source)
|
||
self.assertIn("run_user_domain_action=self.run_user_domain_action", feature_source)
|
||
self.assertIn("_run_user_domain_action", worker_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.page_runtime", worker_source)
|
||
self.assertIn("run_user_domain_action", worker_source)
|
||
|
||
def test_strategy_scan_support_bundle_prepares_through_worker(self) -> None:
|
||
blockcheck_workers = importlib.import_module("blockcheck.workers")
|
||
page_source = inspect.getsource(StrategyScanPage)
|
||
handler_source = inspect.getsource(StrategyScanPage._prepare_support_from_strategy_scan)
|
||
worker_source = inspect.getsource(blockcheck_workers.StrategyScanSupportPrepareWorker.run)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
|
||
self.assertIn("_request_support_prepare", handler_source)
|
||
self.assertNotIn("prepare_strategy_scan_support", handler_source)
|
||
self.assertIn("create_support_prepare_worker", page_source)
|
||
self.assertIn("_support_prepare_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", page_source)
|
||
self.assertNotIn("_support_prepare_worker =", page_source)
|
||
self.assertNotIn("worker.start()", inspect.getsource(StrategyScanPage._request_support_prepare))
|
||
self.assertIn("create_strategy_scan_support_prepare_worker", feature_source)
|
||
self.assertIn("prepare_strategy_scan_support=self.prepare_strategy_scan_support", feature_source)
|
||
self.assertIn("_prepare_strategy_scan_support", worker_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.strategy_scan_logs", worker_source)
|
||
self.assertIn("prepare_strategy_scan_support", worker_source)
|
||
|
||
def test_strategy_scan_apply_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("blockcheck.strategy_apply_worker")
|
||
self.assertIsNotNone(spec)
|
||
strategy_apply_worker = importlib.import_module("blockcheck.strategy_apply_worker")
|
||
|
||
apply_source = inspect.getsource(StrategyScanPage._on_apply_strategy)
|
||
page_source = inspect.getsource(StrategyScanPage)
|
||
finished_source = inspect.getsource(StrategyScanPage._on_strategy_apply_finished)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
worker_source = inspect.getsource(strategy_apply_worker.StrategyApplyWorker.run)
|
||
|
||
self.assertIn("_request_strategy_apply", apply_source)
|
||
self.assertNotIn("self._blockcheck.apply_strategy(", apply_source)
|
||
self.assertIn("create_strategy_apply_worker", page_source)
|
||
self.assertIn("_strategy_apply_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", page_source)
|
||
self.assertNotIn("_strategy_apply_worker =", page_source)
|
||
self.assertNotIn("worker.start()", inspect.getsource(StrategyScanPage._request_strategy_apply))
|
||
self.assertIn("create_strategy_apply_worker", feature_source)
|
||
self.assertIn("build_apply_success_plan", finished_source)
|
||
self.assertIn("apply_strategy", worker_source)
|
||
|
||
def test_strategy_scan_quick_targets_load_through_worker(self) -> None:
|
||
import blockcheck.workers as blockcheck_workers
|
||
|
||
page_source = inspect.getsource(StrategyScanPage)
|
||
handler_source = inspect.getsource(StrategyScanPage._show_quick_domains_menu)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
|
||
self.assertTrue(hasattr(blockcheck_workers, "StrategyScanQuickTargetsWorker"))
|
||
worker_source = inspect.getsource(blockcheck_workers.StrategyScanQuickTargetsWorker.run)
|
||
|
||
self.assertIn("create_quick_targets_worker", page_source)
|
||
self.assertIn("create_strategy_scan_quick_targets_worker", feature_source)
|
||
self.assertIn("build_quick_target_menu_plan=self.build_quick_target_menu_plan", feature_source)
|
||
self.assertIn("_build_quick_target_menu_plan", worker_source)
|
||
self.assertNotIn("blockcheck.commands", worker_source)
|
||
self.assertNotIn("blockcheck.strategy_scan_page_plans", worker_source)
|
||
self.assertIn("build_quick_target_menu_plan", worker_source)
|
||
self.assertNotIn("build_quick_target_menu_plan", handler_source)
|
||
|
||
def test_strategy_scan_resume_progress_saves_through_worker(self) -> None:
|
||
import blockcheck.workers as blockcheck_workers
|
||
|
||
page_source = inspect.getsource(StrategyScanPage)
|
||
result_source = inspect.getsource(StrategyScanPage._on_strategy_result)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
|
||
self.assertTrue(hasattr(blockcheck_workers, "StrategyScanResumeSaveWorker"))
|
||
worker_source = inspect.getsource(blockcheck_workers.StrategyScanResumeSaveWorker.run)
|
||
|
||
self.assertIn("_request_strategy_scan_resume_save", result_source)
|
||
self.assertNotIn("record_strategy_scan_result(", result_source)
|
||
self.assertIn("_strategy_scan_resume_save_worker", page_source)
|
||
self.assertIn("create_strategy_scan_resume_save_worker", feature_source)
|
||
self.assertIn("save_resume_state=self.save_resume_state", feature_source)
|
||
self.assertIn("_save_resume_state", worker_source)
|
||
self.assertNotIn("blockcheck_public", worker_source)
|
||
self.assertIn("save_resume_state", worker_source)
|
||
|
||
def test_strategy_scan_finish_plan_finalizes_through_worker(self) -> None:
|
||
import blockcheck.workers as blockcheck_workers
|
||
|
||
strategy_scan_results_workflow = importlib.import_module("blockcheck.ui.strategy_scan_page_results_workflow")
|
||
page_source = inspect.getsource(StrategyScanPage)
|
||
finished_source = inspect.getsource(StrategyScanPage._on_finished)
|
||
apply_finished_source = inspect.getsource(strategy_scan_results_workflow.apply_finished_scan)
|
||
feature_source = inspect.getsource(BlockcheckFeature)
|
||
|
||
self.assertTrue(hasattr(blockcheck_workers, "StrategyScanFinalizeWorker"))
|
||
worker_source = inspect.getsource(blockcheck_workers.StrategyScanFinalizeWorker.run)
|
||
|
||
self.assertIn("_request_strategy_scan_finalize", finished_source)
|
||
self.assertIn("create_strategy_scan_finalize_worker", page_source)
|
||
self.assertIn("_strategy_scan_finalize_worker", page_source)
|
||
self.assertIn("create_strategy_scan_finalize_worker", feature_source)
|
||
self.assertNotIn("finalize_scan_report", apply_finished_source)
|
||
self.assertIn("finalize_scan_report=self.finalize_scan_report", feature_source)
|
||
self.assertIn("_finalize_scan_report", worker_source)
|
||
self.assertNotIn("blockcheck_public", worker_source)
|
||
self.assertIn("finalize_scan_report", worker_source)
|
||
|
||
def test_logs_file_operations_log_internal_timing_stages(self) -> None:
|
||
list_source = inspect.getsource(log_commands.list_logs)
|
||
stats_source = inspect.getsource(log_commands.build_stats)
|
||
|
||
self.assertIn("logs_feature.list_logs.total", list_source)
|
||
self.assertIn("logs_feature.list_logs.glob", list_source)
|
||
self.assertIn("logs_feature.list_logs.sort", list_source)
|
||
self.assertIn("logs_feature.build_stats.total", stats_source)
|
||
|
||
def test_logs_page_file_listing_and_stats_are_loaded_through_worker(self) -> None:
|
||
from app.page_names import PageName
|
||
from ui.page_composition import PAGE_DEPS_BUILDERS
|
||
from ui.page_deps.system import build_logs_page_kwargs
|
||
|
||
init_source = inspect.getsource(LogsPage.__init__)
|
||
deps_source = inspect.getsource(build_logs_page_kwargs)
|
||
page_source = inspect.getsource(LogsPage)
|
||
refresh_source = inspect.getsource(LogsPage._refresh_logs_list)
|
||
stats_source = inspect.getsource(LogsPage._update_stats)
|
||
runtime_source = inspect.getsource(LogsPage._run_runtime_init_once)
|
||
|
||
self.assertNotIn("runtime_feature", init_source)
|
||
self.assertNotIn("runtime_feature", deps_source)
|
||
self.assertNotIn("self._runtime =", page_source)
|
||
self.assertNotIn("runtime", PAGE_DEPS_BUILDERS[PageName.LOGS].features)
|
||
self.assertIn("_start_logs_overview_worker", refresh_source)
|
||
self.assertIn("_start_logs_overview_worker", stats_source)
|
||
self.assertNotIn(".list_logs(", refresh_source)
|
||
self.assertNotIn(".build_stats(", stats_source)
|
||
self.assertIn("update_stats_fn=self._update_stats", runtime_source)
|
||
self.assertNotIn("refresh_logs_fn=", runtime_source)
|
||
|
||
kwargs = build_logs_page_kwargs(
|
||
page_name=PageName.LOGS,
|
||
logs_feature=Mock(),
|
||
orchestra_feature=Mock(),
|
||
)
|
||
self.assertNotIn("runtime_feature", kwargs)
|
||
|
||
def test_logs_page_secondary_panels_are_built_after_initial_shell(self) -> None:
|
||
build_source = inspect.getsource(LogsPage._build_logs_tab)
|
||
activation_source = inspect.getsource(LogsPage.on_page_activated)
|
||
runtime_schedule_source = inspect.getsource(LogsPage._schedule_runtime_init)
|
||
runtime_flush_source = inspect.getsource(LogsPage._run_scheduled_runtime_init)
|
||
scheduled_source = inspect.getsource(LogsPage._schedule_logs_secondary_panels)
|
||
ensure_source = inspect.getsource(LogsPage._ensure_logs_secondary_panels)
|
||
|
||
self.assertIn("build_logs_primary_tab_ui", build_source)
|
||
self.assertNotIn("build_logs_secondary_panels_ui", build_source)
|
||
self.assertIn("_schedule_logs_secondary_panels", activation_source)
|
||
self.assertIn("_schedule_runtime_init", activation_source)
|
||
self.assertNotIn("self._run_runtime_init_once()", activation_source)
|
||
self.assertIn("QTimer.singleShot", runtime_schedule_source)
|
||
self.assertIn("_run_runtime_init_once", runtime_flush_source)
|
||
self.assertIn("QTimer.singleShot", scheduled_source)
|
||
self.assertIn("build_logs_secondary_panels_ui", ensure_source)
|
||
|
||
def test_logs_page_management_tab_is_built_only_when_opened(self) -> None:
|
||
build_source = inspect.getsource(LogsPage._build_ui)
|
||
switch_source = inspect.getsource(LogsPage._switch_tab)
|
||
|
||
self.assertNotIn("self._build_manage_tab", build_source)
|
||
self.assertIn("index == 2", switch_source)
|
||
self.assertIn("self._build_manage_tab", switch_source)
|
||
|
||
def test_common_one_shot_worker_runtime_is_used_by_shared_pages(self) -> None:
|
||
self.assertIsNotNone(importlib.util.find_spec("ui.one_shot_worker_runtime"))
|
||
runtime = importlib.import_module("ui.one_shot_worker_runtime")
|
||
|
||
runtime_source = inspect.getsource(runtime)
|
||
hosts_source = inspect.getsource(HostsPage)
|
||
logs_source = inspect.getsource(LogsPage)
|
||
locked_source = inspect.getsource(OrchestraLockedPage)
|
||
blocked_source = inspect.getsource(OrchestraBlockedPage)
|
||
ratings_source = inspect.getsource(OrchestraRatingsPage)
|
||
updater_source = inspect.getsource(ServersPage)
|
||
update_runtime_source = inspect.getsource(__import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime)
|
||
|
||
self.assertIn("class OneShotWorkerRuntime", runtime_source)
|
||
self.assertIn("start_qobject_worker", runtime_source)
|
||
self.assertIn("start_qthread_worker", runtime_source)
|
||
for source in (
|
||
hosts_source,
|
||
logs_source,
|
||
locked_source,
|
||
blocked_source,
|
||
ratings_source,
|
||
updater_source + update_runtime_source,
|
||
):
|
||
self.assertIn("OneShotWorkerRuntime", source)
|
||
|
||
def test_updater_auto_check_save_runs_through_worker(self) -> None:
|
||
settings_workers = importlib.import_module("updater.settings_workers")
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
updater_feature_cls = __import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature
|
||
|
||
handler_source = inspect.getsource(update_runtime_cls.set_auto_check_enabled)
|
||
runtime_source = inspect.getsource(update_runtime_cls)
|
||
feature_source = inspect.getsource(updater_feature_cls)
|
||
|
||
self.assertTrue(hasattr(settings_workers, "UpdaterAutoCheckSaveWorker"))
|
||
worker_source = inspect.getsource(settings_workers.UpdaterAutoCheckSaveWorker.run)
|
||
|
||
self.assertIn("_request_auto_check_save", handler_source)
|
||
self.assertNotIn("set_auto_update_enabled", handler_source)
|
||
self.assertIn("_auto_check_save_pending", runtime_source)
|
||
self.assertIn("create_auto_check_save_worker", feature_source)
|
||
self.assertIn("set_auto_update_enabled=self.set_auto_update_enabled", feature_source)
|
||
self.assertIn("_set_auto_update_enabled", worker_source)
|
||
self.assertNotIn("updater_commands", worker_source)
|
||
|
||
def test_updater_auto_check_initial_read_runs_through_worker(self) -> None:
|
||
settings_workers = importlib.import_module("updater.settings_workers")
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
updater_feature_cls = __import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature
|
||
|
||
init_source = inspect.getsource(update_runtime_cls.__init__)
|
||
runtime_source = inspect.getsource(update_runtime_cls)
|
||
page_source = inspect.getsource(ServersPage)
|
||
feature_source = inspect.getsource(updater_feature_cls)
|
||
|
||
self.assertTrue(hasattr(settings_workers, "UpdaterAutoCheckLoadWorker"))
|
||
worker_source = inspect.getsource(settings_workers.UpdaterAutoCheckLoadWorker.run)
|
||
|
||
self.assertNotIn("is_auto_update_enabled", init_source)
|
||
self.assertIn("_request_auto_check_load", runtime_source)
|
||
self.assertIn("_auto_check_load_runtime", runtime_source)
|
||
self.assertIn("set_auto_check_toggle_checked", runtime_source)
|
||
self.assertIn("start_auto_check_load", page_source)
|
||
self.assertIn("create_auto_check_load_worker", feature_source)
|
||
self.assertIn("is_auto_update_enabled=self.is_auto_update_enabled", feature_source)
|
||
self.assertIn("_is_auto_update_enabled", worker_source)
|
||
self.assertNotIn("updater_commands", worker_source)
|
||
|
||
def test_updater_open_channel_runs_through_worker(self) -> None:
|
||
settings_workers = importlib.import_module("updater.settings_workers")
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
updater_feature_cls = __import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature
|
||
|
||
page_handler_source = inspect.getsource(ServersPage._open_telegram_channel)
|
||
runtime_source = inspect.getsource(update_runtime_cls)
|
||
feature_source = inspect.getsource(updater_feature_cls)
|
||
|
||
self.assertTrue(hasattr(settings_workers, "UpdaterChannelOpenWorker"))
|
||
worker_source = inspect.getsource(settings_workers.UpdaterChannelOpenWorker.run)
|
||
|
||
self.assertIn("request_open_update_channel", page_handler_source)
|
||
self.assertNotIn("self._update_runtime.open_update_channel", page_handler_source)
|
||
self.assertIn("_update_channel_open_runtime", runtime_source)
|
||
self.assertIn("create_update_channel_open_worker", feature_source)
|
||
self.assertIn("open_update_channel=self.open_update_channel", feature_source)
|
||
self.assertIn("_open_update_channel", worker_source)
|
||
self.assertNotIn("updater_commands", worker_source)
|
||
|
||
def test_updater_changelog_links_open_through_worker(self) -> None:
|
||
from app.page_names import PageName
|
||
from ui.page_deps.system import build_servers_page_kwargs
|
||
|
||
external_workers = importlib.import_module("app.external_workers")
|
||
page_source = inspect.getsource(ServersPage)
|
||
init_source = inspect.getsource(ServersPage.__init__)
|
||
build_source = inspect.getsource(ServersPage._build_ui)
|
||
create_source = inspect.getsource(ServersPage.create_changelog_link_open_worker)
|
||
|
||
self.assertTrue(hasattr(external_workers, "ExternalOpenUrlWorker"))
|
||
worker_source = inspect.getsource(external_workers.ExternalOpenUrlWorker.run)
|
||
|
||
self.assertIn("open_url=self._request_changelog_link_open", build_source)
|
||
self.assertNotIn("open_url=self._external_actions.open_url", build_source)
|
||
self.assertIn("create_changelog_link_open_worker", page_source)
|
||
self.assertNotIn("external_actions_feature", init_source)
|
||
self.assertNotIn("self._external_actions", page_source)
|
||
self.assertIn("self._create_changelog_link_open_worker", create_source)
|
||
self.assertIn("_request_changelog_link_open", page_source)
|
||
self.assertIn("_changelog_link_open_runtime", page_source)
|
||
self.assertIn("_stop_changelog_link_open_worker", page_source)
|
||
self.assertIn("open_url", worker_source)
|
||
|
||
external_actions = Mock()
|
||
kwargs = build_servers_page_kwargs(
|
||
page_name=PageName.SERVERS,
|
||
runtime_feature=Mock(),
|
||
updater_feature=Mock(),
|
||
external_actions_feature=external_actions,
|
||
show_page=Mock(),
|
||
request_exit=Mock(),
|
||
)
|
||
self.assertNotIn("external_actions_feature", kwargs)
|
||
self.assertIn("create_changelog_link_open_worker", kwargs)
|
||
|
||
parent = object()
|
||
kwargs["create_changelog_link_open_worker"](8, url="https://example.org", parent=parent)
|
||
external_actions.create_open_url_worker.assert_called_once_with(
|
||
8,
|
||
url="https://example.org",
|
||
parent=parent,
|
||
)
|
||
|
||
def test_updater_pipeline_workers_are_created_through_feature(self) -> None:
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
updater_feature_cls = __import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature
|
||
|
||
preflight_source = inspect.getsource(update_runtime_cls._start_update_download)
|
||
download_source = inspect.getsource(update_runtime_cls._start_update_download_stage)
|
||
installer_source = inspect.getsource(update_runtime_cls._start_update_installer_stage)
|
||
feature_source = inspect.getsource(updater_feature_cls)
|
||
|
||
self.assertIn("create_update_preflight_worker", preflight_source)
|
||
self.assertIn("create_update_download_worker", download_source)
|
||
self.assertIn("create_update_installer_worker", installer_source)
|
||
self.assertIn("UpdatePreflightWorker", feature_source)
|
||
self.assertIn("UpdateDownloadWorker", feature_source)
|
||
self.assertIn("UpdateInstallerWorker", feature_source)
|
||
self.assertNotIn("UpdateWorker", feature_source)
|
||
|
||
def test_updater_cache_invalidation_runs_through_worker(self) -> None:
|
||
settings_workers = importlib.import_module("updater.settings_workers")
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
updater_feature_cls = __import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature
|
||
|
||
manual_check_source = inspect.getsource(update_runtime_cls.request_manual_check)
|
||
install_source = inspect.getsource(update_runtime_cls.install_update)
|
||
runtime_source = inspect.getsource(update_runtime_cls)
|
||
feature_source = inspect.getsource(updater_feature_cls)
|
||
|
||
self.assertTrue(hasattr(settings_workers, "UpdaterCacheInvalidateWorker"))
|
||
worker_source = inspect.getsource(settings_workers.UpdaterCacheInvalidateWorker.run)
|
||
|
||
self.assertIn("_request_update_cache_invalidate", manual_check_source)
|
||
self.assertIn("_request_update_cache_invalidate", install_source)
|
||
self.assertNotIn("invalidate_cache", manual_check_source)
|
||
self.assertNotIn("invalidate_cache", install_source)
|
||
self.assertIn("_cache_invalidate_runtime", runtime_source)
|
||
self.assertIn("create_cache_invalidate_worker", runtime_source)
|
||
self.assertIn("create_cache_invalidate_worker", feature_source)
|
||
self.assertIn("invalidate_update_cache=self.invalidate_update_cache", feature_source)
|
||
self.assertIn("_invalidate_update_cache", worker_source)
|
||
|
||
def test_updater_server_retry_without_dpi_runs_through_worker(self) -> None:
|
||
retry_workers = importlib.import_module("updater.retry_workers")
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
updater_feature_cls = __import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature
|
||
|
||
retry_source = inspect.getsource(update_runtime_cls._maybe_retry_server_check_without_dpi)
|
||
runtime_source = inspect.getsource(update_runtime_cls)
|
||
|
||
self.assertTrue(hasattr(retry_workers, "UpdaterServerRetryWithoutDpiWorker"))
|
||
worker_source = inspect.getsource(retry_workers.UpdaterServerRetryWithoutDpiWorker.run)
|
||
command_source = inspect.getsource(importlib.import_module("updater.commands").retry_server_check_without_dpi)
|
||
feature_source = inspect.getsource(updater_feature_cls)
|
||
|
||
self.assertIn("_request_server_retry_without_dpi", retry_source)
|
||
self.assertNotIn("shutdown_sync", retry_source)
|
||
self.assertNotIn("is_any_running", retry_source)
|
||
self.assertIn("_server_retry_without_dpi_runtime", runtime_source)
|
||
self.assertIn("create_server_retry_without_dpi_worker", runtime_source)
|
||
self.assertIn("_teardown_server_retry_without_dpi_worker", runtime_source)
|
||
self.assertIn("create_server_retry_without_dpi_worker", feature_source)
|
||
self.assertIn("retry_server_check_without_dpi=self.retry_server_check_without_dpi", feature_source)
|
||
self.assertIn("_retry_server_check_without_dpi", worker_source)
|
||
self.assertNotIn("updater.commands", worker_source)
|
||
self.assertIn("retry_server_check_without_dpi", worker_source)
|
||
self.assertNotIn("self._is_any_running(", worker_source)
|
||
self.assertNotIn("self._shutdown_sync(", worker_source)
|
||
self.assertIn("is_any_running", command_source)
|
||
self.assertIn("shutdown_sync", command_source)
|
||
|
||
def test_updater_dpi_restart_runs_through_worker(self) -> None:
|
||
retry_workers = importlib.import_module("updater.retry_workers")
|
||
update_runtime_cls = __import__("updater.update_page_runtime", fromlist=["UpdatePageRuntime"]).UpdatePageRuntime
|
||
|
||
restart_source = inspect.getsource(update_runtime_cls._restart_dpi_after_update)
|
||
runtime_source = inspect.getsource(update_runtime_cls)
|
||
|
||
self.assertTrue(hasattr(retry_workers, "UpdaterDpiRestartWorker"))
|
||
worker_source = inspect.getsource(retry_workers.UpdaterDpiRestartWorker.run)
|
||
command_source = inspect.getsource(importlib.import_module("updater.commands").restart_dpi_after_update)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.updater", fromlist=["UpdaterFeature"]).UpdaterFeature)
|
||
|
||
self.assertIn("_request_dpi_restart", restart_source)
|
||
self.assertNotIn(".restart(", restart_source)
|
||
self.assertNotIn(".is_available(", restart_source)
|
||
self.assertIn("_dpi_restart_runtime", runtime_source)
|
||
self.assertIn("create_dpi_restart_worker", runtime_source)
|
||
self.assertIn("_teardown_dpi_restart_worker", runtime_source)
|
||
self.assertIn("create_dpi_restart_worker", feature_source)
|
||
self.assertIn("restart_dpi_after_update=self.restart_dpi_after_update", feature_source)
|
||
self.assertIn("_restart_dpi_after_update", worker_source)
|
||
self.assertNotIn("updater.commands", worker_source)
|
||
self.assertIn("restart_dpi_after_update", worker_source)
|
||
self.assertNotIn(".is_available(", worker_source)
|
||
self.assertNotIn(".restart(", worker_source)
|
||
self.assertIn("is_available", command_source)
|
||
self.assertIn("restart", command_source)
|
||
|
||
def test_logs_cleanup_stops_overview_worker(self) -> None:
|
||
cleanup_source = inspect.getsource(LogsPage.cleanup)
|
||
|
||
self.assertIn("_stop_logs_overview_worker(blocking=False)", cleanup_source)
|
||
self.assertIn("_stop_log_source(blocking=False)", cleanup_source)
|
||
|
||
def test_logs_support_bundle_prepares_through_worker(self) -> None:
|
||
support_worker = importlib.import_module("log.support_worker")
|
||
page_source = inspect.getsource(LogsPage)
|
||
handler_source = inspect.getsource(LogsPage._prepare_support_from_logs)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.logs", fromlist=["LogsFeature"]).LogsFeature)
|
||
|
||
self.assertTrue(hasattr(support_worker, "LogsSupportPrepareWorker"))
|
||
worker_source = inspect.getsource(support_worker.LogsSupportPrepareWorker.run)
|
||
|
||
self.assertIn("_request_support_prepare", handler_source)
|
||
self.assertNotIn("prepare_support_bundle", handler_source)
|
||
self.assertIn("create_support_prepare_worker", page_source)
|
||
self.assertIn("_support_prepare_runtime", page_source)
|
||
self.assertIn("create_support_prepare_worker", feature_source)
|
||
self.assertIn("prepare_support_bundle=self.prepare_support_bundle", feature_source)
|
||
self.assertIn("_prepare_support_bundle", worker_source)
|
||
self.assertNotIn("log_commands", worker_source)
|
||
self.assertIn("prepare_support_bundle", worker_source)
|
||
|
||
def test_logs_open_folder_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("log.open_folder_worker")
|
||
self.assertIsNotNone(spec)
|
||
open_worker = importlib.import_module("log.open_folder_worker")
|
||
page_source = inspect.getsource(LogsPage)
|
||
handler_source = inspect.getsource(LogsPage._open_folder)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.logs", fromlist=["LogsFeature"]).LogsFeature)
|
||
|
||
self.assertTrue(hasattr(open_worker, "LogsOpenFolderWorker"))
|
||
worker_source = inspect.getsource(open_worker.LogsOpenFolderWorker.run)
|
||
|
||
self.assertIn("_request_open_logs_folder", handler_source)
|
||
self.assertNotIn(".open_logs_folder(", handler_source)
|
||
self.assertIn("create_open_folder_worker", page_source)
|
||
self.assertIn("_open_folder_runtime", page_source)
|
||
self.assertIn("create_open_folder_worker", feature_source)
|
||
self.assertIn("open_logs_folder", worker_source)
|
||
|
||
def test_profile_setup_page_loads_profile_payload_through_worker(self) -> None:
|
||
source = inspect.getsource(ProfileSetupPageBase.reload_current_profile)
|
||
|
||
self.assertNotIn("self._controller.load(", source)
|
||
self.assertIn("_request_profile_setup_payload", source)
|
||
|
||
def test_preset_selection_state_uses_profile_snapshot(self) -> None:
|
||
source = inspect.getsource(preset_commands._profile_selection_details)
|
||
|
||
self.assertNotIn(".list_profiles(", source)
|
||
self.assertNotIn("get_profile_setup(", source)
|
||
self.assertIn("get_profile_selection_details", source)
|
||
|
||
def test_control_pages_use_cached_profile_count(self) -> None:
|
||
zapret2_source = inspect.getsource(Zapret2ModeControlPage)
|
||
zapret1_source = inspect.getsource(Zapret1ModeControlPage)
|
||
worker_source = inspect.getsource(control_additional_settings_runtime.ControlTopSummaryWorker.run)
|
||
factory_source = inspect.getsource(control_additional_settings_runtime.create_top_summary_worker)
|
||
|
||
self.assertFalse(hasattr(Zapret2ModeControlPage, "_load_enabled_profile_count"))
|
||
self.assertFalse(hasattr(Zapret1ModeControlPage, "_load_enabled_profile_count"))
|
||
self.assertNotIn("count_enabled_profiles", zapret2_source)
|
||
self.assertNotIn("count_enabled_profiles", zapret1_source)
|
||
self.assertNotIn("get_enabled_profile_count_snapshot", worker_source)
|
||
self.assertIn("get_enabled_profile_count_snapshot", factory_source)
|
||
self.assertNotIn("presets_feature", factory_source)
|
||
self.assertNotIn("profile_feature", factory_source)
|
||
|
||
def test_control_page_docs_open_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("app.external_workers")
|
||
self.assertIsNotNone(spec)
|
||
external_workers = importlib.import_module("app.external_workers")
|
||
external_feature_cls = __import__("app.feature_facades.external", fromlist=["ExternalActionsFeature"]).ExternalActionsFeature
|
||
shared_source = inspect.getsource(control_page_shared.ControlPageActionMixin)
|
||
zapret1_open_source = inspect.getsource(Zapret1ModeControlPage._open_docs)
|
||
zapret2_open_source = inspect.getsource(Zapret2ModeControlPage._open_docs)
|
||
zapret1_cleanup_source = inspect.getsource(Zapret1ModeControlPage.cleanup)
|
||
zapret2_cleanup_source = inspect.getsource(Zapret2ModeControlPage.cleanup)
|
||
|
||
self.assertTrue(hasattr(external_workers, "ExternalOpenUrlWorker"))
|
||
worker_source = inspect.getsource(external_workers.ExternalOpenUrlWorker.run)
|
||
feature_source = inspect.getsource(external_feature_cls)
|
||
|
||
for source in (zapret1_open_source, zapret2_open_source):
|
||
self.assertIn("_request_external_open_url", source)
|
||
self.assertNotIn(".open_url(", source)
|
||
|
||
self.assertIn("create_open_url_worker", feature_source)
|
||
self.assertIn("create_external_open_url_worker", shared_source)
|
||
self.assertIn("_external_open_url_runtime", shared_source)
|
||
self.assertIn("open_url=self.open_url", feature_source)
|
||
self.assertIn("open_url", worker_source)
|
||
self.assertNotIn("external_commands", worker_source)
|
||
self.assertIn("_stop_external_open_url_worker", zapret1_cleanup_source)
|
||
self.assertIn("_stop_external_open_url_worker", zapret2_cleanup_source)
|
||
|
||
def test_zapret1_additional_settings_loads_through_worker(self) -> None:
|
||
page_source = inspect.getsource(Zapret1ModeControlPage)
|
||
refresh_source = inspect.getsource(Zapret1ModeControlPage._refresh_additional_settings)
|
||
pending_source = inspect.getsource(Zapret1ModeControlPage._apply_pending_additional_settings_refresh)
|
||
|
||
self.assertIn("_schedule_additional_settings_reload", refresh_source)
|
||
self.assertIn("_schedule_additional_settings_reload", pending_source)
|
||
self.assertIn("create_additional_settings_worker", page_source)
|
||
self.assertNotIn("get_additional_settings_state", page_source)
|
||
self.assertNotIn("_load_additional_settings_state", page_source)
|
||
|
||
def test_additional_settings_worker_uses_requested_launch_method(self) -> None:
|
||
worker_init_source = inspect.getsource(profile_additional_settings_loader.AdditionalSettingsLoadWorker.__init__)
|
||
worker_run_source = inspect.getsource(profile_additional_settings_loader.AdditionalSettingsLoadWorker.run)
|
||
|
||
self.assertIn("state_loader", worker_init_source)
|
||
self.assertIn("self._state_loader", worker_init_source)
|
||
self.assertNotIn("self._profile", worker_init_source)
|
||
self.assertNotIn("launch_method", worker_init_source)
|
||
self.assertNotIn("self._launch_method", worker_init_source)
|
||
self.assertIn("self._state_loader()", worker_run_source)
|
||
self.assertNotIn("get_additional_settings_state", worker_run_source)
|
||
self.assertNotIn("ZAPRET2_MODE", worker_run_source)
|
||
factory_source = inspect.getsource(control_additional_settings_runtime.create_additional_settings_worker)
|
||
self.assertIn("create_load_worker", factory_source)
|
||
self.assertNotIn("profile_feature", factory_source)
|
||
|
||
def test_control_additional_settings_runtime_is_shared(self) -> None:
|
||
shared_source = inspect.getsource(control_additional_settings_runtime)
|
||
zapret1_source = inspect.getsource(zapret1_runtime_helpers)
|
||
zapret2_source = inspect.getsource(zapret2_page_runtime)
|
||
zapret2_page_source = inspect.getsource(Zapret2ModeControlPage._schedule_additional_settings_reload)
|
||
|
||
self.assertIn("class ModeControlRefreshRuntime", shared_source)
|
||
self.assertIn("class ControlAdditionalSettingsState", shared_source)
|
||
self.assertIn("additional_settings_runtime", zapret1_source)
|
||
self.assertIn("additional_settings_runtime", zapret2_source)
|
||
self.assertNotIn("class ModeControlRefreshRuntime", zapret1_source)
|
||
self.assertNotIn("class ModeControlRefreshRuntime", zapret2_source)
|
||
self.assertNotIn("class ControlAdditionalSettingsState", zapret1_source)
|
||
self.assertNotIn("class ControlAdditionalSettingsState", zapret2_source)
|
||
self.assertNotIn("def create_additional_settings_worker", zapret2_source)
|
||
self.assertIn("create_control_additional_settings_worker", zapret2_page_source)
|
||
self.assertIn("launch_method=ZAPRET2_MODE", zapret2_page_source)
|
||
|
||
def test_control_preset_switch_refresh_uses_restartable_runtime_timers(self) -> None:
|
||
runtime_source = inspect.getsource(control_additional_settings_runtime.ModeControlRefreshRuntime)
|
||
page_sources = [
|
||
inspect.getsource(Zapret1ModeControlPage._schedule_top_summary_reload_after_preset_switch),
|
||
inspect.getsource(Zapret1ModeControlPage._schedule_additional_settings_reload_after_preset_switch),
|
||
inspect.getsource(Zapret2ModeControlPage._schedule_top_summary_reload_after_preset_switch),
|
||
inspect.getsource(Zapret2ModeControlPage._schedule_additional_settings_reload_after_preset_switch),
|
||
]
|
||
|
||
self.assertIn("schedule_top_summary_preset_switch_reload", runtime_source)
|
||
self.assertIn("schedule_additional_settings_preset_switch_reload", runtime_source)
|
||
self.assertIn("timer.start(max(0, int(delay_ms)))", runtime_source)
|
||
self.assertIn("top_summary_preset_switch_reload_timer.stop()", runtime_source)
|
||
self.assertIn("additional_settings_preset_switch_reload_timer.stop()", runtime_source)
|
||
for source in page_sources:
|
||
self.assertIn("runtime.schedule_", source)
|
||
self.assertNotIn("QTimer.singleShot", source)
|
||
self.assertNotIn("state.schedule_start", source)
|
||
|
||
def test_control_additional_settings_saves_through_worker(self) -> None:
|
||
zapret1_discord_source = inspect.getsource(Zapret1ModeControlPage._on_discord_restart_changed)
|
||
zapret1_wssize_source = inspect.getsource(Zapret1ModeControlPage._on_wssize_toggled)
|
||
zapret1_debug_source = inspect.getsource(Zapret1ModeControlPage._on_debug_log_toggled)
|
||
zapret2_discord_source = inspect.getsource(Zapret2ModeControlPage._on_discord_restart_changed)
|
||
zapret2_wssize_source = inspect.getsource(Zapret2ModeControlPage._on_wssize_toggled)
|
||
zapret2_debug_source = inspect.getsource(Zapret2ModeControlPage._on_debug_log_toggled)
|
||
|
||
self.assertTrue(hasattr(control_additional_settings_runtime, "AdditionalSettingsSaveWorker"))
|
||
worker_source = inspect.getsource(control_additional_settings_runtime.AdditionalSettingsSaveWorker.run)
|
||
|
||
for source in (
|
||
zapret1_wssize_source,
|
||
zapret1_debug_source,
|
||
zapret2_wssize_source,
|
||
zapret2_debug_source,
|
||
):
|
||
self.assertIn("_request_additional_settings_save", source)
|
||
self.assertNotIn("save_wssize_enabled(", source)
|
||
self.assertNotIn("save_debug_log_enabled(", source)
|
||
self.assertNotIn("set_wssize_enabled(", source)
|
||
self.assertNotIn("set_debug_log_enabled(", source)
|
||
|
||
for source in (zapret1_discord_source, zapret2_discord_source):
|
||
self.assertIn('_request_additional_settings_save("discord_restart"', source)
|
||
self.assertNotIn("save_discord_restart_setting(", source)
|
||
self.assertNotIn("set_discord_restart_setting(", source)
|
||
self.assertIn("create_additional_settings_save_worker", inspect.getsource(control_additional_settings_runtime))
|
||
worker_init_source = inspect.getsource(control_additional_settings_runtime.AdditionalSettingsSaveWorker.__init__)
|
||
factory_source = inspect.getsource(control_additional_settings_runtime.create_additional_settings_save_worker)
|
||
|
||
self.assertIn("save_setting", worker_init_source)
|
||
self.assertIn("self._save_setting", worker_init_source)
|
||
self.assertNotIn("_profile_feature", worker_init_source)
|
||
self.assertNotIn("launch_method", worker_init_source)
|
||
self.assertIn("self._save_setting(self._setting, self._enabled)", worker_source)
|
||
self.assertNotIn("set_discord_restart_setting", worker_source)
|
||
self.assertNotIn("set_wssize_enabled", worker_source)
|
||
self.assertNotIn("set_debug_log_enabled", worker_source)
|
||
self.assertIn("set_discord_restart_setting", factory_source)
|
||
self.assertIn("set_wssize_enabled", factory_source)
|
||
self.assertIn("set_debug_log_enabled", factory_source)
|
||
self.assertNotIn("profile_feature", factory_source)
|
||
|
||
def test_control_additional_settings_has_no_legacy_sync_save_helpers(self) -> None:
|
||
for module in (zapret1_runtime_helpers, zapret2_page_runtime):
|
||
self.assertFalse(hasattr(module, "save_wssize_enabled"))
|
||
self.assertFalse(hasattr(module, "save_debug_log_enabled"))
|
||
|
||
def test_control_program_settings_save_runs_through_worker(self) -> None:
|
||
zapret1_auto_source = inspect.getsource(Zapret1ModeControlPage._on_auto_dpi_toggled)
|
||
zapret1_tray_source = inspect.getsource(Zapret1ModeControlPage._on_tray_close_mode_changed)
|
||
zapret2_auto_source = inspect.getsource(Zapret2ModeControlPage._on_auto_dpi_toggled)
|
||
zapret2_tray_source = inspect.getsource(Zapret2ModeControlPage._on_tray_close_mode_changed)
|
||
defender_source = inspect.getsource(windows_features_runtime.ControlPageWindowsFeatureMixin._continue_defender_toggle)
|
||
max_source = inspect.getsource(windows_features_runtime.ControlPageWindowsFeatureMixin._on_max_blocker_toggled)
|
||
|
||
self.assertTrue(hasattr(program_settings_workers, "ProgramSettingsSaveWorker"))
|
||
worker_source = inspect.getsource(program_settings_workers.ProgramSettingsSaveWorker.run)
|
||
feature_source = inspect.getsource(
|
||
__import__("app.feature_facades.program_settings", fromlist=["ProgramSettingsFeature"])
|
||
.ProgramSettingsFeature.create_program_settings_save_worker
|
||
)
|
||
shared_source = inspect.getsource(control_page_shared.ControlPageActionMixin)
|
||
runtime_source = inspect.getsource(control_additional_settings_runtime.ModeControlRefreshRuntime)
|
||
|
||
for source in (
|
||
zapret1_auto_source,
|
||
zapret1_tray_source,
|
||
zapret2_auto_source,
|
||
zapret2_tray_source,
|
||
):
|
||
self.assertIn("_request_program_settings_save", source)
|
||
self.assertNotIn("self._program_settings.set_auto_dpi_enabled", source)
|
||
self.assertNotIn("self._program_settings.set_tray_close_mode", source)
|
||
|
||
self.assertIn("create_program_settings_save_worker", shared_source)
|
||
self.assertIn("program_settings_save_state", shared_source)
|
||
self.assertIn("program_settings_save_runtime", runtime_source)
|
||
self.assertIn("program_settings_save_state", runtime_source)
|
||
self.assertIn("start_qthread_worker", inspect.getsource(control_page_shared.ControlPageActionMixin._request_program_settings_save))
|
||
self.assertNotIn("worker.start()", inspect.getsource(control_page_shared.ControlPageActionMixin._request_program_settings_save))
|
||
self.assertIn("save_action=self._program_settings_save_action(action)", feature_source)
|
||
self.assertIn("_save_action", worker_source)
|
||
self.assertNotIn("program_settings_commands", worker_source)
|
||
self.assertIn('_request_program_settings_save("defender_disabled"', defender_source)
|
||
self.assertIn('_request_program_settings_save("max_block"', max_source)
|
||
self.assertNotIn("self._program_settings.set_defender_disabled", defender_source)
|
||
self.assertNotIn("self._program_settings.set_max_block_enabled", max_source)
|
||
self.assertNotIn("set_defender_disabled", worker_source)
|
||
self.assertNotIn("set_max_block_enabled", worker_source)
|
||
|
||
def test_defender_admin_check_runs_through_worker(self) -> None:
|
||
defender_source = "\n".join(
|
||
(
|
||
inspect.getsource(windows_features_runtime.ControlPageWindowsFeatureMixin._on_defender_toggled),
|
||
inspect.getsource(windows_features_runtime.ControlPageWindowsFeatureMixin._continue_defender_toggle),
|
||
)
|
||
)
|
||
mixin_source = inspect.getsource(windows_features_runtime.ControlPageWindowsFeatureMixin)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.program_settings", fromlist=["ProgramSettingsFeature"]).ProgramSettingsFeature)
|
||
|
||
self.assertTrue(hasattr(program_settings_workers, "ProgramSettingsAdminCheckWorker"))
|
||
worker_source = inspect.getsource(program_settings_workers.ProgramSettingsAdminCheckWorker.run)
|
||
|
||
self.assertIn("_request_defender_admin_check", defender_source)
|
||
self.assertNotIn("self._program_settings.is_user_admin", defender_source)
|
||
self.assertIn("create_program_settings_admin_check_worker", mixin_source)
|
||
self.assertIn("create_program_settings_admin_check_worker", feature_source)
|
||
self.assertIn("is_user_admin", worker_source)
|
||
|
||
def test_control_program_settings_load_runs_through_worker(self) -> None:
|
||
zapret1_sync_source = inspect.getsource(Zapret1ModeControlPage._sync_program_settings)
|
||
zapret2_sync_source = inspect.getsource(Zapret2ModeControlPage._sync_program_settings)
|
||
shared_source = inspect.getsource(control_page_shared.ControlPageActionMixin)
|
||
runtime_source = inspect.getsource(control_additional_settings_runtime.ModeControlRefreshRuntime)
|
||
worker_source = inspect.getsource(program_settings_workers.ProgramSettingsLoadWorker.run)
|
||
attach_source = inspect.getsource(program_settings_runtime.attach_program_settings_runtime)
|
||
|
||
self.assertIn("_request_program_settings_load", zapret1_sync_source)
|
||
self.assertIn("_request_program_settings_load", zapret2_sync_source)
|
||
self.assertNotIn("_request_program_settings_system_status_load", zapret1_sync_source)
|
||
self.assertNotIn("_request_program_settings_system_status_load", zapret2_sync_source)
|
||
self.assertNotIn("refresh_program_settings_snapshot", zapret1_sync_source)
|
||
self.assertNotIn("refresh_program_settings_snapshot", zapret2_sync_source)
|
||
self.assertIn("create_program_settings_load_worker", shared_source)
|
||
self.assertNotIn("create_program_settings_system_status_load_worker", shared_source)
|
||
self.assertIn("program_settings_load_runtime", runtime_source)
|
||
self.assertNotIn("program_settings_system_status_load_runtime", runtime_source)
|
||
self.assertIn("start_qthread_worker", inspect.getsource(control_page_shared.ControlPageActionMixin._request_program_settings_load))
|
||
self.assertNotIn("worker.start()", inspect.getsource(control_page_shared.ControlPageActionMixin._request_program_settings_load))
|
||
self.assertIn("load_program_settings_snapshot", worker_source)
|
||
self.assertIn("publish_program_settings_snapshot", shared_source)
|
||
self.assertIn("emit_initial=True", attach_source)
|
||
|
||
def test_zapret2_control_initial_store_snapshot_is_applied_immediately(self) -> None:
|
||
helper_source = inspect.getsource(control_page_shared.bind_control_ui_state_store)
|
||
bind_source = inspect.getsource(Zapret2ModeControlPage.bind_ui_state_store)
|
||
|
||
self.assertIn("emit_initial: bool = True", helper_source)
|
||
self.assertIn("emit_initial=bool(emit_initial)", helper_source)
|
||
self.assertNotIn("defer_initial_state", bind_source)
|
||
self.assertNotIn("emit_initial=not defer_initial_state", bind_source)
|
||
self.assertNotIn("_wait_for_startup_interactive_before_initial_ui_state", bind_source)
|
||
|
||
def test_raw_preset_editor_loads_file_through_worker(self) -> None:
|
||
source = inspect.getsource(PresetRawEditorPage._load_file)
|
||
set_source = inspect.getsource(PresetRawEditorPage.set_preset_file_name)
|
||
header_source = inspect.getsource(PresetRawEditorPage._refresh_header)
|
||
worker_init_source = inspect.getsource(RawPresetLoadWorker.__init__)
|
||
worker_source = inspect.getsource(RawPresetLoadWorker.run)
|
||
|
||
self.assertNotIn("self._controller.load_text(", source)
|
||
self.assertIn("_request_raw_preset_text", source)
|
||
self.assertNotIn("self._controller.manifest", set_source)
|
||
self.assertNotIn("self._controller.source_path", set_source)
|
||
self.assertNotIn("self._controller.manifest", header_source)
|
||
self.assertIn("load_preset", worker_init_source)
|
||
self.assertIn("self._load_preset", worker_init_source)
|
||
self.assertNotIn("self._controller", worker_init_source)
|
||
self.assertIn("self._load_preset(self._file_name)", worker_source)
|
||
self.assertNotIn("self._controller.load_preset", worker_source)
|
||
|
||
def test_raw_preset_editor_saves_file_through_worker(self) -> None:
|
||
from app.feature_facades.presets import PresetsFeature
|
||
|
||
save_source = inspect.getsource(PresetRawEditorPage._save_file)
|
||
feature_source = inspect.getsource(PresetsFeature.create_raw_preset_save_worker)
|
||
worker_init_source = inspect.getsource(RawPresetSaveWorker.__init__)
|
||
worker_source = inspect.getsource(RawPresetSaveWorker.run)
|
||
|
||
self.assertNotIn("self._controller.save_text(", save_source)
|
||
self.assertIn("_request_raw_preset_save", save_source)
|
||
self.assertIn("RawPresetSaveWorker", feature_source)
|
||
self.assertIn("save_text", worker_init_source)
|
||
self.assertIn("self._save_text", worker_init_source)
|
||
self.assertNotIn("controller", worker_init_source)
|
||
self.assertIn("self._save_text(", worker_source)
|
||
self.assertNotIn("controller.save_text", worker_source)
|
||
|
||
def test_raw_preset_editor_waits_for_pending_save_before_file_actions(self) -> None:
|
||
set_file_source = inspect.getsource(PresetRawEditorPage.set_preset_file_name)
|
||
activate_source = inspect.getsource(PresetRawEditorPage._activate_preset)
|
||
open_source = inspect.getsource(PresetRawEditorPage._open_external)
|
||
rename_source = inspect.getsource(PresetRawEditorPage._rename_preset)
|
||
duplicate_source = inspect.getsource(PresetRawEditorPage._duplicate_preset)
|
||
export_source = inspect.getsource(PresetRawEditorPage._export_preset)
|
||
reset_source = inspect.getsource(PresetRawEditorPage._reset_preset)
|
||
delete_source = inspect.getsource(PresetRawEditorPage._delete_preset)
|
||
save_finished_source = inspect.getsource(PresetRawEditorPage._on_raw_preset_save_worker_finished)
|
||
|
||
for source in (
|
||
set_file_source,
|
||
activate_source,
|
||
open_source,
|
||
rename_source,
|
||
duplicate_source,
|
||
export_source,
|
||
reset_source,
|
||
delete_source,
|
||
):
|
||
self.assertIn("_run_after_raw_preset_save", source)
|
||
self.assertIn("_after_raw_preset_save", save_finished_source)
|
||
self.assertIn("_raw_save_succeeded", save_finished_source)
|
||
|
||
def test_raw_preset_write_finished_uses_queued_state_finish_guard(self) -> None:
|
||
save_finished_source = inspect.getsource(PresetRawEditorPage._on_raw_preset_save_worker_finished)
|
||
activate_finished_source = inspect.getsource(PresetRawEditorPage._on_preset_activation_worker_finished)
|
||
action_finished_source = inspect.getsource(PresetRawEditorPage._on_raw_preset_action_worker_finished)
|
||
helper_source = inspect.getsource(PresetRawEditorPage._schedule_next_raw_preset_write_operation_after_finish)
|
||
|
||
self.assertIn("schedule_next_after_finish", helper_source)
|
||
self.assertIn("_accept_current_raw_write_worker_finished", helper_source)
|
||
for source in (save_finished_source, activate_finished_source, action_finished_source):
|
||
self.assertIn("_schedule_next_raw_preset_write_operation_after_finish", source)
|
||
self.assertNotIn("_schedule_next_raw_preset_write_operation_start()", source)
|
||
|
||
def test_raw_preset_editor_activation_runs_through_worker(self) -> None:
|
||
source = inspect.getsource(PresetRawEditorPage._activate_preset)
|
||
request_source = inspect.getsource(PresetRawEditorPage._request_preset_activation)
|
||
start_source = inspect.getsource(PresetRawEditorPage._start_preset_activation_worker)
|
||
worker_init_source = inspect.getsource(RawPresetActivateWorker.__init__)
|
||
worker_source = inspect.getsource(RawPresetActivateWorker.run)
|
||
|
||
self.assertNotIn("_activate_selected_preset()", source)
|
||
self.assertNotIn("self._controller.activate(", source)
|
||
self.assertIn("_request_preset_activation", source)
|
||
self.assertIn("create_raw_preset_activate_worker", request_source + start_source)
|
||
self.assertIn("activate", worker_init_source)
|
||
self.assertIn("self._activate", worker_init_source)
|
||
self.assertNotIn("controller", worker_init_source)
|
||
self.assertIn("self._activate(file_name=self._file_name)", worker_source)
|
||
self.assertNotIn("controller.activate", worker_source)
|
||
|
||
def test_raw_preset_editor_file_actions_run_through_worker(self) -> None:
|
||
from app.feature_facades.presets import PresetsFeature
|
||
|
||
action_sources = "\n".join(
|
||
inspect.getsource(method)
|
||
for method in (
|
||
PresetRawEditorPage._open_external,
|
||
PresetRawEditorPage._rename_preset,
|
||
PresetRawEditorPage._duplicate_preset,
|
||
PresetRawEditorPage._export_preset,
|
||
PresetRawEditorPage._reset_preset,
|
||
PresetRawEditorPage._delete_preset,
|
||
)
|
||
)
|
||
feature_source = inspect.getsource(PresetsFeature.create_raw_preset_action_worker)
|
||
worker_init_source = inspect.getsource(RawPresetActionWorker.__init__)
|
||
worker_source = inspect.getsource(RawPresetActionWorker.run)
|
||
|
||
for call in (
|
||
"self._controller.open_source_file(",
|
||
"self._controller.rename(",
|
||
"self._controller.duplicate(",
|
||
"self._controller.export(",
|
||
"self._controller.reset_to_builtin(",
|
||
"self._controller.delete(",
|
||
):
|
||
self.assertNotIn(call, action_sources)
|
||
self.assertIn("_request_raw_preset_action", action_sources)
|
||
self.assertIn("RawPresetActionWorker", feature_source)
|
||
self.assertIn("open_source_file", worker_init_source)
|
||
self.assertIn("rename_preset", worker_init_source)
|
||
self.assertIn("duplicate_preset", worker_init_source)
|
||
self.assertIn("export_preset", worker_init_source)
|
||
self.assertIn("reset_to_builtin", worker_init_source)
|
||
self.assertIn("delete_preset", worker_init_source)
|
||
self.assertIn("source_path", worker_init_source)
|
||
self.assertNotIn("run_action", worker_init_source)
|
||
self.assertNotIn("controller", worker_init_source)
|
||
self.assertIn("self._rename_preset(", worker_source)
|
||
self.assertIn("self._duplicate_preset(", worker_source)
|
||
self.assertIn("self._export_preset(", worker_source)
|
||
self.assertIn("self._reset_to_builtin(", worker_source)
|
||
self.assertIn("self._delete_preset(", worker_source)
|
||
self.assertIn("self._source_path(", worker_source)
|
||
self.assertNotIn("self._run_action", worker_source)
|
||
self.assertNotIn("controller.rename", worker_source)
|
||
self.assertNotIn("controller.duplicate", worker_source)
|
||
self.assertNotIn("controller.export", worker_source)
|
||
self.assertNotIn("controller.reset_to_builtin", worker_source)
|
||
self.assertNotIn("controller.delete", worker_source)
|
||
|
||
def test_profile_setup_builds_hidden_tabs_lazily(self) -> None:
|
||
build_source = inspect.getsource(ProfileSetupPageBase._build_content)
|
||
switch_source = inspect.getsource(ProfileSetupPageBase._switch_strategy_tab)
|
||
apply_source = inspect.getsource(ProfileSetupPageBase._apply_payload)
|
||
|
||
self.assertNotIn("self._list_file_text = PlainTextEdit()", build_source)
|
||
self.assertNotIn("self._raw_profile_text = PlainTextEdit()", build_source)
|
||
self.assertIn("_ensure_editor_tab_built()", switch_source)
|
||
self.assertIn("_request_list_file_editor_state()", switch_source)
|
||
self.assertNotIn("_apply_list_file_editor_state", apply_source)
|
||
|
||
def test_orchestra_settings_does_not_build_first_tab_in_constructor(self) -> None:
|
||
init_source = inspect.getsource(OrchestraSettingsPage.__init__)
|
||
show_source = inspect.getsource(OrchestraSettingsPage.showEvent)
|
||
|
||
self.assertNotIn("self._switch_tab(0)", init_source)
|
||
self.assertIn("self._switch_tab(0)", show_source)
|
||
|
||
def test_orchestra_settings_language_uses_warmed_cache_not_settings_read(self) -> None:
|
||
source = inspect.getsource(OrchestraSettingsPage._resolve_ui_language)
|
||
|
||
self.assertIn("peek_warmed_ui_language", source)
|
||
self.assertNotIn("load_ui_language", source)
|
||
|
||
def test_orchestra_heavy_tabs_load_state_through_workers(self) -> None:
|
||
locked_reload_source = inspect.getsource(OrchestraLockedPage._reload_from_settings)
|
||
blocked_reload_source = inspect.getsource(OrchestraBlockedPage._reload_from_settings)
|
||
ratings_refresh_source = inspect.getsource(OrchestraRatingsPage._refresh_data)
|
||
orchestra_feature_source = inspect.getsource(orchestra_feature_facade.OrchestraFeature)
|
||
|
||
self.assertIn("_start_snapshot_worker", locked_reload_source)
|
||
self.assertIn("_start_snapshot_worker", blocked_reload_source)
|
||
self.assertIn("_start_ratings_state_worker", ratings_refresh_source)
|
||
self.assertNotIn(".reload_snapshot(", locked_reload_source)
|
||
self.assertNotIn(".reload_snapshot(", blocked_reload_source)
|
||
self.assertNotIn(".load_state(", ratings_refresh_source)
|
||
self.assertIn("create_locked_snapshot_load_worker", orchestra_feature_source)
|
||
self.assertIn("create_blocked_snapshot_load_worker", orchestra_feature_source)
|
||
self.assertIn("create_ratings_state_load_worker", orchestra_feature_source)
|
||
|
||
def test_control_top_summary_loads_preset_and_profile_count_through_worker(self) -> None:
|
||
winws1_refresh_source = inspect.getsource(Zapret1ModeControlPage._refresh_top_summary)
|
||
winws2_refresh_source = inspect.getsource(Zapret2ModeControlPage._refresh_top_summary)
|
||
winws1_page_source = inspect.getsource(Zapret1ModeControlPage)
|
||
winws2_page_source = inspect.getsource(Zapret2ModeControlPage)
|
||
runtime_source = inspect.getsource(control_additional_settings_runtime)
|
||
worker_init_source = inspect.getsource(control_additional_settings_runtime.ControlTopSummaryWorker.__init__)
|
||
worker_source = inspect.getsource(control_additional_settings_runtime.ControlTopSummaryWorker.run)
|
||
factory_source = inspect.getsource(control_additional_settings_runtime.create_top_summary_worker)
|
||
|
||
self.assertIn("_request_top_summary_worker", winws1_refresh_source)
|
||
self.assertIn("_request_top_summary_worker", winws2_refresh_source)
|
||
self.assertNotIn("_load_preset_name()", winws1_refresh_source)
|
||
self.assertNotIn("_load_selected_preset_name()", winws2_refresh_source)
|
||
self.assertNotIn("_load_enabled_profile_count()", winws1_refresh_source)
|
||
self.assertNotIn("_load_enabled_profile_count()", winws2_refresh_source)
|
||
self.assertNotIn("get_selected_source_preset_display(", winws1_page_source)
|
||
self.assertNotIn("get_selected_source_preset_display(", winws2_page_source)
|
||
self.assertNotIn("get_enabled_profile_count_snapshot(", winws1_page_source)
|
||
self.assertNotIn("get_enabled_profile_count_snapshot(", winws2_page_source)
|
||
self.assertIn("create_top_summary_worker", runtime_source)
|
||
self.assertIn("summary_loader", worker_init_source)
|
||
self.assertIn("self._summary_loader", worker_init_source)
|
||
self.assertNotIn("_presets_feature", worker_init_source)
|
||
self.assertNotIn("_profile_feature", worker_init_source)
|
||
self.assertNotIn("launch_method", worker_init_source)
|
||
self.assertIn("self._summary_loader()", worker_source)
|
||
self.assertNotIn("get_selected_source_preset_display", worker_source)
|
||
self.assertNotIn("get_enabled_profile_count_snapshot", worker_source)
|
||
self.assertIn("get_selected_source_preset_display", factory_source)
|
||
self.assertIn("get_enabled_profile_count_snapshot", factory_source)
|
||
self.assertNotIn("presets_feature", factory_source)
|
||
self.assertNotIn("profile_feature", factory_source)
|
||
|
||
def test_orchestra_clear_learned_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("orchestra.page_workers")
|
||
self.assertIsNotNone(spec)
|
||
orchestra_page_workers = importlib.import_module("orchestra.page_workers")
|
||
|
||
clear_source = inspect.getsource(OrchestraPage._clear_learned)
|
||
page_source = inspect.getsource(OrchestraPage)
|
||
feature_source = inspect.getsource(orchestra_feature_facade.OrchestraFeature)
|
||
worker_source = inspect.getsource(orchestra_page_workers.OrchestraClearLearnedWorker.run)
|
||
|
||
request_source = inspect.getsource(OrchestraPage._request_clear_learned_worker)
|
||
start_source = inspect.getsource(OrchestraPage._start_clear_learned_worker)
|
||
|
||
self.assertIn("_request_clear_learned_worker", clear_source)
|
||
self.assertIn("_start_clear_learned_worker", request_source)
|
||
self.assertIn("runtime.start_qthread_worker", start_source)
|
||
self.assertNotIn("self._controller.clear_learned_data()", clear_source)
|
||
self.assertNotIn("self._controller.clear_learned_data()", request_source)
|
||
self.assertNotIn("self._controller.clear_learned_data()", start_source)
|
||
self.assertIn("OneShotWorkerRuntime", page_source)
|
||
self.assertIn("create_clear_learned_worker", page_source)
|
||
self.assertIn("create_clear_learned_worker", feature_source)
|
||
self.assertIn("clear_learned_data", worker_source)
|
||
|
||
def test_orchestra_log_history_loads_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("orchestra.page_workers")
|
||
self.assertIsNotNone(spec)
|
||
orchestra_page_workers = importlib.import_module("orchestra.page_workers")
|
||
orchestra_page_runtime_helpers = importlib.import_module("orchestra.ui.page_runtime_helpers")
|
||
|
||
update_source = inspect.getsource(OrchestraPage._update_log_history)
|
||
helper_source = inspect.getsource(orchestra_page_runtime_helpers.update_log_history_view)
|
||
page_source = inspect.getsource(OrchestraPage)
|
||
feature_source = inspect.getsource(orchestra_feature_facade.OrchestraFeature)
|
||
|
||
self.assertTrue(hasattr(orchestra_page_workers, "OrchestraLogHistoryLoadWorker"))
|
||
worker_source = inspect.getsource(orchestra_page_workers.OrchestraLogHistoryLoadWorker.run)
|
||
|
||
self.assertIn("_request_log_history_load", update_source)
|
||
self.assertNotIn("runner.get_log_history", update_source)
|
||
self.assertNotIn("runner.get_log_history", helper_source)
|
||
self.assertIn("logs=", helper_source)
|
||
self.assertIn("_log_history_runtime", page_source)
|
||
self.assertIn("create_log_history_load_worker", page_source)
|
||
self.assertIn("create_log_history_load_worker", feature_source)
|
||
self.assertIn("load_log_history", worker_source)
|
||
|
||
def test_orchestra_log_actions_use_shared_worker_queue_state(self) -> None:
|
||
page_source = inspect.getsource(OrchestraPage)
|
||
request_history_source = inspect.getsource(OrchestraPage._request_log_history_action)
|
||
finished_history_source = inspect.getsource(OrchestraPage._on_log_history_action_worker_finished)
|
||
request_context_source = inspect.getsource(OrchestraPage._request_log_context_action)
|
||
finished_context_source = inspect.getsource(OrchestraPage._on_log_context_action_worker_finished)
|
||
|
||
self.assertIn("QueuedWorkerState", page_source)
|
||
self.assertIn("_log_history_action_state_obj", page_source)
|
||
self.assertIn("_log_context_action_state_obj", page_source)
|
||
self.assertIn("_log_history_action_state_obj().is_busy()", request_history_source)
|
||
self.assertIn("_log_context_action_state_obj().is_busy()", request_context_source)
|
||
self.assertIn("pop_next_after_finish", finished_history_source)
|
||
self.assertIn("pop_next_after_finish", finished_context_source)
|
||
self.assertNotIn("_log_history_action_pending.pop(0)", finished_history_source)
|
||
self.assertNotIn("_log_context_action_pending.pop(0)", finished_context_source)
|
||
|
||
def test_orchestra_whitelist_actions_run_through_worker(self) -> None:
|
||
whitelist_workers = importlib.import_module("orchestra.managed_lists_workers")
|
||
|
||
add_source = inspect.getsource(OrchestraWhitelistPage._add_domain)
|
||
remove_source = inspect.getsource(OrchestraWhitelistPage._on_row_delete_requested)
|
||
clear_source = inspect.getsource(OrchestraWhitelistPage._clear_user_domains)
|
||
page_source = inspect.getsource(OrchestraWhitelistPage)
|
||
feature_source = inspect.getsource(orchestra_feature_facade.OrchestraFeature)
|
||
worker_source = inspect.getsource(whitelist_workers.OrchestraWhitelistActionWorker.run)
|
||
|
||
for source in (add_source, remove_source, clear_source):
|
||
self.assertIn("_request_whitelist_action", source)
|
||
self.assertNotIn("self._controller.add_domain", source)
|
||
self.assertNotIn("self._controller.remove_domain", source)
|
||
self.assertNotIn("self._controller.clear_user_domains", source)
|
||
|
||
self.assertIn("OneShotWorkerRuntime", page_source)
|
||
self.assertIn("create_action_worker", page_source)
|
||
self.assertIn("create_whitelist_action_worker", feature_source)
|
||
self.assertIn("add_domain", worker_source)
|
||
self.assertIn("remove_domain", worker_source)
|
||
self.assertIn("clear_user_domains", worker_source)
|
||
|
||
def test_orchestra_whitelist_snapshot_loads_through_worker(self) -> None:
|
||
whitelist_workers = importlib.import_module("orchestra.managed_lists_workers")
|
||
sync_source = inspect.getsource(OrchestraWhitelistPage._sync_whitelist_view)
|
||
feature_source = inspect.getsource(orchestra_feature_facade.OrchestraFeature)
|
||
|
||
self.assertTrue(hasattr(whitelist_workers, "OrchestraWhitelistSnapshotLoadWorker"))
|
||
worker_source = inspect.getsource(whitelist_workers.OrchestraWhitelistSnapshotLoadWorker.run)
|
||
self.assertIn("_start_snapshot_worker", sync_source)
|
||
self.assertNotIn(".snapshot(", sync_source)
|
||
self.assertIn("create_whitelist_snapshot_load_worker", feature_source)
|
||
self.assertIn("snapshot(refresh=self._refresh)", worker_source)
|
||
|
||
def test_orchestra_managed_list_actions_run_through_worker(self) -> None:
|
||
managed_workers = importlib.import_module("orchestra.managed_lists_workers")
|
||
|
||
blocked_sources = (
|
||
inspect.getsource(OrchestraBlockedPage._on_row_strategy_changed),
|
||
inspect.getsource(OrchestraBlockedPage._on_row_delete_requested),
|
||
inspect.getsource(OrchestraBlockedPage._block_strategy),
|
||
inspect.getsource(OrchestraBlockedPage._unblock_all),
|
||
)
|
||
locked_sources = (
|
||
inspect.getsource(OrchestraLockedPage._on_row_strategy_changed),
|
||
inspect.getsource(OrchestraLockedPage._on_row_delete_requested),
|
||
inspect.getsource(OrchestraLockedPage._lock_strategy),
|
||
inspect.getsource(OrchestraLockedPage._unlock_all),
|
||
)
|
||
page_sources = "\n".join(
|
||
(
|
||
inspect.getsource(OrchestraBlockedPage),
|
||
inspect.getsource(OrchestraLockedPage),
|
||
)
|
||
)
|
||
feature_source = inspect.getsource(orchestra_feature_facade.OrchestraFeature)
|
||
|
||
for source in blocked_sources + locked_sources:
|
||
self.assertIn("_request_managed_action", source)
|
||
self.assertNotIn("self._managed.change_strategy", source)
|
||
self.assertNotIn("self._managed.remove_strategy", source)
|
||
self.assertNotIn("self._managed.add_strategy", source)
|
||
self.assertNotIn("self._managed.clear_user_strategies", source)
|
||
self.assertNotIn("self._managed.clear_strategies", source)
|
||
|
||
self.assertTrue(hasattr(managed_workers, "OrchestraManagedActionWorker"))
|
||
worker_source = inspect.getsource(managed_workers.OrchestraManagedActionWorker.run)
|
||
self.assertIn("OneShotWorkerRuntime", page_sources)
|
||
self.assertIn("create_action_worker", page_sources)
|
||
self.assertIn("create_locked_action_worker", feature_source)
|
||
self.assertIn("create_blocked_action_worker", feature_source)
|
||
self.assertIn("change_strategy", worker_source)
|
||
self.assertIn("remove_strategy", worker_source)
|
||
self.assertIn("add_strategy", worker_source)
|
||
self.assertIn("clear_user_strategies", worker_source)
|
||
self.assertIn("clear_strategies", worker_source)
|
||
|
||
def test_orchestra_locked_blocked_strategy_validation_runs_through_worker(self) -> None:
|
||
managed_workers = importlib.import_module("orchestra.managed_lists_workers")
|
||
|
||
change_source = inspect.getsource(OrchestraLockedPage._on_row_strategy_changed)
|
||
add_source = inspect.getsource(OrchestraLockedPage._lock_strategy)
|
||
loaded_source = inspect.getsource(OrchestraLockedPage._on_managed_action_loaded)
|
||
worker_source = inspect.getsource(managed_workers.OrchestraManagedActionWorker.run)
|
||
|
||
for source in (change_source, add_source):
|
||
self.assertIn("_request_managed_action", source)
|
||
self.assertNotIn("self._managed.is_blocked_strategy", source)
|
||
self.assertNotIn("self._managed.current_strategy", source)
|
||
|
||
self.assertIn("blocked_by_policy", loaded_source)
|
||
self.assertIn("current_strategy", loaded_source)
|
||
self.assertIn("is_blocked_strategy", worker_source)
|
||
self.assertIn("current_strategy", worker_source)
|
||
|
||
def test_page_host_logs_first_and_repeat_show_for_all_pages(self) -> None:
|
||
init_source = inspect.getsource(WindowPageHost.__init__)
|
||
show_source = inspect.getsource(WindowPageHost.show_page)
|
||
ensure_source = inspect.getsource(WindowPageHost.ensure_page)
|
||
|
||
self.assertIn("_shown_pages", init_source)
|
||
self.assertIn("open.navigation.first", show_source)
|
||
self.assertIn("open.navigation.repeat", show_source)
|
||
self.assertIn("log_page_timing", show_source)
|
||
self.assertIn("open.ensure_page", show_source)
|
||
self.assertIn("open.stack", show_source)
|
||
self.assertIn("open.switch", show_source)
|
||
self.assertIn("open.navigation_sync", show_source)
|
||
self.assertNotIn("animate=", show_source)
|
||
self.assertIn("ensure.cached.language", ensure_source)
|
||
self.assertIn("ensure.created.language", ensure_source)
|
||
|
||
def test_page_host_repeat_show_budget_allows_animated_navigation(self) -> None:
|
||
self.assertEqual(
|
||
WindowPageHost._show_budget_ms(
|
||
PageName.ZAPRET2_USER_PRESETS,
|
||
first_show=False,
|
||
use_nav_route=True,
|
||
),
|
||
120,
|
||
)
|
||
self.assertEqual(
|
||
WindowPageHost._show_budget_ms(
|
||
PageName.ZAPRET2_USER_PRESETS,
|
||
first_show=False,
|
||
use_nav_route=False,
|
||
),
|
||
40,
|
||
)
|
||
|
||
def test_page_host_never_disables_qfluent_animation(self) -> None:
|
||
page = object()
|
||
|
||
class _FakeStack:
|
||
def __init__(self) -> None:
|
||
self.isAnimationEnabled = True
|
||
self.animation_seen_during_switch = None
|
||
|
||
class _FakeWindow:
|
||
def __init__(self) -> None:
|
||
self.stackedWidget = _FakeStack()
|
||
|
||
def switchTo(self, target): # noqa: N802
|
||
self.target = target
|
||
self.stackedWidget.animation_seen_during_switch = self.stackedWidget.isAnimationEnabled
|
||
|
||
window = _FakeWindow()
|
||
host = WindowPageHost(window=window, page_factory=None)
|
||
|
||
self.assertTrue(host.set_stacked_widget_current_page(page))
|
||
self.assertIs(window.target, page)
|
||
self.assertTrue(window.stackedWidget.animation_seen_during_switch)
|
||
self.assertTrue(window.stackedWidget.isAnimationEnabled)
|
||
switch_source = inspect.getsource(WindowPageHost.set_stacked_widget_current_page)
|
||
self.assertNotIn("isAnimationEnabled", switch_source)
|
||
self.assertNotIn("setCurrentWidget", switch_source)
|
||
|
||
def test_page_host_uses_qfluent_animation_for_navigation_switch(self) -> None:
|
||
page = object()
|
||
|
||
class _FakeStack:
|
||
def setCurrentWidget(self, _page, _need_pop_out=False): # noqa: N802
|
||
raise AssertionError("animated navigation must use window.switchTo")
|
||
|
||
class _FakeWindow:
|
||
def __init__(self) -> None:
|
||
self.stackedWidget = _FakeStack()
|
||
self.switched_to = None
|
||
|
||
def switchTo(self, target): # noqa: N802
|
||
self.switched_to = target
|
||
|
||
window = _FakeWindow()
|
||
host = WindowPageHost(window=window, page_factory=None)
|
||
|
||
self.assertTrue(host.set_stacked_widget_current_page(page))
|
||
self.assertIs(window.switched_to, page)
|
||
|
||
def test_page_host_does_not_switch_stack_when_page_is_already_current(self) -> None:
|
||
page = object()
|
||
|
||
class _FakeStack:
|
||
def __init__(self) -> None:
|
||
self.switch_count = 0
|
||
|
||
def currentWidget(self): # noqa: N802
|
||
return page
|
||
|
||
def setCurrentWidget(self, _page, _need_pop_out=False): # noqa: N802
|
||
self.switch_count += 1
|
||
|
||
class _FakeWindow:
|
||
def __init__(self) -> None:
|
||
self.stackedWidget = _FakeStack()
|
||
|
||
def get_launch_method(self) -> str:
|
||
return "zapret2_mode"
|
||
|
||
window = _FakeWindow()
|
||
host = WindowPageHost(window=window, page_factory=None)
|
||
host.pages[PageName.ZAPRET2_USER_PRESETS] = page
|
||
host._shown_pages.add(PageName.ZAPRET2_USER_PRESETS)
|
||
|
||
with patch("ui.page_host.apply_ui_language_to_page"):
|
||
self.assertTrue(host.show_page(PageName.ZAPRET2_USER_PRESETS, allow_internal=True))
|
||
|
||
self.assertEqual(window.stackedWidget.switch_count, 0)
|
||
|
||
def test_page_host_keeps_stack_repaint_enabled_during_direct_switch(self) -> None:
|
||
class _FakeStack:
|
||
def __init__(self) -> None:
|
||
self.updates_enabled = True
|
||
self.updates_seen_during_switch = None
|
||
self.calls: list[tuple[str, bool | None]] = []
|
||
|
||
def setUpdatesEnabled(self, enabled): # noqa: N802
|
||
self.updates_enabled = bool(enabled)
|
||
self.calls.append(("updates", self.updates_enabled))
|
||
|
||
def setCurrentWidget(self, page, need_pop_out=False):
|
||
_ = page
|
||
_ = need_pop_out
|
||
self.updates_seen_during_switch = self.updates_enabled
|
||
self.calls.append(("switch", self.updates_seen_during_switch))
|
||
|
||
def update(self):
|
||
self.calls.append(("update", None))
|
||
|
||
class _FakeWindow:
|
||
def __init__(self) -> None:
|
||
self.stackedWidget = _FakeStack()
|
||
|
||
def switchTo(self, page): # noqa: N802
|
||
self.stackedWidget.setCurrentWidget(page, False)
|
||
|
||
window = _FakeWindow()
|
||
host = WindowPageHost(window=window, page_factory=None)
|
||
self.assertTrue(host.set_stacked_widget_current_page(object()))
|
||
|
||
self.assertTrue(window.stackedWidget.updates_seen_during_switch)
|
||
self.assertTrue(window.stackedWidget.updates_enabled)
|
||
self.assertEqual(
|
||
window.stackedWidget.calls,
|
||
[
|
||
("switch", True),
|
||
],
|
||
)
|
||
|
||
def test_page_host_logs_slow_direct_switch_substeps(self) -> None:
|
||
class _FakeStack:
|
||
def __init__(self) -> None:
|
||
self.updates_enabled = True
|
||
|
||
def setUpdatesEnabled(self, enabled): # noqa: N802
|
||
self.updates_enabled = bool(enabled)
|
||
|
||
def updatesEnabled(self): # noqa: N802
|
||
return self.updates_enabled
|
||
|
||
def setCurrentWidget(self, page, need_pop_out=False):
|
||
_ = page
|
||
_ = need_pop_out
|
||
time.sleep(0.02)
|
||
|
||
def update(self):
|
||
pass
|
||
|
||
class _FakeWindow:
|
||
def __init__(self) -> None:
|
||
self.stackedWidget = _FakeStack()
|
||
|
||
def switchTo(self, page): # noqa: N802
|
||
self.stackedWidget.setCurrentWidget(page, False)
|
||
|
||
host = WindowPageHost(window=_FakeWindow(), page_factory=None)
|
||
events: list[str] = []
|
||
|
||
with patch("ui.page_host.log_page_timing", side_effect=lambda _page, stage, *_args, **_kwargs: events.append(stage)):
|
||
self.assertTrue(
|
||
host.set_stacked_widget_current_page(
|
||
object(),
|
||
page_name=PageName.ZAPRET2_USER_PRESETS,
|
||
)
|
||
)
|
||
|
||
self.assertIn("open.switch.qfluent", events)
|
||
|
||
def test_telegram_proxy_builds_secondary_tabs_lazily(self) -> None:
|
||
setup_source = inspect.getsource(TelegramProxyPage._setup_ui)
|
||
after_source = inspect.getsource(TelegramProxyPage._after_ui_built)
|
||
switch_source = inspect.getsource(TelegramProxyPage._switch_tab)
|
||
timer_source = inspect.getsource(TelegramProxyPage._sync_log_timer)
|
||
|
||
self.assertIn("_built_panel_indexes", setup_source)
|
||
self.assertNotIn("build_telegram_proxy_logs_panel(", setup_source)
|
||
self.assertNotIn("build_telegram_proxy_diag_panel(", setup_source)
|
||
self.assertNotIn("_log_timer.start", after_source)
|
||
self.assertIn("_ensure_panel_built(index)", switch_source)
|
||
self.assertIn("self._stacked.currentIndex() == 1", timer_source)
|
||
|
||
def test_telegram_proxy_builds_advanced_settings_lazily(self) -> None:
|
||
setup_source = inspect.getsource(TelegramProxyPage._setup_ui)
|
||
settings_build_source = inspect.getsource(telegram_proxy_settings_build.build_telegram_proxy_settings_panel)
|
||
initial_apply_source = inspect.getsource(TelegramProxyPage._apply_initial_settings_state)
|
||
advanced_toggle_source = inspect.getsource(TelegramProxyPage._on_advanced_toggled)
|
||
ensure_method = getattr(TelegramProxyPage, "_ensure_advanced_settings_built", None)
|
||
advanced_builder = getattr(
|
||
telegram_proxy_settings_build,
|
||
"build_telegram_proxy_advanced_settings_panel",
|
||
None,
|
||
)
|
||
|
||
self.assertIsNotNone(ensure_method)
|
||
self.assertIsNotNone(advanced_builder)
|
||
if ensure_method is None or advanced_builder is None:
|
||
return
|
||
ensure_source = inspect.getsource(ensure_method)
|
||
advanced_build_source = inspect.getsource(advanced_builder)
|
||
|
||
self.assertNotIn("build_telegram_proxy_advanced_settings_panel(", setup_source)
|
||
self.assertNotIn("advanced_card = setting_card_group_cls", settings_build_source)
|
||
self.assertIn("build_telegram_proxy_advanced_settings_panel", ensure_source)
|
||
self.assertIn("_schedule_initial_advanced_settings_build", initial_apply_source)
|
||
self.assertNotIn("_ensure_advanced_settings_built()", initial_apply_source)
|
||
self.assertIn("_ensure_advanced_settings_built", advanced_toggle_source)
|
||
self.assertIn("advanced_card = setting_card_group_cls", advanced_build_source)
|
||
|
||
def test_telegram_proxy_defers_initial_advanced_settings_build(self) -> None:
|
||
apply_source = inspect.getsource(TelegramProxyPage._apply_initial_settings_state)
|
||
schedule_source = inspect.getsource(TelegramProxyPage._schedule_initial_advanced_settings_build)
|
||
run_source = inspect.getsource(TelegramProxyPage._run_initial_advanced_settings_build)
|
||
|
||
self.assertIn("_schedule_initial_advanced_settings_build", apply_source)
|
||
self.assertNotIn("_ensure_advanced_settings_built()", apply_source)
|
||
self.assertIn("QTimer.singleShot", schedule_source)
|
||
self.assertIn("_run_initial_advanced_settings_build", schedule_source)
|
||
self.assertIn("_ensure_advanced_settings_built()", run_source)
|
||
self.assertIn("_apply_advanced_settings_state", run_source)
|
||
|
||
def test_user_presets_hide_keeps_clean_cache_clean(self) -> None:
|
||
source = inspect.getsource(UserPresetsPageBase.on_page_hidden)
|
||
|
||
self.assertNotIn("stop_watching_presets", source)
|
||
|
||
def test_hosts_page_first_render_does_not_read_hosts_state_in_constructor(self) -> None:
|
||
init_source = inspect.getsource(HostsPage.__init__)
|
||
build_status_source = inspect.getsource(HostsPage._build_status_section)
|
||
activated_source = inspect.getsource(HostsPage.on_page_activated)
|
||
|
||
self.assertNotIn("_run_runtime_init_once()", init_source)
|
||
self.assertNotIn("_get_active_domains()", build_status_source)
|
||
self.assertIn("_run_runtime_init_once(show_access_errors=True)", activated_source)
|
||
|
||
def test_hosts_warmup_defers_access_error_until_real_activation(self) -> None:
|
||
page = HostsPage.__new__(HostsPage)
|
||
page._runtime_initialized = True
|
||
page._runtime_access_checked = False
|
||
page._check_hosts_access = Mock()
|
||
|
||
HostsPage._run_runtime_init_once(page, show_access_errors=False)
|
||
|
||
page._check_hosts_access.assert_not_called()
|
||
self.assertFalse(page._runtime_access_checked)
|
||
|
||
HostsPage._run_runtime_init_once(page, show_access_errors=True)
|
||
|
||
page._check_hosts_access.assert_called_once_with()
|
||
self.assertTrue(page._runtime_access_checked)
|
||
|
||
def test_hosts_services_catalog_is_prepared_through_worker_not_page_reading(self) -> None:
|
||
rebuild_source = inspect.getsource(HostsPage._rebuild_services_selectors)
|
||
build_source = inspect.getsource(HostsPage._build_services_selectors)
|
||
|
||
self.assertIn("_start_services_catalog_worker", rebuild_source)
|
||
self.assertNotIn("read_active_domains_map", build_source)
|
||
self.assertNotIn("build_services_catalog_plan", build_source)
|
||
|
||
def test_hosts_user_selection_loads_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("hosts.selection_load_worker")
|
||
self.assertIsNotNone(spec)
|
||
selection_load_worker = importlib.import_module("hosts.selection_load_worker")
|
||
|
||
init_source = inspect.getsource(HostsPage.__init__)
|
||
runtime_source = inspect.getsource(HostsPage._run_runtime_init_once)
|
||
worker_source = inspect.getsource(selection_load_worker.HostsSelectionLoadWorker.run)
|
||
|
||
self.assertIn("self._hosts = deps.hosts_feature", init_source)
|
||
self.assertIn("_selection_load_runtime", init_source)
|
||
self.assertIn("_start_user_selection_load_worker", runtime_source)
|
||
self.assertNotIn("self._controller.load_user_selection()", runtime_source)
|
||
self.assertIn("self._hosts.create_selection_load_worker", inspect.getsource(HostsPage._start_user_selection_load_worker))
|
||
self.assertIn("_load_user_selection", worker_source)
|
||
self.assertNotIn("hosts.commands", worker_source)
|
||
self.assertNotIn("self._controller", worker_source)
|
||
self.assertIn("load_user_selection", inspect.getsource(hosts_commands.load_user_selection))
|
||
|
||
def test_hosts_runtime_state_loads_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("hosts.state_load_worker")
|
||
self.assertIsNotNone(spec)
|
||
state_load_worker = importlib.import_module("hosts.state_load_worker")
|
||
|
||
init_source = inspect.getsource(HostsPage.__init__)
|
||
update_source = inspect.getsource(HostsPage._update_ui)
|
||
access_source = inspect.getsource(HostsPage._check_hosts_access)
|
||
worker_source = inspect.getsource(state_load_worker.HostsStateLoadWorker.run)
|
||
|
||
self.assertIn("self._hosts = deps.hosts_feature", init_source)
|
||
self.assertIn("_state_load_runtime", init_source)
|
||
self.assertIn("_request_hosts_state_load", update_source)
|
||
self.assertIn("_request_hosts_state_load", access_source)
|
||
self.assertNotIn("_get_hosts_runtime_state()", update_source)
|
||
self.assertNotIn("_get_hosts_runtime_state()", access_source)
|
||
self.assertIn("self._hosts.create_state_load_worker", inspect.getsource(HostsPage._request_hosts_state_load))
|
||
self.assertIn("_get_hosts_state", worker_source)
|
||
self.assertNotIn("hosts.commands", worker_source)
|
||
self.assertNotIn("self._controller", worker_source)
|
||
self.assertIn("get_hosts_state", inspect.getsource(hosts_commands.get_hosts_state))
|
||
|
||
def test_hosts_open_file_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("hosts.open_file_worker")
|
||
self.assertIsNotNone(spec)
|
||
open_file_worker = importlib.import_module("hosts.open_file_worker")
|
||
|
||
init_source = inspect.getsource(HostsPage.__init__)
|
||
open_source = inspect.getsource(HostsPage._open_hosts_file)
|
||
page_source = inspect.getsource(HostsPage)
|
||
worker_source = inspect.getsource(open_file_worker.HostsOpenFileWorker.run)
|
||
|
||
self.assertIn("self._hosts = deps.hosts_feature", init_source)
|
||
self.assertIn("_open_file_runtime", init_source)
|
||
self.assertIn("_request_open_hosts_file", open_source)
|
||
self.assertNotIn(".open_hosts_file(", open_source)
|
||
self.assertIn("create_open_hosts_file_worker", page_source)
|
||
self.assertIn("self._hosts.create_open_hosts_file_worker", page_source)
|
||
self.assertIn("_open_hosts_file", worker_source)
|
||
self.assertNotIn("hosts.commands", worker_source)
|
||
self.assertNotIn("self._controller", worker_source)
|
||
self.assertIn("open_hosts_file", inspect.getsource(hosts_commands.open_hosts_file))
|
||
|
||
def test_hosts_restore_permissions_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("hosts.permission_restore_worker")
|
||
self.assertIsNotNone(spec)
|
||
permission_restore_worker = importlib.import_module("hosts.permission_restore_worker")
|
||
|
||
init_source = inspect.getsource(HostsPage.__init__)
|
||
restore_source = inspect.getsource(HostsPage._restore_hosts_permissions)
|
||
request_source = inspect.getsource(HostsPage._request_restore_hosts_permissions)
|
||
create_source = inspect.getsource(HostsPage.create_permission_restore_worker)
|
||
worker_source = inspect.getsource(permission_restore_worker.HostsPermissionRestoreWorker.run)
|
||
|
||
self.assertIn("self._hosts = deps.hosts_feature", init_source)
|
||
self.assertIn("_permission_restore_runtime", init_source)
|
||
self.assertIn("_request_restore_hosts_permissions", restore_source)
|
||
self.assertNotIn("restore_hosts_permissions_flow(", restore_source)
|
||
self.assertIn("create_permission_restore_worker", request_source)
|
||
self.assertIn("self._hosts.create_permission_restore_worker", create_source)
|
||
self.assertIn("_restore_hosts_permissions", worker_source)
|
||
self.assertNotIn("hosts.commands", worker_source)
|
||
self.assertNotIn("self._controller", worker_source)
|
||
self.assertIn("restore_hosts_permissions", inspect.getsource(hosts_commands.restore_hosts_permissions))
|
||
|
||
def test_hosts_page_cache_cannot_sync_read_runtime_state(self) -> None:
|
||
page_source = inspect.getsource(HostsPage)
|
||
cache_source = inspect.getsource(hosts_page_runtime.HostsPageRuntimeCache)
|
||
|
||
self.assertFalse(hasattr(HostsPage, "_get_hosts_runtime_state"))
|
||
self.assertFalse(hasattr(HostsPage, "_get_active_domains"))
|
||
self.assertNotIn("self._controller.get_hosts_state", page_source)
|
||
self.assertNotIn("get_runtime_state(", cache_source)
|
||
self.assertNotIn("get_active_domains(", cache_source)
|
||
|
||
def test_dns_isp_warning_settings_access_runs_through_worker(self) -> None:
|
||
dns_workers = importlib.import_module("dns.page_workers")
|
||
dns_feature_module = importlib.import_module("app.feature_facades.dns")
|
||
|
||
page_source = inspect.getsource(dns_page.NetworkPage)
|
||
check_source = inspect.getsource(dns_page.NetworkPage._check_and_show_isp_dns_warning)
|
||
feature_source = inspect.getsource(dns_feature_module.DnsFeature)
|
||
|
||
self.assertTrue(hasattr(dns_workers, "DnsIspWarningWorker"))
|
||
worker_source = inspect.getsource(dns_workers.DnsIspWarningWorker.run)
|
||
self.assertIn("_request_isp_dns_warning_plan", check_source)
|
||
self.assertNotIn("self._dns.is_isp_dns_warning_shown", check_source)
|
||
self.assertNotIn("self._dns.mark_isp_dns_warning_shown", check_source)
|
||
self.assertIn("_isp_warning_runtime", page_source)
|
||
self.assertIn("OneShotWorkerRuntime", page_source)
|
||
self.assertIn("create_isp_dns_warning_worker", feature_source)
|
||
self.assertIn("is_isp_dns_warning_shown", worker_source)
|
||
self.assertIn("mark_isp_dns_warning_shown", worker_source)
|
||
|
||
def test_lazy_pages_start_runtime_after_activation_not_constructor(self) -> None:
|
||
page_classes = (
|
||
dns_page.NetworkPage,
|
||
ServersPage,
|
||
TelegramProxyPage,
|
||
PremiumPage,
|
||
DpiSettingsPage,
|
||
OrchestraWhitelistPage,
|
||
OrchestraLockedPage,
|
||
OrchestraBlockedPage,
|
||
OrchestraRatingsPage,
|
||
)
|
||
|
||
for page_cls in page_classes:
|
||
with self.subTest(page=page_cls.__name__):
|
||
init_source = inspect.getsource(page_cls.__init__)
|
||
activated_source = inspect.getsource(page_cls.on_page_activated)
|
||
|
||
self.assertNotIn("self._run_runtime_init_once()", init_source)
|
||
self.assertIn("self._run_runtime_init_once()", activated_source)
|
||
|
||
def test_dpi_settings_page_keeps_radio_icons_visible_immediately(self) -> None:
|
||
page_build_source = inspect.getsource(DpiSettingsPage._build_ui)
|
||
activation_source = inspect.getsource(DpiSettingsPage.on_page_activated)
|
||
radio_source = inspect.getsource(Win11RadioOption.setSelected)
|
||
radio_init_source = inspect.getsource(Win11RadioOption.__init__)
|
||
|
||
self.assertNotIn("defer_icon=True", page_build_source)
|
||
self.assertNotIn("_schedule_deferred_icons()", activation_source)
|
||
self.assertNotIn("defer_icon", radio_init_source)
|
||
self.assertIn("if self._selected == selected", radio_source)
|
||
|
||
def test_dpi_settings_initial_load_reuses_initial_visibility(self) -> None:
|
||
apply_source = inspect.getsource(DpiSettingsPage._apply_dpi_initial_state)
|
||
sync_source = inspect.getsource(DpiSettingsPage._sync_visible_settings)
|
||
|
||
self.assertIn("initial.visibility", apply_source)
|
||
self.assertNotIn("describe_visibility(initial.launch_method)", apply_source)
|
||
self.assertIn("visibility=None", sync_source)
|
||
self.assertNotIn("load_orchestra_settings", sync_source)
|
||
|
||
def test_dpi_settings_load_and_method_apply_run_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("settings.dpi.workers")
|
||
self.assertIsNotNone(spec)
|
||
dpi_workers = importlib.import_module("settings.dpi.workers")
|
||
|
||
load_source = inspect.getsource(DpiSettingsPage._load_settings)
|
||
select_source = inspect.getsource(DpiSettingsPage._select_method)
|
||
page_source = inspect.getsource(DpiSettingsPage)
|
||
dpi_feature_module = __import__("app.feature_facades.dpi_settings", fromlist=["DpiSettingsFeature"])
|
||
feature_source = inspect.getsource(dpi_feature_module.DpiSettingsFeature)
|
||
feature_build_source = inspect.getsource(dpi_feature_module.build_dpi_settings_feature)
|
||
feature_factory_source = inspect.getsource(dpi_feature_module._create_dpi_settings_worker)
|
||
worker_init_source = inspect.getsource(dpi_workers.DpiSettingsWorker.__init__)
|
||
worker_source = inspect.getsource(dpi_workers.DpiSettingsWorker.run)
|
||
start_source = inspect.getsource(DpiSettingsPage._start_dpi_settings_worker)
|
||
|
||
self.assertIn("_request_dpi_initial_state_load", load_source)
|
||
self.assertNotIn("self._dpi_settings.load_initial_state", load_source)
|
||
self.assertIn("_request_launch_method_apply", select_source)
|
||
self.assertNotIn("self._dpi_settings.apply_launch_method", select_source)
|
||
self.assertIn("create_dpi_settings_worker", page_source)
|
||
self.assertIn("load_initial_state", worker_init_source)
|
||
self.assertIn("apply_launch_method", worker_init_source)
|
||
self.assertIn("load_orchestra_settings", worker_init_source)
|
||
self.assertIn("create_dpi_settings_worker", feature_source)
|
||
self.assertIn("load_initial_state=", feature_build_source)
|
||
self.assertIn("apply_launch_method=", feature_build_source)
|
||
self.assertIn("describe_visibility=", feature_build_source)
|
||
self.assertIn("load_orchestra_settings=", feature_build_source)
|
||
self.assertIn("load_initial_state=load_initial_state", feature_factory_source)
|
||
self.assertIn("load_initial_state", worker_source)
|
||
self.assertIn("apply_launch_method", worker_source)
|
||
self.assertIn("load_orchestra_settings", worker_source)
|
||
self.assertNotIn("settings.dpi.commands", worker_source)
|
||
self.assertIn("OneShotWorkerRuntime", page_source)
|
||
self.assertIn("_dpi_settings_runtime", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
|
||
def test_dpi_settings_page_receives_runtime_actions_not_full_runtime_feature(self) -> None:
|
||
from app.page_names import PageName
|
||
from ui.page_deps.system import build_dpi_settings_page_kwargs
|
||
|
||
init_source = inspect.getsource(DpiSettingsPage.__init__)
|
||
page_source = inspect.getsource(DpiSettingsPage)
|
||
runtime = Mock()
|
||
|
||
kwargs = build_dpi_settings_page_kwargs(
|
||
page_name=PageName.DPI_SETTINGS,
|
||
dpi_settings_feature=Mock(),
|
||
orchestra_feature=Mock(),
|
||
runtime_feature=runtime,
|
||
set_status=Mock(),
|
||
after_launch_method_changed=Mock(),
|
||
)
|
||
|
||
self.assertNotIn("runtime_feature", init_source)
|
||
self.assertNotIn("self._runtime =", page_source)
|
||
self.assertIn("runtime_actions", init_source)
|
||
self.assertIn("self._runtime_actions", page_source)
|
||
self.assertNotIn("runtime_feature", kwargs)
|
||
self.assertIs(
|
||
kwargs["runtime_actions"].handle_launch_method_changed,
|
||
runtime.handle_launch_method_changed,
|
||
)
|
||
|
||
def test_dpi_orchestra_settings_save_runs_through_worker(self) -> None:
|
||
spec = importlib.util.find_spec("orchestra.settings_worker")
|
||
self.assertIsNotNone(spec)
|
||
orchestra_settings_worker = importlib.import_module("orchestra.settings_worker")
|
||
|
||
page_source = inspect.getsource(DpiSettingsPage)
|
||
request_source = inspect.getsource(DpiSettingsPage._request_orchestra_setting_save)
|
||
start_source = inspect.getsource(DpiSettingsPage._start_orchestra_setting_save_worker)
|
||
finished_source = inspect.getsource(DpiSettingsPage._on_orchestra_setting_save_worker_finished)
|
||
worker_source = inspect.getsource(orchestra_settings_worker.OrchestraSettingSaveWorker.run)
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.orchestra", fromlist=["OrchestraFeature"]).OrchestraFeature)
|
||
|
||
for method_name in (
|
||
"_on_strict_detection_changed",
|
||
"_on_debug_file_changed",
|
||
"_on_auto_restart_discord_changed",
|
||
"_on_discord_fails_changed",
|
||
"_on_lock_successes_changed",
|
||
"_on_unlock_fails_changed",
|
||
):
|
||
source = inspect.getsource(getattr(DpiSettingsPage, method_name))
|
||
self.assertIn("_request_orchestra_setting_save", source)
|
||
self.assertNotIn("self._orchestra.set_setting", source)
|
||
|
||
self.assertIn("create_orchestra_setting_save_worker", page_source)
|
||
self.assertIn("_orchestra_settings_save_runtime", page_source)
|
||
self.assertIn("_orchestra_settings_save_state_obj()", request_source)
|
||
self.assertIn("_orchestra_settings_save_state_obj().pop_next()", finished_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
self.assertIn("set_setting=self.set_setting", feature_source)
|
||
self.assertIn("_set_setting", worker_source)
|
||
self.assertNotIn("orchestra_commands", worker_source)
|
||
self.assertIn("set_setting", worker_source)
|
||
|
||
def test_network_and_telegram_ui_do_not_create_python_threads(self) -> None:
|
||
modules = (
|
||
dns_diag_workflow,
|
||
dns_load_workflow,
|
||
telegram_diag_workflow,
|
||
telegram_runtime_workflow,
|
||
telegram_page,
|
||
)
|
||
|
||
for module in modules:
|
||
source = inspect.getsource(module)
|
||
self.assertNotIn("threading.Thread", source)
|
||
|
||
def test_dns_page_worker_returns_state_instead_of_calling_page_method(self) -> None:
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["build_dns_feature"]).build_dns_feature)
|
||
loading_source = inspect.getsource(dns_page.NetworkPage._start_loading)
|
||
page_source = inspect.getsource(dns_page.NetworkPage)
|
||
worker_source = inspect.getsource(DnsPageLoadWorker)
|
||
|
||
self.assertIn("create_page_load_worker", feature_source)
|
||
self.assertIn("create_page_load_worker", loading_source)
|
||
self.assertIn("start_qthread_worker", loading_source)
|
||
self.assertIn("loaded = pyqtSignal", worker_source)
|
||
self.assertIn("self.loaded.emit", worker_source)
|
||
self.assertNotIn("self._load_data_fn()", worker_source)
|
||
self.assertNotIn("load_data_fn=self._load_data", page_source)
|
||
|
||
def test_network_force_dns_status_is_applied_from_page_load_worker(self) -> None:
|
||
build_force_source = inspect.getsource(dns_page.NetworkPage._build_force_dns_card)
|
||
loaded_source = inspect.getsource(dns_page.NetworkPage._on_page_state_loaded)
|
||
apply_source = inspect.getsource(dns_page.NetworkPage._apply_loaded_force_dns_state)
|
||
|
||
self.assertNotIn("get_force_dns_status_fn=dns_feature.get_force_dns_status", build_force_source)
|
||
self.assertIn("get_force_dns_status_fn=lambda: self._force_dns_active", build_force_source)
|
||
self.assertIn("_apply_loaded_force_dns_state()", loaded_source)
|
||
self.assertIn("_set_force_dns_toggle", apply_source)
|
||
self.assertIn("_update_force_dns_status", apply_source)
|
||
self.assertIn("_update_dns_selection_state", apply_source)
|
||
|
||
def test_dns_force_dns_actions_run_through_worker(self) -> None:
|
||
page_workers = importlib.import_module("dns.page_workers")
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["DnsFeature"]).DnsFeature)
|
||
page_source = inspect.getsource(dns_page.NetworkPage)
|
||
toggle_source = inspect.getsource(dns_page.NetworkPage._on_force_dns_toggled)
|
||
reset_source = inspect.getsource(dns_page.NetworkPage._reset_dns_to_dhcp)
|
||
toggle_result_source = inspect.getsource(dns_page.NetworkPage._apply_force_dns_toggle_worker_result)
|
||
reset_result_source = inspect.getsource(dns_page.NetworkPage._apply_force_dns_reset_worker_result)
|
||
|
||
self.assertTrue(hasattr(page_workers, "DnsForceDnsActionWorker"))
|
||
worker_source = inspect.getsource(page_workers.DnsForceDnsActionWorker)
|
||
|
||
for source in (toggle_source, reset_source):
|
||
self.assertIn("_request_force_dns_action", source)
|
||
self.assertNotIn("handle_force_dns_toggled_action", source)
|
||
self.assertNotIn("reset_dns_to_dhcp_action", source)
|
||
self.assertNotIn(".enable_force_dns(", source)
|
||
self.assertNotIn(".disable_force_dns(", source)
|
||
|
||
self.assertIn("create_force_dns_action_worker", feature_source)
|
||
self.assertIn("create_force_dns_action_worker", page_source)
|
||
self.assertIn("_force_dns_action_pending", page_source)
|
||
self.assertIn("get_force_dns_status", worker_source)
|
||
self.assertIn("enable_force_dns", worker_source)
|
||
self.assertIn("disable_force_dns", worker_source)
|
||
self.assertIn("refresh_dns_info", worker_source)
|
||
self.assertNotIn("_refresh_adapters_dns", toggle_result_source)
|
||
self.assertNotIn("_refresh_adapters_dns", reset_result_source)
|
||
|
||
def test_dns_flush_cache_runs_through_worker(self) -> None:
|
||
page_workers = importlib.import_module("dns.page_workers")
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["DnsFeature"]).DnsFeature)
|
||
page_source = inspect.getsource(dns_page.NetworkPage)
|
||
flush_source = inspect.getsource(dns_page.NetworkPage._flush_dns_cache)
|
||
|
||
self.assertTrue(hasattr(page_workers, "DnsFlushCacheWorker"))
|
||
worker_source = inspect.getsource(page_workers.DnsFlushCacheWorker)
|
||
|
||
self.assertIn("_request_dns_flush_cache", flush_source)
|
||
self.assertNotIn("flush_dns_cache_action", flush_source)
|
||
self.assertNotIn(".flush_dns_cache(", flush_source)
|
||
self.assertIn("create_dns_flush_cache_worker", feature_source)
|
||
self.assertIn("create_dns_flush_cache_worker", page_source)
|
||
self.assertIn("_dns_flush_cache_pending", page_source)
|
||
self.assertIn("_flush_dns_cache", worker_source)
|
||
self.assertNotIn("dns_public.flush_dns_cache", worker_source)
|
||
self.assertIn("build_flush_dns_cache_result_plan", worker_source)
|
||
|
||
def test_dns_apply_actions_run_through_worker(self) -> None:
|
||
page_workers = importlib.import_module("dns.page_workers")
|
||
feature_source = inspect.getsource(__import__("app.feature_facades.dns", fromlist=["DnsFeature"]).DnsFeature)
|
||
page_source = inspect.getsource(dns_page.NetworkPage)
|
||
force_workflow = importlib.import_module("dns.page_force_dns_workflow")
|
||
|
||
self.assertTrue(hasattr(page_workers, "DnsApplyWorker"))
|
||
worker_source = inspect.getsource(page_workers.DnsApplyWorker)
|
||
|
||
for method_name, old_action in (
|
||
("_apply_auto_dns_quick", "apply_auto_dns_quick"),
|
||
("_apply_provider_dns_quick", "apply_provider_dns_quick"),
|
||
("_apply_custom_dns_quick", "apply_custom_dns_quick"),
|
||
):
|
||
source = inspect.getsource(getattr(dns_page.NetworkPage, method_name))
|
||
body_source = source.split("\n", 1)[1]
|
||
self.assertIn("_request_dns_apply", source)
|
||
self.assertNotIn(f"{old_action}(", body_source)
|
||
self.assertNotIn(".apply_auto_dns(", body_source)
|
||
self.assertNotIn(".apply_provider_dns(", body_source)
|
||
self.assertNotIn(".apply_custom_dns(", body_source)
|
||
|
||
self.assertIn("create_dns_apply_worker", feature_source)
|
||
self.assertIn("create_dns_apply_worker", page_source)
|
||
self.assertIn("_dns_apply_pending", page_source)
|
||
self.assertIn("apply_auto_dns", worker_source)
|
||
self.assertIn("apply_provider_dns", worker_source)
|
||
self.assertIn("apply_custom_dns", worker_source)
|
||
self.assertIn("refresh_dns_info", worker_source)
|
||
self.assertFalse(hasattr(dns_page.NetworkPage, "_refresh_adapters_dns"))
|
||
self.assertIsNone(importlib.util.find_spec("dns.page_apply_workflow"))
|
||
self.assertFalse(hasattr(force_workflow, "handle_force_dns_toggled_action"))
|
||
self.assertFalse(hasattr(force_workflow, "flush_dns_cache_action"))
|
||
self.assertFalse(hasattr(force_workflow, "reset_dns_to_dhcp_action"))
|
||
|
||
def test_network_loaded_adapters_do_not_wait_for_current_dns(self) -> None:
|
||
stored = {}
|
||
build_calls = []
|
||
|
||
dns_load_workflow.handle_loaded_adapters(
|
||
adapters=[("Ethernet", "Intel")],
|
||
current_dns_info={},
|
||
ui_built=False,
|
||
set_adapters_fn=lambda adapters: stored.setdefault("adapters", adapters),
|
||
build_dynamic_ui_fn=lambda: build_calls.append("build"),
|
||
)
|
||
|
||
self.assertEqual(stored["adapters"], [("Ethernet", "Intel")])
|
||
self.assertEqual(build_calls, ["build"])
|
||
|
||
def test_network_page_builds_dns_choices_before_runtime_load(self) -> None:
|
||
build_source = inspect.getsource(dns_page.NetworkPage._build_ui)
|
||
choices_source = inspect.getsource(dns_page.NetworkPage._build_dns_choices_ui)
|
||
|
||
self.assertIn("self._build_dns_choices_ui()", build_source)
|
||
self.assertNotIn("load_page_data", choices_source)
|
||
self.assertNotIn("refresh_dns_info", choices_source)
|
||
|
||
def test_network_page_adds_dns_containers_before_showing_dns_choices(self) -> None:
|
||
build_source = inspect.getsource(dns_page.NetworkPage._build_ui)
|
||
|
||
choices_pos = build_source.index("self._build_dns_choices_ui()")
|
||
dns_container_pos = build_source.index("self.add_widget(self.dns_cards_container)")
|
||
custom_card_pos = build_source.index("self.custom_card = shell.custom_card")
|
||
|
||
self.assertLess(dns_container_pos, choices_pos)
|
||
self.assertLess(custom_card_pos, choices_pos)
|
||
|
||
def test_telegram_diagnostics_worker_uses_progress_signal(self) -> None:
|
||
workflow_source = inspect.getsource(telegram_diag_workflow.start_diagnostics)
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
worker_source = inspect.getsource(TelegramProxyDiagnosticsWorker)
|
||
|
||
self.assertNotIn("progress_callback=publish_diag_result", workflow_source)
|
||
self.assertIn("worker.progress.connect(publish_diag_result)", workflow_source)
|
||
self.assertIn("progress = pyqtSignal", worker_source)
|
||
self.assertIn("progress_callback=self.progress.emit", worker_source)
|
||
self.assertIn("_diag_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", workflow_source)
|
||
self.assertNotIn("_diag_worker", page_source)
|
||
self.assertNotIn("worker.start()", workflow_source)
|
||
|
||
def test_telegram_ensure_hosts_runs_through_worker_runtime(self) -> None:
|
||
page_source = inspect.getsource(TelegramProxyPage)
|
||
ensure_source = inspect.getsource(TelegramProxyPage._ensure_telegram_hosts)
|
||
start_source = inspect.getsource(TelegramProxyPage._start_ensure_hosts_worker)
|
||
cleanup_source = inspect.getsource(TelegramProxyPage.cleanup)
|
||
feature_source = inspect.getsource(TelegramProxyFeature)
|
||
worker_source = inspect.getsource(telegram_proxy_workers.TelegramHostsEnsureWorker)
|
||
|
||
self.assertIn("_ensure_hosts_runtime", page_source)
|
||
self.assertIn("_start_ensure_hosts_worker", ensure_source)
|
||
self.assertIn("start_qthread_worker", start_source)
|
||
self.assertIn("bind_worker", start_source)
|
||
self.assertIn("worker.completed.connect(self._on_telegram_hosts_ensured)", start_source)
|
||
self.assertIn("create_ensure_hosts_worker", feature_source)
|
||
self.assertIn("completed = pyqtSignal(int, object)", worker_source)
|
||
self.assertIn("_ensure_hosts_runtime.stop", cleanup_source)
|
||
self.assertNotIn("_ensure_hosts_worker =", page_source)
|
||
self.assertNotIn("worker.start()", start_source)
|
||
|
||
def test_connection_support_bundle_prepares_through_worker(self) -> None:
|
||
support_worker = importlib.import_module("diagnostics.support_worker")
|
||
page_source = inspect.getsource(ConnectionTestPage)
|
||
handler_source = inspect.getsource(ConnectionTestPage.open_support_with_log)
|
||
request_source = "\n".join(
|
||
(
|
||
inspect.getsource(ConnectionTestPage._request_support_prepare),
|
||
inspect.getsource(ConnectionTestPage._start_support_prepare_worker),
|
||
)
|
||
)
|
||
handler_body = handler_source.split("\n", 1)[1]
|
||
worker_source = inspect.getsource(support_worker.ConnectionSupportPrepareWorker.run)
|
||
feature_source = inspect.getsource(DiagnosticsFeature)
|
||
feature_build_source = inspect.getsource(diagnostics_feature_facade.build_diagnostics_feature)
|
||
|
||
self.assertIn("_request_support_prepare", handler_source)
|
||
self.assertNotIn("open_support_with_log(", handler_body)
|
||
self.assertIn("create_support_prepare_worker", page_source)
|
||
self.assertIn("_support_prepare_runtime", page_source)
|
||
self.assertIn("start_qthread_worker", request_source)
|
||
self.assertNotIn("worker.start()", request_source)
|
||
self.assertIn("create_connection_support_prepare_worker", feature_source)
|
||
self.assertIn("prepare_connection_support=", feature_build_source)
|
||
self.assertIn("_prepare_connection_support", worker_source)
|
||
self.assertNotIn("diagnostics.commands", worker_source)
|
||
self.assertIn("prepare_connection_support", worker_source)
|
||
self.assertFalse(hasattr(diagnostics_runtime_helpers, "open_support_with_log"))
|
||
|
||
def test_connection_test_run_uses_page_worker_runtime(self) -> None:
|
||
page_source = inspect.getsource(ConnectionTestPage)
|
||
start_source = inspect.getsource(ConnectionTestPage.start_test)
|
||
stop_source = inspect.getsource(ConnectionTestPage.stop_test)
|
||
cleanup_source = inspect.getsource(ConnectionTestPage.cleanup)
|
||
runtime_source = inspect.getsource(diagnostics_runtime_helpers.start_connection_test)
|
||
cleanup_runtime_source = inspect.getsource(diagnostics_runtime_helpers.cleanup_connection_runtime)
|
||
|
||
self.assertIn("_connection_test_runtime", page_source)
|
||
self.assertIn("OneShotWorkerRuntime", page_source)
|
||
self.assertIn("start_qobject_worker", start_source)
|
||
self.assertIn("stop_connection_test", stop_source)
|
||
self.assertIn("runtime=self._connection_test_runtime", cleanup_source)
|
||
self.assertIn("runtime.stop", cleanup_runtime_source)
|
||
self.assertNotIn("self.worker_thread", page_source)
|
||
self.assertNotIn("QThread", runtime_source)
|
||
self.assertNotIn("worker_thread.start()", runtime_source)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|