[API] Registering shop users

This commit is contained in:
Kamil Kokot 2020-04-14 19:20:49 +02:00
parent 0e30e3b002
commit e5f7fc3e28
No known key found for this signature in database
GPG key ID: 7BD76F7054D93C89
23 changed files with 855 additions and 11 deletions

7
.env
View file

@ -38,3 +38,10 @@ JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=66d45daf91b2ed1031e62d81c81dba2e
###< lexik/jwt-authentication-bundle ###
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=doctrine://default
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###

View file

@ -75,6 +75,7 @@
"symfony/http-foundation": "^4.4",
"symfony/http-kernel": "^4.4",
"symfony/intl": "^4.4",
"symfony/messenger": "^4.4",
"symfony/monolog-bundle": "^3.0",
"symfony/options-resolver": "^4.4",
"symfony/polyfill-iconv": "^1.3",

View file

@ -0,0 +1,20 @@
framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
# async: '%env(MESSENGER_TRANSPORT_DSN)%'
# failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
# Route your messages to the transports
# 'App\Message\YourMessage': async
buses:
messenger.bus.default:
middleware:
- 'validation'
- 'doctrine_transaction'

View file

@ -7,7 +7,7 @@ Feature: Account registration
Background:
Given the store operates on a single channel in "United States"
@ui
@ui @api
Scenario: Registering a new account with minimum information
When I want to register a new account
And I specify the first name as "Saul"
@ -19,7 +19,7 @@ Feature: Account registration
Then I should be notified that new account has been successfully created
But I should not be logged in
@ui
@ui @api
Scenario: Registering a new account with minimum information when channel has disabled registration verification
Given on this channel account verification is not required
When I want to register a new account
@ -32,7 +32,7 @@ Feature: Account registration
Then I should be notified that new account has been successfully created
And I should be logged in
@ui
@ui @api
Scenario: Registering a new account with all details
When I want to register a new account
And I specify the first name as "Saul"
@ -45,7 +45,7 @@ Feature: Account registration
Then I should be notified that new account has been successfully created
But I should not be logged in
@ui
@ui @api
Scenario: Registering a guest account
Given there is a customer "goodman@gmail.com" that placed an order "#001"
When I want to register a new account

View file

@ -7,7 +7,7 @@ Feature: Account registration
Background:
Given the store operates on a single channel in "United States"
@ui
@ui @api
Scenario: Trying to register a new account with email that has been already used
Given there is a user "goodman@gmail.com" identified by "heisenberg"
When I want to register a new account
@ -16,7 +16,7 @@ Feature: Account registration
Then I should be notified that the email is already used
And I should not be logged in
@ui
@ui @api
Scenario: Trying to register a new account without specifying first name
When I want to register a new account
And I do not specify the first name
@ -27,7 +27,7 @@ Feature: Account registration
Then I should be notified that the first name is required
And I should not be logged in
@ui
@ui @api
Scenario: Trying to register a new account without specifying last name
When I want to register a new account
And I do not specify the last name
@ -39,7 +39,7 @@ Feature: Account registration
Then I should be notified that the last name is required
And I should not be logged in
@ui
@ui @api
Scenario: Trying to register a new account without specifying password
When I want to register a new account
And I do not specify the password
@ -62,7 +62,7 @@ Feature: Account registration
Then I should be notified that the password do not match
And I should not be logged in
@ui
@ui @api
Scenario: Trying to register a new account without specifying email
When I want to register a new account
And I do not specify the email

View file

@ -9,7 +9,7 @@ Feature: Registering an account again after it has been deleted
And there was account of "ted@example.com" with password "pswd"
But its account was deleted
@ui
@ui @api
Scenario: Registering again after my account deletion
When I want to again register a new account
And I specify the first name as "Ted"

View file

