feat(infra): add in-memory directory and repository doubles with unit tests and refactor usecase tests to pure black-box

This commit is contained in:
Jérémy Munsch 2026-08-18 11:46:43 +02:00
parent 1dcf0a8cdf
commit 5c10d51fba
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
16 changed files with 1201 additions and 111 deletions

View file

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Employee;
use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
use Domain\Models\ValueObjects\Employees\EmployeeFilter;
use Domain\Models\ValueObjects\Employees\EmployeeSortColumn;
use Domain\Models\ValueObjects\Employees\EmployeeSorter;
use Domain\Ports\Directories\EmployeeDirectory;
use Shared\Paginated;
use Shared\Pagination;
final class InMemoryEmployeeDirectory implements EmployeeDirectory
{
/** @var array<string, Employee> */
private array $employees = [];
public function add(Employee ...$employees): self
{
foreach ($employees as $employee) {
$this->employees[$employee->id()->value] = $employee;
}
return $this;
}
public function clear(): void
{
$this->employees = [];
}
#[\Override]
public function find(ArcadiaEmployeeId $id): ?Employee
{
return $this->employees[$id->value] ?? null;
}
#[\Override]
public function findAll(
Pagination $pagination,
EmployeeSorter $sorter,
?EmployeeFilter $filter = null,
): Paginated {
$filtered = array_values($this->employees);
if ($filter !== null) {
$filtered = array_filter($filtered, static function (Employee $employee) use ($filter): bool {
if ($filter->establishment !== null && $employee->establishment->value !== $filter->establishment->value) {
return false;
}
if (! empty($filter->sites)) {
$siteValues = array_map(static fn ($site): string => $site->value, $filter->sites);
if (! in_array($employee->site->value, $siteValues, true)) {
return false;
}
}
if ($filter->service !== null && $employee->service->value !== $filter->service->value) {
return false;
}
if ($filter->since !== null && $employee->startDate < $filter->since) {
return false;
}
return true;
});
$filtered = array_values($filtered);
}
usort($filtered, static function (Employee $a, Employee $b) use ($sorter): int {
$cmp = match ($sorter->sortBy) {
EmployeeSortColumn::arcadiaId => strcmp($a->publicEmployeeNumber->value, $b->publicEmployeeNumber->value),
EmployeeSortColumn::startDate => $a->startDate <=> $b->startDate,
EmployeeSortColumn::endDate => $a->endDate <=> $b->endDate,
EmployeeSortColumn::plannedEndDate => $a->plannedEndDate <=> $b->plannedEndDate,
EmployeeSortColumn::createdAt => $a->createdAt <=> $b->createdAt,
EmployeeSortColumn::updatedAt => $a->updatedAt <=> $b->updatedAt,
};
return $sorter->direction === \SortDirection::Ascending ? $cmp : -$cmp;
});
$total = count($filtered);
$offset = $pagination->from();
$limit = $pagination->pageSize();
$paged = array_slice($filtered, $offset, $limit);
return new Paginated($pagination, $paged, $total);
}
}

View file

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\Composition\IncoComposition;
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Ports\Directories\IncoCompositionDirectory;
final class InMemoryIncoCompositionDirectory implements IncoCompositionDirectory
{
/** @var array<string, IncoComposition> */
private array $compositions = [];
public function add(IncoComposition ...$compositions): self
{
foreach ($compositions as $composition) {
$this->compositions[$composition->id->value] = $composition;
}
return $this;
}
public function clear(): void
{
$this->compositions = [];
}
#[\Override]
public function find(IncoCompositionId $id): ?IncoComposition
{
return $this->compositions[$id->value] ?? null;
}
#[\Override]
public function findByIncoId(IncoId $incoId): ?IncoComposition
{
foreach ($this->compositions as $composition) {
if ($composition->idInco->equals($incoId)) {
return $composition;
}
}
return null;
}
}

View file

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Ports\Directories\IncoSheetDirectory;
use Shared\Paginated;
use Shared\Pagination;
final class InMemoryIncoSheetDirectory implements IncoSheetDirectory
{
/** @var array<string, IncoSheet> */
private array $sheets = [];
public function add(IncoSheet ...$sheets): self
{
foreach ($sheets as $sheet) {
$this->sheets[(string) $sheet->id->value] = $sheet;
}
return $this;
}
public function clear(): void
{
$this->sheets = [];
}
#[\Override]
public function find(IncoId $id): ?IncoSheet
{
return $this->sheets[(string) $id->value] ?? null;
}
#[\Override]
public function findAll(
Pagination $pagination,
IncoSheetSorter $sorter,
?IncoSheetFilter $filter = null,
): Paginated {
$filtered = array_values($this->sheets);
if ($filter !== null) {
$filtered = array_filter($filtered, static function (IncoSheet $sheet) use ($filter): bool {
if ($filter->since !== null) {
$createdAtMatches = $sheet->createdAt !== null && $sheet->createdAt >= $filter->since;
$updatedAtMatches = $sheet->updatedAt !== null && $sheet->updatedAt >= $filter->since;
if (! $createdAtMatches && ! $updatedAtMatches) {
return false;
}
}
if (! empty($filter->brands) && ! in_array($sheet->brand->code, $filter->brands, true)) {
return false;
}
return true;
});
$filtered = array_values($filtered);
}
usort($filtered, static function (IncoSheet $a, IncoSheet $b) use ($sorter): int {
$cmp = match ($sorter->sortBy) {
IncoSheetSortColumn::incoSheetId => (int) $a->id->value <=> (int) $b->id->value,
IncoSheetSortColumn::createdAt => $a->createdAt <=> $b->createdAt,
IncoSheetSortColumn::updatedAt => $a->updatedAt <=> $b->updatedAt,
};
return $sorter->direction === \SortDirection::Ascending ? $cmp : -$cmp;
});
$total = count($filtered);
$offset = $pagination->from();
$limit = $pagination->pageSize();
$paged = array_slice($filtered, $offset, $limit);
return new Paginated($pagination, $paged, $total);
}
#[\Override]
public function search(
IncoIdQuery $query,
Pagination $pagination,
string ...$brands,
): Paginated {
$regex = '/^' . str_replace('%', '.*', preg_quote($query->value, '/')) . '$/';
$filtered = array_filter($this->sheets, static function (IncoSheet $sheet) use ($regex, $brands): bool {
if (! preg_match($regex, (string) $sheet->id->value)) {
return false;
}
if (! empty($brands) && ! in_array($sheet->brand->code, $brands, true)) {
return false;
}
return true;
});
$filtered = array_values($filtered);
$total = count($filtered);
$offset = $pagination->from();
$limit = $pagination->pageSize();
$paged = array_slice($filtered, $offset, $limit);
return new Paginated($pagination, $paged, $total);
}
}

