mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
Merge branch '1.13' into 2.0
* 1.13: [Api][Attribute] Add types enum to product attribute schema [Api][Attribute] Minor cleanup [API] Add contract tests for creating and validating attributes types [API] Add example configurations for all product attributes [API] Cover the impossibility to edit the code and type of an existing product attribute [API] Add possibility for optional message to ProductAttributeCannotBeRemoved exception [Behat] Minor improvements in product attributes scenarios and implementation [Api][Attribute] Switch storage type setting from persister to event subscriber [Api][Admin] Sorting product attributes [Api][Admin] Validating product attributes [Api][Admin] Updating product attributes [Api][Admin] Creating product attributes [Api][Admin] Cover product attributes removing [Api][Admin] Cover product attributes index
This commit is contained in:
commit
5231f241a7
73 changed files with 2797 additions and 105 deletions
|
|
@ -8,7 +8,7 @@ Feature: Adding a new checkbox product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new checkbox product attribute
|
||||
When I want to create a new checkbox product attribute
|
||||
And I specify its code as "t_shirt_with_cotton"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Adding a new checkbox product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the checkbox attribute "T-Shirt with cotton" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding a checkbox product attribute
|
||||
When I want to create a new checkbox product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new float product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new float product attribute
|
||||
When I want to create a new float product attribute
|
||||
And I specify its code as "display_size"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Adding a new float product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the float attribute "Display Size" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding a float product attribute
|
||||
When I want to create a new float product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new integer product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new integer product attribute
|
||||
When I want to create a new integer product attribute
|
||||
And I specify its code as "book_pages"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Adding a new integer product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the integer attribute "Book pages" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding a integer product attribute
|
||||
When I want to create a new integer product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new non-translatable product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new non-translatable product attribute
|
||||
When I want to create a new integer product attribute
|
||||
And I specify its code as "damage"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new percent product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new percent product attribute
|
||||
When I want to create a new percent product attribute
|
||||
And I specify its code as "t_shirt_cotton_content"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Adding a new percent product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the percent attribute "T-Shirt cotton content" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding a percent product attribute
|
||||
When I want to create a new percent product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new select product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Adding a new select product attribute
|
||||
When I want to create a new select product attribute
|
||||
And I specify its code as "mug_material"
|
||||
|
|
@ -18,7 +18,7 @@ Feature: Adding a new select product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the select attribute "Mug material" should appear in the store
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Adding multiple select product attribute
|
||||
When I want to create a new select product attribute
|
||||
And I specify its code as "mug_material"
|
||||
|
|
@ -30,7 +30,7 @@ Feature: Adding a new select product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the select attribute "Mug material" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding a select product attribute
|
||||
When I want to create a new select product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new text product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new text product attribute
|
||||
When I want to create a new text product attribute
|
||||
And I specify its code as "t_shirt_brand"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Adding a new text product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the text attribute "T-Shirt brand" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding text a product attribute
|
||||
When I want to create a new text product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Adding a new textarea product attribute
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Adding a new textarea product attribute
|
||||
When I want to create a new textarea product attribute
|
||||
And I specify its code as "t_shirt_details"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Adding a new textarea product attribute
|
|||
Then I should be notified that it has been successfully created
|
||||
And the textarea attribute "T-Shirt details" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing disabled type field while adding a textarea product attribute
|
||||
When I want to create a new textarea product attribute
|
||||
Then the type field should be disabled
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Feature: Browsing product attributes
|
|||
And the store has a integer product attribute "Book pages" with code "book_pages"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Browsing all product attributes in store
|
||||
When I want to see all product attributes in store
|
||||
Then I should see 3 product attributes in the list
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Feature: Deleting multiple product attributes
|
|||
And the store has a integer product attribute "Pages"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @no-api
|
||||
Scenario: Deleting multiple product attributes at once
|
||||
When I browse product attributes
|
||||
And I check the "Publisher" product attribute
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@managing_product_attributes
|
||||
Feature:Removing a attribute
|
||||
Feature: Deleting a product attribute
|
||||
In order to keep my collection of product attributes not cluttered
|
||||
As an administrator
|
||||
I want to be able to remove an attribute that is not assigned to any of the products
|
||||
|
|
@ -8,14 +8,14 @@ Feature:Removing a attribute
|
|||
Given I am logged in as an administrator
|
||||
And the store has a product "44 Magnum"
|
||||
|
||||
@ui
|
||||
Scenario: Try deleting a attribute from the registry when product use him
|
||||
@ui @api
|
||||
Scenario: Trying to delete an attribute from the registry when a product uses it
|
||||
Given this product has a text attribute "Gun caliber" with value "11 mm"
|
||||
When I delete this product attribute
|
||||
Then I should be notified that it has been failed deleted "product attribute"
|
||||
Then I should be notified that it is in use
|
||||
|
||||
@ui
|
||||
Scenario: Deleting a text product attribute when not by used
|
||||
@ui @api
|
||||
Scenario: Deleting a text product attribute when it's not used
|
||||
Given the store has a text product attribute "Gun caliber"
|
||||
When I delete this product attribute
|
||||
Then I should be notified that it has been successfully deleted
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
@managing_product_attributes
|
||||
Feature: Deleting a text product attribute
|
||||
In order to remove test, obsolete or incorrect text product attribute
|
||||
As an Administrator
|
||||
I want to be able to delete a text product attribute
|
||||
|
||||
Background:
|
||||
Given I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
Scenario: Deleting a text product attribute from the registry
|
||||
Given the store has a text product attribute "T-Shirt cotton brand"
|
||||
When I delete this product attribute
|
||||
Then I should be notified that it has been successfully deleted
|
||||
And this product attribute should no longer exist in the registry
|
||||
|
|
@ -9,7 +9,7 @@ Feature: Editing a select product attribute
|
|||
And the store has a select product attribute "T-Shirt material" with value "Banana skin"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Editing a select product attribute name
|
||||
When I want to edit this product attribute
|
||||
And I change its name to "T-Shirt material" in "English (United States)"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Editing a select product attribute
|
|||
Then I should be notified that it has been successfully edited
|
||||
And the select attribute "T-Shirt material" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Editing a select product attribute value
|
||||
When I want to edit this product attribute
|
||||
And I change its value "Banana skin" to "Orange skin"
|
||||
|
|
@ -25,7 +25,7 @@ Feature: Editing a select product attribute
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product attribute should have value "Orange skin"
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Adding a new value to an existing select product attribute
|
||||
When I want to edit this product attribute
|
||||
And I add value "Orange skin" in "English (United States)"
|
||||
|
|
@ -33,7 +33,7 @@ Feature: Editing a select product attribute
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product attribute should have value "Orange skin"
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Deleting a value from an existing select product attribute
|
||||
When I want to edit this product attribute
|
||||
And I delete value "Banana skin"
|
||||
|
|
@ -41,12 +41,12 @@ Feature: Editing a select product attribute
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product attribute should not have value "Banana skin"
|
||||
|
||||
@ui
|
||||
Scenario: Seeing disabled code field while editing a product attribute
|
||||
@ui @api
|
||||
Scenario: Being unable to change code of an existing product attribute
|
||||
When I want to edit this product attribute
|
||||
Then the code field should be disabled
|
||||
Then I should not be able to edit its code
|
||||
|
||||
@ui
|
||||
Scenario: Seeing disabled type field while editing a product attribute
|
||||
@ui @api
|
||||
Scenario: Being unable to change type of an existing product attribute
|
||||
When I want to edit this product attribute
|
||||
Then the type field should be disabled
|
||||
Then I should not be able to edit its type
|
||||
|
|
|
|||
|
|
@ -6,25 +6,23 @@ Feature: Editing a text product attribute
|
|||
|
||||
Background:
|
||||
Given the store is available in "English (United States)"
|
||||
And the store has a text product attribute "T-Shirt cotton brand" with code "t_shirt_brand"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Editing product attribute name
|
||||
Given the store has a text product attribute "T-Shirt cotton brand"
|
||||
When I want to edit this product attribute
|
||||
And I change its name to "T-Shirt material" in "English (United States)"
|
||||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And the text attribute "T-Shirt material" should appear in the store
|
||||
|
||||
@ui
|
||||
Scenario: Seeing disabled code field while editing a product attribute
|
||||
Given the store has a text product attribute "T-Shirt cotton brand" with code "t_shirt_brand"
|
||||
@ui @api
|
||||
Scenario: Being unable to change code of an existing product attribute
|
||||
When I want to edit this product attribute
|
||||
Then the code field should be disabled
|
||||
Then I should not be able to edit its code
|
||||
|
||||
@ui
|
||||
Scenario: Seeing disabled type field while editing a product attribute
|
||||
Given the store has a text product attribute "T-Shirt cotton brand" with code "t_shirt_brand"
|
||||
@ui @api
|
||||
Scenario: Being unable to change type of an existing product attribute
|
||||
When I want to edit this product attribute
|
||||
Then the type field should be disabled
|
||||
Then I should not be able to edit its type
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Product attribute unique code validation
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to add a new product attribute with taken code
|
||||
Given the store has a text product attribute "T-Shirt cotton material" with code "t_shirt_material"
|
||||
When I want to create a new text product attribute
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Seeing correct select attribute values in different locale than default
|
|||
Given the store is available in "French (France)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Seeing correct attribute values in different locale than default one
|
||||
When I want to create a new select product attribute
|
||||
And I specify its code as "mug_material"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Select product attribute validation
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add a new select product attribute with wrong max entries value
|
||||
When I want to create a new select product attribute
|
||||
And I name it "Mug material" in "English (United States)"
|
||||
|
|
@ -22,7 +22,7 @@ Feature: Select product attribute validation
|
|||
Then I should be notified that max entries value must be greater or equal to the min entries value
|
||||
And the attribute with code "mug_material" should not appear in the store
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add a new select product attribute with wrong min entries value
|
||||
When I want to create a new select product attribute
|
||||
And I name it "Mug material" in "English (United States)"
|
||||
|
|
@ -36,7 +36,7 @@ Feature: Select product attribute validation
|
|||
Then I should be notified that min entries value must be lower or equal to the number of added choices
|
||||
And the attribute with code "mug_material" should not appear in the store
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add a new select product attribute with specified entries values but without multiple option
|
||||
When I want to create a new select product attribute
|
||||
And I name it "Mug material" in "English (United States)"
|
||||
|
|
|
|||
|
|
@ -10,21 +10,21 @@ Feature: Sorting listed product attributes by position
|
|||
And the store has a integer product attribute "Book pages" at position 0
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Product attributes are sorted by position in ascending order by default
|
||||
When I want to see all product attributes in store
|
||||
Then I should see 3 product attributes in the list
|
||||
And the first product attribute on the list should have name "Book pages"
|
||||
And the last product attribute on the list should have name "T-Shirt with cotton"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Product attribute added at no position gets put at the bottom of the list
|
||||
Given the store has also a text product attribute "Drive type"
|
||||
When I want to see all product attributes in store
|
||||
Then I should see 4 product attributes in the list
|
||||
And the last product attribute on the list should have name "Drive type"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Product attribute added at position 0 is added as the first one
|
||||
Given the store has also a text product attribute "Drive type" at position 0
|
||||
When I want to see all product attributes in store
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Text product attribute validation
|
|||
Given the store is available in "English (United States)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to add a new text product attribute without name
|
||||
When I want to create a new text product attribute
|
||||
And I specify its code as "t_shirt_with_cotton"
|
||||
|
|
@ -17,7 +17,7 @@ Feature: Text product attribute validation
|
|||
Then I should be notified that name is required
|
||||
And the attribute with code "t_shirt_with_cotton" should not appear in the store
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to add a new text product attribute without code
|
||||
When I want to create a new text product attribute
|
||||
And I name it "T-Shirt brand" in "English (United States)"
|
||||
|
|
@ -26,16 +26,16 @@ Feature: Text product attribute validation
|
|||
Then I should be notified that code is required
|
||||
And the attribute with name "T-Shirt brand" should not appear in the store
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to remove name for existing text product attribute
|
||||
Given the store has a text product attribute "T-Shirt cotton brand"
|
||||
When I want to edit this product attribute
|
||||
And I remove its name from "English (United States)" translation
|
||||
And I try to save my changes
|
||||
Then I should be notified that name is required
|
||||
And the attribute with code "t_shirt_with_cotton" should not appear in the store
|
||||
And the text attribute "T-Shirt cotton brand" should still be in the store
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to add a new text product attribute with wrong configuration
|
||||
When I want to create a new text product attribute
|
||||
And I name it "T-Shirt brand" in "English (United States)"
|
||||
|
|
|
|||
|
|
@ -770,6 +770,26 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:persist\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:remove\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:remove\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:supports\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductTaxonDataPersister\\:\\:persist\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,465 @@
|
|||
<?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\Behat\Context\Api\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ManagingProductAttributesContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to see all product attributes in store
|
||||
*/
|
||||
public function iWantToBrowseProductAttributes(): void
|
||||
{
|
||||
$this->client->index(Resources::PRODUCT_ATTRIBUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I(?:| try to) delete (this product attribute)$/
|
||||
*/
|
||||
public function iDeleteThisProductAttribute(ProductAttributeInterface $attribute): void
|
||||
{
|
||||
$this->client->delete(Resources::PRODUCT_ATTRIBUTES, $attribute->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to create a new :type product attribute
|
||||
*/
|
||||
public function iWantToCreateANewTypedProductAttribute(string $type): void
|
||||
{
|
||||
$this->client->buildCreateRequest(Resources::PRODUCT_ATTRIBUTES);
|
||||
$this->client->addRequestData('type', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify its code as :code
|
||||
*/
|
||||
public function iSpecifyItsCodeAs(string $code): void
|
||||
{
|
||||
$this->client->addRequestData('code', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I name it :name in :localeCode
|
||||
* @When I change its name to :name in :localeCode
|
||||
* @When I do not name it
|
||||
* @When I remove its name from :localeCode translation
|
||||
*/
|
||||
public function iNameItIn(string $name = '', string $localeCode = 'en_US'): void
|
||||
{
|
||||
$this->client->updateRequestData(['translations' => [$localeCode => ['name' => $name, 'locale' => $localeCode]]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I (also) add value :value in :localeCode
|
||||
*/
|
||||
public function iAddValueIn(string $value, string $localeCode): void
|
||||
{
|
||||
$uuid = Uuid::uuid4()->toString();
|
||||
|
||||
$this->client->addRequestData('configuration', ['choices' => [$uuid => [$localeCode => $value]]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I disable its translatability
|
||||
*/
|
||||
public function iDisableItsTranslatability(): void
|
||||
{
|
||||
$this->client->addRequestData('translatable', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I check multiple option
|
||||
*/
|
||||
public function iCheckMultipleOption(): void
|
||||
{
|
||||
$this->client->addRequestData('configuration', ['multiple' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I do not check multiple option
|
||||
* @When I do not specify its code
|
||||
*/
|
||||
public function intentionallyBlank(): void
|
||||
{
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify its :limitType entries value as :count
|
||||
* @When I specify its :limitType length as :count
|
||||
*/
|
||||
public function iSpecifyItsLimitTypeEntriesAs(string $limitType, int $count): void
|
||||
{
|
||||
$this->client->addRequestData('configuration', [$limitType => $count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I want to edit (this product attribute)$/
|
||||
*/
|
||||
public function iWantToEditThisProductAttribute(ProductAttributeInterface $productAttribute): void
|
||||
{
|
||||
$this->sharedStorage->set('product_attribute', $productAttribute);
|
||||
|
||||
$this->client->buildUpdateRequest(Resources::PRODUCT_ATTRIBUTES, $productAttribute->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I add it
|
||||
* @When I try to add it
|
||||
*/
|
||||
public function iAddIt(): void
|
||||
{
|
||||
$this->client->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I change (its) value "([^"]+)" to "([^"]+)"$/
|
||||
*/
|
||||
public function iChangeItsValueTo(
|
||||
ProductAttributeInterface $productAttribute,
|
||||
string $oldValue,
|
||||
string $newValue,
|
||||
): void {
|
||||
$response = $this->client->show(Resources::PRODUCT_ATTRIBUTES, $productAttribute->getCode());
|
||||
$configuration = $this->responseChecker->getValue($response, 'configuration');
|
||||
|
||||
$choices = $configuration['choices'];
|
||||
foreach ($choices as $key => $choice) {
|
||||
if ($choice['en_US'] === $oldValue) {
|
||||
$choices[$key]['en_US'] = $newValue;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->updateRequestData(['configuration' => ['choices' => $choices]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I delete value :value
|
||||
*/
|
||||
public function iDeleteValue(string $value): void
|
||||
{
|
||||
/** @var ProductAttributeInterface $productAttribute */
|
||||
$productAttribute = $this->sharedStorage->get('product_attribute');
|
||||
$response = $this->client->show(Resources::PRODUCT_ATTRIBUTES, $productAttribute->getCode());
|
||||
$configuration = $this->responseChecker->getValue($response, 'configuration');
|
||||
|
||||
$choices = $configuration['choices'];
|
||||
foreach ($choices as $key => $choice) {
|
||||
if ($choice['en_US'] === $value) {
|
||||
unset($choices[$key]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->setRequestData(['configuration' => ['choices' => $choices]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see :count product attributes in the list
|
||||
*/
|
||||
public function iShouldSeeCountProductAttributesInTheList(int $count): void
|
||||
{
|
||||
Assert::same($this->responseChecker->countCollectionItems($this->client->getLastResponse()), $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the first product attribute on the list should have name :name
|
||||
*/
|
||||
public function theFirstProductAttributeOnTheListShouldHaveName(string $name): void
|
||||
{
|
||||
$first = $this->responseChecker->getCollection($this->client->getLastResponse())[0];
|
||||
|
||||
Assert::same($first['translations']['en_US']['name'], $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the last product attribute on the list should have name :name
|
||||
*/
|
||||
public function theLastProductAttributeOnTheListShouldHaveName(string $name): void
|
||||
{
|
||||
$collection = $this->responseChecker->getCollection($this->client->getLastResponse());
|
||||
$last = end($collection);
|
||||
|
||||
Assert::same($last['translations']['en_US']['name'], $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see the product attribute :attributeName in the list
|
||||
*/
|
||||
public function iShouldSeeTheProductAttributeInTheList(string $attributeName): void
|
||||
{
|
||||
Assert::true($this->responseChecker->hasItemWithTranslation(
|
||||
$this->client->getLastResponse(),
|
||||
'en_US',
|
||||
'name',
|
||||
$attributeName,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully deleted
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItHasBeenSuccessfullyDeleted(): void
|
||||
{
|
||||
$this->responseChecker->isDeletionSuccessful($this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this product attribute) should no longer exist in the registry$/
|
||||
*/
|
||||
public function thisProductAttributeShouldNoLongerExistInTheRegistry(ProductAttributeInterface $productAttribute): void
|
||||
{
|
||||
$response = $this->client->index(Resources::PRODUCT_ATTRIBUTES);
|
||||
|
||||
Assert::false(
|
||||
$this->responseChecker->hasItemWithValue($response, 'code', $productAttribute->getCode()),
|
||||
sprintf('Product attribute with code %s exists, but should not', $productAttribute->getCode()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it is in use
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItIsInUse(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'Cannot delete, the product attribute is in use.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully created
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItHasBeenSuccessfullyCreated(): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->isCreationSuccessful($this->client->getLastResponse()),
|
||||
'Product attribute could not be created',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the :type attribute :name should appear in the store
|
||||
* @Then the :type attribute :name should still be in the store
|
||||
*/
|
||||
public function theAttributeShouldAppearInTheStore(string $type, string $name): void
|
||||
{
|
||||
$response = $this->client->index(Resources::PRODUCT_ATTRIBUTES);
|
||||
|
||||
/** @var array<string, mixed> $item */
|
||||
foreach ($this->responseChecker->getCollection($response) as $item) {
|
||||
if ($item['type'] === $type && $item['translations']['en_US']['name'] === $name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Product attribute of type "%s" with name "%s" has not been found',
|
||||
$type,
|
||||
$name,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the attribute with :field :value should not appear in the store
|
||||
*/
|
||||
public function theAttributeWithCodeShouldNotAppearInTheStore(string $field, string $value): void
|
||||
{
|
||||
$response = $this->client->index(Resources::PRODUCT_ATTRIBUTES);
|
||||
|
||||
Assert::false(
|
||||
$this->responseChecker->hasItemWithValue($response, $field, $value),
|
||||
sprintf('Product attribute with %s %s exists, but should not', $field, $value),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see the value :value
|
||||
*/
|
||||
public function iShouldSeeTheValue(string $value): void
|
||||
{
|
||||
$content = $this->responseChecker->getResponseContent($this->client->getLastResponse());
|
||||
$choices = $content['configuration']['choices'];
|
||||
|
||||
foreach ($choices as $values) {
|
||||
if (in_array($value, $values)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Product attribute value "%s" has not been found in choices: %s',
|
||||
$value,
|
||||
json_encode($choices),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this product attribute) should have value "([^"]+)"$/
|
||||
*/
|
||||
public function thisProductAttributeShouldHaveValue(
|
||||
ProductAttributeInterface $productAttribute,
|
||||
string $value,
|
||||
): void {
|
||||
$this->client->show(Resources::PRODUCT_ATTRIBUTES, $productAttribute->getCode());
|
||||
|
||||
$this->iShouldSeeTheValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this product attribute) should not have value "([^"]+)"$/
|
||||
*/
|
||||
public function thisProductAttributeShouldNotHaveValue(
|
||||
ProductAttributeInterface $productAttribute,
|
||||
string $value,
|
||||
): void {
|
||||
$response = $this->client->show(Resources::PRODUCT_ATTRIBUTES, $productAttribute->getCode());
|
||||
$content = $this->responseChecker->getResponseContent($response);
|
||||
$choices = $content['configuration']['choices'];
|
||||
|
||||
foreach ($choices as $values) {
|
||||
if (in_array($value, $values)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Product attribute value "%s" has been found but should not',
|
||||
$value,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that :element is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatFieldIsRequired(string $field): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
sprintf('Please enter attribute %s.', $field),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that product attribute with this code already exists
|
||||
*/
|
||||
public function iShouldBeNotifiedThatProductAttributeWithThisCodeAlreadyExists(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'This code is already in use.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that max length must be greater or equal to the min length
|
||||
*/
|
||||
public function iShouldBeNotifiedThatMaxLengthMustBeGreaterOrEqualToTheMinLength(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'Configuration max length must be greater or equal to the min length.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then there should still be only one product attribute with code :code
|
||||
*/
|
||||
public function thereShouldStillBeOnlyOneProductAttributeWithCode(string $code): void
|
||||
{
|
||||
$items = $this->responseChecker->getCollectionItemsWithValue(
|
||||
$this->client->index(Resources::PRODUCT_ATTRIBUTES),
|
||||
'code',
|
||||
$code,
|
||||
);
|
||||
|
||||
Assert::count($items, 1, sprintf('More than one attribute with code %s found', $code));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that max entries value must be greater or equal to the min entries value
|
||||
*/
|
||||
public function iShouldBeNotifiedThatMaxEntriesValueMustBeGreaterOrEqualToTheMinEntriesValue(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'Configuration max entries value must be greater or equal to the min entries value.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that min entries value must be lower or equal to the number of added choices
|
||||
*/
|
||||
public function iShouldBeNotifiedThatMinEntriesValueMustBeLowerOrEqualToTheNumberOfAddedChoices(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'Configuration min entries value must be lower or equal to the number of added choices.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that multiple must be true if min or max entries values are specified
|
||||
*/
|
||||
public function iShouldBeNotifiedThatMultipleMustBeTrueIfMinOrMaxEntriesValuesAreSpecified(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'Configuration multiple must be true if min or max entries values are specified.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not be able to edit its code
|
||||
*/
|
||||
public function iShouldNotBeAbleToEditItsCode(): void
|
||||
{
|
||||
$this->client->updateRequestData(['code' => 'NEW_CODE']);
|
||||
|
||||
Assert::false(
|
||||
$this->responseChecker->hasValue($this->client->update(), 'code', 'NEW_CODE'),
|
||||
'The code field with value NEW_CODE exist',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not be able to edit its type
|
||||
*/
|
||||
public function iShouldNotBeAbleToEditItsType(): void
|
||||
{
|
||||
$this->client->updateRequestData(['type' => 'percent']);
|
||||
|
||||
Assert::false(
|
||||
$this->responseChecker->hasValue($this->client->update(), 'type', 'percent'),
|
||||
'The product attribute has new type select set.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,8 @@ final class Resources
|
|||
|
||||
public const PRODUCT_ASSOCIATION_TYPES = 'product-association-types';
|
||||
|
||||
public const PRODUCT_ATTRIBUTES = 'product-attributes';
|
||||
|
||||
public const PRODUCT_OPTIONS = 'product-options';
|
||||
|
||||
public const PRODUCT_REVIEWS = 'product-reviews';
|
||||
|
|
|
|||
|
|
@ -99,14 +99,6 @@ final class ManagingPaymentMethodsContext implements Context
|
|||
$this->indexPage->deleteResourceOnPage(['code' => $paymentMethod->getCode(), 'name' => $paymentMethod->getName()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it is in use
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItIsInUse()
|
||||
{
|
||||
$this->notificationChecker->checkNotification('Cannot delete, the Payment method is in use.', NotificationType::failure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then this payment method :element should be :value
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ final class ManagingProductAttributesContext implements Context
|
|||
|
||||
/**
|
||||
* @Then the :type attribute :name should appear in the store
|
||||
* @Then the :type attribute :name should still be in the store
|
||||
*/
|
||||
public function theAttributeShouldAppearInTheStore($type, $name)
|
||||
{
|
||||
|
|
@ -152,17 +153,18 @@ final class ManagingProductAttributesContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then the code field should be disabled
|
||||
* @Then I should not be able to edit its code
|
||||
*/
|
||||
public function theCodeFieldShouldBeDisabled()
|
||||
public function iShouldNotBeAbleToEditItsCode(): void
|
||||
{
|
||||
Assert::true($this->updatePage->isCodeDisabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the type field should be disabled
|
||||
* @Then I should not be able to edit its type
|
||||
*/
|
||||
public function theTypeFieldShouldBeDisabled()
|
||||
public function theTypeFieldShouldBeDisabled(): void
|
||||
{
|
||||
/** @var CreatePageInterface|UpdatePageInterface $currentPage */
|
||||
$currentPage = $this->currentPageResolver->getCurrentPageWithForm([$this->createPage, $this->updatePage]);
|
||||
|
|
@ -328,7 +330,7 @@ final class ManagingProductAttributesContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When /^I delete (this product attribute)$/
|
||||
* @When /^I(?:| try to) delete (this product attribute)$/
|
||||
*/
|
||||
public function iDeleteThisProductAttribute(ProductAttributeInterface $productAttribute)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -448,14 +448,6 @@ final class ManagingShippingMethodsContext implements Context
|
|||
Assert::false($this->indexPage->isSingleResourceOnPage(['code' => $shippingMethod->getCode()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it is in use
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItIsInUse()
|
||||
{
|
||||
$this->notificationChecker->checkNotification('Cannot delete, the Shipping method is in use.', NotificationType::failure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that amount for :channel channel should not be blank
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -76,14 +76,14 @@ final class NotificationContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been failed deleted :name
|
||||
* @Then I should be notified that it is in use
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItHasBeenFailedDeleted(string $name): void
|
||||
public function iShouldBeNotifiedThatItIsInUse(): void
|
||||
{
|
||||
$this->testHelper->waitUntilNotificationPopups(
|
||||
$this->notificationChecker,
|
||||
NotificationType::failure(),
|
||||
'Cannot delete, the ' . ucfirst($name) . ' is in use.',
|
||||
'Cannot delete',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@
|
|||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.admin.managing_product_attributes" class="Sylius\Behat\Context\Api\Admin\ManagingProductAttributesContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.admin.managing_product_options" class="Sylius\Behat\Context\Api\Admin\ManagingProductOptionsContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ imports:
|
|||
- suites/api/payment/managing_payments.yml
|
||||
- suites/api/product/adding_product_review.yml
|
||||
- suites/api/product/managing_product_association_types.yml
|
||||
- suites/api/product/managing_product_attributes.yml
|
||||
- suites/api/product/managing_product_options.yml
|
||||
- suites/api/product/managing_product_reviews.yml
|
||||
- suites/api/product/managing_product_variants.yml
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Sylius Sp. z o.o.
|
||||
|
||||
default:
|
||||
suites:
|
||||
api_managing_product_attributes:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.locale
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.product_attribute
|
||||
|
||||
- sylius.behat.context.api.admin.managing_product_attributes
|
||||
- sylius.behat.context.api.admin.response
|
||||
- sylius.behat.context.api.admin.save
|
||||
|
||||
filters:
|
||||
tags: "@managing_product_attributes&&@api"
|
||||
javascript: false
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?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\ApiBundle\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class ProductAttributeDataPersister implements ContextAwareDataPersisterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports($data, array $context = []): bool
|
||||
{
|
||||
return $data instanceof ProductAttributeInterface;
|
||||
}
|
||||
|
||||
/** @param ProductAttributeInterface $data */
|
||||
public function persist($data, array $context = [])
|
||||
{
|
||||
return $this->decoratedDataPersister->persist($data, $context);
|
||||
}
|
||||
|
||||
public function remove($data, array $context = [])
|
||||
{
|
||||
try {
|
||||
return $this->decoratedDataPersister->remove($data, $context);
|
||||
} catch (ForeignKeyConstraintViolationException) {
|
||||
throw new ProductAttributeCannotBeRemoved();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?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\ApiBundle\EventSubscriber;
|
||||
|
||||
use ApiPlatform\Core\EventListener\EventPriorities;
|
||||
use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface;
|
||||
use Sylius\Component\Attribute\Model\AttributeInterface;
|
||||
use Sylius\Component\Registry\ServiceRegistryInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\ViewEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/** @experimental */
|
||||
final class AttributeEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private ServiceRegistryInterface $attributeTypeRegistry)
|
||||
{
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::VIEW => ['assignStorageType', EventPriorities::PRE_VALIDATE],
|
||||
];
|
||||
}
|
||||
|
||||
public function assignStorageType(ViewEvent $event): void
|
||||
{
|
||||
$attribute = $event->getControllerResult();
|
||||
$method = $event->getRequest()->getMethod();
|
||||
|
||||
if (
|
||||
!$attribute instanceof AttributeInterface ||
|
||||
!in_array($method, [Request::METHOD_POST, Request::METHOD_PUT], true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $attribute->getType() || '' === $attribute->getType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
null !== $attribute->getStorageType() ||
|
||||
!$this->attributeTypeRegistry->has($attribute->getType())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var AttributeTypeInterface $attributeType */
|
||||
$attributeType = $this->attributeTypeRegistry->get($attribute->getType());
|
||||
|
||||
$attribute->setStorageType($attributeType->getStorageType());
|
||||
|
||||
$event->setControllerResult($attribute);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?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\ApiBundle\Exception;
|
||||
|
||||
/** @experimental */
|
||||
final class ProductAttributeCannotBeRemoved extends \RuntimeException
|
||||
{
|
||||
public function __construct(
|
||||
string $message = 'Cannot delete, the product attribute is in use.',
|
||||
int $code = 0,
|
||||
\Throwable $previous = null,
|
||||
) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?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\ApiBundle\OpenApi\Documentation;
|
||||
|
||||
use ApiPlatform\OpenApi\OpenApi;
|
||||
use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface;
|
||||
use Sylius\Component\Registry\ServiceRegistryInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class AttributeTypeDocumentationModifier implements DocumentationModifierInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ServiceRegistryInterface $attributeTypeRegistry,
|
||||
) {
|
||||
}
|
||||
|
||||
public function modify(OpenApi $docs): OpenApi
|
||||
{
|
||||
$components = $docs->getComponents();
|
||||
$schemas = $components->getSchemas();
|
||||
if (null === $schemas) {
|
||||
return $docs;
|
||||
}
|
||||
|
||||
$schemas = $this->updateAttributeTypesSchema($schemas);
|
||||
|
||||
return $docs->withComponents(
|
||||
$components->withSchemas($schemas),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ArrayObject<string, mixed> $schemas
|
||||
*
|
||||
* @return \ArrayObject<string, mixed>
|
||||
*/
|
||||
private function updateAttributeTypesSchema(\ArrayObject $schemas): \ArrayObject
|
||||
{
|
||||
$attributeTypes = $this->getAttributeTypes();
|
||||
|
||||
$schemasToBeUpdated = [
|
||||
'ProductAttribute.admin.product_attribute.read',
|
||||
'ProductAttribute.admin.product_attribute.create',
|
||||
'ProductAttribute.jsonld-admin.product_attribute.read',
|
||||
'ProductAttribute.jsonld-admin.product_attribute.create',
|
||||
];
|
||||
|
||||
foreach ($schemasToBeUpdated as $schemaToBeUpdated) {
|
||||
$schemas[$schemaToBeUpdated]['properties']['type'] = [
|
||||
'type' => 'string',
|
||||
'enum' => $attributeTypes,
|
||||
];
|
||||
}
|
||||
|
||||
return $schemas;
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
private function getAttributeTypes(): array
|
||||
{
|
||||
$attributeTypes = [];
|
||||
|
||||
/** @var AttributeTypeInterface $attributeType */
|
||||
foreach ($this->attributeTypeRegistry->all() as $attributeType) {
|
||||
$attributeTypes[] = $attributeType->getType();
|
||||
}
|
||||
|
||||
return $attributeTypes;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,20 +16,184 @@
|
|||
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
|
||||
>
|
||||
<resource class="%sylius.model.product_attribute.class%" shortName="ProductAttribute">
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>shop:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
|
||||
<attribute name="validation_groups">sylius</attribute>
|
||||
|
||||
<collectionOperations />
|
||||
<attribute name="order">
|
||||
<attribute name="position">ASC</attribute>
|
||||
</attribute>
|
||||
|
||||
<collectionOperations>
|
||||
<collectionOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-attributes</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
|
||||
<collectionOperation name="admin_post">
|
||||
<attribute name="method">POST</attribute>
|
||||
<attribute name="path">/admin/product-attributes</attribute>
|
||||
<attribute name="denormalization_context">
|
||||
<attribute name="groups">admin:product_attribute:create</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
<attribute name="openapi_context">
|
||||
<attribute name="description">
|
||||
Example configuration for a `text` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "text",
|
||||
"configuration": {
|
||||
"min": 5,
|
||||
"max": 255
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example configuration for a `datetime` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "datetime",
|
||||
"configuration": {
|
||||
"format": "Y-m-d H:i:s"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example configuration for a `date` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "date",
|
||||
"configuration": {
|
||||
"format": "Y-m-d"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example configuration for a `select` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "select",
|
||||
"configuration": {
|
||||
"choices": {
|
||||
"0afb212e-cd08-11ec-871e-0242ac120005": {
|
||||
"en_US": "Plastic",
|
||||
"fr_FR": "Plastique"
|
||||
},
|
||||
"3bfb211f-cd08-11ec-871e-0242ac120005": {
|
||||
"en_US": "Cotton",
|
||||
"fr_FR": "Coton"
|
||||
}
|
||||
},
|
||||
"multiple": true,
|
||||
"min": 1,
|
||||
"max": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
</collectionOperations>
|
||||
|
||||
<itemOperations>
|
||||
<itemOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-attributes/{code}</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_delete">
|
||||
<attribute name="method">DELETE</attribute>
|
||||
<attribute name="path">/admin/product-attributes/{code}</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_put">
|
||||
<attribute name="method">PUT</attribute>
|
||||
<attribute name="path">/admin/product-attributes/{code}</attribute>
|
||||
<attribute name="denormalization_context">
|
||||
<attribute name="groups">admin:product_attribute:update</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
<attribute name="openapi_context">
|
||||
<attribute name="description">
|
||||
Example configuration for a `text` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "text",
|
||||
"configuration": {
|
||||
"min": 5,
|
||||
"max": 255
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example configuration for a `datetime` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "datetime",
|
||||
"configuration": {
|
||||
"format": "Y-m-d H:i:s"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example configuration for a `date` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "date",
|
||||
"configuration": {
|
||||
"format": "Y-m-d"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example configuration for a `select` type attribute:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "select",
|
||||
"configuration": {
|
||||
"choices": {
|
||||
"0afb212e-cd08-11ec-871e-0242ac120005": {
|
||||
"en_US": "Plastic",
|
||||
"fr_FR": "Plastique"
|
||||
},
|
||||
"3bfb211f-cd08-11ec-871e-0242ac120005": {
|
||||
"en_US": "Cotton",
|
||||
"fr_FR": "Coton"
|
||||
}
|
||||
},
|
||||
"multiple": true,
|
||||
"min": 1,
|
||||
"max": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="shop_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/shop/product-attributes/{code}</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">shop:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
</itemOperations>
|
||||
|
||||
|
|
@ -39,6 +203,18 @@
|
|||
<property name="type" />
|
||||
<property name="configuration" />
|
||||
<property name="storageType" />
|
||||
<property name="translatable" />
|
||||
<property name="position" />
|
||||
<property name="translations" readable="true" writable="true">
|
||||
<attribute name="openapi_context">
|
||||
<attribute name="type">object</attribute>
|
||||
<attribute name="example">
|
||||
<attribute name="en_US">
|
||||
<attribute name="locale">string</attribute>
|
||||
<attribute name="name">string</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</property>
|
||||
</resource>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
<resources xmlns="https://api-platform.com/schema/metadata"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
|
||||
>
|
||||
<resource class="%sylius.model.product_attribute_translation.class%" shortName="ProductAttributeTranslation">
|
||||
<attribute name="validation_groups">sylius</attribute>
|
||||
|
||||
<collectionOperations />
|
||||
|
||||
<itemOperations>
|
||||
<itemOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-attribute-translations/{id}</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_attribute:read</attribute>
|
||||
<attribute>admin:product_attribute_translation:read</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
</itemOperations>
|
||||
|
||||
<property name="id" identifier="true" writable="false" />
|
||||
<property name="name" required="true" />
|
||||
<property name="locale" required="true" />
|
||||
</resource>
|
||||
</resources>
|
||||
|
|
@ -52,6 +52,7 @@ api_platform:
|
|||
Sylius\Bundle\ApiBundle\Exception\CannotRemoveCurrentlyLoggedInUser: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\OrderItemNotFoundException: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\OrderNoLongerEligibleForPromotion: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProductCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProvinceCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ShippingMethodCannotBeRemoved: 422
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@
|
|||
<tag name="sylius.open_api.modifier" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\OpenApi\Documentation\AttributeTypeDocumentationModifier">
|
||||
<argument type="service" id="sylius.registry.attribute_type" />
|
||||
<tag name="sylius.open_api.modifier" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\OpenApi\Documentation\ProductDocumentationModifier">
|
||||
<tag name="sylius.open_api.modifier" />
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -24,12 +24,36 @@
|
|||
</attribute>
|
||||
<attribute name="code">
|
||||
<group>shop:product_attribute:read</group>
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
</attribute>
|
||||
<attribute name="type">
|
||||
<group>shop:product_attribute:read</group>
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
</attribute>
|
||||
<attribute name="storageType">
|
||||
<group>admin:product_attribute:read</group>
|
||||
</attribute>
|
||||
<attribute name="configuration">
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
<group>admin:product_attribute:update</group>
|
||||
</attribute>
|
||||
<attribute name="translatable">
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
<group>admin:product_attribute:update</group>
|
||||
</attribute>
|
||||
<attribute name="position">
|
||||
<group>shop:product_attribute:read</group>
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:update</group>
|
||||
</attribute>
|
||||
<attribute name="translations">
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
<group>admin:product_attribute:update</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
|
||||
>
|
||||
<class name="Sylius\Component\Product\Model\ProductAttributeTranslation">
|
||||
<attribute name="id">
|
||||
<group>shop:product_attribute:read</group>
|
||||
<group>admin:product_attribute:read</group>
|
||||
</attribute>
|
||||
<attribute name="locale">
|
||||
<group>shop:product_attribute:read</group>
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
<group>admin:product_attribute:update</group>
|
||||
</attribute>
|
||||
<attribute name="name">
|
||||
<group>shop:product_attribute:read</group>
|
||||
<group>admin:product_attribute:read</group>
|
||||
<group>admin:product_attribute:create</group>
|
||||
<group>admin:product_attribute:update</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
||||
|
|
@ -53,6 +53,11 @@
|
|||
<tag name="api_platform.data_persister" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\DataPersister\ProductAttributeDataPersister">
|
||||
<argument type="service" id="api_platform.doctrine.orm.data_persister" />
|
||||
<tag name="api_platform.data_persister" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\DataPersister\ProductTaxonDataPersister">
|
||||
<argument type="service" id="api_platform.doctrine.orm.data_persister" />
|
||||
<argument type="service" id="sylius.event_bus" />
|
||||
|
|
|
|||
|
|
@ -54,5 +54,10 @@
|
|||
<argument type="service" id="sylius.generator.taxon_slug" />
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\EventSubscriber\AttributeEventSubscriber">
|
||||
<argument type="service" id="sylius.registry.attribute_type" />
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -217,5 +217,12 @@
|
|||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.product_attribute_order_filter" parent="api_platform.doctrine.orm.order_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="position" />
|
||||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<?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\ApiBundle\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
|
||||
final class ProductAttributeDataPersisterSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ContextAwareDataPersisterInterface $persister): void
|
||||
{
|
||||
$this->beConstructedWith($persister);
|
||||
}
|
||||
|
||||
function it_is_a_context_aware_persister(): void
|
||||
{
|
||||
$this->shouldImplement(ContextAwareDataPersisterInterface::class);
|
||||
}
|
||||
|
||||
function it_supports_only_product_attribute(ProductAttributeInterface $productAttribute): void
|
||||
{
|
||||
$this->supports(new \stdClass())->shouldReturn(false);
|
||||
$this->supports($productAttribute)->shouldReturn(true);
|
||||
}
|
||||
|
||||
function it_uses_inner_persister_to_persist_product_attribute(
|
||||
ContextAwareDataPersisterInterface $persister,
|
||||
ProductAttributeInterface $productAttribute,
|
||||
): void {
|
||||
$persister->persist($productAttribute, [])->shouldBeCalled();
|
||||
|
||||
$this->persist($productAttribute, []);
|
||||
}
|
||||
|
||||
function it_throws_cannot_be_removed_exception_if_constraint_fails_on_removal(
|
||||
ContextAwareDataPersisterInterface $persister,
|
||||
ProductAttributeInterface $productAttribute,
|
||||
): void {
|
||||
$persister
|
||||
->remove($productAttribute, [])
|
||||
->willThrow(ForeignKeyConstraintViolationException::class)
|
||||
;
|
||||
|
||||
$this
|
||||
->shouldThrow(ProductAttributeCannotBeRemoved::class)
|
||||
->during('remove', [$productAttribute])
|
||||
;
|
||||
}
|
||||
|
||||
function it_uses_inner_persister_to_remove_product_attribute(
|
||||
ContextAwareDataPersisterInterface $persister,
|
||||
ProductAttributeInterface $productAttribute,
|
||||
): void {
|
||||
$persister->remove($productAttribute, [])->shouldBeCalled();
|
||||
|
||||
$this->remove($productAttribute, []);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<?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\ApiBundle\EventSubscriber;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface;
|
||||
use Sylius\Component\Attribute\Model\AttributeInterface;
|
||||
use Sylius\Component\Registry\ServiceRegistryInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\ViewEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
final class AttributeEventSubscriberSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ServiceRegistryInterface $registry): void
|
||||
{
|
||||
$this->beConstructedWith($registry);
|
||||
}
|
||||
|
||||
function it_implements_event_subscriber_interface(): void
|
||||
{
|
||||
$this->shouldImplement(EventSubscriberInterface::class);
|
||||
}
|
||||
|
||||
function it_does_nothing_when_controller_result_is_not_an_attribute(
|
||||
ServiceRegistryInterface $registry,
|
||||
HttpKernelInterface $kernel,
|
||||
Request $request,
|
||||
): void {
|
||||
$request->getMethod()->shouldBeCalled();
|
||||
$registry->has(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->assignStorageType(new ViewEvent(
|
||||
$kernel->getWrappedObject(),
|
||||
$request->getWrappedObject(),
|
||||
HttpKernelInterface::MAIN_REQUEST,
|
||||
new \stdClass(),
|
||||
));
|
||||
}
|
||||
|
||||
function it_does_nothing_when_attribute_has_no_type(
|
||||
ServiceRegistryInterface $registry,
|
||||
HttpKernelInterface $kernel,
|
||||
Request $request,
|
||||
AttributeInterface $attribute,
|
||||
): void {
|
||||
$request->getMethod()->willReturn(Request::METHOD_POST);
|
||||
$attribute->getType()->willReturn(null);
|
||||
|
||||
$registry->has(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->assignStorageType(new ViewEvent(
|
||||
$kernel->getWrappedObject(),
|
||||
$request->getWrappedObject(),
|
||||
HttpKernelInterface::MAIN_REQUEST,
|
||||
$attribute->getWrappedObject(),
|
||||
));
|
||||
}
|
||||
|
||||
function it_does_nothing_when_attribute_has_a_storage_type(
|
||||
ServiceRegistryInterface $registry,
|
||||
HttpKernelInterface $kernel,
|
||||
Request $request,
|
||||
AttributeInterface $attribute,
|
||||
): void {
|
||||
$request->getMethod()->willReturn(Request::METHOD_POST);
|
||||
$attribute->getType()->willReturn('text');
|
||||
$attribute->getStorageType()->willReturn('text');
|
||||
|
||||
$registry->has(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->assignStorageType(new ViewEvent(
|
||||
$kernel->getWrappedObject(),
|
||||
$request->getWrappedObject(),
|
||||
HttpKernelInterface::MAIN_REQUEST,
|
||||
$attribute->getWrappedObject(),
|
||||
));
|
||||
}
|
||||
|
||||
function it_does_nothing_when_attribute_type_is_not_registered(
|
||||
ServiceRegistryInterface $registry,
|
||||
HttpKernelInterface $kernel,
|
||||
Request $request,
|
||||
AttributeInterface $attribute,
|
||||
): void {
|
||||
$request->getMethod()->willReturn(Request::METHOD_POST);
|
||||
$attribute->getType()->willReturn('foo');
|
||||
$attribute->getStorageType()->willReturn(null);
|
||||
|
||||
$registry->has('foo')->willReturn(false);
|
||||
|
||||
$this->assignStorageType(new ViewEvent(
|
||||
$kernel->getWrappedObject(),
|
||||
$request->getWrappedObject(),
|
||||
HttpKernelInterface::MAIN_REQUEST,
|
||||
$attribute->getWrappedObject(),
|
||||
));
|
||||
}
|
||||
|
||||
function it_sets_storage_type_based_on_set_attribute_type(
|
||||
ServiceRegistryInterface $registry,
|
||||
HttpKernelInterface $kernel,
|
||||
Request $request,
|
||||
AttributeInterface $attribute,
|
||||
AttributeTypeInterface $attributeType,
|
||||
): void {
|
||||
$request->getMethod()->willReturn(Request::METHOD_POST);
|
||||
$attribute->getType()->willReturn('foo');
|
||||
$attribute->getStorageType()->willReturn(null);
|
||||
|
||||
$registry->has('foo')->willReturn(true);
|
||||
$registry->get('foo')->willReturn($attributeType);
|
||||
|
||||
$attributeType->getStorageType()->willReturn('bar');
|
||||
|
||||
$attribute->setStorageType('bar')->shouldBeCalled();
|
||||
|
||||
$this->assignStorageType(new ViewEvent(
|
||||
$kernel->getWrappedObject(),
|
||||
$request->getWrappedObject(),
|
||||
HttpKernelInterface::MAIN_REQUEST,
|
||||
$attribute->getWrappedObject(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,11 @@
|
|||
<tag name="form.type" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.validator.attribute_type" class="Sylius\Bundle\AttributeBundle\Validator\Constraints\AttributeTypeValidator">
|
||||
<argument type="service" id="sylius.registry.attribute_type" />
|
||||
<tag name="validator.constraint_validator" alias="sylius_attribute_type_validator" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.validator.valid_attribute_value" class="Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidAttributeValueValidator">
|
||||
<argument type="service" id="sylius.registry.attribute_type" />
|
||||
<tag name="validator.constraint_validator" alias="sylius_valid_attribute_value_validator" />
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@
|
|||
<constraint name="Sylius\Bundle\AttributeBundle\Validator\Constraints\ValidSelectAttributeConfiguration">
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\AttributeBundle\Validator\Constraints\AttributeType">
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<property name="code">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.attribute.code.not_blank</option>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ sylius:
|
|||
not_blank: Please enter attribute presentation.
|
||||
min_length: Attribute presentation must be at least 1 character long.|Attribute presentation must be at least {{ limit }} characters long.
|
||||
max_length: Attribute presentation must not be longer than 1 character.|Attribute presentation must not be longer than {{ limit }} characters.
|
||||
type:
|
||||
unregistered: The attribute type "%type%" is not registered.
|
||||
attribute_value:
|
||||
attribute:
|
||||
not_blank: Please select attribute.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AttributeBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class AttributeType extends Constraint
|
||||
{
|
||||
public string $unregisteredAttributeTypeMessage = 'sylius.attribute.type.unregistered';
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_attribute_type_validator';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?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\Validator\Constraints;
|
||||
|
||||
use Sylius\Component\Attribute\Model\AttributeInterface;
|
||||
use Sylius\Component\Registry\ServiceRegistryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class AttributeTypeValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(private ServiceRegistryInterface $attributeTypeRegistry)
|
||||
{
|
||||
}
|
||||
|
||||
public function validate(mixed $value, Constraint $constraint):void
|
||||
{
|
||||
/** @var mixed|AttributeInterface $value */
|
||||
Assert::isInstanceOf($value, AttributeInterface::class);
|
||||
/** @var Constraint|AttributeType $constraint */
|
||||
Assert::isInstanceOf($constraint, AttributeType::class);
|
||||
|
||||
$type = $value->getType();
|
||||
if (null === $type || $this->attributeTypeRegistry->has($type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->context
|
||||
->buildViolation($constraint->unregisteredAttributeTypeMessage, ['%type%' => $type])
|
||||
->atPath('type')
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ final class ValidSelectAttributeConfigurationValidator extends ConstraintValidat
|
|||
return;
|
||||
}
|
||||
|
||||
$multiple = $value->getConfiguration()['multiple'];
|
||||
$multiple = $value->getConfiguration()['multiple'] ?? false;
|
||||
if (!$multiple) {
|
||||
$this->context->addViolation($constraint->messageMultiple);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
<?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\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\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);
|
||||
|
||||
$context
|
||||
->buildViolation($constraint->unregisteredAttributeTypeMessage, ['%type%' => 'foo'])
|
||||
->shouldBeCalled()
|
||||
->willReturn($violationBuilder)
|
||||
;
|
||||
$violationBuilder->atPath('type')->shouldBeCalled()->willReturn($violationBuilder);
|
||||
$violationBuilder->addViolation()->shouldBeCalled();
|
||||
|
||||
$this->validate($attribute, $constraint);
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,20 @@ final class ValidSelectAttributeConfigurationValidatorSpec extends ObjectBehavio
|
|||
$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,
|
||||
|
|
|
|||
689
tests/Api/Admin/ProductAttributesTest.php
Normal file
689
tests/Api/Admin/ProductAttributesTest.php
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
<?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\Tests\Api\Admin;
|
||||
|
||||
use Sylius\Component\Attribute\AttributeType\CheckboxAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\DateAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\DatetimeAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\FloatAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\IntegerAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\PercentAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\TextareaAttributeType;
|
||||
use Sylius\Component\Attribute\AttributeType\TextAttributeType;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
use Sylius\Tests\Api\JsonApiTestCase;
|
||||
use Sylius\Tests\Api\Utils\AdminUserLoginTrait;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ProductAttributesTest extends JsonApiTestCase
|
||||
{
|
||||
use AdminUserLoginTrait;
|
||||
|
||||
/** @test */
|
||||
public function it_gets_product_attributes(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'product/product_attribute.yaml']);
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(method: 'GET', uri: '/api/v2/admin/product-attributes', server: $header);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/get_product_attributes_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_a_product_attribute(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/api_administrator.yaml',
|
||||
'product/product_attribute.yaml',
|
||||
]);
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
/** @var ProductAttributeInterface $productAttribute */
|
||||
$productAttribute = $fixtures['product_attribute_text'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: '/api/v2/admin/product-attributes/' . $productAttribute->getCode(),
|
||||
server: $header,
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/get_product_attribute_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_deletes_a_product_attribute(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/api_administrator.yaml',
|
||||
'product/product_attribute.yaml',
|
||||
]);
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
/** @var ProductAttributeInterface $productAttribute */
|
||||
$productAttribute = $fixtures['product_attribute_text_delete'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'DELETE',
|
||||
uri: sprintf('/api/v2/admin/product-attributes/%s', $productAttribute->getCode()),
|
||||
server: $header,
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_text_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'configuration' => [
|
||||
'min' => 5,
|
||||
'max' => 255,
|
||||
],
|
||||
'type' => TextAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_text_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_textarea_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'type' => TextareaAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_textarea_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_checkbox_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'new',
|
||||
'type' => CheckboxAttributeType::TYPE,
|
||||
'translatable' => false,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'New',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_checkbox_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_an_integer_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'pages',
|
||||
'type' => IntegerAttributeType::TYPE,
|
||||
'translatable' => false,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Pages',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_integer_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_float_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'display_size',
|
||||
'type' => FloatAttributeType::TYPE,
|
||||
'translatable' => false,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Display size',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_float_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_percent_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'damage_reduction',
|
||||
'type' => PercentAttributeType::TYPE,
|
||||
'translatable' => false,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Damage reduction',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_percent_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_datetime_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'published_at',
|
||||
'configuration' => [
|
||||
'format' => 'Y-m-d H:i:s',
|
||||
],
|
||||
'type' => DatetimeAttributeType::TYPE,
|
||||
'translatable' => false,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Published at',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_datetime_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_date_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'published_at',
|
||||
'configuration' => [
|
||||
'format' => 'Y-m-d',
|
||||
],
|
||||
'type' => DateAttributeType::TYPE,
|
||||
'translatable' => false,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Published at',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_date_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_a_select_product_attribute(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'configuration' => [
|
||||
'choices' => [
|
||||
'0afb212e-cd08-11ec-871e-0242ac120005' => [
|
||||
'en_US' => 'Cotton',
|
||||
'pl_PL' => 'Bawelna',
|
||||
],
|
||||
'0afb4e88-cd08-11ec-bcd4-0242ac120005' => [
|
||||
'en_US' => 'Wool',
|
||||
],
|
||||
],
|
||||
'multiple' => true,
|
||||
'min' => 1,
|
||||
'max' => 3,
|
||||
],
|
||||
'type' => SelectAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_select_product_attribute_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_a_product_attribute_without_required_data(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_product_attribute_without_required_data_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_a_product_attribute_with_unregistered_type(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'test',
|
||||
'type' => 'foobar',
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_product_attribute_with_unregistered_type_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_a_text_product_attribute_with_invalid_configuration(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'configuration' => [
|
||||
'min' => 255,
|
||||
'max' => 250,
|
||||
],
|
||||
'type' => TextAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_text_product_attribute_with_invalid_configuration_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_a_select_product_attribute_with_disabled_multiple_option(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'configuration' => [
|
||||
'choices' => [
|
||||
'0afb212e-cd08-11ec-871e-0242ac120005' => [
|
||||
'en_US' => 'Cotton',
|
||||
'pl_PL' => 'Bawelna',
|
||||
],
|
||||
'0afb4e88-cd08-11ec-bcd4-0242ac120005' => [
|
||||
'en_US' => 'Wool',
|
||||
],
|
||||
],
|
||||
'multiple' => false,
|
||||
'min' => 1,
|
||||
'max' => 3,
|
||||
],
|
||||
'type' => SelectAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_select_product_attribute_with_disabled_multiple_option_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_a_select_product_attribute_with_invalid_max_entries_configuration(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'configuration' => [
|
||||
'choices' => [
|
||||
'0afb212e-cd08-11ec-871e-0242ac120005' => [
|
||||
'en_US' => 'Cotton',
|
||||
'pl_PL' => 'Bawelna',
|
||||
],
|
||||
'0afb4e88-cd08-11ec-bcd4-0242ac120005' => [
|
||||
'en_US' => 'Wool',
|
||||
],
|
||||
],
|
||||
'multiple' => true,
|
||||
'min' => 3,
|
||||
'max' => 1,
|
||||
],
|
||||
'type' => SelectAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_select_product_attribute_with_invalid_max_entries_configuration_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_a_select_product_attribute_with_invalid_min_entries_configuration(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('authentication/api_administrator.yaml');
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-attributes',
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'material',
|
||||
'configuration' => [
|
||||
'choices' => [
|
||||
'0afb212e-cd08-11ec-871e-0242ac120005' => [
|
||||
'en_US' => 'Cotton',
|
||||
'pl_PL' => 'Bawelna',
|
||||
],
|
||||
'0afb4e88-cd08-11ec-bcd4-0242ac120005' => [
|
||||
'en_US' => 'Wool',
|
||||
],
|
||||
],
|
||||
'multiple' => true,
|
||||
'min' => 3,
|
||||
],
|
||||
'type' => SelectAttributeType::TYPE,
|
||||
'translatable' => true,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Material',
|
||||
],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/post_select_product_attribute_with_invalid_min_entries_configuration_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_updates_a_product_attribute(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/api_administrator.yaml',
|
||||
'product/product_attribute.yaml',
|
||||
]);
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
/** @var ProductAttributeInterface $productAttribute */
|
||||
$productAttribute = $fixtures['product_attribute_select'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: '/api/v2/admin/product-attributes/' . $productAttribute->getCode(),
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'configuration' => [
|
||||
'choices' => [
|
||||
'0afb212e-cd08-11ec-871e-0242ac120005' => [
|
||||
'en_US' => 'handmade',
|
||||
'fr_FR' => 'fait la main',
|
||||
],
|
||||
'0afb4e88-cd08-11ec-bcd4-0242ac120005' => [
|
||||
'fr_FR' => 'coffret cadeau',
|
||||
'en_US' => 'gift wrapping',
|
||||
'pl_PL' => 'pakowanie na prezent',
|
||||
],
|
||||
'0afb4e44-cd08-11ec-ad3f-0242ac120005' => [
|
||||
'en_US' => 'eco approved',
|
||||
],
|
||||
],
|
||||
'min' => 1,
|
||||
'max' => 3,
|
||||
'multiple' => true,
|
||||
],
|
||||
'translatable' => true,
|
||||
'position' => 0,
|
||||
'translations' => [
|
||||
'en_US' => [
|
||||
'@id' => sprintf(
|
||||
'/api/v2/admin/product-attribute-translations/%s',
|
||||
$productAttribute->getTranslation('en_US')->getId(),
|
||||
),
|
||||
'locale' => 'en_US',
|
||||
'name' => 'Additional information',
|
||||
],
|
||||
'pl_PL' => [
|
||||
'locale' => 'pl_PL',
|
||||
'name' => 'Dodatkowe informacje',
|
||||
],
|
||||
]
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/put_product_attribute_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_update_the_code_and_type_of_existing_product_attribute(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/api_administrator.yaml',
|
||||
'product/product_attribute.yaml',
|
||||
]);
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
/** @var ProductAttributeInterface $productAttribute */
|
||||
$productAttribute = $fixtures['product_attribute_text'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: '/api/v2/admin/product-attributes/' . $productAttribute->getCode(),
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'code' => 'NEW_CODE',
|
||||
'type' => TextAreaAttributeType::TYPE,
|
||||
], JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_attribute/put_product_attribute_with_code_and_type_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,15 @@ Sylius\Component\Product\Model\ProductAttribute:
|
|||
storage_type: 'text'
|
||||
configuration: []
|
||||
translatable: true
|
||||
product_attribute_text_delete:
|
||||
fallbackLocale: en_US
|
||||
currentLocale: en_US
|
||||
code: 'text_delete'
|
||||
name: 'text delete'
|
||||
type: 'text'
|
||||
storage_type: 'text'
|
||||
configuration: []
|
||||
translatable: false
|
||||
|
||||
Sylius\Component\Product\Model\ProductAttributeTranslation:
|
||||
product_attribute_translation_checkbox:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/origin",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "origin",
|
||||
"configuration": [],
|
||||
"position": 7,
|
||||
"storageType": "text",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "origin",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes",
|
||||
"@type": "hydra:Collection",
|
||||
"hydra:member": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/dishwasher_safe",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "dishwasher_safe",
|
||||
"configuration": [],
|
||||
"position": 0,
|
||||
"storageType": "boolean",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "dishwasher safe",
|
||||
"locale": "en_US"
|
||||
},
|
||||
"pl_PL": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "mozna myc w zmywarce",
|
||||
"locale": "pl_PL"
|
||||
}
|
||||
},
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/manufactured",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "manufactured",
|
||||
"configuration": [],
|
||||
"position": 1,
|
||||
"storageType": "date",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "manufactured",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/available_until",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "available_until",
|
||||
"configuration": [],
|
||||
"position": 2,
|
||||
"storageType": "datetime",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "available until",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/capacity",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "capacity",
|
||||
"configuration": [],
|
||||
"position": 3,
|
||||
"storageType": "integer",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "capacity",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/yixing_clay",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "yixing_clay",
|
||||
"configuration": [],
|
||||
"position": 4,
|
||||
"storageType": "float",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "yixing clay",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "percent"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/extra_info",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "extra_info",
|
||||
"configuration": {
|
||||
"choices": {
|
||||
"0afb212e-cd08-11ec-871e-0242ac120005": {
|
||||
"fr_FR": "fait a la main",
|
||||
"en_US": "handmade"
|
||||
},
|
||||
"0afb4e88-cd08-11ec-bcd4-0242ac120005": {
|
||||
"fr_FR": "coffret cadeau",
|
||||
"en_US": "gift box",
|
||||
"pl_PL": "opakowanie prezentowe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": 5,
|
||||
"storageType": "json",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "extra info",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/note",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "note",
|
||||
"configuration": [],
|
||||
"position": 6,
|
||||
"storageType": "text",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "note",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "textarea"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/origin",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "origin",
|
||||
"configuration": [],
|
||||
"position": 7,
|
||||
"storageType": "text",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "origin",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/text_delete",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "text_delete",
|
||||
"configuration": [],
|
||||
"position": 8,
|
||||
"storageType": "text",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "text delete",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 9
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/new",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "new",
|
||||
"configuration": {},
|
||||
"position": 0,
|
||||
"storageType": "boolean",
|
||||
"type": "checkbox",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "New"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/published_at",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "published_at",
|
||||
"configuration": {
|
||||
"format": "Y-m-d"
|
||||
},
|
||||
"position": 0,
|
||||
"storageType": "date",
|
||||
"type": "date",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Published at"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/published_at",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "published_at",
|
||||
"configuration": {
|
||||
"format": "Y-m-d H:i:s"
|
||||
},
|
||||
"position": 0,
|
||||
"storageType": "datetime",
|
||||
"type": "datetime",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Published at"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/display_size",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "display_size",
|
||||
"configuration": {},
|
||||
"position": 0,
|
||||
"storageType": "float",
|
||||
"type": "float",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Display size"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/pages",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "pages",
|
||||
"configuration": {},
|
||||
"position": 0,
|
||||
"storageType": "integer",
|
||||
"type": "integer",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Pages"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/damage_reduction",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "damage_reduction",
|
||||
"configuration": {},
|
||||
"position": 0,
|
||||
"storageType": "float",
|
||||
"type": "percent",
|
||||
"translatable": false,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Damage reduction"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "type: The attribute type \"foobar\" is not registered.",
|
||||
"violations":[
|
||||
{
|
||||
"propertyPath": "type",
|
||||
"message": "The attribute type \"foobar\" is not registered.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description":"code: Please enter attribute code.\ntranslations[en_US].name: Please enter attribute name.",
|
||||
"violations":[
|
||||
{
|
||||
"propertyPath":"code",
|
||||
"message":"Please enter attribute code.",
|
||||
"code": @string@
|
||||
},
|
||||
{
|
||||
"propertyPath":"translations[en_US].name",
|
||||
"message":"Please enter attribute name.",
|
||||
"code": @string@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/material",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "material",
|
||||
"configuration": {
|
||||
"choices": {
|
||||
"0afb212e-cd08-11ec-871e-0242ac120005": {
|
||||
"en_US": "Cotton",
|
||||
"pl_PL": "Bawelna"
|
||||
},
|
||||
"0afb4e88-cd08-11ec-bcd4-0242ac120005": {
|
||||
"en_US": "Wool"
|
||||
}
|
||||
},
|
||||
"multiple": true,
|
||||
"min": 1,
|
||||
"max": 3
|
||||
},
|
||||
"position": 0,
|
||||
"storageType": "json",
|
||||
"type": "select",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Material"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Configuration multiple must be true if min or max entries values are specified.",
|
||||
"violations":[
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Configuration multiple must be true if min or max entries values are specified.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Configuration max entries value must be greater or equal to the min entries value.",
|
||||
"violations":[
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Configuration max entries value must be greater or equal to the min entries value.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Configuration min entries value must be lower or equal to the number of added choices.",
|
||||
"violations":[
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Configuration min entries value must be lower or equal to the number of added choices.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/material",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "material",
|
||||
"configuration": {
|
||||
"min": 5,
|
||||
"max": 255
|
||||
},
|
||||
"position": 0,
|
||||
"storageType": "text",
|
||||
"type": "text",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Material"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Configuration max length must be greater or equal to the min length.",
|
||||
"violations":[
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Configuration max length must be greater or equal to the min length.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/material",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "material",
|
||||
"configuration": {},
|
||||
"position": 0,
|
||||
"storageType": "text",
|
||||
"type": "textarea",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Material"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes\/extra_info",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "extra_info",
|
||||
"configuration": {
|
||||
"choices": {
|
||||
"0afb212e-cd08-11ec-871e-0242ac120005": {
|
||||
"en_US": "handmade",
|
||||
"fr_FR": "fait la main"
|
||||
},
|
||||
"0afb4e88-cd08-11ec-bcd4-0242ac120005": {
|
||||
"en_US": "gift wrapping",
|
||||
"fr_FR": "coffret cadeau",
|
||||
"pl_PL": "pakowanie na prezent"
|
||||
},
|
||||
"0afb4e44-cd08-11ec-ad3f-0242ac120005": {
|
||||
"en_US": "eco approved"
|
||||
}
|
||||
},
|
||||
"min": 1,
|
||||
"max": 3,
|
||||
"multiple": true
|
||||
},
|
||||
"position": 0,
|
||||
"storageType": "json",
|
||||
"type": "select",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "en_US",
|
||||
"name": "Additional information"
|
||||
},
|
||||
"pl_PL": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"locale": "pl_PL",
|
||||
"name": "Dodatkowe informacje"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAttribute",
|
||||
"@id": "\/api\/v2\/admin\/product-attributes/origin",
|
||||
"@type": "ProductAttribute",
|
||||
"code": "origin",
|
||||
"configuration": [],
|
||||
"position": 7,
|
||||
"storageType": "text",
|
||||
"translatable": true,
|
||||
"translations": {
|
||||
"en_US": {
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-translations\/@integer@",
|
||||
"@type": "ProductAttributeTranslation",
|
||||
"id": "@integer@",
|
||||
"name": "origin",
|
||||
"locale": "en_US"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue