From c123602606e1de57f53442eee5abb732f24fdacf Mon Sep 17 00:00:00 2001 From: Lennart Van Vaerenbergh Date: Thu, 30 Oct 2025 09:18:21 +0100 Subject: [PATCH] Change BasicAuth to OAuth (oidc) --- .gitignore | 2 +- CHANGELOG.md | 11 ++ README.md | 2 +- composer.json | 5 + phpunit.xml | 36 ++++++ src/Client/AbstractClient.php | 43 ++++++- src/Client/Configuration/Configuration.php | 87 +++++++++++++- .../Configuration/ConfigurationInterface.php | 28 +++++ src/Client/Token/OidcTokenProvider.php | 109 ++++++++++++++++++ src/Client/Token/TokenProviderInterface.php | 19 +++ src/Client/Uri/BaseUri.php | 28 +++++ src/Client/Uri/Uri.php | 22 +--- tests/Cache/CacheableTraitTest.php | 25 ++-- tests/Client/ClientTest.php | 109 ++++++++++++------ .../Configuration/ConfigurationTest.php | 88 +++++++++----- .../Client/Exception/HandlerNotFoundTest.php | 4 +- .../Client/Exception/InvalidResponseTest.php | 4 +- .../Request/AbstractHtmlRequestTest.php | 23 ++-- .../Request/AbstractJsonRequestTest.php | 27 ++--- .../Client/Request/AbstractXmlRequestTest.php | 27 ++--- tests/Client/Uri/UriTest.php | 7 +- 21 files changed, 548 insertions(+), 158 deletions(-) create mode 100644 phpunit.xml create mode 100644 src/Client/Token/OidcTokenProvider.php create mode 100644 src/Client/Token/TokenProviderInterface.php create mode 100644 src/Client/Uri/BaseUri.php diff --git a/.gitignore b/.gitignore index 969ca28..5dfc9f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # PHPUnit build artifacts. /build -.phpunit.cache/ +.phpunit.cache .phpunit.result.cache # Never commit vendors! diff --git a/CHANGELOG.md b/CHANGELOG.md index e57fc03..ab7b01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All Notable changes to `digipolisgent/api-client` package. +## [4.0.0] + +_Use version 4 of this module to switch over to OAuth authentication. +Key/secret authentication is dropped from the client. +The client and configuration objects have changed parameters._ + +### Added + +* ZALENZOEK-689: Add OAuth2 API authentication. + ## [3.0.1] ### Fixed @@ -82,6 +92,7 @@ This includes: * Interfaces to create services in client packages. [Unreleased]: https://github.com/digipolisgent/php_package_dg-api-client/compare/master...develop +[4.0.0]: https://github.com/digipolisgent/php_package_dg-api-client/compare/3.0.1...4.0.0 [3.0.1]: https://github.com/digipolisgent/php_package_dg-api-client/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/digipolisgent/php_package_dg-api-client/compare/2.1.0...3.0.0 [2.1.0]: https://github.com/digipolisgent/php_package_dg-api-client/compare/2.0.0...2.1.0 diff --git a/README.md b/README.md index ec446ec..e159bd6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DigipolsigGent API Client +# District09 API Client This package provides interfaces and abstract implementations to create an API client. diff --git a/composer.json b/composer.json index 1d48a14..2f8c8b5 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,10 @@ { "name": "Jelle Sebreghts", "email": "sebreghts.jelle@district09.gent" + }, + { + "name": "Lennart Van Vaerenbergh", + "email": "lennart.vanvaerenbergh@district09.gent" } ], "homepage": "https://github.com/digipolisgent/php_package_api-client", @@ -24,6 +28,7 @@ "php": "^7.4 || ^8.0", "ext-json": "*", "guzzlehttp/guzzle": "^6.5 || ^7.0", + "jumbojett/openid-connect-php": "^1", "psr/http-message": "^1.0", "psr/simple-cache": "^1.0" }, diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..a98ffd8 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,36 @@ + + + + + src + + + + + + tests + + + + + + + + + + diff --git a/src/Client/AbstractClient.php b/src/Client/AbstractClient.php index 018373d..63e24b7 100644 --- a/src/Client/AbstractClient.php +++ b/src/Client/AbstractClient.php @@ -8,15 +8,20 @@ use DigipolisGent\API\Client\Exception\HandlerNotFound; use DigipolisGent\API\Client\Handler\HandlerInterface; use DigipolisGent\API\Client\Response\ResponseInterface; +use DigipolisGent\API\Client\Token\OidcTokenProvider; +use DigipolisGent\API\Client\Token\TokenProviderInterface; use DigipolisGent\API\Logger\LoggableInterface; use DigipolisGent\API\Logger\LoggableTrait; use DigipolisGent\API\Logger\RequestLog; use GuzzleHttp\ClientInterface as GuzzleClientInterface; use GuzzleHttp\Exception\ClientException; use Psr\Http\Message\RequestInterface; +use Psr\SimpleCache\CacheInterface; /** * Abstract implementation of the service client. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ abstract class AbstractClient implements ClientInterface, LoggableInterface { @@ -43,16 +48,37 @@ abstract class AbstractClient implements ClientInterface, LoggableInterface */ protected ConfigurationInterface $configuration; + /** + * The OIDC token provider. + * + * @var \DigipolisGent\API\Client\Token\TokenProviderInterface + */ + protected TokenProviderInterface $tokenProvider; + /** * Client constructor. * * @param \GuzzleHttp\ClientInterface $guzzle + * The Guzzle HTTP client. * @param \DigipolisGent\API\Client\Configuration\ConfigurationInterface $configuration + * The client configuration object. + * @param \Psr\SimpleCache\CacheInterface $cache + * Cache used for auth Bearer tokens. Not that this is not for API responses. */ - public function __construct(GuzzleClientInterface $guzzle, ConfigurationInterface $configuration) - { + public function __construct( + GuzzleClientInterface $guzzle, + ConfigurationInterface $configuration, + CacheInterface $cache, + ) { $this->guzzle = $guzzle; $this->configuration = $configuration; + $this->tokenProvider = new OidcTokenProvider( + $configuration->getAuthUri(), + $configuration->getClientId(), + $configuration->getClientSecret(), + $configuration->getScope(), + $cache, + ); } /** @@ -90,10 +116,15 @@ public function send(RequestInterface $request): ResponseInterface */ protected function injectHeaders(RequestInterface $request): RequestInterface { - return $request->withHeader( - 'Content-Length', - (string) strlen((string) $request->getBody()) - ); + return $request + ->withHeader( + 'Content-Length', + (string) strlen((string) $request->getBody()) + ) + ->withHeader( + 'Authorization', + 'Bearer ' . $this->tokenProvider->getAccessToken() + ); } /** diff --git a/src/Client/Configuration/Configuration.php b/src/Client/Configuration/Configuration.php index e67844b..8606b1f 100644 --- a/src/Client/Configuration/Configuration.php +++ b/src/Client/Configuration/Configuration.php @@ -16,6 +16,34 @@ class Configuration implements ConfigurationInterface */ protected string $endpointUri; + /** + * Endpoint URI for the authentication. + * + * @var string + */ + protected string $authEndpointUri; + + /** + * Client ID. + * + * @var string + */ + protected string $clientId; + + /** + * Client secret. + * + * @var string + */ + protected string $clientSecret; + + /** + * Auth scope. + * + * @var string + */ + protected string $scope; + /** * The configuration options. * @@ -28,10 +56,33 @@ class Configuration implements ConfigurationInterface /** * Create new configuration. + * + * @param string $endpointUri + * The endpoint URI. + * @param string $authEndpointUri + * The authentication endpoint URI. + * @param string $clientId + * The client ID. + * @param string $clientSecret + * The client secret. + * @param string $scope + * The authentication scope. + * @param array $options + * The client extra options. */ - public function __construct(string $endpointUri, array $options = []) - { + public function __construct( + string $endpointUri, + string $authEndpointUri, + string $clientId, + string $clientSecret, + string $scope, + array $options = [], + ) { $this->endpointUri = $endpointUri; + $this->authEndpointUri = $authEndpointUri; + $this->clientId = $clientId; + $this->clientSecret = $clientSecret; + $this->scope = $scope; foreach ($options as $key => $value) { if (!array_key_exists($key, $this->options)) { @@ -50,6 +101,38 @@ public function getUri(): string return $this->endpointUri; } + /** + * @inheritDoc + */ + public function getAuthUri(): string + { + return $this->authEndpointUri; + } + + /** + * @inheritDoc + */ + public function getClientId(): string + { + return $this->clientId; + } + + /** + * @inheritDoc + */ + public function getClientSecret(): string + { + return $this->clientSecret; + } + + /** + * @inheritDoc + */ + public function getScope(): string + { + return $this->scope; + } + /** * @inheritDoc */ diff --git a/src/Client/Configuration/ConfigurationInterface.php b/src/Client/Configuration/ConfigurationInterface.php index da43ccc..5dd1aef 100644 --- a/src/Client/Configuration/ConfigurationInterface.php +++ b/src/Client/Configuration/ConfigurationInterface.php @@ -16,6 +16,34 @@ interface ConfigurationInterface */ public function getUri(): string; + /** + * Get the authentication URI. + * + * @return string + */ + public function getAuthUri(): string; + + /** + * Get the client ID. + * + * @return string + */ + public function getClientId(): string; + + /** + * Get the client secret. + * + * @return string + */ + public function getClientSecret(): string; + + /** + * Get the scope. + * + * @return string + */ + public function getScope(): string; + /** * Get the service version number to use. * diff --git a/src/Client/Token/OidcTokenProvider.php b/src/Client/Token/OidcTokenProvider.php new file mode 100644 index 0000000..7463f4f --- /dev/null +++ b/src/Client/Token/OidcTokenProvider.php @@ -0,0 +1,109 @@ +oidc = new OpenIDConnectClient( + sprintf( + '%s://%s', + parse_url($authEndpointUri, PHP_URL_SCHEME), + parse_url($authEndpointUri, PHP_URL_HOST) + ), + $clientId, + $clientSecret, + ); + + $this->oidc->providerConfigParam(['token_endpoint' => $authEndpointUri]); + $this->oidc->addScope([$scope]); + $this->cache = $cache; + } + + /** + * @inheritDoc + */ + public function getAccessToken(): string + { + if ($this->cache->has($this->cacheKey)) { + return $this->cache->get($this->cacheKey); + } + + return $this->fetchNewToken(); + } + + /** + * Fetch a new access token. + * + * @return string + * The access token. + * @throws \RuntimeException + */ + private function fetchNewToken(): string + { + $tokenResponse = $this->oidc->requestClientCredentialsToken(); + + if (!isset($tokenResponse->access_token)) { + throw new RuntimeException('Failed to obtain OIDC token.'); + } + + $accessToken = $tokenResponse->access_token; + // Default expiration to 1 hour. + $expiresIn = $tokenResponse->expires_in ?? 3600; + + // Cache token with a small buffer before expiration. + $this->cache->set($this->cacheKey, $accessToken, $expiresIn - 60); + + return $accessToken; + } +} diff --git a/src/Client/Token/TokenProviderInterface.php b/src/Client/Token/TokenProviderInterface.php new file mode 100644 index 0000000..9cd0caa --- /dev/null +++ b/src/Client/Token/TokenProviderInterface.php @@ -0,0 +1,19 @@ +uri; + } +} diff --git a/src/Client/Uri/Uri.php b/src/Client/Uri/Uri.php index f03b150..32d2422 100644 --- a/src/Client/Uri/Uri.php +++ b/src/Client/Uri/Uri.php @@ -4,18 +4,13 @@ namespace DigipolisGent\API\Client\Uri; +use DigipolisGent\API\Client\Uri\BaseUri; + /** * Request URI to be used to communicate with the server endpoint. */ -final class Uri implements UriInterface +class Uri extends BaseUri { - /** - * The URI string. - * - * @var string - */ - private string $uri; - /** * Construct the URI object from a URI string. * @@ -25,15 +20,4 @@ public function __construct(string $uri) { $this->uri = $uri; } - - /** - * Get the URI as string. - * - * @return string - * The URI. - */ - public function getUri(): string - { - return $this->uri; - } } diff --git a/tests/Cache/CacheableTraitTest.php b/tests/Cache/CacheableTraitTest.php index a12a602..89e1dd5 100644 --- a/tests/Cache/CacheableTraitTest.php +++ b/tests/Cache/CacheableTraitTest.php @@ -5,6 +5,7 @@ namespace DigipolisGent\Tests\API\Cache; use DigipolisGent\API\Cache\CacheableTrait; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Psr\SimpleCache\CacheInterface; @@ -19,9 +20,8 @@ class CacheableTraitTest extends TestCase /** * False is returned is cache is set without cache service. - * - * @test */ + #[Test] public function cacheSetWithoutCache(): void { $key = uniqid('', true); @@ -32,9 +32,8 @@ public function cacheSetWithoutCache(): void /** * False is returned if cache is deleted without cache service. - * - * @test */ + #[Test] public function cacheDeletedWithoutCache(): void { $key = uniqid('', true); @@ -44,9 +43,8 @@ public function cacheDeletedWithoutCache(): void /** * False is returned when all cache is cleared without cache service. - * - * @test */ + #[Test] public function cacheClearWithoutCache(): void { $this->assertFalse($this->cacheClear()); @@ -54,9 +52,8 @@ public function cacheClearWithoutCache(): void /** * Null is returned when item is retrieved from cache without cache service. - * - * @test */ + #[Test] public function cacheGetWithoutCache(): void { $key = uniqid('', true); @@ -66,9 +63,8 @@ public function cacheGetWithoutCache(): void /** * True is returned when value is stored in cache service. - * - * @test */ + #[Test] public function cacheSetWithCache(): void { $key = uniqid('', true); @@ -87,9 +83,8 @@ public function cacheSetWithCache(): void /** * True is returned when the value is deleted from the cache service. - * - * @test */ + #[Test] public function cacheDeletedWithCache(): void { $key = uniqid('', true); @@ -106,9 +101,8 @@ public function cacheDeletedWithCache(): void /** * True is returned when all cache is cleared from cache service. - * - * @test */ + #[Test] public function cacheClearWithCache(): void { $cacheService = $this->prophesize(CacheInterface::class); @@ -123,9 +117,8 @@ public function cacheClearWithCache(): void /** * Value can be retrieved from the cache backend. - * - * @test */ + #[Test] public function cacheGetWithCache(): void { $key = uniqid('', true); diff --git a/tests/Client/ClientTest.php b/tests/Client/ClientTest.php index d7e85a6..558ac38 100644 --- a/tests/Client/ClientTest.php +++ b/tests/Client/ClientTest.php @@ -13,15 +13,16 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\ClientInterface as GuzzleClientInterface; use GuzzleHttp\Exception\ClientException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface as PsrResponseInterface; +use Psr\SimpleCache\CacheInterface; -/** - * @covers \DigipolisGent\API\Client\AbstractClient - */ +#[CoversClass(\DigipolisGent\API\Client\AbstractClient::class)] class ClientTest extends TestCase { use ProphecyTrait; @@ -41,6 +42,13 @@ class ClientTest extends TestCase */ protected ConfigurationInterface $configuration; + /** + * Cache used for auth Bearer tokens. Not that this is not for API responses. + * + * @var \Psr\SimpleCache\CacheInterface + */ + protected CacheInterface $cache; + /** * @var \Psr\Http\Message\RequestInterface */ @@ -59,7 +67,7 @@ class ClientTest extends TestCase /** * @var \DigipolisGent\API\Client\Response\ResponseInterface */ - protected $response; + protected ResponseInterface $response; /** * @inheritDoc @@ -69,7 +77,22 @@ protected function setUp(): void parent::setUp(); $this->endpointUri = 'https://' . uniqid('', true) . '.com'; - $this->configuration = new Configuration($this->endpointUri); + + $authEndpointUri = 'https://auth.' . uniqid('', true) . '.com'; + $clientId = 'client-id'; + $clientSecret = 'client-secret'; + $scope = 'scope'; + + $this->configuration = new Configuration( + $this->endpointUri, + $authEndpointUri, + $clientId, + $clientSecret, + $scope, + ); + + $cache = $this->prophesize(CacheInterface::class); + $this->cache = $cache->reveal(); $response = $this->prophesize(ResponseInterface::class); $this->response = $response->reveal(); @@ -79,7 +102,7 @@ protected function setUp(): void $request = $this->prophesize(RequestInterface::class); $request->getBody()->willReturn('123'); - $request->withHeader('Content-Length', 3)->willReturn($request->reveal()); + $request->withHeader(Argument::cetera())->willReturn($request->reveal()); $this->request = $request->reveal(); $guzzle = $this->prophesize(GuzzleClientInterface::class); @@ -94,15 +117,19 @@ protected function setUp(): void /** * Exception is thrown when client gets request that cannot be handled. - * - * @test */ + #[Test] public function exceptionIsThrownWhenRequestHasNoHandlers(): void { - $client = $this->getMockForAbstractClass( - AbstractClient::class, - [$this->guzzle, $this->configuration] - ); + $client = new class ($this->guzzle, $this->configuration, $this->cache) extends AbstractClient { + /** + * Avoid token provider side effects in unit tests. + */ + protected function injectHeaders(RequestInterface $request): RequestInterface + { + return $request; + } + }; $this->expectException(HandlerNotFound::class); $client->send($this->request); @@ -110,54 +137,70 @@ public function exceptionIsThrownWhenRequestHasNoHandlers(): void /** * Client sends request and returns handler response. - * - * @test */ + #[Test] public function handlerProcessesResponse(): void { - $client = $this->getMockForAbstractClass( - AbstractClient::class, - [$this->guzzle, $this->configuration] - ); + $client = new class ($this->guzzle, $this->configuration, $this->cache) extends AbstractClient { + /** + * Avoid token provider side effects in unit tests. + */ + protected function injectHeaders(RequestInterface $request): RequestInterface + { + return $request; + } + }; + $client->addHandler($this->handler); - $this->assertEquals($this->response, $client->send($this->request)); + $this->assertSame($this->response, $client->send($this->request)); } /** * Client exception is captured in response. - * - * @test */ + #[Test] public function guzzleExceptionIsCapturedInResponse(): void { $exception = new ClientException('', $this->request, $this->psrResponse); + $guzzle = $this->prophesize(GuzzleClientInterface::class); $guzzle->send(Argument::any())->willThrow($exception); - $client = $this->getMockForAbstractClass( - AbstractClient::class, - [$guzzle->reveal(), $this->configuration] - ); + $client = new class ($guzzle->reveal(), $this->configuration, $this->cache) extends AbstractClient { + /** + * Avoid token provider side effects in unit tests. + */ + protected function injectHeaders(RequestInterface $request): RequestInterface + { + return $request; + } + }; + $client->addHandler($this->handler); - $this->assertEquals($this->response, $client->send($this->request)); + $this->assertSame($this->response, $client->send($this->request)); } /** * Handlers can be retrieved from the client. - * - * @test */ + #[Test] public function itReturnsAllAddedHandlers(): void { - $client = $this->getMockForAbstractClass( - AbstractClient::class, - [$this->guzzle, $this->configuration] - ); + $client = new class ($this->guzzle, $this->configuration, $this->cache) extends AbstractClient { + /** + * Avoid token provider side effects in unit tests. + */ + protected function injectHeaders(RequestInterface $request): RequestInterface + { + return $request; + } + }; + $client->addHandler($this->handler); - self::assertEquals( + self::assertSame( [$this->handler], array_values($client->getHandlers()) ); diff --git a/tests/Client/Configuration/ConfigurationTest.php b/tests/Client/Configuration/ConfigurationTest.php index 7c9c055..4c72a4e 100644 --- a/tests/Client/Configuration/ConfigurationTest.php +++ b/tests/Client/Configuration/ConfigurationTest.php @@ -5,6 +5,7 @@ namespace DigipolisGent\Tests\API\Client\Configuration; use DigipolisGent\API\Client\Configuration\Configuration; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; /** @@ -14,54 +15,83 @@ class ConfigurationTest extends TestCase { /** * Configuration can be created without options. - * - * @test */ + #[Test] public function configurationCanBeCreatedWithoutOptions(): void { - $uri = 'https://test-endpoint.gent'; - $configuration = new Configuration($uri); + $endpointUri = 'https://test-endpoint.gent'; + $authEndpointUri = 'https://auth-endpoint.gent'; + $clientId = 'client-id'; + $clientSecret = 'client-secret'; + $scope = 'scope'; - $this->assertEquals($uri, $configuration->getUri(), 'Uri is set.'); - - $this->assertSame( - '1', - $configuration->getVersion(), - 'Default version is "1".' - ); - $this->assertEquals( - 20, - $configuration->getTimeout(), - 'Default timeout is 20s.' + $configuration = new Configuration( + $endpointUri, + $authEndpointUri, + $clientId, + $clientSecret, + $scope, ); + + $this->assertSame($endpointUri, $configuration->getUri(), 'Endpoint URI is set.'); + $this->assertSame($authEndpointUri, $configuration->getAuthUri(), 'Auth endpoint URI is set.'); + $this->assertSame($clientId, $configuration->getClientId(), 'Client ID is set.'); + $this->assertSame($clientSecret, $configuration->getClientSecret(), 'Client secret is set.'); + $this->assertSame($scope, $configuration->getScope(), 'Scope is set.'); + + $this->assertSame('1', $configuration->getVersion(), 'Default version is "1".'); + $this->assertSame(20, $configuration->getTimeout(), 'Default timeout is 20s.'); } /** * Configuration can be created with options. - * - * @test */ - public function constructorWithOptions(): void + #[Test] + public function configurationCanBeCreatedWithOptions(): void { + $endpointUri = 'https://foo.com'; + $authEndpointUri = 'https://auth.foo.com'; + $clientId = 'client-id'; + $clientSecret = 'client-secret'; + $scope = 'scope'; + $options = [ 'version' => 2, 'timeout' => 10, 'foo' => 'bar', ]; + $configuration = new Configuration( - 'https://foo.com', - $options + $endpointUri, + $authEndpointUri, + $clientId, + $clientSecret, + $scope, + $options, ); - $this->assertSame( - (string) $options['version'], - $configuration->getVersion(), - 'Version is set to custom value.' - ); - $this->assertSame( - $options['timeout'], - $configuration->getTimeout(), - 'Timeout is set to custom value.' + $this->assertSame('2', $configuration->getVersion(), 'Version is set to custom value.'); + $this->assertSame(10, $configuration->getTimeout(), 'Timeout is set to custom value.'); + } + + /** + * Unknown options are ignored. + */ + #[Test] + public function unknownOptionsAreIgnored(): void + { + $configuration = new Configuration( + 'https://foo.com', + 'https://auth.foo.com', + 'client-id', + 'client-secret', + 'scope', + [ + 'foo' => 'bar', + ], ); + + $this->assertSame('1', $configuration->getVersion(), 'Default version remains when unknown options are passed.'); + $this->assertSame(20, $configuration->getTimeout(), 'Default timeout remains when unknown options are passed.'); } } diff --git a/tests/Client/Exception/HandlerNotFoundTest.php b/tests/Client/Exception/HandlerNotFoundTest.php index 13be6db..6b1a353 100644 --- a/tests/Client/Exception/HandlerNotFoundTest.php +++ b/tests/Client/Exception/HandlerNotFoundTest.php @@ -5,6 +5,7 @@ namespace DigipolisGent\Tests\API\Client\Exception; use DigipolisGent\API\Client\Exception\HandlerNotFound; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Http\Message\RequestInterface; @@ -18,9 +19,8 @@ class HandlerNotFoundTest extends TestCase /** * Exception can be created from a given request. - * - * @test */ + #[Test] public function exceptionCanBeCreatedFromRequest(): void { $request = $this->prophesize(RequestInterface::class)->reveal(); diff --git a/tests/Client/Exception/InvalidResponseTest.php b/tests/Client/Exception/InvalidResponseTest.php index 6846cce..0f9ca56 100644 --- a/tests/Client/Exception/InvalidResponseTest.php +++ b/tests/Client/Exception/InvalidResponseTest.php @@ -5,6 +5,7 @@ namespace DigipolisGent\Tests\API\Client\Exception; use DigipolisGent\API\Client\Exception\InvalidResponse; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Http\Message\ResponseInterface; @@ -18,9 +19,8 @@ class InvalidResponseTest extends TestCase /** * Exception can be created from response. - * - * @test */ + #[Test] public function exceptionCanBeCreatedFromResponse(): void { $data = json_encode(['value' => uniqid('', true)], JSON_THROW_ON_ERROR); diff --git a/tests/Client/Request/AbstractHtmlRequestTest.php b/tests/Client/Request/AbstractHtmlRequestTest.php index 7bdd1f0..15b5f19 100644 --- a/tests/Client/Request/AbstractHtmlRequestTest.php +++ b/tests/Client/Request/AbstractHtmlRequestTest.php @@ -7,27 +7,26 @@ use DigipolisGent\API\Client\Request\AbstractHtmlRequest; use DigipolisGent\API\Client\Request\AcceptType; use DigipolisGent\API\Client\Request\MethodType; -use DigipolisGent\API\Client\Uri\Uri; +use DigipolisGent\API\Client\Uri\UriInterface; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -/** - * @covers \DigipolisGent\API\Client\Request\AbstractHtmlRequest - */ +#[CoversClass(AbstractHtmlRequest::class)] class AbstractHtmlRequestTest extends TestCase { /** * Request has default method and accept header. - * - * @test */ + #[Test] public function requestHasProperMethodAndHeaders(): void { - $request = $this->getMockForAbstractClass( - AbstractHtmlRequest::class, - [new Uri('/test')] - ); + $uri = $this->createMock(UriInterface::class); + $uri->method('getUri')->willReturn('/test'); - $this->assertEquals(MethodType::GET, $request->getMethod()); - $this->assertEquals([AcceptType::HTML], $request->getHeader('Accept')); + $request = new class ($uri) extends AbstractHtmlRequest { + }; + + $this->assertSame(MethodType::GET, $request->getMethod()); + $this->assertSame([AcceptType::HTML], $request->getHeader('Accept')); } } diff --git a/tests/Client/Request/AbstractJsonRequestTest.php b/tests/Client/Request/AbstractJsonRequestTest.php index 0e1f2c3..6519bee 100644 --- a/tests/Client/Request/AbstractJsonRequestTest.php +++ b/tests/Client/Request/AbstractJsonRequestTest.php @@ -7,27 +7,24 @@ use DigipolisGent\API\Client\Request\AbstractJsonRequest; use DigipolisGent\API\Client\Request\AcceptType; use DigipolisGent\API\Client\Request\MethodType; -use DigipolisGent\API\Client\Uri\Uri; +use DigipolisGent\API\Client\Uri\UriInterface; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -/** - * @covers \DigipolisGent\API\Client\Request\AbstractJsonRequest - */ +#[CoversClass(\DigipolisGent\API\Client\Request\AbstractJsonRequest::class)] class AbstractJsonRequestTest extends TestCase { - /** - * Request has default method and accept header. - * - * @test - */ + #[Test] public function requestHasProperMethodAndHeaders(): void { - $request = $this->getMockForAbstractClass( - AbstractJsonRequest::class, - [new Uri('/test')] - ); + $uri = $this->createMock(UriInterface::class); + $uri->method('getUri')->willReturn('/test'); - $this->assertEquals(MethodType::GET, $request->getMethod()); - $this->assertEquals([AcceptType::JSON], $request->getHeader('Accept')); + $request = new class ($uri) extends AbstractJsonRequest { + }; + + $this->assertSame(MethodType::GET, $request->getMethod()); + $this->assertSame([AcceptType::JSON], $request->getHeader('Accept')); } } diff --git a/tests/Client/Request/AbstractXmlRequestTest.php b/tests/Client/Request/AbstractXmlRequestTest.php index 243f523..09b9b75 100644 --- a/tests/Client/Request/AbstractXmlRequestTest.php +++ b/tests/Client/Request/AbstractXmlRequestTest.php @@ -7,27 +7,24 @@ use DigipolisGent\API\Client\Request\AbstractXmlRequest; use DigipolisGent\API\Client\Request\AcceptType; use DigipolisGent\API\Client\Request\MethodType; -use DigipolisGent\API\Client\Uri\Uri; +use DigipolisGent\API\Client\Uri\UriInterface; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -/** - * @covers \DigipolisGent\API\Client\Request\AbstractXmlRequest - */ +#[CoversClass(\DigipolisGent\API\Client\Request\AbstractXmlRequest::class)] class AbstractXmlRequestTest extends TestCase { - /** - * Request has default method and accept header. - * - * @test - */ + #[Test] public function requestHasProperMethodAndHeaders(): void { - $request = $this->getMockForAbstractClass( - AbstractXmlRequest::class, - [new Uri('/test')] - ); + $uri = $this->createMock(UriInterface::class); + $uri->method('getUri')->willReturn('/test'); - $this->assertEquals(MethodType::GET, $request->getMethod()); - $this->assertEquals([AcceptType::XML], $request->getHeader('Accept')); + $request = new class ($uri) extends AbstractXmlRequest { + }; + + $this->assertSame(MethodType::GET, $request->getMethod()); + $this->assertSame([AcceptType::XML], $request->getHeader('Accept')); } } diff --git a/tests/Client/Uri/UriTest.php b/tests/Client/Uri/UriTest.php index 9a93d67..5ae5abe 100644 --- a/tests/Client/Uri/UriTest.php +++ b/tests/Client/Uri/UriTest.php @@ -5,18 +5,15 @@ namespace DigipolisGent\Tests\API\Client\Uri; use DigipolisGent\API\Client\Uri\Uri; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -/** - * @covers \DigipolisGent\API\Client\Uri\Uri - */ class UriTest extends TestCase { /** * URI can be created from its URI path. - * - * @test */ + #[Test] public function uriCanBeCreatedFromItsPath(): void { $uri = new Uri('/test');