hermes-hub/tests/test_grok_quota.py
Hermes Team 6ebea01386 feat(grok): читать настоящую квоту из биллинга вместо «Н/Д»
Владелец спросил, где кончились лимиты, и показал экран: еженедельный
лимит SuperGrok израсходован на 14%, запас есть. А вызовы через Hub
падали с 402 «out of credits».

Разгадка — два разных кошелька. Подписка SuperGrok покрывает чат
grok.com, а программный доступ списывается с кредитов API. Проверено на
живом аккаунте: prepaidBalance 0, onDemandCap 0. Именно поэтому 402, и
подписка тут не помогает.

Адрес биллинга взят из плагина hermes-grok-usage, который владелец уже
использует: cli-chat-proxy.grok.com/v1/billing принимает ТОТ ЖЕ
OAuth-токен, что и авторизация. Проверено запросом — 200 и данные.

_collect_grok_quota раньше строила пустые корзины и никуда не ходила,
поэтому Hub показывал «Н/Д» и объяснить ничего не мог. Теперь читает
предоплаченный баланс и лимит по мере использования, а при нулевом
балансе пишет причину прямым текстом, включая различие кошельков.

Процент считается только когда есть от чего считать: при нулевом лимите
доля не определена, и подставлять ноль процентов нельзя — показываются
абсолютные значения.

Тесты: 422 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:56:13 +07:00

73 lines
3.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.

"""Квота Grok читается у провайдера, а не показывается как «Н/Д».
Владелец спросил: «где закончились лимиты?» — и показал экран, где
еженедельный лимит SuperGrok израсходован на 14%. Но вызовы через Hub
падали с 402.
Разгадка: это ДВА РАЗНЫХ кошелька. Подписка SuperGrok покрывает чат
grok.com, а программный доступ списывается с кредитов API. Их у аккаунта
ноль — подтверждено биллинговым эндпоинтом.
Раньше _collect_grok_quota строила пустые корзины и никуда не ходила,
поэтому Hub показывал «Н/Д» и объяснить ничего не мог.
"""
from __future__ import annotations
import json
from unittest.mock import MagicMock, patch
from antigravity_provider.router.quota_collector import AccountQuotaService
def _billing(prepaid: float, cap: float, used: float) -> MagicMock:
payload = {
"config": {
"currentPeriod": {"start": "2026-08-19T00:00:00+00:00", "end": "2026-08-26T00:00:00+00:00"},
"prepaidBalance": {"val": prepaid},
"onDemandCap": {"val": cap},
"onDemandUsed": {"val": used},
}
}
resp = MagicMock()
resp.__enter__.return_value.read.return_value = json.dumps(payload).encode("utf-8")
return resp
def _auth() -> dict:
return {"token": {"access_token": "tok"}}
def test_exhausted_account_explains_two_wallets():
"""Нулевой баланс объясняется, а не показывается прочерком."""
with patch("antigravity_provider.router.quota_collector.urllib.request.urlopen",
return_value=_billing(0, 0, 0)):
snap = AccountQuotaService()._collect_grok_quota("grok-orch", _auth())
assert snap.source == "provider_api"
assert "SuperGrok" in (snap.unavailable_reason or ""), "не объяснено, что это разные кошельки"
assert all(b.status == "exhausted" for b in snap.buckets)
def test_zero_cap_does_not_fabricate_percent():
"""При нулевом лимите доля не определена — процент выдумывать нельзя."""
with patch("antigravity_provider.router.quota_collector.urllib.request.urlopen",
return_value=_billing(0, 0, 0)):
snap = AccountQuotaService()._collect_grok_quota("grok-orch", _auth())
on_demand = next(b for b in snap.buckets if b.id == "grok.on_demand")
assert on_demand.remaining_percent is None
assert on_demand.used_percent is None
assert on_demand.limit_absolute == 0
def test_account_with_credits_reports_real_percent():
"""Когда лимит есть, доля считается по измеренным значениям."""
with patch("antigravity_provider.router.quota_collector.urllib.request.urlopen",
return_value=_billing(50, 200, 50)):
snap = AccountQuotaService()._collect_grok_quota("grok-orch", _auth())
on_demand = next(b for b in snap.buckets if b.id == "grok.on_demand")
assert on_demand.used_percent == 25.0
assert on_demand.remaining_percent == 75.0
assert snap.unavailable_reason is None