Repository: Sylius/Sylius
Pull request #18032: [PHPSpec to PHPUnit] Attribute Bundle
Author: login: rust-le
Labels: name: DX
description: Issues and PRs aimed at improving Developer eXperience.
name: Maintenance
description: CI configurations, READMEs, releases, etc.
Created: 2025-05-15T12:07:41Z | Merged: 2025-05-20T04:46:45Z

| Q               | A
|-----------------|-----
| Branch?         | 2.1
| Bug fix?        | no
| New feature?    | no
| BC breaks?      | no
| Deprecations?   | no
| Related tickets | OP-571
| License         | MIT

Rewrite PHPSpec tests to PHPUnit in Attribute Bundle
<!--
 - Bug fixes must be submitted against the 1.14 or 2.0 branch
 - Features and deprecations must be submitted against the 2.1 branch
 - Make sure that the correct base branch is set

 To be sure you are not breaking any Backward Compatibilities, check the documentation:
 https://docs.sylius.com/en/latest/book/organization/backward-compatibility-promise.html
-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Tests**
  - Migrated test suite from PhpSpec to PHPUnit for improved compatibility and maintenance.
  - Added comprehensive PHPUnit tests for attribute validation and Doctrine event subscribers.
  - Updated test configuration to support PHPUnit 10.
- **Chores**
  - Removed legacy PhpSpec test files and configuration.
  - Updated development dependencies and autoload settings to reflect the new test structure.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php
new file mode 100644
index 00000000000..67b9e344744
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php
@@ -0,0 +1,221 @@
+<?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\AttributeBundle\Tests\Doctrine\ORM\Subscriber;
+
+use Doctrine\Common\EventSubscriber;
+use Doctrine\ORM\EntityManager;
+use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
+use Doctrine\ORM\Mapping\ClassMetadata;
+use Doctrine\ORM\Mapping\ClassMetadataFactory;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Doctrine\ORM\Subscriber\LoadMetadataSubscriber;
+
+final class LoadMetadataSubscriberTest extends TestCase
+{
+    private LoadMetadataSubscriber $loadMetadataSubscriber;
+
+    private LoadClassMetadataEventArgs&MockObject $eventArgs;
+
+    private ClassMetadata&MockObject $metadata;
+
+    private EntityManager&MockObject $entityManager;
+
+    private ClassMetadataFactory&MockObject $classMetadataFactory;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->loadMetadataSubscriber = new LoadMetadataSubscriber([
+            'product' => [
+                'subject' => 'Some\App\Product\Entity\Product',
+                'attribute' => [
+                    'classes' => [
+                        'model' => 'Some\App\Product\Entity\Attribute',
+                    ],
+                ],
+                'attribute_value' => [
+                    'classes' => [
+                        'model' => 'Some\App\Product\Entity\AttributeValue',
+                    ],
+                ],
+            ],
+        ]);
+        $this->eventArgs = $this->createMock(LoadClassMetadataEventArgs::class);
+        $this->metadata = $this->createMock(ClassMetadata::class);
+        $this->entityManager = $this->createMock(EntityManager::class);
+        $this->classMetadataFactory = $this->createMock(ClassMetadataFactory::class);
+    }
+
+    public function testADoctrineEventSubscriber(): void
+    {
+        self::assertInstanceOf(EventSubscriber::class, $this->loadMetadataSubscriber);
+    }
+
+    public function testSubscribesLoadClassMetadataEvent(): void
+    {
+        self::assertSame(['loadClassMetadata'], $this->loadMetadataSubscriber->getSubscribedEvents());
+    }
+
+    public function testMapsManyToOneAssociationsFromAttributeValueModelToSubjectModelAndAttributeModel(): void
+    {
+        $this->eventArgs->expects(self::once())
+            ->method('getClassMetadata')
+            ->willReturn($this->metadata);
+
+        $this->eventArgs->expects(self::once())
+            ->method('getEntityManager')
+            ->willReturn($this->entityManager);
+
+        $this->entityManager->expects(self::once())
+            ->method('getMetadataFactory')
+            ->willReturn($this->classMetadataFactory);
+
+        $this->metadata->fieldMappings = [
+            'id' => [
+                'columnName' => 'id',
+                'type' => 'integer',
+                'id' => true,
+                'nullable' => false,
+                'fieldName' => 'id',
+            ],
+        ];
+
+        $this->classMetadataFactory->expects(self::exactly(2))
+            ->method('getMetadataFor')
+            ->willReturnMap([
+                ['Some\App\Product\Entity\Product', $this->metadata],
+                ['Some\App\Product\Entity\Attribute', $this->metadata],
+            ]);
+
+        $this->metadata->expects(self::once())
+            ->method('getName')
+            ->willReturn('Some\App\Product\Entity\AttributeValue');
+
+        $this->metadata->expects(self::exactly(2))
+            ->method('hasAssociation')
+            ->willReturnMap([
+                ['subject', false],
+                ['attribute', false],
+            ]);
+
+        $subjectMapping = [
+            'fieldName' => 'subject',
+            'targetEntity' => 'Some\App\Product\Entity\Product',
+            'inversedBy' => 'attributes',
+            'joinColumns' => [[
+                'name' => 'product_id',
+                'referencedColumnName' => 'id',
+                'nullable' => false,
+                'onDelete' => 'CASCADE',
+            ]],
+        ];
+
+        $attributeMapping = [
+            'fieldName' => 'attribute',
+            'targetEntity' => 'Some\App\Product\Entity\Attribute',
+            'joinColumns' => [[
+                'name' => 'attribute_id',
+                'referencedColumnName' => 'id',
+                'nullable' => false,
+            ]],
+        ];
+
+        $this->metadata->expects(self::exactly(2))
+            ->method('mapManyToOne')
+            ->willReturnCallback(function ($mapping) use ($subjectMapping, $attributeMapping) {
+                static $callCount = 0;
+                ++$callCount;
+
+                if ($callCount === 1) {
+                    self::assertEquals($subjectMapping, $mapping);
+                } elseif ($callCount === 2) {
+                    self::assertEquals($attributeMapping, $mapping);
+                }
+            });
+
+        $this->loadMetadataSubscriber->loadClassMetadata($this->eventArgs);
+    }
+
+    public function testDoesNotMapRelationsForAttributeValueModelIfTheRelationsAlreadyExist(): void
+    {
+        $this->eventArgs->expects(self::once())
+            ->method('getClassMetadata')
+            ->willReturn($this->metadata);
+
+        $this->eventArgs->expects(self::once())
+            ->method('getEntityManager')
+            ->willReturn($this->entityManager);
+
+        $this->entityManager->expects(self::once())
+            ->method('getMetadataFactory')
+            ->willReturn($this->classMetadataFactory);
+
+        $this->metadata->expects(self::once())
+            ->method('getName')
+            ->willReturn('Some\App\Product\Entity\AttributeValue');
+
+        $this->metadata->expects(self::exactly(2))
+            ->method('hasAssociation')
+            ->willReturnMap([['subject', true], ['attribute', true]]);
+
+        $this->metadata->expects(self::never())->method('mapManyToOne');
+
+        $this->loadMetadataSubscriber->loadClassMetadata($this->eventArgs);
+    }
+
+    public function testDoesNotAddAManyToOneMappingIfTheClassIsNotAConfiguredAttributeValueModel(): void
+    {
+        $this->eventArgs->expects(self::once())
+            ->method('getEntityManager')
+            ->willReturn($this->entityManager);
+
+        $this->entityManager->expects(self::once())
+            ->method('getMetadataFactory')
+            ->willReturn($this->classMetadataFactory);
+
+        $this->eventArgs->expects(self::once())
+            ->method('getClassMetadata')
+            ->willReturn($this->metadata);
+
+        $this->metadata->expects(self::once())
+            ->method('getName')
+            ->willReturn('KeepMoving\ThisClass\DoesNot\Concern\You');
+
+        $this->metadata->expects(self::never())->method('mapManyToOne');
+
+        $this->loadMetadataSubscriber->loadClassMetadata($this->eventArgs);
+    }
+
+    public function testDoesNotAddValuesOneToManyMappingIfTheClassIsNotAConfiguredAttributeModel(): void
+    {
+        $this->eventArgs->expects(self::once())
+            ->method('getEntityManager')
+            ->willReturn($this->entityManager);
+
+        $this->entityManager->expects(self::once())
+            ->method('getMetadataFactory')
+            ->willReturn($this->classMetadataFactory);
+
+        $this->eventArgs->expects(self::once())->method('getClassMetadata')->willReturn($this->metadata);
+
+        $this->metadata->expects(self::once())
+            ->method('getName')
+            ->willReturn('KeepMoving\ThisClass\DoesNot\Concern\You');
+
+        $this->metadata->expects(self::never())->method('mapOneToMany');
+
+        $this->loadMetadataSubscriber->loadClassMetadata($this->eventArgs);
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/AttributeTypeValidatorTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/AttributeTypeValidatorTest.php
new file mode 100644
index 00000000000..017f28c9c70
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/AttributeTypeValidatorTest.php
@@ -0,0 +1,135 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\AttributeType;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\AttributeTypeValidator;
+use Sylius\Component\Attribute\Model\AttributeInterface;
+use Sylius\Component\Registry\ServiceRegistryInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
+
+final class AttributeTypeValidatorTest extends TestCase
+{
+    private MockObject&ServiceRegistryInterface $attributeTypeRegistry;
+
+    private ExecutionContextInterface&MockObject $contextMock;
+
+    private AttributeTypeValidator $attributeTypeValidator;
+
+    private AttributeInterface&MockObject $attribute;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->attributeTypeRegistry = $this->createMock(ServiceRegistryInterface::class);
+        $this->contextMock = $this->createMock(ExecutionContextInterface::class);
+        $this->attribute = $this->createMock(AttributeInterface::class);
+        $this->attributeTypeValidator = new AttributeTypeValidator($this->attributeTypeRegistry);
+        $this->initialize($this->contextMock);
+    }
+
+    private function initialize(ExecutionContextInterface $context): void
+    {
+        $this->attributeTypeValidator->initialize($context);
+    }
+
+    public function testThrowsExceptionWhenValueIsNotAnAttribute(): void
+    {
+        self::expectException(\InvalidArgumentException::class);
+
+        $this->attributeTypeValidator->validate(new \stdClass(), new AttributeType());
+    }
+
+    public function testThrowsExceptionWhenConstraintIsNotAnAttributeType(): void
+    {
+        /** @var Constraint&MockObject $constraint */
+        $constraint = $this->createMock(Constraint::class);
+
+        self::expectException(\InvalidArgumentException::class);
+
+        $this->attributeTypeValidator->validate($this->attribute, $constraint);
+    }
+
+    public function testDoesNothingWhenAttributeTypeIsNull(): void
+    {
+        $this->attribute->expects(self::once())->method('getType')->willReturn(null);
+
+        $this->attributeTypeRegistry->expects(self::never())->method('has');
+
+        $this->contextMock->expects(self::never())->method('addViolation');
+
+        $this->attributeTypeValidator->validate($this->attribute, new AttributeType());
+    }
+
+    public function testDoesNothingWhenAttributeTypeIsRegistered(): void
+    {
+        $this->attribute->expects(self::once())->method('getType')->willReturn('foo');
+
+        $this->attributeTypeRegistry->expects(self::once())
+            ->method('has')
+            ->with('foo')
+            ->willReturn(true);
+
+        $this->contextMock->expects(self::never())->method('addViolation');
+
+        $this->attributeTypeValidator->validate($this->attribute, new AttributeType());
+    }
+
+    public function testAddsViolationWhenAttributeTypeIsNotRegistered(): void
+    {
+        /** @var ConstraintViolationBuilderInterface&MockObject $violationBuilder */
+        $violationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class);
+        $constraint = new AttributeType();
+
+        $this->attribute->expects(self::once())->method('getType')->willReturn('foo');
+
+        $this->attributeTypeRegistry->expects(self::once())
+            ->method('has')
+            ->with('foo')
+            ->willReturn(false);
+
+        $this->attributeTypeRegistry->expects(self::once())
+            ->method('all')
+            ->willReturn(
+                [
+                    'foo_attribute_name' => 'foo_value',
+                    'bar_attribute_name' => 'bar_value',
+                ],
+            );
+
+        $this->contextMock->expects(self::once())
+            ->method('buildViolation')
+            ->with(
+                $constraint->unregisteredAttributeTypeMessage,
+                [
+                    '%type%' => 'foo',
+                    '%available_types%' => 'foo_attribute_name, bar_attribute_name',
+                ],
+            )
+            ->willReturn($violationBuilder);
+
+        $violationBuilder->expects(self::once())
+            ->method('atPath')
+            ->with('type')
+            ->willReturn($violationBuilder);
+
+        $violationBuilder->expects(self::once())->method('addViolation');
+
+        $this->attributeTypeValidator->validate($this->attribute, $constraint);
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php
new file mode 100644
index 00000000000..e827bd277da
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php
@@ -0,0 +1,39 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValue;
+use Symfony\Component\Validator\Constraint;
+
+final class ValidAttributeValueTest extends TestCase
+{
+    private ValidAttributeValue $validAttributeValue;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->validAttributeValue = new ValidAttributeValue();
+    }
+
+    public function testHasTargets(): void
+    {
+        self::assertSame(Constraint::CLASS_CONSTRAINT, $this->validAttributeValue->getTargets());
+    }
+
+    public function testValidatedBySpecificValidator(): void
+    {
+        self::assertSame('sylius_valid_attribute_value_validator', $this->validAttributeValue->validatedBy());
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php
new file mode 100644
index 00000000000..b33888de357
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php
@@ -0,0 +1,107 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use DateTime;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValue;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValueValidator;
+use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface;
+use Sylius\Component\Attribute\AttributeType\TextAttributeType;
+use Sylius\Component\Attribute\Model\AttributeInterface;
+use Sylius\Component\Attribute\Model\AttributeValueInterface;
+use Sylius\Component\Registry\ServiceRegistryInterface;
+use Sylius\Resource\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+final class ValidAttributeValueValidatorTest extends TestCase
+{
+    private MockObject&ServiceRegistryInterface $attributeTypesRegistry;
+
+    private ExecutionContextInterface&MockObject $context;
+
+    private ValidAttributeValueValidator $validAttributeValueValidator;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->attributeTypesRegistry = $this->createMock(ServiceRegistryInterface::class);
+        $this->context = $this->createMock(ExecutionContextInterface::class);
+        $this->validAttributeValueValidator = new ValidAttributeValueValidator($this->attributeTypesRegistry);
+        $this->initialize($this->context);
+    }
+
+    private function initialize(ExecutionContextInterface $context): void
+    {
+        $this->validAttributeValueValidator->initialize($context);
+    }
+
+    public function testValidatesAttributeValueBasedOnTheirType(): void
+    {
+        /** @var AttributeInterface&MockObject $attributeMock */
+        $attributeMock = $this->createMock(AttributeInterface::class);
+        /** @var AttributeTypeInterface&MockObject $attributeTypeMock */
+        $attributeTypeMock = $this->createMock(AttributeTypeInterface::class);
+        /** @var AttributeValueInterface&MockObject $attributeValueMock */
+        $attributeValueMock = $this->createMock(AttributeValueInterface::class);
+        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
+        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);
+
+        $attributeValueMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(TextAttributeType::TYPE);
+
+        $this->attributeTypesRegistry->expects(self::once())
+            ->method('get')
+            ->with('text')
+            ->willReturn($attributeTypeMock);
+
+        $attributeValueMock->expects(self::once())
+            ->method('getAttribute')
+            ->willReturn($attributeMock);
+
+        $attributeMock->expects(self::once())
+            ->method('getConfiguration')
+            ->willReturn(['min' => 2, 'max' => 255]);
+
+        $attributeTypeMock->expects(self::once())
+            ->method('validate')
+            ->with(
+                $attributeValueMock,
+                $this->isInstanceOf(ExecutionContextInterface::class),
+                [
+                    'min' => 2,
+                    'max' => 255,
+                ],
+            );
+
+        $this->validAttributeValueValidator->validate($attributeValueMock, $attributeValueConstraintMock);
+    }
+
+    public function testThrowsExceptionIfValidatedValueIsNotAttributeValue(): void
+    {
+        /** @var DateTime&MockObject $badObjectMock */
+        $badObjectMock = $this->createMock(DateTime::class);
+        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
+        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);
+
+        self::expectException(UnexpectedTypeException::class);
+        self::expectExceptionMessage(
+            'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "string" given.',
+        );
+
+        $this->validAttributeValueValidator->validate($badObjectMock, $attributeValueConstraintMock);
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php
new file mode 100644
index 00000000000..a131b19d23f
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php
@@ -0,0 +1,39 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfiguration;
+use Symfony\Component\Validator\Constraint;
+
+final class ValidSelectAttributeConfigurationTest extends TestCase
+{
+    private ValidSelectAttributeConfiguration $validSelectAttributeConfiguration;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->validSelectAttributeConfiguration = new ValidSelectAttributeConfiguration();
+    }
+
+    public function testHasTargets(): void
+    {
+        self::assertSame(Constraint::CLASS_CONSTRAINT, $this->validSelectAttributeConfiguration->getTargets());
+    }
+
+    public function testValidatedBySpecificValidator(): void
+    {
+        self::assertSame('sylius_valid_select_attribute_validator', $this->validSelectAttributeConfiguration->validatedBy());
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php
new file mode 100644
index 00000000000..813497a8a2a
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php
@@ -0,0 +1,150 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfiguration;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfigurationValidator;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidTextAttributeConfiguration;
+use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
+use Sylius\Component\Attribute\AttributeType\TextAttributeType;
+use Sylius\Component\Attribute\Model\AttributeInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+final class ValidSelectAttributeConfigurationValidatorTest extends TestCase
+{
+    private ExecutionContextInterface&MockObject $context;
+
+    private ValidSelectAttributeConfigurationValidator $validator;
+
+    private AttributeInterface&MockObject $attributeMock;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->context = $this->createMock(ExecutionContextInterface::class);
+        $this->validator = new ValidSelectAttributeConfigurationValidator();
+        $this->attributeMock = $this->createMock(AttributeInterface::class);
+        $this->initialize($this->context);
+    }
+
+    private function initialize(ExecutionContextInterface $context): void
+    {
+        $this->validator->initialize($context);
+    }
+
+    public function testAddsAViolationIfMaxEntriesValueIsLowerThanMinEntriesValue(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(SelectAttributeType::TYPE);
+
+        $this->attributeMock->expects(self::atLeastOnce())->method('getConfiguration')->willReturn(['multiple' => true, 'min' => 6, 'max' => 4]);
+
+        $this->context->expects(self::once())->method('addViolation');
+
+        $this->validator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testAddsAViolationIfMinEntriesValueIsGreaterThanTheNumberOfAddedChoices(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(SelectAttributeType::TYPE);
+
+        $this->attributeMock->expects(self::atLeastOnce())->method('getConfiguration')->willReturn([
+            'multiple' => true,
+            'min' => 4,
+            'max' => 6,
+            'choices' => [
+                'ec134e10-6a80-4eaf-8346-e9bb0f7406a4' => 'Banana',
+                '63148775-be39-47eb-8afd-a4818981e3c0' => 'Watermelon',
+            ],
+        ]);
+
+        $this->context->expects(self::once())->method('addViolation');
+
+        $this->validator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testAddsAViolationIfMultipleIsNotTrueWhenMinOrMaxEntriesValuesAreSpecified(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(SelectAttributeType::TYPE);
+
+        $this->attributeMock->expects(self::atLeastOnce())
+            ->method('getConfiguration')
+            ->willReturn([
+                'multiple' => false,
+                'min' => 4,
+                'max' => 6,
+            ]);
+
+        $this->context->expects(self::once())->method('addViolation');
+
+        $this->validator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testAddsAViolationIfMultipleIsNotSetWhenMinOrMaxEntriesValuesAreSpecified(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(SelectAttributeType::TYPE);
+
+        $this->attributeMock->expects(self::atLeastOnce())
+            ->method('getConfiguration')
+            ->willReturn([
+                'min' => 4,
+                'max' => 6,
+            ]);
+
+        $this->context->expects(self::once())->method('addViolation');
+
+        $this->validator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testDoesNothingIfAnAttributeIsNotASelectType(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(TextAttributeType::TYPE);
+
+        $this->context->expects(self::never())->method('addViolation');
+
+        $this->validator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testThrowsAnExceptionIfValidatedValueIsNotAnAttribute(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        self::expectException(\InvalidArgumentException::class);
+
+        $this->validator->validate('badObject', $constraint);
+    }
+
+    public function testThrowsAnExceptionIfConstraintIsNotAValidSelectAttributeConfigurationConstraint(): void
+    {
+        $constraint = new ValidTextAttributeConfiguration();
+        self::expectException(\InvalidArgumentException::class);
+
+        $this->validator->validate($this->attributeMock, $constraint);
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php
new file mode 100644
index 00000000000..d04279e5da3
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php
@@ -0,0 +1,42 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidTextAttributeConfiguration;
+use Symfony\Component\Validator\Constraint;
+
+final class ValidTextAttributeConfigurationTest extends TestCase
+{
+    private ValidTextAttributeConfiguration $validTextAttributeConfiguration;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->validTextAttributeConfiguration = new ValidTextAttributeConfiguration();
+    }
+
+    public function testHasTargets(): void
+    {
+        self::assertSame(Constraint::CLASS_CONSTRAINT, $this->validTextAttributeConfiguration->getTargets());
+    }
+
+    public function testValidatedBySpecificValidator(): void
+    {
+        self::assertSame(
+            'sylius_valid_text_attribute_validator',
+            $this->validTextAttributeConfiguration->validatedBy(),
+        );
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php
new file mode 100644
index 00000000000..7c88e3095f7
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php
@@ -0,0 +1,93 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfiguration;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidTextAttributeConfiguration;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidTextAttributeConfigurationValidator;
+use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
+use Sylius\Component\Attribute\AttributeType\TextAttributeType;
+use Sylius\Component\Attribute\Model\AttributeInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+final class ValidTextAttributeConfigurationValidatorTest extends TestCase
+{
+    private ExecutionContextInterface&MockObject $context;
+
+    private ValidTextAttributeConfigurationValidator $validTextAttributeConfigurationValidator;
+
+    private AttributeInterface&MockObject $attributeMock;
+
+    protected function setUp(): void
+    {
+        $this->context = $this->createMock(ExecutionContextInterface::class);
+        $this->validTextAttributeConfigurationValidator = new ValidTextAttributeConfigurationValidator();
+        $this->initialize($this->context);
+        $this->attributeMock = $this->createMock(AttributeInterface::class);
+    }
+
+    private function initialize(ExecutionContextInterface $context): void
+    {
+        $this->validTextAttributeConfigurationValidator->initialize($context);
+    }
+
+    public function testAddsAViolationIfMaxEntriesValueIsLowerThanMinEntriesValue(): void
+    {
+        $constraint = new ValidTextAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(TextAttributeType::TYPE);
+
+        $this->attributeMock->expects(self::once())
+            ->method('getConfiguration')
+            ->willReturn([
+                'min' => 6,
+                'max' => 4,
+            ]);
+
+        $this->context->expects(self::once())->method('addViolation');
+
+        $this->validTextAttributeConfigurationValidator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testDoesNothingIfAnAttributeIsNotATextType(): void
+    {
+        $constraint = new ValidTextAttributeConfiguration();
+        $this->attributeMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(SelectAttributeType::TYPE);
+
+        $this->context->expects(self::never())->method('addViolation');
+
+        $this->validTextAttributeConfigurationValidator->validate($this->attributeMock, $constraint);
+    }
+
+    public function testThrowsAnExceptionIfValidatedValueIsNotAnAttribute(): void
+    {
+        $constraint = new ValidTextAttributeConfiguration();
+        self::expectException(\InvalidArgumentException::class);
+
+        $this->validTextAttributeConfigurationValidator->validate('badObject', $constraint);
+    }
+
+    public function testThrowsAnExceptionIfConstraintIsNotAValidTextAttributeConfigurationConstraint(): void
+    {
+        $constraint = new ValidSelectAttributeConfiguration();
+        self::expectException(\InvalidArgumentException::class);
+
+        $this->validTextAttributeConfigurationValidator->validate($this->attributeMock, $constraint);
+    }
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/composer.json b/src/Sylius/Bundle/AttributeBundle/composer.json
index 07b24c0a2f8..e2193378078 100644
--- a/src/Sylius/Bundle/AttributeBundle/composer.json
+++ b/src/Sylius/Bundle/AttributeBundle/composer.json
@@ -37,7 +37,6 @@
         "doctrine/doctrine-bundle": "^2.13",
         "doctrine/orm": "^2.18",
         "matthiasnoback/symfony-dependency-injection-test": "^5.1",
-        "phpspec/phpspec": "^7.5",
         "phpspec/prophecy-phpunit": "^2.2",
         "phpunit/phpunit": "^10.5",
         "symfony/browser-kit": "^6.4 || ^7.2",
@@ -68,7 +67,7 @@
     },
     "autoload-dev": {
         "psr-4": {
-            "Sylius\\Bundle\\AttributeBundle\\spec\\": "spec/"
+            "Tests\\Sylius\\Bundle\\AttributeBundle\\": "Tests/"
         },
         "classmap": [
             "test/app/AppKernel.php"
diff --git a/src/Sylius/Bundle/AttributeBundle/phpspec.yml.dist b/src/Sylius/Bundle/AttributeBundle/phpspec.yml.dist
deleted file mode 100644
index 7a5b4194981..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/phpspec.yml.dist
+++ /dev/null
@@ -1,5 +0,0 @@
-suites:
-    main:
-        namespace: Sylius\Bundle\AttributeBundle
-        psr4_prefix: Sylius\Bundle\AttributeBundle
-        src_path: .
diff --git a/src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist b/src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist
index bd0bb452f3b..69fbca56bbe 100644
--- a/src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist
+++ b/src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/8.5/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/phpunit-10.xsd"
          colors="true"
 >
     <php>
@@ -9,7 +9,7 @@
     </php>
 
     <testsuites>
-        <testsuite name="SyliusAttributeBundle Test Suite">
+        <testsuite name="sylius_attribute_bundle_test_suite">
             <directory>./test/</directory>
             <directory>./Tests/</directory>
         </testsuite>
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Doctrine/ORM/Subscriber/LoadMetadataSubscriberSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Doctrine/ORM/Subscriber/LoadMetadataSubscriberSpec.php
deleted file mode 100644
index b5c270a419c..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Doctrine/ORM/Subscriber/LoadMetadataSubscriberSpec.php
+++ /dev/null
@@ -1,163 +0,0 @@
-<?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\AttributeBundle\Doctrine\ORM\Subscriber;
-
-use Doctrine\Common\EventSubscriber;
-use Doctrine\ORM\EntityManager;
-use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
-use Doctrine\ORM\Mapping\ClassMetadata;
-use Doctrine\ORM\Mapping\ClassMetadataFactory;
-use PhpSpec\ObjectBehavior;
-use Prophecy\Argument;
-use Sylius\Bundle\AttributeBundle\Doctrine\ORM\Subscriber\LoadMetadataSubscriber;
-
-final class LoadMetadataSubscriberSpec extends ObjectBehavior
-{
-    function let(): void
-    {
-        $this->beConstructedWith([
-            'product' => [
-                'subject' => 'Some\App\Product\Entity\Product',
-                'attribute' => [
-                    'classes' => [
-                        'model' => 'Some\App\Product\Entity\Attribute',
-                    ],
-                ],
-                'attribute_value' => [
-                    'classes' => [
-                        'model' => 'Some\App\Product\Entity\AttributeValue',
-                    ],
-                ],
-            ],
-        ]);
-    }
-
-    function it_is_initializable(): void
-    {
-        $this->shouldHaveType(LoadMetadataSubscriber::class);
-    }
-
-    function it_is_a_doctrine_event_subscriber(): void
-    {
-        $this->shouldImplement(EventSubscriber::class);
-    }
-
-    function it_subscribes_load_class_metadata_event(): void
-    {
-        $this->getSubscribedEvents()->shouldReturn(['loadClassMetadata']);
-    }
-
-    function it_maps_many_to_one_associations_from_the_attribute_value_model_to_the_subject_model_and_the_attribute_model(
-        LoadClassMetadataEventArgs $eventArgs,
-        ClassMetadata $metadata,
-        EntityManager $entityManager,
-        ClassMetadataFactory $classMetadataFactory,
-        ClassMetadata $classMetadata,
-    ): void {
-        $eventArgs->getClassMetadata()->willReturn($metadata);
-        $eventArgs->getEntityManager()->willReturn($entityManager);
-        $entityManager->getMetadataFactory()->willReturn($classMetadataFactory);
-        $classMetadata->fieldMappings = [
-            'id' => [
-                'columnName' => 'id',
-            ],
-        ];
-        $classMetadataFactory->getMetadataFor('Some\App\Product\Entity\Product')->willReturn($classMetadata);
-        $classMetadataFactory->getMetadataFor('Some\App\Product\Entity\Attribute')->willReturn($classMetadata);
-
-        $metadata->getName()->willReturn('Some\App\Product\Entity\AttributeValue');
-        $metadata->hasAssociation('subject')->willReturn(false);
-        $metadata->hasAssociation('attribute')->willReturn(false);
-
-        $subjectMapping = [
-            'fieldName' => 'subject',
-            'targetEntity' => 'Some\App\Product\Entity\Product',
-            'inversedBy' => 'attributes',
-            'joinColumns' => [[
-                'name' => 'product_id',
-                'referencedColumnName' => 'id',
-                'nullable' => false,
-                'onDelete' => 'CASCADE',
-            ]],
-        ];
-
-        $attributeMapping = [
-            'fieldName' => 'attribute',
-            'targetEntity' => 'Some\App\Product\Entity\Attribute',
-            'joinColumns' => [[
-                'name' => 'attribute_id',
-                'referencedColumnName' => 'id',
-                'nullable' => false,
-            ]],
-        ];
-
-        $metadata->mapManyToOne($subjectMapping)->shouldBeCalled();
-        $metadata->mapManyToOne($attributeMapping)->shouldBeCalled();
-
-        $this->loadClassMetadata($eventArgs);
-    }
-
-    function it_does_not_map_relations_for_attribute_value_model_if_the_relations_already_exist(
-        LoadClassMetadataEventArgs $eventArgs,
-        ClassMetadata $metadata,
-        EntityManager $entityManager,
-        ClassMetadataFactory $classMetadataFactory,
-    ): void {
-        $eventArgs->getClassMetadata()->willReturn($metadata);
-        $eventArgs->getEntityManager()->willReturn($entityManager);
-        $entityManager->getMetadataFactory()->willReturn($classMetadataFactory);
-
-        $metadata->getName()->willReturn('Some\App\Product\Entity\AttributeValue');
-        $metadata->hasAssociation('subject')->willReturn(true);
-        $metadata->hasAssociation('attribute')->willReturn(true);
-
-        $metadata->mapManyToOne(Argument::any())->shouldNotBeCalled();
-
-        $this->loadClassMetadata($eventArgs);
-    }
-
-    function it_does_not_add_a_many_to_one_mapping_if_the_class_is_not_a_configured_attribute_value_model(
-        LoadClassMetadataEventArgs $eventArgs,
-        ClassMetadata $metadata,
-        EntityManager $entityManager,
-        ClassMetadataFactory $classMetadataFactory,
-    ): void {
-        $eventArgs->getEntityManager()->willReturn($entityManager);
-        $entityManager->getMetadataFactory()->willReturn($classMetadataFactory);
-
-        $eventArgs->getClassMetadata()->willReturn($metadata);
-        $metadata->getName()->willReturn('KeepMoving\ThisClass\DoesNot\Concern\You');
-
-        $metadata->mapManyToOne(Argument::any())->shouldNotBeCalled();
-
-        $this->loadClassMetadata($eventArgs);
-    }
-
-    function it_does_not_add_values_one_to_many_mapping_if_the_class_is_not_a_configured_attribute_model(
-        LoadClassMetadataEventArgs $eventArgs,
-        ClassMetadata $metadata,
-        EntityManager $entityManager,
-        ClassMetadataFactory $classMetadataFactory,
-    ): void {
-        $eventArgs->getEntityManager()->willReturn($entityManager);
-        $entityManager->getMetadataFactory()->willReturn($classMetadataFactory);
-
-        $eventArgs->getClassMetadata()->willReturn($metadata);
-        $metadata->getName()->willReturn('KeepMoving\ThisClass\DoesNot\Concern\You');
-
-        $metadata->mapOneToMany(Argument::any())->shouldNotBeCalled();
-
-        $this->loadClassMetadata($eventArgs);
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/AttributeTypeValidatorSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/AttributeTypeValidatorSpec.php
deleted file mode 100644
index cac432ce681..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/AttributeTypeValidatorSpec.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Prophecy\Argument;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\AttributeType;
-use Sylius\Component\Attribute\Model\AttributeInterface;
-use Sylius\Component\Registry\ServiceRegistryInterface;
-use Symfony\Component\Validator\Constraint;
-use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\Validator\Context\ExecutionContextInterface;
-use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
-
-final class AttributeTypeValidatorSpec extends ObjectBehavior
-{
-    function let(ServiceRegistryInterface $attributeTypesRegistry, ExecutionContextInterface $context): void
-    {
-        $this->beConstructedWith($attributeTypesRegistry);
-        $this->initialize($context);
-    }
-
-    function it_is_a_constraint_validator(): void
-    {
-        $this->shouldHaveType(ConstraintValidator::class);
-    }
-
-    function it_throws_exception_when_value_is_not_an_attribute(): void
-    {
-        $this
-            ->shouldThrow(\InvalidArgumentException::class)
-            ->during('validate', [new \stdClass(), new AttributeType()])
-        ;
-    }
-
-    function it_throws_exception_when_constraint_is_not_an_attribute_type(
-        AttributeInterface $attribute,
-        Constraint $constraint,
-    ): void {
-        $this
-            ->shouldThrow(\InvalidArgumentException::class)
-            ->during('validate', [$attribute, $constraint]);
-    }
-
-    function it_does_nothing_when_attribute_type_is_null(
-        ServiceRegistryInterface $attributeTypesRegistry,
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $attribute->getType()->willReturn(null);
-
-        $attributeTypesRegistry->has(Argument::any())->shouldNotBeCalled();
-        $context->addViolation(Argument::any())->shouldNotBeCalled();
-
-        $this->validate($attribute, new AttributeType());
-    }
-
-    function it_does_nothing_when_attribute_type_is_registered(
-        ServiceRegistryInterface $attributeTypesRegistry,
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $attribute->getType()->willReturn('foo');
-
-        $attributeTypesRegistry->has('foo')->willReturn(true);
-
-        $context->addViolation(Argument::any())->shouldNotBeCalled();
-
-        $this->validate($attribute, new AttributeType());
-    }
-
-    function it_adds_violation_when_attribute_type_is_not_registered(
-        ServiceRegistryInterface $attributeTypesRegistry,
-        ExecutionContextInterface $context,
-        ConstraintViolationBuilderInterface $violationBuilder,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new AttributeType();
-
-        $attribute->getType()->willReturn('foo');
-        $attributeTypesRegistry->has('foo')->willReturn(false);
-        $attributeTypesRegistry->all()->willReturn(['foo_attribute_name' => 'foo_value', 'bar_attribute_name' => 'bar_value']);
-
-        $context
-            ->buildViolation($constraint->unregisteredAttributeTypeMessage, [
-                '%type%' => 'foo', '%available_types%' => 'foo_attribute_name, bar_attribute_name',
-            ])
-            ->shouldBeCalled()
-            ->willReturn($violationBuilder);
-        $violationBuilder->atPath('type')->shouldBeCalled()->willReturn($violationBuilder);
-        $violationBuilder->addViolation()->shouldBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueSpec.php
deleted file mode 100644
index d27a7f54e76..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueSpec.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValue;
-use Symfony\Component\Validator\Constraint;
-
-final class ValidAttributeValueSpec extends ObjectBehavior
-{
-    function it_is_initializable(): void
-    {
-        $this->shouldHaveType(ValidAttributeValue::class);
-    }
-
-    function it_is_constraint(): void
-    {
-        $this->shouldHaveType(Constraint::class);
-    }
-
-    function it_has_targets(): void
-    {
-        $this->getTargets()->shouldReturn(Constraint::CLASS_CONSTRAINT);
-    }
-
-    function it_is_validated_by_specific_validator(): void
-    {
-        $this->validatedBy()->shouldReturn('sylius_valid_attribute_value_validator');
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueValidatorSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueValidatorSpec.php
deleted file mode 100644
index 2e031c72af0..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueValidatorSpec.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Prophecy\Argument;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValue;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValueValidator;
-use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface;
-use Sylius\Component\Attribute\AttributeType\TextAttributeType;
-use Sylius\Component\Attribute\Model\AttributeInterface;
-use Sylius\Component\Attribute\Model\AttributeValueInterface;
-use Sylius\Component\Registry\ServiceRegistryInterface;
-use Sylius\Resource\Exception\UnexpectedTypeException;
-use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\Validator\Context\ExecutionContextInterface;
-
-final class ValidAttributeValueValidatorSpec extends ObjectBehavior
-{
-    function let(ServiceRegistryInterface $attributeTypesRegistry, ExecutionContextInterface $context): void
-    {
-        $this->beConstructedWith($attributeTypesRegistry);
-        $this->initialize($context);
-    }
-
-    function it_is_initializable(): void
-    {
-        $this->shouldHaveType(ValidAttributeValueValidator::class);
-    }
-
-    function it_is_constraint_validator(): void
-    {
-        $this->shouldHaveType(ConstraintValidator::class);
-    }
-
-    function it_validates_attribute_value_based_on_their_type(
-        AttributeInterface $attribute,
-        AttributeTypeInterface $attributeType,
-        AttributeValueInterface $attributeValue,
-        ServiceRegistryInterface $attributeTypesRegistry,
-        ValidAttributeValue $attributeValueConstraint,
-    ): void {
-        $attributeValue->getType()->willReturn(TextAttributeType::TYPE);
-        $attributeTypesRegistry->get('text')->willReturn($attributeType);
-        $attributeValue->getAttribute()->willReturn($attribute);
-        $attribute->getConfiguration()->willReturn(['min' => 2, 'max' => 255]);
-
-        $attributeType->validate($attributeValue, Argument::type(ExecutionContextInterface::class), ['min' => 2, 'max' => 255])->shouldBeCalled();
-
-        $this->validate($attributeValue, $attributeValueConstraint);
-    }
-
-    function it_throws_exception_if_validated_value_is_not_attribute_value(
-        \DateTime $badObject,
-        ValidAttributeValue $attributeValueConstraint,
-    ): void {
-        $this
-            ->shouldThrow(new UnexpectedTypeException('\DateTimeInterface', AttributeValueInterface::class))
-            ->during('validate', [$badObject, $attributeValueConstraint])
-        ;
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationSpec.php
deleted file mode 100644
index ddc7555597e..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationSpec.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Symfony\Component\Validator\Constraint;
-
-final class ValidSelectAttributeConfigurationSpec extends ObjectBehavior
-{
-    function it_is_a_constraint(): void
-    {
-        $this->shouldHaveType(Constraint::class);
-    }
-
-    function it_has_targets(): void
-    {
-        $this->getTargets()->shouldReturn(Constraint::CLASS_CONSTRAINT);
-    }
-
-    function it_is_validated_by_specific_validator(): void
-    {
-        $this->validatedBy()->shouldReturn('sylius_valid_select_attribute_validator');
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationValidatorSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationValidatorSpec.php
deleted file mode 100644
index e744a46d015..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationValidatorSpec.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Prophecy\Argument;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfiguration;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidTextAttributeConfiguration;
-use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
-use Sylius\Component\Attribute\AttributeType\TextAttributeType;
-use Sylius\Component\Attribute\Model\AttributeInterface;
-use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\Validator\Context\ExecutionContextInterface;
-
-final class ValidSelectAttributeConfigurationValidatorSpec extends ObjectBehavior
-{
-    function let(ExecutionContextInterface $context): void
-    {
-        $this->initialize($context);
-    }
-
-    function it_is_a_constraint_validator(): void
-    {
-        $this->shouldHaveType(ConstraintValidator::class);
-    }
-
-    function it_adds_a_violation_if_max_entries_value_is_lower_than_min_entries_value(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $attribute->getType()->willReturn(SelectAttributeType::TYPE);
-        $attribute->getConfiguration()->willReturn(['multiple' => true, 'min' => 6, 'max' => 4]);
-
-        $context->addViolation(Argument::any())->shouldBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_adds_a_violation_if_min_entries_value_is_greater_than_the_number_of_added_choices(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $attribute->getType()->willReturn(SelectAttributeType::TYPE);
-        $attribute->getConfiguration()->willReturn([
-            'multiple' => true,
-            'min' => 4,
-            'max' => 6,
-            'choices' => [
-                'ec134e10-6a80-4eaf-8346-e9bb0f7406a4' => 'Banana',
-                '63148775-be39-47eb-8afd-a4818981e3c0' => 'Watermelon',
-            ],
-        ]);
-
-        $context->addViolation(Argument::any())->shouldBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_adds_a_violation_if_multiple_is_not_true_when_min_or_max_entries_values_are_specified(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $attribute->getType()->willReturn(SelectAttributeType::TYPE);
-        $attribute->getConfiguration()->willReturn(['multiple' => false, 'min' => 4, 'max' => 6]);
-
-        $context->addViolation(Argument::any())->shouldBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_adds_a_violation_if_multiple_is_not_set_when_min_or_max_entries_values_are_specified(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $attribute->getType()->willReturn(SelectAttributeType::TYPE);
-        $attribute->getConfiguration()->willReturn(['min' => 4, 'max' => 6]);
-
-        $context->addViolation(Argument::any())->shouldBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_does_nothing_if_an_attribute_is_not_a_select_type(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $attribute->getType()->willReturn(TextAttributeType::TYPE);
-
-        $context->addViolation(Argument::any())->shouldNotBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_throws_an_exception_if_validated_value_is_not_an_attribute(): void
-    {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $this
-            ->shouldThrow(\InvalidArgumentException::class)
-            ->during('validate', ['badObject', $constraint])
-        ;
-    }
-
-    function it_throws_an_exception_if_constraint_is_not_a_valid_select_attribute_configuration_constraint(
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidTextAttributeConfiguration();
-
-        $this
-            ->shouldThrow(\InvalidArgumentException::class)
-            ->during('validate', [$attribute, $constraint])
-        ;
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationSpec.php
deleted file mode 100644
index 78e90f73d41..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationSpec.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Symfony\Component\Validator\Constraint;
-
-final class ValidTextAttributeConfigurationSpec extends ObjectBehavior
-{
-    function it_is_a_constraint(): void
-    {
-        $this->shouldHaveType(Constraint::class);
-    }
-
-    function it_has_targets(): void
-    {
-        $this->getTargets()->shouldReturn(Constraint::CLASS_CONSTRAINT);
-    }
-
-    function it_is_validated_by_specific_validator(): void
-    {
-        $this->validatedBy()->shouldReturn('sylius_valid_text_attribute_validator');
-    }
-}
diff --git a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationValidatorSpec.php b/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationValidatorSpec.php
deleted file mode 100644
index f9010e5292f..00000000000
--- a/src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationValidatorSpec.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?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\AttributeBundle\Validator\Constraints;
-
-use PhpSpec\ObjectBehavior;
-use Prophecy\Argument;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfiguration;
-use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidTextAttributeConfiguration;
-use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
-use Sylius\Component\Attribute\AttributeType\TextAttributeType;
-use Sylius\Component\Attribute\Model\AttributeInterface;
-use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\Validator\Context\ExecutionContextInterface;
-
-final class ValidTextAttributeConfigurationValidatorSpec extends ObjectBehavior
-{
-    function let(ExecutionContextInterface $context): void
-    {
-        $this->initialize($context);
-    }
-
-    function it_is_a_constraint_validator(): void
-    {
-        $this->shouldHaveType(ConstraintValidator::class);
-    }
-
-    function it_adds_a_violation_if_max_entries_value_is_lower_than_min_entries_value(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidTextAttributeConfiguration();
-
-        $attribute->getType()->willReturn(TextAttributeType::TYPE);
-        $attribute->getConfiguration()->willReturn(['min' => 6, 'max' => 4]);
-
-        $context->addViolation(Argument::any())->shouldBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_does_nothing_if_an_attribute_is_not_a_text_type(
-        ExecutionContextInterface $context,
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidTextAttributeConfiguration();
-
-        $attribute->getType()->willReturn(SelectAttributeType::TYPE);
-
-        $context->addViolation(Argument::any())->shouldNotBeCalled();
-
-        $this->validate($attribute, $constraint);
-    }
-
-    function it_throws_an_exception_if_validated_value_is_not_an_attribute(): void
-    {
-        $constraint = new ValidTextAttributeConfiguration();
-
-        $this
-            ->shouldThrow(\InvalidArgumentException::class)
-            ->during('validate', ['badObject', $constraint])
-        ;
-    }
-
-    function it_throws_an_exception_if_constraint_is_not_a_valid_text_attribute_configuration_constraint(
-        AttributeInterface $attribute,
-    ): void {
-        $constraint = new ValidSelectAttributeConfiguration();
-
-        $this
-            ->shouldThrow(\InvalidArgumentException::class)
-            ->during('validate', [$attribute, $constraint])
-        ;
-    }
-}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php (4)</summary><blockquote>

`29-34`: **Property initialization order is inconsistent with setup method**

The property `attributeMock` is declared before the setup method but initialized after the validator in the setup method. Consider maintaining a consistent order between property declarations and their initialization.

```diff
 protected function setUp(): void
 {
     parent::setUp();
     $this->context = $this->createMock(ExecutionContextInterface::class);
     $this->validSelectAttributeConfigurationValidator = new ValidSelectAttributeConfigurationValidator();
+    $this->attributeMock = $this->createMock(AttributeInterface::class);
     $this->initialize($this->context);
-    $this->attributeMock = $this->createMock(AttributeInterface::class);
 }
```

---

`49-61`: **Test assertion could be more specific**

The test verifies that `addViolation` is called once but doesn't assert which specific violation message is expected. Consider using `with()` to assert the exact violation message for more precise testing.

```diff
 $this->attributeMock->expects(self::atLeastOnce())->method('getConfiguration')->willReturn(['multiple' => true, 'min' => 6, 'max' => 4]);

-$this->context->expects(self::once())->method('addViolation');
+$this->context->expects(self::once())
+    ->method('addViolation')
+    ->with('sylius.attribute.select.max_entries_must_be_greater_than_min_entries');

 $this->validSelectAttributeConfigurationValidator->validate($this->attributeMock, $constraint);
```

---

`131-131`: **Inconsistent assertion style**

The test uses `$this->never()` while other tests in the file use `self::once()`, `self::atLeastOnce()`, etc. Maintain consistent assertion style throughout the test class.

```diff
-$this->context->expects($this->never())->method('addViolation');
+$this->context->expects(self::never())->method('addViolation');
```

---

`27-151`: **Property name could be shortened for better readability**

The property name `validSelectAttributeConfigurationValidator` is quite long. Consider using a shorter name like `validator` to improve code readability, especially when it's used multiple times throughout the class.

```diff
-private ValidSelectAttributeConfigurationValidator $validSelectAttributeConfigurationValidator;
+private ValidSelectAttributeConfigurationValidator $validator;

 // And update all usages accordingly
```

</blockquote></details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/AttributeTypeValidatorTest.php (3)</summary><blockquote>

`30-37`: **Inconsistent order of type union annotations**

Property type annotations use inconsistent ordering:
- `MockObject&ServiceRegistryInterface` 
- `ExecutionContextInterface&MockObject`

Maintain a consistent order in type unions across the codebase.

```diff
-private MockObject&ServiceRegistryInterface $attributeTypeRegistry;
+private ServiceRegistryInterface&MockObject $attributeTypeRegistry;

 private ExecutionContextInterface&MockObject $contextMock;
```

---

`38-46`: **Property initialization order doesn't match declaration order**

The property `attribute` is created after the validator is initialized. For consistency, initialize dependencies before using them.

```diff
 protected function setUp(): void
 {
     parent::setUp();
     $this->attributeTypeRegistry = $this->createMock(ServiceRegistryInterface::class);
     $this->contextMock = $this->createMock(ExecutionContextInterface::class);
+    $this->attribute = $this->createMock(AttributeInterface::class);
     $this->attributeTypeValidator = new AttributeTypeValidator($this->attributeTypeRegistry);
     $this->initialize($this->contextMock);
-    $this->attribute = $this->createMock(AttributeInterface::class);
 }
```

---

`97-99`: **Missing PHPDoc for $constraint variable**

There's a PHPDoc comment for `$violationBuilder` but not for `$constraint`. Add a similar type annotation for consistency or consider using PHP 8 native type annotations instead.

```diff
 /** @var ConstraintViolationBuilderInterface&MockObject $violationBuilder */
 $violationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class);
+/** @var AttributeType $constraint */
 $constraint = new AttributeType();
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 08da0ac015f1f59b374e27fe72daa3beb6520815 and 6bd6ab49245f299a04fb2fbcc709e43bd95ce64f.

</details>

<details>
<summary>📒 Files selected for processing (19)</summary>

* `src/Sylius/Bundle/AttributeBundle/composer.json` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/phpspec.yml.dist` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Doctrine/ORM/Subscriber/LoadMetadataSubscriberSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/AttributeTypeValidatorSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueValidatorSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationValidatorSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationValidatorSpec.php` (0 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/AttributeTypeValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (9)</summary>

* src/Sylius/Bundle/AttributeBundle/phpspec.yml.dist
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidTextAttributeConfigurationValidatorSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidAttributeValueValidatorSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/AttributeTypeValidatorSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Doctrine/ORM/Subscriber/LoadMetadataSubscriberSpec.php
* src/Sylius/Bundle/AttributeBundle/spec/Validator/Constraints/ValidSelectAttributeConfigurationValidatorSpec.php

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (3)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueTest.php (3)</summary>

* `setUp` (24-28)
* `testHasTargets` (30-33)
* `testValidatedBySpecificValidator` (35-38)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php (3)</summary>

* `setUp` (24-28)
* `testHasTargets` (30-33)
* `testValidatedBySpecificValidator` (35-41)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php (1)</summary>

* `setUp` (35-42)

</details>

</blockquote></details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php (2)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueTest.php (3)</summary>

* `setUp` (24-28)
* `testHasTargets` (30-33)
* `testValidatedBySpecificValidator` (35-38)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (3)</summary>

* `setUp` (24-28)
* `testHasTargets` (30-33)
* `testValidatedBySpecificValidator` (35-38)

</details>

</blockquote></details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php (5)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (1)</summary>

* `setUp` (24-28)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueValidatorTest.php (2)</summary>

* `setUp` (37-44)
* `initialize` (46-49)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php (1)</summary>

* `setUp` (24-28)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/AttributeTypeValidatorTest.php (2)</summary>

* `setUp` (38-46)
* `initialize` (48-51)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php (4)</summary>

* `setUp` (35-42)
* `initialize` (44-47)
* `testAddsAViolationIfMaxEntriesValueIsLowerThanMinEntriesValue` (49-61)
* `testThrowsAnExceptionIfValidatedValueIsNotAnAttribute` (136-142)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: WIP

</details>

<details>
<summary>🔇 Additional comments (20)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist (2)</summary>

`4-4`: **Well-implemented update to PHPUnit 10 schema**

The update to PHPUnit 10's schema URL ensures compatibility with the newer PHPUnit version (10.5) specified in the composer.json file.

---

`12-12`: **Correct test suite configuration for PHPUnit tests**

Both changes align well with the migration from PHPSpec to PHPUnit:
1. The test suite name follows the standard naming convention (lowercase with underscores)
2. Adding the "./tests/" directory ensures the new PHPUnit tests will be properly included in test runs

This configuration properly maintains backward compatibility while adding support for the new test structure.



Also applies to: 15-15

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/composer.json (1)</summary>

`70-70`: **PSR-4 namespace correctly updated for PHPUnit tests**

The autoload-dev PSR-4 namespace has been correctly updated from PHPSpec's convention to PHPUnit's convention, mapping "Tests\\Sylius\\Bundle\\AttributeBundle\\" to the "tests/" directory. This follows standard PHPUnit practices and enables proper autoloading of the new test classes.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueTest.php (1)</summary>

`1-40`: **Well-structured PHPUnit test class**

This new test class properly implements PHPUnit testing for the ValidAttributeValue constraint. The class follows best practices:

1. Proper namespace structure for PHPUnit tests
2. Test class is marked as final
3. Follows AAA (Arrange-Act-Assert) pattern
4. Tests essential aspects of the constraint's configuration
5. Clear and descriptive test method names

This is a good implementation of the PHPSpec to PHPUnit migration for constraint testing.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (1)</summary>

`1-40`: **Consistent and well-implemented PHPUnit test class**

This test class maintains the same structure and patterns as other constraint tests in the bundle, ensuring consistency throughout the codebase. The implementation:

1. Uses the same testing approach as other constraint tests
2. Properly validates the constraint's configuration
3. Follows PHPUnit best practices with clear setup and assertions

The consistency across constraint test classes demonstrates a methodical approach to the PHPSpec to PHPUnit migration.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php (1)</summary>

`20-42`: **LGTM - Well-structured PHPUnit test class for the ValidTextAttributeConfiguration constraint**

The test class is well-organized and follows PHPUnit best practices. It tests two key aspects of the constraint:
1. The constraint targets class-level validation
2. The constraint specifies the correct validator service name 'sylius_valid_text_attribute_validator'

The tests are clear, focused, and match the pattern used in other similar test classes in the bundle.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidAttributeValueValidatorTest.php (3)</summary>

`31-49`: **LGTM - Good test setup with proper mock initialization**

The setUp method correctly initializes all necessary mocks and the validator. The use of intersection types for type-hinting (`ExecutionContextInterface&MockObject`) is a good practice for maintaining type safety while testing.

---

`51-91`: **LGTM - Thorough test of validation delegation to appropriate attribute type**

This test effectively verifies that the validator:
1. Checks the attribute value type
2. Retrieves the correct validator from the registry
3. Gets configuration from the attribute
4. Passes the right parameters to the validation method

The test asserts the complete validation flow with all dependencies and interactions.

---

`93-106`: **LGTM - Proper exception testing**

The test correctly verifies that an UnexpectedTypeException is thrown when the validated value is not an AttributeValueInterface. The expected exception message is also verified.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php (4)</summary>

`35-46`: **LGTM - Proper initialization of validator and test dependencies**

The setUp method correctly initializes the validator and necessary mocks. The separate initialize method follows the pattern used in other validator tests in the bundle.

---

`48-65`: **LGTM - Tests violation addition for invalid configuration**

This test correctly verifies that the validator adds a violation when the max value is less than the min value for text attributes, which is the main validation rule being tested.

---

`67-77`: **LGTM - Tests validator handling of non-text attributes**

This test correctly verifies that the validator does nothing when the attribute is not a text type, which is important for ensuring the validator only validates its own attribute type.

---

`79-93`: **LGTM - Tests exception handling for invalid inputs**

These tests properly verify that the validator throws InvalidArgumentException when:
1. The validated value is not an attribute
2. The constraint is not a ValidTextAttributeConfiguration constraint

This ensures proper type checking in the validator.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php (5)</summary>

`37-59`: **LGTM - Well-structured test setup with proper configuration**

The setUp method correctly initializes the subscriber with appropriate test configuration and creates all necessary mocks for Doctrine components.

---

`61-69`: **LGTM - Basic interface and event subscription tests**

These tests verify the fundamental aspects of the subscriber:
1. It implements the EventSubscriber interface
2. It subscribes to the 'loadClassMetadata' event

These are important foundational tests for any event subscriber.

---

`71-149`: **LGTM - Comprehensive test of association mapping**

This test thoroughly verifies the core functionality of the subscriber - mapping associations between attribute value and its related entities. The test:

1. Sets up appropriate mock expectations
2. Verifies that the correct metadata is obtained
3. Checks for existing associations
4. Ensures the correct mapping parameters are used

The use of a callback with a static counter to verify sequential method calls is a clever approach.

---

`151-176`: **LGTM - Tests skipping of existing associations**

This test verifies that the subscriber does not attempt to map associations that already exist, which is important for preventing duplicate mappings and potential errors.

---

`178-220`: **LGTM - Tests behavior with non-configured entities**

These tests verify that the subscriber does not attempt to add mappings when:
1. The class is not a configured attribute value model
2. The class is not a configured attribute model

This is important to ensure the subscriber only affects the entities it's supposed to.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/AttributeTypeValidatorTest.php (2)</summary>

`28-137`: **LGTM - Test class is comprehensive**

This test class thoroughly covers the behavior of the AttributeTypeValidator, including validation of inputs, proper error handling, and all expected scenarios for attribute type validation. The methodology aligns well with the PHPUnit testing approach.

---

`108-115`: <details>
<summary>✅ Verification successful</summary>

**Test uses simplistic mock data**

The mock for `all()` returns a simple key-value array. Consider using a more realistic example that aligns with how the registry actually stores attribute types in the real application, which would make the tests more representative.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check how attribute types are registered in the application
fd --type file --extension php "ServiceRegistry" --exec grep -l "AttributeType" {} \; | xargs -I{} cat {}
```

Length of output: 110

---

I wasn’t able to locate the actual `AttributeTypeRegistry` implementation with the first command. Let’s pinpoint how the registry is defined and what `all()` returns:

---

🏁 Script executed:

```shell
#!/bin/bash
set -e

# 1. Locate the test to see the type hint for attributeTypeRegistry
fd --type f --extension php AttributeTypeValidatorTest \
  --exec sed -n '1,200p' {} \;

# 2. Search for the registry class/interface name in the codebase
grep -R "AttributeTypeRegistry" -n src

# 3. Find its declaration file
fd --type f --extension php AttributeTypeRegistry
```

Length of output: 4966

---



</details>

**No change needed for the `all()` mock shape**  
The validator under test only uses the array keys from `all()` to compose the error message. Even if the real registry returns service objects, mocking the values as simple strings doesn’t affect the logic or coverage here. You can leave the placeholders (`foo_attribute_name`, `bar_attribute_name`) as is.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-16T13:41:49Z
user: login: GSadee
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T05:15:40Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php (1)</summary><blockquote>

`137-146`: **Consider refactoring the callback with static variable**

While your current implementation is functional, using a static variable in the callback creates a hidden state that can make the test more difficult to understand and maintain.


Consider refactoring to use the `withConsecutive` method or breaking this into separate test cases:

```diff
-        $this->metadata->expects(self::exactly(2))
-            ->method('mapManyToOne')
-            ->willReturnCallback(function ($mapping) use ($subjectMapping, $attributeMapping) {
-                static $callCount = 0;
-                ++$callCount;
-
-                if ($callCount === 1) {
-                    self::assertEquals($subjectMapping, $mapping);
-                } elseif ($callCount === 2) {
-                    self::assertEquals($attributeMapping, $mapping);
-                }
-            });
+        // Test each mapping separately for better clarity
+        $this->metadata->expects(self::exactly(2))
+            ->method('mapManyToOne')
+            ->withConsecutive(
+                [$this->equalTo($subjectMapping)],
+                [$this->equalTo($attributeMapping)]
+            );
```

Note: If you're using PHPUnit 10+, `withConsecutive` might not be available, in which case separate test methods might be clearer.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e880647027373cf881d04866a959e689be9b9fe3 and 3c1c8a91b2e855f3a6352564cab929c30527ecc3.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/AttributeTypeValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php` (1 hunks)
* `src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (2)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php (3)</summary>

* `setUp` (24-28)
* `testHasTargets` (30-33)
* `testValidatedBySpecificValidator` (35-38)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php (3)</summary>

* `setUp` (24-28)
* `testHasTargets` (30-33)
* `testValidatedBySpecificValidator` (35-41)

</details>

</blockquote></details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php (2)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (1)</summary>

* `setUp` (24-28)

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php (4)</summary>

* `setUp` (34-40)
* `initialize` (42-45)
* `testAddsAViolationIfMaxEntriesValueIsLowerThanMinEntriesValue` (47-64)
* `testThrowsAnExceptionIfValidatedValueIsNotAnAttribute` (78-84)

</details>

</blockquote></details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php (1)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/Subscriber/LoadMetadataSubscriber.php (2)</summary>

* `LoadMetadataSubscriber` (21-97)
* `loadClassMetadata` (34-45)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (14)</summary>

* GitHub Check: End-to-end tests (PostgreSQL) / Non-JS, PHP 8.3, Symfony ^7.2, PostgreSQL 16.4
* GitHub Check: End-to-end tests (PostgreSQL) / Non-JS, PHP 8.2, Symfony ^6.4, PostgreSQL 15.8
* GitHub Check: Frontend / NodeJS 20.x
* GitHub Check: End-to-end tests (MySQL) / JS with Panther, PHP 8.2, Symfony ^6.4 (test_cached), MySQL 8.0, Twig ^3.3
* GitHub Check: End-to-end tests (MySQL) / JS with Panther, PHP 8.3, Symfony ^7.2 (test_cached), MySQL 8.4, Twig ^3.3
* GitHub Check: End-to-end tests (MySQL) / Non-JS, PHP 8.3, Symfony ^7.2 (test_cached), MySQL 8.4, Twig ^3.3
* GitHub Check: End-to-end tests (MySQL) / JS with Chromedriver, PHP 8.3, Symfony ^7.2 (test_cached), MySQL 8.4, Twig ^3.3
* GitHub Check: End-to-end tests (MySQL) / Non-JS, PHP 8.2, Symfony ^6.4 (test_cached), MySQL 8.0, Twig ^3.3
* GitHub Check: End-to-end tests (MySQL) / JS with Chromedriver, PHP 8.2, Symfony ^6.4 (test_cached), MySQL 8.0, Twig ^3.3
* GitHub Check: Packages / PHP 8.3, Symfony ^7.2
* GitHub Check: Packages / PHP 8.2, Symfony ^6.4
* GitHub Check: End-to-end tests (MariaDB) / Non-JS, PHP 8.3, Symfony ^7.2, MariaDB 11.4.3, State Machine Adapter symfony_workflow
* GitHub Check: End-to-end tests (MariaDB) / Non-JS, PHP 8.3, Symfony ^7.2, MariaDB 11.4.3, State Machine Adapter winzou_state_machine
* GitHub Check: End-to-end tests (MariaDB) / Non-JS, PHP 8.2, Symfony ^6.4, MariaDB 10.11.9, State Machine Adapter symfony_workflow

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php (1)</summary>

`1-93`: **The PHPUnit test implementation is correct and comprehensive.**

This test class properly validates the behavior of the `ValidTextAttributeConfigurationValidator`. It covers all essential scenarios:
- Testing violation when max < min
- Testing no violations for non-text attribute types
- Testing exceptions for invalid inputs
- Testing exceptions for invalid constraint types

The code follows PHPUnit best practices with proper mocking, expectations, and assertions.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php (5)</summary>

`34-41`: **Setup method looks good.**

The setup method properly initializes all the necessary components for testing the validator.

---

`48-60`: **Test case correctly verifies min/max validation.**

This test properly validates that a violation is added when the max entries value is lower than the min entries value for select attributes.

---

`62-82`: **Test case correctly verifies min vs. choices count validation.**

This test properly validates that a violation is added when the min entries value is greater than the number of available choices.

---

`84-121`: **Test cases properly verify multiple flag validation.**

Both test cases correctly verify that violations are added when:
1. multiple is false but min/max are specified
2. multiple is not set at all but min/max are specified

These are important edge cases to validate.

---

`123-149`: **Edge case tests are correctly implemented.**

The tests correctly verify:
- No violations for non-select attribute types
- Exceptions for invalid input types
- Exceptions for invalid constraint types

These edge cases ensure robust validation behavior.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php (1)</summary>

`1-39`: **Test class correctly verifies constraint configuration.**

This test class properly validates the basic behavior of the `ValidSelectAttributeConfiguration` constraint by:
1. Verifying it targets class-level validation
2. Confirming it uses the correct validator service name

The test class follows the same pattern as other constraint tests in the bundle, maintaining consistency.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php (1)</summary>

`1-39`: **Test class correctly verifies constraint configuration.**

This test class properly validates the basic behavior of the `ValidAttributeValue` constraint by:
1. Verifying it targets class-level validation
2. Confirming it uses the correct validator service name

The implementation follows the consistent pattern used across all constraint tests.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/AttributeTypeValidatorTest.php (1)</summary>

`1-135`: **Well-structured PHPUnit test implementation**

The test class thoroughly covers all validation scenarios for the AttributeTypeValidator, including proper exception handling, null values, registered types, and unregistered types. The test structure follows PHPUnit best practices with good use of mocks, clear assertions, and appropriate test methods.

</details>
<details>
<summary>src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php (1)</summary>

`1-221`: **Well-implemented test suite for the LoadMetadataSubscriber**

The test thoroughly covers all aspects of the Doctrine event subscriber functionality, including interface implementation, association mapping, and conditional behavior. The test structure is clear with good separation of test cases.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-19T06:47:59Z
user: login: GSadee
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T04:46:33Z

Review comments:
diff_hunk: @@ -1,17 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/8.5/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/phpunit-10.xsd"
          colors="true"
 >
     <php>
         <server name="KERNEL_CLASS" value="AppKernel" />
     </php>
 
     <testsuites>
-        <testsuite name="SyliusAttributeBundle Test Suite">
+        <testsuite name="sylius_attribute_bundle_test_suite">
             <directory>./test/</directory>
             <directory>./Tests/</directory>
+            <directory>./tests/</directory>
path: src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist
user: login: GSadee
body: ```suggestion
            <directory>./tests/</directory>
```
the existing tests should be moved from `tests` to `Tests`
created_at: 2025-05-19T05:11:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,137 @@
+<?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 Tests\Sylius\Bundle\AttributeBundle\Validator\Constraints;
+
+use InvalidArgumentException;
path: src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/AttributeTypeValidatorTest.php
user: login: GSadee
body: ```suggestion
```
created_at: 2025-05-19T05:13:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,137 @@
+<?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 Tests\Sylius\Bundle\AttributeBundle\Validator\Constraints;
+
+use InvalidArgumentException;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use stdClass;
path: src/Sylius/Bundle/AttributeBundle/tests/Validator/Constraints/AttributeTypeValidatorTest.php
user: login: GSadee
body: ```suggestion
```
created_at: 2025-05-19T05:13:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,107 @@
+<?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\AttributeBundle\Tests\Validator\Constraints;
+
+use DateTime;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValue;
+use Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValueValidator;
+use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface;
+use Sylius\Component\Attribute\AttributeType\TextAttributeType;
+use Sylius\Component\Attribute\Model\AttributeInterface;
+use Sylius\Component\Attribute\Model\AttributeValueInterface;
+use Sylius\Component\Registry\ServiceRegistryInterface;
+use Sylius\Resource\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+final class ValidAttributeValueValidatorTest extends TestCase
+{
+    private MockObject&ServiceRegistryInterface $attributeTypesRegistry;
+
+    private ExecutionContextInterface&MockObject $context;
+
+    private ValidAttributeValueValidator $validAttributeValueValidator;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->attributeTypesRegistry = $this->createMock(ServiceRegistryInterface::class);
+        $this->context = $this->createMock(ExecutionContextInterface::class);
+        $this->validAttributeValueValidator = new ValidAttributeValueValidator($this->attributeTypesRegistry);
+        $this->initialize($this->context);
+    }
+
+    private function initialize(ExecutionContextInterface $context): void
+    {
+        $this->validAttributeValueValidator->initialize($context);
+    }
+
+    public function testValidatesAttributeValueBasedOnTheirType(): void
+    {
+        /** @var AttributeInterface&MockObject $attributeMock */
+        $attributeMock = $this->createMock(AttributeInterface::class);
+        /** @var AttributeTypeInterface&MockObject $attributeTypeMock */
+        $attributeTypeMock = $this->createMock(AttributeTypeInterface::class);
+        /** @var AttributeValueInterface&MockObject $attributeValueMock */
+        $attributeValueMock = $this->createMock(AttributeValueInterface::class);
+        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
+        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);
+
+        $attributeValueMock->expects(self::once())
+            ->method('getType')
+            ->willReturn(TextAttributeType::TYPE);
+
+        $this->attributeTypesRegistry->expects(self::once())
+            ->method('get')
+            ->with('text')
+            ->willReturn($attributeTypeMock);
+
+        $attributeValueMock->expects(self::once())
+            ->method('getAttribute')
+            ->willReturn($attributeMock);
+
+        $attributeMock->expects(self::once())
+            ->method('getConfiguration')
+            ->willReturn(['min' => 2, 'max' => 255]);
+
+        $attributeTypeMock->expects(self::once())
+            ->method('validate')
+            ->with(
+                $attributeValueMock,
+                $this->isInstanceOf(ExecutionContextInterface::class),
+                [
+                    'min' => 2,
+                    'max' => 255,
+                ],
+            );
+
+        $this->validAttributeValueValidator->validate($attributeValueMock, $attributeValueConstraintMock);
+    }
+
+    public function testThrowsExceptionIfValidatedValueIsNotAttributeValue(): void
+    {
+        /** @var DateTime&MockObject $badObjectMock */
+        $badObjectMock = $this->createMock(DateTime::class);
+        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
+        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);
+
+        self::expectException(UnexpectedTypeException::class);
+        self::expectExceptionMessage(
+            'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "string" given.',
+        );
+
+        $this->validAttributeValueValidator->validate($badObjectMock, $attributeValueConstraintMock);
+    }
path: src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix inconsistency in exception test**

There's a mismatch between the mock object type and the expected exception message. You're creating a `DateTime` mock but the expected exception message refers to "string" type.


```diff
-        /** @var DateTime&MockObject $badObjectMock */
-        $badObjectMock = $this->createMock(DateTime::class);
+        // Create a string value instead of DateTime to match the expected error message
+        $invalidValue = 'not an attribute value';

         /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
         $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);

         self::expectException(UnexpectedTypeException::class);
         self::expectExceptionMessage(
             'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "string" given.',
         );

-        $this->validAttributeValueValidator->validate($badObjectMock, $attributeValueConstraintMock);
+        $this->validAttributeValueValidator->validate($invalidValue, $attributeValueConstraintMock);
```

Alternatively, if you want to keep using a mock object, update the expected message to match:

```diff
        /** @var DateTime&MockObject $badObjectMock */
        $badObjectMock = $this->createMock(DateTime::class);

        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);

         self::expectException(UnexpectedTypeException::class);
         self::expectExceptionMessage(
-            'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "string" given.',
+            'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "DateTime" given.',
         );
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    public function testThrowsExceptionIfValidatedValueIsNotAttributeValue(): void
    {
        // Create a string value instead of DateTime to match the expected error message
        $invalidValue = 'not an attribute value';
        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);

        self::expectException(UnexpectedTypeException::class);
        self::expectExceptionMessage(
            'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "string" given.',
        );

        $this->validAttributeValueValidator->validate($invalidValue, $attributeValueConstraintMock);
    }
```

```suggestion
    public function testThrowsExceptionIfValidatedValueIsNotAttributeValue(): void
    {
        /** @var DateTime&MockObject $badObjectMock */
        $badObjectMock = $this->createMock(DateTime::class);
        /** @var ValidAttributeValue&MockObject $attributeValueConstraintMock */
        $attributeValueConstraintMock = $this->createMock(ValidAttributeValue::class);

        self::expectException(UnexpectedTypeException::class);
        self::expectExceptionMessage(
            'Expected argument of type "Sylius\Component\Attribute\Model\AttributeValueInterface", "DateTime" given.',
        );

        $this->validAttributeValueValidator->validate($badObjectMock, $attributeValueConstraintMock);
    }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php
around lines 93 to 106, the test expects an exception message stating a "string"
type was given, but the mock object used is a DateTime instance. To fix this,
either change the mock object to a string type to match the expected message or
update the expected exception message to reflect the actual type of the mock
object used (DateTime). Ensure the expected exception message accurately
describes the type of the invalid value passed to the validator.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T06:47:58Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-15T12:07:48Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change removes all PhpSpec-based specification tests and their configuration from the Sylius AttributeBundle, replacing them with PHPUnit-based test classes for validators and Doctrine event subscribers. The `composer.json` and PHPUnit configuration are updated accordingly, while new PHPUnit tests comprehensively cover the previously specified behaviors.

## Changes

| File(s)                                                                                              | Change Summary |
|----------------------------------------------------------------------------------------------------|----------------|
| `src/Sylius/Bundle/AttributeBundle/composer.json`                                                  | Removed `phpspec/phpspec` from `require-dev`; updated `autoload-dev` PSR-4 namespace from `spec/` to `Tests/`. |
| `src/Sylius/Bundle/AttributeBundle/phpspec.yml.dist`                                               | Deleted PhpSpec configuration file. |
| `src/Sylius/Bundle/AttributeBundle/phpunit.xml.dist`                                               | Updated PHPUnit schema to version 10; changed test suite name to lowercase with underscores. |
| `src/Sylius/Bundle/AttributeBundle/spec/...` (all files under `spec/`)                             | Deleted all PhpSpec specification classes for Doctrine subscriber and validator constraints/validators. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Doctrine/ORM/Subscriber/LoadMetadataSubscriberTest.php`   | Added PHPUnit test class for `LoadMetadataSubscriber`, verifying event subscription and mapping logic. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/AttributeTypeValidatorTest.php`     | Added PHPUnit test class for `AttributeTypeValidator`, covering validation scenarios and exception handling. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueTest.php`        | Added PHPUnit test class for `ValidAttributeValue` constraint, checking its target and validator service. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidAttributeValueValidatorTest.php` | Added PHPUnit test class for `ValidAttributeValueValidator`, covering validation logic and type checks. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationTest.php` | Added PHPUnit test class for `ValidSelectAttributeConfiguration` constraint, checking target and validator. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidSelectAttributeConfigurationValidatorTest.php` | Added PHPUnit test class for `ValidSelectAttributeConfigurationValidator`, testing validation logic and exceptions. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationTest.php` | Added PHPUnit test class for `ValidTextAttributeConfiguration` constraint, checking target and validator. |
| `src/Sylius/Bundle/AttributeBundle/Tests/Validator/Constraints/ValidTextAttributeConfigurationValidatorTest.php` | Added PHPUnit test class for `ValidTextAttributeConfigurationValidator`, testing validation logic and exceptions. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant PHPUnitTest as PHPUnit Test
    participant Validator as Validator (e.g., AttributeTypeValidator)
    participant Registry as AttributeTypeRegistry
    participant Context as ValidationContext

    PHPUnitTest->>Validator: validate(value, constraint)
    Validator->>Registry: has(attribute_type)
    alt Type registered
        Validator->>Context: (no violation)
    else Type not registered
        Validator->>Context: buildViolation(message)
        Context->>Context: addViolation(path)
    end
```

```mermaid
sequenceDiagram
    participant PHPUnitTest as PHPUnit Test
    participant Subscriber as LoadMetadataSubscriber
    participant EventArgs as LoadClassMetadataEventArgs
    participant Metadata as ClassMetadata

    PHPUnitTest->>Subscriber: loadClassMetadata(EventArgs)
    Subscriber->>EventArgs: getClassMetadata()
    EventArgs->>Metadata: (returns metadata)
    alt Is attribute value model
        Subscriber->>Metadata: mapManyToOne(subject)
        Subscriber->>Metadata: mapManyToOne(attribute)
    else Not attribute value model
        Subscriber->>Metadata: (no mapping)
    end
```

## Poem

> 🐇  
> Goodbye to specs, the old have hopped,  
> PHPUnit tests now take the spot.  
> Mappings checked and values tried,  
> Validators tested, bugs denied!  
> With every test, the code’s more bright—  
> A bunny’s work, from day to night.  
> 🥕

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!NOTE]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!NOTE]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3c1c8a91b2e855f3a6352564cab929c30527ecc3 and 6968db40952f206ed17a89937d406ea0803bb18b.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/Sylius/Bundle/AttributeBundle/composer.json` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/Sylius/Bundle/AttributeBundle/composer.json

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Static checks / PHP 8.3, Symfony ^7.2
* GitHub Check: Static checks / PHP 8.2, Symfony ^6.4

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Sylius/Sylius&utm_content=18032):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-15T12:08:49Z
body: ### :x: Preview Environment deleted from [Bunnyshell](https://www.bunnyshell.com)

Available commands:
  - :rocket: `/bns:deploy` to redeploy the environment
<!-- thollander/actions-comment-pull-request "bunnyshell-preview-env" -->
author_association: NONE
user: login: GSadee
created_at: 2025-05-20T04:46:58Z
body: Thank you @rust-le! 🎉 
author_association: MEMBER