feat(inco): restrict inco sheets list and search to short projection and remove short query parameter

This commit is contained in:
Jérémy Munsch 2026-09-18 10:07:04 +02:00
parent 2b8aae51af
commit 10674f3295
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
9 changed files with 24 additions and 220 deletions

View file

@ -41,7 +41,7 @@ final readonly class IncoSheetsIndexController
pagination: $request->pagination(),
sorter: $request->sorter(),
filter: $request->filter(),
short: $request->isShort(),
short: true,
);
}
}

View file

@ -41,7 +41,7 @@ final readonly class IncoSheetsSearchController
{
return new SearchInput(
$request->incoIdSearch(),
$request->isShort(),
true,
$request->pagination(),
...$request->brands()
);

View file

@ -35,7 +35,6 @@ final class IncoSheetsIndexRequest extends FormRequest
'since' => ['sometimes', 'date'],
'brands' => ['sometimes', 'array'],
'brands.*' => ['sometimes', 'string'],
'short' => ['sometimes', Rule::in(['true', 'false', true, false, '1', '0', 1, 0])],
];
}
@ -86,9 +85,4 @@ final class IncoSheetsIndexRequest extends FormRequest
default => \SortDirection::Ascending,
};
}
public function isShort(): bool
{
return filter_var($this->input('short', false), FILTER_VALIDATE_BOOLEAN);
}
}

View file

@ -7,7 +7,6 @@ namespace Presentation\Api\V1\Http\Requests\IncoSheets;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/**
* @method \Shared\Pagination pagination(int $defaultPageSize = 25)
@ -22,7 +21,6 @@ final class IncoSheetsSearchRequest extends FormRequest
'incoIdLike' => ['required', 'string'],
'brands' => ['sometimes', 'array'],
'brands.*' => ['sometimes', 'string'],
'short' => ['sometimes', Rule::in(['true', 'false', true, false, '1', '0', 1, 0])],
];
}
@ -47,9 +45,4 @@ final class IncoSheetsSearchRequest extends FormRequest
return $result;
}
public function isShort(): bool
{
return filter_var($this->input('short', false), FILTER_VALIDATE_BOOLEAN);
}
}

View file

@ -6,19 +6,9 @@ namespace Presentation\Api\V1\Http\Responses\IncoSheets;
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedIncoSheet;
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet;
use Domain\Models\ValueObjects\Inco\Additive;
use Domain\Models\ValueObjects\Inco\Allergen;
use Illuminate\Http\JsonResponse;
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\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;
use Shared\Time\Utc;
@ -51,7 +41,14 @@ final readonly class IncoSheetsIndexResponse implements ApiResponse
$items = array_map(
fn (PaginatedIncoSheet | PaginatedShortIncoSheet $sheet): array => match (true) {
$sheet instanceof PaginatedShortIncoSheet => $this->fromPaginatedShortIncoSheet($sheet)->toArray(),
$sheet instanceof PaginatedIncoSheet => $this->fromPaginatedIncoSheet($sheet)->toArray(),
$sheet instanceof PaginatedIncoSheet => (new IncoShortSheetDto(
incoId: (string) $sheet->id,
name: $sheet->name,
label: $sheet->label,
description: $sheet->description,
createdAt: Utc::toNullableRfc3339($sheet->createdAt),
updatedAt: Utc::toNullableRfc3339($sheet->updatedAt),
))->toArray(),
},
$this->view->incoSheets
);
@ -73,40 +70,4 @@ final readonly class IncoSheetsIndexResponse implements ApiResponse
updatedAt: Utc::toNullableRfc3339($sheet->updatedAt),
);
}
private function fromPaginatedIncoSheet(PaginatedIncoSheet $sheet): IncoSheetDto
{
$ingGroups = $sheet->ingredients->groups;
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
return new IncoSheetDto(
incoId: (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): string => $a->label,
$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
)),
incoCompoId: $sheet->compo !== null ? (string) $sheet->compo : null,
createdAt: Utc::toNullableRfc3339($sheet->createdAt),
updatedAt: Utc::toNullableRfc3339($sheet->updatedAt),
);
}
}

View file

@ -6,19 +6,9 @@ namespace Presentation\Api\V1\Http\Responses\IncoSheets;
use Application\Arcadia\Usecase\IncoSheets\Search\PaginatedIncoSheet;
use Application\Arcadia\Usecase\IncoSheets\Search\PaginatedShortIncoSheet;
use Domain\Models\ValueObjects\Inco\Additive;
use Domain\Models\ValueObjects\Inco\Allergen;
use Illuminate\Http\JsonResponse;
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\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;
use Shared\Time\Utc;
@ -51,7 +41,14 @@ final readonly class IncoSheetsSearchResponse implements ApiResponse
$items = array_map(
fn (PaginatedIncoSheet | PaginatedShortIncoSheet $sheet): array => match (true) {
$sheet instanceof PaginatedShortIncoSheet => $this->fromPaginatedIncoSheetToShort($sheet)->toArray(),
$sheet instanceof PaginatedIncoSheet => $this->fromPaginatedIncoSheetToFull($sheet)->toArray(),
$sheet instanceof PaginatedIncoSheet => (new IncoShortSheetDto(
incoId: (string) $sheet->id,
name: $sheet->name,
label: $sheet->label,
description: $sheet->description,
createdAt: Utc::toNullableRfc3339($sheet->createdAt),
updatedAt: Utc::toNullableRfc3339($sheet->updatedAt),
))->toArray(),
},
$this->view->incoSheets
);
@ -73,40 +70,4 @@ final readonly class IncoSheetsSearchResponse implements ApiResponse
updatedAt: Utc::toNullableRfc3339($sheet->updatedAt),
);
}
private function fromPaginatedIncoSheetToFull(PaginatedIncoSheet $sheet): IncoSheetDto
{
$ingGroups = $sheet->ingredients->groups;
$ingObject = empty($ingGroups) ? (object) [] : (object) $ingGroups;
return new IncoSheetDto(
incoId: (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): string => $a->label,
$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
)),
incoCompoId: $sheet->compo !== null ? (string) $sheet->compo : null,
createdAt: Utc::toNullableRfc3339($sheet->createdAt),
updatedAt: Utc::toNullableRfc3339($sheet->updatedAt),
);
}
}

View file

@ -106,9 +106,7 @@ describe('IncoSheets Index Feature API', function (): void {
// THEN
$response->assertStatus(200);
$response->assertJsonPath('incoSheets.0.incoId', '9999001')
->assertJsonPath('incoSheets.0.brand.code', $brand->code->value)
->assertJsonPath('incoSheets.0.brand.label', 'Le Gaulois Professionnel');
$response->assertJsonPath('incoSheets.0.incoId', '9999001');
});
it('returns 422 Unprocessable Entity when brand code format is invalid', function (): void {
@ -123,13 +121,13 @@ describe('IncoSheets Index Feature API', function (): void {
$response->assertStatus(422);
});
it('returns 200 OK with short form inco sheets when short parameter is true', function (): void {
it('returns 200 OK with short form inco sheets structure', function (): void {
// GIVEN
/** @var \Tests\TestCase $test */
$test = $this;
// WHEN
$response = $test->getJson('/api/v1/inco-sheets?short=true');
$response = $test->getJson('/api/v1/inco-sheets');
// THEN
$response->assertStatus(200);
@ -154,34 +152,4 @@ describe('IncoSheets Index Feature API', function (): void {
->and($firstItem)->not->toHaveKey('ingredients')
->and($firstItem)->not->toHaveKey('brand');
});
it('returns 200 OK with short form inco sheets when short parameter is numeric 1', function (): void {
// GIVEN
/** @var \Tests\TestCase $test */
$test = $this;
// WHEN
$response = $test->getJson('/api/v1/inco-sheets?short=1');
// THEN
$response->assertStatus(200);
$response->assertJsonStructure([
'incoSheets' => [
'*' => [
'incoId',
'name',
'label',
'description',
'createdAt',
'updatedAt',
],
],
'pagination',
]);
$firstItem = $response->json('incoSheets.0');
expect($firstItem)
->toHaveKeys(['incoId', 'name', 'label', 'description', 'createdAt', 'updatedAt'])
->and($firstItem)->not->toHaveKey('ingredients');
});
});

