From d755a079034b079115942611a384eee2ab45c57d Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Mon, 24 Aug 2026 19:17:51 +0700 Subject: [PATCH] =?UTF-8?q?fix(accounts):=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=BA=D0=BA=D0=B0=D1=83=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=20=D1=80=D0=B0=D0=BF=D0=BE=D1=80=D1=82=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=20=D1=83=D1=81=D0=BF=D0=B5=D1=85,=20=D0=BD=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D0=B3=D0=BE=20=D0=BD=D0=B5=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D1=8F=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец подключил не тот аккаунт Grok и не смог его убрать: «кнопка не функционирует». Хуже: действие возвращало ok=True с сообщением об успехе, а профиль оставался авторизованным. Причина: сигнатура get_profile_dir — (profile_id, provider), а do_delete_credentials звала её наоборот. Внутри функции есть костыль, молча исправляющий перестановку, но только для трёх провайдеров: antigravity, openai-codex, opencode-go. Для grok, claude и local путь получался неверным (grok_profiles/grok вместо grok_profiles/grok-worker-1), файл «не находился», и срабатывала ветка «учетные данные отсутствовали» с ok=True. То есть удаление работало у трёх провайдеров из шести, а у остальных молча лгало. Теперь используется get_profile_auth_path, который берёт аргументы в правильном порядке. Отсутствие файла больше не считается успехом удаления: возвращается честный отказ «удалять нечего». Проверено на живом профиле: до — авторизован, после удаления — нет, повторная попытка сообщает, что удалять нечего. Тест покрывает все четыре провайдера, у которых костыль не срабатывал. Тесты: 419 passed, ruff чисто. Co-Authored-By: Claude Opus 5 --- .../router/action_handler.py | 14 ++++- tests/test_delete_credentials.py | 56 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 tests/test_delete_credentials.py diff --git a/src/antigravity_provider/router/action_handler.py b/src/antigravity_provider/router/action_handler.py index 5878af2..fea31e1 100644 --- a/src/antigravity_provider/router/action_handler.py +++ b/src/antigravity_provider/router/action_handler.py @@ -103,7 +103,15 @@ def do_test_profile(provider: str, profile_id: str) -> Dict[str, Any]: return {'success': False, 'model': model, 'duration_sec': round(time.time() - t0, 2), 'error': str(e)} def do_delete_credentials(provider: str, profile_id: str) -> Tuple[bool, str]: - auth_p = ProfileAuthManager.get_profile_dir(provider, profile_id) / 'auth.json' + # Сигнатура get_profile_dir — (profile_id, provider), а здесь её звали + # наоборот. Внутри есть костыль, молча исправляющий перестановку, но только + # для antigravity, openai-codex и opencode-go. Для grok, claude и local путь + # получался неверным, файл «не находился», и кнопка удаления РАПОРТОВАЛА + # УСПЕХ, ничего не удалив. Используем готовый помощник, который берёт + # аргументы в правильном порядке. + from antigravity_provider.router.profile_manager import get_profile_auth_path + + auth_p = get_profile_auth_path(provider, profile_id) if auth_p.is_file(): try: auth_p.unlink() @@ -111,7 +119,9 @@ def do_delete_credentials(provider: str, profile_id: str) -> Tuple[bool, str]: return True, f"Учетные данные для '{profile_id}' удалены" except Exception as e: return False, f'Ошибка удаления: {e}' - return True, 'Учетные данные отсутствовали' + # Отсутствие файла — это не успех удаления. Раньше такой ответ выглядел + # для пользователя как «сработало», хотя аккаунт оставался подключённым. + return False, f"Учетных данных для '{profile_id}' не найдено — удалять нечего" def do_save_settings(settings: Dict[str, Any]) -> Tuple[bool, str]: settings_file = paths.get_hermes_home() / "hub_settings.json" diff --git a/tests/test_delete_credentials.py b/tests/test_delete_credentials.py new file mode 100644 index 0000000..7f55a2a --- /dev/null +++ b/tests/test_delete_credentials.py @@ -0,0 +1,56 @@ +"""Кнопка удаления обязана удалять — и говорить правду, если удалять нечего. + +Владелец подключил не тот аккаунт Grok и не смог его убрать: «кнопка не +функционирует». Хуже — действие возвращало ok=True с сообщением об +успехе, а профиль оставался авторизованным. + +Причина: сигнатура get_profile_dir — (profile_id, provider), а +do_delete_credentials звала её наоборот. Внутри есть костыль, молча +исправляющий перестановку, но только для antigravity, openai-codex и +opencode-go. Для grok, claude и local путь получался неверным, файл «не +находился», и удаление рапортовало успех, ничего не сделав. +""" + +from __future__ import annotations + +import json + +import pytest + +from antigravity_provider.router.action_handler import do_delete_credentials +from antigravity_provider.router.profile_manager import get_profile_auth_path + + +@pytest.mark.parametrize("provider,profile_id", [ + ("grok", "grok-worker-2"), + ("claude", "claude-worker-1"), + ("local", "local-2"), + ("antigravity", "ag-spare-2"), +]) +def test_delete_removes_credentials_for_every_provider(provider, profile_id, monkeypatch, tmp_path): + """Удаление обязано работать у всех провайдеров, а не у трёх из шести.""" + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + + auth_path = get_profile_auth_path(provider, profile_id) + auth_path.parent.mkdir(parents=True, exist_ok=True) + auth_path.write_text(json.dumps({"api_key": "secret"}), encoding="utf-8") + assert auth_path.is_file() + + ok, msg = do_delete_credentials(provider, profile_id) + + assert ok, f"{provider}: удаление отчиталось об отказе — {msg}" + assert not auth_path.is_file(), f"{provider}: файл остался на диске, а действие сообщило успех" + + +def test_missing_credentials_reported_as_failure(monkeypatch, tmp_path): + """Отсутствие файла — не успех удаления. + + Раньше такой ответ выглядел для пользователя как «сработало», хотя + аккаунт оставался подключённым. + """ + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + + ok, msg = do_delete_credentials("grok", "grok-worker-2") + + assert ok is False + assert "нечего" in msg or "не найдено" in msg