Files

101 lines
3.1 KiB
Python

"""AccessToken:缓存复用、失败抛 TokenError、并发只换取一次、invalidate 强制刷新。"""
import asyncio
import pytest
from aiohttp import ClientTimeout
from qqbotsdk.config import Config
from qqbotsdk.token import AccessToken, TokenError
def make_config() -> Config:
return Config(
app_id="id",
app_secret="s",
base_url="https://x",
timeout=ClientTimeout(total=5),
connecter="websocket",
intents_file="intents.toml",
webhook_host="0.0.0.0",
webhook_port=8080,
webhook_path="/qqbot/webhook",
)
class FakeResponse:
def __init__(self, payload, status: int = 200) -> None:
self.status = status
self._payload = payload
async def json(self, **kwargs):
return self._payload
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
class FakeSession:
"""按序弹出响应体,记录 post 次数。"""
def __init__(self, *payloads) -> None:
self._payloads = list(payloads)
self.posts = 0
def post(self, *args, **kwargs):
self.posts += 1
return FakeResponse(self._payloads.pop(0))
def make_token(*payloads) -> AccessToken:
return AccessToken(make_config(), FakeSession(*payloads)) # type: ignore[arg-type]
async def test_success_caches_token():
session = FakeSession({"access_token": "t1", "expires_in": 7200})
token = AccessToken(make_config(), session) # type: ignore[arg-type]
assert await token.get_access_token() == "t1"
# 缓存期内直接复用,不再发请求
assert await token.get_access_token() == "t1"
assert session.posts == 1
async def test_error_payload_raises_token_error():
token = make_token({"code": 11253, "message": "invalid appid or secret"})
with pytest.raises(TokenError) as exc_info:
await token.get_access_token()
assert exc_info.value.code == 11253
assert exc_info.value.message == "invalid appid or secret"
assert "invalid appid or secret" in str(exc_info.value)
async def test_non_dict_payload_raises_token_error():
token = make_token("gateway busy")
with pytest.raises(TokenError) as exc_info:
await token.get_access_token()
assert exc_info.value.message == "gateway busy"
async def test_concurrent_first_fetch_only_requests_once():
session = FakeSession({"access_token": "t1", "expires_in": 7200})
token = AccessToken(make_config(), session) # type: ignore[arg-type]
results = await asyncio.gather(*(token.get_access_token() for _ in range(5)))
assert results == ["t1"] * 5
assert session.posts == 1 # 双重检查锁:并发首调只换取一次
async def test_invalidate_forces_refetch():
session = FakeSession(
{"access_token": "t1", "expires_in": 7200},
{"access_token": "t2", "expires_in": 7200},
)
token = AccessToken(make_config(), session) # type: ignore[arg-type]
assert await token.get_access_token() == "t1"
token.invalidate() # 模拟服务端提前作废
assert await token.get_access_token() == "t2"
assert session.posts == 2