@ -0,0 +1,185 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Shop;
use Behat\Behat\Context\Context;
use Symfony\Component\BrowserKit\AbstractBrowser;
use Webmozart\Assert\Assert;
final class RegistrationContext implements Context
{
/** @var AbstractBrowser */
private $client;
private $content = [];
public function __construct(AbstractBrowser $client)
{
$this->client = $client;
}
/**
* @When I want to register a new account
* @When I want to again register a new account
*/
public function iWantToRegisterNewAccount(): void
{
// Intentionally left empty
}
/**
* @When I specify the first name as :firstName
* @When I do not specify the first name
*/
public function iSpecifyTheFirstNameAs(string $firstName = ''): void
{
$this->content['firstName'] = $firstName;
}
/**
* @When I specify the last name as :lastName
* @When I do not specify the last name
*/
public function iSpecifyTheLastNameAs(string $lastName = ''): void
{
$this->content['lastName'] = $lastName;
}
/**
* @When I specify the email as :email
* @When I do not specify the email
*/
public function iSpecifyTheEmailAs(string $email = ''): void
{
$this->content['email'] = $email;
}
/**
* @When I specify the password as :password
* @When I do not specify the password
*/
public function iSpecifyThePasswordAs(string $password = ''): void
{
$this->content['password'] = $password;
}
/**
* @When I specify the phone number as :phoneNumber
*/
public function iSpecifyThePhoneNumberAs(string $phoneNumber): void
{
$this->content['phoneNumber'] = $phoneNumber;
}
/**
* @When I confirm this password
*/
public function iConfirmThisPassword(): void
{
// Intentionally left blank
}
/**
* @When I register this account
* @When I try to register this account
*/
public function iRegisterThisAccount(): void
{
$this->client->request(
'POST',
'/new-api/shop/register',
[],
[],
['HTTP_ACCEPT' => 'application/ld+json', 'CONTENT_TYPE' => 'application/ld+json'],
json_encode($this->content, \JSON_THROW_ON_ERROR)
);
$this->content = [];
}
/**
* @Then I should be notified that new account has been successfully created
*/
public function iShouldBeNotifiedThatNewAccountHasBeenSuccessfullyCreated(): void
{
Assert::same($this->client->getResponse()->getStatusCode(), 204);
}
/**
* @Then I should be notified that the first name is required
*/
public function iShouldBeNotifiedThatTheFirstNameIsRequired(): void
{
$this->assertFieldValidationMessage('firstName', 'Please enter your first name.');
}
/**
* @Then I should be notified that the last name is required
*/
public function iShouldBeNotifiedThatTheLastNameIsRequired(): void
{
$this->assertFieldValidationMessage('lastName', 'Please enter your last name.');
}
/**
* @Then I should be notified that the password is required
*/
public function iShouldBeNotifiedThatThePasswordIsRequired(): void
{
$this->assertFieldValidationMessage('password', 'Please enter your password.');
}
/**
* @Then I should be notified that the email is required
*/
public function iShouldBeNotifiedThatTheEmailIsRequired(): void
{
$this->assertFieldValidationMessage('email', 'Please enter your email.');
}
/**
* @Then I should be notified that the email is already used
*/
public function iShouldBeNotifiedThatTheEmailIsAlreadyUsed(): void
{
$this->assertFieldValidationMessage('email', 'This email is already used.');
}
/**
* @Then I should not be logged in
*/
public function iShouldNotBeLoggedIn(): void
{
// Intentionally left blank
}
/**
* @Then I should be logged in
*/
public function iShouldBeLoggedIn(): void
{
// Intentionally left blank
}
private function assertFieldValidationMessage(string $path, string $message): void
{
$decodedResponse = json_decode($this->client->getResponse()->getContent(), true, 512, \JSON_THROW_ON_ERROR);
Assert::keyExists($decodedResponse, 'violations');
Assert::oneOf(
['propertyPath' => $path, 'message' => $message],
$decodedResponse['violations']
);
}
}

View file

@ -28,5 +28,9 @@
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
<argument type="service" id="sylius.behat.shared_storage" />
</service>
<service id="sylius.behat.context.api.shop.registration" class="Sylius\Behat\Context\Api\Shop\RegistrationContext">
<argument type="service" id="test.client" />
</service>
</services>
</container>

View file

@ -2,6 +2,7 @@
# (c) Paweł Jędrzejewski
imports:
- suites/api/account/customer_registration.yml
- suites/api/account/login.yml
- suites/api/admin/login.yml
- suites/api/currency/managing_currencies.yml
@ -36,9 +37,9 @@ imports:
- suites/ui/account/address_book.yml
- suites/ui/account/customer.yml
- suites/ui/account/customer_registration.yml
- suites/ui/account/email_verification.yml
- suites/ui/account/login.yml
- suites/ui/account/registration.yml
- suites/ui/addressing/managing_countries.yml
- suites/ui/addressing/managing_zones.yml
- suites/ui/admin/dashboard.yml

View file

@ -0,0 +1,23 @@
# This file is part of the Sylius package.
# (c) Paweł Jędrzejewski
default:
suites:
api_customer_registration:
contexts:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.transform.customer
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.customer
- sylius.behat.context.setup.locale
- sylius.behat.context.setup.order
- sylius.behat.context.setup.shop_security
- sylius.behat.context.setup.user
- sylius.behat.context.api.shop.registration
filters:
tags: "@customer_registration && @api"

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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;
/**
* @psalm-immutable
*/
class RegisterShopUser
{
/** @var string */
public $firstName;
/** @var string */
public $lastName;
/** @var string */
public $email;
/** @var string */
public $password;
/** @var string|null */
public $phoneNumber;
public function __construct(
string $firstName,
string $lastName,
string $email,
string $password,
?string $phoneNumber = null
) {
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->email = $email;
$this->password = $password;
$this->phoneNumber = $phoneNumber;
}
}

