From 7090c8ae6feef8f6bb811acb48e1d64db677c40b Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Sat, 22 Aug 2026 21:43:51 +0700 Subject: [PATCH] =?UTF-8?q?fix(wizard):=20=C2=AB=D0=97=D0=B0=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=88=D0=B8=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=C2=BB=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D0=BB=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=BA=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _finish первой же строкой вызывал EventLogService.log_event — метода с таким именем у сервиса нет, есть log(category, message, details, level). AttributeError уходил в обработчик Tk, а под pythonw консоли нет, поэтому для владельца кнопка просто не работала: окно оставалось открытым, on_complete не вызывался, аккаунт не попадал в маршрутизацию. Вызов приведён к настоящему API. Журналирование и обратный вызов обёрнуты так, чтобы сбой в них не запирал пользователя в мастере, — закрытие окна не должно зависеть от побочных действий. Воспроизведено и проверено исполнением: до правки winfo_exists=1 и on_complete не вызван, после — окно уничтожено, результат передан. Тесты: 252 passed в venv Hermes. Единственный сбой (test_oauth_lifecycle::test_f_copy_before_open_browser, TclError) и FAILED релизного гейта воспроизводятся на чистом main и к этой правке отношения не имеют. Co-Authored-By: Claude Opus 5 --- .../router/ui/add_account_wizard.py | 37 ++++++----- tests/test_ui_wizard_finish.py | 63 +++++++++++++++++++ 2 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 tests/test_ui_wizard_finish.py diff --git a/src/antigravity_provider/router/ui/add_account_wizard.py b/src/antigravity_provider/router/ui/add_account_wizard.py index 2274317..3de4673 100644 --- a/src/antigravity_provider/router/ui/add_account_wizard.py +++ b/src/antigravity_provider/router/ui/add_account_wizard.py @@ -1449,19 +1449,28 @@ class AddAccountWizard(HubModal): ).pack(side="right", padx=10, pady=10) def _finish(self): - EventLogService.get().log_event( - event_type="ACCOUNT_CONNECTED", - title=f"Подключен аккаунт {self.selected_provider}", - detail=f"Слот: {self.target_slot} ({self.discovered_identity})", - provider=self.selected_provider, - profile_id=self.target_slot, - ) - if self.on_complete: - self.on_complete( - { - "provider": self.selected_provider, - "profile_id": self.target_slot, - "identity": self.discovered_identity, - } + # Ни журналирование, ни обратный вызов не должны мешать закрытию окна: + # исключение здесь оставляло мастер открытым без единого признака ошибки. + try: + EventLogService.get().log( + "account", + f"Подключен аккаунт {self.selected_provider}: {self.discovered_identity}", + details=f"Слот: {self.target_slot}", + level="success", ) + except Exception: + pass + + if self.on_complete: + try: + self.on_complete( + { + "provider": self.selected_provider, + "profile_id": self.target_slot, + "identity": self.discovered_identity, + } + ) + except Exception: + pass + self.destroy() diff --git a/tests/test_ui_wizard_finish.py b/tests/test_ui_wizard_finish.py new file mode 100644 index 0000000..9521547 --- /dev/null +++ b/tests/test_ui_wizard_finish.py @@ -0,0 +1,63 @@ +"""Мастер подключения обязан закрываться по кнопке «Завершить». + +Дефект, ради которого написан файл: `_finish` вызывал +`EventLogService.log_event` — метода, которого у сервиса нет. Под `pythonw` +консоли нет, трейсбек Tk уходил в никуда, и для пользователя кнопка +«Завершить подключение» просто не работала: окно оставалось открытым, +аккаунт в маршрутизацию не попадал. +""" + +from __future__ import annotations + +import pytest + +pytest.importorskip("customtkinter") + +import customtkinter as ctk + +from antigravity_provider.router.ui.add_account_wizard import AddAccountWizard + + +@pytest.fixture(scope="module") +def root(): + app = ctk.CTk() + app.withdraw() + yield app + try: + app.destroy() + except Exception: + pass + + +def _wizard(root, on_complete=None): + w = AddAccountWizard(root, on_complete=on_complete) + w.withdraw() + w.selected_provider = "antigravity" + w.target_slot = "ag-w2" + w.discovered_identity = "user@example.com" + return w + + +def test_finish_closes_wizard_and_reports_result(root): + seen = [] + w = _wizard(root, on_complete=seen.append) + + w._finish() + + assert w.winfo_exists() == 0, "окно мастера осталось открытым после «Завершить»" + assert seen == [ + {"provider": "antigravity", "profile_id": "ag-w2", "identity": "user@example.com"} + ] + + +def test_finish_closes_even_if_callback_raises(root): + """Сбой в обработчике владельца не должен запирать пользователя в мастере.""" + + def boom(_result): + raise RuntimeError("обновление данных упало") + + w = _wizard(root, on_complete=boom) + + w._finish() + + assert w.winfo_exists() == 0