Files
myep-team/tests/MyEp/Api/ApiClientTest.php
T

167 lines
5.3 KiB
PHP

<?php
namespace App\Tests\MyEp\Api;
use App\MyEp\Api\AccessTokenProvider;
use App\MyEp\Api\ApiClient;
use App\MyEp\Api\ApiException;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
class ApiClientTest extends TestCase
{
public function testPayloadIsReturned(): void
{
$httpClient = $this->mockClient([new MockResponse('[{"id":1}]')]);
$this->assertEquals([['id' => 1]], $this->getClientInstance($httpClient)->get('api/hotels'));
}
public function testTheTokenIsSentAsABearer(): void
{
$authorization = null;
$httpClient = $this->mockClient(function (string $method, string $url, array $options) use (&$authorization) {
$this->assertEquals('GET', $method);
$authorization = $this->headerOf($options, 'authorization');
return new MockResponse('[]');
});
$this->getClientInstance($httpClient)->get('api/hotels');
$this->assertEquals('Authorization: Bearer token-1', $authorization);
}
public function testQueryParametersArePassedOn(): void
{
$requestedUrl = null;
$httpClient = $this->mockClient(function (string $method, string $url) use (&$requestedUrl) {
$requestedUrl = $url;
return new MockResponse('[]');
});
$this->getClientInstance($httpClient)->get('api/travels/export', ['page' => 2, 'perPage' => 500]);
$this->assertStringContainsString('page=2', $requestedUrl);
$this->assertStringContainsString('perPage=500', $requestedUrl);
}
public function testARejectedTokenIsRenewedOnce(): void
{
$httpClient = $this->mockClient([
new MockResponse('{"message":"Unauthorized"}', ['http_code' => 401]),
new MockResponse('[{"id":1}]'),
]);
$result = $this->getClientInstance($httpClient)->get('api/hotels');
$this->assertEquals([['id' => 1]], $result);
$this->assertEquals(2, $httpClient->getRequestsCount());
}
public function testARepeatedlyRejectedTokenFailsWithoutLooping(): void
{
$httpClient = $this->mockClient([
new MockResponse('{"message":"Unauthorized"}', ['http_code' => 401]),
new MockResponse('{"message":"Unauthorized"}', ['http_code' => 401]),
]);
try {
$this->getClientInstance($httpClient)->get('api/hotels');
$this->fail('Expected an ApiException');
} catch (ApiException) {
}
$this->assertEquals(2, $httpClient->getRequestsCount());
}
public function testAMissingScopeFails(): void
{
$httpClient = $this->mockClient([new MockResponse('{"message":"Forbidden"}', ['http_code' => 403])]);
$this->expectException(ApiException::class);
$this->expectExceptionMessageMatches('/scope/');
$this->getClientInstance($httpClient)->get('api/hotels');
}
public function testAServerErrorFails(): void
{
$httpClient = $this->mockClient([new MockResponse('', ['http_code' => 500])]);
$this->expectException(ApiException::class);
$this->getClientInstance($httpClient)->get('api/hotels');
}
public function testAnErrorReportedWithStatusOkFails(): void
{
// some MyE&P endpoints answer 200 with an error body, so the payload has to decide
$httpClient = $this->mockClient([
new MockResponse('{"error":"upstream unavailable","type":"api_error"}'),
]);
$this->expectException(ApiException::class);
$this->getClientInstance($httpClient)->get('api/products');
}
public function testAnUnreadableBodyFails(): void
{
$httpClient = $this->mockClient([new MockResponse('<html>not json</html>')]);
$this->expectException(ApiException::class);
$this->getClientInstance($httpClient)->get('api/hotels');
}
public function testAnUnreachableApiFails(): void
{
$httpClient = $this->mockClient([new MockResponse('', ['error' => 'connection refused'])]);
$this->expectException(ApiException::class);
$this->getClientInstance($httpClient)->get('api/hotels');
}
private function mockClient(array|callable $responses): MockHttpClient
{
return new MockHttpClient($responses, 'https://myep.example/');
}
private function headerOf(array $options, string $name): ?string
{
foreach ($options['headers'] ?? [] as $header) {
if (str_starts_with(strtolower($header), $name.':')) {
return $header;
}
}
return null;
}
private function getClientInstance(MockHttpClient $httpClient): ApiClient
{
$tokenClient = new MockHttpClient(function () {
static $count = 0;
return new MockResponse(json_encode([
'access_token' => 'token-'.++$count,
'expires_in' => 600,
]));
});
$tokens = new AccessTokenProvider($tokenClient, new ArrayAdapter(), new NullLogger(), [
'client_id' => 'team-app',
'client_secret' => 'secret',
'token_path' => 'https://myep.example/token',
]);
return new ApiClient($httpClient, $tokens, new NullLogger());
}
}