fix(web): удаление аккаунта не работало из интерфейса
Владелец: «удалить так и не могу ненужный». Backend был починен в
d755a07, но кнопка по-прежнему не работала.
Причина в клиенте: он отправлял только profile_id, без provider. Сервер
не знал, в каком каталоге искать auth.json, строил неверный путь и снова
отвечал «удалять нечего».
Идентификатор профиля однозначен, поэтому провайдер теперь берётся из
конфигурации, когда его не передали. Действие работает независимо от
того, кто его вызвал.
Заодно в клиенте: подтверждение перед необратимым удалением и обновление
экрана после — раньше карточка оставалась в прежнем виде, и было
непонятно, сработало ли.
Проверено через веб-API без параметра provider: до — авторизован,
после — нет.
Тесты: 426 passed, ruff чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
98b6e68617
commit
a40bcac3c3
3 changed files with 77 additions and 1 deletions
|
|
@ -248,6 +248,17 @@ class ActionExecutor:
|
|||
"""
|
||||
pid = data.get('profile_id', '')
|
||||
prov = data.get('provider', '')
|
||||
# Провайдер часто не передают: веб-клиент шлёт только profile_id, и
|
||||
# раньше удаление из-за этого искало файл не в том каталоге и отвечало
|
||||
# «удалять нечего». Идентификатор профиля однозначен — берём провайдера
|
||||
# из конфигурации, чтобы работал любой вызывающий.
|
||||
if not prov and pid:
|
||||
try:
|
||||
_pcfg = load_router_config().get_profile(pid)
|
||||
if _pcfg:
|
||||
prov = _pcfg.provider
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Device-flow для Grok и Codex через веб. Backend был готов давно, но
|
||||
# наружу не выведен: веб-мастер показывал заглушку «не реализовано», и
|
||||
|
|
|
|||
|
|
@ -1371,7 +1371,7 @@ function openAccountDetailsModal(profileId) {
|
|||
elements.modalFooter.innerHTML = `
|
||||
<button class="btn btn-secondary" onclick="handleTestProfile('${escapeHtml(profileId)}')">⚡ Проверить подключение</button>
|
||||
<button class="btn btn-secondary" onclick="executeAction('set_main', { profile_id: '${escapeHtml(profileId)}' })">★ Сделать основным</button>
|
||||
<button class="btn btn-secondary" onclick="executeAction('delete_credentials', { profile_id: '${escapeHtml(profileId)}' })">Удалить ключ</button>
|
||||
<button class="btn btn-secondary" onclick="handleDeleteCredentials('${escapeHtml(profileId)}')">Удалить ключ</button>
|
||||
<button class="btn btn-primary" onclick="closeModal()">Закрыть</button>
|
||||
`;
|
||||
|
||||
|
|
@ -2060,3 +2060,16 @@ async function pollDeviceAuth(providerId) {
|
|||
status.innerHTML = `<span style="color:var(--status-error); font-weight:600;">${escapeHtml(res.message || 'Авторизация не завершена')}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteCredentials(profileId) {
|
||||
// Подтверждение обязательно: действие необратимо, аккаунт придётся
|
||||
// подключать заново.
|
||||
if (!confirm(`Удалить учётные данные профиля ${profileId}? Аккаунт придётся подключить заново.`)) {
|
||||
return;
|
||||
}
|
||||
const res = await executeAction('delete_credentials', { profile_id: profileId });
|
||||
if (res && res.ok) {
|
||||
closeModal();
|
||||
fetchSnapshot();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
tests/test_delete_without_provider.py
Normal file
52
tests/test_delete_without_provider.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Удаление должно работать, когда провайдер не передан.
|
||||
|
||||
Владелец: «удалить так и не могу ненужный». Backend был уже починен, но
|
||||
веб-клиент отправлял только profile_id, без provider. Сервер не знал, в
|
||||
каком каталоге искать файл, и снова отвечал «удалять нечего» — кнопка
|
||||
по-прежнему выглядела неработающей.
|
||||
|
||||
Идентификатор профиля однозначен, поэтому провайдер берётся из
|
||||
конфигурации, и действие работает независимо от того, кто его вызвал.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from antigravity_provider.router.action_handler import ActionExecutor
|
||||
from antigravity_provider.router.profile_manager import get_profile_auth_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize("profile_id,provider", [
|
||||
("grok-worker-2", "grok"),
|
||||
("ag-spare-2", "antigravity"),
|
||||
("opengo-2", "opencode-go"),
|
||||
])
|
||||
def test_delete_works_without_provider_argument(profile_id, provider, 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": "x"}), encoding="utf-8")
|
||||
|
||||
# Ровно то, что шлёт веб-клиент: без provider.
|
||||
result = ActionExecutor().execute("delete_credentials", {"profile_id": profile_id})
|
||||
|
||||
assert result.get("ok"), f"{profile_id}: {result.get('message')}"
|
||||
assert not auth_path.is_file(), f"{profile_id}: файл остался, а действие отчиталось об успехе"
|
||||
|
||||
|
||||
def test_client_confirms_before_deleting():
|
||||
"""Необратимое действие обязано спрашивать подтверждение."""
|
||||
import pathlib
|
||||
|
||||
app_js = pathlib.Path(
|
||||
"src/antigravity_provider/router/web/static/app.js"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "handleDeleteCredentials" in app_js
|
||||
block = app_js.split("function handleDeleteCredentials", 1)[1][:600]
|
||||
assert "confirm(" in block, "удаление выполняется без подтверждения"
|
||||
assert "fetchSnapshot" in block, "экран не обновляется после удаления"
|
||||
Loading…
Reference in a new issue