ldc-arcadia-api/tests/Unit/Presentation/Console/Services/ClientConsoleResolverTest.php

249 lines
9.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Presentation\Console\Services;
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 Application\Security\Usecases\FindClient\FindClient;
use Infra\Adapters\Repositories\InMemoryClientRepository;
use Presentation\Console\Services\ClientConsoleResolver;
use Ramsey\Uuid\Uuid;
use Shared\DDD\Usecase\Builder\UsecaseExecutor;
use Shared\DDD\Usecase\Input;
use Shared\DDD\Usecase\Presenter;
use Shared\DDD\Usecase\Usecase;
use Symfony\Component\Console\Completion\CompletionSuggestions;
covers(ClientConsoleResolver::class);
describe('ClientConsoleResolver Unit Tests', function (): void {
$repository = new InMemoryClientRepository();
$findClient = new FindClient($repository);
$executor = new class () implements UsecaseExecutor
{
#[\Override]
public function execute(Usecase $usecase, Input $input, ?Presenter $presenter = null): object
{
$output = $usecase->execute($input);
return $presenter !== null ? $presenter->present($output) : $output;
}
};
beforeEach(function () use ($repository): void {
$repository->clear();
});
it('provides shell autocompletion suggestions from directory', function () use ($repository, $findClient, $executor): void {
// GIVEN
$client1 = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Production Gateway'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('a', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('1', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$client2 = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Staging Hub'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('b', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('2', 86)),
isActive: false,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client1, $client2);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
$suggestions = new CompletionSuggestions();
// WHEN
$resolver->complete($suggestions);
// THEN
$values = $suggestions->getValueSuggestions();
expect($values)->toHaveCount(2)
->and($values[0]->getValue())->toBe('arcadia_id_' . str_repeat('a', 32))
->and($values[0]->getDescription())->toBe('Production Gateway (Active)')
->and($values[1]->getValue())->toBe('arcadia_id_' . str_repeat('b', 32))
->and($values[1]->getDescription())->toBe('Staging Hub (Inactive)');
});
it('returns default search options when query is empty', function () use ($repository, $findClient, $executor): void {
// GIVEN
$client = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('App Alpha'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('c', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('3', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN
$options = $resolver->searchOptions(' ');
// THEN
expect($options)->toHaveKey('arcadia_id_' . str_repeat('c', 32))
->and($options['arcadia_id_' . str_repeat('c', 32)])->toBe('App Alpha (Active) - arcadia_id_' . str_repeat('c', 32));
});
it('returns searched options when query is provided', function () use ($repository, $findClient, $executor): void {
// GIVEN
$client = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Beta Portal'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('d', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('4', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN
$options = $resolver->searchOptions('beta');
// THEN
expect($options)->toHaveKey('arcadia_id_' . str_repeat('d', 32))
->and($options['arcadia_id_' . str_repeat('d', 32)])->toBe('Beta Portal (Active) - arcadia_id_' . str_repeat('d', 32));
});
it('returns empty array when search options encounter an exception', function () use ($repository, $findClient): void {
// GIVEN
$failingExecutor = new class () implements UsecaseExecutor
{
#[\Override]
public function execute(Usecase $usecase, Input $input, ?Presenter $presenter = null): object
{
throw new \RuntimeException('Execution failed');
}
};
$resolver = new ClientConsoleResolver($repository, $findClient, $failingExecutor);
// WHEN
$options = $resolver->searchOptions('broken');
// THEN
expect($options)->toBe([]);
});
it('finds a client by identifier pattern embedded in string', function () use ($repository, $findClient, $executor): void {
// GIVEN
$identifier = 'arcadia_id_' . str_repeat('e', 32);
$client = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Target Client'),
identifier: new ClientIdentifier($identifier),
secret: new ClientSecret('arcadia_sec_' . str_repeat('5', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN
$found = $resolver->find("Selected: {$identifier} (Active)");
// THEN
expect($found)->not->toBeNull()
->and($found->identifier->value)->toBe($identifier);
});
it('finds a client by valid UUIDv7 string', function () use ($repository, $findClient, $executor): void {
// GIVEN
$uuid7 = Uuid::uuid7();
$client = Client::hydrate(
id: new ClientId($uuid7),
name: new ClientName('UUID Client'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('f', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('6', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN
$found = $resolver->find($uuid7->toString());
// THEN
expect($found)->not->toBeNull()
->and($found->id->value)->toBe($uuid7->toString());
});
it('finds a client by exact search match when single result', function () use ($repository, $findClient, $executor): void {
// GIVEN
$client = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Unique App'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('1', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('7', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN
$found = $resolver->find('Unique App');
// THEN
expect($found)->not->toBeNull()
->and($found->name->value)->toBe('Unique App');
});
it('finds a client among multiple search results by matching exact name', function () use ($repository, $findClient, $executor): void {
// GIVEN
$client1 = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Payment Service'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('2', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('8', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$client2 = Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Payment Gateway'),
identifier: new ClientIdentifier('arcadia_id_' . str_repeat('3', 32)),
secret: new ClientSecret('arcadia_sec_' . str_repeat('9', 86)),
isActive: true,
updatedAt: new \DateTimeImmutable(),
);
$repository->add($client1, $client2);
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN
$found = $resolver->find('Payment Service');
// THEN
expect($found)->not->toBeNull()
->and($found->name->value)->toBe('Payment Service');
});
it('returns null when input is empty or client not found', function () use ($repository, $findClient, $executor): void {
// GIVEN
$resolver = new ClientConsoleResolver($repository, $findClient, $executor);
// WHEN & THEN
expect($resolver->find(''))->toBeNull()
->and($resolver->find(' '))->toBeNull()
->and($resolver->find('non-existent-app'))->toBeNull();
});
});