View file

@ -84,8 +84,7 @@ describe('IncoSheets Search Feature API', function (): void {
// THEN
$response->assertStatus(200);
$response->assertJsonCount(1, 'incoSheets');
$response->assertJsonPath('incoSheets.0.incoId', '1000101')
->assertJsonPath('incoSheets.0.brand.code', $brand->code->value);
$response->assertJsonPath('incoSheets.0.incoId', '1000101');
});
it('returns 422 Unprocessable Entity when brand code format is invalid', function (): void {
@ -101,14 +100,14 @@ describe('IncoSheets Search Feature API', function (): void {
$response->assertStatus(422);
});
it('returns 200 OK with short form inco sheets when short parameter is true', function (): void {
it('returns 200 OK with short form inco sheets structure on search', 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");
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}");
// THEN
$response->assertStatus(200);
@ -134,37 +133,6 @@ describe('IncoSheets Search Feature API', function (): void {
->and($firstItem)->not->toHaveKey('brand');
});
it('returns 200 OK with short form inco sheets when short parameter is numeric 1', function (): void {
// GIVEN
/** @var \Tests\TestCase $test */
$test = $this;
$query = urlencode('%10001%');
// WHEN
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&short=1");
// THEN
$response->assertStatus(200);
$response->assertJsonStructure([
'incoSheets' => [
'*' => [
'incoId',
'name',
'label',
'description',
'createdAt',
'updatedAt',
],
],
'pagination',
]);
$firstItem = $response->json('incoSheets.0');
expect($firstItem)
->toHaveKeys(['incoId', 'name', 'label', 'description', 'createdAt', 'updatedAt'])
->and($firstItem)->not->toHaveKey('ingredients');
});
it('returns 200 OK with empty incoSheets list when no matching inco sheets found', function (): void {
// GIVEN
/** @var \Tests\TestCase $test */

View file

@ -36,26 +36,6 @@ describe('IncoSheet OpenAPI Conformance', function (): void {
$response->assertValidResponse(200);
});
it('validates GET /api/v1/inco-sheets list response with short projection against openapi spec', function (): void {
// GIVEN
/** @var \Tests\TestCase $test */
$test = $this;
IncoFactory::new()->create([
'NO_ART' => 10002,
'DENOM_LEG' => 'Salade Niçoise 300g',
]);
// WHEN
/** @var \Illuminate\Testing\TestResponse<\Symfony\Component\HttpFoundation\Response> $response */
$response = $test->getJson('/api/v1/inco-sheets?short=true');
// THEN
$response->assertStatus(200);
$response->assertValidRequest();
$response->assertValidResponse(200);
});
it('validates GET /api/v1/inco-sheets list response with brands filter against openapi spec', function (): void {
// GIVEN
/** @var \Tests\TestCase $test */
@ -142,25 +122,4 @@ describe('IncoSheet OpenAPI Conformance', function (): void {
$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%');
IncoFactory::new()->create([
'NO_ART' => 1000402,
'DENOM_LEG' => '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);
});
});