Cover preventing to view customers cart from the orders resource

This commit is contained in:
Kamil Grygierzec 2023-12-18 15:37:47 +01:00 committed by Jacob Tobiasz
parent b090f472c6
commit d6b36dafc9
No known key found for this signature in database
GPG key ID: 3F84290201B006E0
9 changed files with 304 additions and 6 deletions

View file

@ -10,7 +10,7 @@ Feature: Being unable to see details of a cart
And the customer added "PHP T-Shirt" product to the cart
And I am logged in as an administrator
@ui
Scenario: Seeing basic information about an order
@ui @api
Scenario: Being unable to see the details of a cart
When I try to view the summary of the customer's latest cart
Then I should be informed that the order does not exist

View file

@ -16,6 +16,7 @@ namespace Sylius\Behat\Context\Api\Admin;
use ApiPlatform\Api\IriConverterInterface;
use Behat\Behat\Context\Context;
use Sylius\Behat\Client\ApiClientInterface;
use Sylius\Behat\Client\RequestFactoryInterface;
use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Context\Api\Subresources;
@ -29,6 +30,7 @@ use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\Component\Core\Model\ShipmentInterface;
use Sylius\Component\Core\Model\ShippingMethodInterface;
use Sylius\Component\Currency\Model\CurrencyInterface;
use Sylius\Component\Order\OrderTransitions;
@ -43,6 +45,7 @@ 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,
@ -104,6 +107,30 @@ final class ManagingOrdersContext implements Context
$this->client->addFilter('checkoutCompletedAt[after]', $dateTime);
}
/**
* @When specify its tracking code as :trackingCode
*/
public function specifyItsTrackingCodeAs(string $trackingCode): void
{
$shipment = $this->sharedStorage->get('order')->getShipments()->first();
$this->client->buildUpdateRequest(
Resources::SHIPMENTS,
(string) $shipment->getId(),
);
$this->client->addRequestData('tracking', $trackingCode);
$this->client->update();
}
/**
* @When /^I try to view the summary of the (customer's latest cart)$/
*/
public function iTryToViewTheSummaryOfTheCustomersLatestCart(OrderInterface $cart): void
{
$this->client->show(Resources::ORDERS, $cart->getTokenValue());
}
/**
* @When I specify filter date to as :dateTime
*/
@ -989,6 +1016,77 @@ final class ManagingOrdersContext implements Context
Assert::same($this->getTotalAsInt($subTotal), $orderItem['unitPrice'] * $orderItem['quantity'] + $unitPromotionAdjustments);
}
/**
* @Then I should be notified that the order has been successfully shipped
*/
public function iShouldBeNotifiedThatTheOrderHasBeenSuccessfullyShipped(): void
{
$response = $this->client->getLastResponse();
Assert::true(
$this->responseChecker->isUpdateSuccessful($response),
'Order could not be shipped. Reason: ' . $response->getContent(),
);
}
/**
* @Then it should have shipment in state shipped
*/
public function itShouldHaveShipmentInStateShipped(): void
{
$shipmentIri = $this->responseChecker->getValue(
$this->client->show(Resources::ORDERS, $this->sharedStorage->get('order')->getTokenValue()),
'shipments',
)[0];
Assert::true(
$this->responseChecker->hasValue($this->client->showByIri($shipmentIri['@id']), 'state', ShipmentInterface::STATE_SHIPPED),
sprintf('Shipment for this order is not %s', ShipmentInterface::STATE_SHIPPED),
);
}
/**
* @Then this order should have order shipping state :orderShippingState
*/
public function thisOrderShouldHaveOrderShippingState(string $orderShippingState): void
{
$ordersResponse = $this->client->index(Resources::ORDERS);
Assert::true(
$this->responseChecker->hasItemWithValue($ordersResponse, 'shippingState', strtolower($orderShippingState)),
sprintf('Order does not have %s shipping state', $orderShippingState),
);
}
/**
* @Then I should not be able to ship this order
*/
public function iShouldNotBeAbleToShipThisOrder(): void
{
$order = $this->sharedStorage->get('order');
$this->client->applyTransition(
Resources::SHIPMENTS,
(string) $order->getShipments()->first()->getId(),
ShipmentTransitions::TRANSITION_SHIP,
);
Assert::false(
$this->responseChecker->isUpdateSuccessful($this->client->getLastResponse()),
'Order has been shipped, but should not.',
);
}
/**
* @Then I should be informed that the order does not exist
*/
public function iShouldBeInformedThatTheOrderDoesNotExist(): void
{
Assert::same(
$this->responseChecker->getError($this->client->getLastResponse()),
'Not Found',
);
}
/**
* @Then there should be :count shipping address changes in the registry
*/

View file

@ -66,6 +66,7 @@ final class CartContext implements Context
* @Given /^I (?:have|had) (product "[^"]+") in the (cart)$/
* @Given /^I have (product "[^"]+") added to the (cart)$/
* @Given /^the (?:customer|visitor) has (product "[^"]+") in the (cart)$/
* @Given /^the (?:customer|visitor) added ("[^"]+" product) to the (cart)$/
* @When /^the (?:customer|visitor) try to add (product "[^"]+") in the customer (cart)$/
*/
public function iAddedProductToTheCart(ProductInterface $product, ?string $tokenValue): void

View file

