fix(wizard): «Завершить подключение» не закрывало окно

_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 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-22 21:43:51 +07:00
parent c75fb35082
commit 7090c8ae6f
2 changed files with 86 additions and 14 deletions

View file

@ -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()

View file

@ -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