View file

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoShortSheet;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Ports\Directories\IncoShortSheetDirectory;
use Shared\Paginated;
use Shared\Pagination;
final class InMemoryIncoShortSheetDirectory implements IncoShortSheetDirectory
{
/** @var array<string, IncoShortSheet> */
private array $sheets = [];
public function add(IncoShortSheet ...$sheets): self
{
foreach ($sheets as $sheet) {
$this->sheets[(string) $sheet->id->value] = $sheet;
}
return $this;
}
public function clear(): void
{
$this->sheets = [];
}
#[\Override]
public function find(IncoId $id): ?IncoShortSheet
{
return $this->sheets[(string) $id->value] ?? null;
}
#[\Override]
public function findAll(
Pagination $pagination,
IncoSheetSorter $sorter,
?IncoSheetFilter $filter = null,
): Paginated {
$filtered = array_values($this->sheets);
if ($filter !== null) {
$filtered = array_filter($filtered, static function (IncoShortSheet $sheet) use ($filter): bool {
if ($filter->since !== null) {
$createdAtMatches = $sheet->createdAt !== null && $sheet->createdAt >= $filter->since;
$updatedAtMatches = $sheet->updatedAt !== null && $sheet->updatedAt >= $filter->since;
if (! $createdAtMatches && ! $updatedAtMatches) {
return false;
}
}
return true;
});
$filtered = array_values($filtered);
}
usort($filtered, static function (IncoShortSheet $a, IncoShortSheet $b) use ($sorter): int {
$cmp = match ($sorter->sortBy) {
IncoSheetSortColumn::incoSheetId => (int) $a->id->value <=> (int) $b->id->value,
IncoSheetSortColumn::createdAt => $a->createdAt <=> $b->createdAt,
IncoSheetSortColumn::updatedAt => $a->updatedAt <=> $b->updatedAt,
};
return $sorter->direction === \SortDirection::Ascending ? $cmp : -$cmp;
});
$total = count($filtered);
$offset = $pagination->from();
$limit = $pagination->pageSize();
$paged = array_slice($filtered, $offset, $limit);
return new Paginated($pagination, $paged, $total);
}
#[\Override]
public function search(
IncoIdQuery $query,
Pagination $pagination,
string ...$brands,
): Paginated {
$regex = '/^' . str_replace('%', '.*', preg_quote($query->value, '/')) . '$/';
$filtered = array_filter($this->sheets, static function (IncoShortSheet $sheet) use ($regex): bool {
return (bool) preg_match($regex, (string) $sheet->id->value);
});
$filtered = array_values($filtered);
$total = count($filtered);
$offset = $pagination->from();
$limit = $pagination->pageSize();
$paged = array_slice($filtered, $offset, $limit);
return new Paginated($pagination, $paged, $total);
}
}

View file

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Repositories;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\Entities\ClientId;
use Application\Security\Ports\Repositories\ClientRepository;
use Shared\Paginated;
use Shared\Pagination;
final class InMemoryClientRepository implements ClientRepository
{
/** @var array<string, Client> */
private array $clients = [];
public function add(Client ...$clients): self
{
foreach ($clients as $client) {
$this->clients[$client->id->value] = $client;
}
return $this;
}
public function clear(): void
{
$this->clients = [];
}
#[\Override]
public function find(ClientId $id): ?Client
{
return $this->clients[$id->value] ?? null;
}
#[\Override]
public function persist(Client $client): void
{
$this->clients[$client->id->value] = $client;
}
#[\Override]
public function delete(Client $client): void
{
unset($this->clients[$client->id->value]);
}
#[\Override]
public function findByIdentifier(ClientIdentifier $identifier): ?Client
{
foreach ($this->clients as $client) {
if ($client->identifier->equals($identifier)) {
return $client;
}
}
return null;
}
#[\Override]
public function findAll(Pagination $pagination): Paginated
{
$items = array_values($this->clients);
$total = count($items);
$offset = $pagination->from();
$limit = $pagination->pageSize();
$paged = array_slice($items, $offset, $limit);
return new Paginated($pagination, $paged, $total);
}
}

View file

