[API][Admin] Validate product variants on product images

This commit is contained in:
Grzegorz Sadowski 2023-11-13 13:15:37 +01:00
parent 078ea0436c
commit 2bb655e2ee
No known key found for this signature in database
GPG key ID: EF87FF4E6E3BD364
12 changed files with 261 additions and 2 deletions

View file

@ -97,5 +97,9 @@
<argument type="service" id="sylius.repository.product" />
<tag name="validator.constraint_validator" alias="sylius_product_code_exists" />
</service>
<service id="sylius.validator.sylius_product_image_variants_belong_to_owner" class="Sylius\Bundle\CoreBundle\Validator\Constraints\ProductImageVariantsBelongToOwnerValidator">
<tag name="validator.constraint_validator" alias="sylius_product_image_variants_belong_to_owner" />
</service>
</services>
</container>

View file

@ -13,6 +13,10 @@
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Component\Core\Model\ProductImage">
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ProductImageVariantsBelongToOwner">
<option name="groups">sylius</option>
</constraint>
<property name="file">
<constraint name="Image">
<option name="maxSize">10M</option>

View file

@ -98,6 +98,8 @@ sylius:
file:
max_size: The image is too big - {{ size }}{{ suffix }}. Maximum allowed size is {{ limit }}{{ suffix }}.
upload_ini_size: The image is too big. Maximum allowed size is {{ limit }}{{ suffix }}.
product_variant:
not_belong_to_owner: 'The product variant with code "%productVariantCode%" does not belong to the product with "%ownerCode%", which is the owner of the image.'
product_taxon:
unique: Product taxons cannot be duplicated.
product:

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
final class ProductImageVariantsBelongToOwner extends Constraint
{
public string $message = 'sylius.product_image.product_variant.not_belong_to_owner';
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy(): string
{
return 'sylius_product_image_variants_belong_to_owner';
}
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Validator\Constraints;
use Sylius\Component\Core\Model\ProductImageInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
final class ProductImageVariantsBelongToOwnerValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
/** @var ProductImageInterface $value */
Assert::isInstanceOf($value, ProductImageInterface::class);
/** @var ProductImageVariantsBelongToOwner $constraint */
Assert::isInstanceOf($constraint, ProductImageVariantsBelongToOwner::class);
/** @var ProductInterface $product */
$owner = $value->getOwner();
foreach ($value->getProductVariants() as $productVariant) {
if (!$owner->hasVariant($productVariant)) {
$this->context->addViolation($constraint->message, [
'%productVariantCode%' => $productVariant->getCode(),
'%ownerCode%' => $owner->getCode(),
]);
}
}
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\CoreBundle\Validator\Constraints;
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\CoreBundle\Validator\Constraints\ProductImageVariantsBelongToOwner;
use Sylius\Component\Core\Model\ProductImageInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
final class ProductImageVariantsBelongToOwnerValidatorSpec extends ObjectBehavior
{
function let(ExecutionContextInterface $executionContext): void
{
$this->initialize($executionContext);
}
function it_is_a_constraint_validator(): void
{
$this->shouldImplement(ConstraintValidatorInterface::class);
}
function it_throws_an_exception_if_value_is_not_product_image(): void
{
$this
->shouldThrow(\InvalidArgumentException::class)
->during('validate', [new \stdClass(), new ProductImageVariantsBelongToOwner()])
;
}
function it_throws_an_exception_if_constraint_is_not_product_image_variants_belong_to_owner(
Constraint $constraint,
ProductImageInterface $image,
): void {
$this
->shouldThrow(\InvalidArgumentException::class)
->during('validate', [$image, $constraint])
;
}
function it_adds_a_violation_if_any_variant_does_not_belong_to_a_product_which_is_an_owner(
ExecutionContextInterface $executionContext,
ProductImageInterface $image,
ProductInterface $product,
ProductVariantInterface $variant,
): void {
$image->getOwner()->willReturn($product);
$image->getProductVariants()->willReturn(new ArrayCollection([$variant->getWrappedObject()]));
$product->getCode()->willReturn('MUG');
$product->hasVariant($variant)->willReturn(false);
$variant->getCode()->willReturn('GREEN_MUG');
$executionContext
->addViolation(
'sylius.product_image.product_variant.not_belong_to_owner',
['%productVariantCode%' => 'GREEN_MUG', '%ownerCode%' => 'MUG'],
)
->shouldBeCalled()
;
$this->validate($image, new ProductImageVariantsBelongToOwner());
}
}

View file

@ -27,7 +27,7 @@ class ProductImage extends Image implements ProductImageInterface
public function __construct()
{
/** @var ArrayCollection<array-key, ProductVariantInterface> $this->productVaraints */
/** @var ArrayCollection<array-key, ProductVariantInterface> $this->productVariants */
$this->productVariants = new ArrayCollection();
}

View file

@ -146,11 +146,44 @@ final class ProductImagesTest extends JsonApiTestCase
$response = $this->client->getResponse();
$this->assertResponse(
$response,
'admin/product_image/post_product_image_with_type_response',
'admin/product_image/post_product_image_with_type_and_variant_response',
Response::HTTP_CREATED,
);
}
/** @test */
public function it_creates_a_product_image_with_invalid_variant(): void
{
$fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'product/product_image.yaml']);
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
/** @var ProductInterface $product */
$product = $fixtures['product_mug'];
/** @var ProductVariantInterface $productVariant */
$productVariant = $fixtures['product_variant_cap_yellow'];
$this->client->request(
method: 'POST',
uri: sprintf('/api/v2/admin/products/%s/images', $product->getCode()),
parameters: [
'type' => 'banner',
'productVariants' => [
sprintf('/api/v2/admin/product-variants/%s', $productVariant->getCode()),
],
],
files: ['file' => $this->getUploadedFile('fixtures/mugs.jpg', 'mugs.jpg')],
server: $header,
);
$response = $this->client->getResponse();
$this->assertResponse(
$response,
'admin/product_image/post_product_image_with_invalid_variant_response',
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}
/** @test */
public function it_updates_only_the_type_and_variants_of_the_existing_product_image(): void
{
@ -191,6 +224,36 @@ final class ProductImagesTest extends JsonApiTestCase
);
}
/** @test */
public function it_updates_the_existing_product_image_with_invalid_variant(): void
{
$fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'product/product_image.yaml']);
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
/** @var ProductImageInterface $productImage */
$productImage = $fixtures['product_mug_thumbnail'];
/** @var ProductVariantInterface $productVariant */
$productVariant = $fixtures['product_variant_cap_yellow'];
$this->client->request(
method: 'PUT',
uri: sprintf('/api/v2/admin/product-images/%s', $productImage->getId()),
server: $header,
content: json_encode([
'productVariants' => [
sprintf('/api/v2/admin/product-variants/%s', $productVariant->getCode()),
],
], JSON_THROW_ON_ERROR),
);
$this->assertResponse(
$this->client->getResponse(),
'admin/product_image/put_product_image_with_invalid_variant_response',
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}
/** @test */
public function it_deletes_a_product_image(): void
{

View file

@ -21,6 +21,11 @@ Sylius\Component\Core\Model\ProductVariant:
product: '@product_mug'
fallbackLocale: 'en_US'
currentLocale: 'en'
product_variant_cap_yellow:
code: 'CAP_YELLOW'
product: '@product_cap'
fallbackLocale: 'en_US'
currentLocale: 'en'
Sylius\Component\Core\Model\ProductImage:
product_mug_thumbnail:

View file

@ -0,0 +1,13 @@
{
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "The product variant with code \"CAP_YELLOW\" does not belong to the product with \"MUG\", which is the owner of the image.",
"violations": [
{
"propertyPath": "",
"message": "The product variant with code \"CAP_YELLOW\" does not belong to the product with \"MUG\", which is the owner of the image.",
"code": null
}
]
}

View file

@ -0,0 +1,13 @@
{
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "The product variant with code \"CAP_YELLOW\" does not belong to the product with \"MUG\", which is the owner of the image.",
"violations": [
{
"propertyPath": "",
"message": "The product variant with code \"CAP_YELLOW\" does not belong to the product with \"MUG\", which is the owner of the image.",
"code": null
}
]
}