2026-08-18 12:03:25 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Tests\Unit\Application\Security\Usecases\ToggleClientStatus;
|
|
|
|
|
|
|
|
|
|
use Application\Security\Events\ClientStatusToggled;
|
|
|
|
|
use Application\Security\Exceptions\ClientNotFoundException;
|
|
|
|
|
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\ToggleClientStatus\Input;
|
|
|
|
|
use Application\Security\Usecases\ToggleClientStatus\Output;
|
2026-08-18 16:56:15 +02:00
|
|
|
use Application\Security\Usecases\ToggleClientStatus\SetClientStatus;
|
2026-08-18 12:03:25 +02:00
|
|
|
use Infra\Adapters\Repositories\InMemoryClientRepository;
|
|
|
|
|
use Mockery;
|
|
|
|
|
use Ramsey\Uuid\Uuid;
|
|
|
|
|
use Shared\DDD\Event\DomainEventDispatcher;
|
|
|
|
|
|
|
|
|
|
covers(
|
2026-08-18 16:56:15 +02:00
|
|
|
SetClientStatus::class,
|
2026-08-18 12:03:25 +02:00
|
|
|
Input::class,
|
|
|
|
|
Output::class,
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-18 16:56:15 +02:00
|
|
|
describe('SetClientStatus Usecase', function (): void {
|
2026-08-18 12:03:25 +02:00
|
|
|
$repository = new InMemoryClientRepository();
|
|
|
|
|
|
|
|
|
|
beforeEach(function () use ($repository): void {
|
|
|
|
|
$repository->clear();
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-18 16:56:15 +02:00
|
|
|
it('deactivates an active client and dispatches event', function () use ($repository): void {
|
2026-08-18 12:03:25 +02:00
|
|
|
// GIVEN
|
|
|
|
|
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
|
2026-08-18 16:56:15 +02:00
|
|
|
$usecase = new SetClientStatus($repository, $dispatcher);
|
2026-08-18 12:03:25 +02:00
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::uuid7());
|
|
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('a', 32));
|
2026-08-18 16:56:15 +02:00
|
|
|
$name = new ClientName('Test Gateway 1');
|
2026-08-18 12:03:25 +02:00
|
|
|
$secret = new ClientSecret('arcadia_sec_' . str_repeat('b', 86));
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: $name,
|
|
|
|
|
identifier: $identifier,
|
|
|
|
|
secret: $secret,
|
|
|
|
|
isActive: true,
|
|
|
|
|
updatedAt: new \DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$repository->add($client);
|
|
|
|
|
|
|
|
|
|
$dispatcher->expects('dispatch')
|
|
|
|
|
->once()
|
|
|
|
|
->withArgs(fn ($event): bool => $event instanceof ClientStatusToggled
|
|
|
|
|
&& $event->clientId->equals($clientId)
|
|
|
|
|
&& $event->isActive === false);
|
|
|
|
|
|
2026-08-18 16:56:15 +02:00
|
|
|
$input = new Input($identifier, desiredStatus: false);
|
2026-08-18 12:03:25 +02:00
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$result = $usecase->execute($input);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($result)->toBeInstanceOf(Output::class)
|
|
|
|
|
->and($result->clientId)->toBe($clientId)
|
|
|
|
|
->and($result->clientIdentifier)->toBe($identifier)
|
|
|
|
|
->and($result->clientName)->toBe($name)
|
|
|
|
|
->and($result->isActive)->toBeFalse();
|
|
|
|
|
|
|
|
|
|
$saved = $repository->find($clientId);
|
|
|
|
|
expect($saved?->isActive)->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-18 16:56:15 +02:00
|
|
|
it('activates an inactive client and dispatches event', function () use ($repository): void {
|
2026-08-18 12:03:25 +02:00
|
|
|
// GIVEN
|
|
|
|
|
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
|
2026-08-18 16:56:15 +02:00
|
|
|
$usecase = new SetClientStatus($repository, $dispatcher);
|
2026-08-18 12:03:25 +02:00
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::uuid7());
|
|
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('a', 32));
|
|
|
|
|
$name = new ClientName('Inactive Client');
|
|
|
|
|
$secret = new ClientSecret('arcadia_sec_' . str_repeat('b', 86));
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: $name,
|
|
|
|
|
identifier: $identifier,
|
|
|
|
|
secret: $secret,
|
|
|
|
|
isActive: false,
|
|
|
|
|
updatedAt: new \DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$repository->add($client);
|
|
|
|
|
|
|
|
|
|
$dispatcher->expects('dispatch')
|
|
|
|
|
->once()
|
|
|
|
|
->withArgs(fn ($event): bool => $event instanceof ClientStatusToggled
|
|
|
|
|
&& $event->clientId->equals($clientId)
|
|
|
|
|
&& $event->isActive === true);
|
|
|
|
|
|
2026-08-18 16:56:15 +02:00
|
|
|
$input = new Input($identifier, desiredStatus: true);
|
2026-08-18 12:03:25 +02:00
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$result = $usecase->execute($input);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($result)->toBeInstanceOf(Output::class)
|
|
|
|
|
->and($result->isActive)->toBeTrue();
|
|
|
|
|
|
|
|
|
|
$saved = $repository->find($clientId);
|
|
|
|
|
expect($saved?->isActive)->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('throws ClientNotFoundException when client does not exist', function () use ($repository): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
|
2026-08-18 16:56:15 +02:00
|
|
|
$usecase = new SetClientStatus($repository, $dispatcher);
|
2026-08-18 12:03:25 +02:00
|
|
|
|
2026-08-18 16:56:15 +02:00
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('9', 32));
|
|
|
|
|
$input = new Input($identifier, desiredStatus: true);
|
2026-08-18 12:03:25 +02:00
|
|
|
|
|
|
|
|
// WHEN & THEN
|
|
|
|
|
expect(fn () => $usecase->execute($input))
|
|
|
|
|
->toThrow(ClientNotFoundException::class);
|
|
|
|
|
});
|
2026-08-18 16:56:15 +02:00
|
|
|
|
|
|
|
|
it('does not dispatch event when status is already the desired value', function () use ($repository): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
|
|
|
|
|
$usecase = new SetClientStatus($repository, $dispatcher);
|
|
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::uuid7());
|
|
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('a', 32));
|
|
|
|
|
$name = new ClientName('Already Active Client');
|
|
|
|
|
$secret = new ClientSecret('arcadia_sec_' . str_repeat('b', 86));
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: $name,
|
|
|
|
|
identifier: $identifier,
|
|
|
|
|
secret: $secret,
|
|
|
|
|
isActive: true,
|
|
|
|
|
updatedAt: new \DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$repository->add($client);
|
|
|
|
|
|
|
|
|
|
$dispatcher->expects('dispatch')->never();
|
|
|
|
|
|
|
|
|
|
$input = new Input($identifier, desiredStatus: true);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$result = $usecase->execute($input);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($result)->toBeInstanceOf(Output::class)
|
|
|
|
|
->and($result->isActive)->toBeTrue();
|
|
|
|
|
|
|
|
|
|
$saved = $repository->find($clientId);
|
|
|
|
|
expect($saved?->isActive)->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deactivates an active client when desiredStatus is false', function () use ($repository): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
|
|
|
|
|
$usecase = new SetClientStatus($repository, $dispatcher);
|
|
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::uuid7());
|
|
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('a', 32));
|
|
|
|
|
$name = new ClientName('Test Gateway 3');
|
|
|
|
|
$secret = new ClientSecret('arcadia_sec_' . str_repeat('b', 86));
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: $name,
|
|
|
|
|
identifier: $identifier,
|
|
|
|
|
secret: $secret,
|
|
|
|
|
isActive: true,
|
|
|
|
|
updatedAt: new \DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$repository->add($client);
|
|
|
|
|
|
|
|
|
|
$dispatcher->expects('dispatch')
|
|
|
|
|
->once()
|
|
|
|
|
->withArgs(fn ($event): bool => $event instanceof ClientStatusToggled
|
|
|
|
|
&& $event->clientId->equals($clientId)
|
|
|
|
|
&& $event->isActive === false);
|
|
|
|
|
|
|
|
|
|
$input = new Input($identifier, desiredStatus: false);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$result = $usecase->execute($input);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($result)->toBeInstanceOf(Output::class)
|
|
|
|
|
->and($result->isActive)->toBeFalse();
|
|
|
|
|
|
|
|
|
|
$saved = $repository->find($clientId);
|
|
|
|
|
expect($saved?->isActive)->toBeFalse();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('activates an inactive client when desiredStatus is true', function () use ($repository): void {
|
|
|
|
|
// GIVEN
|
|
|
|
|
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
|
|
|
|
|
$usecase = new SetClientStatus($repository, $dispatcher);
|
|
|
|
|
|
|
|
|
|
$clientId = new ClientId(Uuid::uuid7());
|
|
|
|
|
$identifier = new ClientIdentifier('arcadia_id_' . str_repeat('a', 32));
|
|
|
|
|
$name = new ClientName('Test Gateway 2');
|
|
|
|
|
$secret = new ClientSecret('arcadia_sec_' . str_repeat('b', 86));
|
|
|
|
|
|
|
|
|
|
$client = Client::hydrate(
|
|
|
|
|
id: $clientId,
|
|
|
|
|
name: $name,
|
|
|
|
|
identifier: $identifier,
|
|
|
|
|
secret: $secret,
|
|
|
|
|
isActive: false,
|
|
|
|
|
updatedAt: new \DateTimeImmutable(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$repository->add($client);
|
|
|
|
|
|
|
|
|
|
$dispatcher->expects('dispatch')
|
|
|
|
|
->once()
|
|
|
|
|
->withArgs(fn ($event): bool => $event instanceof ClientStatusToggled
|
|
|
|
|
&& $event->clientId->equals($clientId)
|
|
|
|
|
&& $event->isActive === true);
|
|
|
|
|
|
|
|
|
|
$input = new Input($identifier, desiredStatus: true);
|
|
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
|
$result = $usecase->execute($input);
|
|
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
|
expect($result)->toBeInstanceOf(Output::class)
|
|
|
|
|
->and($result->isActive)->toBeTrue();
|
|
|
|
|
|
|
|
|
|
$saved = $repository->find($clientId);
|
|
|
|
|
expect($saved?->isActive)->toBeTrue();
|
|
|
|
|
});
|
2026-08-18 12:03:25 +02:00
|
|
|
});
|