Files
myep-team/tests/Saas/ApiClientTest.php
2026-10-01 10:07:05 +02:00

131 lines
4.6 KiB
PHP

<?php
namespace App\Tests\Saas;
use App\Saas\ApiClient;
use App\Saas\ApiClientException;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
class ApiClientTest extends TestCase
{
/**
* The whole point of the calendar-month argument: the app never has to know that the
* API counts from zero.
*/
#[\PHPUnit\Framework\Attributes\DataProvider('provideMonths')]
public function testItTranslatesTheCalendarMonthIntoTheApiIndex(int $month, string $expectedPath): void
{
$requested = null;
$client = $this->createClient(function (string $method, string $url) use (&$requested) {
$requested = $url;
return new MockResponse('{"ist": 0, "list": []}', ['response_headers' => ['content-type' => 'application/json']]);
});
$client->getTimeAccount('[email protected]', 2026, $month);
$this->assertSame('https://saas.example'.$expectedPath, $requested);
}
public static function provideMonths(): iterable
{
yield 'January is index 0' => [1, '/api/v2/time/account/secret%2Fkey/theo%40example.com/2026/0'];
yield 'March is index 2' => [3, '/api/v2/time/account/secret%2Fkey/theo%40example.com/2026/2'];
yield 'December is index 11' => [12, '/api/v2/time/account/secret%2Fkey/theo%40example.com/2026/11'];
}
/**
* Both the key and the address sit in the path, so both have to survive a slash or an
* "@" without changing the route.
*/
public function testItEscapesTheKeyAndTheAddress(): void
{
$requested = null;
$client = $this->createClient(function (string $method, string $url) use (&$requested) {
$requested = $url;
return new MockResponse('{"ist": 0, "list": []}', ['response_headers' => ['content-type' => 'application/json']]);
});
$client->getTimeAccount('a [email protected]', 2026, 3);
$this->assertStringContainsString('/secret%2Fkey/', $requested);
$this->assertStringContainsString('/a%20b%2Bc%40example.com/', $requested);
}
public function testItReturnsTheDecodedPayload(): void
{
$client = $this->createClient(fn () => new MockResponse(
'{"ist": 480, "employee": "e-1", "list": []}',
['response_headers' => ['content-type' => 'application/json']],
));
$this->assertSame(
['ist' => 480, 'employee' => 'e-1', 'list' => []],
$client->getTimeAccount('[email protected]', 2026, 9),
);
}
public function testAnErrorStatusBecomesAnApiClientException(): void
{
$client = $this->createClient(fn () => new MockResponse('nope', ['http_code' => 500]));
$this->expectException(ApiClientException::class);
$this->expectExceptionMessageMatches('/[email protected] \(2026-03\)/');
$client->getTimeAccount('[email protected]', 2026, 3);
}
public function testUnreadableJsonBecomesAnApiClientException(): void
{
$client = $this->createClient(fn () => new MockResponse(
'not json',
['response_headers' => ['content-type' => 'application/json']],
));
$this->expectException(ApiClientException::class);
$client->getTimeAccount('[email protected]', 2026, 3);
}
public function testItRefusesAMonthThatIsNotACalendarMonth(): void
{
$client = $this->createClient(fn () => new MockResponse('{}'));
$this->expectException(ApiClientException::class);
$this->expectExceptionMessage('0 is not a calendar month.');
$client->getTimeAccount('[email protected]', 2026, 0);
}
public function testAnUnconfiguredBaseUrlFailsAtCallTimeNotAtBoot(): void
{
// The command has to stay usable - "--help" included - on a machine that has no
// credentials, so this cannot be a container-time failure.
$client = new ApiClient(
new MockHttpClient(fn () => new MockResponse('{}')),
new NullLogger(),
['base_url' => '', 'api_key' => 'k'],
);
$this->expectException(ApiClientException::class);
$this->expectExceptionMessage('APP_SAAS_BASE_URL is not configured.');
$client->getTimeAccount('[email protected]', 2026, 3);
}
private function createClient(callable $handler): ApiClient
{
return new ApiClient(
new MockHttpClient($handler),
new NullLogger(),
['base_url' => 'https://saas.example', 'api_key' => 'secret/key'],
);
}
}