tokenResponse('abc123')]); $this->assertEquals('abc123', $this->getProviderInstance($httpClient)->getToken()); } public function testTheClientCredentialsGrantIsRequested(): void { $body = null; $httpClient = new MockHttpClient(function (string $method, string $url, array $options) use (&$body) { $this->assertEquals('POST', $method); $this->assertEquals('https://myep.example/token', $url); $body = $options['body']; return $this->tokenResponse('abc123'); }); $this->getProviderInstance($httpClient)->getToken(); $this->assertStringContainsString('grant_type=client_credentials', $body); $this->assertStringContainsString('scope=api', $body); $this->assertStringContainsString('client_secret=secret', $body); } public function testTheTokenIsCachedRatherThanRequestedPerCall(): void { $httpClient = new MockHttpClient([$this->tokenResponse('abc123')]); $cache = new ArrayAdapter(); $provider = $this->getProviderInstance($httpClient, $cache); $provider->getToken(); $provider->getToken(); $this->assertEquals(1, $httpClient->getRequestsCount()); $this->assertTrue($cache->getItem(self::CACHE_KEY)->isHit()); } public function testACachedTokenIsUsedWithoutAskingForANewOne(): void { $cache = new ArrayAdapter(); $item = $cache->getItem(self::CACHE_KEY); $item->set('cached-token'); $cache->save($item); $httpClient = new MockHttpClient([]); $this->assertEquals('cached-token', $this->getProviderInstance($httpClient, $cache)->getToken()); $this->assertEquals(0, $httpClient->getRequestsCount()); } public function testAnInvalidatedTokenIsRequestedAgain(): void { $httpClient = new MockHttpClient([$this->tokenResponse('abc123'), $this->tokenResponse('def456')]); $provider = $this->getProviderInstance($httpClient); $provider->getToken(); $provider->invalidate(); $this->assertEquals('def456', $provider->getToken()); $this->assertEquals(2, $httpClient->getRequestsCount()); } public function testARejectedRequestFails(): void { $httpClient = new MockHttpClient([ new MockResponse('{"error":"invalid_client"}', ['http_code' => 401]), ]); $this->expectException(ApiException::class); $this->getProviderInstance($httpClient)->getToken(); } public function testAResponseWithoutATokenFails(): void { $httpClient = new MockHttpClient([new MockResponse('{"expires_in":600}')]); $this->expectException(ApiException::class); $this->getProviderInstance($httpClient)->getToken(); } public function testAnUnreachableTokenEndpointFails(): void { $httpClient = new MockHttpClient([new MockResponse('', ['error' => 'connection refused'])]); $this->expectException(ApiException::class); $this->getProviderInstance($httpClient)->getToken(); } public function testAnUnparsableResponseFails(): void { $httpClient = new MockHttpClient([new MockResponse('gateway timeout')]); $this->expectException(ApiException::class); $this->getProviderInstance($httpClient)->getToken(); } private function tokenResponse(string $token, int $expiresIn = 600): MockResponse { return new MockResponse(json_encode([ 'access_token' => $token, 'token_type' => 'Bearer', 'expires_in' => $expiresIn, ])); } private function getProviderInstance(MockHttpClient $httpClient, ?CacheItemPoolInterface $cache = null): AccessTokenProvider { return new AccessTokenProvider( $httpClient, $cache ?? new ArrayAdapter(), new NullLogger(), [ 'client_id' => 'team-app', 'client_secret' => 'secret', 'token_path' => 'https://myep.example/token', 'scope' => 'api', ], ); } }