From d6b36dafc91d8c72c9de3d71e09b0eb1a5c35185 Mon Sep 17 00:00:00 2001 From: Kamil Grygierzec Date: Mon, 18 Dec 2023 15:37:47 +0100 Subject: [PATCH] Cover preventing to view customers cart from the orders resource --- .../being_unable_to_see_cart.feature | 4 +- .../Api/Admin/ManagingOrdersContext.php | 98 +++++++++++++++ .../Behat/Context/Setup/CartContext.php | 1 + .../QueryExtension/OrderExtension.php | 71 +++++++++++ .../Resources/config/services/extensions.xml | 9 ++ .../QueryExtension/OrderExtensionSpec.php | 118 ++++++++++++++++++ tests/Api/Admin/OrdersTest.php | 4 +- .../ORM/order/fulfilled_order.yaml | 1 + .../gets_orders_for_customer_response.json | 4 +- 9 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 src/Sylius/Bundle/ApiBundle/Doctrine/QueryExtension/OrderExtension.php create mode 100644 src/Sylius/Bundle/ApiBundle/spec/Doctrine/QueryExtension/OrderExtensionSpec.php diff --git a/features/order/managing_orders/order_details/being_unable_to_see_cart.feature b/features/order/managing_orders/order_details/being_unable_to_see_cart.feature index 92e846a0dd..a88a25b0a8 100644 --- a/features/order/managing_orders/order_details/being_unable_to_see_cart.feature +++ b/features/order/managing_orders/order_details/being_unable_to_see_cart.feature @@ -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 diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingOrdersContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingOrdersContext.php index 92bf3eacd9..1ce4b1c591 100644 --- a/src/Sylius/Behat/Context/Api/Admin/ManagingOrdersContext.php +++ b/src/Sylius/Behat/Context/Api/Admin/ManagingOrdersContext.php @@ -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 */ diff --git a/src/Sylius/Behat/Context/Setup/CartContext.php b/src/Sylius/Behat/Context/Setup/CartContext.php index c87952f061..81f7bdfd8e 100644 --- a/src/Sylius/Behat/Context/Setup/CartContext.php +++ b/src/Sylius/Behat/Context/Setup/CartContext.php @@ -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 diff --git a/src/Sylius/Bundle/ApiBundle/Doctrine/QueryExtension/OrderExtension.php b/src/Sylius/Bundle/ApiBundle/Doctrine/QueryExtension/OrderExtension.php new file mode 100644 index 0000000000..f51d3b9e57 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Doctrine/QueryExtension/OrderExtension.php @@ -0,0 +1,71 @@ + $orderStatesToFilterOut + */ + public function __construct( + private UserContextInterface $userContext, + private array $orderStatesToFilterOut, + ) { + } + + /** + * @param array $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 $context + * @param array $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); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/extensions.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/extensions.xml index 22a4d7df2e..962c0fd0af 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/extensions.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/extensions.xml @@ -129,6 +129,15 @@ + + + + Sylius\Component\Order\Model\OrderInterface::STATE_CART + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/spec/Doctrine/QueryExtension/OrderExtensionSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Doctrine/QueryExtension/OrderExtensionSpec.php new file mode 100644 index 0000000000..16cee480eb --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/Doctrine/QueryExtension/OrderExtensionSpec.php @@ -0,0 +1,118 @@ +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, + ); + } +} diff --git a/tests/Api/Admin/OrdersTest.php b/tests/Api/Admin/OrdersTest.php index 689faca3ba..7c95a49a9c 100644 --- a/tests/Api/Admin/OrdersTest.php +++ b/tests/Api/Admin/OrdersTest.php @@ -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 */ diff --git a/tests/Api/DataFixtures/ORM/order/fulfilled_order.yaml b/tests/Api/DataFixtures/ORM/order/fulfilled_order.yaml index 7d18c8383f..7e2bf69d8a 100644 --- a/tests/Api/DataFixtures/ORM/order/fulfilled_order.yaml +++ b/tests/Api/DataFixtures/ORM/order/fulfilled_order.yaml @@ -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" diff --git a/tests/Api/Responses/Expected/admin/order/gets_orders_for_customer_response.json b/tests/Api/Responses/Expected/admin/order/gets_orders_for_customer_response.json index 5405ec6af0..b596b5a87b 100644 --- a/tests/Api/Responses/Expected/admin/order/gets_orders_for_customer_response.json +++ b/tests/Api/Responses/Expected/admin/order/gets_orders_for_customer_response.json @@ -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,