Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ApiBundle][Admin] Implement Shop User Removal and Password Change #15707

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions features/user/managing_users/deleting_account.feature
jakubtobiasz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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
@ui @no-api
NoResponseMate marked this conversation as resolved.
Show resolved Hide resolved
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
51 changes: 51 additions & 0 deletions src/Sylius/Behat/Context/Api/Admin/ManagingCustomersContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
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;

Expand All @@ -31,6 +33,7 @@ public function __construct(
private ApiClientInterface $client,
private ResponseCheckerInterface $responseChecker,
private IriConverterInterface $iriConverter,
private SharedStorageInterface $sharedStorage,
) {
}

Expand Down Expand Up @@ -210,6 +213,26 @@ public function iSortThemBy(string $sortType = 'ascending'): void
]);
}

/**
* @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->client->delete(sprintf('customer/%s', $shopUser->getCustomer()->getId()), 'user');

$this->sharedStorage->set('deleted_user', $shopUser);
}

/**
* @When I do not specify any information
* @When I do not choose create account option
Expand Down Expand Up @@ -561,6 +584,21 @@ public function thisCustomerShouldHaveAsTheirGroup(CustomerGroupInterface $custo
);
}

/**
* @Then the customer with this email should still exist
*/
public function customerShouldStillExist(): void
{
$deletedUser = $this->sharedStorage->get('deleted_user');

$this->client->show(Resources::CUSTOMERS, (string) $deletedUser->getCustomer()->getId());

Assert::same(
$this->responseChecker->getValue($this->client->getLastResponse(), 'email'),
$deletedUser->getCustomer()->getEmail(),
);
}

/**
* @Then I should not see create account option
* @Then I should still be on the customer creation page
Expand All @@ -572,4 +610,17 @@ public function thisCustomerShouldHaveAsTheirGroup(CustomerGroupInterface $custo
public function intentionallyLeftBlank(): void
{
}

/**
* @Then the user account should be deleted
*/
public function accountShouldBeDeleted(): void
{
/** @var ShopUserInterface $deletedUser */
$deletedUser = $this->sharedStorage->get('deleted_user');

$response = $this->client->show(Resources::CUSTOMERS, (string) $deletedUser->getCustomer()->getId());

Assert::null($this->responseChecker->getValue($response, 'user'));
}
}
38 changes: 38 additions & 0 deletions src/Sylius/Behat/Context/Transform/ShopUserContext.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
1 change: 1 addition & 0 deletions src/Sylius/Behat/Resources/config/suites.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ 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/installer.yml
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
34 changes: 34 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Command/Customer/RemoveShopUser.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}