View file

@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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;
use Doctrine\Persistence\ObjectManager;
use Sylius\Bundle\ApiBundle\Command\RegisterShopUser;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Core\Repository\CustomerRepositoryInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class RegisterShopUserHandler implements MessageHandlerInterface
{
/** @var CanonicalizerInterface */
protected $canonicalizer;
/** @var FactoryInterface */
protected $shopUserFactory;
/** @var FactoryInterface */
protected $customerFactory;
/** @var CustomerRepositoryInterface */
protected $customerRepository;
/** @var ObjectManager */
protected $shopUserManager;
public function __construct(
CanonicalizerInterface $canonicalizer,
FactoryInterface $shopUserFactory,
FactoryInterface $customerFactory,
CustomerRepositoryInterface $customerRepository,
ObjectManager $shopUserManager
) {
$this->canonicalizer = $canonicalizer;
$this->shopUserFactory = $shopUserFactory;
$this->customerFactory = $customerFactory;
$this->customerRepository = $customerRepository;
$this->shopUserManager = $shopUserManager;
}
public function __invoke(RegisterShopUser $command): void
{
/** @var ShopUserInterface $user */
$user = $this->shopUserFactory->createNew();
$user->setPlainPassword($command->password);
$customer = $this->provideCustomer($command->email);
$customer->setFirstName($command->firstName);
$customer->setLastName($command->lastName);
$customer->setEmail($command->email);
$customer->setPhoneNumber($command->phoneNumber);
$customer->setUser($user);
$this->shopUserManager->persist($user);
}
protected function provideCustomer(string $email): CustomerInterface
{
$emailCanonical = $this->canonicalizer->canonicalize($email);
/** @var CustomerInterface|null $customer */
$customer = $this->customerRepository->findOneBy(['emailCanonical' => $emailCanonical]);
if ($customer === null) {
/** @var CustomerInterface $customer */
$customer = $this->customerFactory->createNew();
}
if ($customer->getUser() !== null) {
throw new \DomainException(sprintf('User with email "%s" is already registered.', $emailCanonical));
}
return $customer;
}
}

View file

@ -0,0 +1,45 @@
<?xml version="1.0" ?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<resources xmlns="https://api-platform.com/schema/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.5.xsd"
>
<resource class="Sylius\Bundle\ApiBundle\Command\RegisterShopUser" shortName="RegisterShopUser">
<attribute name="messenger">true</attribute>
<attribute name="output">false</attribute>
<attribute name="route_prefix">shop</attribute>
<attribute name="validation_groups">sylius</attribute>
<collectionOperations>
<collectionOperation name="post">
<attribute name="path">/register</attribute>
<attribute name="openapi_context">
<attribute name="summary">Register a shop user</attribute>
</attribute>
<attribute name="validation_groups">
<attribute>sylius</attribute>
</attribute>
</collectionOperation>
</collectionOperations>
<itemOperations />
<property name="firstName" required="true" />
<property name="lastName" required="true" />
<property name="email" required="true" />
<property name="password" required="true" />
</resource>
</resources>

View file

