feat: Codex account_name 透传并优化池列表 OAuth 标识与额度重置倒计时展示

(cherry picked from commit 1b9176fc4a)
This commit is contained in:
kayphoon
2026-03-18 20:58:33 +08:00
committed by fawney19
parent 8d8cddcef6
commit 6e55968487
16 changed files with 888 additions and 251 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock
import jwt
import pytest
@@ -17,18 +18,28 @@ def test_codex_provider_behavior_has_no_runtime_envelope_or_variants() -> None:
assert behavior.cross_format_variant is None
def test_openai_cli_normalizer_request_from_internal_codex_variant_preserves_store() -> None:
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
def test_openai_cli_normalizer_request_from_internal_codex_variant_preserves_store() -> (
None
):
from src.core.api_format.conversion.normalizers.openai_cli import (
OpenAICliNormalizer,
)
normalizer = OpenAICliNormalizer()
internal = normalizer.request_to_internal({"model": "gpt-test", "input": [], "store": True})
internal = normalizer.request_to_internal(
{"model": "gpt-test", "input": [], "store": True}
)
out = normalizer.request_from_internal(internal, target_variant="codex")
assert out["store"] is True
def test_openai_cli_normalizer_request_from_internal_codex_variant_does_not_inject_store() -> None:
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
def test_openai_cli_normalizer_request_from_internal_codex_variant_does_not_inject_store() -> (
None
):
from src.core.api_format.conversion.normalizers.openai_cli import (
OpenAICliNormalizer,
)
normalizer = OpenAICliNormalizer()
internal = normalizer.request_to_internal({"model": "gpt-test", "input": []})
@@ -37,9 +48,13 @@ def test_openai_cli_normalizer_request_from_internal_codex_variant_does_not_inje
assert "store" not in out
def test_openai_cli_normalizer_codex_variant_keeps_instructions_missing_for_default_rule() -> None:
def test_openai_cli_normalizer_codex_variant_keeps_instructions_missing_for_default_rule() -> (
None
):
from src.api.handlers.base.request_builder import apply_body_rules
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
from src.core.api_format.conversion.normalizers.openai_cli import (
OpenAICliNormalizer,
)
from src.core.api_format.metadata import CODEX_DEFAULT_BODY_RULES
normalizer = OpenAICliNormalizer()
@@ -53,7 +68,9 @@ def test_openai_cli_normalizer_codex_variant_keeps_instructions_missing_for_defa
def test_openai_cli_normalizer_patch_for_codex_is_noop() -> None:
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
from src.core.api_format.conversion.normalizers.openai_cli import (
OpenAICliNormalizer,
)
normalizer = OpenAICliNormalizer()
out = normalizer.patch_for_variant(
@@ -73,7 +90,9 @@ def test_openai_cli_normalizer_patch_for_codex_is_noop() -> None:
def test_codex_passthrough_builder_preserves_real_codex_headers() -> None:
builder = PassthroughRequestBuilder()
endpoint = SimpleNamespace(api_family="openai", endpoint_kind="cli", header_rules=None)
endpoint = SimpleNamespace(
api_family="openai", endpoint_kind="cli", header_rules=None
)
key = SimpleNamespace(api_key="unused")
headers = builder.build_headers(
@@ -139,7 +158,9 @@ def _encode_unsigned_jwt(payload: dict[str, object]) -> str:
@pytest.mark.asyncio
async def test_enrich_codex_uses_access_token_when_id_token_missing() -> None:
async def test_enrich_codex_uses_access_token_when_id_token_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.services.provider.adapters.codex.plugin import enrich_codex
access_token = _encode_unsigned_jwt(
@@ -154,6 +175,10 @@ async def test_enrich_codex_uses_access_token_when_id_token_missing() -> None:
)
auth_config: dict[str, object] = {}
monkeypatch.setattr(
"src.core.provider_oauth_utils.fetch_openai_account_name",
AsyncMock(return_value="Workspace Alpha"),
)
out = await enrich_codex(
auth_config=auth_config,
token_response={"access_token": access_token},
@@ -163,5 +188,6 @@ async def test_enrich_codex_uses_access_token_when_id_token_missing() -> None:
assert out["email"] == "u@example.com"
assert out["account_id"] == "acc-access"
assert out["account_name"] == "Workspace Alpha"
assert out["plan_type"] == "team"
assert out["user_id"] == "user-access"

View File

@@ -6,6 +6,7 @@ from datetime import datetime, timezone
from types import SimpleNamespace
from typing import Any
import httpx
import pytest
from src.services.provider import auth as module
@@ -46,7 +47,9 @@ class _FakeSessionCtx:
return False
def _install_module(monkeypatch: pytest.MonkeyPatch, name: str, attrs: dict[str, Any]) -> None:
def _install_module(
monkeypatch: pytest.MonkeyPatch, name: str, attrs: dict[str, Any]
) -> None:
fake_module = types.ModuleType(name)
for key, value in attrs.items():
setattr(fake_module, key, value)
@@ -88,7 +91,9 @@ def test_mark_refresh_token_invalid_persists_detached_key(
module, "object_session", lambda _key: (_ for _ in ()).throw(RuntimeError())
)
_install_module(
monkeypatch, "src.database", {"create_session": lambda: _FakeSessionCtx(fake_db)}
monkeypatch,
"src.database",
{"create_session": lambda: _FakeSessionCtx(fake_db)},
)
_install_module(
monkeypatch,
@@ -105,7 +110,9 @@ def test_mark_refresh_token_invalid_persists_detached_key(
assert fake_db.committed is True
assert key.oauth_invalid_at is not None
assert row.oauth_invalid_at is not None
assert str(key.oauth_invalid_reason).startswith("[REFRESH_FAILED] Token 续期失败 (401)")
assert str(key.oauth_invalid_reason).startswith(
"[REFRESH_FAILED] Token 续期失败 (401)"
)
assert "refresh_token_reused" in str(row.oauth_invalid_reason)
@@ -144,3 +151,66 @@ def test_persist_refreshed_token_clears_legacy_token_invalidated_account_block(
assert key.auth_config == 'enc:{"refresh_token": "rt-2"}'
assert key.oauth_invalid_at is None
assert key.oauth_invalid_reason is None
@pytest.mark.asyncio
async def test_refresh_generic_oauth_token_persists_enriched_account_name(
monkeypatch: pytest.MonkeyPatch,
) -> None:
key = SimpleNamespace(id="key-1")
endpoint = SimpleNamespace()
template = SimpleNamespace(
oauth=SimpleNamespace(
token_url="https://example.com/oauth/token",
client_id="client-id",
client_secret=None,
scopes=[],
)
)
persisted: dict[str, Any] = {}
async def _fake_post_oauth_token(**_kwargs: Any) -> httpx.Response:
return httpx.Response(
200,
json={
"access_token": "new-token",
"refresh_token": "rt-2",
"expires_in": 3600,
"token_type": "Bearer",
},
request=httpx.Request("POST", "https://example.com/oauth/token"),
)
async def _fake_enrich_auth_config(**kwargs: Any) -> dict[str, Any]:
auth_config = dict(kwargs["auth_config"])
auth_config["account_name"] = "Workspace Alpha"
return auth_config
monkeypatch.setattr(module, "_get_proxy_config", lambda *_args: None)
monkeypatch.setattr(module, "post_oauth_token", _fake_post_oauth_token)
monkeypatch.setattr(module, "enrich_auth_config", _fake_enrich_auth_config)
monkeypatch.setattr(
module,
"_persist_refreshed_token",
lambda _key, _access_token, token_meta: persisted.update(
{"access_token": _access_token, "token_meta": dict(token_meta)}
),
)
token_meta = {
"provider_type": "codex",
"refresh_token": "rt-1",
}
refreshed = await module._refresh_generic_oauth_token(
key,
endpoint,
template,
"codex",
"rt-1",
token_meta,
)
assert refreshed["account_name"] == "Workspace Alpha"
assert persisted["access_token"] == "new-token"
assert persisted["token_meta"]["account_name"] == "Workspace Alpha"