131 lines
4.6 KiB
PHP
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'],
|
|
);
|
|
}
|
|
}
|