diff --git a/examples/custom.php b/examples/custom.php new file mode 100644 index 0000000..b1cf4aa --- /dev/null +++ b/examples/custom.php @@ -0,0 +1,49 @@ +custom(); + +$response = $custom->query(new CustomQuery( + '/admin/custom/endpoint', + new Criteria([ + 'key' => 'value', + 'bool' => true, + ]) +)); + +$custom->command(new CustomCommand( + '/admin/custom/endpoint', + Method::POST, + [ + 'key' => 'value', + 'bool' => true, + ] +)); + +$custom->request( + Method::GET->value, + '/admin/custom/endpoint', + [ + 'headers' => [ + 'Custom-Header' => 'Custom-Value', + ], + 'json' => [ + 'custom' => 'body', + ], + ], +); diff --git a/src/Http/Client.php b/src/Http/Client.php index 5eb698f..cd89934 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -4,7 +4,6 @@ namespace Fschmtt\Keycloak\Http; -use DateTime; use Fschmtt\Keycloak\Keycloak; use Fschmtt\Keycloak\OAuth\TokenStorageInterface; use GuzzleHttp\ClientInterface; @@ -35,6 +34,7 @@ public function request(string $method, string $path = '', array $options = []): 'base_uri' => $this->keycloak->getBaseUrl(), 'headers' => [ 'Authorization' => 'Bearer ' . $this->tokenStorage->retrieveAccessToken()->toString(), + 'Content-Type' => 'application/json', ], ]; @@ -49,7 +49,9 @@ public function request(string $method, string $path = '', array $options = []): public function isAuthorized(): bool { - return $this->tokenStorage->retrieveAccessToken() !== null && !$this->tokenStorage->retrieveAccessToken()->isExpired(new DateTime()); + $accessToken = $this->tokenStorage->retrieveAccessToken(); + + return $accessToken && !$accessToken->isExpired(new \DateTimeImmutable()); } private function authorize(): void diff --git a/src/Http/Command.php b/src/Http/Command.php index 7d61e26..bbd3c87 100644 --- a/src/Http/Command.php +++ b/src/Http/Command.php @@ -12,9 +12,10 @@ class Command public function __construct( private readonly string $path, private readonly Method $method, - /** @var array */ + /** @var array $parameters */ private readonly array $parameters = [], - private readonly Representation|Collection|null $payload = null, + /** @var Representation|Collection|array|null $payload */ + private readonly Representation|Collection|array|null $payload = null, ) { } @@ -39,7 +40,10 @@ public function getPath(): string ); } - public function getPayload(): Representation|Collection|null + /** + * @return Representation|Collection|array|null + */ + public function getPayload(): Representation|Collection|array|null { return $this->payload; } diff --git a/src/Http/CommandExecutor.php b/src/Http/CommandExecutor.php index f9459d6..19ab842 100644 --- a/src/Http/CommandExecutor.php +++ b/src/Http/CommandExecutor.php @@ -31,19 +31,25 @@ public function executeCommand(Command $command): void protected function prepareBody(Command $command): ?string { - if ($command->getPayload() === null) { + $payload = $command->getPayload(); + + if ($payload === null) { return null; } $jsonEncoder = new JsonEncoder(); - if ($command->getPayload() instanceof Representation) { - return $jsonEncoder->encode($this->propertyFilter->filter($command->getPayload())); + if (is_array($payload)) { + return $jsonEncoder->encode($payload); + } + + if ($payload instanceof Representation) { + return $jsonEncoder->encode($this->propertyFilter->filter($payload)); } $representations = []; - foreach ($command->getPayload() as $representation) { + foreach ($payload as $representation) { $representations[] = $this->propertyFilter->filter($representation); } diff --git a/src/Http/CustomCommand.php b/src/Http/CustomCommand.php new file mode 100644 index 0000000..82e6fc2 --- /dev/null +++ b/src/Http/CustomCommand.php @@ -0,0 +1,22 @@ +|null $payload + */ + public function __construct( + string $path, + Method $method, + Representation|Collection|array|null $payload = null, + ) { + parent::__construct($path, $method, [], $payload); + } +} diff --git a/src/Http/CustomQuery.php b/src/Http/CustomQuery.php new file mode 100644 index 0000000..2e001f9 --- /dev/null +++ b/src/Http/CustomQuery.php @@ -0,0 +1,16 @@ +commandExecutor, $this->queryExecutor); } + public function custom(): Custom + { + return new Custom($this->commandExecutor, $this->queryExecutor, $this->client); + } + private function fetchVersion(): void { if ($this->version) { diff --git a/src/Resource/Custom.php b/src/Resource/Custom.php new file mode 100644 index 0000000..ac33410 --- /dev/null +++ b/src/Resource/Custom.php @@ -0,0 +1,41 @@ +queryExecutor->executeQuery($query); + } + + public function command(CustomCommand $command): void + { + $this->commandExecutor->executeCommand($command); + } + + /** + * @param array $options + */ + public function request(string $method, string $path, array $options = []): ResponseInterface + { + return $this->client->request($method, $path, $options); + } +} diff --git a/src/Resource/Resource.php b/src/Resource/Resource.php index d8b25d4..d74dcfb 100644 --- a/src/Resource/Resource.php +++ b/src/Resource/Resource.php @@ -6,9 +6,10 @@ use Fschmtt\Keycloak\Http\CommandExecutor; use Fschmtt\Keycloak\Http\QueryExecutor; -use PHPUnit\Framework\Attributes\IgnoreClassForCodeCoverage; -#[IgnoreClassForCodeCoverage(self::class)] +/** + * @codeCoverageIgnore + */ abstract class Resource { public function __construct( diff --git a/tests/Unit/Http/CommandExecutorTest.php b/tests/Unit/Http/CommandExecutorTest.php index ecd413f..e86afd4 100644 --- a/tests/Unit/Http/CommandExecutorTest.php +++ b/tests/Unit/Http/CommandExecutorTest.php @@ -99,4 +99,35 @@ public function testCallsClientWithBodyIfCommandHasCollection(): void $executor = new CommandExecutor($client, new PropertyFilter()); $executor->executeCommand($command); } + + public function testCallsClientWithBodyIfCommandHasArrayPayload(): void + { + $payload = [ + 'custom' => 'payload', + ]; + + $command = new Command( + '/path/to/resource', + Method::PUT, + [], + $payload, + ); + + $client = $this->createMock(Client::class); + $client->expects(static::once()) + ->method('request') + ->with( + Method::PUT->value, + '/path/to/resource', + [ + 'body' => (new JsonEncoder())->encode($payload), + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + + $executor = new CommandExecutor($client, new PropertyFilter()); + $executor->executeCommand($command); + } } diff --git a/tests/Unit/Http/CommandTest.php b/tests/Unit/Http/CommandTest.php index 6fe9801..6850a7a 100644 --- a/tests/Unit/Http/CommandTest.php +++ b/tests/Unit/Http/CommandTest.php @@ -39,6 +39,18 @@ public function testCanGetCollectionPayload(): void ); } + public function testCanGetArrayPayload(): void + { + $payload = [ + 'custom' => 'payload', + ]; + + static::assertSame( + $payload, + (new Command('/path', Method::POST, [], $payload))->getPayload() + ); + } + public function testSubstitutesParametersInPath(): void { static::assertSame( diff --git a/tests/Unit/Http/CustomCommandTest.php b/tests/Unit/Http/CustomCommandTest.php new file mode 100644 index 0000000..77143ac --- /dev/null +++ b/tests/Unit/Http/CustomCommandTest.php @@ -0,0 +1,42 @@ +getPath()); + static::assertSame(Method::PUT, $customCommand->getMethod()); + static::assertNull($customCommand->getPayload()); + } + + public function testCustomCommandWithRepresentationPayload(): void + { + $representation = new Representation(); + + $customCommand = new CustomCommand( + '/path/to/resource', + Method::PUT, + $representation, + ); + + static::assertSame('/path/to/resource', $customCommand->getPath()); + static::assertSame(Method::PUT, $customCommand->getMethod()); + static::assertSame($representation, $customCommand->getPayload()); + } +} diff --git a/tests/Unit/Http/CustomQueryTest.php b/tests/Unit/Http/CustomQueryTest.php new file mode 100644 index 0000000..f766159 --- /dev/null +++ b/tests/Unit/Http/CustomQueryTest.php @@ -0,0 +1,38 @@ +getPath()); + static::assertEquals(Method::GET, $customQuery->getMethod()); + static::assertSame('array', $customQuery->getReturnType()); + } + + public function testCustomCommandWithRepresentationReturnType(): void + { + $customQuery = new CustomQuery( + '/path/to/resource', + returnType: User::class, + ); + + static::assertSame('/path/to/resource', $customQuery->getPath()); + static::assertEquals(Method::GET, $customQuery->getMethod()); + static::assertSame(User::class, $customQuery->getReturnType()); + } +} diff --git a/tests/Unit/Resource/CustomTest.php b/tests/Unit/Resource/CustomTest.php new file mode 100644 index 0000000..7eb7177 --- /dev/null +++ b/tests/Unit/Resource/CustomTest.php @@ -0,0 +1,123 @@ +createMock(QueryExecutor::class); + $queryExecutor->expects(static::once()) + ->method('executeQuery') + ->with(new CustomQuery( + '/admin/my/custom-endpoint', + new Criteria([ + 'foo' => 'bar', + ]), + )) + ->willReturn([ + 'return' => 'value', + ]); + + $custom = new Custom( + $this->createMock(CommandExecutor::class), + $queryExecutor, + $this->createMock(Client::class), + ); + + $result = $custom->query(new CustomQuery( + '/admin/my/custom-endpoint', + new Criteria([ + 'foo' => 'bar', + ]), + )); + + static::assertSame([ + 'return' => 'value', + ], $result); + } + + public function testRunsCustomCommand(): void + { + $commandExecutor = $this->createMock(CommandExecutor::class); + $commandExecutor->expects(static::once()) + ->method('executeCommand') + ->with(new CustomCommand( + '/admin/my/custom-endpoint', + Method::POST, + [ + 'foo' => 'bar', + ], + )); + + $custom = new Custom( + $commandExecutor, + $this->createMock(QueryExecutor::class), + $this->createMock(Client::class), + ); + + $custom->command(new CustomCommand( + '/admin/my/custom-endpoint', + Method::POST, + [ + 'foo' => 'bar', + ], + )); + } + + public function testRunsCustomRequest(): void + { + $client = $this->createMock(Client::class); + $client->expects(static::once()) + ->method('request') + ->with( + 'POST', + '/admin/my/custom-endpoint', + [ + 'headers' => [ + 'Custom-Header' => 'custom-value', + ], + ], + ) + ->willReturn(new Response( + 200, + [], + json_encode([ + 'return' => 'value', + ]), + )); + + $custom = new Custom( + $this->createMock(CommandExecutor::class), + $this->createMock(QueryExecutor::class), + $client, + ); + + $response = $custom->request( + 'POST', + '/admin/my/custom-endpoint', + [ + 'headers' => [ + 'Custom-Header' => 'custom-value', + ], + ] + ); + + static::assertSame(200, $response->getStatusCode()); + } +}