2026-08-18 12:03:29 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Tests\Integration\Infra\Adapters\Repositories;
|
|
|
|
|
|
2026-08-20 09:48:10 +02:00
|
|
|
use Application\Security\Exceptions\ClientNotFoundException;
|
2026-08-18 12:03:29 +02:00
|
|
|
use Application\Security\Models\Entities\Client;
|
|
|
|
|
use Application\Security\Models\ValueObjects\ClientIdentifier;
|
|
|
|
|
use Application\Security\Models\ValueObjects\ClientName;
|
|
|
|
|
use Application\Security\Models\ValueObjects\ClientSecret;
|
|
|
|
|
use Application\Security\Models\ValueObjects\Entities\ClientId;
|
|
|
|
|
use DateTimeImmutable;
|
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
use Infra\Adapters\Repositories\ApiClientRepository;
|
|
|
|
|
use Infra\Database\Models\ApiClient;
|
|
|
|
|
use Ramsey\Uuid\Uuid;
|
|
|
|
|
use Shared\Pagination;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
|
covers(ApiClientRepository::class);
|
|
|
|
|
|
|
|
|
|
beforeEach(function (): void {
|
|
|
|
|
ApiClient::query()->delete();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('ApiClientRepository Integration Tests', function (): void {
|
|
|
|
|
it('persists a new client into database', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
$clientId = new ClientId(Uuid::uuid7());
|
|
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('a', 32));
|
|
|
|
|
$secret = new ClientSecret('arcadia_sec_' . str_repeat('b', 86));
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: new ClientName('New API Client'),
|
|
|
|
|
identifier: $identifier,
|
|
|
|
|
secret: $secret,
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdAt: new DateTimeImmutable(),
|
|
|
|
|
updatedAt: new DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$repository->persist($client);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
$found = $repository->find($clientId);
|
|
|
|
|
expect($found)->not->toBeNull()
|
|
|
|
|
->and($found->id->value)->toBe($clientId->value)
|
|
|
|
|
->and($found->name->value)->toBe('New API Client')
|
|
|
|
|
->and($found->identifier->value)->toBe($identifier->value)
|
|
|
|
|
->and($found->secret->value)->toBe($secret->value)
|
|
|
|
|
->and($found->isActive)->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('updates an existing client in database on persist', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
$rawId = Uuid::uuid7()->toString();
|
|
|
|
|
$identifier = 'arcadia_id_' . str_repeat('a', 32);
|
|
|
|
|
$oldSecret = 'arcadia_sec_' . str_repeat('1', 86);
|
|
|
|
|
$newSecret = 'arcadia_sec_' . str_repeat('2', 86);
|
|
|
|
|
|
2026-08-20 12:08:15 +02:00
|
|
|
ApiClient::factory()->create([
|
2026-08-18 12:03:29 +02:00
|
|
|
'id' => $rawId,
|
|
|
|
|
'name' => 'Initial Client',
|
2026-08-18 17:31:29 +02:00
|
|
|
'slug' => 'initial-client',
|
2026-08-18 12:03:29 +02:00
|
|
|
'identifier' => $identifier,
|
|
|
|
|
'secret' => $oldSecret,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::fromString($rawId));
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: new ClientName('Initial Client'),
|
|
|
|
|
identifier: new ClientIdentifier($identifier),
|
|
|
|
|
secret: new ClientSecret($oldSecret),
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdAt: new DateTimeImmutable(),
|
|
|
|
|
updatedAt: new DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$client->regenerateSecret(new ClientSecret($newSecret));
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$repository->persist($client);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
$found = $repository->find($clientId);
|
|
|
|
|
expect($found)->not->toBeNull()
|
|
|
|
|
->and($found->secret->value)->toBe($newSecret);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('finds a client by its ClientId', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
$rawId = Uuid::uuid7()->toString();
|
|
|
|
|
$identifier = 'arcadia_id_' . str_repeat('a', 32);
|
|
|
|
|
$secret = 'arcadia_sec_' . str_repeat('b', 86);
|
|
|
|
|
|
2026-08-20 12:08:15 +02:00
|
|
|
ApiClient::factory()->create([
|
2026-08-18 12:03:29 +02:00
|
|
|
'id' => $rawId,
|
|
|
|
|
'name' => 'Existing Client',
|
2026-08-18 17:31:29 +02:00
|
|
|
'slug' => 'existing-client',
|
2026-08-18 12:03:29 +02:00
|
|
|
'identifier' => $identifier,
|
|
|
|
|
'secret' => $secret,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::fromString($rawId));
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$client = $repository->find($clientId);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($client)->not->toBeNull()
|
|
|
|
|
->and($client->id->value)->toBe($rawId)
|
|
|
|
|
->and($client->name->value)->toBe('Existing Client')
|
|
|
|
|
->and($client->identifier->value)->toBe($identifier);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('finds a client by its ClientIdentifier', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
$rawId = Uuid::uuid7()->toString();
|
|
|
|
|
$identifier = 'arcadia_id_' . str_repeat('a', 32);
|
|
|
|
|
$secret = 'arcadia_sec_' . str_repeat('b', 86);
|
|
|
|
|
|
2026-08-20 12:08:15 +02:00
|
|
|
ApiClient::factory()->create([
|
2026-08-18 12:03:29 +02:00
|
|
|
'id' => $rawId,
|
|
|
|
|
'name' => 'Searchable Client',
|
2026-08-18 17:31:29 +02:00
|
|
|
'slug' => 'searchable-client',
|
2026-08-18 12:03:29 +02:00
|
|
|
'identifier' => $identifier,
|
|
|
|
|
'secret' => $secret,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$clientIdentifier = new ClientIdentifier($identifier);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$client = $repository->findByIdentifier($clientIdentifier);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($client)->not->toBeNull()
|
|
|
|
|
->and($client->identifier->value)->toBe($identifier)
|
|
|
|
|
->and($client->name->value)->toBe('Searchable Client');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deletes an existing client from database', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
/** @var \Tests\TestCase $this */
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
$rawId = Uuid::uuid7()->toString();
|
|
|
|
|
$identifier = 'arcadia_id_' . str_repeat('a', 32);
|
|
|
|
|
$secret = 'arcadia_sec_' . str_repeat('b', 86);
|
|
|
|
|
|
2026-08-20 12:08:15 +02:00
|
|
|
ApiClient::factory()->create([
|
2026-08-18 12:03:29 +02:00
|
|
|
'id' => $rawId,
|
|
|
|
|
'name' => 'Client to Delete',
|
2026-08-18 17:31:29 +02:00
|
|
|
'slug' => 'client-to-delete',
|
2026-08-18 12:03:29 +02:00
|
|
|
'identifier' => $identifier,
|
|
|
|
|
'secret' => $secret,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: new ClientId(Uuid::fromString($rawId)),
|
|
|
|
|
name: new ClientName('Client to Delete'),
|
|
|
|
|
identifier: new ClientIdentifier($identifier),
|
|
|
|
|
secret: new ClientSecret($secret),
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdAt: new DateTimeImmutable(),
|
|
|
|
|
updatedAt: new DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$repository->delete($client);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
$this->assertDatabaseMissing('api_clients', [
|
|
|
|
|
'id' => $rawId,
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-20 09:48:10 +02:00
|
|
|
it('throws ClientNotFoundException when deleting non-existent client', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: new ClientId(Uuid::uuid7()),
|
|
|
|
|
name: new ClientName('Non Existent'),
|
|
|
|
|
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('z', 32)),
|
|
|
|
|
secret: new ClientSecret('arcadia_sec_' . str_repeat('z', 86)),
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdAt: new DateTimeImmutable(),
|
|
|
|
|
updatedAt: new DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// WHEN & THEN
|
|
|
|
|
expect(fn () => $repository->delete($client))
|
|
|
|
|
->toThrow(ClientNotFoundException::class);
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-18 12:03:29 +02:00
|
|
|
it('finds all clients with pagination', function (): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$repository = new ApiClientRepository();
|
|
|
|
|
|
|
|
|
|
for ($i = 1; $i <= 5; $i++) {
|
2026-08-20 12:08:15 +02:00
|
|
|
ApiClient::factory()->create([
|
2026-08-18 12:03:29 +02:00
|
|
|
'id' => Uuid::uuid7()->toString(),
|
|
|
|
|
'name' => "Client $i",
|
2026-08-18 17:31:29 +02:00
|
|
|
'slug' => "client-$i",
|
2026-08-18 12:03:29 +02:00
|
|
|
'identifier' => 'arcadia_id_' . str_pad((string) $i, 32, '0', STR_PAD_LEFT),
|
|
|
|
|
'secret' => 'arcadia_sec_' . str_pad((string) $i, 86, '0', STR_PAD_LEFT),
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$pagination = Pagination::ofPage(pageNumber: 1, pageSize: 2);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$paginated = $repository->findAll($pagination);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($paginated->getTotal())->toBe(5)
|
|
|
|
|
->and(count($paginated->getItems()))->toBe(2)
|
|
|
|
|
->and($paginated->getPagination()->pageNumber())->toBe(1);
|
|
|
|
|
});
|
|
|
|
|
});
|