@ -7,6 +7,7 @@ namespace Tests\Unit\Application\Arcadia\Usecase\Employees;
use Application\Arcadia\Usecase\Employees\FindAll\FindAllEmployees;
use Application\Arcadia\Usecase\Employees\FindAll\Input;
use Application\Arcadia\Usecase\Employees\FindAll\Output;
use Application\Arcadia\Usecase\Employees\FindAll\PaginatedEmployee;
use Domain\Models\Entities\Employee;
use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
use Domain\Models\ValueObjects\Employees\EmployeeSortColumn;
@ -14,24 +15,25 @@ use Domain\Models\ValueObjects\Employees\EmployeeSorter;
use Domain\Models\ValueObjects\EstablishmentCode;
use Domain\Models\ValueObjects\ServiceId;
use Domain\Models\ValueObjects\SiteId;
use Domain\Ports\Directories\EmployeeDirectory;
use Shared\Paginated;
use Infra\Adapters\Directories\InMemoryEmployeeDirectory;
use Shared\Pagination;
covers(FindAllEmployees::class);
covers(
FindAllEmployees::class,
Input::class,
Output::class,
PaginatedEmployee::class,
);
describe('FindAllEmployees Usecase Unit Tests', function (): void {
it('executes successfully and returns paginated output DTO', function (): void {
// GIVEN
$pagination = Pagination::ofPage(1, 10);
$sorter = new EmployeeSorter(
EmployeeSortColumn::arcadiaId,
\SortDirection::Ascending
);
$input = new Input($pagination, $sorter);
$employeeDirectory = \Mockery::mock(EmployeeDirectory::class);
$usecase = new FindAllEmployees($employeeDirectory);
$directory = new InMemoryEmployeeDirectory();
beforeEach(function () use ($directory): void {
$directory->clear();
});
it('executes successfully and returns paginated output DTO', function () use ($directory): void {
// GIVEN
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
publicEmployeeNumber: $employeeId,
@ -47,16 +49,14 @@ describe('FindAllEmployees Usecase Unit Tests', function (): void {
updatedAt: null,
);
$paginatedResult = new Paginated($pagination, [$employee], 1);
$employeeDirectory->shouldReceive('findAll')
->once()
->withArgs(function (Pagination $actualPagination, EmployeeSorter $actualSorter, mixed $actualFilter) use ($pagination): bool {
return $actualPagination === $pagination
&& $actualSorter->sortBy === EmployeeSortColumn::arcadiaId
&& $actualSorter->direction === \SortDirection::Ascending
&& $actualFilter === null;
})
->andReturn($paginatedResult);
$directory->add($employee);
$usecase = new FindAllEmployees($directory);
$pagination = Pagination::ofPage(1, 10);
$sorter = new EmployeeSorter(
EmployeeSortColumn::arcadiaId,
\SortDirection::Ascending
);
$input = new Input($pagination, $sorter);
// WHEN
$result = $usecase->execute($input);

View file

@ -13,19 +13,25 @@ use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
use Domain\Models\ValueObjects\EstablishmentCode;
use Domain\Models\ValueObjects\ServiceId;
use Domain\Models\ValueObjects\SiteId;
use Domain\Ports\Directories\EmployeeDirectory;
use Infra\Adapters\Directories\InMemoryEmployeeDirectory;
covers(FindEmployee::class);
covers(
FindEmployee::class,
Input::class,
Output::class,
);
describe('FindEmployee Usecase Unit Tests', function (): void {
it('executes successfully and returns Output when employee exists', function (): void {
// GIVEN
$employeeId = new ArcadiaEmployeeId('01155902');
$input = new Input($employeeId);
$employeeDirectory = \Mockery::mock(EmployeeDirectory::class);
$usecase = new FindEmployee($employeeDirectory);
$directory = new InMemoryEmployeeDirectory();
$employee = new Employee(
beforeEach(function () use ($directory): void {
$directory->clear();
});
it('executes successfully and returns Output when employee exists', function () use ($directory): void {
// GIVEN
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',
@ -39,10 +45,9 @@ describe('FindEmployee Usecase Unit Tests', function (): void {
updatedAt: null,
);
$employeeDirectory->shouldReceive('find')
->once()
->with($employeeId)
->andReturn($employee);
$directory->add($employee);
$usecase = new FindEmployee($directory);
$input = new Input($employeeId);
// WHEN
$result = $usecase->execute($input);
@ -57,20 +62,16 @@ describe('FindEmployee Usecase Unit Tests', function (): void {
->and($result->service)->toBe('097');
});
it('throws NotFoundException when employee does not exist', function (): void {
it('throws NotFoundException when employee does not exist', function () use ($directory): void {
// GIVEN
$employeeId = new ArcadiaEmployeeId('01155902');
$input = new Input($employeeId);
$employeeDirectory = \Mockery::mock(EmployeeDirectory::class);
$usecase = new FindEmployee($employeeDirectory);
$employeeId = new ArcadiaEmployeeId('01155902');
$usecase = new FindEmployee($directory);
$input = new Input($employeeId);
$employeeDirectory->shouldReceive('find')
->once()
->with($employeeId)
->andReturnNull();
// WHEN
$execution = fn () => $usecase->execute($input);
// WHEN & THEN
expect(fn () => $usecase->execute($input))
->toThrow(NotFoundException::class, "Employee '01155902' not found.");
// THEN
expect($execution)->toThrow(NotFoundException::class, "Employee '01155902' not found.");
});
});

View file

@ -29,17 +29,26 @@ use Domain\Models\ValueObjects\Inco\Composition\ReferenceMacronutrients;
use Domain\Models\ValueObjects\Inco\Composition\SpecialCompounds;
use Domain\Models\ValueObjects\Inco\Composition\Vitamins;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Ports\Directories\IncoCompositionDirectory;
use Mockery;
use Infra\Adapters\Directories\InMemoryIncoCompositionDirectory;
covers(FindIncoComposition::class);
covers(
FindIncoComposition::class,
Input::class,
Output::class,
);
describe('FindIncoComposition Usecase Unit Tests', function (): void {
it('executes successfully and returns Output when composition exists', function (): void {
// GIVEN
$incoId = new IncoId('12345');
$compoId = new IncoCompositionId('67890');
$identification = new Identification('Poulet Rôti 1kg Test', '12345', 'LDC');
$directory = new InMemoryIncoCompositionDirectory();
beforeEach(function () use ($directory): void {
$directory->clear();
});
function createComposition(string $compoIdStr, string $incoIdStr): IncoComposition
{
$compoId = new IncoCompositionId($compoIdStr);
$incoId = new IncoId($incoIdStr);
$identification = new Identification('Poulet Rôti 1kg Test', $incoIdStr, 'LDC');
$energyValues = new EnergyValues('100g', '0', '0');
$energy = new Energy($energyValues, $energyValues);
@ -63,7 +72,7 @@ describe('FindIncoComposition Usecase Unit Tests', function (): void {
$amino = new AminoAcids('0', '0', '0');
$special = new SpecialCompounds('0', '0', '0', '0');
$composition = new IncoComposition(
return new IncoComposition(
id: $compoId,
idInco: $incoId,
identification: $identification,
@ -81,9 +90,13 @@ describe('FindIncoComposition Usecase Unit Tests', function (): void {
aminoAcids: $amino,
specialCompounds: $special,
);
}
$directory = Mockery::mock(IncoCompositionDirectory::class);
$directory->shouldReceive('findByIncoId')->with($incoId)->andReturn($composition);
it('executes successfully and returns Output when composition exists', function () use ($directory): void {
// GIVEN
$incoId = new IncoId('12345');
$composition = createComposition('67890', '12345');
$directory->add($composition);
$usecase = new FindIncoComposition($directory);
$input = new Input($incoId);
@ -93,17 +106,13 @@ describe('FindIncoComposition Usecase Unit Tests', function (): void {
// THEN
expect($output)->toBeInstanceOf(Output::class)
->and($output->id)->toBe($compoId)
->and($output->idInco)->toBe($incoId);
->and($output->id->value)->toBe('67890')
->and($output->idInco->value)->toBe('12345');
});
it('throws NotFoundException when composition does not exist', function (): void {
it('throws NotFoundException when composition does not exist', function () use ($directory): void {
// GIVEN
$incoId = new IncoId('99999');
$directory = Mockery::mock(IncoCompositionDirectory::class);
$directory->shouldReceive('findByIncoId')->with($incoId)->andReturn(null);
$incoId = new IncoId('99999');
$usecase = new FindIncoComposition($directory);
$input = new Input($incoId);

View file

@ -7,6 +7,8 @@ namespace Tests\Unit\Application\Arcadia\Usecase\IncoSheets;
use Application\Arcadia\Usecase\IncoSheets\FindAll\FindAllIncoSheets;
use Application\Arcadia\Usecase\IncoSheets\FindAll\Input;
use Application\Arcadia\Usecase\IncoSheets\FindAll\Output;
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedIncoSheet;
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\ValueObjects\Inco\Allegations;
use Domain\Models\ValueObjects\Inco\Brand;
@ -14,23 +16,29 @@ use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Models\ValueObjects\Inco\Ingredients;
use Domain\Ports\Directories\IncoSheetDirectory;
use Domain\Ports\Directories\IncoShortSheetDirectory;
use Shared\Paginated;
use Infra\Adapters\Directories\InMemoryIncoSheetDirectory;
use Infra\Adapters\Directories\InMemoryIncoShortSheetDirectory;
use Shared\Pagination;
covers(FindAllIncoSheets::class);
covers(
FindAllIncoSheets::class,
Input::class,
Output::class,
PaginatedIncoSheet::class,
PaginatedShortIncoSheet::class,
);
describe('FindAllIncoSheets Usecase Unit Tests', function (): void {
it('executes successfully and returns paginated output DTO', function (): void {
// GIVEN
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Ascending);
$input = new Input($pagination, $sorter);
$incoDirectory = \Mockery::mock(IncoSheetDirectory::class);
$shortIncoDirectory = \Mockery::mock(IncoShortSheetDirectory::class);
$usecase = new FindAllIncoSheets($incoDirectory, $shortIncoDirectory);
$directory = new InMemoryIncoSheetDirectory();
$shortDirectory = new InMemoryIncoShortSheetDirectory();
beforeEach(function () use ($directory, $shortDirectory): void {
$directory->clear();
$shortDirectory->clear();
});
it('executes successfully and returns paginated output DTO', function () use ($directory, $shortDirectory): void {
// GIVEN
$incoId = new IncoId('12345');
$incoSheet = new IncoSheet(
id: $incoId,
@ -56,16 +64,11 @@ describe('FindAllIncoSheets Usecase Unit Tests', function (): void {
updatedAt: null,
);
$paginatedResult = new Paginated($pagination, [$incoSheet], 1);
$incoDirectory->shouldReceive('findAll')
->once()
->withArgs(function (Pagination $actualPagination, IncoSheetSorter $actualSorter, mixed $actualFilter) use ($pagination): bool {
return $actualPagination === $pagination
&& $actualSorter->sortBy === IncoSheetSortColumn::incoSheetId
&& $actualSorter->direction === \SortDirection::Ascending
&& $actualFilter === null;
})
->andReturn($paginatedResult);
$directory->add($incoSheet);
$usecase = new FindAllIncoSheets($directory, $shortDirectory);
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Ascending);
$input = new Input($pagination, $sorter);
// WHEN
$result = $usecase->execute($input);

View file

@ -13,18 +13,24 @@ use Domain\Models\ValueObjects\Inco\Allegations;
use Domain\Models\ValueObjects\Inco\Brand;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\Ingredients;
use Domain\Ports\Directories\IncoSheetDirectory;
use Infra\Adapters\Directories\InMemoryIncoSheetDirectory;
covers(FindIncoSheet::class);
covers(
FindIncoSheet::class,
Input::class,
Output::class,
);
describe('FindIncoSheet Usecase Unit Tests', function (): void {
it('executes successfully and returns Output when inco sheet exists', function (): void {
// GIVEN
$incoId = new IncoId('12345');
$input = new Input($incoId);
$incoDirectory = \Mockery::mock(IncoSheetDirectory::class);
$usecase = new FindIncoSheet($incoDirectory);
$directory = new InMemoryIncoSheetDirectory();
beforeEach(function () use ($directory): void {
$directory->clear();
});
it('executes successfully and returns Output when inco sheet exists', function () use ($directory): void {
// GIVEN
$incoId = new IncoId('12345');
$incoSheet = new IncoSheet(
id: $incoId,
name: 'Poulet Rôti 1kg',
@ -49,10 +55,9 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void {
updatedAt: null,
);
$incoDirectory->shouldReceive('find')
->once()
->with($incoId)
->andReturn($incoSheet);
$directory->add($incoSheet);
$usecase = new FindIncoSheet($directory);
$input = new Input($incoId);
// WHEN
$result = $usecase->execute($input);
@ -66,17 +71,11 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void {
->and($result->isSeasonal)->toBeTrue();
});
it('throws NotFoundException when inco sheet does not exist', function (): void {
it('throws NotFoundException when inco sheet does not exist', function () use ($directory): void {
// GIVEN
$incoId = new IncoId('99999');
$input = new Input($incoId);
$incoDirectory = \Mockery::mock(IncoSheetDirectory::class);
$usecase = new FindIncoSheet($incoDirectory);
$incoDirectory->shouldReceive('find')
->once()
->with($incoId)
->andReturnNull();
$incoId = new IncoId('99999');
$usecase = new FindIncoSheet($directory);
$input = new Input($incoId);
// WHEN & THEN
expect(fn () => $usecase->execute($input))

View file

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Application\Arcadia\Usecase\IncoSheets;
use Application\Arcadia\Usecase\IncoSheets\Search\Input;
use Application\Arcadia\Usecase\IncoSheets\Search\Output;
use Application\Arcadia\Usecase\IncoSheets\Search\PaginatedIncoSheet;
use Application\Arcadia\Usecase\IncoSheets\Search\PaginatedShortIncoSheet;
use Application\Arcadia\Usecase\IncoSheets\Search\SearchIncoSheets;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\Entities\Inco\IncoShortSheet;
use Domain\Models\ValueObjects\Inco\Allegations;
use Domain\Models\ValueObjects\Inco\Brand;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\Ingredients;
use Infra\Adapters\Directories\InMemoryIncoSheetDirectory;
use Infra\Adapters\Directories\InMemoryIncoShortSheetDirectory;
use Shared\Pagination;
covers(
SearchIncoSheets::class,
Input::class,
Output::class,
PaginatedIncoSheet::class,
PaginatedShortIncoSheet::class,
);
describe('SearchIncoSheets Usecase Unit Tests', function (): void {
$directory = new InMemoryIncoSheetDirectory();
$shortDirectory = new InMemoryIncoShortSheetDirectory();
beforeEach(function () use ($directory, $shortDirectory): void {
$directory->clear();
$shortDirectory->clear();
});
it('searches full inco sheets matching query and brands', function () use ($directory, $shortDirectory): void {
// GIVEN
$incoSheet = new IncoSheet(
id: new IncoId('12345'),
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',
description: 'Poulet prêt à cuire',
allegations: new Allegations(['Volaille française']),
sellArguments: 'Délicieux',
preparationAdvices: 'Cuire 45min à 180°C',
tips: null,
sellFamilly: 'Boucherie',
ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]),
portions: null,
allergens: [],
brand: new Brand('M01', 'Le Gaulois'),
isSeasonal: true,
weight: null,
nutriscore: null,
storageCondition: null,
additives: [],
compo: null,
createdAt: null,
updatedAt: null,
);
$directory->add($incoSheet);
$usecase = new SearchIncoSheets($directory, $shortDirectory);
$input = new Input(
query: new IncoIdQuery('12%'),
short: false,
pagination: Pagination::ofPage(1, 10),
brands: 'M01',
);
// WHEN
$result = $usecase->execute($input);
// THEN
expect($result)->toBeInstanceOf(Output::class)
->and($result->total)->toBe(1)
->and($result->incoSheets)->toHaveCount(1);
});
it('searches short inco sheets when short option is true', function () use ($directory, $shortDirectory): void {
// GIVEN
$shortSheet = new IncoShortSheet(
id: new IncoId('12345'),
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',
createdAt: null,
updatedAt: null,
);
$shortDirectory->add($shortSheet);
$usecase = new SearchIncoSheets($directory, $shortDirectory);
$input = new Input(
query: new IncoIdQuery('12%'),
short: true,
pagination: Pagination::ofPage(1, 10),
);
// WHEN
$result = $usecase->execute($input);
// THEN
expect($result)->toBeInstanceOf(Output::class)
->and($result->total)->toBe(1)
->and($result->incoSheets)->toHaveCount(1);
});
});

View file

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Adapters\Directories;
use Domain\Models\Entities\Employee;
use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
use Domain\Models\ValueObjects\Employees\EmployeeFilter;
use Domain\Models\ValueObjects\Employees\EmployeeSortColumn;
use Domain\Models\ValueObjects\Employees\EmployeeSorter;
use Domain\Models\ValueObjects\EstablishmentCode;
use Domain\Models\ValueObjects\ServiceId;
use Domain\Models\ValueObjects\SiteId;
use Infra\Adapters\Directories\InMemoryEmployeeDirectory;
use Shared\Pagination;
covers(InMemoryEmployeeDirectory::class);
describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
it('finds an employee by id or returns null if not found', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',
startDate: new \DateTimeImmutable('2024-01-01'),
endDate: null,
plannedEndDate: null,
establishment: new EstablishmentCode('EST01'),
site: new SiteId('LDCSER'),
service: new ServiceId('097'),
createdAt: new \DateTimeImmutable('2024-01-01'),
updatedAt: null,
);
$directory->add($employee);
// WHEN
$found = $directory->find($employeeId);
$missing = $directory->find(new ArcadiaEmployeeId('99999999'));
// THEN
expect($found)->toBe($employee)
->and($missing)->toBeNull();
});
it('filters, sorts and paginates employees correctly', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$emp1 = new Employee(
publicEmployeeNumber: new ArcadiaEmployeeId('00000001'),
lastName: 'ALICE',
firstName: 'A',
startDate: new \DateTimeImmutable('2024-01-01'),
endDate: null,
plannedEndDate: null,
establishment: new EstablishmentCode('EST01'),
site: new SiteId('SITEO'),
service: new ServiceId('097'),
createdAt: new \DateTimeImmutable('2024-01-01'),
updatedAt: null,
);
$emp2 = new Employee(
publicEmployeeNumber: new ArcadiaEmployeeId('00000002'),
lastName: 'BOB',
firstName: 'B',
startDate: new \DateTimeImmutable('2024-02-01'),
endDate: null,
plannedEndDate: null,
establishment: new EstablishmentCode('EST02'),
site: new SiteId('SITETWO'),
service: new ServiceId('098'),
createdAt: new \DateTimeImmutable('2024-02-01'),
updatedAt: null,
);
$directory->add($emp1, $emp2);
$pagination = Pagination::ofPage(1, 10);
$sorter = new EmployeeSorter(EmployeeSortColumn::arcadiaId, \SortDirection::Descending);
$filter = new EmployeeFilter(establishment: new EstablishmentCode('EST01'));
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(1)
->and($result->getItems())->toHaveCount(1)
->and($result->getItems()[0]->publicEmployeeNumber->value)->toBe('00000001');
});
it('clears all stored employees', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',
startDate: new \DateTimeImmutable('2024-01-01'),
endDate: null,
plannedEndDate: null,
establishment: new EstablishmentCode('EST01'),
site: new SiteId('LDCSER'),
service: new ServiceId('097'),
createdAt: new \DateTimeImmutable('2024-01-01'),
updatedAt: null,
);
$directory->add($employee);
// WHEN
$directory->clear();
// THEN
expect($directory->find($employeeId))->toBeNull();
});
});

View file

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\Composition\IncoComposition;
use Domain\Models\ValueObjects\Inco\Composition\AminoAcids;
use Domain\Models\ValueObjects\Inco\Composition\CarbohydratesDetail;
use Domain\Models\ValueObjects\Inco\Composition\Energy;
use Domain\Models\ValueObjects\Inco\Composition\EnergyValues;
use Domain\Models\ValueObjects\Inco\Composition\FattyAcids;
use Domain\Models\ValueObjects\Inco\Composition\FreshProduceAndNuts;
use Domain\Models\ValueObjects\Inco\Composition\FruitAndNutAmount;
use Domain\Models\ValueObjects\Inco\Composition\Identification;
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
use Domain\Models\ValueObjects\Inco\Composition\Minerals;
use Domain\Models\ValueObjects\Inco\Composition\NutrientValue;
use Domain\Models\ValueObjects\Inco\Composition\NutriScore;
use Domain\Models\ValueObjects\Inco\Composition\NutriScorePoints;
use Domain\Models\ValueObjects\Inco\Composition\PercentageValue;
use Domain\Models\ValueObjects\Inco\Composition\PortionMacronutrients;
use Domain\Models\ValueObjects\Inco\Composition\ReferenceIntakes;
use Domain\Models\ValueObjects\Inco\Composition\ReferenceMacronutrients;
use Domain\Models\ValueObjects\Inco\Composition\SpecialCompounds;
use Domain\Models\ValueObjects\Inco\Composition\Vitamins;
use Domain\Models\ValueObjects\Inco\IncoId;
use Infra\Adapters\Directories\InMemoryIncoCompositionDirectory;
covers(InMemoryIncoCompositionDirectory::class);
describe('InMemoryIncoCompositionDirectory Unit Tests', function (): void {
function createComposition(string $compoIdStr, string $incoIdStr): IncoComposition
{
$compoId = new IncoCompositionId($compoIdStr);
$incoId = new IncoId($incoIdStr);
$identification = new Identification('Poulet Rôti 1kg Test', $incoIdStr, 'LDC');
$energyValues = new EnergyValues('100g', '0', '0');
$energy = new Energy($energyValues, $energyValues);
$nutrient = new NutrientValue('0');
$refMacro = new ReferenceMacronutrients($nutrient, $nutrient, $nutrient, $nutrient, $nutrient, $nutrient, $nutrient);
$porMacro = new PortionMacronutrients($nutrient, $nutrient, $nutrient, $nutrient, $nutrient, $nutrient, $nutrient);
$fnAmount = new FruitAndNutAmount();
$pct = new PercentageValue('0');
$fresh = new FreshProduceAndNuts($fnAmount, $fnAmount, $pct, $pct);
$refIntakes = new ReferenceIntakes($pct, $pct, $pct, $pct, $pct, $pct, $pct, $pct, '100g');
$nutriScore = new NutriScore('0', '0', 'A');
$nsPoints = new NutriScorePoints('0', '0', '0', '0', '0', '0', '0');
$minerals = new Minerals('0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0');
$vitamins = new Vitamins('0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0');
$carbs = new CarbohydratesDetail('0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0');
$fatty = new FattyAcids('0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0');
$amino = new AminoAcids('0', '0', '0');
$special = new SpecialCompounds('0', '0', '0', '0');
return new IncoComposition(
id: $compoId,
idInco: $incoId,
identification: $identification,
energy: $energy,
referenceMacronutrients: $refMacro,
portionMacronutrients: $porMacro,
freshProduceAndNuts: $fresh,
referenceIntakes: $refIntakes,
nutriScore: $nutriScore,
nutriScorePoints: $nsPoints,
minerals: $minerals,
vitamins: $vitamins,
carbohydratesDetail: $carbs,
fattyAcids: $fatty,
aminoAcids: $amino,
specialCompounds: $special,
);
}
it('finds an inco composition by id or incoId', function (): void {
// GIVEN
$directory = new InMemoryIncoCompositionDirectory();
$composition = createComposition('67890', '12345');
$directory->add($composition);
// WHEN
$foundById = $directory->find(new IncoCompositionId('67890'));
$foundByIncoId = $directory->findByIncoId(new IncoId('12345'));
$notFound = $directory->findByIncoId(new IncoId('99999'));
// THEN
expect($foundById)->toBe($composition)
->and($foundByIncoId)->toBe($composition)
->and($notFound)->toBeNull();
});
it('clears all compositions', function (): void {
// GIVEN
$directory = new InMemoryIncoCompositionDirectory();
$composition = createComposition('67890', '12345');
$directory->add($composition);
// WHEN
$directory->clear();
// THEN
expect($directory->find(new IncoCompositionId('67890')))->toBeNull();
});
});

View file

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\ValueObjects\Inco\Allegations;
use Domain\Models\ValueObjects\Inco\Brand;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Models\ValueObjects\Inco\Ingredients;
use Infra\Adapters\Directories\InMemoryIncoSheetDirectory;
use Shared\Pagination;
covers(InMemoryIncoSheetDirectory::class);
describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
function createSheet(string $idStr, string $brandCode, string $brandLabel): IncoSheet
{
return new IncoSheet(
id: new IncoId($idStr),
name: 'Sheet ' . $idStr,
label: 'Label ' . $idStr,
description: 'Desc',
allegations: new Allegations(['Volaille']),
sellArguments: 'Arg',
preparationAdvices: 'Advice',
tips: null,
sellFamilly: 'Family',
ingredients: new Ingredients(['Ingrédients' => ['Test']]),
portions: null,
allergens: [],
brand: new Brand($brandCode, $brandLabel),
isSeasonal: true,
weight: null,
nutriscore: null,
storageCondition: null,
additives: [],
compo: null,
createdAt: new \DateTimeImmutable('2024-01-01'),
updatedAt: new \DateTimeImmutable('2024-01-02'),
);
}
it('finds an inco sheet by id', function (): void {
// GIVEN
$directory = new InMemoryIncoSheetDirectory();
$sheet = createSheet('12345', 'M01', 'Le Gaulois');
$directory->add($sheet);
// WHEN
$found = $directory->find(new IncoId('12345'));
$missing = $directory->find(new IncoId('99999'));
// THEN
expect($found)->toBe($sheet)
->and($missing)->toBeNull();
});
it('filters, sorts and paginates inco sheets', function (): void {
// GIVEN
$directory = new InMemoryIncoSheetDirectory();
$sheet1 = createSheet('10001', 'M01', 'Le Gaulois');
$sheet2 = createSheet('10002', 'M02', 'Maître CoQ');
$directory->add($sheet1, $sheet2);
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Descending);
$filter = new IncoSheetFilter(null, 'M01');
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(1)
->and($result->getItems())->toHaveCount(1)
->and((string) $result->getItems()[0]->id->value)->toBe('10001');
});
it('searches inco sheets by id query and brands', function (): void {
// GIVEN
$directory = new InMemoryIncoSheetDirectory();
$sheet1 = createSheet('12345', 'M01', 'Le Gaulois');
$sheet2 = createSheet('12999', 'M01', 'Le Gaulois');
$sheet3 = createSheet('54321', 'M02', 'Maître CoQ');
$directory->add($sheet1, $sheet2, $sheet3);
$query = new IncoIdQuery('12%');
$pagination = Pagination::ofPage(1, 10);
// WHEN
$result = $directory->search($query, $pagination, 'M01');
// THEN
expect($result->getTotal())->toBe(2)
->and($result->getItems())->toHaveCount(2);
});
it('clears all sheets', function (): void {
// GIVEN
$directory = new InMemoryIncoSheetDirectory();
$sheet = createSheet('12345', 'M01', 'Le Gaulois');
$directory->add($sheet);
// WHEN
$directory->clear();
// THEN
expect($directory->find(new IncoId('12345')))->toBeNull();
});
});

View file

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoShortSheet;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Infra\Adapters\Directories\InMemoryIncoShortSheetDirectory;
use Shared\Pagination;
covers(InMemoryIncoShortSheetDirectory::class);
describe('InMemoryIncoShortSheetDirectory Unit Tests', function (): void {
function createShortSheet(string $idStr): IncoShortSheet
{
return new IncoShortSheet(
id: new IncoId($idStr),
name: 'Sheet ' . $idStr,
label: 'Label ' . $idStr,
createdAt: new \DateTimeImmutable('2024-01-01'),
updatedAt: new \DateTimeImmutable('2024-01-02'),
);
}
it('finds an inco short sheet by id', function (): void {
// GIVEN
$directory = new InMemoryIncoShortSheetDirectory();
$sheet = createShortSheet('12345');
$directory->add($sheet);
// WHEN
$found = $directory->find(new IncoId('12345'));
$missing = $directory->find(new IncoId('99999'));
// THEN
expect($found)->toBe($sheet)
->and($missing)->toBeNull();
});
it('filters, sorts and paginates short sheets', function (): void {
// GIVEN
$directory = new InMemoryIncoShortSheetDirectory();
$sheet1 = createShortSheet('10001');
$sheet2 = createShortSheet('10002');
$directory->add($sheet1, $sheet2);
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Descending);
$filter = new IncoSheetFilter(new \DateTimeImmutable('2023-01-01'));
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(2)
->and($result->getItems())->toHaveCount(2)
->and((string) $result->getItems()[0]->id->value)->toBe('10002');
});
it('searches short sheets by query', function (): void {
// GIVEN
$directory = new InMemoryIncoShortSheetDirectory();
$sheet1 = createShortSheet('12345');
$sheet2 = createShortSheet('12999');
$sheet3 = createShortSheet('54321');
$directory->add($sheet1, $sheet2, $sheet3);
$query = new IncoIdQuery('12%');
$pagination = Pagination::ofPage(1, 10);
// WHEN
$result = $directory->search($query, $pagination);
// THEN
expect($result->getTotal())->toBe(2)
->and($result->getItems())->toHaveCount(2);
});
it('clears all short sheets', function (): void {
// GIVEN
$directory = new InMemoryIncoShortSheetDirectory();
$sheet = createShortSheet('12345');
$directory->add($sheet);
// WHEN
$directory->clear();
// THEN
expect($directory->find(new IncoId('12345')))->toBeNull();
});
});

View file

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Infra\Adapters\Repositories;
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 Infra\Adapters\Repositories\InMemoryClientRepository;
use Ramsey\Uuid\Uuid;
use Shared\Pagination;
covers(InMemoryClientRepository::class);
describe('InMemoryClientRepository Unit Tests', function (): void {
function createClient(string $identifierStr): Client
{
return Client::hydrate(
id: new ClientId(Uuid::uuid7()),
name: new ClientName('Test Client'),
identifier: new ClientIdentifier($identifierStr),
secret: new ClientSecret('arcadia_sec_' . str_repeat('a', 86)),
isActive: true,
createdAt: new \DateTimeImmutable(),
updatedAt: new \DateTimeImmutable(),
);
}
it('finds client by id or identifier', function (): void {
// GIVEN
$repository = new InMemoryClientRepository();
$client = createClient('arcadia_id_' . str_repeat('1', 32));
$repository->add($client);
// WHEN
$foundById = $repository->find($client->id);
$foundByIdentifier = $repository->findByIdentifier($client->identifier);
$missing = $repository->findByIdentifier(new ClientIdentifier('arcadia_id_' . str_repeat('9', 32)));
// THEN
expect($foundById)->toBe($client)
->and($foundByIdentifier)->toBe($client)
->and($missing)->toBeNull();
});
it('persists and deletes a client', function (): void {
// GIVEN
$repository = new InMemoryClientRepository();
$client = createClient('arcadia_id_' . str_repeat('2', 32));
// WHEN
$repository->persist($client);
$foundAfterPersist = $repository->find($client->id);
$repository->delete($client);
$foundAfterDelete = $repository->find($client->id);
// THEN
expect($foundAfterPersist)->toBe($client)
->and($foundAfterDelete)->toBeNull();
});
it('finds all clients with pagination', function (): void {
// GIVEN
$repository = new InMemoryClientRepository();
$client1 = createClient('arcadia_id_' . str_repeat('1', 32));
$client2 = createClient('arcadia_id_' . str_repeat('2', 32));
$repository->add($client1, $client2);
$pagination = Pagination::ofPage(1, 10);
// WHEN
$result = $repository->findAll($pagination);
// THEN
expect($result->getTotal())->toBe(2)
->and($result->getItems())->toHaveCount(2);
});
it('clears all clients', function (): void {
// GIVEN
$repository = new InMemoryClientRepository();
$client = createClient('arcadia_id_' . str_repeat('3', 32));
$repository->add($client);
// WHEN
$repository->clear();
// THEN
expect($repository->find($client->id))->toBeNull();
});
});