Владелец: «не даёт зайти в грок через аутх». Backend при этом исправен — проверено: провайдер возвращает настоящую сессию, адрес accounts.x.ai/oauth2/device и код. Дефект в цикле опроса: коды 400, 403 и 404 скопом считались «авторизация ещё не подтверждена» и опрос молча продолжался. Но в device-flow сервер сообщает РАЗНЫЕ вещи одним кодом 400, различая их полем error в теле: authorization_pending, slow_down, access_denied, expired_token. Следствие: отказ пользователя и просроченный код выглядели как ожидание. Мастер показывал «Ожидание подтверждения...» до самого таймаута и не говорил, что подтверждение уже отклонено или код давно истёк. Теперь каждый исход обрабатывается по существу: ожидание продолжает опрос, slow_down увеличивает интервал, отказ и просрочка прекращают его с внятным сообщением. Тесты: 414 passed, ruff чисто. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
52 lines
2.8 KiB
Python
52 lines
2.8 KiB
Python
"""Опрос device-flow обязан различать исходы.
|
||
|
||
Владелец: «не даёт зайти в грок через аутх». Backend при этом исправен —
|
||
провайдер возвращает настоящие адрес и код.
|
||
|
||
Дефект в цикле опроса: коды 400, 403 и 404 скопом считались «ещё не
|
||
подтверждено». Но в device-flow сервер сообщает РАЗНЫЕ вещи одним кодом
|
||
400, различая их полем error в теле: authorization_pending, slow_down,
|
||
access_denied, expired_token. Отказ пользователя и просроченный код
|
||
выглядели как ожидание — мастер крутил «Ожидание подтверждения...» до
|
||
таймаута и не говорил правду.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import io
|
||
import json
|
||
import pathlib
|
||
import urllib.error
|
||
|
||
|
||
def _error(reason: str, code: int = 400) -> urllib.error.HTTPError:
|
||
body = json.dumps({"error": reason}).encode("utf-8")
|
||
return urllib.error.HTTPError("https://x", code, "Bad Request", {}, io.BytesIO(body))
|
||
|
||
|
||
def test_poll_loop_handles_each_outcome_distinctly():
|
||
src = pathlib.Path("src/antigravity_provider/router/grok_oauth.py").read_text(encoding="utf-8")
|
||
|
||
assert "authorization_pending" in src, "ожидание больше не распознаётся явно"
|
||
assert "access_denied" in src, "отказ пользователя не отличается от ожидания"
|
||
assert "expired_token" in src, "просроченный код не отличается от ожидания"
|
||
assert "slow_down" in src, "требование сбавить темп игнорируется"
|
||
|
||
# Прежняя конструкция считала все три кода одним исходом.
|
||
assert "if http_err.code in (400, 403, 404):\n # Authorization pending" not in src
|
||
|
||
|
||
def test_error_body_is_readable():
|
||
"""Тело ответа должно разбираться — на нём и держится различение."""
|
||
err = _error("access_denied")
|
||
parsed = json.loads(err.read().decode("utf-8"))
|
||
assert parsed["error"] == "access_denied"
|
||
|
||
|
||
def test_denied_and_expired_are_terminal():
|
||
"""Оба исхода обязаны прекращать опрос, а не продолжать его."""
|
||
src = pathlib.Path("src/antigravity_provider/router/grok_oauth.py").read_text(encoding="utf-8")
|
||
tail = src.split("if reason == \"access_denied\":", 1)[1][:400]
|
||
assert "break" in tail, "отказ не прекращает опрос"
|
||
expired = src.split("if reason == \"expired_token\":", 1)[1][:400]
|
||
assert "break" in expired, "просрочка не прекращает опрос"
|