@ -0,0 +1,71 @@
<?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\Doctrine\QueryExtension;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\ContextAwareQueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\OrderInterface;
/** @experimental */
final class OrderExtension implements ContextAwareQueryCollectionExtensionInterface, QueryItemExtensionInterface
{
/**
* @param array<string> $orderStatesToFilterOut
*/
public function __construct(
private UserContextInterface $userContext,
private array $orderStatesToFilterOut,
) {
}
/**
* @param array<mixed> $context
*/
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
string $operationName = null,
array $context = [],
): void {
if (!is_a($resourceClass, OrderInterface::class, true)) {
return;
}
$user = $this->userContext->getUser();
if ($user instanceof AdminUserInterface && in_array('ROLE_API_ACCESS', $user->getRoles(), true)) {
$stateParameter = $queryNameGenerator->generateParameterName('state');
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder
->andWhere(sprintf('%s.state != :%s', $rootAlias, $stateParameter))
->setParameter($stateParameter, $this->orderStatesToFilterOut)
;
}
}
/**
* @param array<mixed> $context
* @param array<mixed> $identifiers
*/
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void
{
$this->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
}
}

View file

@ -129,6 +129,15 @@
<tag name="api_platform.doctrine.orm.query_extension.item" />
</service>
<service id="Sylius\Bundle\ApiBundle\Doctrine\QueryExtension\OrderExtension">
<argument type="service" id="Sylius\Bundle\ApiBundle\Context\UserContextInterface" />
<argument type="collection" >
<argument type="constant">Sylius\Component\Order\Model\OrderInterface::STATE_CART</argument>
</argument>
<tag name="api_platform.doctrine.orm.query_extension.collection" />
<tag name="api_platform.doctrine.orm.query_extension.item" />
</service>
<service id="Sylius\Bundle\ApiBundle\Doctrine\QueryItemExtension\TaxonItemExtension">
<argument type="service" id="Sylius\Bundle\ApiBundle\Context\UserContextInterface" />
<tag name="api_platform.doctrine.orm.query_extension.item" />

View file

@ -0,0 +1,118 @@
<?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\Doctrine\QueryExtension;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
use Sylius\Component\Core\Model\AdminUserInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Symfony\Component\HttpFoundation\Request;
final class OrderExtensionSpec extends ObjectBehavior
{
function let(UserContextInterface $userContext): void
{
$this->beConstructedWith($userContext, ['cart']);
}
function it_does_not_apply_conditions_to_collection_for_shop_user(
UserContextInterface $userContext,
QueryBuilder $queryBuilder,
ShopUserInterface $shopUser,
QueryNameGeneratorInterface $queryNameGenerator,
): void {
$userContext->getUser()->willReturn($shopUser);
$queryBuilder->getRootAliases()->shouldNotBeCalled();
$this->applyToCollection(
$queryBuilder,
$queryNameGenerator,
OrderInterface::class,
Request::METHOD_GET,
[],
);
}
function it_does_not_apply_conditions_to_item_for_shop_user(
UserContextInterface $userContext,
QueryBuilder $queryBuilder,
ShopUserInterface $shopUser,
QueryNameGeneratorInterface $queryNameGenerator,
): void {
$userContext->getUser()->willReturn($shopUser);
$queryBuilder->getRootAliases()->shouldNotBeCalled();
$this->applyToItem(
$queryBuilder,
$queryNameGenerator,
OrderInterface::class,
[],
Request::METHOD_GET,
[],
);
}
function it_applies_conditions_to_collection_for_admin(
UserContextInterface $userContext,
AdminUserInterface $adminUser,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
): void {
$userContext->getUser()->willReturn($adminUser);
$adminUser->getRoles()->willReturn(['ROLE_API_ACCESS']);
$queryBuilder->getRootAliases()->willReturn(['o']);
$queryNameGenerator->generateParameterName('state')->shouldBeCalled()->willReturn('state');
$queryBuilder->andWhere('o.state != :state')->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
$queryBuilder->setParameter('state', ['cart'])->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
$this->applyToCollection(
$queryBuilder,
$queryNameGenerator,
OrderInterface::class,
Request::METHOD_GET,
);
}
function it_applies_conditions_to_item_for_admin(
UserContextInterface $userContext,
AdminUserInterface $adminUser,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
): void {
$queryBuilder->getRootAliases()->willReturn(['o']);
$userContext->getUser()->willReturn($adminUser);
$adminUser->getRoles()->willReturn(['ROLE_API_ACCESS']);
$queryBuilder->getRootAliases()->willReturn(['o']);
$queryNameGenerator->generateParameterName('state')->shouldBeCalled()->willReturn('state');
$queryBuilder->andWhere('o.state != :state')->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
$queryBuilder->setParameter('state', ['cart'])->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
$this->applyToItem(
$queryBuilder,
$queryNameGenerator,
OrderInterface::class,
[],
Request::METHOD_GET,
);
}
}

View file

@ -72,8 +72,8 @@ final class OrdersTest extends JsonApiTestCase
$fixtures = $this->loadFixturesFromFiles([
'authentication/api_administrator.yaml',
'channel.yaml',
'customer.yaml',
'customer_order.yaml',
'order/customer.yaml',
'order/fulfilled_order.yaml',
]);
/** @var CustomerInterface $customer */

View file

@ -4,6 +4,7 @@ Sylius\Component\Core\Model\Order:
currencyCode: "USD"
localeCode: "en_US"
state: "fulfilled"
checkoutState: "fulfilled"
tokenValue: "token"
customer: "@customer_tony"
billingAddress: "@billing_address"

View file

@ -38,7 +38,7 @@
"shipments": [],
"currencyCode": "USD",
"localeCode": "en_US",
"checkoutState": "cart",
"checkoutState": "fulfilled",
"paymentState": "cart",
"shippingState": "cart",
"tokenValue": "token",
@ -46,7 +46,7 @@
"items": [],
"itemsTotal": 0,
"total": 0,
"state": "new",
"state": "fulfilled",
"itemsSubtotal": 0,
"taxTotal": 0,
"shippingTaxTotal": 0,