fix(antigravity): вход через терминал не регистрировал аккаунт
Владелец: «в программе нет аккаунтов. я добавил первый… аккаунты так и не появились». При этом вход проходил, и agy отдавал одиннадцать моделей. Список аккаунтов строится по конфигурации маршрутизатора. Вход через терминал завершается своим путём, мимо add_account, и записи в конфигурации не создаёт: учётные данные на диске, get_profile_status отвечает «подключён», а в интерфейсе пусто. Воспроизведено исполнением — профиль с ключами agy и нулём записей в конфигурации не виден нигде. Теперь при успешном завершении входа профиль регистрируется, и только потом идёт обновление состояния. Неудача регистрации возвращается отказом с причиной, а не молчаливым успехом: аккаунт, о котором сказано «подключён», но которого нигде нет, — та же слепота, что и зависший мастер. Проверено исполнением до и после правки: было «профилей в конфиге: 0, видно: ничего», стало «профилей: 1 [ag-5], видно: antigravity[ag-5]». 708 passed, 2 skipped; ruff чисто; релизный гейт пройден. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7b36538ded
commit
58cb88e529
5 changed files with 126 additions and 2 deletions
|
|
@ -18,7 +18,7 @@ namespace HermesHubSetup
|
||||||
// Подставляется сборщиком из фактического git-коммита. Раньше здесь
|
// Подставляется сборщиком из фактического git-коммита. Раньше здесь
|
||||||
// жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из
|
// жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из
|
||||||
// какого кода собран установщик.
|
// какого кода собран установщик.
|
||||||
public const string BuildCommit = "90aeceb";
|
public const string BuildCommit = "e431e39";
|
||||||
public const string MIN_HERMES_VERSION = "0.20.0";
|
public const string MIN_HERMES_VERSION = "0.20.0";
|
||||||
public const string MAX_TESTED_HERMES = "0.20.4";
|
public const string MAX_TESTED_HERMES = "0.20.4";
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -952,7 +952,21 @@ class ActionExecutor:
|
||||||
|
|
||||||
ok, msg, res_data = poll_native_agy_login(session_id)
|
ok, msg, res_data = poll_native_agy_login(session_id)
|
||||||
if ok and res_data.get('status') == 'completed':
|
if ok and res_data.get('status') == 'completed':
|
||||||
_rescan_after_auth('antigravity', res_data.get('profile_id'))
|
slot = res_data.get('profile_id')
|
||||||
|
# Вход через терминал завершается своим путём и мимо add_account.
|
||||||
|
# Учётные данные при этом на диске, профиль числится
|
||||||
|
# подключённым — но записи о нём в конфигурации маршрутизатора
|
||||||
|
# нет, а список аккаунтов строится по ней. Владелец входил
|
||||||
|
# успешно и видел пустой экран.
|
||||||
|
if slot:
|
||||||
|
def_ok, def_msg = AutoAssigner.ensure_profile_definition('antigravity', slot)
|
||||||
|
if not def_ok:
|
||||||
|
return {
|
||||||
|
'ok': False,
|
||||||
|
'message': f'Вход выполнен, но аккаунт не зарегистрирован: {def_msg}',
|
||||||
|
'data': res_data,
|
||||||
|
}
|
||||||
|
_rescan_after_auth('antigravity', slot)
|
||||||
return {'ok': ok, 'message': msg, 'data': res_data}
|
return {'ok': ok, 'message': msg, 'data': res_data}
|
||||||
|
|
||||||
if action in ('cancel_native_auth', 'cancel_native_agy_login', 'cancel_terminal_auth'):
|
if action in ('cancel_native_auth', 'cancel_native_agy_login', 'cancel_terminal_auth'):
|
||||||
|
|
|
||||||
110
tests/test_native_login_registers_account.py
Normal file
110
tests/test_native_login_registers_account.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
"""Вход через терминал должен регистрировать аккаунт, а не только записывать ключи.
|
||||||
|
|
||||||
|
Владелец: «в программе нет аккаунтов. я добавил первый… аккаунты так и не
|
||||||
|
появились». При этом вход проходил, agy отдавал одиннадцать моделей.
|
||||||
|
|
||||||
|
Причина: список аккаунтов строится по конфигурации маршрутизатора, а вход через
|
||||||
|
терминал завершается своим путём, мимо add_account, и записи в конфигурации не
|
||||||
|
создаёт. Учётные данные на диске, профиль числится подключённым — и его нигде
|
||||||
|
не видно.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from antigravity_provider import paths
|
||||||
|
from antigravity_provider.router.action_handler import ActionExecutor
|
||||||
|
from antigravity_provider.router.router_config import load_router_config
|
||||||
|
from antigravity_provider.router.unified_health import UnifiedHealthService
|
||||||
|
|
||||||
|
|
||||||
|
def _profile_with_agy_credentials(slot: str = "ag-5"):
|
||||||
|
pdir = paths.get_profile_dir(slot, "antigravity", create=True)
|
||||||
|
cli = pdir / ".gemini" / "antigravity-cli"
|
||||||
|
cli.mkdir(parents=True, exist_ok=True)
|
||||||
|
(cli / "antigravity-oauth-token").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"auth_method": "consumer",
|
||||||
|
"token": {"access_token": "ya29.TEST", "refresh_token": "1//TEST"},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return pdir
|
||||||
|
|
||||||
|
|
||||||
|
def _completed(slot: str = "ag-5"):
|
||||||
|
return (
|
||||||
|
True,
|
||||||
|
"Авторизация успешно завершена через agy CLI",
|
||||||
|
{"status": "completed", "profile_id": slot, "email": "owner@gmail.com"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_completed_login_registers_the_profile():
|
||||||
|
_profile_with_agy_credentials()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.agy_subprocess.poll_native_agy_login",
|
||||||
|
return_value=_completed(),
|
||||||
|
):
|
||||||
|
res = ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
|
||||||
|
|
||||||
|
assert res["ok"], res
|
||||||
|
assert "ag-5" in load_router_config().profiles, (
|
||||||
|
"без записи в конфигурации аккаунт нигде не появится"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_registered_profile_is_visible_in_the_interface():
|
||||||
|
_profile_with_agy_credentials()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.agy_subprocess.poll_native_agy_login",
|
||||||
|
return_value=_completed(),
|
||||||
|
):
|
||||||
|
ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
|
||||||
|
|
||||||
|
shown = [
|
||||||
|
view.profile_id
|
||||||
|
for group in UnifiedHealthService.get().scan_all(force=True).values()
|
||||||
|
for view in group
|
||||||
|
]
|
||||||
|
assert "ag-5" in shown
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_failed_registration_is_reported_not_swallowed():
|
||||||
|
"""Молчаливый успех при незарегистрированном аккаунте — то же зависание вслепую."""
|
||||||
|
_profile_with_agy_credentials()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.agy_subprocess.poll_native_agy_login",
|
||||||
|
return_value=_completed(),
|
||||||
|
), patch(
|
||||||
|
"antigravity_provider.router.auto_assigner.AutoAssigner.ensure_profile_definition",
|
||||||
|
return_value=(False, "слот занят другим провайдером"),
|
||||||
|
):
|
||||||
|
res = ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
|
||||||
|
|
||||||
|
assert not res["ok"]
|
||||||
|
assert "не зарегистрирован" in res["message"]
|
||||||
|
assert "слот занят другим провайдером" in res["message"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_login_still_in_progress_registers_nothing():
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.agy_subprocess.poll_native_agy_login",
|
||||||
|
return_value=(True, "Ожидание завершения авторизации в терминале...", {"status": "pending"}),
|
||||||
|
):
|
||||||
|
res = ActionExecutor.execute("poll_native_auth", {"session_id": "s1"})
|
||||||
|
|
||||||
|
assert res["ok"]
|
||||||
|
assert not load_router_config().profiles
|
||||||
Loading…
Reference in a new issue