@ -17,6 +17,7 @@
>
<imports>
<import resource="services/applicators.xml" />
<import resource="services/command_handlers.xml" />
<import resource="services/context_builders.xml" />
<import resource="services/data_persisters.xml" />
<import resource="services/data_providers.xml" />
@ -58,5 +59,11 @@
>
<argument type="service" id="sylius.api.swagger_shop_authentication_documentation_normalizer.inner" />
</service>
<service id="sylius.validator.unique_shop_user_email" class="Sylius\Bundle\ApiBundle\Validator\Constraints\UniqueShopUserEmailValidator">
<argument type="service" id="sylius.canonicalizer" />
<argument type="service" id="sylius.repository.shop_user" />
<tag name="validator.constraint_validator" alias="sylius.validator.unique_shop_user_email" />
</service>
</services>
</container>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="Sylius\Bundle\ApiBundle\CommandHandler\RegisterShopUserHandler">
<argument type="service" id="sylius.canonicalizer" />
<argument type="service" id="sylius.factory.shop_user" />
<argument type="service" id="sylius.factory.customer" />
<argument type="service" id="sylius.repository.customer" />
<argument type="service" id="sylius.manager.shop_user" />
<tag name="messenger.message_handler" />
</service>
</services>
</container>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Bundle\ApiBundle\Command\RegisterShopUser">
<property name="firstName">
<constraint name="NotBlank">
<option name="message">sylius.customer.first_name.not_blank</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
<property name="lastName">
<constraint name="NotBlank">
<option name="message">sylius.customer.last_name.not_blank</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
<property name="password">
<constraint name="NotBlank">
<option name="message">sylius.user.plainPassword.not_blank</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Length">
<option name="min">4</option>
<option name="minMessage">sylius.user.password.min</option>
<option name="max">254</option>
<option name="maxMessage">sylius.user.password.max</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
<property name="email">
<constraint name="NotBlank">
<option name="message">sylius.customer.email.not_blank</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Length">
<option name="max">254</option>
<option name="maxMessage">sylius.customer.email.max</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Email">
<option name="message">sylius.customer.email.invalid</option>
<option name="strict">true</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\UniqueShopUserEmail">
<option name="message">sylius.user.email.unique</option>
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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;
final class UniqueShopUserEmail extends Constraint
{
/** @var string */
public $message = 'sylius.user.email.unique';
public function validatedBy(): string
{
return 'sylius.validator.unique_shop_user_email';
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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 Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
final class UniqueShopUserEmailValidator extends ConstraintValidator
{
/** @var CanonicalizerInterface */
private $canonicalizer;
/** @var UserRepositoryInterface */
private $shopUserRepository;
public function __construct(CanonicalizerInterface $canonicalizer, UserRepositoryInterface $shopUserRepository)
{
$this->canonicalizer = $canonicalizer;
$this->shopUserRepository = $shopUserRepository;
}
public function validate($value, Constraint $constraint): void
{
if ($value === null) {
return;
}
/** @var UniqueShopUserEmail $constraint */
Assert::isInstanceOf($constraint, UniqueShopUserEmail::class);
$emailCanonical = $this->canonicalizer->canonicalize($value);
$shopUser = $this->shopUserRepository->findOneByEmail($emailCanonical);
if ($shopUser === null) {
return;
}
$this->context->addViolation($constraint->message);
}
}

View file

@ -23,6 +23,7 @@
"php": "^7.3",
"api-platform/core": "^2.5",
"symfony/messenger": "^4.4",
"sylius/core-bundle": "^1.7"
},
"require-dev": {

View file

@ -0,0 +1,131 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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 Doctrine\Persistence\ObjectManager;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Command\RegisterShopUser;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Core\Repository\CustomerRepositoryInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final class RegisterShopUserHandlerSpec extends ObjectBehavior
{
function let(
CanonicalizerInterface $canonicalizer,
FactoryInterface $shopUserFactory,
FactoryInterface $customerFactory,
CustomerRepositoryInterface $customerRepository,
ObjectManager $shopUserManager
): void {
$this->beConstructedWith(
$canonicalizer,
$shopUserFactory,
$customerFactory,
$customerRepository,
$shopUserManager
);
}
function it_is_a_message_handler(): void
{
$this->shouldImplement(MessageHandlerInterface::class);
}
function it_creates_a_customer_and_user_with_given_data(
CanonicalizerInterface $canonicalizer,
FactoryInterface $shopUserFactory,
FactoryInterface $customerFactory,
CustomerRepositoryInterface $customerRepository,
ObjectManager $shopUserManager,
ShopUserInterface $shopUser,
CustomerInterface $customer
): void {
$canonicalizer->canonicalize('WILL.SMITH@example.com')->willReturn('will.smith@example.com');
$customerRepository->findOneBy(['emailCanonical' => 'will.smith@example.com'])->willReturn(null);
$shopUserFactory->createNew()->willReturn($shopUser);
$customerFactory->createNew()->willReturn($customer);
$customer->getUser()->willReturn(null);
$shopUser->setPlainPassword('iamrobot')->shouldBeCalled();
$customer->setFirstName('Will')->shouldBeCalled();
$customer->setLastName('Smith')->shouldBeCalled();
$customer->setEmail('WILL.SMITH@example.com')->shouldBeCalled();
$customer->setPhoneNumber('+13104322400')->shouldBeCalled();
$customer->setUser($shopUser)->shouldBeCalled();
$shopUserManager->persist($shopUser)->shouldBeCalled();
$this(new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400'));
}
function it_creates_only_a_user_if_customer_without_user_already_exists(
CanonicalizerInterface $canonicalizer,
FactoryInterface $shopUserFactory,
FactoryInterface $customerFactory,
CustomerRepositoryInterface $customerRepository,
ObjectManager $shopUserManager,
ShopUserInterface $shopUser,
CustomerInterface $customer
): void {
$canonicalizer->canonicalize('WILL.SMITH@example.com')->willReturn('will.smith@example.com');
$customerRepository->findOneBy(['emailCanonical' => 'will.smith@example.com'])->willReturn($customer);
$shopUserFactory->createNew()->willReturn($shopUser);
$customerFactory->createNew()->shouldNotBeCalled();
$customer->getUser()->willReturn(null);
$shopUser->setPlainPassword('iamrobot')->shouldBeCalled();
$customer->setFirstName('Will')->shouldBeCalled();
$customer->setLastName('Smith')->shouldBeCalled();
$customer->setEmail('WILL.SMITH@example.com')->shouldBeCalled();
$customer->setPhoneNumber('+13104322400')->shouldBeCalled();
$customer->setUser($shopUser)->shouldBeCalled();
$shopUserManager->persist($shopUser)->shouldBeCalled();
$this(new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400'));
}
function it_throws_an_exception_if_customer_with_user_already_exists(
CanonicalizerInterface $canonicalizer,
FactoryInterface $shopUserFactory,
FactoryInterface $customerFactory,
CustomerRepositoryInterface $customerRepository,
ObjectManager $shopUserManager,
ShopUserInterface $shopUser,
CustomerInterface $customer,
ShopUserInterface $existingShopUser
): void {
$canonicalizer->canonicalize('WILL.SMITH@example.com')->willReturn('will.smith@example.com');
$customerRepository->findOneBy(['emailCanonical' => 'will.smith@example.com'])->willReturn($customer);
$customer->getUser()->willReturn($existingShopUser);
$shopUserFactory->createNew()->willReturn($shopUser);
$customerFactory->createNew()->shouldNotBeCalled();
$shopUserManager->persist($shopUser)->shouldNotBeCalled();
$this->shouldThrow(\DomainException::class)->during('__invoke', [new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400')]);
}
}

View file

@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\Validator\Constraints;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\ApiBundle\Validator\Constraints\UniqueShopUserEmail;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
final class UniqueShopUserEmailValidatorSpec extends ObjectBehavior
{
function let(
CanonicalizerInterface $canonicalizer,
UserRepositoryInterface $shopUserRepository,
ExecutionContextInterface $executionContext
): void {
$this->beConstructedWith($canonicalizer, $shopUserRepository);
$this->initialize($executionContext);
}
function it_is_a_constraint_validator(): void
{
$this->shouldImplement(ConstraintValidatorInterface::class);
}
function is_does_nothing_if_value_is_null(ExecutionContextInterface $executionContext): void
{
$executionContext->addViolation(Argument::cetera())->shouldNotBeCalled();
$this->validate(null, new UniqueShopUserEmail());
}
function it_throws_an_exception_if_constraint_is_not_of_expected_type(): void
{
$this->shouldThrow(\InvalidArgumentException::class)->during('validate', ['', new class() extends Constraint {
}]);
}
function it_does_not_add_violation_if_a_user_with_given_email_is_not_found(
CanonicalizerInterface $canonicalizer,
UserRepositoryInterface $shopUserRepository,
ExecutionContextInterface $executionContext
): void {
$canonicalizer->canonicalize('eMaIl@example.com')->willReturn('email@example.com');
$shopUserRepository->findOneByEmail('email@example.com')->willReturn(null);
$executionContext->addViolation(Argument::cetera())->shouldNotBeCalled();
$this->validate('eMaIl@example.com', new UniqueShopUserEmail());
}
function it_adds_violation_if_a_user_with_given_email_is_found(
CanonicalizerInterface $canonicalizer,
UserRepositoryInterface $shopUserRepository,
ExecutionContextInterface $executionContext,
ShopUserInterface $shopUser
): void {
$canonicalizer->canonicalize('eMaIl@example.com')->willReturn('email@example.com');
$shopUserRepository->findOneByEmail('email@example.com')->willReturn($shopUser);
$executionContext->addViolation(Argument::cetera())->shouldBeCalled();
$this->validate('eMaIl@example.com', new UniqueShopUserEmail());
}
}

View file

@ -352,6 +352,18 @@
"symfony/intl": {
"version": "v4.1.3"
},
"symfony/messenger": {
"version": "4.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.3",
"ref": "8a2675c061737658bed85102e9241c752620e575"
},
"files": [
"config/packages/messenger.yaml"
]
},
"symfony/monolog-bridge": {
"version": "v4.1.3"
},