hermes-hub/tests/test_agy_cli_token_file.py
Hermes Team 9641957d5b fix(antigravity): вход завершался успешно, а agy требовал войти снова
agy 2.0 читает учётные данные не из .gemini/oauth_creds.json — это формат
Gemini CLI. Свой токен он берёт из .gemini/antigravity-cli/antigravity-oauth-token
в виде {"auth_method": ..., "token": {...}}. Хаб этот файл никогда не создавал,
поэтому «Авторизация успешно завершена» соседствовала с ответом
«Please sign in to view available models».

Установлено сравнением рабочего профиля владельца с неработающим: оба имели
oauth_creds.json одинакового размера, различие было только в этом файле.
Значение auth_method («consumer») взято из рабочего профиля, а не выведено
из общих соображений.

Срок годности пишется в обоих видах: expiry_date в миллисекундах для формата
Node и expiry строкой RFC3339 для Go-шного oauth2.Token.

Заодно устранён рост номеров профилей: слот выбирался до входа, когда почта
ещё неизвестна, и повторный вход тем же аккаунтом занимал очередной свободный
слот — один аккаунт владельца расползся на ag-2, ag-3, ag-4. Теперь после
опознания почты учётные данные возвращаются в слот, который этот аккаунт уже
занимает. Функция проверки двойников в проекте была, но её никто не вызывал.

Версия поднята до 0.1.3, чтобы отличать сборки на глаз. Проверка версии в
тестах сверяется с исходником, а не с записанным числом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 10:37:27 +07:00

96 lines
4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""agy 2.0 читает вход не из oauth_creds.json.
Сравнение рабочего профиля владельца с неработающим показало единственное
значимое различие: у рабочего есть `.gemini/antigravity-cli/antigravity-oauth-token`,
у неработающего — только `.gemini/oauth_creds.json`. Оба файла с токенами были
на месте и одинакового размера, но `agy models` отвечал «Please sign in to view
available models».
Формат подтверждён на машине владельца: верхний уровень — ключи `auth_method`
и `token`, значение `auth_method` — `consumer`.
"""
from __future__ import annotations
import json
import time
from antigravity_provider.router.profile_manager import ProfileAuthManager
def _write(tmp_path):
return ProfileAuthManager.write_agy_oauth_creds(
tmp_path,
{
"access_token": "ya29.TEST-ACCESS",
"refresh_token": "1//TEST-REFRESH",
"scope": "https://www.googleapis.com/auth/cloud-platform",
"token_type": "Bearer",
"id_token": "eyJTEST",
"expiry_date": int((time.time() + 3600) * 1000),
},
)
def test_antigravity_cli_token_written(tmp_path):
_write(tmp_path)
target = tmp_path / ".gemini" / "antigravity-cli" / "antigravity-oauth-token"
assert target.is_file(), "agy читает вход именно отсюда"
payload = json.loads(target.read_text(encoding="utf-8"))
assert sorted(payload) == ["auth_method", "token"]
assert payload["auth_method"] == "consumer"
assert payload["token"]["access_token"] == "ya29.TEST-ACCESS"
assert payload["token"]["refresh_token"] == "1//TEST-REFRESH"
def test_expiry_written_in_both_formats(tmp_path):
_write(tmp_path)
target = tmp_path / ".gemini" / "antigravity-cli" / "antigravity-oauth-token"
token = json.loads(target.read_text(encoding="utf-8"))["token"]
# Node-формат — миллисекунды, Go-шный oauth2.Token — строка RFC3339.
assert isinstance(token["expiry_date"], int)
assert token["expiry"].endswith("Z")
def test_gemini_creds_still_written(tmp_path):
"""Прежний файл остаётся: его читают другие части agy и сам хаб."""
target_file = _write(tmp_path)
assert target_file == tmp_path / ".gemini" / "oauth_creds.json"
creds = json.loads(target_file.read_text(encoding="utf-8"))
assert creds["access_token"] == "ya29.TEST-ACCESS"
assert "auth_method" not in creds
def test_auth_method_taken_from_working_neighbour(tmp_path):
"""Значение способа входа берётся у уже работающего профиля, если он есть."""
neighbour = tmp_path / "ag-working" / ".gemini" / "antigravity-cli"
neighbour.mkdir(parents=True)
(neighbour / "antigravity-oauth-token").write_text(
json.dumps({"auth_method": "workforce", "token": {}}), encoding="utf-8"
)
profile = tmp_path / "ag-new"
profile.mkdir()
_write(profile)
payload = json.loads(
(profile / ".gemini" / "antigravity-cli" / "antigravity-oauth-token").read_text(
encoding="utf-8"
)
)
assert payload["auth_method"] == "workforce"
def test_empty_login_writes_nothing(tmp_path):
"""Вход без токена не должен оставлять ни одного файла учётных данных."""
try:
ProfileAuthManager.write_agy_oauth_creds(tmp_path, {"email": "x@y.z"})
except ValueError:
pass
else: # pragma: no cover - защита от возврата прежнего поведения
raise AssertionError("пустой вход обязан отказывать")
assert not (tmp_path / ".gemini" / "oauth_creds.json").exists()
assert not (
tmp_path / ".gemini" / "antigravity-cli" / "antigravity-oauth-token"
).exists()