set($key, 'cached-token', 3600); $http = new FakeHttpClient(['access_token' => 'remote-token', 'expires_in' => 7200]); $token = (new Client(cache: $cache, http: $http)) ->wechatPlatform(new WechatPlatformConfig('wx_app', 'secret')) ->accessToken(); $this->assertSame('cached-token', $token); $this->assertSame(0, $http->requests); $this->assertSame(0, $cache->lockCalls); } /** * 测试刷新 access_token 时会使用锁并写入缓存。 */ public function testAccessTokenRefreshUsesLockAndWritesCache(): void { $cache = new ArrayCacheStore(); $http = new FakeHttpClient(['access_token' => 'remote-token', 'expires_in' => 7200]); $platform = (new Client(cache: $cache, http: $http)) ->wechatPlatform(new WechatPlatformConfig('wx_app', 'secret')); $this->assertSame('remote-token', $platform->accessToken()); $this->assertSame(1, $http->requests); $this->assertSame(1, $cache->lockCalls); $this->assertSame('remote-token', $platform->accessToken()); $this->assertSame(1, $http->requests); } } /** * 测试用 StoreCacheInterface 内存实现。 */ final class ArrayCacheStore implements StoreCacheInterface { /** @var array */ private array $values = []; public int $lockCalls = 0; /** * 读取测试缓存值。 */ public function get(string $key, mixed $default = null): mixed { return $this->values[$key] ?? $default; } /** * 写入缓存值。 */ public function set(string $key, mixed $value, int $ttl): void { $this->values[$key] = $value; } /** * 删除缓存值。 */ public function del(string $key): void { unset($this->values[$key]); } /** * 在锁语义下执行回调。 */ public function lock(string $key, int $ttl, callable $callback): mixed { ++$this->lockCalls; return $callback(); } } /** * 返回固定 JSON 响应的测试 HTTP 客户端。 */ final class FakeHttpClient implements ClientInterface { public int $requests = 0; /** * 创建固定响应测试 HTTP 客户端。 * * @param array $payload */ public function __construct(private readonly array $payload) {} /** * 实现测试 HTTP 客户端同步发送接口。 */ public function send(RequestInterface $request, array $options = []): ResponseInterface { return $this->response(); } /** * 实现测试 HTTP 客户端异步发送接口。 */ public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface { return Create::rejectionFor(new \RuntimeException('sendAsync is not used in this test')); } /** * 实现测试 HTTP 客户端请求接口或记录请求。 */ public function request(string $method, $uri = '', array $options = []): ResponseInterface { ++$this->requests; return $this->response(); } /** * 实现测试 HTTP 客户端异步请求接口。 */ public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface { return Create::rejectionFor(new \RuntimeException('requestAsync is not used in this test')); } /** * 返回测试 HTTP 客户端配置。 */ public function getConfig(?string $option = null): mixed { return null; } /** * 构造测试 HTTP 响应对象。 */ private function response(): ResponseInterface { return new Response(200, [], json_encode($this->payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'); } }