mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
minor #15639 [API][Admin] Finish covering product scenarios (NoResponseMate)
This PR was merged into the 1.13 branch. Discussion ---------- | Q | A | |-----------------|--------------------------------------------------------------| | Branch? | 1.13 | | Bug fix? | no | | New feature? | kinda | | BC breaks? | no | | Deprecations? | no | | Related tickets | - | | License | MIT | Commits ------- [API][Admin] Finish covering product filtering [API][Admin] Finish covering product and product taxon sorting [API][Admin] More product related scenarios marked no-api [API][Admin] Cover seeing applied promotions on variants [API][Admin] Refactor viewing product suite split [API] Finish covering viewing product attributes [API][Admin] Cover managing product associations Cleanup [API] Cover viewing details of a product [Behat] Update step names in scenarios after changing name of steps
This commit is contained in:
commit
249eb7476f
81 changed files with 1565 additions and 251 deletions
|
|
@ -110,7 +110,6 @@
|
|||
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter`
|
||||
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantCatalogPromotionFilter`
|
||||
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantOptionValueFilter`
|
||||
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantCatalogPromotionFilter`
|
||||
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\ProductVariantOptionValueFilter`
|
||||
* `Sylius\Bundle\ApiBundle\Filter\Doctrine\TaxonFilter`
|
||||
* `Sylius\Bundle\ApiBundle\Serializer\ChannelPriceHistoryConfigDenormalizer`
|
||||
|
|
@ -118,6 +117,10 @@
|
|||
* `Sylius\Bundle\ApiBundle\Serializer\ProductVariantNormalizer`
|
||||
* `Sylius\Bundle\ApiBundle\Serializer\ZoneDenormalizer`
|
||||
|
||||
1. The `Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter` service and class has been renamed to `Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter`.
|
||||
|
||||
1. The `sylius.api.product_taxon_filter` filter has been removed and its functionality has been superseded by the `sylius.api.search_filter.taxon.code` filter. The usage stays the same.
|
||||
|
||||
1. Update in Translations Handling
|
||||
|
||||
The process for creating or updating translations via the API has been refined. Now, the locale for each translation
|
||||
|
|
|
|||
|
|
@ -9,10 +9,16 @@ Feature: Adding associations to an existing product
|
|||
And the store has "LG G3", "LG headphones" and "LG earphones" products
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @no-api
|
||||
Scenario: Adding an association to an existing product
|
||||
When I want to modify the "LG G3" product
|
||||
And I associate as "Accessories" the "LG headphones" and "LG earphones" products
|
||||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And this product should have an association "Accessories" with products "LG headphones" and "LG earphones"
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Adding an association to na existing product
|
||||
When I associate as "Accessories" the product "LG G3" with the products "LG headphones" and "LG earphones"
|
||||
And this product should have an association "Accessories"
|
||||
And this association should have products "LG headphones" and "LG earphones"
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ Feature: Adding a new product with associations
|
|||
And this product should not have an association "Accessories" with product "LG earphones"
|
||||
And the product "LG G3" should appear in the store
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @no-api
|
||||
Scenario: Adding a new product with association with numeric code
|
||||
Given the store has 123 product association type
|
||||
When I want to create a new simple product
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Feature: Changing associations of an existing product
|
|||
And the store has "LG G3", "LG headphones" and "LG earphones" products
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @no-api
|
||||
Scenario: Changing associated products of a product association
|
||||
Given the product "LG G3" has an association "Accessories" with product "LG headphones"
|
||||
When I want to modify the "LG G3" product
|
||||
|
|
@ -18,7 +18,19 @@ Feature: Changing associations of an existing product
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product should have an association "Accessories" with products "LG headphones" and "LG earphones"
|
||||
|
||||
@ui @javascript
|
||||
@api @no-ui
|
||||
Scenario: Adding another product to a product association
|
||||
Given the product "LG G3" has an association "Accessories" with product "LG headphones"
|
||||
When I add the product "LG earphones" to this product association
|
||||
Then this association should have products "LG headphones" and "LG earphones"
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Changing a product of a product association
|
||||
Given the product "LG G3" has an association "Accessories" with product "LG headphones"
|
||||
When I change this product association's product to the "LG earphones" product
|
||||
Then this association should only have product "LG earphones"
|
||||
|
||||
@ui @javascript @no-api
|
||||
Scenario: Removing an associated product of a product association
|
||||
Given the product "LG G3" has an association "Accessories" with products "LG headphones" and "LG earphones"
|
||||
When I want to modify the "LG G3" product
|
||||
|
|
@ -27,3 +39,9 @@ Feature: Changing associations of an existing product
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product should have an association "Accessories" with product "LG headphones"
|
||||
And this product should not have an association "Accessories" with product "LG earphones"
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Removing a product of a product association
|
||||
Given the product "LG G3" has an association "Accessories" with products "LG headphones" and "LG earphones"
|
||||
When I remove the product "LG earphones" from this product association
|
||||
Then this association should only have product "LG headphones"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Feature: Filtering products by a channel
|
|||
And the store also has a product "HP Spectre" in channel "Web-US"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Filtering products by a chosen channel
|
||||
When I browse products
|
||||
And I choose "Web-EU" as a channel filter
|
||||
|
|
@ -22,7 +22,7 @@ Feature: Filtering products by a channel
|
|||
And I should see a product with name "MacBook Pro"
|
||||
But I should not see any product with name "HP Spectre"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Filtering products by a chosen channel
|
||||
When I browse products
|
||||
And I choose "Web-US" as a channel filter
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Feature: Sorting listed products
|
|||
Then I should see 3 products in the list
|
||||
And the first product on the list should have name "Szałowy Mops"
|
||||
|
||||
@ui @no-postgres
|
||||
@ui @api @no-postgres
|
||||
Scenario: Missing translations are sorted as first when sorting by name ascending
|
||||
When I change my locale to "Polish"
|
||||
And I browse products
|
||||
|
|
@ -74,7 +74,7 @@ Feature: Sorting listed products
|
|||
And the first product on the list shouldn't have a name
|
||||
And the last product on the list should have name "Ekstremalny Mops"
|
||||
|
||||
@ui @no-postgres
|
||||
@ui @api @no-postgres
|
||||
Scenario: Missing translation are sorted as last when sorting by name descending
|
||||
When I change my locale to "Polish"
|
||||
And I browse products
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ Feature: Sorting listed products from a taxon by position
|
|||
I want to sort products from a taxon by their positions
|
||||
|
||||
Background:
|
||||
Given the store classifies its products as "Soft Toys"
|
||||
Given the store operates on a single channel in "United States"
|
||||
And the store classifies its products as "Soft Toys"
|
||||
And the store has a product "Old pug" in the "Soft Toys" taxon at 0th position
|
||||
And the store has a product "Young pug" in the "Soft Toys" taxon at 1st position
|
||||
And the store has a product "Small pug" in the "Soft Toys" taxon at 2nd position
|
||||
|
|
@ -28,7 +29,7 @@ Feature: Sorting listed products from a taxon by position
|
|||
And the store has a product "Ultimate Pug" in the "Soft Toys" taxon at 19th position
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Setting two products to position -1 on the non-last page
|
||||
When I am browsing the 1st page of products from "Soft Toys" taxon
|
||||
And I set the position of "Old pug" to "-1"
|
||||
|
|
@ -38,7 +39,7 @@ Feature: Sorting listed products from a taxon by position
|
|||
Then the one before last product on the list should have name "Old pug" with position 18
|
||||
And the last product on the list should have name "Young pug" with position 19
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Setting two products to position -1 on the last page
|
||||
When I am browsing the 2nd page of products from "Soft Toys" taxon
|
||||
And I set the position of "Pug XL" to "-1"
|
||||
|
|
@ -47,7 +48,7 @@ Feature: Sorting listed products from a taxon by position
|
|||
Then the one before last product on the list should have name "Pug XL" with position 18
|
||||
And the last product on the list should have name "Pug XS" with position 19
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Setting two products to the already occupied position on the other page
|
||||
When I am browsing the 1st page of products from "Soft Toys" taxon
|
||||
And I set the position of "Old pug" to "15"
|
||||
|
|
@ -63,7 +64,7 @@ Feature: Sorting listed products from a taxon by position
|
|||
And the 7th product on this page should be named "Pug Master"
|
||||
And this product should be at position 16
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Setting two products to the already occupied position on the same page
|
||||
When I am browsing the 2nd page of products from "Soft Toys" taxon
|
||||
And I set the position of "Puglet" to "15"
|
||||
|
|
@ -79,7 +80,7 @@ Feature: Sorting listed products from a taxon by position
|
|||
And the 7th product on this page should be named "Pug Master"
|
||||
And this product should be at position 16
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Setting two products to the positions overflowing the max available position on the non-last page
|
||||
When I am browsing the 1st page of products from "Soft Toys" taxon
|
||||
And I set the position of "Old pug" to "25"
|
||||
|
|
@ -89,7 +90,7 @@ Feature: Sorting listed products from a taxon by position
|
|||
Then the one before last product on the list should have name "Old pug" with position 18
|
||||
And the last product on the list should have name "Young pug" with position 19
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Setting two products to the positions overflowing the max available position on the last page
|
||||
When I am browsing the 2nd page of products from "Soft Toys" taxon
|
||||
And I set the position of "Puglet" to "25"
|
||||
|
|
@ -99,33 +100,35 @@ Feature: Sorting listed products from a taxon by position
|
|||
Then the one before last product on the list should have name "Puglet" with position 18
|
||||
And the last product on the list should have name "Pug XL" with position 19
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: New product is added as last one
|
||||
Given I added a product "Big pug"
|
||||
And I assigned this product to "Soft Toys" taxon
|
||||
When I am browsing the 3rd page of products from "Soft Toys" taxon
|
||||
Then the last product on the list should have name "Big pug"
|
||||
Then the last product on the list within this taxon should have name "Big pug"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Product with position 0 is set as the first one
|
||||
When I am browsing products from "Soft Toys" taxon
|
||||
And I set the position of "Young pug" to 0
|
||||
And I save my new configuration
|
||||
Then the first product on the list should have name "Young pug"
|
||||
And I go to the 1st page of products from "Soft Toys" taxon
|
||||
Then the first product on the list within this taxon should have name "Young pug"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Being unable to use a non-numeric string as a product position
|
||||
Given I am browsing products from "Soft Toys" taxon
|
||||
When I set the position of "Young pug" to "test"
|
||||
And I save my new configuration
|
||||
Then I should be notified that the position "test" is invalid
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Sort products in descending order
|
||||
When I am browsing products from "Soft Toys" taxon
|
||||
And I start sorting products by position
|
||||
Then the first product on the list should have name "Ultimate Pug"
|
||||
@ui
|
||||
And I sort this taxon's products "descending" by "position"
|
||||
Then the first product on the list within this taxon should have name "Ultimate Pug"
|
||||
|
||||
@ui @api
|
||||
Scenario: Products are sorted by position in ascending order by default
|
||||
When I am browsing products from "Soft Toys" taxon
|
||||
Then the first product on the list should have name "Old pug"
|
||||
Then the first product on the list within this taxon should have name "Old pug"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ Feature: Accessing the price history from the configurable product show page
|
|||
@ui @no-api
|
||||
Scenario: Being able to access the price history of variant from the configurable product show page
|
||||
Given I am browsing products
|
||||
When I access "Wyborowa Vodka" product page
|
||||
When I access the "Wyborowa Vodka" product
|
||||
And I access the price history of a product variant "Wyborowa Vodka Exquisite" for "United States" channel
|
||||
Then I should see 1 log entries in the catalog price history
|
||||
|
|
|
|||
|
|
@ -17,6 +17,6 @@ Feature: Accessing the price history from the simple product show page
|
|||
@ui @no-api
|
||||
Scenario: Being able to access price history from simple product show page
|
||||
Given I am browsing products
|
||||
When I access "Ursus C-355" product page
|
||||
When I access the "Ursus C-355" product
|
||||
And I access the price history of a simple product for "United States" channel
|
||||
Then I should see 2 log entries in the catalog price history
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ Feature: Accessing to product edit page from show page
|
|||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Accessing to product edit page from product show page
|
||||
When I access "Iron shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
And I go to edit page
|
||||
Then I should be on "Iron shield" product edit page
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Accessing to variant edit page from product show page
|
||||
When I access "Iron shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
And I go to edit page of "Iron shield - very big" variant
|
||||
Then I should be on "Iron shield - very big" variant edit page
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Checking products in the shop while viewing them
|
||||
In order to check a product in shop in all channels it is available in
|
||||
As an Administrator
|
||||
|
|
@ -14,18 +14,18 @@ Feature: Checking products in the shop while viewing them
|
|||
@ui @no-api
|
||||
Scenario: Accessing product show page in shop from the product show page where product is available in more than one channel
|
||||
Given this product is available in the "Europe" channel
|
||||
When I access "Bugatti" product page
|
||||
When I access the "Bugatti" product
|
||||
And I show this product in the "Europe" channel
|
||||
Then I should see this product in the "Europe" channel in the shop
|
||||
|
||||
@ui @no-api
|
||||
Scenario: Accessing product show page in shop from the product show page where product is available in one channel
|
||||
When I access "Bugatti" product page
|
||||
When I access the "Bugatti" product
|
||||
And I show this product in this channel
|
||||
Then I should see this product in the "Europe" channel in the shop
|
||||
|
||||
@ui @no-api
|
||||
Scenario: Being unable to access product show page in shop when the product is disabled
|
||||
Given this product has been disabled
|
||||
When I access "Bugatti" product page
|
||||
When I access the "Bugatti" product
|
||||
Then I should not be able to show this product in shop
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Seeing applied catalog promotions details for a simple product
|
||||
In order to be aware of simple product price change reason
|
||||
As an Administrator
|
||||
|
|
@ -13,8 +13,9 @@ Feature: Seeing applied catalog promotions details for a simple product
|
|||
And it reduces price by "90%"
|
||||
And it is enabled
|
||||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing applied catalog promotion details on a simple product
|
||||
When I access "Ursus C-355" product
|
||||
Then this product price should be decreased by catalog promotion "Company bankruptcy sale" in "United States" channel
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Seeing applied catalog promotions details within variant
|
||||
In order to be aware of variant's price change reason
|
||||
As an Administrator
|
||||
|
|
@ -20,11 +20,20 @@ Feature: Seeing applied catalog promotions details within variant
|
|||
And it reduces price by "37%"
|
||||
And it is enabled
|
||||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Seeing applied catalog promotion details within variant
|
||||
When I access "Wyborowa Vodka" product
|
||||
Then "Wyborowa Vodka Exquisite" variant price should be decreased by catalog promotion "Winter sale" in "United States" channel
|
||||
And "Wyborowa Vodka Lemon" variant price should not be decreased by catalog promotion "Winter sale" in "United States" channel
|
||||
And "Wyborowa Vodka Exquisite" variant price should not be decreased by catalog promotion "Christmas sale" in "United States" channel
|
||||
And "Wyborowa Vodka Lemon" variant price should be decreased by catalog promotion "Christmas sale" in "United States" channel
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Seeing applied catalog promotion details within variant
|
||||
When I view all variants of the product "Wyborowa Vodka"
|
||||
Then "Wyborowa Vodka Exquisite" variant price should be decreased by catalog promotion "Winter sale" in "United States" channel
|
||||
And "Wyborowa Vodka Lemon" variant price should not be decreased by catalog promotion "Winter sale" in "United States" channel
|
||||
And "Wyborowa Vodka Exquisite" variant price should not be decreased by catalog promotion "Christmas sale" in "United States" channel
|
||||
And "Wyborowa Vodka Lemon" variant price should be decreased by catalog promotion "Christmas sale" in "United States" channel
|
||||
|
|
|
|||
|
|
@ -17,55 +17,69 @@ Feature: Viewing details of a product with variants
|
|||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
Scenario: Viewing a configurable product show page
|
||||
When I access "Iron Shield" product page
|
||||
@ui @no-api
|
||||
Scenario: Viewing a configurable product
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product show page with variants
|
||||
And I should see product name "Iron Shield"
|
||||
|
||||
@ui
|
||||
Scenario: Viewing taxonomy block
|
||||
@ui @api
|
||||
Scenario: Viewing taxonomies
|
||||
Given the store classifies its products as "Shield" and "Equipment"
|
||||
And the product "Iron Shield" has a main taxon "Equipment"
|
||||
And the product "Iron Shield" belongs to taxon "Shield"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see main taxon is "Equipment"
|
||||
And I should see product taxon is "Shield"
|
||||
And I should see product taxon "Shield"
|
||||
|
||||
@ui
|
||||
Scenario: Viewing options block
|
||||
When I access "Iron Shield" product page
|
||||
@ui @api
|
||||
Scenario: Viewing options
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see option "Shield size"
|
||||
|
||||
@ui
|
||||
Scenario: Viewing variants block
|
||||
When I access "Iron Shield" product page
|
||||
@ui @api
|
||||
Scenario: Viewing variants
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see 2 variants
|
||||
And I should see the "Iron Shield - very big" variant
|
||||
And I should see the "Iron Shield - very small" variant
|
||||
|
||||
@ui @no-api
|
||||
Scenario: Viewing variants' details
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see 2 variants
|
||||
And I should see "Iron Shield - very big" variant with code "123456789-xl", priced "$25.00" and current stock 5 and in "United States" channel
|
||||
And I should see "Iron Shield - very small" variant with code "123456789-xs", priced "$15.00" and current stock 12 and in "United States" channel
|
||||
|
||||
@ui @javascript @api
|
||||
Scenario: Viewing media block
|
||||
Scenario: Viewing media
|
||||
Given the "Iron Shield" product has an image "mugs.jpg" with "main" type
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see an image related to this product
|
||||
|
||||
@ui
|
||||
Scenario: Viewing "more details" block
|
||||
@ui @api
|
||||
Scenario: Viewing more details
|
||||
Given the product "Iron Shield" has the slug "iron-shield"
|
||||
And the description of product "Iron Shield" is "Shield created by dwarf"
|
||||
And the meta keywords of product "Iron Shield" is "shield"
|
||||
And the short description of product "Iron Shield" is "good shield"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product name is "Iron Shield"
|
||||
And I should see product slug is "iron-shield"
|
||||
And I should see product's description is "Shield created by dwarf"
|
||||
And I should see product's meta keywords is "shield"
|
||||
And I should see product's short description is "good shield"
|
||||
|
||||
@ui
|
||||
Scenario: Viewing associations block
|
||||
@ui @api
|
||||
Scenario: Viewing association types
|
||||
Given the store has a "Glass shield" product
|
||||
And the product "Iron Shield" has an association "Similar" with product "Glass shield"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product association type "Similar"
|
||||
|
||||
@ui @no-api
|
||||
Scenario: Viewing associations
|
||||
Given the store has a "Glass shield" product
|
||||
And the product "Iron Shield" has an association "Similar" with product "Glass shield"
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product association "Similar" with "Glass shield"
|
||||
|
|
|
|||
|
|
@ -11,54 +11,54 @@ Feature: Viewing details of a simple product
|
|||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing a simple product show page
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product show page without variants
|
||||
And I should see product name "Iron Shield"
|
||||
And I should see product breadcrumb "Iron Shield"
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing pricing block
|
||||
Given the product "Iron Shield" has original price "$25.00"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see price "$20.00" for channel "United States"
|
||||
And I should see original price "$25.00" for channel "United States"
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing price block without channel enable
|
||||
Given this product is unavailable in "United States" channel
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product name "Iron Shield"
|
||||
And I should see the product in neither channel
|
||||
And I should not see price for channel "United States"
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing details block
|
||||
Given the store has a tax category "No tax" with a code "nt"
|
||||
And product's "Iron Shield" code is "123456789"
|
||||
And there are 4 units of product "Iron Shield" available in the inventory
|
||||
And the product "Iron Shield" belongs to "No tax" tax category
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product's code is "123456789"
|
||||
And I should see the product is enabled for channel "United States"
|
||||
And I should see 4 as a current stock of this product
|
||||
And I should see product's tax category is "No tax"
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing taxonomy block
|
||||
Given this product belongs to "Shield"
|
||||
And the product "Iron Shield" has a main taxon "Equipment"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see main taxon is "Equipment"
|
||||
And I should see product taxon is "Shield"
|
||||
And I should see product taxon "Shield"
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing shipping block
|
||||
Given the store has "Over sized" and "Standard" shipping category
|
||||
And the product "Iron Shield" has height "10.0", width "15.0", depth "20.0", weight "25.0"
|
||||
And this product belongs to "Over sized" shipping category
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product's shipping category is "Over sized"
|
||||
And I should see product's height is 10
|
||||
And I should see product's width is 15
|
||||
|
|
@ -68,26 +68,26 @@ Feature: Viewing details of a simple product
|
|||
@ui @javascript @api
|
||||
Scenario: Viewing media block
|
||||
Given the "Iron Shield" product has an image "mugs.jpg" with "main" type
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see an image related to this product
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing "more details" block
|
||||
Given the product "Iron Shield" has the slug "iron-shield"
|
||||
And the description of product "Iron Shield" is "Shield created by dwarf"
|
||||
And the meta keywords of product "Iron Shield" is "shield"
|
||||
And the short description of product "Iron Shield" is "good shield"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product name is "Iron Shield"
|
||||
And I should see product slug is "iron-shield"
|
||||
And I should see product's description is "Shield created by dwarf"
|
||||
And I should see product's meta keywords is "shield"
|
||||
And I should see product's short description is "good shield"
|
||||
|
||||
@ui
|
||||
@ui @no-api
|
||||
Scenario: Viewing associations block
|
||||
Given the store has "Similar" and "Dwarf equipment" product association types
|
||||
And the store has a "Glass Shield" product
|
||||
And the product "Iron Shield" has an association "Similar" with product "Glass Shield"
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see product association "Similar" with "Glass Shield"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Seeing the lowest price before the discount for a simple product
|
||||
In order to be aware of simple product prices
|
||||
As an Administrator
|
||||
|
|
@ -13,10 +13,10 @@ Feature: Seeing the lowest price before the discount for a simple product
|
|||
@ui @no-api
|
||||
Scenario: Seeing price block with lowest price before the discount
|
||||
Given this product's price changed to "$21.00" and original price changed to "$37.00"
|
||||
When I access "Bizon Z056" product page
|
||||
When I access the "Bizon Z056" product
|
||||
Then I should see "$42.00" as its lowest price before the discount in "United States" channel
|
||||
|
||||
@ui @no-api
|
||||
Scenario: Seeing price block without lowest price before the discount
|
||||
When I access "Bizon Z056" product page
|
||||
When I access the "Bizon Z056" product
|
||||
Then I should not see the lowest price before the discount in "United States" channel
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Seeing the lowest price before the discount within variant
|
||||
In order to be aware of variant's prices
|
||||
As an Administrator
|
||||
|
|
@ -15,6 +15,6 @@ Feature: Seeing the lowest price before the discount within variant
|
|||
|
||||
@ui @no-api
|
||||
Scenario: Seeing price block with lowest price before the discount within variant
|
||||
When I access "Wyborowa Vodka" product page
|
||||
When I access the "Wyborowa Vodka" product
|
||||
Then I should not see the lowest price before the discount for "Wyborowa Vodka Rye" variant in "United States" channel
|
||||
And I should see the lowest price before the discount of "$11.00" for "Wyborowa Vodka Potato" variant in "United States" channel
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Viewing product's non translatable attributes
|
||||
In order to see product's non translatable attribute
|
||||
As an Administrator
|
||||
|
|
@ -14,9 +14,9 @@ Feature: Viewing product's non translatable attributes
|
|||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Viewing product's non translatable attributes along with default ones
|
||||
When I access "Iron Pickaxe" product page
|
||||
Then I should see non-translatable attribute "crit chance" with value "10 %"
|
||||
When I access the "Iron Pickaxe" product
|
||||
Then I should see non-translatable attribute "crit chance" with value 10%
|
||||
And I should see attribute "Material" with value "Iron" in "English (United States)" locale
|
||||
And I should see attribute "Material" with value "Żelazo" in "Polish (Poland)" locale
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@viewing_products
|
||||
@viewing_product_in_admin_panel
|
||||
Feature: Viewing product's attributes in different locales
|
||||
In order to see product's specification in all locales
|
||||
As a Administrator
|
||||
|
|
@ -15,9 +15,9 @@ Feature: Viewing product's attributes in different locales
|
|||
And I am logged in as an administrator
|
||||
And I am browsing products
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Viewing product's attributes defined in different locales
|
||||
When I access "Iron Shield" product page
|
||||
When I access the "Iron Shield" product
|
||||
Then I should see attribute "material" with value "oak wood" in "English (United States)" locale
|
||||
And I should see attribute "shield details" with value "oak wood is a very good material." in "English (United States)" locale
|
||||
And I should see attribute "material" with value "drewno dębowe" in "Polish (Poland)" locale
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ Feature: Not seeing the lowest price for a product that has a taxon excluded on
|
|||
And the store also has a product "Cauliflower" priced at "$25.00"
|
||||
And it belongs to "Vegetables" and "Special offers"
|
||||
And this product's price changed to "$15.00" and original price changed to "$25.00"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@api @ui
|
||||
Scenario: Not seeing the lowest price for a product that has a taxon excluded on the channel
|
||||
|
|
|
|||
|
|
@ -17,24 +17,24 @@ Feature: Viewing product's select attributes
|
|||
Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list
|
||||
And I should also see the product attribute "T-Shirt material" with value "Cotton" on the list
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Viewing a detailed page with product's select attribute after changing a value
|
||||
Given this product has select attribute "T-Shirt material" with values "Banana skin" and "Cotton"
|
||||
When the administrator changes this product attribute's value "Cotton" to "Orange skin"
|
||||
When this product attribute's value changed from "Cotton" to "Orange skin"
|
||||
And I check this product's details
|
||||
Then I should see the product attribute "T-Shirt material" with value "Banana skin" on the list
|
||||
And I should also see the product attribute "T-Shirt material" with value "Orange skin" on the list
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api @no-postgres
|
||||
Scenario: Viewing a detailed page with product's select attribute after removing an only value
|
||||
Given this product has select attribute "T-Shirt material" with value "Cotton"
|
||||
When the administrator deletes the value "Cotton" from this product attribute
|
||||
When this product attribute's value "Cotton" has been removed
|
||||
And I check this product's details
|
||||
Then I should not see the product attribute "T-Shirt material"
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @api @no-postgres
|
||||
Scenario: Viewing a detailed page with product's select attribute after removing one of the value
|
||||
Given this product has select attribute "T-Shirt material" with values "Banana skin" and "Cotton"
|
||||
When the administrator deletes the value "Cotton" from this product attribute
|
||||
When this product attribute's value "Cotton" has been removed
|
||||
And I check this product's details
|
||||
Then I should see the product attribute "T-Shirt material" with value "Banana skin"
|
||||
|
|
|
|||
|
|
@ -1601,14 +1601,14 @@ parameters:
|
|||
path: src/Sylius/Bundle/ApiBundle/EventSubscriber/TaxonDeletionEventSubscriber.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\CatalogPromotionChannelFilter\\:\\:__construct\\(\\) has parameter \\$properties with no value type specified in iterable type array\\.$#"
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ChannelsAwareChannelFilter\\:\\:__construct\\(\\) has parameter \\$properties with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/CatalogPromotionChannelFilter.php
|
||||
path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/ChannelsAwareChannelFilter.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\CatalogPromotionChannelFilter\\:\\:getDescription\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ChannelsAwareChannelFilter\\:\\:getDescription\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/CatalogPromotionChannelFilter.php
|
||||
path: src/Sylius/Bundle/ApiBundle/Filter/Doctrine/ChannelsAwareChannelFilter.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Filter\\\\Doctrine\\\\ExchangeRateFilter\\:\\:filterProperty\\(\\) has no return type specified\\.$#"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ use Sylius\Behat\Client\ApiClientInterface;
|
|||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Component\Core\Model\CatalogPromotionInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Core\Model\ProductVariantInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class BrowsingCatalogPromotionProductVariantsContext implements Context
|
||||
|
|
@ -40,6 +43,17 @@ final class BrowsingCatalogPromotionProductVariantsContext implements Context
|
|||
$this->client->filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I want to view all variants of (this product)$/
|
||||
* @When /^I view(?:| all) variants of the (product "[^"]+")$/
|
||||
*/
|
||||
public function iWantToViewAllVariantsOfThisProduct(ProductInterface $product): void
|
||||
{
|
||||
$this->client->index(Resources::PRODUCT_VARIANTS);
|
||||
$this->client->addFilter('product', $this->iriConverter->getIriFromResource($product));
|
||||
$this->client->filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^there should be (\d+) product variants? on the list$/
|
||||
*/
|
||||
|
|
@ -66,4 +80,71 @@ final class BrowsingCatalogPromotionProductVariantsContext implements Context
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then :variant variant price should be decreased by catalog promotion :catalogPromotion in :channel channel
|
||||
*/
|
||||
public function variantPriceShouldBeDecreasedByCatalogPromotion(
|
||||
ProductVariantInterface $variant,
|
||||
CatalogPromotionInterface $catalogPromotion,
|
||||
ChannelInterface $channel,
|
||||
): void {
|
||||
Assert::true(
|
||||
$this->variantHasCatalogPromotionInChannel($variant, $catalogPromotion, $channel),
|
||||
sprintf(
|
||||
'Catalog promotion "%s" was not found in applied promotions of variant "%s" in channel "%s".',
|
||||
$catalogPromotion->getCode(),
|
||||
$variant->getCode(),
|
||||
$channel->getCode(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then :variant variant price should not be decreased by catalog promotion :catalogPromotion in :channel channel
|
||||
*/
|
||||
public function variantPriceShouldNotBeDecreasedByCatalogPromotion(
|
||||
ProductVariantInterface $variant,
|
||||
CatalogPromotionInterface $catalogPromotion,
|
||||
ChannelInterface $channel,
|
||||
): void {
|
||||
Assert::false(
|
||||
$this->variantHasCatalogPromotionInChannel($variant, $catalogPromotion, $channel),
|
||||
sprintf(
|
||||
'Catalog promotion "%s" was found in applied promotions of variant "%s" in channel "%s".',
|
||||
$catalogPromotion->getCode(),
|
||||
$variant->getCode(),
|
||||
$channel->getCode(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function variantHasCatalogPromotionInChannel(
|
||||
ProductVariantInterface $variant,
|
||||
CatalogPromotionInterface $catalogPromotion,
|
||||
ChannelInterface $channel
|
||||
): bool {
|
||||
$variantData = $this->getDataOfVariantWithCode($variant->getCode());
|
||||
|
||||
$promotions = $variantData['channelPricings'][$channel->getCode()]['appliedPromotions'] ?? [];
|
||||
foreach ($promotions as $promotion) {
|
||||
if ($promotion['code'] === $catalogPromotion->getCode()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getDataOfVariantWithCode(string $code): array
|
||||
{
|
||||
$variantsData = $this->responseChecker->getCollection($this->client->getLastResponse());
|
||||
foreach ($variantsData as $variantData) {
|
||||
if ($variantData['code'] === $code) {
|
||||
return $variantData;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Variant with code "%s" was not found.', $code));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Context\Api\Admin;
|
||||
|
||||
use ApiPlatform\Api\IriConverterInterface;
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Repository\ProductAssociationRepositoryInterface;
|
||||
use Sylius\Component\Product\Model\ProductAssociationInterface;
|
||||
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class ManagingProductAssociationsContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private IriConverterInterface $iriConverter,
|
||||
private SectionAwareIriConverterInterface $sectionAwareIriConverter,
|
||||
private ProductAssociationRepositoryInterface $associationRepository,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I (associate as "[^"]+") the (product "[^"]+") with the ("[^"]+" product)$/
|
||||
*/
|
||||
public function iAssociateAsTypeTheProductWithTheProduct(
|
||||
ProductAssociationTypeInterface $type,
|
||||
ProductInterface $owner,
|
||||
ProductInterface $product,
|
||||
): void {
|
||||
$this->iAssociateAsTypeTheProductWithTheProducts($type, $owner, [$product]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I (associate as "[^"]+") the (product "[^"]+") with the (products "[^"]+" and "[^"]+")$/
|
||||
*/
|
||||
public function iAssociateAsTypeTheProductWithTheProducts(
|
||||
ProductAssociationTypeInterface $type,
|
||||
ProductInterface $owner,
|
||||
array $products,
|
||||
): void {
|
||||
$associatedProductsData = [];
|
||||
/** @var ProductInterface $product */
|
||||
foreach ($products as $product) {
|
||||
$associatedProductsData[] = $this->iriConverter->getIriFromItem($product);
|
||||
}
|
||||
|
||||
$this->client->buildCreateRequest(Resources::PRODUCT_ASSOCIATIONS);
|
||||
$this->client->addRequestData('type', $this->iriConverter->getIriFromItem($type));
|
||||
$this->client->addRequestData('owner', $this->iriConverter->getIriFromItem($owner));
|
||||
$this->client->addRequestData('associatedProducts', $associatedProductsData);
|
||||
$this->client->create();
|
||||
|
||||
/** @var ProductAssociationInterface $association */
|
||||
$association = $this->associationRepository->findOneBy([
|
||||
'owner' => $owner,
|
||||
'type' => $type,
|
||||
]);
|
||||
|
||||
$this->sharedStorage->set('association', $association);
|
||||
$this->sharedStorage->set('product', $association->getOwner());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I add the (product "[^"]+") to (this product association)$/
|
||||
*/
|
||||
public function iAddTheProductToThisProductAssociation(
|
||||
ProductInterface $product,
|
||||
ProductAssociationInterface $association,
|
||||
): void {
|
||||
$this->client->buildUpdateRequest(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
|
||||
|
||||
$associatedProducts = [$this->iriConverter->getIriFromItem($product)];
|
||||
foreach ($association->getAssociatedProducts() as $associatedProduct) {
|
||||
$associatedProducts[] = $this->iriConverter->getIriFromItem($associatedProduct);
|
||||
}
|
||||
|
||||
$this->client->setRequestData(['associatedProducts' => $associatedProducts]);
|
||||
$this->client->update();
|
||||
|
||||
$this->sharedStorage->set('association', $association);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I change (this product association)'s product to the ("[^"]+" product)$/
|
||||
*/
|
||||
public function iChangeThisProductAssociationProductToProduct(
|
||||
ProductAssociationInterface $association,
|
||||
ProductInterface $product,
|
||||
): void {
|
||||
$this->client->buildUpdateRequest(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
|
||||
$this->client->addRequestData('associatedProducts', [$this->iriConverter->getIriFromItem($product)]);
|
||||
$this->client->update();
|
||||
|
||||
$this->sharedStorage->set('association', $association);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I remove the (product "[^"]+") from (this product association)$/
|
||||
*/
|
||||
public function iRemoveTheProductFromThisProductAssociation(
|
||||
ProductInterface $product,
|
||||
ProductAssociationInterface $association,
|
||||
): void {
|
||||
$this->client->buildUpdateRequest(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
|
||||
|
||||
$associatedProducts = [];
|
||||
foreach ($association->getAssociatedProducts() as $associatedProduct) {
|
||||
if ($associatedProduct->getCode() !== $product->getCode()) {
|
||||
$associatedProducts[] = $this->iriConverter->getIriFromItem($associatedProduct);
|
||||
}
|
||||
}
|
||||
|
||||
$this->client->setRequestData(['associatedProducts' => $associatedProducts]);
|
||||
$this->client->update();
|
||||
|
||||
$this->sharedStorage->set('association', $association);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this product) should have an (association "[^"]+")$/
|
||||
*/
|
||||
public function thisProductShouldHaveAnAssociation(
|
||||
ProductInterface $product,
|
||||
ProductAssociationTypeInterface $type,
|
||||
): void {
|
||||
$response = $this->client->show(Resources::PRODUCTS, $product->getCode());
|
||||
$associations = $this->responseChecker->getValue($response, 'associations');
|
||||
|
||||
$associationTypeIri = $this->sectionAwareIriConverter->getIriFromResourceInSection($type, 'admin');
|
||||
|
||||
foreach ($associations as $associationIri) {
|
||||
$response = $this->client->showByIri($associationIri);
|
||||
$productAssociationType = $this->responseChecker->getValue($response, 'type');
|
||||
|
||||
if ($associationTypeIri === $productAssociationType) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Product %s does not have an association of type %s',
|
||||
$product->getCode(),
|
||||
$type->getName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this association) should only have (product "[^"]+")$/
|
||||
*/
|
||||
public function thisAssociationShouldOnlyHaveProduct(
|
||||
ProductAssociationInterface $association,
|
||||
ProductInterface $product,
|
||||
): void {
|
||||
$this->thisAssociationShouldHaveProducts($association, [$product]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this association) should have (products "[^"]+" and "[^"]+")$/
|
||||
*/
|
||||
public function thisAssociationShouldHaveProducts(
|
||||
ProductAssociationInterface $association,
|
||||
array $products,
|
||||
): void {
|
||||
$response = $this->client->show(Resources::PRODUCT_ASSOCIATIONS, (string) $association->getId());
|
||||
|
||||
$content = $this->responseChecker->getResponseContent($response);
|
||||
$associatedProducts = $content['associatedProducts'];
|
||||
|
||||
Assert::count($associatedProducts, count($products));
|
||||
|
||||
/** @var ProductInterface $product */
|
||||
foreach ($products as $product) {
|
||||
Assert::inArray(
|
||||
$this->sectionAwareIriConverter->getIriFromResourceInSection($product, 'admin'),
|
||||
$associatedProducts,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,9 @@ namespace Sylius\Behat\Context\Api\Admin;
|
|||
use ApiPlatform\Api\IriConverterInterface;
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Core\Model\ProductTaxonInterface;
|
||||
use Sylius\Component\Core\Model\TaxonInterface;
|
||||
|
|
@ -24,8 +26,47 @@ use Webmozart\Assert\Assert;
|
|||
|
||||
final class ManagingProductTaxonsContext implements Context
|
||||
{
|
||||
public function __construct(private ApiClientInterface $client, private IriConverterInterface $iriConverter)
|
||||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private IriConverterInterface $iriConverter,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I am browsing the (\d+)(?:st|nd|rd|th) page of products from ("([^"]+)" taxon)$/
|
||||
* @When /^I go to the (\d+)(?:st|nd|rd|th) page of products from ("([^"]+)" taxon)$/
|
||||
*/
|
||||
public function iAmBrowsingThePageOfProductsFromTaxon(int $page, TaxonInterface $taxon): void
|
||||
{
|
||||
$this->iAmBrowsingProductsFromTaxon($taxon);
|
||||
$this->client->addFilter('page', $page);
|
||||
$this->client->filter();
|
||||
|
||||
$this->sharedStorage->set('response', $this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I am browsing products from ("([^"]+)" taxon)$/
|
||||
*/
|
||||
public function iAmBrowsingProductsFromTaxon(TaxonInterface $taxon): void
|
||||
{
|
||||
$this->client->index(Resources::PRODUCT_TAXONS);
|
||||
$this->client->addFilter('taxon.code', $taxon->getCode());
|
||||
$this->client->addFilter('itemsPerPage', 10);
|
||||
$this->client->filter();
|
||||
|
||||
$this->sharedStorage->set('response', $this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the position of :product to :position
|
||||
*/
|
||||
public function iSetThePositionOfProductTo(ProductInterface $product, string|int $position): void
|
||||
{
|
||||
$this->client->buildUpdateRequest(Resources::PRODUCT_TAXONS, (string) $product->getProductTaxons()->current()->getId());
|
||||
$this->client->updateRequestData(['position' => is_numeric($position) ? (int) $position : $position]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,6 +125,38 @@ final class ManagingProductTaxonsContext implements Context
|
|||
$this->client->delete(Resources::PRODUCT_TAXONS, (string) $productTaxon->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I sort this taxon's products :order by :field
|
||||
*/
|
||||
public function iSortProductsBy(string $order, string $field): void
|
||||
{
|
||||
$this->client->sort([$field => ManagingProductsContext::SORT_TYPES[$order]]);
|
||||
$this->sharedStorage->set('response', $this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I save my new configuration
|
||||
*/
|
||||
public function iSaveMyNewConfiguration(): void
|
||||
{
|
||||
$this->client->update();
|
||||
$this->sharedStorage->set('response', $this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the (first|last) product on the list within this taxon should have name "([^"]+)"$/
|
||||
*/
|
||||
public function theLastProductOnTheListWithinThisTaxonShouldHaveName(string $position, string $name): void
|
||||
{
|
||||
$productTaxons = $this->responseChecker->getCollection($this->sharedStorage->get('response'));
|
||||
$productTaxon = $position === 'last' ? end($productTaxons) : reset($productTaxons);
|
||||
|
||||
/** @var ProductInterface $product */
|
||||
$product = $this->iriConverter->getResourceFromIri($productTaxon['product']);
|
||||
|
||||
Assert::same($product->getTranslation()->getName(), $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that specifying a :part is required
|
||||
*/
|
||||
|
|
@ -105,4 +178,15 @@ final class ManagingProductTaxonsContext implements Context
|
|||
'Product taxons cannot be duplicated.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the position :position is invalid
|
||||
*/
|
||||
public function iShouldBeNotifiedThatThePositionIsInvalid(): void
|
||||
{
|
||||
Assert::contains(
|
||||
(string) $this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'The type of the "position" attribute must be "int", "string" given.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,12 @@ use Sylius\Component\Attribute\Model\AttributeValueInterface;
|
|||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Core\Model\ProductTaxonInterface;
|
||||
use Sylius\Component\Core\Model\ProductVariantInterface;
|
||||
use Sylius\Component\Core\Model\TaxonInterface;
|
||||
use Sylius\Component\Locale\Model\LocaleInterface;
|
||||
use Sylius\Component\Product\Model\ProductAssociationInterface;
|
||||
use Sylius\Component\Product\Model\ProductAssociationTypeInterface;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
|
@ -396,13 +401,21 @@ final class ManagingProductsContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When I access :product product page
|
||||
* @When I access the :product product
|
||||
*/
|
||||
public function iAccessProductPage(ProductInterface $product): void
|
||||
public function iAccessTheProduct(ProductInterface $product): void
|
||||
{
|
||||
$this->client->show(Resources::PRODUCTS, $product->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I choose :channel as a channel filter
|
||||
*/
|
||||
public function iChooseChannelAsAChannelFilter(ChannelInterface $channel): void
|
||||
{
|
||||
$this->client->addFilter('channel', $this->iriConverter->getIriFromResource($channel));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I save my changes to the images
|
||||
*/
|
||||
|
|
@ -411,6 +424,125 @@ final class ManagingProductsContext implements Context
|
|||
// Intentionally left blank
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I filter
|
||||
*/
|
||||
public function iFilter(): void
|
||||
{
|
||||
$this->client->filter();
|
||||
|
||||
$this->sharedStorage->set('response', $this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see main taxon is :taxon
|
||||
*/
|
||||
public function iShouldSeeMainTaxonIs(TaxonInterface $taxon): void
|
||||
{
|
||||
Assert::same(
|
||||
$this->responseChecker->getValue($this->client->getLastResponse(), 'mainTaxon'),
|
||||
$this->sectionAwareIriConverter->getIriFromResourceInSection($taxon, 'admin'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product taxon :taxon
|
||||
*/
|
||||
public function iShouldSeeProductTaxon(TaxonInterface $taxon): void
|
||||
{
|
||||
$product = $this->sharedStorage->get('product');
|
||||
Assert::isInstanceOf($product, ProductInterface::class);
|
||||
$productTaxon = $product->getProductTaxons()->filter(
|
||||
fn(ProductTaxonInterface $productTaxon) => $productTaxon->getTaxon()->getCode() === $taxon->getCode()
|
||||
)->first();
|
||||
Assert::isInstanceOf($productTaxon, ProductTaxonInterface::class);
|
||||
|
||||
Assert::true($this->responseChecker->hasValueInCollection(
|
||||
$this->client->getLastResponse(),
|
||||
'productTaxons',
|
||||
$this->sectionAwareIriConverter->getIriFromResourceInSection($productTaxon, 'admin'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see option :productOption
|
||||
*/
|
||||
public function iShouldSeeOption(ProductOptionInterface $productOption): void
|
||||
{
|
||||
Assert::true($this->responseChecker->hasValueInCollection(
|
||||
$this->client->getLastResponse(),
|
||||
'options',
|
||||
$this->sectionAwareIriConverter->getIriFromResourceInSection($productOption, 'admin'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see :count variants
|
||||
*/
|
||||
public function iShouldSeeVariants(int $count): void
|
||||
{
|
||||
Assert::count(
|
||||
$this->responseChecker->getResponseContent($this->client->getLastResponse())['variants'] ?? [],
|
||||
$count,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see the :variant variant
|
||||
*/
|
||||
public function iShouldSeeTheVariant(ProductVariantInterface $variant): void
|
||||
{
|
||||
Assert::true($this->responseChecker->hasValueInCollection(
|
||||
$this->client->getLastResponse(),
|
||||
'variants',
|
||||
$this->sectionAwareIriConverter->getIriFromResourceInSection($variant, 'admin'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product :field is :value
|
||||
* @Then I should see product's :field is :value
|
||||
*/
|
||||
public function iShouldSeeProductFieldIs(string $field, string $value): void
|
||||
{
|
||||
$this->assertResponseHasTranslationFieldWithValue($field, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product's meta keyword(s) is/are :metaKeywords
|
||||
*/
|
||||
public function iShouldSeeProductMetaKeywordsAre(string $metaKeywords): void
|
||||
{
|
||||
$this->assertResponseHasTranslationFieldWithValue('metaKeywords', $metaKeywords);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product's short description is :shortDescription
|
||||
*/
|
||||
public function iShouldSeeProductShortDescriptionIs(string $shortDescription): void
|
||||
{
|
||||
$this->assertResponseHasTranslationFieldWithValue('shortDescription', $shortDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product association type :productAssociationType
|
||||
*/
|
||||
public function iShouldSeeProductAssociationType(ProductAssociationTypeInterface $productAssociationType): void
|
||||
{
|
||||
$associations = $this->responseChecker->getValue($this->client->getLastResponse(), 'associations');
|
||||
foreach ($associations as $associationIri) {
|
||||
/** @var ProductAssociationInterface $association */
|
||||
$association = $this->iriConverter->getResourceFromIri($associationIri);
|
||||
if ($association->getType()->getCode() === $productAssociationType->getCode()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Product association type "%s" not found.', $productAssociationType->getCode()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully created
|
||||
*/
|
||||
|
|
@ -486,9 +618,10 @@ final class ManagingProductsContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should see a single product in the list
|
||||
* @Then I should see :count products in the list
|
||||
*/
|
||||
public function iShouldSeeProductsInTheList(int $count): void
|
||||
public function iShouldSeeProductsInTheList(int $count = 1): void
|
||||
{
|
||||
Assert::count($this->responseChecker->getCollection($this->client->getLastResponse()), $count);
|
||||
}
|
||||
|
|
@ -612,11 +745,31 @@ final class ManagingProductsContext implements Context
|
|||
*/
|
||||
public function theFirstProductOnTheListShouldHave(string $field, string $value): void
|
||||
{
|
||||
$response = $this->getLastResponse();
|
||||
$products = $this->responseChecker->getCollection($this->getLastResponse());
|
||||
|
||||
$products = $this->responseChecker->getCollection($response);
|
||||
Assert::same($this->getFieldValueOfProduct($products[0], $field), $value);
|
||||
}
|
||||
|
||||
Assert::same($this->getFieldValueOfFirstProduct($products[0], $field), $value);
|
||||
/**
|
||||
* @Then the last product on the list should have name :name
|
||||
*/
|
||||
public function theLastProductOnTheListShouldHaveName(string $name): void
|
||||
{
|
||||
$products = $this->responseChecker->getCollection($this->getLastResponse());
|
||||
|
||||
Assert::same($this->getFieldValueOfProduct(end($products), 'name'), $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the (first|last) product on the list shouldn't have a name$/
|
||||
*/
|
||||
public function theProductOnTheListShouldNotHaveAName(string $position): void
|
||||
{
|
||||
$products = $this->responseChecker->getCollection($this->getLastResponse());
|
||||
|
||||
$product = $position === 'last' ? end($products) : reset($products);
|
||||
|
||||
Assert::null($this->getFieldValueOfProduct($product, 'name'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -820,6 +973,17 @@ final class ManagingProductsContext implements Context
|
|||
Assert::notEmpty($this->responseChecker->getValue($this->client->getLastResponse(), 'images'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see attribute :attribute with value :value in :locale locale
|
||||
*/
|
||||
public function iShouldSeeAttributeWithValueInLocale(
|
||||
ProductAttributeInterface $attribute,
|
||||
string $value,
|
||||
LocaleInterface $locale,
|
||||
): void {
|
||||
$this->hasAttributeWithValueInLastResponse($attribute, $value, $locale->getCode());
|
||||
}
|
||||
|
||||
private function getAdminLocaleCode(): string
|
||||
{
|
||||
/** @var AdminUserInterface $adminUser */
|
||||
|
|
@ -830,14 +994,14 @@ final class ManagingProductsContext implements Context
|
|||
return $this->responseChecker->getValue($response, 'localeCode');
|
||||
}
|
||||
|
||||
private function getFieldValueOfFirstProduct(array $product, string $field): ?string
|
||||
private function getFieldValueOfProduct(array $product, string $field): ?string
|
||||
{
|
||||
if ($field === 'code') {
|
||||
return $product['code'];
|
||||
}
|
||||
|
||||
if ($field === 'name') {
|
||||
return $product['translations'][$this->getAdminLocaleCode()]['name'];
|
||||
return $product['translations'][$this->getAdminLocaleCode()]['name'] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -934,4 +1098,12 @@ final class ManagingProductsContext implements Context
|
|||
|
||||
Assert::same((string) $value, $expectedValue);
|
||||
}
|
||||
|
||||
private function assertResponseHasTranslationFieldWithValue(string $field, string $value): void
|
||||
{
|
||||
Assert::same(
|
||||
$this->responseChecker->getTranslationValue($this->client->getLastResponse(), $field),
|
||||
$value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ final class Resources
|
|||
|
||||
public const PRODUCT_ASSOCIATION_TYPES = 'product-association-types';
|
||||
|
||||
public const PRODUCT_ASSOCIATIONS = 'product-associations';
|
||||
|
||||
public const PRODUCT_ATTRIBUTES = 'product-attributes';
|
||||
|
||||
public const PRODUCT_IMAGES = 'product-images';
|
||||
|
|
|
|||
|
|
@ -37,8 +37,15 @@ final class ProductAttributeContext implements Context
|
|||
public function iShouldSeeTheProductAttributeWithValue(string $attributeName, string $expectedAttribute): void
|
||||
{
|
||||
$attribute = $this->getAttributeByName($attributeName);
|
||||
$attributeValue = $attribute['value'];
|
||||
|
||||
Assert::same($attribute['value'], $expectedAttribute);
|
||||
if (is_array($attributeValue)) {
|
||||
Assert::inArray($expectedAttribute, $attributeValue);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Assert::same($attributeValue, $expectedAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,6 +78,14 @@ final class ProductAttributeContext implements Context
|
|||
Assert::inArray($expectedAttribute, $attribute['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not see the product attribute :attributeName
|
||||
*/
|
||||
public function iShouldNotSeeTheProductAttribute(string $attributeName): void
|
||||
{
|
||||
Assert::false($this->hasAttributeByName($attributeName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should (also) see the product attribute :attributeName with date :expectedAttribute
|
||||
*/
|
||||
|
|
@ -113,6 +128,17 @@ final class ProductAttributeContext implements Context
|
|||
Assert::same($attribute['name'], $name);
|
||||
}
|
||||
|
||||
private function hasAttributeByName(string $name): bool
|
||||
{
|
||||
foreach ($this->getAttributes() as $attribute) {
|
||||
if ($attribute['name'] === $name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getAttributeByName(string $name): array
|
||||
{
|
||||
foreach ($this->getAttributes() as $attribute) {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,8 @@ final class ProductAssociationContext implements Context
|
|||
$product->addAssociation($productAssociation);
|
||||
|
||||
$this->productAssociationRepository->add($productAssociation);
|
||||
|
||||
$this->sharedStorage->set('product_association', $productAssociation);
|
||||
}
|
||||
|
||||
private function addProductAssociationTypeTranslation(
|
||||
|
|
|
|||
|
|
@ -423,6 +423,59 @@ final class ProductAttributeContext implements Context
|
|||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(this product attribute)'s value changed from "([^"]+)" to "([^"]+)"$/
|
||||
*/
|
||||
public function thisAttributeValueChangedFromTo(
|
||||
ProductAttributeInterface $attribute,
|
||||
string $from,
|
||||
string $to,
|
||||
): void {
|
||||
$configuration = $attribute->getConfiguration();
|
||||
$choices = $configuration['choices'] ?? [];
|
||||
|
||||
foreach ($choices as $uuid => $choice) {
|
||||
foreach ($choice as $localeCode => $item) {
|
||||
if ($item === $from) {
|
||||
$choices[$uuid][$localeCode] = $to;
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$configuration['choices'] = $choices;
|
||||
$attribute->setConfiguration($configuration);
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(this product attribute)'s value "([^"]+)" has been removed$/
|
||||
*/
|
||||
public function thisAttributeValueHasBeenRemoved(
|
||||
ProductAttributeInterface $attribute,
|
||||
string $value,
|
||||
): void {
|
||||
$configuration = $attribute->getConfiguration();
|
||||
$choices = $configuration['choices'] ?? [];
|
||||
|
||||
foreach ($choices as $uuid => $choice) {
|
||||
foreach ($choice as $item) {
|
||||
if ($value === $item) {
|
||||
unset($choices[$uuid]);
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$configuration['choices'] = $choices;
|
||||
$attribute->setConfiguration($configuration);
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
private function createProductAttribute(
|
||||
string $type,
|
||||
string $name,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ final class ProductAssociationTypeContext implements Context
|
|||
|
||||
/**
|
||||
* @Transform /^association "([^"]+)"$/
|
||||
* @Transform /^associate as "([^"]+)"$/
|
||||
* @Transform :productAssociationType
|
||||
*/
|
||||
public function getProductAssociationTypeByName($productAssociationTypeName)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ use Sylius\Behat\Page\Admin\Crud\IndexPageInterface;
|
|||
use Sylius\Behat\Page\Admin\ProductAttribute\CreatePageInterface;
|
||||
use Sylius\Behat\Page\Admin\ProductAttribute\UpdatePageInterface;
|
||||
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
|
||||
use Sylius\Behat\Service\SharedSecurityServiceInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
@ -30,7 +28,6 @@ final class ManagingProductAttributesContext implements Context
|
|||
private IndexPageInterface $indexPage,
|
||||
private UpdatePageInterface $updatePage,
|
||||
private CurrentPageResolverInterface $currentPageResolver,
|
||||
private SharedSecurityServiceInterface $sharedSecurityService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -233,25 +230,6 @@ final class ManagingProductAttributesContext implements Context
|
|||
$this->indexPage->open();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(the administrator) changes (this product attribute)'s value "([^"]*)" to "([^"]*)"$/
|
||||
*/
|
||||
public function theAdministratorChangesThisProductAttributesValueTo(
|
||||
AdminUserInterface $user,
|
||||
ProductAttributeInterface $productAttribute,
|
||||
string $oldValue,
|
||||
string $newValue,
|
||||
): void {
|
||||
$this->sharedSecurityService->performActionAsAdminUser(
|
||||
$user,
|
||||
function () use ($productAttribute, $oldValue, $newValue) {
|
||||
$this->iWantToEditThisAttribute($productAttribute);
|
||||
$this->iChangeItsValueTo($oldValue, $newValue);
|
||||
$this->iSaveMyChanges();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify its min length as :min
|
||||
* @When I specify its min entries value as :min
|
||||
|
|
@ -286,24 +264,6 @@ final class ManagingProductAttributesContext implements Context
|
|||
// Intentionally left blank to fulfill context expectation
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(the administrator) deletes the value "([^"]+)" from (this product attribute)$/
|
||||
*/
|
||||
public function theAdministratorDeletesTheValueFromThisProductAttribute(
|
||||
AdminUserInterface $user,
|
||||
string $value,
|
||||
ProductAttributeInterface $productAttribute,
|
||||
): void {
|
||||
$this->sharedSecurityService->performActionAsAdminUser(
|
||||
$user,
|
||||
function () use ($productAttribute, $value) {
|
||||
$this->iWantToEditThisAttribute($productAttribute);
|
||||
$this->iDeleteValue($value);
|
||||
$this->iSaveMyChanges();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I check (also) the :productAttributeName product attribute
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ final class ManagingProductsContext implements Context
|
|||
|
||||
/**
|
||||
* @Then the first product on the list should have :field :value
|
||||
* @Then the first product on the list within this taxon should have :field :value
|
||||
*/
|
||||
public function theFirstProductOnTheListShouldHave($field, $value)
|
||||
{
|
||||
|
|
@ -366,6 +367,7 @@ final class ManagingProductsContext implements Context
|
|||
|
||||
/**
|
||||
* @Then the last product on the list should have :field :value
|
||||
* @Then the last product on the list within this taxon should have :field :value
|
||||
*/
|
||||
public function theLastProductOnTheListShouldHave($field, $value)
|
||||
{
|
||||
|
|
@ -400,6 +402,17 @@ final class ManagingProductsContext implements Context
|
|||
$this->indexPage->sortBy($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I sort this taxon's products :sortType by :field
|
||||
*/
|
||||
public function iSortThisTaxonsProductsBy(string $sortType, string $field): void
|
||||
{
|
||||
$this->indexPerTaxonPage->sortBy(
|
||||
$field,
|
||||
str_starts_with($sortType, 'de') ? 'desc' : 'asc',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see a single product in the list
|
||||
* @Then I should see :numberOfProducts products in the list
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ final class ProductShowPageContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When I access :product product page
|
||||
* @When I access the :product product
|
||||
*/
|
||||
public function iAccessProductPage(ProductInterface $product): void
|
||||
public function iAccessTheProduct(ProductInterface $product): void
|
||||
{
|
||||
$this->indexPage->showProductPage($product->getName());
|
||||
}
|
||||
|
|
@ -285,9 +285,9 @@ final class ProductShowPageContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product taxon is :taxonName
|
||||
* @Then I should see product taxon :taxonName
|
||||
*/
|
||||
public function iShouldSeeProductTaxonIs(string $taxonName): void
|
||||
public function iShouldSeeProductTaxon(string $taxonName): void
|
||||
{
|
||||
Assert::true($this->taxonomyElement->hasProductTaxon($taxonName));
|
||||
}
|
||||
|
|
@ -385,7 +385,15 @@ final class ProductShowPageContext implements Context
|
|||
*/
|
||||
public function iShouldSeeProductAssociationWith(string $association, string $productName): void
|
||||
{
|
||||
Assert::true($this->associationsElement->isProductAssociated($association, $productName));
|
||||
Assert::true($this->associationsElement->isAssociatedWith($association, $productName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see product association type :association
|
||||
*/
|
||||
public function iShouldSeeProductAssociationType(string $association): void
|
||||
{
|
||||
Assert::true($this->associationsElement->hasAssociation($association));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -423,6 +431,14 @@ final class ProductShowPageContext implements Context
|
|||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see the :variantName variant
|
||||
*/
|
||||
public function iShouldSeeTheVariant(string $variantName): void
|
||||
{
|
||||
Assert::true($this->variantsElement->hasProductVariant($variantName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see attribute :attribute with value :value in :nameOfLocale locale
|
||||
*/
|
||||
|
|
@ -432,7 +448,7 @@ final class ProductShowPageContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should see non-translatable attribute :attribute with value :value
|
||||
* @Then /^I should see non-translatable attribute "([^"]+)" with value ([^"]+)%$/
|
||||
*/
|
||||
public function iShouldSeeNonTranslatableAttributeWithValue(string $attribute, string $value): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,9 +18,13 @@ use FriendsOfBehat\PageObjectExtension\Element\Element;
|
|||
|
||||
final class AssociationsElement extends Element implements AssociationsElementInterface
|
||||
{
|
||||
public function isProductAssociated(string $associationName, string $productName): bool
|
||||
public function hasAssociation(string $associationName): bool
|
||||
{
|
||||
return [] !== $this->getAssociatedProducts($this->getElement('associations'), $associationName);
|
||||
}
|
||||
|
||||
public function isAssociatedWith(string $associationName, string $productName): bool
|
||||
{
|
||||
/** @var NodeElement $associations */
|
||||
$associations = $this->getElement('associations');
|
||||
|
||||
/** @var NodeElement $product */
|
||||
|
|
|
|||
|
|
@ -15,5 +15,7 @@ namespace Sylius\Behat\Element\Product\ShowPage;
|
|||
|
||||
interface AssociationsElementInterface
|
||||
{
|
||||
public function isProductAssociated(string $associationName, string $productName): bool;
|
||||
public function hasAssociation(string $associationName): bool;
|
||||
|
||||
public function isAssociatedWith(string $associationName, string $productName): bool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ final class AttributesElement extends Element implements AttributesElementInterf
|
|||
{
|
||||
$attributeValue = $this->getDocument()->find('css', sprintf('.ui.segment[data-tab="non-translatable"] tr:contains("%s") td:nth-child(2)', $attribute))->getText();
|
||||
|
||||
return $attributeValue === $value;
|
||||
return str_contains($attributeValue, $value);
|
||||
}
|
||||
|
||||
protected function getDefinedElements(): array
|
||||
|
|
|
|||
|
|
@ -26,6 +26,20 @@ final class VariantsElement extends Element implements VariantsElementInterface
|
|||
return \count($variants);
|
||||
}
|
||||
|
||||
public function hasProductVariant(string $name): bool
|
||||
{
|
||||
$variantRows = $this->getDocument()->findAll('css', '#variants .variants-accordion__title');
|
||||
|
||||
/** @var NodeElement $variant */
|
||||
foreach ($variantRows as $variant) {
|
||||
if ($variant->find('css', '.content .variant-name')->getText() === $name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasProductVariantWithCodePriceAndCurrentStock(
|
||||
string $name,
|
||||
string $code,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ interface VariantsElementInterface
|
|||
{
|
||||
public function countVariantsOnPage(): int;
|
||||
|
||||
public function hasProductVariant(string $name): bool;
|
||||
|
||||
public function hasProductVariantWithCodePriceAndCurrentStock(
|
||||
string $name,
|
||||
string $code,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@
|
|||
<service id="Sylius\Behat\Context\Api\Admin\ManagingProductTaxonsContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="api_platform.iri_converter" />
|
||||
<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_channels" class="Sylius\Behat\Context\Api\Admin\ManagingChannelsContext">
|
||||
|
|
@ -101,6 +103,15 @@
|
|||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.admin.managing_product_associations" class="Sylius\Behat\Context\Api\Admin\ManagingProductAssociationsContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="api_platform.iri_converter" />
|
||||
<argument type="service" id="sylius.behat.section_iri_converter" />
|
||||
<argument type="service" id="sylius.repository.product_association" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.admin.managing_product_association_types" class="Sylius\Behat\Context\Api\Admin\ManagingProductAssociationTypesContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
|
|
|
|||
|
|
@ -207,7 +207,6 @@
|
|||
<argument type="service" id="sylius.behat.page.admin.product_attribute.index" />
|
||||
<argument type="service" id="sylius.behat.page.admin.product_attribute.update" />
|
||||
<argument type="service" id="sylius.behat.current_page_resolver" />
|
||||
<argument type="service" id="sylius.behat.shared_security" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.ui.admin.managing_product_options" class="Sylius\Behat\Context\Ui\Admin\ManagingProductOptionsContext">
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ default:
|
|||
- sylius.behat.context.setup.taxonomy
|
||||
- sylius.behat.context.setup.zone
|
||||
|
||||
- sylius.behat.context.api.admin.managing_product_associations
|
||||
- sylius.behat.context.api.admin.managing_product_images
|
||||
- sylius.behat.context.api.admin.managing_products
|
||||
- sylius.behat.context.api.admin.response
|
||||
|
|
|
|||
|
|
@ -8,20 +8,38 @@ default:
|
|||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.currency
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.locale
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.product_association_type
|
||||
- sylius.behat.context.transform.product_attribute
|
||||
- sylius.behat.context.transform.product_option
|
||||
- sylius.behat.context.transform.product_option_value
|
||||
- sylius.behat.context.transform.product_variant
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
- sylius.behat.context.transform.shipping_category
|
||||
- sylius.behat.context.transform.tax_category
|
||||
- sylius.behat.context.transform.taxon
|
||||
- Sylius\Behat\Context\Transform\CatalogPromotionContext
|
||||
|
||||
- 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_association
|
||||
- sylius.behat.context.setup.product_attribute
|
||||
- sylius.behat.context.setup.product_option
|
||||
- sylius.behat.context.setup.product_taxon
|
||||
- sylius.behat.context.setup.shipping_category
|
||||
- sylius.behat.context.setup.taxation
|
||||
- sylius.behat.context.setup.taxonomy
|
||||
- Sylius\Behat\Context\Setup\CatalogPromotionContext
|
||||
- Sylius\Behat\Context\Setup\PriceHistoryContext
|
||||
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
|
||||
|
||||
- sylius.behat.context.api.admin.managing_products
|
||||
- Sylius\Behat\Context\Api\Admin\BrowsingCatalogPromotionProductVariantsContext
|
||||
|
||||
filters:
|
||||
tags: "@viewing_product_in_admin_panel&&@api"
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ default:
|
|||
- sylius.behat.context.transform.product_variant
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
- sylius.behat.context.transform.taxon
|
||||
- Sylius\Behat\Context\Transform\CatalogPromotionContext
|
||||
|
||||
- sylius.behat.context.setup.admin_user
|
||||
- sylius.behat.context.setup.admin_security
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.customer
|
||||
- sylius.behat.context.setup.locale
|
||||
|
|
@ -27,7 +27,7 @@ default:
|
|||
- sylius.behat.context.setup.product_attribute
|
||||
- sylius.behat.context.setup.product_review
|
||||
- sylius.behat.context.setup.product_taxon
|
||||
- sylius.behat.context.setup.shop_security
|
||||
- sylius.behat.context.setup.shop_api_security
|
||||
- sylius.behat.context.setup.taxonomy
|
||||
- Sylius\Behat\Context\Setup\CatalogPromotionContext
|
||||
- Sylius\Behat\Context\Setup\PriceHistoryContext
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ default:
|
|||
- sylius.behat.context.hook.session
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.currency
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.locale
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.product_association_type
|
||||
- sylius.behat.context.transform.product_option
|
||||
|
|
@ -19,19 +21,26 @@ default:
|
|||
- sylius.behat.context.transform.shipping_category
|
||||
- sylius.behat.context.transform.tax_category
|
||||
- sylius.behat.context.transform.taxon
|
||||
- Sylius\Behat\Context\Transform\CatalogPromotionContext
|
||||
|
||||
- sylius.behat.context.setup.admin_security
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.product_association
|
||||
- sylius.behat.context.setup.product_attribute
|
||||
- sylius.behat.context.setup.product_option
|
||||
- sylius.behat.context.setup.product_taxon
|
||||
- sylius.behat.context.setup.shipping_category
|
||||
- sylius.behat.context.setup.taxonomy
|
||||
- sylius.behat.context.setup.taxation
|
||||
- sylius.behat.context.setup.taxonomy
|
||||
- Sylius\Behat\Context\Setup\CatalogPromotionContext
|
||||
- Sylius\Behat\Context\Setup\PriceHistoryContext
|
||||
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
|
||||
|
||||
- sylius.behat.context.ui.admin.managing_product_attributes
|
||||
- sylius.behat.context.ui.admin.product_showpage
|
||||
- sylius.behat.context.ui.shop.browsing_product
|
||||
|
||||
filters:
|
||||
tags: "@viewing_product_in_admin_panel&&@ui"
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ default:
|
|||
- Sylius\Behat\Context\Setup\PriceHistoryContext
|
||||
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
|
||||
|
||||
- sylius.behat.context.ui.admin.managing_product_attributes
|
||||
- sylius.behat.context.ui.admin.product_showpage
|
||||
- sylius.behat.context.ui.channel
|
||||
- sylius.behat.context.ui.shop.locale
|
||||
- sylius.behat.context.ui.shop.product
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
|||
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class CatalogPromotionChannelFilter extends AbstractContextAwareFilter
|
||||
final class ChannelsAwareChannelFilter extends AbstractContextAwareFilter
|
||||
{
|
||||
public function __construct(
|
||||
private IriConverterInterface $iriConverter,
|
||||
|
|
@ -42,7 +42,7 @@ final class TranslationOrderNameAndLocaleFilter extends AbstractContextAwareFilt
|
|||
|
||||
$queryBuilder
|
||||
->addSelect('translation')
|
||||
->innerJoin(
|
||||
->leftJoin(
|
||||
sprintf('%s.translations', $rootAlias),
|
||||
'translation',
|
||||
'WITH',
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<attribute>sylius.api.catalog_promotion_enabled_filter</attribute>
|
||||
<attribute>sylius.api.catalog_promotion_start_date_filter</attribute>
|
||||
<attribute>sylius.api.catalog_promotion_end_date_filter</attribute>
|
||||
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter</attribute>
|
||||
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter</attribute>
|
||||
<attribute>sylius.api.order_filter.code</attribute>
|
||||
<attribute>sylius.api.order_filter.name</attribute>
|
||||
<attribute>sylius.api.order_filter.start_date</attribute>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
<attribute>sylius.api.product_name_filter</attribute>
|
||||
<attribute>sylius.api.product_order_filter</attribute>
|
||||
<attribute>sylius.api.product_taxon_code_filter</attribute>
|
||||
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter</attribute>
|
||||
<attribute>Sylius\Bundle\ApiBundle\Filter\Doctrine\TranslationOrderNameAndLocaleFilter</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
|
|
|
|||
|
|
@ -18,9 +18,67 @@
|
|||
<resource class="%sylius.model.product_association.class%" shortName="ProductAssociation">
|
||||
<attribute name="validation_groups">sylius</attribute>
|
||||
|
||||
<collectionOperations />
|
||||
<collectionOperations>
|
||||
<collectionOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-associations</attribute>
|
||||
<attribute name="filters">
|
||||
<attribute>sylius.api.product_association_filter</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_association:read</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
|
||||
<collectionOperation name="admin_post">
|
||||
<attribute name="method">POST</attribute>
|
||||
<attribute name="path">/admin/product-associations</attribute>
|
||||
<attribute name="denormalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_association:create</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_association:read</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
</collectionOperations>
|
||||
|
||||
<itemOperations>
|
||||
<itemOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-associations/{id}</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_association:read</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_put">
|
||||
<attribute name="method">PUT</attribute>
|
||||
<attribute name="path">/admin/product-associations/{id}</attribute>
|
||||
<attribute name="denormalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_association:update</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">
|
||||
<attribute>admin:product_association:read</attribute>
|
||||
</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_delete">
|
||||
<attribute name="method">DELETE</attribute>
|
||||
<attribute name="path">/admin/product-associations/{id}</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="shop_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/shop/product-associations/{id}</attribute>
|
||||
|
|
@ -34,6 +92,7 @@
|
|||
|
||||
<property name="id" identifier="true" writable="false" />
|
||||
<property name="type" identifier="false" required="true" />
|
||||
<property name="owner" required="true" />
|
||||
<property name="associatedProducts" required="true" />
|
||||
</resource>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
<collectionOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-attributes</attribute>
|
||||
<attribute name="filters">
|
||||
<attribute>sylius.api.order_filter.position</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_attribute:read</attribute>
|
||||
</attribute>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
<?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"
|
||||
|
|
@ -7,16 +18,21 @@
|
|||
<resource class="%sylius.model.product_taxon.class%" shortName="ProductTaxon">
|
||||
<attribute name="validation_groups">sylius</attribute>
|
||||
|
||||
<attribute name="order">
|
||||
<attribute name="position">ASC</attribute>
|
||||
</attribute>
|
||||
|
||||
<collectionOperations>
|
||||
<collectionOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/product-taxons</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_taxon:read</attribute>
|
||||
</attribute>
|
||||
<attribute name="filters">
|
||||
<attribute>sylius.api.search_filter.product.code</attribute>
|
||||
<attribute>sylius.api.search_filter.taxon.code</attribute>
|
||||
<attribute>sylius.api.order_filter.position</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:product_taxon:read</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
<collectionOperation name="admin_post">
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@
|
|||
<group>admin:catalog_promotion:read</group>
|
||||
<group>admin:catalog_promotion:create</group>
|
||||
<group>admin:catalog_promotion:update</group>
|
||||
<group>admin:product_variant:read</group>
|
||||
<group>shop:catalog_promotion:read</group>
|
||||
</attribute>
|
||||
|
||||
<attribute name="code">
|
||||
<group>admin:catalog_promotion:read</group>
|
||||
<group>admin:catalog_promotion:create</group>
|
||||
<group>admin:product_variant:read</group>
|
||||
</attribute>
|
||||
|
||||
<attribute name="name">
|
||||
|
|
|
|||
|
|
@ -42,5 +42,9 @@
|
|||
<attribute name="lowestPriceBeforeDiscount">
|
||||
<group>admin:product_variant:read</group>
|
||||
</attribute>
|
||||
|
||||
<attribute name="appliedPromotions">
|
||||
<group>admin:product_variant:read</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
<group>shop:product:read</group>
|
||||
</attribute>
|
||||
<attribute name="associations">
|
||||
<group>admin:product:read</group>
|
||||
<group>shop:product:read</group>
|
||||
</attribute>
|
||||
<attribute name="attributes">
|
||||
|
|
|
|||
|
|
@ -20,9 +20,18 @@
|
|||
<group>shop:product_association:read</group>
|
||||
</attribute>
|
||||
<attribute name="type">
|
||||
<group>admin:product_association:read</group>
|
||||
<group>admin:product_association:create</group>
|
||||
<group>shop:product_association:read</group>
|
||||
</attribute>
|
||||
<attribute name="owner">
|
||||
<group>admin:product_association:read</group>
|
||||
<group>admin:product_association:create</group>
|
||||
</attribute>
|
||||
<attribute name="associatedProducts">
|
||||
<group>admin:product_association:read</group>
|
||||
<group>admin:product_association:create</group>
|
||||
<group>admin:product_association:update</group>
|
||||
<group>shop:product_association:read</group>
|
||||
</attribute>
|
||||
</class>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@
|
|||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.product_association_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="owner.code">partial</argument>
|
||||
<argument key="type.code">partial</argument>
|
||||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.product_taxon_code_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="productTaxons.taxon.code">exact</argument>
|
||||
|
|
@ -71,6 +79,13 @@
|
|||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.order_filter.position" parent="api_platform.doctrine.orm.order_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="position" />
|
||||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.product_variant_product_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="product">exact</argument>
|
||||
|
|
@ -246,7 +261,7 @@
|
|||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Filter\Doctrine\CatalogPromotionChannelFilter" public="true">
|
||||
<service id="Sylius\Bundle\ApiBundle\Filter\Doctrine\ChannelsAwareChannelFilter" public="true">
|
||||
<argument type="service" id="api_platform.symfony.iri_converter" />
|
||||
<argument type="service" id="doctrine" />
|
||||
<tag name="api_platform.filter" />
|
||||
|
|
@ -281,13 +296,6 @@
|
|||
<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>
|
||||
|
||||
<service id="sylius.api.customer_group_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="group.name">exact</argument>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Component\Product\Model\ProductAssociation">
|
||||
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
|
||||
<option name="fields">
|
||||
<value>owner</value>
|
||||
<value>type</value>
|
||||
</option>
|
||||
<option name="errorPath">owner</option>
|
||||
<option name="message">sylius.association.unique</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<property name="type">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.association.type.not_blank</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="owner">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.association.owner.not_blank</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
@ -47,6 +47,12 @@ sylius:
|
|||
unique: The option value with given code already exists.
|
||||
value:
|
||||
not_blank: Please enter option value.
|
||||
association:
|
||||
unique: An association with this owner and type already exists.
|
||||
type:
|
||||
not_blank: Please enter association type.
|
||||
owner:
|
||||
not_blank: Please enter association owner.
|
||||
association_type:
|
||||
name:
|
||||
not_blank: Please enter association type name.
|
||||
|
|
|
|||
265
tests/Api/Admin/ProductAssociationsTest.php
Normal file
265
tests/Api/Admin/ProductAssociationsTest.php
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
<?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 Api\Admin;
|
||||
|
||||
use Sylius\Component\Product\Model\ProductAssociationInterface;
|
||||
use Sylius\Tests\Api\JsonApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ProductAssociationsTest extends JsonApiTestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_gets_product_association(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'authentication/api_administrator.yaml',
|
||||
'product/product_with_many_locales.yaml',
|
||||
]);
|
||||
|
||||
/** @var ProductAssociationInterface $association */
|
||||
$association = $fixtures['product_association'];
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/admin/product-associations/%s', $association->getId()),
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_association/get_product_association_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_nothing_if_association_not_found(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'authentication/api_administrator.yaml',
|
||||
'product/product_with_many_locales.yaml',
|
||||
]);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: '/api/v2/admin/product-associations/nope',
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_product_association_collection(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'product/product_with_many_locales.yaml',
|
||||
'authentication/api_administrator.yaml'
|
||||
]);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: '/api/v2/admin/product-associations',
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_association/get_product_association_collection_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_product_association(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'product/products_with_associations.yaml',
|
||||
'authentication/api_administrator.yaml',
|
||||
]);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-associations',
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdContentType()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
content: json_encode([
|
||||
'type' => '/api/v2/admin/product-association-types/similar_products',
|
||||
'owner' => '/api/v2/admin/products/CUP',
|
||||
'associatedProducts' => [
|
||||
'/api/v2/admin/products/MUG',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_association/post_product_association_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_updates_product_association(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'product/products_with_associations.yaml',
|
||||
'authentication/api_administrator.yaml',
|
||||
]);
|
||||
|
||||
/** @var ProductAssociationInterface $association */
|
||||
$association = $fixtures['product_association'];
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/admin/product-associations/%s', $association->getId()),
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdContentType()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
content: json_encode([
|
||||
'associatedProducts' => [
|
||||
'/api/v2/admin/products/TANKARD',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_association/put_product_association_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_deletes_product_association(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'product/products_with_associations.yaml',
|
||||
'authentication/api_administrator.yaml',
|
||||
]);
|
||||
|
||||
$header = $this
|
||||
->headerBuilder()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
;
|
||||
|
||||
/** @var ProductAssociationInterface $association */
|
||||
$association = $fixtures['product_association'];
|
||||
$associationId = $association->getId();
|
||||
|
||||
$this->client->request(
|
||||
method: 'DELETE',
|
||||
uri: sprintf('/api/v2/admin/product-associations/%s', $associationId),
|
||||
server: $header,
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/admin/product-associations/%s', $associationId),
|
||||
server: $header,
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_product_association_without_required_data(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'product/products_with_associations.yaml',
|
||||
'authentication/api_administrator.yaml',
|
||||
]);
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-associations',
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdContentType()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
content: '{}',
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_association/post_product_association_without_required_data_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_create_duplicated_product_association(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles([
|
||||
'product/products_with_associations.yaml',
|
||||
'authentication/api_administrator.yaml',
|
||||
]);
|
||||
|
||||
/** @var ProductAssociationInterface $association */
|
||||
$association = $fixtures['product_association'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/product-associations',
|
||||
server: $this
|
||||
->headerBuilder()
|
||||
->withJsonLdContentType()
|
||||
->withJsonLdAccept()
|
||||
->withAdminUserAuthorization('api@example.com')
|
||||
->build()
|
||||
,
|
||||
content: json_encode([
|
||||
'type' => sprintf('/api/v2/admin/product-association-types/%s', $association->getType()->getCode()),
|
||||
'owner' => sprintf('/api/v2/admin/products/%s', $association->getOwner()->getCode()),
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/product_association/post_duplicated_association_product_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ Sylius\Component\Core\Model\Product:
|
|||
translations:
|
||||
en_US: '@product_translation_mug_en_US'
|
||||
pl_PL: '@product_translation_mug_pl_PL'
|
||||
attributes:
|
||||
attributes:
|
||||
- '@product_attribute_value_material_en_US'
|
||||
- '@product_attribute_value_material_pl_PL'
|
||||
product_cap:
|
||||
|
|
@ -95,7 +95,7 @@ Sylius\Component\Product\Model\ProductVariantTranslation:
|
|||
locale: 'en_US'
|
||||
name: 'Red Mug'
|
||||
translatable: '@product_variant_mug_red'
|
||||
|
||||
|
||||
Sylius\Component\Core\Model\ChannelPricing:
|
||||
channel_pricing_mug_blue_web:
|
||||
channelCode: 'WEB'
|
||||
|
|
@ -114,7 +114,7 @@ Sylius\Component\Core\Model\ChannelPricing:
|
|||
channel_pricing_cap_red_web:
|
||||
channelCode: 'WEB'
|
||||
price: 2000
|
||||
|
||||
|
||||
Sylius\Component\Product\Model\ProductOption:
|
||||
product_option_color:
|
||||
code: 'COLOR'
|
||||
|
|
@ -228,3 +228,21 @@ Sylius\Component\Core\Model\OrderItem:
|
|||
order_item:
|
||||
variant: '@product_variant_cap_red'
|
||||
order: '@cart_with_items'
|
||||
|
||||
Sylius\Component\Product\Model\ProductAssociation:
|
||||
product_association:
|
||||
type: '@product_association_type'
|
||||
owner: '@product_mug'
|
||||
associatedProducts: ['@product_cap']
|
||||
|
||||
Sylius\Component\Product\Model\ProductAssociationType:
|
||||
product_association_type:
|
||||
code: 'similar_products'
|
||||
translations:
|
||||
en_US: '@product_association_type_translation'
|
||||
|
||||
Sylius\Component\Product\Model\ProductAssociationTypeTranslation:
|
||||
product_association_type_translation:
|
||||
name: 'Similar products'
|
||||
locale: 'en_US'
|
||||
translatable: '@product_association_type'
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ Sylius\Component\Core\Model\Product:
|
|||
currentLocale: 'en_US'
|
||||
translations:
|
||||
en_US: '@product_translation_cup_en_US'
|
||||
product_tankard:
|
||||
code: 'TANKARD'
|
||||
channels: [ '@channel_web' ]
|
||||
currentLocale: 'en_US'
|
||||
translations:
|
||||
en_US: '@product_translation_tankard_en_US'
|
||||
product_hat:
|
||||
code: 'HAT'
|
||||
channels: [ '@channel_web' ]
|
||||
|
|
@ -56,6 +62,13 @@ Sylius\Component\Core\Model\ProductTranslation:
|
|||
description: 'Short cup description'
|
||||
shortDescription: 'Cup'
|
||||
translatable: '@product_cup'
|
||||
product_translation_tankard_en_US:
|
||||
slug: 'Tankard'
|
||||
locale: 'en_US'
|
||||
name: 'Tankard'
|
||||
description: 'Tankard description'
|
||||
shortDescription: 'Tankard'
|
||||
translatable: '@product_tankard'
|
||||
product_translation_hat_en_US:
|
||||
slug: 'Hat'
|
||||
locale: 'en_US'
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@
|
|||
"\/api\/v2\/admin\/product-reviews\/@integer@",
|
||||
"\/api\/v2\/admin\/product-reviews\/@integer@"
|
||||
],
|
||||
"associations": [
|
||||
"\/api\/v2\/admin\/product-associations\/@integer@"
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"\/api\/v2\/admin\/channels\/WEB"
|
||||
],
|
||||
"reviews": [],
|
||||
"associations": [],
|
||||
"attributes": [],
|
||||
"averageRating": 0,
|
||||
"images": [],
|
||||
|
|
@ -66,6 +67,9 @@
|
|||
"\/api\/v2\/admin\/product-reviews\/@integer@",
|
||||
"\/api\/v2\/admin\/product-reviews\/@integer@"
|
||||
],
|
||||
"associations": [
|
||||
"\/api\/v2\/admin\/product-associations\/@integer@"
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",
|
||||
|
|
@ -139,6 +143,7 @@
|
|||
"\/api\/v2\/admin\/channels\/WEB"
|
||||
],
|
||||
"reviews": [],
|
||||
"associations": [],
|
||||
"attributes": [],
|
||||
"averageRating": 0,
|
||||
"images": [],
|
||||
|
|
@ -162,7 +167,7 @@
|
|||
"hydra:totalItems": 3,
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/admin\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],order[translation.name],localeCode for order[translation.name]}",
|
||||
"hydra:template": "\/api\/v2\/admin\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],channel,order[translation.name],localeCode for order[translation.name]}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
|
|
@ -195,6 +200,12 @@
|
|||
"property": "productTaxons.taxon.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channel",
|
||||
"property": "channels",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[translation.name]",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"\/api\/v2\/admin\/channels\/WEB_GB"
|
||||
],
|
||||
"reviews": [],
|
||||
"associations": [],
|
||||
"attributes": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@
|
|||
"\/api\/v2\/admin\/product-reviews\/@integer@",
|
||||
"\/api\/v2\/admin\/product-reviews\/@integer@"
|
||||
],
|
||||
"associations": [
|
||||
"\/api\/v2\/admin\/product-associations\/@integer@"
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-attribute-values\/@integer@",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAssociation",
|
||||
"@id": "\/api\/v2\/admin\/product-associations",
|
||||
"@type": "hydra:Collection",
|
||||
"hydra:member": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
|
||||
"@type": "ProductAssociation",
|
||||
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
|
||||
"owner": "\/api\/v2\/admin\/products\/MUG",
|
||||
"associatedProducts": [
|
||||
"\/api\/v2\/admin\/products\/CUP"
|
||||
]
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 1,
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/admin\/product-associations{?owner.code,type.code}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "owner.code",
|
||||
"property": "owner.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "type.code",
|
||||
"property": "type.code",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAssociation",
|
||||
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
|
||||
"@type": "ProductAssociation",
|
||||
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
|
||||
"owner": "\/api\/v2\/admin\/products\/MUG",
|
||||
"associatedProducts": [
|
||||
"\/api\/v2\/admin\/products\/CUP"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "owner: An association with this owner and type already exists.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "owner",
|
||||
"message": "An association with this owner and type already exists.",
|
||||
"code": @string@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAssociation",
|
||||
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
|
||||
"@type": "ProductAssociation",
|
||||
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
|
||||
"owner": "\/api\/v2\/admin\/products\/CUP",
|
||||
"associatedProducts": [
|
||||
"\/api\/v2\/admin\/products\/MUG"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "type: Please enter association type.\nowner: Please enter association owner.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "type",
|
||||
"message": "Please enter association type.",
|
||||
"code": @string@
|
||||
},
|
||||
{
|
||||
"propertyPath": "owner",
|
||||
"message": "Please enter association owner.",
|
||||
"code": @string@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ProductAssociation",
|
||||
"@id": "\/api\/v2\/admin\/product-associations\/@integer@",
|
||||
"@type": "ProductAssociation",
|
||||
"type": "\/api\/v2\/admin\/product-association-types\/similar_products",
|
||||
"owner": "\/api\/v2\/admin\/products\/MUG",
|
||||
"associatedProducts": [
|
||||
"\/api\/v2\/admin\/products\/TANKARD"
|
||||
]
|
||||
}
|
||||
|
|
@ -184,5 +184,18 @@
|
|||
"type": "text"
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 9
|
||||
"hydra:totalItems": 9,
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/admin\/product-attributes{?order[position]}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[position]",
|
||||
"property": "position",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"hydra:totalItems": 2,
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/admin\/product-taxons{?product.code,product.code[],taxon.code,taxon.code[]}",
|
||||
"hydra:template": "\/api\/v2\/admin\/product-taxons{?product.code,product.code[],taxon.code,taxon.code[],order[position]}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
|
|
@ -49,6 +49,12 @@
|
|||
"variable": "taxon.code[]",
|
||||
"property": "taxon.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[position]",
|
||||
"property": "position",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
"price": 2000,
|
||||
"originalPrice": null,
|
||||
"lowestPriceBeforeDiscount": null,
|
||||
"minimumPrice": 0
|
||||
"minimumPrice": 0,
|
||||
"appliedPromotions": []
|
||||
}
|
||||
},
|
||||
"translations": {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
"price": 2000,
|
||||
"originalPrice": null,
|
||||
"lowestPriceBeforeDiscount": null,
|
||||
"minimumPrice": 0
|
||||
"minimumPrice": 0,
|
||||
"appliedPromotions": []
|
||||
}
|
||||
},
|
||||
"translations": {
|
||||
|
|
@ -59,7 +60,8 @@
|
|||
"price": 3000,
|
||||
"originalPrice": null,
|
||||
"lowestPriceBeforeDiscount": null,
|
||||
"minimumPrice": 0
|
||||
"minimumPrice": 0,
|
||||
"appliedPromotions": []
|
||||
}
|
||||
},
|
||||
"translations": {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
"price": 4000,
|
||||
"originalPrice": null,
|
||||
"minimumPrice": 0,
|
||||
"lowestPriceBeforeDiscount": null
|
||||
"lowestPriceBeforeDiscount": null,
|
||||
"appliedPromotions": []
|
||||
}
|
||||
},
|
||||
"translations": {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
"price": 4000,
|
||||
"originalPrice": 5000,
|
||||
"minimumPrice": 2000,
|
||||
"lowestPriceBeforeDiscount": null
|
||||
"lowestPriceBeforeDiscount": null,
|
||||
"appliedPromotions": []
|
||||
}
|
||||
},
|
||||
"translations": {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
"price": 3000,
|
||||
"originalPrice": 4000,
|
||||
"lowestPriceBeforeDiscount": 2000,
|
||||
"minimumPrice": 500
|
||||
"minimumPrice": 500,
|
||||
"appliedPromotions": []
|
||||
}
|
||||
},
|
||||
"translations": {
|
||||
|
|
|
|||
|
|
@ -51,68 +51,89 @@
|
|||
"\/api\/v2\/shop\/product-associations\/@integer@"
|
||||
],
|
||||
"defaultVariant": "\/api\/v2\/shop\/product-variants\/MUG_BLUE"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/products\/TANKARD",
|
||||
"@type": "Product",
|
||||
"productTaxons": [],
|
||||
"mainTaxon": null,
|
||||
"reviews": [],
|
||||
"averageRating": @integer@,
|
||||
"images": [],
|
||||
"id": @integer@,
|
||||
"code": "TANKARD",
|
||||
"variants": [],
|
||||
"options": [],
|
||||
"associations": [],
|
||||
"createdAt": @date@,
|
||||
"updatedAt": @date@,
|
||||
"shortDescription": "Tankard",
|
||||
"name": "Tankard",
|
||||
"description": "Tankard description",
|
||||
"slug": "Tankard",
|
||||
"defaultVariant": null
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 2,
|
||||
"hydra:totalItems": 3,
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/shop\/products{?translations.name,order[code],order[createdAt],productTaxons.taxon.code,productTaxons.taxon.code[],order[price],order[translation.name],localeCode for order[translation.name],taxon}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "translations.name",
|
||||
"property": "translations.name",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[code]",
|
||||
"property": "code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[createdAt]",
|
||||
"property": "createdAt",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "productTaxons.taxon.code",
|
||||
"property": "productTaxons.taxon.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "productTaxons.taxon.code[]",
|
||||
"property": "productTaxons.taxon.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[price]",
|
||||
"property": "price",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[translation.name]",
|
||||
"property": "translation",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "localeCode for order[translation.name]",
|
||||
"property": "localeCode",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "taxon",
|
||||
"property": null,
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "translations.name",
|
||||
"property": "translations.name",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[code]",
|
||||
"property": "code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[createdAt]",
|
||||
"property": "createdAt",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "productTaxons.taxon.code",
|
||||
"property": "productTaxons.taxon.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "productTaxons.taxon.code[]",
|
||||
"property": "productTaxons.taxon.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[price]",
|
||||
"property": "price",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "order[translation.name]",
|
||||
"property": "translation",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "localeCode for order[translation.name]",
|
||||
"property": "localeCode",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "taxon",
|
||||
"property": null,
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue