mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-05 20:57:12 +00:00
Merge branch '1.13' into 2.0
* 1.13: (82 commits) CreateAdminUserCommandTest class - sorting of used classes InformAboutGUSCommand class - fix a typo Update upgrade file [AdminBundle] Change namespace of CreateAdminUser [CoreBundle] Move classes from Command to Console/Command [UserBundle] Move classes from Command to Console/Command [UiBundle] Move classes from Command to Console/Command [PromotionBundle] Move classes from Command to Console/Command [OrderBundle] Move classes from Command to Console/Command [AdminBundle] Move classes from Command to Console/Command [ECS] Apply ecs fixes [Addressing] Support BC for ZoneMatcher [Upgrade] Note ZoneRepository addition and ZoneMatcher constructor changes [DX] Update OrderRepository [Statistics] Refactor GetStatisticsAction Change new orders name to paid orders to not mix up names fix unit test Apply changes from comments and improve outdated StatisticsModifier fix behat tests Remove specs for unexisting classes and add existing ones ...
This commit is contained in:
commit
1e943288a6
229 changed files with 6817 additions and 1867 deletions
|
|
@ -80,7 +80,9 @@ class RoboFile extends Tasks
|
|||
$task->exec('Tests/Application/bin/console doctrine:schema:update --force');
|
||||
}
|
||||
|
||||
$task->exec('vendor/bin/phpspec run --ansi --no-interaction -f dot');
|
||||
if (file_exists(sprintf('%s/phpspec.yml', $packagePath)) || file_exists(sprintf('%s/phpspec.yml.dist', $packagePath)) || file_exists(sprintf('%s/phpspec.yaml', $packagePath))) {
|
||||
$task->exec('vendor/bin/phpspec run --ansi --no-interaction -f dot');
|
||||
}
|
||||
|
||||
if (file_exists(sprintf('%s/phpunit.xml', $packagePath)) || file_exists(sprintf('%s/phpunit.xml.dist', $packagePath))) {
|
||||
$task->exec('vendor/bin/phpunit --colors=always');
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@
|
|||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sylius\\Abstraction\\StateMachine\\": "src/Sylius/Abstraction/StateMachine/src",
|
||||
"Sylius\\Behat\\": "src/Sylius/Behat/",
|
||||
"Sylius\\Bundle\\": "src/Sylius/Bundle/",
|
||||
"Sylius\\Component\\": "src/Sylius/Component/"
|
||||
|
|
@ -316,7 +317,8 @@
|
|||
"spec\\Sylius\\Bundle\\TaxationBundle\\": "src/Sylius/Bundle/TaxationBundle/spec/",
|
||||
"spec\\Sylius\\Bundle\\TaxonomyBundle\\": "src/Sylius/Bundle/TaxonomyBundle/spec/",
|
||||
"spec\\Sylius\\Bundle\\UiBundle\\": "src/Sylius/Bundle/UiBundle/spec/",
|
||||
"spec\\Sylius\\Bundle\\UserBundle\\": "src/Sylius/Bundle/UserBundle/spec/"
|
||||
"spec\\Sylius\\Bundle\\UserBundle\\": "src/Sylius/Bundle/UserBundle/spec/",
|
||||
"Tests\\Sylius\\Abstraction\\StateMachine\\": "src/Sylius/Abstraction/StateMachine/tests"
|
||||
},
|
||||
"classmap": [
|
||||
"src/Kernel.php"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ return [
|
|||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Sylius\Abstraction\StateMachine\SyliusStateMachineAbstractionBundle::class => ['all' => true],
|
||||
Sylius\Bundle\OrderBundle\SyliusOrderBundle::class => ['all' => true],
|
||||
Sylius\Bundle\MoneyBundle\SyliusMoneyBundle::class => ['all' => true],
|
||||
Sylius\Bundle\CurrencyBundle\SyliusCurrencyBundle::class => ['all' => true],
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ Feature: Resending an order confirmation email for a chosen order
|
|||
And the customer chose "Free" shipping method with "Cash on Delivery" payment
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @email
|
||||
@ui @email @api
|
||||
Scenario: Resending a confirmation email for a given order
|
||||
When I view the summary of the order "#00000666"
|
||||
And I resend the order confirmation email
|
||||
Then I should be notified that the order confirmation email has been successfully resent to the customer
|
||||
And an email with the confirmation of the order "#00000666" should be sent to "lucy@teamlucifer.com"
|
||||
|
||||
@ui @email
|
||||
@ui @email @api
|
||||
Scenario: Sending a confirmation email after shipping an order in different locale than the default one
|
||||
Given the order "#00000666" has been placed in "Polish (Poland)" locale
|
||||
When I view the summary of the order "#00000666"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Feature: Adding a new product variant
|
|||
When I want to create a new variant of this product
|
||||
And I specify its code as "VODKA_WYBOROWA_PREMIUM"
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "VODKA_WYBOROWA_PREMIUM" variant of the "Wyborowa Vodka" product should appear in the store
|
||||
|
|
@ -29,6 +30,7 @@ Feature: Adding a new product variant
|
|||
And I name it "Vodka Wyborowa Premium" in "English (United States)"
|
||||
And I name it "Wódka Wyborowa Premium" in "Polish (Poland)"
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "VODKA_WYBOROWA_PREMIUM" variant of the "Wyborowa Vodka" product should appear in the store
|
||||
|
|
@ -52,6 +54,7 @@ Feature: Adding a new product variant
|
|||
And I specify its code as "VODKA_WYBOROWA_PREMIUM"
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I set its shipping category as "Fragile"
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "VODKA_WYBOROWA_PREMIUM" variant of the "Wyborowa Vodka" product should appear in the store
|
||||
|
|
@ -62,6 +65,7 @@ Feature: Adding a new product variant
|
|||
And I specify its code as "VODKA_WYBOROWA_EXQUISITE"
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I set its original price to "$120.00" for "United States" channel
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "VODKA_WYBOROWA_EXQUISITE" variant of the "Wyborowa Vodka" product should appear in the store
|
||||
|
|
@ -74,6 +78,7 @@ Feature: Adding a new product variant
|
|||
And I specify its code as "VODKA_WYBOROWA_PREMIUM"
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I do not want to have shipping required for this product variant
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the variant with code "VODKA_WYBOROWA_PREMIUM" should not have shipping required
|
||||
|
|
@ -85,6 +90,7 @@ Feature: Adding a new product variant
|
|||
When I want to create a new variant of this product
|
||||
And I specify its code as "VODKA_WYBOROWA_PREMIUM"
|
||||
And I set its price to "$0.00" for "United States" channel
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "VODKA_WYBOROWA_PREMIUM" variant of the "Wyborowa Vodka" product should appear in the store
|
||||
|
|
@ -96,6 +102,7 @@ Feature: Adding a new product variant
|
|||
And I specify its code as "VODKA_WYBOROWA"
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I set its minimum price to "$50.00" for "United States" channel
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "VODKA_WYBOROWA" variant of the "Wyborowa Vodka" product should appear in the store
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ Feature: Editing a product variant
|
|||
And this channel allows to shop using "English (United States)" and "Polish (Poland)" locales
|
||||
And the store has a "T-Shirt" configurable product
|
||||
And this product has option "Size" with values "S", "M" and "L"
|
||||
And this product has option "Color" with values "Green" and "Blue"
|
||||
And this product has "Go" variant priced at "$100.00" configured with "S" option value
|
||||
And this product is named "Go" in the "English (United States)" locale
|
||||
And this product is named "Idź" in the "Polish (Poland)" locale
|
||||
|
|
@ -28,9 +27,7 @@ Feature: Editing a product variant
|
|||
@api @ui
|
||||
Scenario: Changing product variant option values
|
||||
When I want to modify the "Go" product variant
|
||||
And I set its "Color" option to "Green"
|
||||
And I change its "Size" option to "L"
|
||||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And the variant "Go" should have "Color" option as "Green"
|
||||
And the variant "Go" should have "Size" option as "L"
|
||||
|
|
|
|||
|
|
@ -70,6 +70,31 @@ Feature: Product variant validation
|
|||
Then I should be notified that the variant can have only one value configured for a single option
|
||||
And the "Wyborowa Vodka" product should have no variants
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Adding a new product variant without any of required options configured
|
||||
Given this product has option "Taste" with values "Orange" and "Melon"
|
||||
And this product has option "Size" with values "Small" and "Big"
|
||||
When I want to create a new variant of this product
|
||||
And I specify its code as "VODKA_WYBOROWA_PREMIUM"
|
||||
And I do not set its "Taste" and "Size" options
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I try to add it
|
||||
Then I should be notified that required options have not been configured
|
||||
And the "Wyborowa Vodka" product should have no variants
|
||||
|
||||
@api @no-ui
|
||||
Scenario: Adding a new product variant without one of required options configured
|
||||
Given this product has option "Taste" with values "Orange" and "Melon"
|
||||
And this product has option "Size" with values "Small" and "Big"
|
||||
When I want to create a new variant of this product
|
||||
And I specify its code as "VODKA_WYBOROWA_PREMIUM"
|
||||
And I set its "Taste" option to "Orange"
|
||||
And I do not set its "Size" option
|
||||
And I set its price to "$100.00" for "United States" channel
|
||||
And I try to add it
|
||||
Then I should be notified that required options have not been configured
|
||||
And the "Wyborowa Vodka" product should have no variants
|
||||
|
||||
@api @ui
|
||||
Scenario: Adding a new product variant with negative properties
|
||||
When I want to create a new variant of this product
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ Feature: Securing access to the administration panel after using the back button
|
|||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
And I am logged in as an administrator
|
||||
And I am on the administration dashboard
|
||||
|
||||
@ui @mink:chromedriver @no-api
|
||||
Scenario: Securing access to administration dashboard after using the back button after logging out
|
||||
When I log out
|
||||
When I am on the administration dashboard
|
||||
And I log out
|
||||
And I go back one page in the browser
|
||||
Then I should not see the administration dashboard
|
||||
And I should be on the login page
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ Feature: Statistics
|
|||
And 2 more new customers have paid 2 orders placed for total of "$5,000.37"
|
||||
When I view statistics
|
||||
Then I should see 5 new customers
|
||||
And I should see 6 new orders
|
||||
And I should see 6 paid orders
|
||||
And there should be total sales of "$7,000.58"
|
||||
And the average order value should be "$1,166.76"
|
||||
|
||||
@ui @javascript
|
||||
@api @ui @javascript
|
||||
Scenario: Seeing statistics for the previous year
|
||||
Given it is "first day of January last year" now
|
||||
And 3 new customers have fulfilled 2 orders placed for total of "$2,000.00"
|
||||
|
|
@ -35,11 +35,11 @@ Feature: Statistics
|
|||
And 2 more new customers have paid 2 orders placed for total of "$5,000.37"
|
||||
When I view statistics for "United States" channel and previous year split by month
|
||||
Then I should see 3 new customers
|
||||
And I should see 2 new orders
|
||||
And I should see 2 paid orders
|
||||
And there should be total sales of "$2,000.00"
|
||||
And the average order value should be "$1,000.00"
|
||||
|
||||
@ui @javascript
|
||||
@ui @javascript @no-api
|
||||
Scenario: Seeing statistics for the next year
|
||||
Given it is "first day of January last year" now
|
||||
And 3 new customers have fulfilled 2 orders placed for total of "$2,000.00"
|
||||
|
|
@ -49,11 +49,11 @@ Feature: Statistics
|
|||
When I view statistics for "United States" channel and previous year split by month
|
||||
And I view statistics for "United States" channel and next year split by month
|
||||
Then I should see 6 new customers
|
||||
And I should see 7 new orders
|
||||
And I should see 7 paid orders
|
||||
And there should be total sales of "$10,000.74"
|
||||
And the average order value should be "$1,428.68"
|
||||
|
||||
@ui @javascript
|
||||
@api @ui @javascript
|
||||
Scenario: Seeing statistics that include only fulfilled orders that were not cancelled
|
||||
Given 4 new customers have fulfilled 4 orders placed for total of "$5,241.00"
|
||||
And 2 more new customers have placed 2 orders for total of "$459.00"
|
||||
|
|
@ -61,7 +61,7 @@ Feature: Statistics
|
|||
And a single customer has placed an order for total of "$1,000.00"
|
||||
But the customer cancelled this order
|
||||
When I view statistics for "United States" channel and current year split by month
|
||||
Then I should see 4 new orders
|
||||
Then I should see 4 paid orders
|
||||
And I should see 9 new customers
|
||||
And there should be total sales of "$5,241.00"
|
||||
And the average order value should be "$1,310.25"
|
||||
|
|
|
|||
|
|
@ -18,43 +18,43 @@ Feature: Statistics for a specific channel
|
|||
Given 3 new customers have fulfilled 4 orders placed for total of "$8,566.00" mostly "Onion" product
|
||||
And 2 more new customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
|
||||
When I view statistics
|
||||
Then I should see 4 new orders
|
||||
Then I should see 4 paid orders
|
||||
And I should see 5 new customers
|
||||
And there should be total sales of "$8,566.00"
|
||||
And the average order value should be "$2,141.50"
|
||||
|
||||
@ui @mink:chromedriver
|
||||
@ui @mink:chromedriver @api
|
||||
Scenario: Switching to the channel with only fulfilled orders
|
||||
Given 4 new customers have fulfilled 4 orders placed for total of "$5,241.00" mostly "Onion" product
|
||||
And 2 more new customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
|
||||
And 2 more new customers have placed 3 orders for total of "$1,259.00" mostly "Banana" product
|
||||
When I view statistics for "WEB-POLAND" channel and current year split by month
|
||||
And I choose "WEB-US" channel
|
||||
Then I should see 2 new orders
|
||||
Then I should see 2 paid orders
|
||||
And I should see 8 new customers
|
||||
And there should be total sales of "$459.00"
|
||||
And the average order value should be "$229.50"
|
||||
|
||||
@ui @mink:chromedriver
|
||||
@ui @mink:chromedriver @api
|
||||
Scenario: Switching to the channel with both fulfilled and placed orders
|
||||
Given 4 new customers have fulfilled 4 orders placed for total of "$5,241.00" mostly "Onion" product
|
||||
And 2 more new customers have fulfilled 2 orders placed for total of "$459.00" mostly "Banana" product
|
||||
And 2 more new customers have placed 3 orders for total of "$1,259.00" mostly "Banana" product
|
||||
When I view statistics for "WEB-US" channel
|
||||
And I choose "WEB-POLAND" channel
|
||||
Then I should see 4 new orders
|
||||
Then I should see 4 paid orders
|
||||
And I should see 8 new customers
|
||||
And there should be total sales of "$5,241.00"
|
||||
And the average order value should be "$1,310.25"
|
||||
|
||||
@ui
|
||||
@no-api @ui
|
||||
Scenario: Seeing recent orders in a specific channel
|
||||
Given 3 new customers have placed 4 orders for total of "$8,566.00" mostly "Onion" product
|
||||
And 2 more new customers have placed 2 orders for total of "$459.00" mostly "Banana" product
|
||||
When I view statistics for "WEB-POLAND" channel
|
||||
Then I should see 4 new orders in the list
|
||||
|
||||
@ui
|
||||
@no-api @ui
|
||||
Scenario: Seeing recent orders in a specific channel
|
||||
Given 3 new customers have placed 4 orders for total of "$8,566.00" mostly "Onion" product
|
||||
And 2 more new customers have placed 2 orders for total of "$459.00" mostly "Banana" product
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Feature: Changing shop user's password
|
|||
And there is a user "kibsoon@example.com" identified by "goodGuy"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Changing a password of a shop user
|
||||
When I change the password of user "kibsoon@example.com" to "veryGoodGuy"
|
||||
Then I should be notified that it has been successfully edited
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ Feature: Deleting the customer account
|
|||
And there is a user "theodore@example.com" identified by "pswd"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Deleting account should not delete customer details
|
||||
When I delete the account of "theodore@example.com" user
|
||||
Then the user account should be deleted
|
||||
But the customer with this email should still exist
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: A customer with no user cannot be deleted
|
||||
Given the account of "theodore@example.com" was deleted
|
||||
Then I should not be able to delete it again
|
||||
|
|
|
|||
17
features/cli/change_admin_password.feature
Normal file
17
features/cli/change_admin_password.feature
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@change_admin_password
|
||||
Feature: Changing an administrator's password via CLI
|
||||
In order to login to my administrator account when I forget my password
|
||||
As a administrator
|
||||
I want to be able to change my administrator password via CLI
|
||||
|
||||
Background:
|
||||
Given there is an administrator "sylius_change_password@example.com" identified by "sylius"
|
||||
|
||||
@cli
|
||||
Scenario: Changing an administrator's password
|
||||
When I want to change password
|
||||
And I specify email as "sylius_change_password@example.com"
|
||||
And I specify my new password as "newp@ssw0rd"
|
||||
And I run command
|
||||
Then I should be informed that password has been changed successfully
|
||||
And I should be able to log in as "sylius_change_password@example.com" authenticated by "newp@ssw0rd" password
|
||||
|
|
@ -18,7 +18,7 @@ Feature: Seeing validation messages during address addition
|
|||
Then I should still be on the address addition page
|
||||
And I should be notified about 6 errors
|
||||
|
||||
@ui @javascript @api
|
||||
@ui @api
|
||||
Scenario: The province needs to be selected when the chosen country has at least one stated
|
||||
When I want to add a new address to my address book
|
||||
And I specify the address as "Lucifer Morningstar", "Seaside Fwy", "90802", "Los Angeles", "United States", "Arkansas"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Feature: Province field entry stays after validation errors
|
|||
And I have an address "Lucifer Morningstar", "Seaside Fwy", "90802", "Los Angeles", "United States", "Arkansas" in my address book
|
||||
And I have an address "Fletcher Ren", "Upper Barkly Street", "3377", "Ararat", "Australia", "Victoria" in my address book
|
||||
|
||||
@ui @javascript @api
|
||||
@ui @api
|
||||
Scenario: The province name stays after validation error
|
||||
Given I am editing the address of "Lucifer Morningstar"
|
||||
When I remove the street
|
||||
|
|
@ -21,7 +21,7 @@ Feature: Province field entry stays after validation errors
|
|||
Then I should still be on the "Lucifer Morningstar" address edit page
|
||||
And I should still have "Arkansas" as my specified province
|
||||
|
||||
@ui @javascript @api
|
||||
@ui @api
|
||||
Scenario: The selected province stays after validation error
|
||||
Given I am editing the address of "Fletcher Ren"
|
||||
When I remove the street
|
||||
|
|
|
|||
|
|
@ -80,16 +80,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/Action/ResendShipmentConfirmationEmailAction.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\Command\\\\CreateAdminUserCommand\\:\\:askAdminUserData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/Command/CreateAdminUserCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\Command\\\\CreateAdminUserCommand\\:\\:showSummary\\(\\) has parameter \\$adminUserData with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/Command/CreateAdminUserCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\Controller\\\\CustomerStatisticsController\\:\\:__construct\\(\\) has parameter \\$customerRepository with generic interface Sylius\\\\Component\\\\Resource\\\\Repository\\\\RepositoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
|
|
@ -190,36 +180,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/Menu/PromotionUpdateMenuBuilder.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\MessageHandler\\\\CreateAdminUserHandler\\:\\:__construct\\(\\) has parameter \\$adminUserFactory with generic interface Sylius\\\\Component\\\\Resource\\\\Factory\\\\FactoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/MessageHandler/CreateAdminUserHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\MessageHandler\\\\CreateAdminUserHandler\\:\\:__construct\\(\\) has parameter \\$adminUserRepository with generic interface Sylius\\\\Component\\\\User\\\\Repository\\\\UserRepositoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/MessageHandler/CreateAdminUserHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\MessageHandler\\\\CreateAdminUserHandler\\:\\:__construct\\(\\) has parameter \\$validationGroups with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/MessageHandler/CreateAdminUserHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\MessageHandler\\\\CreateAdminUserHandler\\:\\:getViolationMessages\\(\\) return type has no value type specified in iterable type iterable\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/MessageHandler/CreateAdminUserHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\Provider\\\\StatisticsDataProvider\\:\\:getRawData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/Provider/StatisticsDataProvider.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\Provider\\\\StatisticsDataProviderInterface\\:\\:getRawData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AdminBundle/Provider/StatisticsDataProviderInterface.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AdminBundle\\\\Twig\\\\ChannelNameExtension\\:\\:__construct\\(\\) has parameter \\$channelRepository with generic interface Sylius\\\\Component\\\\Channel\\\\Repository\\\\ChannelRepositoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
|
|
@ -2295,21 +2255,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Collector/SyliusCollector.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\Command\\\\AbstractInstallCommand\\:\\:renderTable\\(\\) has parameter \\$headers with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/AbstractInstallCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\Command\\\\AbstractInstallCommand\\:\\:renderTable\\(\\) has parameter \\$rows with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/AbstractInstallCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\Command\\\\AbstractInstallCommand\\:\\:runCommands\\(\\) has parameter \\$commands with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/AbstractInstallCommand.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @var for variable \\$command has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
|
|
@ -3990,11 +3935,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/LocaleBundle/SyliusLocaleBundle.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\OrderBundle\\\\Command\\\\RemoveExpiredCartsCommand\\:\\:__construct\\(\\) has parameter \\$expirationTime with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/OrderBundle/Command/RemoveExpiredCartsCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\OrderBundle\\\\Context\\\\SessionBasedCartContext\\:\\:__construct\\(\\) has parameter \\$orderRepository with generic interface Sylius\\\\Component\\\\Order\\\\Repository\\\\OrderRepositoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
|
|
@ -4330,11 +4270,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/ProductBundle/Validator/UniqueSimpleProductCodeValidator.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\PromotionBundle\\\\Command\\\\GenerateCouponsCommand\\:\\:__construct\\(\\) has parameter \\$promotionRepository with generic interface Sylius\\\\Component\\\\Promotion\\\\Repository\\\\PromotionRepositoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/PromotionBundle/Command/GenerateCouponsCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\PromotionBundle\\\\DiscountApplicationCriteria\\\\DiscountApplicationCriteriaInterface\\:\\:isApplicable\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
|
|
@ -4871,24 +4806,14 @@ parameters:
|
|||
path: src/Sylius/Bundle/UiBundle/Twig/TemplateEventExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\UserBundle\\\\Command\\\\AbstractRoleCommand\\:\\:executeRoleCommand\\(\\) has parameter \\$securityRoles with no value type specified in iterable type array\\.$#"
|
||||
message: "#^Method Sylius\\\\Bundle\\\\UserBundle\\\\Console\\\\Command\\\\AbstractRoleCommand\\:\\:getUserRepository\\(\\) return type with generic interface Sylius\\\\Component\\\\User\\\\Repository\\\\UserRepositoryInterface does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/AbstractRoleCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\UserBundle\\\\Command\\\\AbstractRoleCommand\\:\\:getAvailableUserTypes\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/AbstractRoleCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\UserBundle\\\\Command\\\\AbstractRoleCommand\\:\\:getUserRepository\\(\\) return type with generic interface Sylius\\\\Component\\\\User\\\\Repository\\\\UserRepositoryInterface does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/AbstractRoleCommand.php
|
||||
path: src/Sylius/Bundle/UserBundle/Console/Command/AbstractRoleCommand.php
|
||||
|
||||
-
|
||||
message: "#^Unable to resolve the template type T in call to method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getRepository\\(\\)$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/AbstractRoleCommand.php
|
||||
path: src/Sylius/Bundle/UserBundle/Console/Command/AbstractRoleCommand.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\UserBundle\\\\Command\\\\DemoteUserCommand\\:\\:executeRoleCommand\\(\\) has parameter \\$securityRoles with no value type specified in iterable type array\\.$#"
|
||||
|
|
@ -6719,3 +6644,88 @@ parameters:
|
|||
message: "#^Interface Sylius\\\\Bundle\\\\UserBundle\\\\Provider\\\\UserProviderInterface extends generic interface Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserProviderInterface but does not specify its types\\: TUser$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Provider/UserProviderInterface.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/CancelUnpaidOrdersCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/CheckRequirementsCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/InformAboutGUSCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/InstallAssetsCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/InstallCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/InstallDatabaseCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/InstallSampleDataCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/JwtConfigurationCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/SetupCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/ShowAvailablePluginsCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Command/Model/PluginInfo.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/OrderBundle/Command/RemoveExpiredCartsCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/PromotionBundle/Command/GenerateCouponsCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UiBundle/Command/DebugTemplateEventCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/AbstractRoleCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/DemoteUserCommand.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always false.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/UserBundle/Command/PromoteUserCommand.php
|
||||
|
|
|
|||
3
src/Sylius/Abstraction/StateMachine/.gitignore
vendored
Normal file
3
src/Sylius/Abstraction/StateMachine/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/vendor
|
||||
/composer.lock
|
||||
/.phpunit.result.cache
|
||||
43
src/Sylius/Abstraction/StateMachine/composer.json
Normal file
43
src/Sylius/Abstraction/StateMachine/composer.json
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "sylius/state-machine-abstraction",
|
||||
"description": "Abstraction layer for State Machine used in Sylius bundles",
|
||||
"type": "symfony-bundle",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jacob Tobiasz",
|
||||
"email": "jakub.tobiasz@icloud.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sylius\\Abstraction\\StateMachine\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\Sylius\\Abstraction\\StateMachine\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"symfony/dependency-injection": "^5.4.21 || ^6.0",
|
||||
"symfony/http-kernel": "^5.4.21 || ^6.0",
|
||||
"symfony/workflow": "^5.4.21 || ^6.0",
|
||||
"winzou/state-machine": "^0.4",
|
||||
"winzou/state-machine-bundle": "^0.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"matthiasnoback/symfony-config-test": "^4.2",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"webmozart/assert": "^1.9"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.12-dev"
|
||||
},
|
||||
"symfony": {
|
||||
"require": "^5.4.21"
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/Sylius/Abstraction/StateMachine/config/services.xml
Normal file
41
src/Sylius/Abstraction/StateMachine/config/services.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?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.
|
||||
-->
|
||||
|
||||
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://symfony.com/schema/dic/services"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service id="sylius_abstraction.state_machine.composite" class="Sylius\Abstraction\StateMachine\CompositeStateMachine">
|
||||
<argument type="tagged_iterator" tag="sylius.state_machine" index-by="key" />
|
||||
<argument>%sylius_abstraction.state_machine.default_adapter%</argument>
|
||||
<argument>%sylius_abstraction.state_machine.graphs_to_adapters_mapping%</argument>
|
||||
</service>
|
||||
<service id="Sylius\Abstraction\StateMachine\StateMachineInterface $compositeStateMachine" alias="sylius_abstraction.state_machine.composite" />
|
||||
|
||||
<service id="sylius_abstraction.state_machine" alias="sylius_abstraction.state_machine.composite" />
|
||||
<service id="Sylius\Abstraction\StateMachine\StateMachineInterface" alias="sylius_abstraction.state_machine" />
|
||||
|
||||
<service id="sylius_abstraction.state_machine.adapter.symfony_workflow" class="Sylius\Abstraction\StateMachine\SymfonyWorkflowAdapter">
|
||||
<argument type="service" id="workflow.registry" />
|
||||
|
||||
<tag name="sylius.state_machine" key="symfony_workflow" />
|
||||
</service>
|
||||
<service id="Sylius\Abstraction\StateMachine\StateMachineInterface $symfonyWorkflow" alias="sylius_abstraction.state_machine.adapter.symfony_workflow" />
|
||||
|
||||
<service id="sylius_abstraction.state_machine.adapter.winzou_state_machine" class="Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter">
|
||||
<argument type="service" id="sm.factory" />
|
||||
|
||||
<tag name="sylius.state_machine" key="winzou_state_machine" />
|
||||
</service>
|
||||
<service id="Sylius\Abstraction\StateMachine\StateMachineInterface $winzouStateMachine" alias="sylius_abstraction.state_machine.adapter.winzou_state_machine" />
|
||||
</services>
|
||||
</container>
|
||||
18
src/Sylius/Abstraction/StateMachine/phpunit.xml.dist
Normal file
18
src/Sylius/Abstraction/StateMachine/phpunit.xml.dist
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/9.5/phpunit.xsd"
|
||||
colors="true"
|
||||
convertDeprecationsToExceptions="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="all">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine;
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
use Traversable;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Abstraction\StateMachine\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
final class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('sylius_abstraction');
|
||||
/** @var ArrayNodeDefinition $rootNode */
|
||||
$rootNode = $treeBuilder->getRootNode();
|
||||
|
||||
$rootNode
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->arrayNode('state_machine')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('default_adapter')->defaultValue('winzou_state_machine')->end()
|
||||
->arrayNode('graphs_to_adapters_mapping')
|
||||
->useAttributeAsKey('graph_name')
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Abstraction\StateMachine\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
|
||||
final class SyliusStateMachineAbstractionExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$config = $this->processConfiguration($this->getConfiguration([], $container), $configs);
|
||||
|
||||
$loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__, 2) . '/config/'));
|
||||
$loader->load('services.xml');
|
||||
|
||||
$container->setParameter('sylius_abstraction.state_machine.default_adapter', $config['state_machine']['default_adapter']);
|
||||
$container->setParameter('sylius_abstraction.state_machine.graphs_to_adapters_mapping', $config['state_machine']['graphs_to_adapters_mapping']);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine\Exception;
|
||||
namespace Sylius\Abstraction\StateMachine\Exception;
|
||||
|
||||
class StateMachineExecutionException extends \Exception
|
||||
{
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine;
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException;
|
||||
use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException;
|
||||
|
||||
interface StateMachineInterface
|
||||
{
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class SyliusStateMachineAbstractionBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine;
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException;
|
||||
use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException;
|
||||
use Symfony\Component\Workflow\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\Transition as SymfonyWorkflowTransition;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine;
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
final class Transition implements TransitionInterface
|
||||
{
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine;
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
interface TransitionInterface
|
||||
{
|
||||
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\StateMachine;
|
||||
namespace Sylius\Abstraction\StateMachine;
|
||||
|
||||
use SM\Factory\FactoryInterface;
|
||||
use SM\SMException;
|
||||
use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException;
|
||||
use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException;
|
||||
|
||||
final class WinzouStateMachineAdapter implements StateMachineInterface
|
||||
{
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Abstraction\StateMachine\Functional\DependencyInjection;
|
||||
|
||||
use Matthias\SymfonyConfigTest\PhpUnit\ConfigurationTestCaseTrait;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Abstraction\StateMachine\DependencyInjection\Configuration;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
final class ConfigurationTest extends TestCase
|
||||
{
|
||||
use ConfigurationTestCaseTrait;
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_configure_a_default_state_machine_adapter(): void
|
||||
{
|
||||
$this->assertProcessedConfigurationEquals(
|
||||
[
|
||||
[
|
||||
'state_machine' => [
|
||||
'default_adapter' => 'symfony_workflow',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'state_machine' => [
|
||||
'default_adapter' => 'symfony_workflow',
|
||||
'graphs_to_adapters_mapping' => [],
|
||||
],
|
||||
],
|
||||
'state_machine',
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_configure_the_state_machines_adapters_mapping(): void
|
||||
{
|
||||
$this->assertProcessedConfigurationEquals(
|
||||
[
|
||||
[
|
||||
'state_machine' => [
|
||||
'graphs_to_adapters_mapping' => [
|
||||
'order' => 'symfony_workflow',
|
||||
'payment' => 'winzou_state_machine',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'state_machine' => [
|
||||
'default_adapter' => 'winzou_state_machine',
|
||||
'graphs_to_adapters_mapping' => [
|
||||
'order' => 'symfony_workflow',
|
||||
'payment' => 'winzou_state_machine',
|
||||
],
|
||||
],
|
||||
],
|
||||
'state_machine',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): ConfigurationInterface
|
||||
{
|
||||
return new Configuration();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Abstraction\StateMachine\Unit;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Abstraction\StateMachine\CompositeStateMachine;
|
||||
use Sylius\Abstraction\StateMachine\StateMachineInterface;
|
||||
use Webmozart\Assert\InvalidArgumentException;
|
||||
|
||||
final class CompositeStateMachineTest extends TestCase
|
||||
{
|
||||
/** @var StateMachineInterface&MockObject */
|
||||
private StateMachineInterface $someStateMachineAdapter;
|
||||
|
||||
/** @var StateMachineInterface&MockObject */
|
||||
private StateMachineInterface $anotherStateMachineAdapter;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->someStateMachineAdapter = $this->createMock(StateMachineInterface::class);
|
||||
$this->anotherStateMachineAdapter = $this->createMock(StateMachineInterface::class);
|
||||
}
|
||||
|
||||
public function testItThrowsAnExceptionIfNoStateMachineAdapterIsProvided(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('At least one state machine adapter should be provided.');
|
||||
|
||||
$this->createTestSubject(stateMachineAdapters: []);
|
||||
}
|
||||
|
||||
public function testItThrowsAnExceptionIfStateMachineAdapterDoesNotImplementTheInterface(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('All state machine adapters should implement the "Sylius\Abstraction\StateMachine\StateMachineInterface" interface.');
|
||||
|
||||
$this->createTestSubject(stateMachineAdapters: [$this->createMock(\stdClass::class)]);
|
||||
}
|
||||
|
||||
public function testItUsesMappedAdapterToUseWhetherTransitionCanBeApplied(): void
|
||||
{
|
||||
$this->someStateMachineAdapter->expects($this->never())->method('can');
|
||||
$this->anotherStateMachineAdapter->expects($this->once())->method('can')->willReturn(true);
|
||||
|
||||
$stateMachine = $this->createTestSubject();
|
||||
$this->assertTrue($stateMachine->can(new \stdClass(), 'another_graph', 'some_transition'));
|
||||
}
|
||||
|
||||
public function testItAppliesTransitionUsingMappedAdapter(): void
|
||||
{
|
||||
$this->someStateMachineAdapter->expects($this->once())->method('apply');
|
||||
$this->anotherStateMachineAdapter->expects($this->never())->method('apply');
|
||||
|
||||
$stateMachine = $this->createTestSubject();
|
||||
$stateMachine->apply(new \stdClass(), 'some_graph', 'some_transition');
|
||||
}
|
||||
|
||||
public function testItReturnsEnabledTransitionsUsingMappedAdapter(): void
|
||||
{
|
||||
$this->someStateMachineAdapter->expects($this->never())->method('getEnabledTransitions');
|
||||
$this->anotherStateMachineAdapter->expects($this->once())->method('getEnabledTransitions')->willReturn(['some_transition']);
|
||||
|
||||
$stateMachine = $this->createTestSubject();
|
||||
$this->assertSame(['some_transition'], $stateMachine->getEnabledTransitions(new \stdClass(), 'another_graph'));
|
||||
}
|
||||
|
||||
private function createTestSubject(mixed ...$arguments): StateMachineInterface
|
||||
{
|
||||
return new CompositeStateMachine(...array_replace([
|
||||
'stateMachineAdapters' => ['some_adapter' => $this->someStateMachineAdapter, 'another_adapter' => $this->anotherStateMachineAdapter],
|
||||
'defaultAdapter' => 'some_adapter',
|
||||
'graphsToAdaptersMapping' => ['some_graph' => 'some_adapter', 'another_graph' => 'another_adapter'],
|
||||
], $arguments));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Abstraction\StateMachine\Unit;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException;
|
||||
use Sylius\Abstraction\StateMachine\StateMachineInterface;
|
||||
use Sylius\Abstraction\StateMachine\SymfonyWorkflowAdapter;
|
||||
use Symfony\Component\Workflow\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\Transition as SymfonyWorkflowTransition;
|
||||
use Symfony\Component\Workflow\Workflow;
|
||||
|
||||
final class SymfonyWorkflowAdapterTest extends TestCase
|
||||
{
|
||||
/** @var Registry&MockObject */
|
||||
private Registry $symfonyWorkflowRegistry;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->symfonyWorkflowRegistry = $this->createMock(Registry::class);
|
||||
}
|
||||
|
||||
public function testItReturnWhetherTransitionCanBeApplied(): void
|
||||
{
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$workflow = $this->createMock(Workflow::class);
|
||||
$workflow
|
||||
->expects($this->once())
|
||||
->method('can')
|
||||
->with($subject, $transition)
|
||||
->willReturn(true)
|
||||
;
|
||||
|
||||
$this->symfonyWorkflowRegistry
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with($subject, $graphName)
|
||||
->willReturn($workflow)
|
||||
;
|
||||
|
||||
$this->createTestSubject()->can($subject, $graphName, $transition);
|
||||
}
|
||||
|
||||
public function testItAppliesTransition(): void
|
||||
{
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
/** @var array<string, mixed> $context */
|
||||
$context = ['context'];
|
||||
|
||||
$workflow = $this->createMock(Workflow::class);
|
||||
$workflow
|
||||
->expects($this->once())
|
||||
->method('apply')
|
||||
->with($subject, $transition, $context)
|
||||
;
|
||||
|
||||
$this->symfonyWorkflowRegistry
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with($subject, $graphName)
|
||||
->willReturn($workflow)
|
||||
;
|
||||
|
||||
$this->createTestSubject()->apply($subject, $graphName, $transition, $context);
|
||||
}
|
||||
|
||||
public function testItReturnsEnabledTransitions(): void
|
||||
{
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
|
||||
$workflow = $this->createMock(Workflow::class);
|
||||
$workflow
|
||||
->expects($this->once())
|
||||
->method('getEnabledTransitions')
|
||||
->with($subject)
|
||||
->willReturn([$this->createSampleTransition()])
|
||||
;
|
||||
|
||||
$this->symfonyWorkflowRegistry
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with($subject, $graphName)
|
||||
->willReturn($workflow)
|
||||
;
|
||||
|
||||
$enabledTransition = $this->createTestSubject()->getEnabledTransitions($subject, $graphName);
|
||||
|
||||
$this->assertCount(1, $enabledTransition);
|
||||
$this->assertSame('transition', $enabledTransition[0]->getName());
|
||||
$this->assertSame(['from'], $enabledTransition[0]->getFroms());
|
||||
$this->assertSame(['to'], $enabledTransition[0]->getTos());
|
||||
}
|
||||
|
||||
public function testItConvertsWorkflowExceptionsToCustomOnesOnCan(): void
|
||||
{
|
||||
$this->expectException(StateMachineExecutionException::class);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$this->symfonyWorkflowRegistry->method('get')->willThrowException(new InvalidArgumentException());
|
||||
|
||||
$this->createTestSubject()->can($subject, $graphName, $transition);
|
||||
}
|
||||
|
||||
public function testItConvertsWorkflowExceptionsToCustomOnApply(): void
|
||||
{
|
||||
$this->expectException(StateMachineExecutionException::class);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$this->symfonyWorkflowRegistry->method('get')->willThrowException(new InvalidArgumentException());
|
||||
|
||||
$this->createTestSubject()->apply($subject, $graphName, $transition);
|
||||
}
|
||||
|
||||
public function testItConvertsWorkflowExceptionsToCustomOnGetEnabledTransitions(): void
|
||||
{
|
||||
$this->expectException(StateMachineExecutionException::class);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
|
||||
$this->symfonyWorkflowRegistry->method('get')->willThrowException(new InvalidArgumentException());
|
||||
|
||||
$this->createTestSubject()->getEnabledTransitions($subject, $graphName);
|
||||
}
|
||||
|
||||
private function createTestSubject(): StateMachineInterface
|
||||
{
|
||||
return new SymfonyWorkflowAdapter($this->symfonyWorkflowRegistry);
|
||||
}
|
||||
|
||||
private function createSampleTransition(): SymfonyWorkflowTransition
|
||||
{
|
||||
return new SymfonyWorkflowTransition('transition', ['from'], ['to']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Abstraction\StateMachine\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Abstraction\StateMachine\Transition;
|
||||
|
||||
final class TransitionTest extends TestCase
|
||||
{
|
||||
public function testItReturnsItsName(): void
|
||||
{
|
||||
$this->assertSame('name', $this->createTestSubject()->getName());
|
||||
}
|
||||
|
||||
public function testItReturnsItsFroms(): void
|
||||
{
|
||||
$this->assertSame(['from'], $this->createTestSubject()->getFroms());
|
||||
}
|
||||
|
||||
public function testItReturnsItsTos(): void
|
||||
{
|
||||
$this->assertSame(['to'], $this->createTestSubject()->getTos());
|
||||
}
|
||||
|
||||
private function createTestSubject(): Transition
|
||||
{
|
||||
return new Transition('name', ['from'], ['to']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Abstraction\StateMachine\Unit;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SM\Factory\FactoryInterface;
|
||||
use SM\SMException;
|
||||
use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface;
|
||||
use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException;
|
||||
use Sylius\Abstraction\StateMachine\StateMachineInterface;
|
||||
use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter;
|
||||
|
||||
final class WinzouStateMachineAdapterTest extends TestCase
|
||||
{
|
||||
/** @var FactoryInterface&MockObject */
|
||||
private FactoryInterface $winzouStateMachineFactory;
|
||||
|
||||
/** @var WinzouStateMachineInterface&MockObject */
|
||||
private WinzouStateMachineInterface $winzouStateMachine;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->winzouStateMachine = $this->createMock(WinzouStateMachineInterface::class);
|
||||
|
||||
$this->winzouStateMachineFactory = $this->createMock(FactoryInterface::class);
|
||||
$this->winzouStateMachineFactory
|
||||
->method('get')
|
||||
->willReturn($this->winzouStateMachine)
|
||||
;
|
||||
}
|
||||
|
||||
public function testItReturnWhetherTransitionCanBeApplied(): void
|
||||
{
|
||||
$this->winzouStateMachine->method('can')->willReturn(true);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$this->assertTrue($this->createTestSubject()->can($subject, $graphName, $transition));
|
||||
}
|
||||
|
||||
public function testItAppliesTransition(): void
|
||||
{
|
||||
$this->winzouStateMachine->expects($this->once())->method('apply');
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$this->createTestSubject()->apply($subject, $graphName, $transition);
|
||||
}
|
||||
|
||||
public function testItReturnsEnabledTransitions(): void
|
||||
{
|
||||
$this->winzouStateMachine->method('getPossibleTransitions')->willReturn(['transition']);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
|
||||
$transitions = $this->createTestSubject()->getEnabledTransitions($subject, $graphName);
|
||||
|
||||
$this->assertCount(1, $transitions);
|
||||
$this->assertSame('transition', $transitions[0]->getName());
|
||||
$this->assertNull($transitions[0]->getFroms());
|
||||
$this->assertNull($transitions[0]->getTos());
|
||||
}
|
||||
|
||||
public function testItConvertsWorkflowExceptionsToCustomOnesOnCan(): void
|
||||
{
|
||||
$this->expectException(StateMachineExecutionException::class);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$this->winzouStateMachineFactory->method('get')->willThrowException(new SMException());
|
||||
|
||||
$this->createTestSubject()->can($subject, $graphName, $transition);
|
||||
}
|
||||
|
||||
public function testItConvertsWorkflowExceptionsToCustomOnApply(): void
|
||||
{
|
||||
$this->expectException(StateMachineExecutionException::class);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
$transition = 'transition';
|
||||
|
||||
$this->winzouStateMachineFactory->method('get')->willThrowException(new SMException());
|
||||
|
||||
$this->createTestSubject()->apply($subject, $graphName, $transition);
|
||||
}
|
||||
|
||||
public function testItConvertsWorkflowExceptionsToCustomOnGetEnabledTransitions(): void
|
||||
{
|
||||
$this->expectException(StateMachineExecutionException::class);
|
||||
|
||||
$subject = new \stdClass();
|
||||
$graphName = 'graph_name';
|
||||
|
||||
$this->winzouStateMachineFactory->method('get')->willThrowException(new SMException());
|
||||
|
||||
$this->createTestSubject()->getEnabledTransitions($subject, $graphName);
|
||||
}
|
||||
|
||||
private function createTestSubject(): StateMachineInterface
|
||||
{
|
||||
return new WinzouStateMachineAdapter($this->winzouStateMachineFactory);
|
||||
}
|
||||
}
|
||||
157
src/Sylius/Behat/Context/Api/Admin/DashboardContext.php
Normal file
157
src/Sylius/Behat/Context/Api/Admin/DashboardContext.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Context\Api\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Calendar\Provider\DateTimeProviderInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class DashboardContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private DateTimeProviderInterface $dateTimeProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I view statistics
|
||||
*/
|
||||
public function iBrowseStatistics(): void
|
||||
{
|
||||
$this->client->index('statistics');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I view statistics for :channel channel and current year split by month
|
||||
* @When I choose :channel channel
|
||||
* @When I view statistics for :channel channel
|
||||
*/
|
||||
public function iViewStatisticsForChannelAndYear(ChannelInterface $channel): void
|
||||
{
|
||||
$this->client->index(
|
||||
'statistics',
|
||||
[
|
||||
'channelCode' => $channel->getCode(),
|
||||
'startDate' => $this->dateTimeProvider->now()->format('Y-01-01\T00:00:00'),
|
||||
'dateInterval' => 'P1M',
|
||||
'endDate' => $this->dateTimeProvider->now()->format('Y-12-31\T23:59:59'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I view statistics for :channel channel and previous year split by month
|
||||
*/
|
||||
public function iViewStatisticsForChannelAndPreviousYear(ChannelInterface $channel): void
|
||||
{
|
||||
$currentYear = (int) $this->dateTimeProvider->now()->format('Y');
|
||||
|
||||
$this->client->index(
|
||||
'statistics',
|
||||
[
|
||||
'channelCode' => $channel->getCode(),
|
||||
'startDate' => ($currentYear - 1) . '-01-01T00:00:00',
|
||||
'dateInterval' => 'P1M',
|
||||
'endDate' => ($currentYear - 1) . '-12-31T23:59:59',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I view statistics for :channel channel and next year split by month
|
||||
*/
|
||||
public function iViewStatisticsForChannelAndNextYear(ChannelInterface $channel): void
|
||||
{
|
||||
$currentYear = (int) $this->dateTimeProvider->now()->format('Y');
|
||||
|
||||
$this->client->index(
|
||||
'statistics',
|
||||
[
|
||||
'channelCode' => $channel->getCode(),
|
||||
'startDate' => ($currentYear + 1) . '-01-01T00:00:00',
|
||||
'dateInterval' => 'P1M',
|
||||
'endDate' => ($currentYear + 1) . '-12-31T23:59:59',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see :count paid orders
|
||||
*/
|
||||
public function iShouldSeeNewOrders(int $count): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->hasValuesInSubresourceObject(
|
||||
$this->client->getLastResponse(),
|
||||
'businessActivitySummary',
|
||||
['paidOrdersCount' => $count],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see :number new customers( in the list)
|
||||
*/
|
||||
public function iShouldSeeNewCustomers(int $count): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->hasValuesInSubresourceObject(
|
||||
$this->client->getLastResponse(),
|
||||
'businessActivitySummary',
|
||||
['newCustomersCount' => $count],
|
||||
),
|
||||
sprintf(
|
||||
'There should be %s new customers, but got %s.',
|
||||
$count,
|
||||
json_encode(
|
||||
$this->responseChecker->getValue($this->client->getLastResponse(), 'businessActivitySummary'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^there should be total sales of ("[^"]+")$/
|
||||
*/
|
||||
public function thereShouldBeTotalSalesOf(int $totalSales): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->hasValuesInSubresourceObject(
|
||||
$this->client->getLastResponse(),
|
||||
'businessActivitySummary',
|
||||
['totalSales' => $totalSales],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the average order value should be ("[^"]+")$/
|
||||
*/
|
||||
public function myAverageOrderValueShouldBe(int $averageTotalValue): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->hasValuesInSubresourceObject(
|
||||
$this->client->getLastResponse(),
|
||||
'businessActivitySummary',
|
||||
['averageOrderValue' => $averageTotalValue],
|
||||
),
|
||||
sprintf('Average order value should be %s, but it does not.', $averageTotalValue),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,8 +18,10 @@ 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\Addressing\Model\CountryInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Customer\Model\CustomerGroupInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
@ -31,6 +33,7 @@ final class ManagingCustomersContext implements Context
|
|||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private IriConverterInterface $iriConverter,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +213,25 @@ final class ManagingCustomersContext implements Context
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I change the password of user :customer to :newPassword
|
||||
*/
|
||||
public function iChangeThePasswordOfUserTo(CustomerInterface $customer, string $newPassword): void
|
||||
{
|
||||
$this->iWantToEditThisCustomer($customer);
|
||||
$this->iSpecifyItsPasswordAs($newPassword);
|
||||
$this->client->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I delete the account of :shopUser user
|
||||
*/
|
||||
public function iDeleteAccount(ShopUserInterface $shopUser): void
|
||||
{
|
||||
$this->sharedStorage->set('customer', $shopUser->getCustomer());
|
||||
$this->client->delete(sprintf('customer/%s', $shopUser->getCustomer()->getId()), 'user');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I do not specify any information
|
||||
* @When I do not choose create account option
|
||||
|
|
@ -561,6 +583,20 @@ final class ManagingCustomersContext implements Context
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the customer with this email should still exist
|
||||
*/
|
||||
public function customerShouldStillExist(): void
|
||||
{
|
||||
/** @var CustomerInterface $customer */
|
||||
$customer = $this->sharedStorage->get('customer');
|
||||
|
||||
$this->client->show(Resources::CUSTOMERS, (string) $customer->getId());
|
||||
|
||||
Assert::same($this->client->getLastResponse()->getStatusCode(), 200);
|
||||
Assert::same($this->responseChecker->getValue($this->client->getLastResponse(), 'email'), $customer->getEmail());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not see create account option
|
||||
* @Then I should still be on the customer creation page
|
||||
|
|
@ -572,4 +608,28 @@ final class ManagingCustomersContext implements Context
|
|||
public function intentionallyLeftBlank(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the user account should be deleted
|
||||
*/
|
||||
public function accountShouldBeDeleted(): void
|
||||
{
|
||||
/** @var CustomerInterface $customer */
|
||||
$customer = $this->sharedStorage->get('customer');
|
||||
|
||||
$response = $this->client->show(Resources::CUSTOMERS, (string) $customer->getId());
|
||||
|
||||
Assert::null($this->responseChecker->getValue($response, 'user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not be able to delete it again
|
||||
*/
|
||||
public function iShouldNotBeAbleToDeleteCustomerAgain(): void
|
||||
{
|
||||
$customer = $this->sharedStorage->get('customer');
|
||||
$this->client->delete(sprintf('customer/%s', $customer->getId()), 'user');
|
||||
|
||||
Assert::same($this->client->getLastResponse()->getStatusCode(), 404);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ use Sylius\Component\Currency\Model\CurrencyInterface;
|
|||
use Sylius\Component\Order\OrderTransitions;
|
||||
use Sylius\Component\Payment\PaymentTransitions;
|
||||
use Sylius\Component\Shipping\ShipmentTransitions;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Intl\Countries;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
|
@ -45,7 +46,6 @@ final class ManagingOrdersContext implements Context
|
|||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private RequestFactoryInterface $requestFactory,
|
||||
private IriConverterInterface $iriConverter,
|
||||
private SecurityServiceInterface $adminSecurityService,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
|
|
@ -139,6 +139,19 @@ final class ManagingOrdersContext implements Context
|
|||
$this->client->addFilter('checkoutCompletedAt[before]', $dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I resend the order confirmation email
|
||||
*/
|
||||
public function iResendTheOrderConfirmationEmail(): void
|
||||
{
|
||||
$this->client->customItemAction(
|
||||
Resources::ORDERS,
|
||||
$this->sharedStorage->get('order')->getTokenValue(),
|
||||
HttpRequest::METHOD_POST,
|
||||
'resend-confirmation-email',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I filter by product :productName
|
||||
* @When I filter by products :firstProduct and :secondProduct
|
||||
|
|
@ -312,6 +325,14 @@ final class ManagingOrdersContext implements Context
|
|||
Assert::same($this->responseChecker->countCollectionItems($this->client->getLastResponse()), $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the order confirmation email has been successfully resent to the customer
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheOrderConfirmationEmailHasBeenSuccessfullyResentToTheCustomer(): void
|
||||
{
|
||||
$this->responseChecker->isCreationSuccessful($this->client->getLastResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully updated
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ final class ManagingProductVariantsContext implements Context
|
|||
/**
|
||||
* @When I do not set its price
|
||||
* @When I do not specify its code
|
||||
* @When I do not set its :optionName option
|
||||
* @When I do not set its :firstOptionName and :secondOptionName options
|
||||
*/
|
||||
public function iDoNotSetValue(): void
|
||||
{
|
||||
|
|
@ -723,6 +725,17 @@ final class ManagingProductVariantsContext implements Context
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that required options have not been configured
|
||||
*/
|
||||
public function iShouldBeNotifiedThatRequiredOptionsHaveNotBeenConfigured(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'The product variant must have configured values for all options chosen on the product.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that on hand quantity must be greater than the number of on hold units
|
||||
*/
|
||||
|
|
|
|||
98
src/Sylius/Behat/Context/Cli/ChangeAdminPasswordContext.php
Normal file
98
src/Sylius/Behat/Context/Cli/ChangeAdminPasswordContext.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Context\Cli;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Sylius\Component\User\Security\UserPasswordHasherInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ChangeAdminPasswordContext implements Context
|
||||
{
|
||||
private const ADMIN_USER_CHANGE_PASSWORD = 'sylius:admin-user:change-password';
|
||||
|
||||
private Application $application;
|
||||
|
||||
private ?CommandTester $commandTester = null;
|
||||
|
||||
private $input = [];
|
||||
|
||||
/** @param UserRepositoryInterface<AdminUserInterface> $adminUserRepository */
|
||||
public function __construct(
|
||||
KernelInterface $kernel,
|
||||
private UserRepositoryInterface $adminUserRepository,
|
||||
private UserPasswordHasherInterface $userPasswordHasher,
|
||||
) {
|
||||
$this->application = new Application($kernel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to change password
|
||||
*/
|
||||
public function iWantToChangePassword(): void
|
||||
{
|
||||
$command = $this->application->find(self::ADMIN_USER_CHANGE_PASSWORD);
|
||||
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify email as :email
|
||||
*/
|
||||
public function iSpecifyEmailAs(string $email = ''): void
|
||||
{
|
||||
$this->input['email'] = $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify my new password as :password
|
||||
*/
|
||||
public function iSpecifyMyNewPassword(string $password = ''): void
|
||||
{
|
||||
$this->input['password'] = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I run command
|
||||
*/
|
||||
public function iRunCommand(): void
|
||||
{
|
||||
$this->commandTester->setInputs($this->input);
|
||||
$this->commandTester->execute(['command' => self::ADMIN_USER_CHANGE_PASSWORD]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be informed that password has been changed successfully
|
||||
*/
|
||||
public function iShouldBeInformedThatPasswordHasBeenChangedSuccessfully(): void
|
||||
{
|
||||
Assert::contains($this->commandTester->getDisplay(), 'Admin user password has been changed successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be able to log in as :email authenticated by :password password
|
||||
*/
|
||||
public function iShouldBeAbleToLoginWithEmailAndPassword(string $email = '', string $password = ''): void
|
||||
{
|
||||
/** @var AdminUserInterface|null $adminUser */
|
||||
$adminUser = $this->adminUserRepository->findOneByEmail($email);
|
||||
$adminUser->setPlainPassword($password);
|
||||
|
||||
Assert::same($adminUser->getPassword(), $this->userPasswordHasher->hash($adminUser));
|
||||
}
|
||||
}
|
||||
38
src/Sylius/Behat/Context/Transform/ShopUserContext.php
Normal file
38
src/Sylius/Behat/Context/Transform/ShopUserContext.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Context\Transform;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ShopUserContext implements Context
|
||||
{
|
||||
public function __construct(private UserRepositoryInterface $shopUserRepository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Transform :shopUser
|
||||
*/
|
||||
public function getShopUserByEmail(string $email): ShopUserInterface
|
||||
{
|
||||
$shopUser = $this->shopUserRepository->findOneByEmail($email);
|
||||
|
||||
Assert::notNull($shopUser, sprintf('Shop User with email "%s" does not exist', $email));
|
||||
|
||||
return $shopUser;
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ final class DashboardContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should see :number new orders
|
||||
* @Then I should see :number paid orders
|
||||
*/
|
||||
public function iShouldSeeNewOrders(int $number): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -222,7 +222,6 @@
|
|||
<service id="sylius.behat.context.api.admin.managing_orders" class="Sylius\Behat\Context\Api\Admin\ManagingOrdersContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.request_factory" />
|
||||
<argument type="service" id="api_platform.iri_converter" />
|
||||
<argument type="service" id="sylius.behat.api_security" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
|
|
@ -303,6 +302,7 @@
|
|||
<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.shared_storage" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.admin.managing_placed_order_addresses" class="Sylius\Behat\Context\Api\Admin\ManagingPlacedOrderAddressesContext">
|
||||
|
|
@ -317,5 +317,11 @@
|
|||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.section_iri_converter" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Behat\Context\Api\Admin\DashboardContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="Sylius\Calendar\Provider\DateTimeProviderInterface" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -27,5 +27,11 @@
|
|||
<argument type="service" id="kernel" />
|
||||
<argument type="service" id="sylius.repository.order" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.cli.change_admin_password" class="Sylius\Behat\Context\Cli\ChangeAdminPasswordContext">
|
||||
<argument type="service" id="kernel" />
|
||||
<argument type="service" id="sylius.repository.admin_user" />
|
||||
<argument type="service" id="sylius.security.password_hasher" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -179,5 +179,9 @@
|
|||
<argument type="service" id="sylius.repository.zone" />
|
||||
<argument type="service" id="sylius.repository.zone_member" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.transform.shop_user" class="Sylius\Behat\Context\Transform\ShopUserContext">
|
||||
<argument type="service" id="sylius.repository.shop_user" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ imports:
|
|||
- suites/api/account/login.yml
|
||||
- suites/api/addressing/managing_countries.yml
|
||||
- suites/api/addressing/managing_zones.yml
|
||||
- suites/api/admin/dashboard.yaml
|
||||
- suites/api/admin/login.yml
|
||||
- suites/api/admin/security.yml
|
||||
- suites/api/cart/accessing_cart.yml
|
||||
|
|
@ -68,8 +69,10 @@ imports:
|
|||
- suites/api/user/managing_administrators.yml
|
||||
- suites/api/user/managing_customer_groups.yml
|
||||
- suites/api/user/managing_customers.yml
|
||||
- suites/api/user/managing_users.yml
|
||||
|
||||
- suites/cli/canceling_unpaid_orders.yml
|
||||
- suites/cli/change_admin_password.yml
|
||||
- suites/cli/installer.yml
|
||||
- suites/cli/showing_available_plugins.yml
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Sylius Sp. z o.o.
|
||||
|
||||
default:
|
||||
suites:
|
||||
api_dashboard:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
- Sylius\Calendar\Tests\Behat\Context\Hook\CalendarContext
|
||||
|
||||
- sylius.behat.context.transform.admin
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
- sylius.behat.context.transform.user
|
||||
- Sylius\Calendar\Tests\Behat\Context\Setup\CalendarContext
|
||||
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.currency
|
||||
- sylius.behat.context.setup.geographical
|
||||
- sylius.behat.context.setup.order
|
||||
- sylius.behat.context.setup.payment
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.promotion
|
||||
- sylius.behat.context.setup.shipping
|
||||
- sylius.behat.context.setup.zone
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.customer
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.order
|
||||
- sylius.behat.context.transform.product
|
||||
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- Sylius\Behat\Context\Api\Admin\DashboardContext
|
||||
- sylius.behat.context.api.admin.login
|
||||
|
||||
filters:
|
||||
tags: "@admin_dashboard&&@api"
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Sylius Sp. z o.o.
|
||||
|
||||
default:
|
||||
suites:
|
||||
api_managing_users:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.customer
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
- sylius.behat.context.transform.shop_user
|
||||
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.customer
|
||||
- sylius.behat.context.setup.user
|
||||
|
||||
- sylius.behat.context.api.admin.managing_customers
|
||||
- sylius.behat.context.api.admin.response
|
||||
|
||||
- sylius.behat.context.api.shop.login
|
||||
|
||||
filters:
|
||||
tags: "@managing_users&&@api"
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Sylius Sp. z o.o.
|
||||
|
||||
default:
|
||||
suites:
|
||||
change_admin_password:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.setup.admin_user
|
||||
|
||||
- sylius.behat.context.cli.change_admin_password
|
||||
|
||||
filters:
|
||||
tags: "@change_admin_password&&@cli"
|
||||
|
|
@ -7,7 +7,14 @@ default:
|
|||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
- sylius.behat.context.hook.session
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.customer
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.order
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.admin_security
|
||||
- sylius.behat.context.setup.calendar
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.currency
|
||||
|
|
@ -16,18 +23,9 @@ default:
|
|||
- sylius.behat.context.setup.payment
|
||||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.promotion
|
||||
- sylius.behat.context.setup.admin_security
|
||||
- sylius.behat.context.setup.shipping
|
||||
- sylius.behat.context.setup.zone
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.customer
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.order
|
||||
- sylius.behat.context.transform.product
|
||||
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.ui.admin.dashboard
|
||||
- sylius.behat.context.ui.admin.login
|
||||
- sylius.behat.context.ui.admin.notification
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\AdminBundle\Action;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\EmailManager\OrderEmailManagerInterface;
|
||||
use Sylius\Bundle\CoreBundle\MessageDispatcher\ResendOrderConfirmationEmailDispatcherInterface;
|
||||
use Sylius\Bundle\CoreBundle\Provider\FlashBagProvider;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
|
|
@ -30,7 +30,7 @@ final readonly class ResendOrderConfirmationEmailAction
|
|||
{
|
||||
public function __construct(
|
||||
private OrderRepositoryInterface $orderRepository,
|
||||
private OrderEmailManagerInterface $orderEmailManager,
|
||||
private ResendOrderConfirmationEmailDispatcherInterface $resendOrderConfirmationEmailDispatcher,
|
||||
private CsrfTokenManagerInterface $csrfTokenManager,
|
||||
private RequestStack $requestStack,
|
||||
) {
|
||||
|
|
@ -52,7 +52,7 @@ final readonly class ResendOrderConfirmationEmailAction
|
|||
throw new NotFoundHttpException(sprintf('The order with id %s has not been found', $orderId));
|
||||
}
|
||||
|
||||
$this->orderEmailManager->sendConfirmationEmail($order);
|
||||
$this->resendOrderConfirmationEmailDispatcher->dispatch($order);
|
||||
|
||||
FlashBagProvider
|
||||
::getFlashBag($this->requestStack)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Console\Command;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactoryInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Sylius\Component\User\Security\PasswordUpdaterInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'sylius:admin-user:change-password',
|
||||
description: 'Change password of admin user',
|
||||
)]
|
||||
final class ChangeAdminUserPasswordCommand extends Command
|
||||
{
|
||||
protected SymfonyStyle $io;
|
||||
|
||||
/** @param UserRepositoryInterface<AdminUserInterface> $adminUserRepository */
|
||||
public function __construct(
|
||||
private UserRepositoryInterface $adminUserRepository,
|
||||
private PasswordUpdaterInterface $passwordUpdater,
|
||||
private QuestionFactoryInterface $questionFactory,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (!$input->isInteractive()) {
|
||||
$this->io->error('This command must be run interactively.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->io->title('Change admin user password');
|
||||
|
||||
$email = $this->io->askQuestion($this->questionFactory->createEmail());
|
||||
|
||||
/** @var AdminUserInterface|null $adminUser */
|
||||
$adminUser = $this->adminUserRepository->findOneByEmail($email);
|
||||
if ($adminUser === null) {
|
||||
$this->io->error(sprintf('Admin user with email address %s not found!', $email));
|
||||
|
||||
return Command::INVALID;
|
||||
}
|
||||
|
||||
$password = $this->io->askQuestion($this->questionFactory->createWithNotNullValidator('New password', true));
|
||||
$adminUser->setPlainPassword($password);
|
||||
|
||||
$this->passwordUpdater->updatePassword($adminUser);
|
||||
$this->adminUserRepository->add($adminUser);
|
||||
|
||||
$this->io->success('Admin user password has been changed successfully.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,15 +11,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Command;
|
||||
namespace Sylius\Bundle\AdminBundle\Console\Command;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\Exception\CreateAdminUserFailedException;
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactoryInterface;
|
||||
use Sylius\Bundle\AdminBundle\Message\CreateAdminUser;
|
||||
use Sylius\Bundle\AdminBundle\Exception\CreateAdminUserFailedException;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Intl\Locales;
|
||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||
|
|
@ -34,10 +34,13 @@ final class CreateAdminUserCommand extends Command
|
|||
{
|
||||
use HandleTrait;
|
||||
|
||||
private SymfonyStyle $io;
|
||||
protected SymfonyStyle $io;
|
||||
|
||||
public function __construct(MessageBusInterface $messageBus, private string $defaultLocaleCode)
|
||||
{
|
||||
public function __construct(
|
||||
MessageBusInterface $messageBus,
|
||||
private string $defaultLocaleCode,
|
||||
private QuestionFactoryInterface $questionFactory,
|
||||
) {
|
||||
$this->messageBus = $messageBus;
|
||||
|
||||
parent::__construct();
|
||||
|
|
@ -85,18 +88,19 @@ final class CreateAdminUserCommand extends Command
|
|||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/** @return array<array-key, mixed> */
|
||||
private function askAdminUserData(): array
|
||||
{
|
||||
$adminUserData = [];
|
||||
|
||||
$adminUserData['email'] = $this->io->askQuestion($this->createEmailQuestion());
|
||||
$adminUserData['email'] = $this->io->askQuestion($this->questionFactory->createEmail());
|
||||
$adminUserData['username'] = $this->io->askQuestion(
|
||||
$this->createQuestionWithNonBlankValidator('Username'),
|
||||
$this->questionFactory->createWithNotNullValidator('Username'),
|
||||
);
|
||||
$adminUserData['first_name'] = $this->io->ask('First name');
|
||||
$adminUserData['last_name'] = $this->io->ask('Last name');
|
||||
$adminUserData['plain_password'] = $this->io->askQuestion(
|
||||
$this->createQuestionWithNonBlankValidator('Password', true),
|
||||
$this->questionFactory->createWithNotNullValidator('Password', true),
|
||||
);
|
||||
|
||||
$localeCodes = Locales::getNames();
|
||||
|
|
@ -107,40 +111,7 @@ final class CreateAdminUserCommand extends Command
|
|||
return $adminUserData;
|
||||
}
|
||||
|
||||
private function createEmailQuestion(): Question
|
||||
{
|
||||
$question = new Question('Email');
|
||||
$question->setValidator(function (?string $email) {
|
||||
if (!filter_var($email, \FILTER_VALIDATE_EMAIL) || $email === null) {
|
||||
throw new \InvalidArgumentException('The email address provided is invalid. Please try again.');
|
||||
}
|
||||
|
||||
return $email;
|
||||
});
|
||||
$question->setMaxAttempts(3);
|
||||
|
||||
return $question;
|
||||
}
|
||||
|
||||
private function createQuestionWithNonBlankValidator(string $askedQuestion, bool $hidden = false): Question
|
||||
{
|
||||
$question = new Question($askedQuestion);
|
||||
$question->setValidator(function (?string $value) {
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException('The value cannot be empty.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
$question->setMaxAttempts(3);
|
||||
|
||||
if ($hidden) {
|
||||
$question->setHidden(true);
|
||||
}
|
||||
|
||||
return $question;
|
||||
}
|
||||
|
||||
/** @param array<array-key, mixed> $adminUserData */
|
||||
private function showSummary(array $adminUserData): void
|
||||
{
|
||||
$this->io->writeln('The following admin user will be created:');
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Console\Command\Factory;
|
||||
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
final class QuestionFactory implements QuestionFactoryInterface
|
||||
{
|
||||
public function createEmail(): Question
|
||||
{
|
||||
$question = new Question('Email');
|
||||
$question->setValidator(function (?string $email) {
|
||||
if ($email === null || !filter_var($email, \FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException('The email address provided is invalid. Please try again.');
|
||||
}
|
||||
|
||||
return $email;
|
||||
});
|
||||
$question->setMaxAttempts(3);
|
||||
|
||||
return $question;
|
||||
}
|
||||
|
||||
public function createWithNotNullValidator(string $askedQuestion, bool $hidden = false): Question
|
||||
{
|
||||
$question = new Question($askedQuestion);
|
||||
$question->setValidator(function (?string $value) {
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException('The value cannot be empty.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
$question->setMaxAttempts(3);
|
||||
$question->setHidden($hidden);
|
||||
|
||||
return $question;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Console\Command\Factory;
|
||||
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
interface QuestionFactoryInterface
|
||||
{
|
||||
public function createEmail(): Question;
|
||||
|
||||
public function createWithNotNullValidator(string $askedQuestion, bool $hidden = false): Question;
|
||||
}
|
||||
|
|
@ -18,6 +18,15 @@ use Sylius\Component\Core\Model\OrderInterface;
|
|||
use Sylius\Component\Mailer\Sender\SenderInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
trigger_deprecation(
|
||||
'sylius/admin-bundle',
|
||||
'1.13',
|
||||
'The "%s" class is deprecated, use "%s" instead.',
|
||||
OrderEmailManager::class,
|
||||
\Sylius\Bundle\CoreBundle\EmailManager\OrderEmailManager::class,
|
||||
);
|
||||
|
||||
/** @deprecated since Sylius 1.13 and will be removed in Sylius 2.0. Use {@see \Sylius\Bundle\CoreBundle\Mailer\OrderEmailManager} instead. */
|
||||
final class OrderEmailManager implements OrderEmailManagerInterface
|
||||
{
|
||||
public function __construct(private SenderInterface $emailSender)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|||
|
||||
final class CreateAdminUserHandler implements MessageHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @param UserRepositoryInterface<AdminUserInterface> $adminUserRepository
|
||||
* @param FactoryInterface<AdminUserInterface> $adminUserFactory
|
||||
* @param array<array-key, string> $validationGroups
|
||||
*/
|
||||
public function __construct(
|
||||
private UserRepositoryInterface $adminUserRepository,
|
||||
private FactoryInterface $adminUserFactory,
|
||||
|
|
@ -65,6 +70,7 @@ final class CreateAdminUserHandler implements MessageHandlerInterface
|
|||
return $adminUser;
|
||||
}
|
||||
|
||||
/** @return iterable<string> */
|
||||
private function getViolationMessages(ConstraintViolationListInterface $constraintViolationList): iterable
|
||||
{
|
||||
foreach ($constraintViolationList as $violation) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\AdminBundle\Provider;
|
||||
|
||||
use Sylius\Bundle\MoneyBundle\Formatter\MoneyFormatterInterface;
|
||||
use Sylius\Component\Core\Dashboard\DashboardStatistics;
|
||||
use Sylius\Component\Core\Dashboard\DashboardStatisticsProviderInterface;
|
||||
use Sylius\Component\Core\Dashboard\Interval;
|
||||
use Sylius\Component\Core\Dashboard\SalesDataProviderInterface;
|
||||
|
|
@ -31,7 +30,6 @@ class StatisticsDataProvider implements StatisticsDataProviderInterface
|
|||
|
||||
public function getRawData(ChannelInterface $channel, \DateTimeInterface $startDate, \DateTimeInterface $endDate, string $interval): array
|
||||
{
|
||||
/** @var DashboardStatistics $statistics */
|
||||
$statistics = $this->statisticsProvider->getStatisticsForChannelInPeriod($channel, $startDate, $endDate);
|
||||
|
||||
$salesSummary = $this->salesDataProvider->getSalesSummary(
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@ use Sylius\Component\Core\Model\ChannelInterface;
|
|||
|
||||
interface StatisticsDataProviderInterface
|
||||
{
|
||||
/** @return array<array-key, array<array-key, mixed>> */
|
||||
public function getRawData(ChannelInterface $channel, \DateTimeInterface $startDate, \DateTimeInterface $endDate, string $interval): array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,17 @@
|
|||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Command\CreateAdminUserCommand">
|
||||
<service id="Sylius\Bundle\AdminBundle\Console\Command\CreateAdminUserCommand">
|
||||
<argument type="service" id="sylius.command_bus" />
|
||||
<argument>%sylius_locale.locale%</argument>
|
||||
<argument type="service" id="sylius.console.command_factory.question" />
|
||||
<tag name="console.command" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Console\Command\ChangeAdminUserPasswordCommand">
|
||||
<argument type="service" id="sylius.repository.admin_user" />
|
||||
<argument type="service" id="sylius.security.password_updater" />
|
||||
<argument type="service" id="sylius.console.command_factory.question" />
|
||||
<tag name="console.command" />
|
||||
</service>
|
||||
|
||||
|
|
@ -51,6 +59,10 @@
|
|||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.console.command_factory.question" class="Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactory" />
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactoryInterface" alias="sylius.console.command_factory.question" />
|
||||
|
||||
<service id="sylius.context.locale.admin_based" class="Sylius\Bundle\AdminBundle\Context\AdminBasedLocaleContext">
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<tag name="sylius.context.locale" priority="128" />
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
<service id="Sylius\Bundle\AdminBundle\Action\ResendOrderConfirmationEmailAction" public="true">
|
||||
<argument type='service' id="sylius.repository.order" />
|
||||
<argument type="service" id="Sylius\Bundle\AdminBundle\EmailManager\OrderEmailManagerInterface" />
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\MessageDispatcher\ResendOrderConfirmationEmailDispatcherInterface" />
|
||||
<argument type="service" id="security.csrf.token_manager" />
|
||||
<argument type="service" id="request_stack" />
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Tests\Console\Command;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\ChangeAdminUserPasswordCommand;
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactoryInterface;
|
||||
use Sylius\Component\Core\Model\AdminUser;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Sylius\Component\User\Security\PasswordUpdaterInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
final class ChangeAdminUserPasswordCommandTest extends TestCase
|
||||
{
|
||||
private const EMAIL = 'sylius@example.com';
|
||||
|
||||
private const PASSWORD = 'Password';
|
||||
|
||||
private CommandTester $command;
|
||||
|
||||
private UserRepositoryInterface $userRepository;
|
||||
|
||||
private PasswordUpdaterInterface $passwordUpdater;
|
||||
|
||||
private QuestionFactoryInterface $questionFactory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
|
||||
$this->passwordUpdater = $this->createMock(PasswordUpdaterInterface::class);
|
||||
$this->questionFactory = $this->createMock(QuestionFactoryInterface::class);
|
||||
|
||||
$this->command = new CommandTester(
|
||||
new ChangeAdminUserPasswordCommand($this->userRepository, $this->passwordUpdater, $this->questionFactory),
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_execute_in_non_interactive_mode(): void
|
||||
{
|
||||
$this->command->execute([], ['interactive' => false]);
|
||||
|
||||
self::assertSame(Command::FAILURE, $this->command->getStatusCode());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_change_password_when_admin_user_is_not_found(): void
|
||||
{
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->once())
|
||||
->method('createEmail')
|
||||
->willReturn($this->createQuestionMock('Email'));
|
||||
|
||||
$this
|
||||
->userRepository
|
||||
->expects($this->once())
|
||||
->method('findOneByEmail')
|
||||
->willReturn(null);
|
||||
|
||||
$this
|
||||
->command
|
||||
->setInputs([
|
||||
'email' => self::EMAIL,
|
||||
]);
|
||||
|
||||
$this->command->execute([]);
|
||||
|
||||
self::assertSame(Command::INVALID, $this->command->getStatusCode());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_changes_password_for_existing_admin_user(): void
|
||||
{
|
||||
$adminUser = $this->createMock(AdminUser::class);
|
||||
$adminUser
|
||||
->expects($this->once())
|
||||
->method('setPlainPassword')
|
||||
->with(self::PASSWORD);
|
||||
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->once())
|
||||
->method('createEmail')
|
||||
->willReturn($this->createQuestionMock('Email'));
|
||||
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->once())
|
||||
->method('createWithNotNullValidator')
|
||||
->with('New password', true)
|
||||
->willReturn($this->createQuestionMock('New password'));
|
||||
|
||||
$this
|
||||
->userRepository
|
||||
->expects($this->once())
|
||||
->method('findOneByEmail')
|
||||
->willReturn($adminUser);
|
||||
$this
|
||||
->userRepository
|
||||
->expects($this->once())
|
||||
->method('add')
|
||||
->with($adminUser);
|
||||
|
||||
$this
|
||||
->passwordUpdater
|
||||
->expects($this->once())
|
||||
->method('updatePassword')
|
||||
->with($adminUser);
|
||||
|
||||
$this
|
||||
->command
|
||||
->setInputs([
|
||||
'email' => self::EMAIL,
|
||||
'password' => self::PASSWORD,
|
||||
]);
|
||||
|
||||
$this->command->execute([]);
|
||||
|
||||
self::assertSame(Command::SUCCESS, $this->command->getStatusCode());
|
||||
}
|
||||
|
||||
private function createQuestionMock(string $askedQuestion): MockObject
|
||||
{
|
||||
$question = $this->createMock(Question::class);
|
||||
$question
|
||||
->method('isTrimmable')
|
||||
->willReturn(true);
|
||||
$question
|
||||
->method('getQuestion')
|
||||
->willReturn($askedQuestion);
|
||||
|
||||
return $question;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,14 +11,16 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Tests\Command;
|
||||
namespace Sylius\Bundle\AdminBundle\Tests\Console\Command;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\AdminBundle\Command\CreateAdminUserCommand;
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactoryInterface;
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\CreateAdminUserCommand;
|
||||
use Sylius\Bundle\AdminBundle\Exception\CreateAdminUserFailedException;
|
||||
use Sylius\Bundle\AdminBundle\Message\CreateAdminUser;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||
|
|
@ -47,83 +49,52 @@ final class CreateAdminUserCommandTest extends TestCase
|
|||
|
||||
private MockObject $messageBus;
|
||||
|
||||
private QuestionFactoryInterface $questionFactory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->messageBus = $this->createMock(MessageBusInterface::class);
|
||||
$this->questionFactory = $this->createMock(QuestionFactoryInterface::class);
|
||||
|
||||
$this->command = new CommandTester(
|
||||
new CreateAdminUserCommand($this->messageBus, self::LOCALE_CODE),
|
||||
new CreateAdminUserCommand($this->messageBus, self::LOCALE_CODE, $this->questionFactory),
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_an_admin_user_if_accepted_in_the_summary(): void
|
||||
{
|
||||
$adminUserData = $this->getDefaultAdminUserDataSetup();
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->once())
|
||||
->method('createEmail')
|
||||
->willReturn($this->createQuestionMock('Email'));
|
||||
|
||||
$commandInputs = $this->getDefaultCommandInputsSetup();
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->exactly(2))
|
||||
->method('createWithNotNullValidator')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$this->createQuestionMock('Username'),
|
||||
$this->createQuestionMock('New password'),
|
||||
);
|
||||
|
||||
$this->assertSuccessfulCommandExecution($adminUserData, $commandInputs);
|
||||
}
|
||||
$this->command->setInputs($this->getDefaultCommandInputsSetup());
|
||||
|
||||
/** @test */
|
||||
public function it_has_set_up_three_attempts_to_write_a_valid_email(): void
|
||||
{
|
||||
$adminUserData = $this->getDefaultAdminUserDataSetup();
|
||||
$message = new CreateAdminUser(...array_values($this->getDefaultAdminUserDataSetup()));
|
||||
|
||||
$commandInputs = array_merge(
|
||||
[
|
||||
'first_email_entry' => 'invalid-email',
|
||||
'second_email_entry' => 'still-invalid-email',
|
||||
],
|
||||
$this->getDefaultCommandInputsSetup(),
|
||||
);
|
||||
$this->messageBus->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($message)
|
||||
->willReturn(new Envelope($message, [new HandledStamp(self::anything(), 'handler')]))
|
||||
;
|
||||
|
||||
$this->assertSuccessfulCommandExecution($adminUserData, $commandInputs);
|
||||
}
|
||||
$this->command->execute([]);
|
||||
|
||||
/** @test */
|
||||
public function it_has_set_up_three_attempts_to_write_a_non_blank_username(): void
|
||||
{
|
||||
$adminUserData = $this->getDefaultAdminUserDataSetup();
|
||||
|
||||
$commandInputs = [
|
||||
'email' => self::EMAIL,
|
||||
'first_username_entry' => '',
|
||||
'second_username_entry' => '',
|
||||
'username' => self::USERNAME,
|
||||
'first_name' => self::FIRST_NAME,
|
||||
'last_name' => self::LAST_NAME,
|
||||
'password' => self::PASSWORD,
|
||||
'locale_code' => self::LOCALE_CODE,
|
||||
'admin_user_enabled' => self::YES,
|
||||
'creation_confirmation' => self::YES,
|
||||
];
|
||||
|
||||
$this->assertSuccessfulCommandExecution($adminUserData, $commandInputs);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_has_set_up_three_attempts_to_write_a_non_blank_password(): void
|
||||
{
|
||||
$adminUserData = $this->getDefaultAdminUserDataSetup();
|
||||
|
||||
$commandInputs = [
|
||||
'email' => self::EMAIL,
|
||||
'username' => self::USERNAME,
|
||||
'first_name' => self::FIRST_NAME,
|
||||
'last_name' => self::LAST_NAME,
|
||||
'first_password_entry' => '',
|
||||
'second_password_entry' => '',
|
||||
'password' => self::PASSWORD,
|
||||
'locale_code' => self::LOCALE_CODE,
|
||||
'admin_user_enabled' => self::YES,
|
||||
'creation_confirmation' => self::YES,
|
||||
];
|
||||
|
||||
$this->assertSuccessfulCommandExecution($adminUserData, $commandInputs);
|
||||
$this->command->assertCommandIsSuccessful();
|
||||
self::assertStringContainsString('Admin user has been successfully created.', $this->command->getDisplay());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -140,6 +111,21 @@ final class CreateAdminUserCommandTest extends TestCase
|
|||
'creation_confirmation' => self::NO,
|
||||
]);
|
||||
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->once())
|
||||
->method('createEmail')
|
||||
->willReturn($this->createQuestionMock('Email'));
|
||||
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->exactly(2))
|
||||
->method('createWithNotNullValidator')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$this->createQuestionMock('Username'),
|
||||
$this->createQuestionMock('New password'),
|
||||
);
|
||||
|
||||
$this->messageBus->expects($this->never())->method('dispatch');
|
||||
|
||||
self::assertSame(Command::INVALID, $this->command->execute([]));
|
||||
|
|
@ -155,6 +141,21 @@ final class CreateAdminUserCommandTest extends TestCase
|
|||
|
||||
$message = new CreateAdminUser(...array_values($adminUserData));
|
||||
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->once())
|
||||
->method('createEmail')
|
||||
->willReturn($this->createQuestionMock('Email'));
|
||||
|
||||
$this
|
||||
->questionFactory
|
||||
->expects($this->exactly(2))
|
||||
->method('createWithNotNullValidator')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$this->createQuestionMock('Username'),
|
||||
$this->createQuestionMock('New password'),
|
||||
);
|
||||
|
||||
$this->messageBus->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($message)
|
||||
|
|
@ -175,24 +176,6 @@ final class CreateAdminUserCommandTest extends TestCase
|
|||
self::assertSame(Command::FAILURE, $this->command->execute([], ['interactive' => false]));
|
||||
}
|
||||
|
||||
private function assertSuccessfulCommandExecution(array $adminUserData, array $commandInputs): void
|
||||
{
|
||||
$this->command->setInputs($commandInputs);
|
||||
|
||||
$message = new CreateAdminUser(...array_values($adminUserData));
|
||||
|
||||
$this->messageBus->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($message)
|
||||
->willReturn(new Envelope($message, [new HandledStamp(self::anything(), 'handler')]))
|
||||
;
|
||||
|
||||
$this->command->execute([]);
|
||||
|
||||
$this->command->assertCommandIsSuccessful();
|
||||
self::assertStringContainsString('Admin user has been successfully created.', $this->command->getDisplay());
|
||||
}
|
||||
|
||||
private function getDefaultCommandInputsSetup(): array
|
||||
{
|
||||
return [
|
||||
|
|
@ -219,4 +202,17 @@ final class CreateAdminUserCommandTest extends TestCase
|
|||
'admin_user_enabled' => true,
|
||||
];
|
||||
}
|
||||
|
||||
private function createQuestionMock(string $askedQuestion): MockObject
|
||||
{
|
||||
$question = $this->createMock(Question::class);
|
||||
$question
|
||||
->method('isTrimmable')
|
||||
->willReturn(true);
|
||||
$question
|
||||
->method('getQuestion')
|
||||
->willReturn($askedQuestion);
|
||||
|
||||
return $question;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Tests\Console\Command\Factory;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\AdminBundle\Console\Command\Factory\QuestionFactory;
|
||||
|
||||
final class QuestionFactoryTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_creates_email_question(): void
|
||||
{
|
||||
$questionFactory = new QuestionFactory();
|
||||
$question = $questionFactory->createEmail();
|
||||
|
||||
$this->assertSame('Email', $question->getQuestion());
|
||||
$this->assertSame(3, $question->getMaxAttempts());
|
||||
$this->assertEquals('test@example.com', $question->getValidator()('test@example.com'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_email_question_with_invalid_email(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('The email address provided is invalid. Please try again.');
|
||||
|
||||
$questionFactory = new QuestionFactory();
|
||||
$emailQuestion = $questionFactory->createEmail();
|
||||
|
||||
$emailQuestion->getValidator()('invalid-email');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_email_question_with_null_email(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('The email address provided is invalid. Please try again.');
|
||||
|
||||
$questionFactory = new QuestionFactory();
|
||||
$emailQuestion = $questionFactory->createEmail();
|
||||
|
||||
$emailQuestion->getValidator()(null);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_question_with_not_null_validator(): void
|
||||
{
|
||||
$questionFactory = new QuestionFactory();
|
||||
$question = $questionFactory->createWithNotNullValidator('Question');
|
||||
|
||||
$this->assertSame('Question', $question->getQuestion());
|
||||
$this->assertSame(3, $question->getMaxAttempts());
|
||||
$this->assertEquals('test', $question->getValidator()('test'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_creates_question_with_not_null_validator_with_null_value(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('The value cannot be empty.');
|
||||
|
||||
$questionFactory = new QuestionFactory();
|
||||
$question = $questionFactory->createWithNotNullValidator('Question');
|
||||
|
||||
$question->getValidator()(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ return [
|
|||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Sylius\Abstraction\StateMachine\SyliusStateMachineAbstractionBundle::class => ['all' => true],
|
||||
Sylius\Bundle\OrderBundle\SyliusOrderBundle::class => ['all' => true],
|
||||
Sylius\Bundle\MoneyBundle\SyliusMoneyBundle::class => ['all' => true],
|
||||
Sylius\Bundle\CurrencyBundle\SyliusCurrencyBundle::class => ['all' => true],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Command\Customer;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\ShopUserIdAwareInterface;
|
||||
|
||||
/** @experimental */
|
||||
class RemoveShopUser implements ShopUserIdAwareInterface
|
||||
{
|
||||
public function __construct(private mixed $shopUserId)
|
||||
{
|
||||
}
|
||||
|
||||
public function getShopUserId(): mixed
|
||||
{
|
||||
return $this->shopUserId;
|
||||
}
|
||||
|
||||
public function setShopUserId(mixed $shopUserId): void
|
||||
{
|
||||
$this->shopUserId = $shopUserId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Command;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Message\ResendOrderConfirmationEmail as BaseResendOrderConfirmationEmail;
|
||||
|
||||
class ResendOrderConfirmationEmail extends BaseResendOrderConfirmationEmail implements OrderTokenValueAwareInterface
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\CommandHandler\Customer;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Customer\RemoveShopUser;
|
||||
use Sylius\Bundle\ApiBundle\Exception\UserNotFoundException;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class RemoveShopUserHandler
|
||||
{
|
||||
/**
|
||||
* @param UserRepositoryInterface<ShopUserInterface> $shopUserRepository
|
||||
*/
|
||||
public function __construct(
|
||||
private UserRepositoryInterface $shopUserRepository,
|
||||
){
|
||||
}
|
||||
|
||||
public function __invoke(RemoveShopUser $removeShopUser): void
|
||||
{
|
||||
$shopUser = $this->shopUserRepository->find($removeShopUser->getShopUserId());
|
||||
|
||||
if (null === $shopUser) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
$shopUser->setCustomer(null);
|
||||
$this->shopUserRepository->remove($shopUser);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Controller;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Exception\ChannelNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Query\GetStatistics;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints as Assert;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||
use Symfony\Component\Messenger\HandleTrait;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Constraints as BaseAssert;
|
||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
final class GetStatisticsAction
|
||||
{
|
||||
use HandleTrait;
|
||||
|
||||
private BaseAssert\Collection $constraint;
|
||||
|
||||
public function __construct(
|
||||
MessageBusInterface $messageBus,
|
||||
private SerializerInterface $serializer,
|
||||
private ValidatorInterface $validator,
|
||||
) {
|
||||
$this->messageBus = $messageBus;
|
||||
|
||||
$this->constraint = new BaseAssert\Collection([
|
||||
'channelCode' => new Assert\Code(),
|
||||
'startDate' => new BaseAssert\DateTime('Y-m-d\TH:i:s', message: 'sylius.date_time.invalid'),
|
||||
'dateInterval' => new Assert\DateInterval(),
|
||||
'endDate' => new BaseAssert\DateTime('Y-m-d\TH:i:s', message: 'sylius.date_time.invalid'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$parameters = $request->query->all();
|
||||
|
||||
$violations = $this->validator->validate($parameters, $this->constraint);
|
||||
if (count($violations) > 0) {
|
||||
return $this->createBadRequestResponse($violations);
|
||||
}
|
||||
|
||||
$period = new \DatePeriod(
|
||||
new \DateTimeImmutable($parameters['startDate']),
|
||||
new \DateInterval($parameters['dateInterval']),
|
||||
new \DateTimeImmutable($parameters['endDate']),
|
||||
);
|
||||
|
||||
$violations = $this->validator->validate($period, new Assert\DatePeriod());
|
||||
if (count($violations) > 0) {
|
||||
return $this->createBadRequestResponse($violations);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->handle(new GetStatistics($period, $parameters['channelCode']));
|
||||
$status = Response::HTTP_OK;
|
||||
} catch (HandlerFailedException $exception) {
|
||||
$exception = $exception->getPrevious();
|
||||
$result = ['message' => $exception->getMessage()];
|
||||
|
||||
if ($exception instanceof ChannelNotFoundException) {
|
||||
$status = Response::HTTP_NOT_FOUND;
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(data: $this->serializer->serialize($result, 'json'), status: $status, json: true);
|
||||
}
|
||||
|
||||
private function createBadRequestResponse(ConstraintViolationListInterface $violations): JsonResponse
|
||||
{
|
||||
return new JsonResponse(
|
||||
data: $this->serializer->serialize($violations, 'json'),
|
||||
status: Response::HTTP_BAD_REQUEST,
|
||||
json: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Controller;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Customer\RemoveShopUser;
|
||||
use Sylius\Bundle\ApiBundle\Exception\CustomerNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\UserNotFoundException;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class RemoveCustomerShopUserAction
|
||||
{
|
||||
/**
|
||||
* @param UserRepositoryInterface<ShopUserInterface> $shopUserRepository
|
||||
*/
|
||||
public function __construct(
|
||||
private MessageBusInterface $commandBus,
|
||||
private UserRepositoryInterface $shopUserRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$customerId = $request->attributes->get('id', '');
|
||||
|
||||
$user = $this->shopUserRepository->findOneBy(['customer' => $customerId]);
|
||||
if (null === $user) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
$this->commandBus->dispatch(new RemoveShopUser($user->getId()));
|
||||
|
||||
return new JsonResponse(status: Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\User\Security\PasswordUpdaterInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class CustomerDataPersister implements ContextAwareDataPersisterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
private PasswordUpdaterInterface $passwordUpdater,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $context */
|
||||
public function supports($data, array $context = []): bool
|
||||
{
|
||||
return $data instanceof CustomerInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CustomerInterface $data
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
public function persist($data, array $context = []): void
|
||||
{
|
||||
$user = $data->getUser();
|
||||
if (null !== $user && null !== $user->getPlainPassword()) {
|
||||
$this->passwordUpdater->updatePassword($user);
|
||||
}
|
||||
|
||||
$this->decoratedDataPersister->persist($data, $context);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $context */
|
||||
public function remove($data, array $context = []): void
|
||||
{
|
||||
$this->decoratedDataPersister->remove($data, $context);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\ApiBundle\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use Sylius\Bundle\LocaleBundle\Checker\Exception\LocaleIsUsedException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\LocaleIsUsedException;
|
||||
use Sylius\Bundle\LocaleBundle\Checker\LocaleUsageCheckerInterface;
|
||||
use Sylius\Component\Locale\Model\LocaleInterface;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Exception;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/** @experimental */
|
||||
final class ChannelNotFoundException extends NotFoundHttpException
|
||||
{
|
||||
public function __construct(
|
||||
string $message = 'Channel not found.',
|
||||
\Throwable $previous = null,
|
||||
int $code = 0,
|
||||
) {
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\LocaleBundle\Checker\Exception;
|
||||
namespace Sylius\Bundle\ApiBundle\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Exception;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/** @experimental */
|
||||
final class UserNotFoundException extends NotFoundHttpException
|
||||
{
|
||||
/** @param array<array-key, mixed> $headers */
|
||||
public function __construct(
|
||||
string $message = 'User not found.',
|
||||
\Throwable $previous = null,
|
||||
int $code = 0,
|
||||
array $headers = [],
|
||||
) {
|
||||
parent::__construct($message, $previous, $code, $headers);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\OpenApi\Documentation;
|
||||
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\Parameter;
|
||||
use ApiPlatform\OpenApi\Model\PathItem;
|
||||
use ApiPlatform\OpenApi\OpenApi;
|
||||
use Sylius\Calendar\Provider\DateTimeProviderInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/** @experimental */
|
||||
final class StatisticsModifier implements DocumentationModifierInterface
|
||||
{
|
||||
private const PATH = '/admin/statistics';
|
||||
|
||||
public function __construct(
|
||||
private string $apiRoute,
|
||||
private DateTimeProviderInterface $dateTimeProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function modify(OpenApi $docs): OpenApi
|
||||
{
|
||||
$schemas = $docs->getComponents()->getSchemas();
|
||||
$schemas['Statistics'] = [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'salesPerPeriod' => [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'period' => [
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
],
|
||||
'total' => [
|
||||
'type' => 'integer',
|
||||
'example' => '1000',
|
||||
],
|
||||
],
|
||||
],
|
||||
'minItems' => 12,
|
||||
],
|
||||
'newCustomersCount' => [
|
||||
'type' => 'integer',
|
||||
'example' => '10',
|
||||
],
|
||||
'paidOrdersCount' => [
|
||||
'type' => 'integer',
|
||||
'example' => '12',
|
||||
],
|
||||
'averageOrderValue' => [
|
||||
'type' => 'integer',
|
||||
'example' => '1000',
|
||||
],
|
||||
'totalSales' => [
|
||||
'type' => 'integer',
|
||||
'example' => '12000',
|
||||
],
|
||||
'intervalType' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['month', 'year', 'week', 'day'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$path = $this->apiRoute . self::PATH;
|
||||
$paths = $docs->getPaths();
|
||||
$paths->addPath($path, $this->getPathItem());
|
||||
|
||||
return $docs
|
||||
->withPaths($paths)
|
||||
->withComponents($docs->getComponents()->withSchemas($schemas))
|
||||
;
|
||||
}
|
||||
|
||||
private function getPathItem(): PathItem
|
||||
{
|
||||
return new PathItem(
|
||||
ref: 'Statistics',
|
||||
summary: 'Get statistics',
|
||||
get: new Operation(
|
||||
operationId: 'get_statistics',
|
||||
tags: ['Statistics'],
|
||||
responses: [
|
||||
Response::HTTP_OK => [
|
||||
'description' => 'Statistics',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'$ref' => '#/components/schemas/Statistics',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
summary: 'Get statistics',
|
||||
description: 'Get statistics',
|
||||
parameters: $this->getParameters(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return Parameter[] */
|
||||
private function getParameters(): array
|
||||
{
|
||||
$channelCode = new Parameter(
|
||||
name: 'channelCode',
|
||||
in: 'query',
|
||||
description: 'Channel to get statistics for',
|
||||
required: true,
|
||||
schema: [
|
||||
'type' => 'string',
|
||||
],
|
||||
);
|
||||
|
||||
$startDate = new Parameter(
|
||||
name: 'startDate',
|
||||
in: 'query',
|
||||
description: 'Start date for statistics',
|
||||
required: true,
|
||||
schema: [
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'default' => $this->dateTimeProvider->now()->format('Y-01-01\T00:00:00'),
|
||||
],
|
||||
);
|
||||
|
||||
$dateInterval = new Parameter(
|
||||
name: 'dateInterval',
|
||||
in: 'query',
|
||||
description: 'Date interval for statistics',
|
||||
required: true,
|
||||
schema: [
|
||||
'type' => 'string',
|
||||
'default' => 'P1M',
|
||||
],
|
||||
);
|
||||
|
||||
$endDate = new Parameter(
|
||||
name: 'endDate',
|
||||
in: 'query',
|
||||
description: 'End date for statistics',
|
||||
required: true,
|
||||
schema: [
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'default' => $this->dateTimeProvider->now()->format('Y-12-31\T23:59:59'),
|
||||
],
|
||||
);
|
||||
|
||||
return [$channelCode, $startDate, $dateInterval, $endDate];
|
||||
}
|
||||
}
|
||||
32
src/Sylius/Bundle/ApiBundle/Query/GetStatistics.php
Normal file
32
src/Sylius/Bundle/ApiBundle/Query/GetStatistics.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Query;
|
||||
|
||||
/** @experimental */
|
||||
class GetStatistics
|
||||
{
|
||||
public function __construct(private \DatePeriod $datePeriod, private string $channelCode)
|
||||
{
|
||||
}
|
||||
|
||||
public function getDatePeriod(): \DatePeriod
|
||||
{
|
||||
return $this->datePeriod;
|
||||
}
|
||||
|
||||
public function getChannelCode(): string
|
||||
{
|
||||
return $this->channelCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\QueryHandler;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Exception\ChannelNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Query\GetStatistics;
|
||||
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Statistics\Provider\StatisticsProviderInterface;
|
||||
use Sylius\Component\Core\Statistics\ValueObject\Statistics;
|
||||
|
||||
/** @experimental */
|
||||
final class GetStatisticsHandler
|
||||
{
|
||||
/** @param ChannelRepositoryInterface<ChannelInterface> $channelRepository */
|
||||
public function __construct(
|
||||
private StatisticsProviderInterface $statisticsProvider,
|
||||
private ChannelRepositoryInterface $channelRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(GetStatistics $query): Statistics
|
||||
{
|
||||
/** @var ChannelInterface|null $channel */
|
||||
$channel = $this->channelRepository->findOneByCode($query->getChannelCode());
|
||||
|
||||
if ($channel === null) {
|
||||
throw new ChannelNotFoundException(
|
||||
sprintf('Channel with code "%s" does not exist.', $query->getChannelCode()),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->statisticsProvider->provide($query->getDatePeriod(), $channel);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,6 +95,12 @@
|
|||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_delete_user">
|
||||
<attribute name="method">DELETE</attribute>
|
||||
<attribute name="path">/admin/customer/{id}/user</attribute>
|
||||
<attribute name="controller">Sylius\Bundle\ApiBundle\Controller\RemoveCustomerShopUserAction</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="shop_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/shop/customers/{id}</attribute>
|
||||
|
|
|
|||
|
|
@ -382,6 +382,18 @@
|
|||
<attribute name="summary">Addresses cart to given location, logged in Customer does not have to provide an email. Applies coupon to cart.</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_resend_confirmation">
|
||||
<attribute name="method">POST</attribute>
|
||||
<attribute name="path">/admin/orders/{tokenValue}/resend-confirmation-email</attribute>
|
||||
<attribute name="messenger">input</attribute>
|
||||
<attribute name="input">Sylius\Bundle\ApiBundle\Command\ResendOrderConfirmationEmail</attribute>
|
||||
<attribute name="output">false</attribute>
|
||||
<attribute name="status">202</attribute>
|
||||
<attribute name="openapi_context">
|
||||
<attribute name="summary">Resends order confirmation email</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
</itemOperations>
|
||||
|
||||
<subresourceOperations>
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ api_platform:
|
|||
Sylius\Bundle\ApiBundle\Exception\ChannelPricingChannelCodeMismatchException: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\InvalidProductAttributeValueTypeException: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\InvalidRequestArgumentException: 400
|
||||
Sylius\Bundle\ApiBundle\Exception\LocaleIsUsedException: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\OrderItemNotFoundException: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\OrderNoLongerEligibleForPromotion: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeRemoved: 422
|
||||
|
|
@ -75,7 +76,6 @@ api_platform:
|
|||
Sylius\Bundle\ApiBundle\Exception\ZoneCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\CannotRemoveMenuTaxonException: 409
|
||||
Sylius\Bundle\ApiBundle\Exception\PromotionCannotBeRemoved: 422
|
||||
Sylius\Bundle\LocaleBundle\Checker\Exception\LocaleIsUsedException: 422
|
||||
Sylius\Bundle\UserBundle\Exception\UserNotFoundException: 404
|
||||
Sylius\Component\Promotion\Exception\FailedGenerationException: 422
|
||||
Symfony\Component\Serializer\Exception\UnexpectedValueException: 400
|
||||
|
|
|
|||
|
|
@ -80,6 +80,12 @@
|
|||
<tag name="sylius.open_api.modifier" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\OpenApi\Documentation\StatisticsModifier">
|
||||
<argument>%sylius.security.new_api_route%</argument>
|
||||
<argument type="service" id="Sylius\Calendar\Provider\DateTimeProviderInterface" />
|
||||
<tag name="sylius.open_api.modifier" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\OpenApi\Documentation\PromotionDocumentationModifier">
|
||||
<argument>%sylius.security.new_api_route%</argument>
|
||||
<argument>%sylius.promotion_actions%</argument>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ sylius_api_admin_authentication_token:
|
|||
path: /admin/authentication-token
|
||||
methods: ['POST']
|
||||
|
||||
sylius_api_admin_statistics:
|
||||
path: /admin/statistics
|
||||
methods: ['GET']
|
||||
defaults:
|
||||
_controller: Sylius\Bundle\ApiBundle\Controller\GetStatisticsAction
|
||||
|
||||
sylius_api_shop_authentication_token:
|
||||
path: /shop/authentication-token
|
||||
methods: ['POST']
|
||||
|
|
|
|||
|
|
@ -231,5 +231,11 @@
|
|||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\CommandHandler\Customer\RemoveShopUserHandler">
|
||||
<argument type="service" id="sylius.repository.shop_user" />
|
||||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -60,5 +60,15 @@
|
|||
<tag name="controller.service_arguments" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Controller\RemoveCustomerShopUserAction">
|
||||
<argument type="service" id="sylius.command_bus" />
|
||||
<argument type="service" id="sylius.repository.shop_user" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Controller\GetStatisticsAction">
|
||||
<argument type="service" id="sylius.query_bus" />
|
||||
<argument type="service" id="serializer" />
|
||||
<argument type="service" id="validator" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -105,5 +105,11 @@
|
|||
<argument type="service" id="api_platform.doctrine.orm.data_persister" />
|
||||
<tag name="api_platform.data_persister" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\DataPersister\CustomerDataPersister">
|
||||
<argument type="service" id="api_platform.doctrine.orm.data_persister" />
|
||||
<argument type="service" id="sylius.security.password_updater" />
|
||||
<tag name="api_platform.data_persister" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@
|
|||
<argument type="service" id="sylius.customer_statistics_provider" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\QueryHandler\GetStatisticsHandler">
|
||||
<argument type="service" id="Sylius\Component\Core\Statistics\Provider\StatisticsProviderInterface" />
|
||||
<argument type="service" id="sylius.repository.channel" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\QueryHandler\GetAddressLogEntryCollectionHandler">
|
||||
<argument type="service" id="sylius.repository.address_log_entry" />
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -188,5 +188,17 @@
|
|||
<argument type="service" id="Sylius\Bundle\ApiBundle\Validator\CatalogPromotion\ForVariantsScopeValidator.inner" />
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\Constraints\DateIntervalValidator">
|
||||
<tag name="validator.constraint_validator" alias="sylius_api_validator_date_interval" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\Constraints\EndDateIsNotBeforeStartDateValidator">
|
||||
<tag name="validator.constraint_validator" alias="sylius_api_validator_date_period_end_date_is_not_before_start_date" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\Constraints\EndDateAgainstIntervalValidator">
|
||||
<tag name="validator.constraint_validator" alias="sylius_api_validator_end_date_against_interval" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,20 @@ sylius:
|
|||
is_verified: 'Account with email %email% is currently verified.'
|
||||
address:
|
||||
without_country: 'The address without country cannot exist'
|
||||
code:
|
||||
invalid: 'The code should contain only letters, numbers, dashes ("-" symbol) and underscores ("_" symbol).'
|
||||
contact_request:
|
||||
email_is_invalid: 'The provided email is invalid.'
|
||||
country:
|
||||
not_exist: 'The country %countryCode% does not exist.'
|
||||
date_interval:
|
||||
empty: 'The date interval cannot be empty.'
|
||||
invalid: 'The date interval is not valid ISO 8601 interval.'
|
||||
date_period:
|
||||
end_date_is_not_before_start_date: 'The end date is not before start date.'
|
||||
end_date_must_be_multiple_of_interval: 'End date "%givenDate%" must be multiple of interval, expected "%expectedDate%".'
|
||||
date_time:
|
||||
invalid: 'The date time is not valid ISO 8601 date time in Y-m-d\TH:i:s format.'
|
||||
order:
|
||||
not_empty: 'An empty order cannot be completed.'
|
||||
payment_method:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ return [
|
|||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Sylius\Abstraction\StateMachine\SyliusStateMachineAbstractionBundle::class => ['all' => true],
|
||||
Sylius\Bundle\OrderBundle\SyliusOrderBundle::class => ['all' => true],
|
||||
Sylius\Bundle\MoneyBundle\SyliusMoneyBundle::class => ['all' => true],
|
||||
Sylius\Bundle\CurrencyBundle\SyliusCurrencyBundle::class => ['all' => true],
|
||||
|
|
|
|||
35
src/Sylius/Bundle/ApiBundle/Validator/Constraints/Code.php
Normal file
35
src/Sylius/Bundle/ApiBundle/Validator/Constraints/Code.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraints\Compound;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
|
||||
/** @experimental */
|
||||
final class Code extends Compound
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
protected function getConstraints(array $options): array
|
||||
{
|
||||
return [
|
||||
new NotBlank(message: 'sylius.code.not_blank'),
|
||||
new Type('string'),
|
||||
new Regex(['pattern' => '/^[\w-]*$/'], message: 'sylius.code.invalid'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/** @experimental */
|
||||
final class DateInterval extends Constraint
|
||||
{
|
||||
public string $message = 'sylius.date_interval.invalid';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_api_validator_date_interval';
|
||||
}
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @experimental */
|
||||
final class DateIntervalValidator extends ConstraintValidator
|
||||
{
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
Assert::string($value);
|
||||
Assert::isInstanceOf($constraint, DateInterval::class);
|
||||
|
||||
try {
|
||||
new \DateInterval($value);
|
||||
} catch (\Exception) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraints\Compound;
|
||||
|
||||
/** @experimental */
|
||||
final class DatePeriod extends Compound
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
protected function getConstraints(array $options): array
|
||||
{
|
||||
return [
|
||||
new EndDateIsNotBeforeStartDate(),
|
||||
new EndDateAgainstInterval(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/** @experimental */
|
||||
final class EndDateAgainstInterval extends Constraint
|
||||
{
|
||||
/** @var string */
|
||||
public $message = 'sylius.date_period.end_date_must_be_multiple_of_interval';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_api_validator_end_date_against_interval';
|
||||
}
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @experimental */
|
||||
final class EndDateAgainstIntervalValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @param \DatePeriod $value
|
||||
* @param EndDateAgainstInterval $constraint
|
||||
*
|
||||
* Validates that the end date is a multiple of the interval.
|
||||
* The end date is adjusted by subtracting one second to make it inclusive (closed interval).
|
||||
* If the adjusted end date does not match the provided end date, an exception is thrown.
|
||||
*/
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
Assert::isInstanceOf($value, \DatePeriod::class);
|
||||
Assert::isInstanceOf($constraint, EndDateAgainstInterval::class);
|
||||
|
||||
$currentDate = clone $value->getStartDate();
|
||||
$endDate = $value->getEndDate();
|
||||
$interval = $value->getDateInterval();
|
||||
|
||||
while ($currentDate <= $endDate) {
|
||||
$currentDate = $currentDate->add($interval);
|
||||
}
|
||||
|
||||
/** We shift to make closed interval. */
|
||||
$intervalEndDate = $currentDate->modify('-1 second');
|
||||
|
||||
if ($intervalEndDate != $endDate) {
|
||||
$this
|
||||
->context
|
||||
->buildViolation($constraint->message)
|
||||
->setParameter('%givenDate%', $endDate->format('Y-m-d H:i:s'))
|
||||
->setParameter('%expectedDate%', $intervalEndDate->format('Y-m-d H:i:s'))
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/** @experimental */
|
||||
final class EndDateIsNotBeforeStartDate extends Constraint
|
||||
{
|
||||
public string $message = 'sylius.date_period.end_date_is_not_before_start_date';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_api_validator_date_period_end_date_is_not_before_start_date';
|
||||
}
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @experimental */
|
||||
final class EndDateIsNotBeforeStartDateValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @param \DatePeriod $value
|
||||
* @param EndDateIsNotBeforeStartDate $constraint
|
||||
*/
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
Assert::isInstanceOf($value, \DatePeriod::class);
|
||||
Assert::isInstanceOf($constraint, EndDateIsNotBeforeStartDate::class);
|
||||
|
||||
if ($value->getStartDate() > $value->getEndDate()) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\CommandHandler;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Command\Customer\RemoveShopUser;
|
||||
use Sylius\Bundle\UserBundle\Exception\UserNotFoundException;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
|
||||
final class RemoveShopUserHandlerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(UserRepositoryInterface $userRepository): void
|
||||
{
|
||||
$this->beConstructedWith($userRepository);
|
||||
}
|
||||
|
||||
public function it_throws_an_exception_if_user_has_not_been_found(UserRepositoryInterface $userRepository): void
|
||||
{
|
||||
$userRepository->find(42)->willReturn(null);
|
||||
|
||||
$this->shouldThrow(UserNotFoundException::class)->during('__invoke', [new RemoveShopUser(42)]);
|
||||
}
|
||||
|
||||
public function it_should_remove_shop_user(UserRepositoryInterface $userRepository, ShopUserInterface $shopUser): void
|
||||
{
|
||||
$userRepository->find(42)->willReturn($shopUser);
|
||||
$shopUser->setCustomer(null)->shouldBeCalled();
|
||||
$userRepository->remove($shopUser)->shouldBeCalled();
|
||||
|
||||
$this->__invoke(new RemoveShopUser(42));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\User\Security\PasswordUpdaterInterface;
|
||||
|
||||
final class CustomerDataPersisterSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
PasswordUpdaterInterface $passwordUpdater,
|
||||
): void {
|
||||
$this->beConstructedWith($decoratedDataPersister, $passwordUpdater);
|
||||
}
|
||||
|
||||
function it_supports_only_customer_entity(CustomerInterface $customer, ProductInterface $product): void
|
||||
{
|
||||
$this->supports($customer)->shouldReturn(true);
|
||||
$this->supports($product)->shouldReturn(false);
|
||||
}
|
||||
|
||||
function it_does_not_update_password_when_user_is_null(
|
||||
ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
PasswordUpdaterInterface $passwordUpdater,
|
||||
CustomerInterface $customer,
|
||||
): void {
|
||||
$customer->getUser()->willReturn(null);
|
||||
$passwordUpdater->updatePassword(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$decoratedDataPersister->persist($customer, [])->shouldBeCalled();
|
||||
|
||||
$this->persist($customer, []);
|
||||
}
|
||||
|
||||
function it_does_not_update_password_when_plain_password_is_null(
|
||||
ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
PasswordUpdaterInterface $passwordUpdater,
|
||||
CustomerInterface $customer,
|
||||
ShopUserInterface $user,
|
||||
): void {
|
||||
$user->getPlainPassword()->willReturn(null);
|
||||
$customer->getUser()->willReturn($user);
|
||||
$passwordUpdater->updatePassword($user)->shouldNotBeCalled();
|
||||
|
||||
$decoratedDataPersister->persist($customer, [])->shouldBeCalled();
|
||||
|
||||
$this->persist($customer, []);
|
||||
}
|
||||
|
||||
function it_updates_password_when_plain_password_is_set(
|
||||
ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
PasswordUpdaterInterface $passwordUpdater,
|
||||
CustomerInterface $customer,
|
||||
ShopUserInterface $user,
|
||||
): void {
|
||||
$user->getPlainPassword()->willReturn('password');
|
||||
$customer->getUser()->willReturn($user);
|
||||
$passwordUpdater->updatePassword($user)->shouldBeCalled();
|
||||
|
||||
$decoratedDataPersister->persist($customer, [])->shouldBeCalled();
|
||||
|
||||
$this->persist($customer, []);
|
||||
}
|
||||
|
||||
function it_uses_decorated_data_persister_to_remove_customer(
|
||||
ContextAwareDataPersisterInterface $decoratedDataPersister,
|
||||
CustomerInterface $customer,
|
||||
): void {
|
||||
$decoratedDataPersister->remove($customer, [])->shouldBeCalled();
|
||||
|
||||
$this->remove($customer, []);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue