diff --git a/app/config/config_in_memory.yml b/app/config/config_in_memory.yml new file mode 100644 index 000000000000..e93aef880172 --- /dev/null +++ b/app/config/config_in_memory.yml @@ -0,0 +1,27 @@ +imports: + - { resource: config.yml } + +framework: + test: ~ + session: + storage_id: session.storage.mock_array + +swiftmailer: + disable_delivery: true + +assetic: + use_controller: false + +pim_enrich: + record_mails: true + +services: + session.storage.mock_array: + class: Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage + public: false + + pim_catalog.saver.attribute: + class: Pim\Test\Common\Persistence\InMemoryAttributeRepository + + pim_catalog.repository.attribute: + alias: pim_catalog.saver.attribute diff --git a/behat.yml.dist b/behat.yml.dist index c2772a1f0fe0..c4bc586a368b 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -103,4 +103,20 @@ default: page: [Context\Page] factory: page_parameters: - base_url: http://akeneo-pim-behat.local/ + base_url: http://ce-20-behat.pim.local + +acceptance: + suites: + acceptance: + paths: ['%paths.base%/tests/Acceptance/'] + contexts: + - Pim\Test\Acceptance\Behat\AcceptanceContext: + - '@pim_catalog.factory.attribute' + - '@pim_catalog.saver.attribute' + - '@pim_catalog.builder.product' + - '@pim_catalog.validator.product' + extensions: + Behat\Symfony2Extension: + kernel: + env: in_memory + debug: false diff --git a/composer.json b/composer.json index c7c2e4df52ff..5dbf777bc4da 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,9 @@ "classmap": [ "app/AppKernel.php", "app/AppCache.php", "tests/AppKernelTest.php" ] }, "autoload-dev": { + "psr-4": { + "Pim\\Test\\": "tests/" + }, "files": [ "vendor/phpunit/phpunit/src/Framework/Assert/Functions.php" ] diff --git a/features/product/validation/validate_text_attributes.feature b/features/product/validation/validate_text_attributes.feature index 5330a33ffa53..69edda53d70b 100644 --- a/features/product/validation/validate_text_attributes.feature +++ b/features/product/validation/validate_text_attributes.feature @@ -10,14 +10,11 @@ Feature: Validate text attributes of a product | code | label-en_US | type | scopable | unique | max_characters | validation_rule | validation_regexp | group | | barcode | Barcode | pim_catalog_text | 0 | 1 | 8 | regexp | /^0\d*$/ | other | | email | Email | pim_catalog_text | 0 | 1 | | email | | other | - | link | Link | pim_catalog_text | 0 | 0 | | url | | other | | manufacturer_number | Manufacturer number | pim_catalog_text | 1 | 0 | 8 | regexp | /^0\d*$/ | other | - | recipient | Recipient | pim_catalog_text | 1 | 0 | | email | | other | - | references | References | pim_catalog_text | 1 | 0 | | url | | other | | desc | Description | pim_catalog_text | 0 | 0 | | | | other | And the following family: - | code | label-en_US | attributes | requirements-ecommerce | requirements-mobile | - | baz | Baz | sku,barcode,email,link,manufacturer_number,recipient,references,desc | sku | sku | + | code | label-en_US | attributes | requirements-ecommerce | requirements-mobile | + | baz | Baz | sku,barcode,email,manufacturer_number,desc | sku | sku | And the following products: | sku | family | | foo | baz | @@ -47,45 +44,6 @@ Feature: Validate text attributes of a product Then I should see validation tooltip "This value is too long. It should have 8 characters or less." And there should be 1 error in the "Other" tab - Scenario: Validate the email validation rule constraint of text attribute - Given I change the Email to "foo" - And I save the product - Then I should see validation tooltip "This value is not a valid email address." - And there should be 1 error in the "Other" tab - - Scenario: Validate the email validation rule constraint of scopable text attribute - Given I switch the scope to "ecommerce" - And I change the Recipient to "foo" - And I save the product - Then I should see validation tooltip "This value is not a valid email address." - And there should be 1 error in the "Other" tab - - Scenario: Validate the url validation rule constraint of text attribute - Given I change the Link to "bar" - And I save the product - Then I should see validation tooltip "This value is not a valid URL." - And there should be 1 error in the "Other" tab - - Scenario: Validate the url validation rule constraint of scopable text attribute - Given I switch the scope to "ecommerce" - Given I change the References to "bar" - And I save the product - Then I should see validation tooltip "This value is not a valid URL." - And there should be 1 error in the "Other" tab - - Scenario: Validate the regexp validation rule constraint of text attribute - Given I change the Barcode to "111111" - And I save the product - Then I should see validation tooltip "This value is not valid." - And there should be 1 error in the "Other" tab - - Scenario: Validate the regexp validation rule constraint of scopable text attribute - Given I switch the scope to "ecommerce" - Given I change the "Manufacturer number" to "111111" - And I save the product - Then I should see validation tooltip "This value is not valid." - And there should be 1 error in the "Other" tab - @jira https://akeneo.atlassian.net/browse/PIM-3447 Scenario: Validate the max database value length of text attribute Given I change the Description to an invalid value diff --git a/tests/Acceptance/Behat/AcceptanceContext.php b/tests/Acceptance/Behat/AcceptanceContext.php new file mode 100644 index 000000000000..48d6e2fbc079 --- /dev/null +++ b/tests/Acceptance/Behat/AcceptanceContext.php @@ -0,0 +1,128 @@ + + * @copyright 2017 Akeneo SAS (http://www.akeneo.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +final class AcceptanceContext implements Context +{ + /** @var AttributeFactory */ + private $attributeFactory; + + /** @var InMemoryAttributeRepository */ + private $attributeRepository; + + /** @var ProductBuilderInterface */ + private $productBuilder; + + /** @var ValidatorInterface */ + private $productValidator; + + /** @var ConstraintViolationListInterface|null */ + private $lastProductErrors; + + public function __construct( + AttributeFactory $attributeFactory, + InMemoryAttributeRepository $attributeRepository, + ProductBuilderInterface $productBuilder, + ValidatorInterface $productValidator + ) { + $this->attributeFactory = $attributeFactory; + $this->attributeRepository = $attributeRepository; + $this->productBuilder = $productBuilder; + $this->productValidator = $productValidator; + } + + /** + * @Given an identifier attribute has been created + */ + public function loadIdentifierAttribute(): void + { + $identifierAttribute = $this->attributeFactory->createAttribute(AttributeTypes::IDENTIFIER); + $identifierAttribute->setCode('sku'); + + $this->attributeRepository->save($identifierAttribute); + } + + /** + * @Given a text attribute :attributeCode with a validation rule ":validationRule" has been created + */ + public function loadTextAttributeWithValidationRule(string $attributeCode, string $validationRule): void + { + $attribute = $this->attributeFactory->createAttribute(AttributeTypes::TEXT); + $attribute->setCode($attributeCode); + $attribute->setValidationRule($validationRule); + + $this->attributeRepository->save($attribute); + } + + /** + * @Given a text attribute :attributeCode with a validation rule "regexp" and a pattern ":pattern" has been created + */ + public function loadTextAttributeWithRegexValidationRule(string $attributeCode, string $pattern): void + { + $attribute = $this->attributeFactory->createAttribute(AttributeTypes::TEXT); + $attribute->setCode($attributeCode); + $attribute->setValidationRule('regexp'); + $attribute->setValidationRegexp($pattern); + + $this->attributeRepository->save($attribute); + } + + /** + * @When I create a product containing the following values: + * + * @param TableNode $values + */ + public function createAProductContainingTheFollowingValues(TableNode $values): void + { + $product = $this->productBuilder->createProduct('new_product'); + + foreach ($values->getRows() as [$attributeCode, $value]) { + $attribute = $this->attributeRepository->findOneByIdentifier($attributeCode); + $this->productBuilder->addOrReplaceValue($product, $attribute, null, null, $value); + } + + $this->lastProductErrors = $this->productValidator->validate($product); + } + + /** + * @Then the :code value should be invalid with the message ":message" + */ + public function theValueShouldBeInvalid(string $code, string $message): void + { + if (null === $this->lastProductErrors || count($this->lastProductErrors) === 0) { + throw new \Exception('No validation error found.'); + } + + foreach ($this->lastProductErrors as $error) { + if (sprintf('values[%s--].data', $code) === $error->getPropertyPath() + && $message === $error->getMessage() + ) { + return; + } + } + + throw new \Exception( + sprintf('The value of "%s" should be invalid but is not, or the error message is wrong.', $code) + ); + } +} diff --git a/tests/Acceptance/ValuesValidation/apply_validation_rules_on_values.feature b/tests/Acceptance/ValuesValidation/apply_validation_rules_on_values.feature new file mode 100644 index 000000000000..9177a73eb1db --- /dev/null +++ b/tests/Acceptance/ValuesValidation/apply_validation_rules_on_values.feature @@ -0,0 +1,25 @@ +Feature: Apply validation rules on values + In order to guarantee the quality of my data + As a regular user + I need to be able to validate values using attribute validation rules and be properly informed by a message + + Background: + Given an identifier attribute has been created + + Scenario: Forbid incorrect email values + Given a text attribute "email_address" with a validation rule "email" has been created + When I create a product containing the following values: + | email_address | not a valid email address | + Then the email_address value should be invalid with the message "This value is not a valid email address." + + Scenario: Forbid incorrect URL values + Given a text attribute "link" with a validation rule "url" has been created + When I create a product containing the following values: + | link | https:/www.akeneo.com | + Then the link value should be invalid with the message "This value is not a valid URL." + + Scenario: Forbid values not matching a regular expression + Given a text attribute "custom_code" with a validation rule "regexp" and a pattern "/^[a-z]*$/" has been created + When I create a product containing the following values: + | custom_code | 42 | + Then the custom_code value should be invalid with the message "This value is not valid." diff --git a/tests/Common/Persistence/InMemoryAttributeRepository.php b/tests/Common/Persistence/InMemoryAttributeRepository.php new file mode 100644 index 000000000000..1599b2bc8fc9 --- /dev/null +++ b/tests/Common/Persistence/InMemoryAttributeRepository.php @@ -0,0 +1,189 @@ + + * @copyright 2017 Akeneo SAS (http://www.akeneo.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +final class InMemoryAttributeRepository implements AttributeRepositoryInterface, SaverInterface +{ + /** @var ArrayCollection */ + private $attributes; + + public function __construct() + { + $this->attributes = new ArrayCollection(); + } + + /** + * {@inheritdoc} + * + * TODO: At some point we'll need to generate ids for new objects. Maybe use it as a key ? + */ + public function save($attribute, array $options = []) + { + if (!$attribute instanceof AttributeInterface) { + throw new \InvalidArgumentException('This repository can only store attribute objects.'); + } + + if ($this->attributes->contains($attribute)) { + throw new \Exception('This attribute is already stored.'); + } + + $this->attributes->add($attribute); + } + + /** + * {@inheritdoc} + */ + public function getIdentifier() + { + $matchingAttributes = $this->attributes->filter( + function (AttributeInterface $attribute) { + return AttributeTypes::IDENTIFIER === $attribute->getType(); + } + ); + + if (0 === count($matchingAttributes)) { + return null; + } + + return $matchingAttributes->first(); + } + + /** + * {@inheritdoc} + */ + public function findOneByIdentifier($identifier) + { + $matchingAttributes = $this->attributes->filter( + function (AttributeInterface $attribute) use ($identifier) { + return $identifier === $attribute->getCode(); + } + ); + + if (0 === count($matchingAttributes)) { + return null; + } + + return $matchingAttributes->first(); + } + + /********************************* + * NOT IMPLEMENTED YET + ********************************/ + + public function findAllInDefaultGroup() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findUniqueAttributeCodes() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findMediaAttributeCodes() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findAllAxesQB() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getAttributesAsArray($withLabel = false, $locale = null, array $ids = []) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getAttributeIdsUseableInGrid($codes = null, $groupIds = null) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getIdentifierCode() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getAttributeTypeByCodes(array $codes) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getAttributeCodesByType($type) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getAttributeCodesByGroup(AttributeGroupInterface $group) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findAttributesByFamily(FamilyInterface $family) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function countAll() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findAvailableAxes($locale) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getIdentifierProperties() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function find($id) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findAll() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function findOneBy(array $criteria) + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } + + public function getClassName() + { + throw new \RuntimeException(__METHOD__.' not implemented.'); + } +}