fix(accounts): удаление аккаунта рапортовало успех, ничего не удаляя

Владелец подключил не тот аккаунт 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 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-24 19:17:51 +07:00
parent 0be0a58a5d
commit d755a07903
2 changed files with 68 additions and 2 deletions

View file

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

View file

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