feat(IncoSheets): add search functionality for inco sheets
- Introduced IncoIdQuery value object for validating search queries. - Implemented search method in IncoSheetDirectory and EloquentIncoSheetDirectory. - Created IncoSheetsSearchController to handle search requests. - Added IncoSheetsSearchRequest for validating incoming search parameters. - Developed IncoSheetsSearchResponse for formatting search results. - Added tests for search functionality in IncoSheetsSearchTest. - Updated OpenAPI tests to validate search endpoint.
This commit is contained in:
parent
ef8de85088
commit
4d24e5c3fa
26 changed files with 917 additions and 157 deletions
|
|
@ -0,0 +1,30 @@
|
|||
info:
|
||||
name: Search Inco Sheets
|
||||
type: http
|
||||
seq: 3
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{ENDPOINT}}/api/{{APIV1}}/inco-sheets/search?incoIdLike=%1%"
|
||||
params:
|
||||
- name: incoIdLike
|
||||
value: "%1%"
|
||||
type: query
|
||||
description: Pattern used with SQL-like matching rules
|
||||
- name: brands[]
|
||||
value: M01
|
||||
type: query
|
||||
description: Filter by brand code
|
||||
disabled: true
|
||||
- name: short
|
||||
value: "true"
|
||||
type: query
|
||||
description: Return short projection if true
|
||||
disabled: true
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
|
|
@ -154,12 +154,12 @@ paths:
|
|||
- IncoSheets
|
||||
summary: Recherche de fiches INCO par identifiant (motif SQL LIKE)
|
||||
description: |
|
||||
Recherche des fiches INCO dont l'ID correspond à un motif (`incoIdLike`) pour des marques données (`brands`).
|
||||
Recherche des fiches INCO dont l'ID correspond à un motif (`incoIdLike`).
|
||||
Prend également en charge la projection au format court (`short=true`).
|
||||
operationId: searchIncoSheets
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/IncoIdLike"
|
||||
- $ref: "#/components/parameters/BrandsRequired"
|
||||
- $ref: "#/components/parameters/Brands"
|
||||
- $ref: "#/components/parameters/ShortProjection"
|
||||
responses:
|
||||
"200":
|
||||
|
|
@ -442,20 +442,6 @@ components:
|
|||
type: string
|
||||
example: ["M01", "M02"]
|
||||
|
||||
BrandsRequired:
|
||||
name: brands
|
||||
in: query
|
||||
required: true
|
||||
description: Filtrer par un ou plusieurs codes marque (obligatoire).
|
||||
style: form
|
||||
explode: true
|
||||
schema:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
example: ["M01", "M02"]
|
||||
|
||||
ShortProjection:
|
||||
name: short
|
||||
in: query
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Route;
|
|||
use Presentation\Api\V1\Http\Controllers\Employees\EmployeeShowController;
|
||||
use Presentation\Api\V1\Http\Controllers\Employees\EmployeesIndexController;
|
||||
use Presentation\Api\V1\Http\Controllers\IncoSheets\IncoSheetsIndexController;
|
||||
use Presentation\Api\V1\Http\Controllers\IncoSheets\IncoSheetsSearchController;
|
||||
use Presentation\Api\V1\Http\Controllers\IncoSheets\IncoSheetsShowController;
|
||||
|
||||
// * ROOT
|
||||
|
|
@ -32,5 +33,6 @@ Route::prefix('employees')->group(function () {
|
|||
// * INCO SHEETS
|
||||
Route::prefix('inco-sheets')->group(function () {
|
||||
Route::get('/', IncoSheetsIndexController::class)->name('inco-sheets.index');
|
||||
Route::get('/search', IncoSheetsSearchController::class)->name('inco-sheets.search');
|
||||
Route::get('/{incoId}', IncoSheetsShowController::class)->name('inco-sheets.show');
|
||||
});
|
||||
|
|
|
|||
26
src/Application/Usecase/IncoSheets/Search/Input.php
Normal file
26
src/Application/Usecase/IncoSheets/Search/Input.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Usecase\IncoSheets\Search;
|
||||
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoIdQuery;
|
||||
use Shared\DDD\Usecase\Input as UsecaseInput;
|
||||
|
||||
final readonly class Input implements UsecaseInput
|
||||
{
|
||||
/** @var array<string> */
|
||||
public array $brands;
|
||||
|
||||
public function __construct(
|
||||
public IncoIdQuery $query,
|
||||
string ...$brands,
|
||||
) {
|
||||
$this->brands = $brands;
|
||||
foreach ($this->brands as $brand) {
|
||||
if (trim($brand) === '') {
|
||||
throw new \InvalidArgumentException('Every item in brands array must be a non empty string.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Application/Usecase/IncoSheets/Search/Output.php
Normal file
21
src/Application/Usecase/IncoSheets/Search/Output.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Usecase\IncoSheets\Search;
|
||||
|
||||
use Shared\DDD\Usecase\Output as UsecaseOutput;
|
||||
|
||||
final readonly class Output implements UsecaseOutput
|
||||
{
|
||||
/**
|
||||
* @param array<PaginatedIncoSheet> $incoSheets
|
||||
*/
|
||||
public function __construct(
|
||||
public array $incoSheets = [],
|
||||
public int $total = 0,
|
||||
public int $page = 1,
|
||||
public int $perPage = 10,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Usecase\IncoSheets\Search;
|
||||
|
||||
use Domain\Models\Entities\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Entities\IncoCompoId;
|
||||
use Domain\Models\ValueObjects\Entities\IncoId;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Allegations;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Brand;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoWeight;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Ingredients;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Nutriscore;
|
||||
use Domain\Models\ValueObjects\IncoSheets\PortionAmount;
|
||||
use Domain\Models\ValueObjects\IncoSheets\StorageCondition;
|
||||
use Shared\DDD\Usecase\Output as UsecaseOutput;
|
||||
|
||||
final readonly class PaginatedIncoSheet implements UsecaseOutput
|
||||
{
|
||||
/**
|
||||
* @param array<\Domain\Models\ValueObjects\IncoSheets\Allergen> $allergens
|
||||
* @param array<\Domain\Models\ValueObjects\IncoSheets\Additive> $additives
|
||||
*/
|
||||
public function __construct(
|
||||
public IncoId $id,
|
||||
public string $name,
|
||||
public string $label,
|
||||
public string $description,
|
||||
public Allegations $allegations,
|
||||
public string $sellArguments,
|
||||
public string $preparationAdvices,
|
||||
public ?string $tips,
|
||||
public ?string $sellFamilly,
|
||||
public Ingredients $ingredients,
|
||||
public ?PortionAmount $portions,
|
||||
public array $allergens,
|
||||
public Brand $brand,
|
||||
public bool $isSeasonal,
|
||||
public ?IncoWeight $weight,
|
||||
public ?Nutriscore $nutriscore,
|
||||
public ?StorageCondition $storageCondition,
|
||||
public array $additives,
|
||||
public ?IncoCompoId $compo,
|
||||
public ?\DateTimeInterface $createdAt,
|
||||
public ?\DateTimeInterface $updatedAt,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromDomain(IncoSheet $incoSheet): self
|
||||
{
|
||||
return new self(
|
||||
id: $incoSheet->id,
|
||||
name: $incoSheet->name,
|
||||
label: $incoSheet->label,
|
||||
description: $incoSheet->description,
|
||||
allegations: $incoSheet->allegations,
|
||||
sellArguments: $incoSheet->sellArguments,
|
||||
preparationAdvices: $incoSheet->preparationAdvices,
|
||||
tips: $incoSheet->tips,
|
||||
sellFamilly: $incoSheet->sellFamilly,
|
||||
ingredients: $incoSheet->ingredients,
|
||||
portions: $incoSheet->portions,
|
||||
allergens: $incoSheet->allergens,
|
||||
brand: $incoSheet->brand,
|
||||
isSeasonal: $incoSheet->isSeasonal,
|
||||
weight: $incoSheet->weight,
|
||||
nutriscore: $incoSheet->nutriscore,
|
||||
storageCondition: $incoSheet->storageCondition,
|
||||
additives: $incoSheet->additives,
|
||||
compo: $incoSheet->compo,
|
||||
createdAt: $incoSheet->createdAt,
|
||||
updatedAt: $incoSheet->updatedAt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Usecase\IncoSheets\Search;
|
||||
|
||||
use Domain\Models\Entities\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Entities\IncoId;
|
||||
use Shared\DDD\Usecase\Output as UsecaseOutput;
|
||||
|
||||
final readonly class PaginatedShortIncoSheet implements UsecaseOutput
|
||||
{
|
||||
public function __construct(
|
||||
public IncoId $id,
|
||||
public string $name,
|
||||
public string $label,
|
||||
public ?\DateTimeInterface $createdAt,
|
||||
public ?\DateTimeInterface $updatedAt,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromDomain(IncoSheet $incoSheet): self
|
||||
{
|
||||
return new self(
|
||||
id: $incoSheet->id,
|
||||
name: $incoSheet->name,
|
||||
label: $incoSheet->label,
|
||||
createdAt: $incoSheet->createdAt,
|
||||
updatedAt: $incoSheet->updatedAt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Usecase\IncoSheets\Search;
|
||||
|
||||
use Domain\Models\Entities\IncoSheet;
|
||||
use Domain\Ports\Directories\IncoSheetDirectory;
|
||||
use Shared\DDD\Usecase\Input as BaseInput;
|
||||
use Shared\DDD\Usecase\Usecase;
|
||||
use Shared\Paginated;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @implements Usecase<\Shared\DDD\Usecase\Input, \Shared\DDD\Usecase\Output> */
|
||||
final readonly class SearchIncoSheets implements Usecase
|
||||
{
|
||||
public function __construct(
|
||||
private IncoSheetDirectory $directory
|
||||
) {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function execute(BaseInput $input): Output
|
||||
{
|
||||
Assert::isInstanceOf($input, Input::class);
|
||||
|
||||
$page = $this->directory->search(
|
||||
$input->query,
|
||||
...$input->brands
|
||||
);
|
||||
|
||||
return $this->toOutput($page);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Paginated<IncoSheet> $resultPage
|
||||
*/
|
||||
private function toOutput(Paginated $resultPage): Output
|
||||
{
|
||||
$pagination = $resultPage->getPagination();
|
||||
|
||||
return new Output(
|
||||
incoSheets: array_map([$this, 'toPaginatedIncoSheet'], $resultPage->getItems()),
|
||||
total: $resultPage->getTotal(),
|
||||
page: $pagination->pageNumber(),
|
||||
perPage: $pagination->pageSize(),
|
||||
);
|
||||
}
|
||||
|
||||
private function toPaginatedIncoSheet(IncoSheet $incoSheet): PaginatedIncoSheet
|
||||
{
|
||||
return PaginatedIncoSheet::fromDomain($incoSheet);
|
||||
}
|
||||
}
|
||||
20
src/Domain/Models/ValueObjects/IncoSheets/IncoIdQuery.php
Normal file
20
src/Domain/Models/ValueObjects/IncoSheets/IncoIdQuery.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\IncoSheets;
|
||||
|
||||
final readonly class IncoIdQuery
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new \DomainException('value cannot be empty');
|
||||
}
|
||||
|
||||
if (! preg_match('/^[%0-9]+$/', $value)) {
|
||||
throw new \DomainException('value must be a string of digits optionally prefixed and/or suffixed with a % sign');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ namespace Domain\Ports\Directories;
|
|||
|
||||
use Domain\Models\Entities\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Entities\IncoId;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoIdQuery;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoSheetFilter;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoSheetSorter;
|
||||
use Shared\DDD\Directory;
|
||||
|
|
@ -22,5 +23,11 @@ interface IncoSheetDirectory extends Directory
|
|||
?IncoSheetFilter $filter = null
|
||||
): Paginated;
|
||||
|
||||
/** @return Paginated<IncoSheet> */
|
||||
public function search(
|
||||
IncoIdQuery $query,
|
||||
string ...$brands
|
||||
): Paginated;
|
||||
|
||||
public function find(IncoId $id): ?IncoSheet;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Domain\Models\ValueObjects\IncoSheets\Additive;
|
|||
use Domain\Models\ValueObjects\IncoSheets\Allegations;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Allergen;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Brand;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoIdQuery;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoSheetFilter;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoSheetSortColumn;
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoSheetSorter;
|
||||
|
|
@ -49,7 +50,6 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
|
||||
$total = (int) (clone $query)->count();
|
||||
|
||||
/** @var \Illuminate\Support\Collection<int, IncoSheetModel> $rows */
|
||||
$rows = (clone $query)
|
||||
->orderBy(
|
||||
$this->toSqlColumn($sorter->sortBy),
|
||||
|
|
@ -69,6 +69,35 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
return new Paginated($pagination, $items, $total);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function search(
|
||||
IncoIdQuery $query,
|
||||
string ...$brands
|
||||
): Paginated {
|
||||
$dbQuery = IncoSheetModel::query();
|
||||
|
||||
$dbQuery->where('id', 'LIKE', $query->value);
|
||||
|
||||
if (! empty($brands)) {
|
||||
$dbQuery->whereIn('brand->code', $brands);
|
||||
}
|
||||
|
||||
$total = (int) (clone $dbQuery)->count();
|
||||
|
||||
$rows = $dbQuery->get();
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$items[] = $this->toDomain($row);
|
||||
}
|
||||
|
||||
$pagination = Pagination::ofPage(1, max(1, $total));
|
||||
|
||||
/** @var IncoSheet[] $items */
|
||||
return new Paginated($pagination, $items, $total);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function find(IncoId $id): ?IncoSheet
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Dto\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\Find\Output as FindOutput;
|
||||
use Application\Usecase\IncoSheets\FindAll\PaginatedIncoSheet;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Additive;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Allergen;
|
||||
use Presentation\Api\V1\Contracts\ApiResponseDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AdditiveDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllegationsDto;
|
||||
|
|
@ -49,78 +45,6 @@ final readonly class IncoSheetDto implements ApiResponseDto
|
|||
) {
|
||||
}
|
||||
|
||||
public static function fromFindOutput(FindOutput $output): self
|
||||
{
|
||||
$ingGroups = $output->ingredients->groups;
|
||||
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
|
||||
|
||||
return new self(
|
||||
id: (string) $output->id,
|
||||
name: $output->name,
|
||||
label: $output->label,
|
||||
description: $output->description,
|
||||
allegations: AllegationsDto::fromDomain($output->allegations),
|
||||
sellArguments: $output->sellArguments,
|
||||
preparationAdvices: $output->preparationAdvices,
|
||||
tips: $output->tips,
|
||||
sellFamilly: $output->sellFamilly,
|
||||
ingredients: $ingObject,
|
||||
portions: $output->portions !== null ? PortionsDto::fromDomain($output->portions) : null,
|
||||
allergens: array_values(array_map(
|
||||
static fn (Allergen $a): AllergenDto => AllergenDto::fromDomain($a),
|
||||
$output->allergens
|
||||
)),
|
||||
brand: BrandDto::fromDomain($output->brand),
|
||||
isSeasonal: $output->isSeasonal,
|
||||
weight: $output->weight !== null ? WeightDto::fromDomain($output->weight) : null,
|
||||
nutriscore: $output->nutriscore !== null ? NutriscoreDto::fromDomain($output->nutriscore) : null,
|
||||
storageCondition: $output->storageCondition !== null ? StorageConditionDto::fromDomain($output->storageCondition) : null,
|
||||
additives: array_values(array_map(
|
||||
static fn (Additive $a): AdditiveDto => AdditiveDto::fromDomain($a),
|
||||
$output->additives
|
||||
)),
|
||||
compo: $output->compo !== null ? (string) $output->compo : null,
|
||||
createdAt: $output->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $output->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromPaginatedIncoSheet(PaginatedIncoSheet $sheet): self
|
||||
{
|
||||
$ingGroups = $sheet->ingredients->groups;
|
||||
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
|
||||
|
||||
return new self(
|
||||
id: (string) $sheet->id,
|
||||
name: $sheet->name,
|
||||
label: $sheet->label,
|
||||
description: $sheet->description,
|
||||
allegations: AllegationsDto::fromDomain($sheet->allegations),
|
||||
sellArguments: $sheet->sellArguments,
|
||||
preparationAdvices: $sheet->preparationAdvices,
|
||||
tips: $sheet->tips,
|
||||
sellFamilly: $sheet->sellFamilly,
|
||||
ingredients: $ingObject,
|
||||
portions: $sheet->portions !== null ? PortionsDto::fromDomain($sheet->portions) : null,
|
||||
allergens: array_values(array_map(
|
||||
static fn (Allergen $a): AllergenDto => AllergenDto::fromDomain($a),
|
||||
$sheet->allergens
|
||||
)),
|
||||
brand: BrandDto::fromDomain($sheet->brand),
|
||||
isSeasonal: $sheet->isSeasonal,
|
||||
weight: $sheet->weight !== null ? WeightDto::fromDomain($sheet->weight) : null,
|
||||
nutriscore: $sheet->nutriscore !== null ? NutriscoreDto::fromDomain($sheet->nutriscore) : null,
|
||||
storageCondition: $sheet->storageCondition !== null ? StorageConditionDto::fromDomain($sheet->storageCondition) : null,
|
||||
additives: array_values(array_map(
|
||||
static fn (Additive $a): AdditiveDto => AdditiveDto::fromDomain($a),
|
||||
$sheet->additives
|
||||
)),
|
||||
compo: $sheet->compo !== null ? (string) $sheet->compo : null,
|
||||
createdAt: $sheet->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $sheet->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
#[\Override]
|
||||
public function toArray(): array
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Dto\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet;
|
||||
use Presentation\Api\V1\Contracts\ApiResponseDto;
|
||||
|
||||
final readonly class IncoShortSheetDto implements ApiResponseDto
|
||||
|
|
@ -18,17 +17,6 @@ final readonly class IncoShortSheetDto implements ApiResponseDto
|
|||
) {
|
||||
}
|
||||
|
||||
public static function fromPaginatedShortIncoSheet(PaginatedShortIncoSheet $sheet): self
|
||||
{
|
||||
return new self(
|
||||
id: (string) $sheet->id,
|
||||
name: $sheet->name,
|
||||
label: $sheet->label,
|
||||
createdAt: $sheet->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $sheet->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
#[\Override]
|
||||
public function toArray(): array
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Http\Controllers\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\Search\Input as SearchInput;
|
||||
use Application\Usecase\IncoSheets\Search\SearchIncoSheets;
|
||||
use Presentation\Api\V1\Contracts\ApiResponse;
|
||||
use Presentation\Api\V1\Http\Requests\IncoSheets\IncoSheetsSearchRequest;
|
||||
use Presentation\Api\V1\Http\Responses\IncoSheets\IncoSheetsSearchResponse;
|
||||
use Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsSearchPresenter;
|
||||
use Shared\DDD\Usecase\Builder\UsecaseBuilder;
|
||||
use Shared\DDD\Usecase\Builder\UsecaseExecutor;
|
||||
|
||||
final readonly class IncoSheetsSearchController
|
||||
{
|
||||
/** @param UsecaseExecutor<\Shared\DDD\Usecase\Input, \Shared\DDD\Usecase\Output> $executor */
|
||||
public function __construct(
|
||||
private SearchIncoSheets $usecase,
|
||||
private UsecaseExecutor $executor,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(IncoSheetsSearchRequest $request): ApiResponse
|
||||
{
|
||||
$input = new SearchInput(
|
||||
$request->incoIdSearch(),
|
||||
...$request->brands()
|
||||
);
|
||||
|
||||
$view = UsecaseBuilder::using($this->executor)
|
||||
->do($this->usecase)
|
||||
->from($input)
|
||||
->with(new IncoSheetsSearchPresenter())
|
||||
->run();
|
||||
|
||||
return new IncoSheetsSearchResponse(
|
||||
view: $view,
|
||||
baseUri: $request->toLeagueUri(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,9 @@ use Illuminate\Validation\Rule;
|
|||
use Shared\Helpers\DatesHelper;
|
||||
use Shared\Pagination;
|
||||
|
||||
/**
|
||||
* @method \Shared\Pagination pagination(int $defaultPageSize = 25)
|
||||
*/
|
||||
final class EmployeesIndexRequest extends FormRequest
|
||||
{
|
||||
/** @return array<string, array<int, mixed>> */
|
||||
|
|
@ -44,21 +47,6 @@ final class EmployeesIndexRequest extends FormRequest
|
|||
return true;
|
||||
}
|
||||
|
||||
public function pagination(): Pagination
|
||||
{
|
||||
if ($this->has('offset') || $this->has('limit')) {
|
||||
return Pagination::ofOffsetAndLimit(
|
||||
offset: $this->integer('offset', 0),
|
||||
limit: $this->integer('limit', 25),
|
||||
);
|
||||
}
|
||||
|
||||
return Pagination::ofPage(
|
||||
pageNumber: $this->integer('page', 1),
|
||||
pageSize: $this->integer('pageSize', 25),
|
||||
);
|
||||
}
|
||||
|
||||
public function sorter(): Sorter
|
||||
{
|
||||
return new Sorter($this->sortBy(), $this->direction());
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ use Application\Usecase\IncoSheets\FindAll\SortColumn;
|
|||
use Application\Usecase\IncoSheets\FindAll\Sorter;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use League\Uri\Http;
|
||||
use Shared\Helpers\DatesHelper;
|
||||
use Shared\Pagination;
|
||||
|
||||
/**
|
||||
* @method \Shared\Pagination pagination(int $defaultPageSize = 25)
|
||||
* @method \League\Uri\Http toLeagueUri()
|
||||
*/
|
||||
final class IncoSheetsIndexRequest extends FormRequest
|
||||
{
|
||||
/** @return array<string, array<int, mixed>> */
|
||||
|
|
@ -40,21 +43,6 @@ final class IncoSheetsIndexRequest extends FormRequest
|
|||
return true;
|
||||
}
|
||||
|
||||
public function pagination(): Pagination
|
||||
{
|
||||
if ($this->has('offset') || $this->has('limit')) {
|
||||
return Pagination::ofOffsetAndLimit(
|
||||
offset: $this->integer('offset', 0),
|
||||
limit: $this->integer('limit', 25),
|
||||
);
|
||||
}
|
||||
|
||||
return Pagination::ofPage(
|
||||
pageNumber: $this->integer('page', 1),
|
||||
pageSize: $this->integer('pageSize', 25),
|
||||
);
|
||||
}
|
||||
|
||||
public function sorter(): Sorter
|
||||
{
|
||||
return new Sorter($this->sortBy(), $this->direction());
|
||||
|
|
@ -107,9 +95,4 @@ final class IncoSheetsIndexRequest extends FormRequest
|
|||
{
|
||||
return filter_var($this->input('short', false), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
public function toLeagueUri(): Http
|
||||
{
|
||||
return Http::new($this->fullUrl());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Http\Requests\IncoSheets;
|
||||
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoIdQuery;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* @method \League\Uri\Http toLeagueUri()
|
||||
*/
|
||||
final class IncoSheetsSearchRequest extends FormRequest
|
||||
{
|
||||
/** @return array<string, array<int, mixed>> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'incoIdLike' => ['required', 'string'],
|
||||
'brands' => ['sometimes', 'array'],
|
||||
'brands.*' => ['sometimes', 'string'],
|
||||
'short' => ['sometimes', Rule::in(['true', 'false', true, false, '1', '0', 1, 0])],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function incoIdSearch(): IncoIdQuery
|
||||
{
|
||||
return new IncoIdQuery($this->string('incoIdLike')->value());
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function brands(): array
|
||||
{
|
||||
$raw = $this->array('brands');
|
||||
$result = [];
|
||||
foreach ($raw as $b) {
|
||||
if (is_string($b) || is_numeric($b)) {
|
||||
$val = trim((string) $b);
|
||||
if ($val !== '') {
|
||||
$result[] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function isShort(): bool
|
||||
{
|
||||
return filter_var($this->input('short', false), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,21 @@ namespace Presentation\Api\V1\Http\Responses\IncoSheets;
|
|||
|
||||
use Application\Usecase\IncoSheets\FindAll\PaginatedIncoSheet;
|
||||
use Application\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Additive;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Allergen;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Uri\Http;
|
||||
use Presentation\Api\V1\Contracts\ApiResponse;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\IncoSheetDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\IncoShortSheetDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AdditiveDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllegationsDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllergenDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\BrandDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\NutriscoreDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\PortionsDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\StorageConditionDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\WeightDto;
|
||||
use Presentation\Api\V1\Dto\PaginationDto;
|
||||
use Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsIndexView;
|
||||
|
||||
|
|
@ -39,9 +49,10 @@ final readonly class IncoSheetsIndexResponse implements ApiResponse
|
|||
);
|
||||
|
||||
$items = array_map(
|
||||
fn (PaginatedIncoSheet | PaginatedShortIncoSheet $sheet): array => $sheet instanceof PaginatedShortIncoSheet
|
||||
? IncoShortSheetDto::fromPaginatedShortIncoSheet($sheet)->toArray()
|
||||
: IncoSheetDto::fromPaginatedIncoSheet($sheet)->toArray(),
|
||||
fn (PaginatedIncoSheet | PaginatedShortIncoSheet $sheet): array => match ($sheet::class) {
|
||||
PaginatedShortIncoSheet::class => $this->fromPaginatedShortIncoSheet($sheet)->toArray(),
|
||||
PaginatedIncoSheet::class => $this->fromPaginatedIncoSheet($sheet)->toArray(),
|
||||
},
|
||||
$this->view->incoSheets
|
||||
);
|
||||
|
||||
|
|
@ -50,4 +61,51 @@ final readonly class IncoSheetsIndexResponse implements ApiResponse
|
|||
'pagination' => $pagination,
|
||||
]);
|
||||
}
|
||||
|
||||
private function fromPaginatedShortIncoSheet(PaginatedShortIncoSheet $sheet): IncoShortSheetDto
|
||||
{
|
||||
return new IncoShortSheetDto(
|
||||
id: (string) $sheet->id,
|
||||
name: $sheet->name,
|
||||
label: $sheet->label,
|
||||
createdAt: $sheet->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $sheet->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
|
||||
private function fromPaginatedIncoSheet(PaginatedIncoSheet $sheet): IncoSheetDto
|
||||
{
|
||||
$ingGroups = $sheet->ingredients->groups;
|
||||
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
|
||||
|
||||
return new IncoSheetDto(
|
||||
id: (string) $sheet->id,
|
||||
name: $sheet->name,
|
||||
label: $sheet->label,
|
||||
description: $sheet->description,
|
||||
allegations: AllegationsDto::fromDomain($sheet->allegations),
|
||||
sellArguments: $sheet->sellArguments,
|
||||
preparationAdvices: $sheet->preparationAdvices,
|
||||
tips: $sheet->tips,
|
||||
sellFamilly: $sheet->sellFamilly,
|
||||
ingredients: $ingObject,
|
||||
portions: $sheet->portions !== null ? PortionsDto::fromDomain($sheet->portions) : null,
|
||||
allergens: array_values(array_map(
|
||||
static fn (Allergen $a): AllergenDto => AllergenDto::fromDomain($a),
|
||||
$sheet->allergens
|
||||
)),
|
||||
brand: BrandDto::fromDomain($sheet->brand),
|
||||
isSeasonal: $sheet->isSeasonal,
|
||||
weight: $sheet->weight !== null ? WeightDto::fromDomain($sheet->weight) : null,
|
||||
nutriscore: $sheet->nutriscore !== null ? NutriscoreDto::fromDomain($sheet->nutriscore) : null,
|
||||
storageCondition: $sheet->storageCondition !== null ? StorageConditionDto::fromDomain($sheet->storageCondition) : null,
|
||||
additives: array_values(array_map(
|
||||
static fn (Additive $a): AdditiveDto => AdditiveDto::fromDomain($a),
|
||||
$sheet->additives
|
||||
)),
|
||||
compo: $sheet->compo !== null ? (string) $sheet->compo : null,
|
||||
createdAt: $sheet->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $sheet->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Http\Responses\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\Search\PaginatedIncoSheet;
|
||||
use Application\Usecase\IncoSheets\Search\PaginatedShortIncoSheet;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Additive;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Allergen;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Uri\Http;
|
||||
use Presentation\Api\V1\Contracts\ApiResponse;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\IncoSheetDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\IncoShortSheetDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AdditiveDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllegationsDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllergenDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\BrandDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\NutriscoreDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\PortionsDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\StorageConditionDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\WeightDto;
|
||||
use Presentation\Api\V1\Dto\PaginationDto;
|
||||
use Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsSearchView;
|
||||
|
||||
final readonly class IncoSheetsSearchResponse implements ApiResponse
|
||||
{
|
||||
public function __construct(
|
||||
private IncoSheetsSearchView $view,
|
||||
private Http $baseUri,
|
||||
) {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function toResponse($request): JsonResponse
|
||||
{
|
||||
return $this->present();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function present(): JsonResponse
|
||||
{
|
||||
$pagination = PaginationDto::create(
|
||||
page: $this->view->page,
|
||||
pageSize: $this->view->perPage,
|
||||
totalItems: $this->view->total,
|
||||
baseUri: $this->baseUri,
|
||||
);
|
||||
|
||||
$items = array_map(
|
||||
fn (PaginatedIncoSheet | PaginatedShortIncoSheet $sheet): array => match ($sheet::class) {
|
||||
PaginatedShortIncoSheet::class => $this->fromPaginatedIncoSheetToShort($sheet)->toArray(),
|
||||
PaginatedIncoSheet::class => $this->fromPaginatedIncoSheetToFull($sheet)->toArray(),
|
||||
},
|
||||
$this->view->incoSheets
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'incoSheets' => $items,
|
||||
'pagination' => $pagination,
|
||||
]);
|
||||
}
|
||||
|
||||
private function fromPaginatedIncoSheetToShort(PaginatedIncoSheet $sheet): IncoShortSheetDto
|
||||
{
|
||||
return new IncoShortSheetDto(
|
||||
id: (string) $sheet->id,
|
||||
name: $sheet->name,
|
||||
label: $sheet->label,
|
||||
createdAt: $sheet->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $sheet->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
|
||||
private function fromPaginatedIncoSheetToFull(PaginatedIncoSheet $sheet): IncoSheetDto
|
||||
{
|
||||
$ingGroups = $sheet->ingredients->groups;
|
||||
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
|
||||
|
||||
return new IncoSheetDto(
|
||||
id: (string) $sheet->id,
|
||||
name: $sheet->name,
|
||||
label: $sheet->label,
|
||||
description: $sheet->description,
|
||||
allegations: AllegationsDto::fromDomain($sheet->allegations),
|
||||
sellArguments: $sheet->sellArguments,
|
||||
preparationAdvices: $sheet->preparationAdvices,
|
||||
tips: $sheet->tips,
|
||||
sellFamilly: $sheet->sellFamilly,
|
||||
ingredients: $ingObject,
|
||||
portions: $sheet->portions !== null ? PortionsDto::fromDomain($sheet->portions) : null,
|
||||
allergens: array_values(array_map(
|
||||
static fn (Allergen $a): AllergenDto => AllergenDto::fromDomain($a),
|
||||
$sheet->allergens
|
||||
)),
|
||||
brand: BrandDto::fromDomain($sheet->brand),
|
||||
isSeasonal: $sheet->isSeasonal,
|
||||
weight: $sheet->weight !== null ? WeightDto::fromDomain($sheet->weight) : null,
|
||||
nutriscore: $sheet->nutriscore !== null ? NutriscoreDto::fromDomain($sheet->nutriscore) : null,
|
||||
storageCondition: $sheet->storageCondition !== null ? StorageConditionDto::fromDomain($sheet->storageCondition) : null,
|
||||
additives: array_values(array_map(
|
||||
static fn (Additive $a): AdditiveDto => AdditiveDto::fromDomain($a),
|
||||
$sheet->additives
|
||||
)),
|
||||
compo: $sheet->compo !== null ? (string) $sheet->compo : null,
|
||||
createdAt: $sheet->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $sheet->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Http\Responses\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\Find\Output as FindOutput;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Additive;
|
||||
use Domain\Models\ValueObjects\IncoSheets\Allergen;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Presentation\Api\V1\Contracts\ApiResponse;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\IncoSheetDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AdditiveDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllegationsDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\AllergenDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\BrandDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\NutriscoreDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\PortionsDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\StorageConditionDto;
|
||||
use Presentation\Api\V1\Dto\IncoSheets\Parts\WeightDto;
|
||||
use Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsShowView;
|
||||
|
||||
final readonly class IncoSheetsShowResponse implements ApiResponse
|
||||
|
|
@ -26,7 +37,43 @@ final readonly class IncoSheetsShowResponse implements ApiResponse
|
|||
public function present(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'incoSheet' => IncoSheetDto::fromFindOutput($this->view->output),
|
||||
'incoSheet' => $this->fromFindOutput($this->view->output)->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fromFindOutput(FindOutput $output): IncoSheetDto
|
||||
{
|
||||
$ingGroups = $output->ingredients->groups;
|
||||
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
|
||||
|
||||
return new IncoSheetDto(
|
||||
id: (string) $output->id,
|
||||
name: $output->name,
|
||||
label: $output->label,
|
||||
description: $output->description,
|
||||
allegations: AllegationsDto::fromDomain($output->allegations),
|
||||
sellArguments: $output->sellArguments,
|
||||
preparationAdvices: $output->preparationAdvices,
|
||||
tips: $output->tips,
|
||||
sellFamilly: $output->sellFamilly,
|
||||
ingredients: $ingObject,
|
||||
portions: $output->portions !== null ? PortionsDto::fromDomain($output->portions) : null,
|
||||
allergens: array_values(array_map(
|
||||
static fn (Allergen $a): AllergenDto => AllergenDto::fromDomain($a),
|
||||
$output->allergens
|
||||
)),
|
||||
brand: BrandDto::fromDomain($output->brand),
|
||||
isSeasonal: $output->isSeasonal,
|
||||
weight: $output->weight !== null ? WeightDto::fromDomain($output->weight) : null,
|
||||
nutriscore: $output->nutriscore !== null ? NutriscoreDto::fromDomain($output->nutriscore) : null,
|
||||
storageCondition: $output->storageCondition !== null ? StorageConditionDto::fromDomain($output->storageCondition) : null,
|
||||
additives: array_values(array_map(
|
||||
static fn (Additive $a): AdditiveDto => AdditiveDto::fromDomain($a),
|
||||
$output->additives
|
||||
)),
|
||||
compo: $output->compo !== null ? (string) $output->compo : null,
|
||||
createdAt: $output->createdAt?->format(\DateTimeInterface::ATOM),
|
||||
updatedAt: $output->updatedAt?->format(\DateTimeInterface::ATOM),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Presenters\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\Search\Output;
|
||||
use Shared\DDD\Usecase\Output as UsecaseOutput;
|
||||
use Shared\DDD\Usecase\Presenter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @implements Presenter<\Shared\DDD\Usecase\Output, IncoSheetsSearchView> */
|
||||
final readonly class IncoSheetsSearchPresenter implements Presenter
|
||||
{
|
||||
#[\Override]
|
||||
public function present(UsecaseOutput $output): object
|
||||
{
|
||||
Assert::isInstanceOf($output, Output::class);
|
||||
|
||||
return IncoSheetsSearchView::fromOutput($output);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Presenters\IncoSheets;
|
||||
|
||||
use Application\Usecase\IncoSheets\Search\Output;
|
||||
|
||||
final readonly class IncoSheetsSearchView
|
||||
{
|
||||
/**
|
||||
* @param \Application\Usecase\IncoSheets\Search\PaginatedIncoSheet[] $incoSheets
|
||||
*/
|
||||
public function __construct(
|
||||
public array $incoSheets,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $perPage,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromOutput(Output $output): self
|
||||
{
|
||||
return new self(
|
||||
incoSheets: $output->incoSheets,
|
||||
total: $output->total,
|
||||
page: $output->page,
|
||||
perPage: $output->perPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
108
tests/Feature/Api/V1/IncoSheets/IncoSheetsSearchTest.php
Normal file
108
tests/Feature/Api/V1/IncoSheets/IncoSheetsSearchTest.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\V1\IncoSheets;
|
||||
|
||||
use Infra\Database\Models\IncoSheet;
|
||||
|
||||
describe('IncoSheets Search Feature API', function (): void {
|
||||
beforeEach(function (): void {
|
||||
IncoSheet::factory()->create([
|
||||
'id' => '1000101',
|
||||
'name' => 'Poulet Rôti 1kg',
|
||||
'brand' => ['code' => 'M01', 'label' => 'Standard'],
|
||||
]);
|
||||
|
||||
IncoSheet::factory()->create([
|
||||
'id' => '1000102',
|
||||
'name' => 'Poulet Fermier',
|
||||
'brand' => ['code' => 'M02', 'label' => 'Bio'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns 200 OK for valid inco-sheets search request with incoIdLike pattern', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%1000101%');
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonStructure([
|
||||
'incoSheets',
|
||||
'pagination' => [
|
||||
'page',
|
||||
'pageSize',
|
||||
'totalItems',
|
||||
'totalPages',
|
||||
'links' => [
|
||||
'self',
|
||||
'first',
|
||||
'prev',
|
||||
'next',
|
||||
'last',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$response->assertJsonCount(1, 'incoSheets');
|
||||
});
|
||||
|
||||
it('returns 200 OK with matching sheets filtered by brand', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%10001%');
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]=M01");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonCount(1, 'incoSheets');
|
||||
$response->assertJsonPath('incoSheets.0.id', '1000101');
|
||||
});
|
||||
|
||||
it('returns 200 OK with short projection when short is true', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%1000101%');
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&short=true");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonStructure([
|
||||
'incoSheets' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'name',
|
||||
'label',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
],
|
||||
],
|
||||
'pagination',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns 200 OK with empty incoSheets list when no matching inco sheets found', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%9999999%');
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonCount(0, 'incoSheets');
|
||||
$response->assertJsonPath('pagination.totalItems', 0);
|
||||
});
|
||||
});
|
||||
|
|
@ -29,16 +29,4 @@ describe('IncoSheets Show Feature API', function (): void {
|
|||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns 404 Not Found for non-existing inco sheet ID', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson('/api/v1/inco-sheets/9999999');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(404);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -89,4 +89,46 @@ describe('IncoSheet OpenAPI Conformance', function (): void {
|
|||
$response->assertValidRequest();
|
||||
$response->assertValidResponse(404);
|
||||
});
|
||||
|
||||
it('validates GET /api/v1/inco-sheets/search response against openapi spec', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%10004%');
|
||||
|
||||
IncoSheet::factory()->create([
|
||||
'id' => '1000401',
|
||||
'name' => 'Pizza Quatre Fromages',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
/** @var \Illuminate\Testing\TestResponse<\Symfony\Component\HttpFoundation\Response> $response */
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertValidRequest();
|
||||
$response->assertValidResponse(200);
|
||||
});
|
||||
|
||||
it('validates GET /api/v1/inco-sheets/search with short projection against openapi spec', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%10004%');
|
||||
|
||||
IncoSheet::factory()->create([
|
||||
'id' => '1000402',
|
||||
'name' => 'Tarte aux Pommes',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
/** @var \Illuminate\Testing\TestResponse<\Symfony\Component\HttpFoundation\Response> $response */
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&short=true");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertValidRequest();
|
||||
$response->assertValidResponse(200);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\IncoSheets;
|
||||
|
||||
use Domain\Models\ValueObjects\IncoSheets\IncoIdQuery;
|
||||
|
||||
covers(IncoIdQuery::class);
|
||||
|
||||
describe('IncoIdQuery Value Object Instantiation', function (): void {
|
||||
it('can be instantiated with a valid string of digits', function (): void {
|
||||
// GIVEN
|
||||
$validValue = '12345';
|
||||
|
||||
// WHEN
|
||||
$incoIdQuery = new IncoIdQuery($validValue);
|
||||
|
||||
// THEN
|
||||
expect($incoIdQuery->value)->toBe('12345');
|
||||
});
|
||||
|
||||
it('can be instantiated with a string of digits prefixed with %', function (): void {
|
||||
// GIVEN
|
||||
$validValue = '%12345';
|
||||
|
||||
// WHEN
|
||||
$incoIdQuery = new IncoIdQuery($validValue);
|
||||
|
||||
// THEN
|
||||
expect($incoIdQuery->value)->toBe('%12345');
|
||||
});
|
||||
|
||||
it('can be instantiated with a string of digits suffixed with %', function (): void {
|
||||
// GIVEN
|
||||
$validValue = '12345%';
|
||||
|
||||
// WHEN
|
||||
$incoIdQuery = new IncoIdQuery($validValue);
|
||||
|
||||
// THEN
|
||||
expect($incoIdQuery->value)->toBe('12345%');
|
||||
});
|
||||
|
||||
it('can be instantiated with a string of digits prefixed and suffixed with %', function (): void {
|
||||
// GIVEN
|
||||
$validValue = '%12345%';
|
||||
|
||||
// WHEN
|
||||
$incoIdQuery = new IncoIdQuery($validValue);
|
||||
|
||||
// THEN
|
||||
expect($incoIdQuery->value)->toBe('%12345%');
|
||||
});
|
||||
|
||||
it('can be instantiated with wildcards anywhere in the digit string', function (): void {
|
||||
// GIVEN
|
||||
$validValue = '%12%345%';
|
||||
|
||||
// WHEN
|
||||
$incoIdQuery = new IncoIdQuery($validValue);
|
||||
|
||||
// THEN
|
||||
expect($incoIdQuery->value)->toBe('%12%345%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IncoIdQuery Value Object Domain Rules', function (): void {
|
||||
it('throws DomainException when instantiated with an empty string', function (): void {
|
||||
// GIVEN
|
||||
$invalidValue = '';
|
||||
|
||||
// WHEN & THEN
|
||||
expect(fn () => new IncoIdQuery($invalidValue))
|
||||
->toThrow(\DomainException::class, 'value cannot be empty');
|
||||
});
|
||||
|
||||
it('throws DomainException when instantiated with a non-digit string', function (): void {
|
||||
// GIVEN
|
||||
$invalidValue = 'abc123';
|
||||
|
||||
// WHEN & THEN
|
||||
expect(fn () => new IncoIdQuery($invalidValue))
|
||||
->toThrow(\DomainException::class, 'value must be a string of digits optionally prefixed and/or suffixed with a % sign');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue