mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
Merge branch '1.12' into 1.13
* 1.12: [PostgreSQL] Handle DriverException gracefully with 22P02 SQLState [Orders][Shop] Improve order item quantity change validation [Unit][Orders] Add tests for items quantity change [Orders][Shop] Add checkout completion validation [Unit][Orders] Add tests for checkout completion [Unit][Orders] Add tests for preventing from order completion [Orders][Shop] Add shipment validation when trying to assign shipping method [Unit][Orders] Add test for validating shipmentId when trying to assign shipping method [Unit][Utils] Introduce ContentType helper class [Orders] Add missing order payment configuration endpoint validation [Unit][Orders][Shop] Add test for validation invalid payment param [Unit][Orders][Shop] Add test for invalid uri param validation [Unit][Orders][Shop] Extract helper methods
This commit is contained in:
commit
1d46d40031
29 changed files with 1227 additions and 148 deletions
|
|
@ -13,12 +13,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\ApiBundle\Controller\Payment;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Provider\CompositePaymentConfigurationProviderInterface;
|
||||
use Sylius\Component\Core\Model\PaymentInterface;
|
||||
use Sylius\Component\Core\Repository\PaymentRepositoryInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @experimental */
|
||||
final class GetPaymentConfiguration
|
||||
|
|
@ -37,7 +37,9 @@ final class GetPaymentConfiguration
|
|||
$request->attributes->get('tokenValue'),
|
||||
);
|
||||
|
||||
Assert::notNull($payment);
|
||||
if ($payment === null) {
|
||||
throw new PaymentNotFoundException();
|
||||
}
|
||||
|
||||
return new JsonResponse($this->compositePaymentConfigurationProvider->provide($payment));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
<?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\EventListener;
|
||||
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
|
||||
final class PostgreSQLDriverExceptionListener
|
||||
{
|
||||
public function onKernelException(ExceptionEvent $event): void
|
||||
{
|
||||
$exception = $event->getThrowable();
|
||||
|
||||
if ($exception instanceof DriverException && $exception->getSQLState() === '22P02') {
|
||||
$event
|
||||
->setResponse(
|
||||
new JSONResponse(
|
||||
['message' => 'Invalid URL parameter for type integer'],
|
||||
$event->getRequest()->getMethod() === Request::METHOD_GET ? Response::HTTP_NOT_FOUND : Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
),
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?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 PaymentNotFoundException extends NotFoundHttpException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Payment not found.');
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +126,10 @@
|
|||
<tag name="kernel.event_listener" event="lexik_jwt_authentication.on_authentication_success" method="onAuthenticationSuccessResponse" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.listener.api_postgresql_driver_exception_listener" class="Sylius\Bundle\ApiBundle\EventListener\PostgreSQLDriverExceptionListener">
|
||||
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Converter\IriToIdentifierConverterInterface" class="Sylius\Bundle\ApiBundle\Converter\IriToIdentifierConverter">
|
||||
<argument type="service" id="api_platform.router" />
|
||||
<argument type="service" id="api_platform.identifier.converter" on-invalid="ignore" />
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@
|
|||
<tag name="validator.constraint_validator" alias="sylius_api_validator_order_shipping_method_eligibility" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\Constraints\CheckoutCompletionValidator">
|
||||
<argument type="service" id="sylius.repository.order" />
|
||||
<argument type="service" id="sm.factory" />
|
||||
<tag name="validator.constraint_validator" alias="sylius_api_validator_checkout_completion" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\Constraints\ChosenShippingMethodEligibilityValidator">
|
||||
<argument type="service" id="sylius.repository.shipment" />
|
||||
<argument type="service" id="sylius.repository.shipping_method" />
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@
|
|||
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\Checkout\CompleteOrder">
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\CheckoutCompletion">
|
||||
<option name="groups">
|
||||
<value>sylius_checkout_complete</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\OrderNotEmpty">
|
||||
<option name="groups">
|
||||
<value>sylius_checkout_complete</value>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ sylius:
|
|||
not_sufficient: 'The product variant with %productVariantCode% code does not have sufficient stock.'
|
||||
product_variant_with_name_not_sufficient: 'The product variant with %productVariantName% name does not have sufficient stock.'
|
||||
shipment:
|
||||
not_found: 'The shipment does not exist.'
|
||||
shipped: 'You cannot ship a shipment that was shipped before.'
|
||||
shipping_method:
|
||||
not_found: 'The shipping method with %code% code does not exist.'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
<?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\Tests\Listener;
|
||||
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\EventListener\PostgreSQLDriverExceptionListener;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
class PostgreSQLDriverExceptionListenerTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_does_nothing_if_exception_is_not_a_driver_exception(): void
|
||||
{
|
||||
$event = $this->createExceptionEvent();
|
||||
|
||||
(new PostgreSQLDriverExceptionListener())->onKernelException($event);
|
||||
|
||||
$this->assertNull($event->getResponse());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_nothing_if_exception_is_a_driver_exception_but_sql_state_is_not_22P02(): void
|
||||
{
|
||||
$event = $this->createDriverExceptionEvent('some_other_sql_state', Request::METHOD_PATCH);
|
||||
|
||||
(new PostgreSQLDriverExceptionListener())->onKernelException($event);
|
||||
|
||||
$this->assertNull($event->getResponse());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_responses_with_not_found_status_if_exception_is_a_driver_exception_with_sql_state_22P02_and_request_method_is_get(): void
|
||||
{
|
||||
$event = $this->createDriverExceptionEvent('22P02', Request::METHOD_GET);
|
||||
|
||||
(new PostgreSQLDriverExceptionListener())->onKernelException($event);
|
||||
|
||||
$response = $event->getResponse();
|
||||
|
||||
$this->assertNotNull($response);
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode());
|
||||
$this->assertEquals(json_encode(['message' => 'Invalid URL parameter for type integer']), $response->getContent());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_responses_with_unprocessable_entity_status_if_exception_is_a_driver_exception_with_sql_state_22P02_and_request_method_is_not_get(): void
|
||||
{
|
||||
$event = $this->createDriverExceptionEvent('22P02', Request::METHOD_PATCH);
|
||||
|
||||
(new PostgreSQLDriverExceptionListener())->onKernelException($event);
|
||||
|
||||
$response = $event->getResponse();
|
||||
|
||||
$this->assertNotNull($response);
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$this->assertEquals(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode());
|
||||
$this->assertEquals(json_encode(['message' => 'Invalid URL parameter for type integer']), $response->getContent());
|
||||
}
|
||||
|
||||
private function createExceptionEvent(): ExceptionEvent
|
||||
{
|
||||
$kernel = $this->createMock(HttpKernelInterface::class);
|
||||
$exception = new \Exception();
|
||||
|
||||
return new ExceptionEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $exception);
|
||||
}
|
||||
|
||||
private function createDriverExceptionEvent(string $sqlState, string $requestMethod): ExceptionEvent
|
||||
{
|
||||
$kernel = $this->createMock(HttpKernelInterface::class);
|
||||
|
||||
$exception = $this->getMockBuilder(DriverException::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getSQLState'])
|
||||
->getMock();
|
||||
|
||||
$request = new Request();
|
||||
$request->setMethod($requestMethod);
|
||||
|
||||
$exception->method('getSQLState')->willReturn($sqlState);
|
||||
|
||||
return new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,12 +14,13 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Cart\ChangeItemQuantityInCart;
|
||||
use Sylius\Bundle\ApiBundle\Exception\OrderItemNotFoundException;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\OrderItemInterface;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Core\Repository\OrderItemRepositoryInterface;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
|
@ -27,7 +28,7 @@ use Webmozart\Assert\Assert;
|
|||
final class ChangedItemQuantityInCartValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private RepositoryInterface $orderItemRepository,
|
||||
private OrderItemRepositoryInterface $orderItemRepository,
|
||||
private OrderRepositoryInterface $orderRepository,
|
||||
private AvailabilityCheckerInterface $availabilityChecker,
|
||||
) {
|
||||
|
|
@ -45,8 +46,14 @@ final class ChangedItemQuantityInCartValidator extends ConstraintValidator
|
|||
}
|
||||
|
||||
/** @var OrderItemInterface|null $orderItem */
|
||||
$orderItem = $this->orderItemRepository->findOneBy(['id' => $value->orderItemId]);
|
||||
Assert::notNull($orderItem);
|
||||
$orderItem = $this->orderItemRepository->findOneByIdAndCartTokenValue(
|
||||
$value->orderItemId,
|
||||
$value->orderTokenValue,
|
||||
);
|
||||
|
||||
if ($orderItem === null) {
|
||||
throw new OrderItemNotFoundException();
|
||||
}
|
||||
|
||||
$productVariant = $orderItem->getVariant();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
class CheckoutCompletion extends Constraint
|
||||
{
|
||||
public string $message = 'sylius.order.invalid_state_transition';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_api_validator_checkout_completion';
|
||||
}
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?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 SM\Factory\FactoryInterface;
|
||||
use Sylius\Bundle\ApiBundle\Command\OrderTokenValueAwareInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\OrderCheckoutTransitions;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @experimental */
|
||||
class CheckoutCompletionValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private OrderRepositoryInterface $orderRepository,
|
||||
private FactoryInterface $stateMachineFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @param OrderTokenValueAwareInterface $value */
|
||||
public function validate($value, Constraint $constraint): void
|
||||
{
|
||||
Assert::isInstanceOf($value, OrderTokenValueAwareInterface::class);
|
||||
|
||||
/** @var CheckoutCompletion $constraint */
|
||||
Assert::isInstanceOf($constraint, CheckoutCompletion::class);
|
||||
|
||||
$order = $this->orderRepository->findOneBy(['tokenValue' => $value->getOrderTokenValue()]);
|
||||
|
||||
/** @var OrderInterface $order */
|
||||
Assert::isInstanceOf($order, OrderInterface::class);
|
||||
|
||||
$stateMachine = $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);
|
||||
|
||||
if ($stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->context->addViolation($constraint->message, [
|
||||
'%currentState%' => $stateMachine->getState(),
|
||||
'%possibleTransitions%' => implode(', ', $stateMachine->getPossibleTransitions()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ final class ChosenShippingMethodEligibility extends Constraint
|
|||
|
||||
public string $notFoundMessage = 'sylius.shipping_method.not_found';
|
||||
|
||||
public string $shipmentNotFoundMessage = 'sylius.shipment.not_found';
|
||||
|
||||
/** @var string */
|
||||
public $shippingAddressNotFoundMessage = 'sylius.shipping_method.shipping_address_not_found';
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,12 @@ final class ChosenShippingMethodEligibilityValidator extends ConstraintValidator
|
|||
|
||||
/** @var ShipmentInterface|null $shipment */
|
||||
$shipment = $this->shipmentRepository->find($value->shipmentId);
|
||||
Assert::notNull($shipment);
|
||||
|
||||
if (null === $shipment) {
|
||||
$this->context->addViolation($constraint->shipmentNotFoundMessage);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$order = $shipment->getOrder();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use PhpSpec\ObjectBehavior;
|
|||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\ApiBundle\Command\Cart\ChangeItemQuantityInCart;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder;
|
||||
use Sylius\Bundle\ApiBundle\Exception\OrderItemNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\AddingEligibleProductVariantToCart;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\ChangedItemQuantityInCart;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
|
|
@ -24,9 +25,9 @@ use Sylius\Component\Core\Model\OrderInterface;
|
|||
use Sylius\Component\Core\Model\OrderItemInterface;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Core\Model\ProductVariantInterface;
|
||||
use Sylius\Component\Core\Repository\OrderItemRepositoryInterface;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
|
@ -34,7 +35,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
AvailabilityCheckerInterface $availabilityChecker,
|
||||
): void {
|
||||
|
|
@ -66,14 +67,31 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
;
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_order_item_does_not_exist(
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn(null);
|
||||
|
||||
$this
|
||||
->shouldThrow(OrderItemNotFoundException::class)
|
||||
->during('validate', [
|
||||
ChangeItemQuantityInCart::createFromData('token', '11', 2),
|
||||
new ChangedItemQuantityInCart(),
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
function it_adds_violation_if_product_variant_does_not_exist(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderItemInterface $orderItem,
|
||||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneBy(['id' => '11'])->willReturn($orderItem);
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn($orderItem);
|
||||
$orderItem->getVariant()->willReturn(null);
|
||||
$orderItem->getVariantName()->willReturn('MacPro');
|
||||
|
||||
|
|
@ -89,7 +107,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
}
|
||||
|
||||
function it_adds_violation_if_product_is_disabled(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderItemInterface $orderItem,
|
||||
ProductVariantInterface $productVariant,
|
||||
|
|
@ -97,7 +115,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneBy(['id' => '11'])->willReturn($orderItem);
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn($orderItem);
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$orderItem->getVariantName()->willReturn('Variant Name');
|
||||
|
||||
|
|
@ -119,7 +137,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
}
|
||||
|
||||
function it_adds_violation_if_product_variant_is_disabled(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderItemInterface $orderItem,
|
||||
ProductVariantInterface $productVariant,
|
||||
|
|
@ -127,7 +145,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneBy(['id' => '11'])->willReturn($orderItem);
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn($orderItem);
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$orderItem->getVariantName()->willReturn('Variant Name');
|
||||
|
||||
|
|
@ -151,7 +169,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
}
|
||||
|
||||
function it_adds_violation_if_product_variant_stock_is_not_sufficient(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderItemInterface $orderItem,
|
||||
ProductVariantInterface $productVariant,
|
||||
|
|
@ -160,7 +178,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneBy(['id' => '11'])->willReturn($orderItem);
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn($orderItem);
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$orderItem->getVariantName()->willReturn('Variant Name');
|
||||
|
||||
|
|
@ -186,7 +204,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
}
|
||||
|
||||
function it_adds_violation_if_product_is_not_available_in_channel(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderItemInterface $orderItem,
|
||||
|
|
@ -198,7 +216,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneBy(['id' => '11'])->willReturn($orderItem);
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn($orderItem);
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$orderItem->getVariantName()->willReturn('Variant Name');
|
||||
|
||||
|
|
@ -214,7 +232,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
|
||||
$product->getName()->willReturn('PRODUCT NAME');
|
||||
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($cart);
|
||||
$orderRepository->findCartByTokenValue('token')->willReturn($cart);
|
||||
$cart->getChannel()->willReturn($channel);
|
||||
|
||||
$product->hasChannel($channel)->willReturn(false);
|
||||
|
|
@ -225,13 +243,13 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
;
|
||||
|
||||
$this->validate(
|
||||
ChangeItemQuantityInCart::createFromData('TOKEN', '11', 2),
|
||||
ChangeItemQuantityInCart::createFromData('token', '11', 2),
|
||||
new ChangedItemQuantityInCart(),
|
||||
);
|
||||
}
|
||||
|
||||
function it_does_nothing_if_product_and_variant_are_enabled_and_available_in_channel(
|
||||
RepositoryInterface $orderItemRepository,
|
||||
OrderItemRepositoryInterface $orderItemRepository,
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderItemInterface $orderItem,
|
||||
|
|
@ -243,7 +261,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderItemRepository->findOneBy(['id' => '11'])->willReturn($orderItem);
|
||||
$orderItemRepository->findOneByIdAndCartTokenValue('11', 'token')->willReturn($orderItem);
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$orderItem->getVariantName()->willReturn('Variant Name');
|
||||
|
||||
|
|
@ -259,7 +277,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
|
||||
$product->getName()->willReturn('PRODUCT NAME');
|
||||
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($cart);
|
||||
$orderRepository->findCartByTokenValue('token')->willReturn($cart);
|
||||
$cart->getChannel()->willReturn($channel);
|
||||
|
||||
$product->hasChannel($channel)->willReturn(true);
|
||||
|
|
@ -278,7 +296,7 @@ final class ChangedItemQuantityInCartValidatorSpec extends ObjectBehavior
|
|||
;
|
||||
|
||||
$this->validate(
|
||||
ChangeItemQuantityInCart::createFromData('TOKEN', '11', 2),
|
||||
ChangeItemQuantityInCart::createFromData('token', '11', 2),
|
||||
new ChangedItemQuantityInCart(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
<?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\Validator\Constraints;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use SM\Factory\FactoryInterface;
|
||||
use SM\StateMachine\StateMachineInterface;
|
||||
use Sylius\Bundle\ApiBundle\Command\OrderTokenValueAwareInterface;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\CheckoutCompletion;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\OrderCheckoutTransitions;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
final class CheckoutCompletionValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
FactoryInterface $stateMachineFactory,
|
||||
): void {
|
||||
$this->beConstructedWith($orderRepository, $stateMachineFactory);
|
||||
}
|
||||
|
||||
function it_is_a_constraint_validator(): void
|
||||
{
|
||||
$this->shouldImplement(ConstraintValidatorInterface::class);
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_value_is_not_an_instance_of_order_token_value_aware_interface(
|
||||
Constraint $constraint,
|
||||
): void {
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', ['', $constraint])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_constraint_is_not_an_instance_of_checkout_completion(
|
||||
Constraint $constraint,
|
||||
OrderTokenValueAwareInterface $orderTokenValueAware,
|
||||
): void {
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [$orderTokenValueAware, $constraint])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_order_with_given_token_value_does_not_exist(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
Constraint $constraint,
|
||||
OrderTokenValueAwareInterface $orderTokenValueAware,
|
||||
): void {
|
||||
$orderTokenValueAware->getOrderTokenValue()->willReturn('xxx');
|
||||
$orderRepository->findOneBy(['tokenValue' => 'xxx'])->willReturn(null);
|
||||
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [$orderTokenValueAware, $constraint])
|
||||
;
|
||||
}
|
||||
|
||||
function it_does_nothing_if_order_can_be_completed(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
FactoryInterface $stateMachineFactory,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderInterface $order,
|
||||
OrderTokenValueAwareInterface $orderTokenValueAware,
|
||||
StateMachineInterface $stateMachine,
|
||||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderTokenValueAware->getOrderTokenValue()->willReturn('xxx');
|
||||
$orderRepository->findOneBy(['tokenValue' => 'xxx'])->willReturn($order);
|
||||
|
||||
$stateMachineFactory->get($order, 'sylius_order_checkout')->willReturn($stateMachine);
|
||||
$stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE)->willReturn(true);
|
||||
$stateMachine->getState()->shouldNotBeCalled();
|
||||
$stateMachine->getPossibleTransitions()->shouldNotBeCalled();
|
||||
|
||||
$executionContext
|
||||
->addViolation(Argument::cetera())
|
||||
->shouldNotBeCalled()
|
||||
;
|
||||
|
||||
$this->validate($orderTokenValueAware, new CheckoutCompletion());
|
||||
}
|
||||
|
||||
function it_adds_violation_if_order_cannot_be_completed(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
FactoryInterface $stateMachineFactory,
|
||||
ExecutionContextInterface $executionContext,
|
||||
OrderInterface $order,
|
||||
OrderTokenValueAwareInterface $orderTokenValueAware,
|
||||
StateMachineInterface $stateMachine,
|
||||
): void {
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$orderTokenValueAware->getOrderTokenValue()->willReturn('xxx');
|
||||
$orderRepository->findOneBy(['tokenValue' => 'xxx'])->willReturn($order);
|
||||
|
||||
$stateMachineFactory->get($order, 'sylius_order_checkout')->willReturn($stateMachine);
|
||||
$stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE)->willReturn(false);
|
||||
$stateMachine->getState()->willReturn('some_state_that_does_not_allow_to_complete_order');
|
||||
$stateMachine->getPossibleTransitions()->willReturn(['some_possible_transition', 'another_possible_transition']);
|
||||
|
||||
$executionContext
|
||||
->addViolation(
|
||||
'sylius.order.invalid_state_transition',
|
||||
[
|
||||
'%currentState%' => 'some_state_that_does_not_allow_to_complete_order',
|
||||
'%possibleTransitions%' => 'some_possible_transition, another_possible_transition',
|
||||
],
|
||||
)
|
||||
->shouldBeCalled()
|
||||
;
|
||||
|
||||
$this->validate($orderTokenValueAware, new CheckoutCompletion());
|
||||
}
|
||||
}
|
||||
|
|
@ -186,7 +186,7 @@ final class ChosenShippingMethodEligibilityValidatorSpec extends ObjectBehavior
|
|||
$this->validate($command, new ChosenShippingMethodEligibility());
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_given_shipmnent_is_null(
|
||||
function it_adds_violation_if_order_shipment_is_not_found(
|
||||
ShipmentRepositoryInterface $shipmentRepository,
|
||||
ShippingMethodRepositoryInterface $shippingMethodRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
|
|
@ -199,14 +199,20 @@ final class ChosenShippingMethodEligibilityValidatorSpec extends ObjectBehavior
|
|||
$shippingMethodRepository->findOneBy(['code' => 'SHIPPING_METHOD_CODE'])->willReturn($shippingMethod);
|
||||
|
||||
$shipmentRepository->find('123')->willReturn(null);
|
||||
|
||||
$executionContext
|
||||
->addViolation('sylius.shipping_method.not_available', Argument::any())
|
||||
->shouldNotBeCalled()
|
||||
;
|
||||
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [$command, new ChosenShippingMethodEligibility()])
|
||||
$executionContext
|
||||
->addViolation('sylius.shipping_method.not_found', Argument::any())
|
||||
->shouldNotBeCalled()
|
||||
;
|
||||
$executionContext
|
||||
->addViolation('sylius.shipment.not_found')
|
||||
->shouldBeCalled()
|
||||
;
|
||||
|
||||
$this->validate($command, new ChosenShippingMethodEligibility());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ sylius:
|
|||
order:
|
||||
currency_code:
|
||||
not_valid: The currency code you entered is invalid.
|
||||
invalid_state_transition: 'Cannot complete as order is in a wrong state. Current: %currentState%. Possible transitions: %possibleTransitions%.'
|
||||
payment_method_eligibility: 'This payment method %paymentMethodName% has been disabled. Please reselect your payment method.'
|
||||
product_eligibility: 'This product %productName% has been disabled.'
|
||||
shipping_method_eligibility: 'Product does not fit requirements for %shippingMethodName% shipping method. Please reselect your shipping method.'
|
||||
|
|
|
|||
|
|
@ -28,21 +28,55 @@ Sylius\Component\Core\Model\ProductVariant:
|
|||
product: '@product_mug'
|
||||
currentLocale: 'en_US'
|
||||
translations:
|
||||
en_US: '@product_variant_translation_mug'
|
||||
en_US: '@product_variant_translation_mug_blue'
|
||||
optionValues: ['@product_option_value_color_blue']
|
||||
channelPricings:
|
||||
WEB: '@channel_pricing_product_variant_mug_blue_web'
|
||||
product_variant_mug_nft:
|
||||
channelPricings:
|
||||
WEB: '@channel_pricing_product_variant_mug_nft_web'
|
||||
code: 'MUG_NFT'
|
||||
currentLocale: 'en_US'
|
||||
optionValues: ['@product_option_value_color_blue']
|
||||
product: '@product_mug'
|
||||
shippingRequired: false
|
||||
translations:
|
||||
en_US: '@product_variant_translation_mug_nft'
|
||||
product_variant_free_mug_nft:
|
||||
channelPricings:
|
||||
WEB: '@channel_pricing_product_variant_free_mug_nft_web'
|
||||
code: 'MUG_NFT_FREE'
|
||||
currentLocale: 'en_US'
|
||||
optionValues: ['@product_option_value_color_blue']
|
||||
product: '@product_mug'
|
||||
shippingRequired: false
|
||||
translations:
|
||||
en_US: '@product_variant_translation_free_mug_nft'
|
||||
|
||||
Sylius\Component\Product\Model\ProductVariantTranslation:
|
||||
product_variant_translation_mug:
|
||||
product_variant_translation_mug_blue:
|
||||
locale: 'en_US'
|
||||
name: 'Blue Mug'
|
||||
translatable: '@product_variant_mug_blue'
|
||||
product_variant_translation_mug_nft:
|
||||
locale: 'en_US'
|
||||
name: 'NFT Mug'
|
||||
translatable: '@product_variant_mug_nft'
|
||||
product_variant_translation_free_mug_nft:
|
||||
locale: 'en_US'
|
||||
name: 'NFT Mug'
|
||||
translatable: '@product_variant_free_mug_nft'
|
||||
|
||||
Sylius\Component\Core\Model\ChannelPricing:
|
||||
channel_pricing_product_variant_mug_blue_web:
|
||||
channelCode: 'WEB'
|
||||
price: 2000
|
||||
channel_pricing_product_variant_mug_nft_web:
|
||||
channelCode: 'WEB'
|
||||
price: 3000
|
||||
channel_pricing_product_variant_free_mug_nft_web:
|
||||
channelCode: 'WEB'
|
||||
price: 0
|
||||
|
||||
Sylius\Component\Product\Model\ProductOption:
|
||||
product_option_color:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "The shipment does not exist.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "The shipment does not exist.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Cannot complete as order is in a wrong state. Current: cart. Possible transitions: address.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Cannot complete as order is in a wrong state. Current: cart. Possible transitions: address.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/Order",
|
||||
"@id": "\/api\/v2\/shop\/orders\/nAWw2jewpA",
|
||||
"@type": "Order",
|
||||
"channel": "\/api\/v2\/shop\/channels\/WEB",
|
||||
"shippingAddress": {
|
||||
"@id": "\/api\/v2\/shop\/addresses\/@integer@",
|
||||
"@type": "Address",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"countryCode": "US",
|
||||
"street": "Avenue",
|
||||
"city": "New York",
|
||||
"postcode": "90000"
|
||||
},
|
||||
"billingAddress": {
|
||||
"@id": "\/api\/v2\/shop\/addresses\/@integer@",
|
||||
"@type": "Address",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"countryCode": "US",
|
||||
"street": "Avenue",
|
||||
"city": "New York",
|
||||
"postcode": "90000"
|
||||
},
|
||||
"payments": [],
|
||||
"shipments": [],
|
||||
"currencyCode": "USD",
|
||||
"localeCode": "en_US",
|
||||
"checkoutState": "completed",
|
||||
"paymentState": "paid",
|
||||
"shippingState": "shipped",
|
||||
"tokenValue": "nAWw2jewpA",
|
||||
"id": @integer@,
|
||||
"number": "000000001",
|
||||
"items": [
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/order-items\/@integer@",
|
||||
"@type": "OrderItem",
|
||||
"variant": "\/api\/v2\/shop\/product-variants\/MUG_NFT_FREE",
|
||||
"productName": "Mug",
|
||||
"id": @integer@,
|
||||
"quantity": 1,
|
||||
"unitPrice": 0,
|
||||
"originalUnitPrice": 0,
|
||||
"total": 0,
|
||||
"discountedUnitPrice": 0,
|
||||
"subtotal": 0
|
||||
}
|
||||
],
|
||||
"itemsTotal": 0,
|
||||
"total": 0,
|
||||
"state": "fulfilled",
|
||||
"taxTotal": 0,
|
||||
"taxExcludedTotal": 0,
|
||||
"taxIncludedTotal": 0,
|
||||
"shippingTotal": 0,
|
||||
"orderPromotionTotal": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/Order",
|
||||
"@id": "\/api\/v2\/shop\/orders\/nAWw2jewpA",
|
||||
"@type": "Order",
|
||||
"channel": "\/api\/v2\/shop\/channels\/WEB",
|
||||
"shippingAddress": {
|
||||
"@id": "\/api\/v2\/shop\/addresses\/@integer@",
|
||||
"@type": "Address",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"countryCode": "US",
|
||||
"street": "Avenue",
|
||||
"city": "New York",
|
||||
"postcode": "90000"
|
||||
},
|
||||
"billingAddress": {
|
||||
"@id": "\/api\/v2\/shop\/addresses\/@integer@",
|
||||
"@type": "Address",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"countryCode": "US",
|
||||
"street": "Avenue",
|
||||
"city": "New York",
|
||||
"postcode": "90000"
|
||||
},
|
||||
"payments": [
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/payments\/@integer@",
|
||||
"@type": "Payment",
|
||||
"id": @integer@,
|
||||
"method": "\/api\/v2\/shop\/payment-methods\/BANK_TRANSFER"
|
||||
}
|
||||
],
|
||||
"shipments": [],
|
||||
"currencyCode": "USD",
|
||||
"localeCode": "en_US",
|
||||
"checkoutState": "completed",
|
||||
"paymentState": "awaiting_payment",
|
||||
"shippingState": "shipped",
|
||||
"tokenValue": "nAWw2jewpA",
|
||||
"id": @integer@,
|
||||
"number": "000000001",
|
||||
"items": [
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/order-items\/@integer@",
|
||||
"@type": "OrderItem",
|
||||
"variant": "\/api\/v2\/shop\/product-variants\/MUG_NFT",
|
||||
"productName": "Mug",
|
||||
"id": @integer@,
|
||||
"quantity": 1,
|
||||
"unitPrice": 3000,
|
||||
"originalUnitPrice": 3000,
|
||||
"total": 3000,
|
||||
"discountedUnitPrice": 3000,
|
||||
"subtotal": 3000
|
||||
}
|
||||
],
|
||||
"itemsTotal": 3000,
|
||||
"total": 3000,
|
||||
"state": "new",
|
||||
"taxTotal": 0,
|
||||
"taxExcludedTotal": 0,
|
||||
"taxIncludedTotal": 0,
|
||||
"shippingTotal": 0,
|
||||
"orderPromotionTotal": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/Order",
|
||||
"@id": "\/api\/v2\/shop\/orders\/nAWw2jewpA",
|
||||
"@type": "Order",
|
||||
"channel": "\/api\/v2\/shop\/channels\/WEB",
|
||||
"shippingAddress": {
|
||||
"@id": "\/api\/v2\/shop\/addresses\/@integer@",
|
||||
"@type": "Address",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"countryCode": "US",
|
||||
"street": "Avenue",
|
||||
"city": "New York",
|
||||
"postcode": "90000"
|
||||
},
|
||||
"billingAddress": {
|
||||
"@id": "\/api\/v2\/shop\/addresses\/@integer@",
|
||||
"@type": "Address",
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"countryCode": "US",
|
||||
"street": "Avenue",
|
||||
"city": "New York",
|
||||
"postcode": "90000"
|
||||
},
|
||||
"payments": [
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/payments\/@integer@",
|
||||
"@type": "Payment",
|
||||
"id": @integer@,
|
||||
"method": "\/api\/v2\/shop\/payment-methods\/BANK_TRANSFER"
|
||||
}
|
||||
],
|
||||
"shipments": [
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/shipments\/@integer@",
|
||||
"@type": "Shipment",
|
||||
"id": @integer@,
|
||||
"method": "\/api\/v2\/shop\/shipping-methods\/DHL"
|
||||
}
|
||||
],
|
||||
"currencyCode": "USD",
|
||||
"localeCode": "en_US",
|
||||
"checkoutState": "completed",
|
||||
"paymentState": "awaiting_payment",
|
||||
"shippingState": "ready",
|
||||
"tokenValue": "nAWw2jewpA",
|
||||
"id": @integer@,
|
||||
"number": "000000001",
|
||||
"items": [
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/order-items\/@integer@",
|
||||
"@type": "OrderItem",
|
||||
"variant": "\/api\/v2\/shop\/product-variants\/MUG_BLUE",
|
||||
"productName": "Mug",
|
||||
"id": @integer@,
|
||||
"quantity": 3,
|
||||
"unitPrice": 2000,
|
||||
"originalUnitPrice": 2000,
|
||||
"total": 6000,
|
||||
"discountedUnitPrice": 2000,
|
||||
"subtotal": 6000
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/shop\/order-items\/@integer@",
|
||||
"@type": "OrderItem",
|
||||
"variant": "\/api\/v2\/shop\/product-variants\/MUG_NFT",
|
||||
"productName": "Mug",
|
||||
"id": @integer@,
|
||||
"quantity": 1,
|
||||
"unitPrice": 3000,
|
||||
"originalUnitPrice": 3000,
|
||||
"total": 3000,
|
||||
"discountedUnitPrice": 3000,
|
||||
"subtotal": 3000
|
||||
}
|
||||
],
|
||||
"itemsTotal": 9000,
|
||||
"total": 10000,
|
||||
"state": "new",
|
||||
"taxTotal": 0,
|
||||
"taxExcludedTotal": 0,
|
||||
"taxIncludedTotal": 0,
|
||||
"shippingTotal": 1000,
|
||||
"orderPromotionTotal": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Cannot complete as order is in a wrong state. Current: shipping_selected. Possible transitions: address, select_shipping, skip_payment, select_payment.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Cannot complete as order is in a wrong state. Current: shipping_selected. Possible transitions: address, select_shipping, skip_payment, select_payment.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Cannot complete as order is in a wrong state. Current: shipping_skipped. Possible transitions: address, skip_payment, select_payment.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Cannot complete as order is in a wrong state. Current: shipping_skipped. Possible transitions: address, skip_payment, select_payment.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ConstraintViolationList",
|
||||
"@type": "ConstraintViolationList",
|
||||
"hydra:title": "An error occurred",
|
||||
"hydra:description": "Cannot complete as order is in a wrong state. Current: addressed. Possible transitions: address, skip_shipping, select_shipping.",
|
||||
"violations": [
|
||||
{
|
||||
"propertyPath": "",
|
||||
"message": "Cannot complete as order is in a wrong state. Current: addressed. Possible transitions: address, skip_shipping, select_shipping.",
|
||||
"code": null
|
||||
}
|
||||
]
|
||||
}
|
||||
283
tests/Api/Shop/CheckoutCompletionTest.php
Normal file
283
tests/Api/Shop/CheckoutCompletionTest.php
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
<?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\Tests\Api\Shop;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Cart\AddItemToCart;
|
||||
use Sylius\Bundle\ApiBundle\Command\Cart\PickupCart;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\ChoosePaymentMethod;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\ChooseShippingMethod;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\UpdateCart;
|
||||
use Sylius\Component\Core\Model\Address;
|
||||
use Sylius\Tests\Api\JsonApiTestCase;
|
||||
use Sylius\Tests\Api\Utils\ContentType;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
final class CheckoutCompletionTest extends JsonApiTestCase
|
||||
{
|
||||
private MessageBusInterface $commandBus;
|
||||
|
||||
/** @test */
|
||||
public function it_prevents_from_order_completion_if_order_is_in_the_cart_state(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/order_not_addressed_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_prevents_from_order_completion_if_order_has_been_addressed_but_does_not_have_a_shipping_method_assigned(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml', 'shipping_method.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/shipping_method_not_chosen_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_prevents_from_order_completion_if_order_has_shipping_method_assigned_but_the_payment_has_not_been_chosen(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml', 'shipping_method.yaml', 'payment_method.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
$this->chooseShippingMethod($tokenValue, $this->getFirstShipmentId($tokenValue));
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/payment_method_not_chosen_with_shipping_selected_state_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_prevents_from_checkout_completion_if_there_is_no_shippable_item_in_the_cart_and_order_is_in_addressed_state(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml', 'shipping_method.yaml', 'payment_method.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_NFT', 3, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/payment_method_not_chosen_with_shipping_skipped_state_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_complete_checkout_with_shippable_and_non_shippable_items_if_all_checkout_steps_have_been_completed(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml', 'shipping_method.yaml', 'payment_method.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
$this->addItemToCart('MUG_NFT', 1, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
$this->chooseShippingMethod($tokenValue, $this->getFirstShipmentId($tokenValue));
|
||||
$this->choosePaymentMethod($tokenValue, $this->getFirstPaymentId($tokenValue));
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/order_with_shippable_and_non_shippable_items_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_complete_checkout_with_non_shippable_items_without_shipping_method_assigned(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml', 'payment_method.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_NFT', 1, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
$this->choosePaymentMethod($tokenValue, $this->getFirstPaymentId($tokenValue));
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/order_with_non_shippable_items_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_complete_checkout_with_free_non_shippable_items_without_shipping_method_and_payment_method_assigned(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_NFT_FREE', 1, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/complete', $tokenValue),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode([]),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/order_completion/order_with_free_non_shippable_items_response',
|
||||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
}
|
||||
|
||||
private function pickUpCart(): string
|
||||
{
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
|
||||
$this->commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
return $tokenValue;
|
||||
}
|
||||
|
||||
private function addItemToCart(string $productVariantCode, int $quantity, string $tokenValue): void
|
||||
{
|
||||
$addItemToCartCommand = new AddItemToCart($productVariantCode, $quantity);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
|
||||
$this->commandBus->dispatch($addItemToCartCommand);
|
||||
}
|
||||
|
||||
private function updateCartWithAddress(string $tokenValue): void
|
||||
{
|
||||
$address = new Address();
|
||||
$address->setFirstName('John');
|
||||
$address->setLastName('Doe');
|
||||
$address->setCity('New York');
|
||||
$address->setStreet('Avenue');
|
||||
$address->setCountryCode('US');
|
||||
$address->setPostcode('90000');
|
||||
|
||||
$updateCartCommand = new UpdateCart(email: 'sylius@example.com', billingAddress: $address);
|
||||
$updateCartCommand->setOrderTokenValue($tokenValue);
|
||||
|
||||
$this->commandBus->dispatch($updateCartCommand);
|
||||
}
|
||||
|
||||
private function chooseShippingMethod(string $tokenValue, string $shipmentId): void
|
||||
{
|
||||
$chooseShippingMethodCommand = new ChooseShippingMethod('DHL');
|
||||
$chooseShippingMethodCommand->setSubresourceId($shipmentId);
|
||||
$chooseShippingMethodCommand->setOrderTokenValue($tokenValue);
|
||||
|
||||
$this->commandBus->dispatch($chooseShippingMethodCommand);
|
||||
}
|
||||
|
||||
private function choosePaymentMethod(string $tokenValue, string $paymentId): void
|
||||
{
|
||||
$choosePaymentMethodCommand = new ChoosePaymentMethod('BANK_TRANSFER');
|
||||
$choosePaymentMethodCommand->setSubresourceId($paymentId);
|
||||
$choosePaymentMethodCommand->setOrderTokenValue($tokenValue);
|
||||
|
||||
$this->commandBus->dispatch($choosePaymentMethodCommand);
|
||||
}
|
||||
|
||||
private function getFirstShipmentId(string $tokenValue): string
|
||||
{
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/orders/%s', $tokenValue),
|
||||
);
|
||||
|
||||
return (string) json_decode($this->client->getResponse()->getContent())->shipments[0]->id;
|
||||
}
|
||||
|
||||
private function getFirstPaymentId(string $tokenValue): string
|
||||
{
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/shop/orders/%s', $tokenValue),
|
||||
);
|
||||
|
||||
return (string) json_decode($this->client->getResponse()->getContent())->payments[0]->id;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ use Sylius\Component\Core\Model\Address;
|
|||
use Sylius\Component\Core\Model\AdjustmentInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Tests\Api\JsonApiTestCase;
|
||||
use Sylius\Tests\Api\Utils\ContentType;
|
||||
use Sylius\Tests\Api\Utils\OrderPlacerTrait;
|
||||
use Sylius\Tests\Api\Utils\ShopUserLoginTrait;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
|
@ -31,6 +32,8 @@ final class OrdersTest extends JsonApiTestCase
|
|||
use ShopUserLoginTrait;
|
||||
use OrderPlacerTrait;
|
||||
|
||||
private MessageBusInterface $commandBus;
|
||||
|
||||
/** @test */
|
||||
public function it_gets_an_order(): void
|
||||
{
|
||||
|
|
@ -42,30 +45,9 @@ final class OrdersTest extends JsonApiTestCase
|
|||
'payment_method.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
|
||||
$address = new Address();
|
||||
$address->setFirstName('John');
|
||||
$address->setLastName('Doe');
|
||||
$address->setCity('New York');
|
||||
$address->setStreet('Avenue');
|
||||
$address->setCountryCode('US');
|
||||
$address->setPostcode('90000');
|
||||
|
||||
$updateCartCommand = new UpdateCart('sylius@example.com', $address);
|
||||
$updateCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($updateCartCommand);
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
|
||||
$this->client->request(method: 'GET', uri: '/api/v2/shop/orders/nAWw2jewpA', server: self::CONTENT_TYPE_HEADER);
|
||||
$response = $this->client->getResponse();
|
||||
|
|
@ -78,30 +60,10 @@ final class OrdersTest extends JsonApiTestCase
|
|||
{
|
||||
$this->loadFixturesFromFiles(['authentication/customer.yaml', 'channel.yaml', 'cart.yaml', 'country.yaml', 'shipping_method.yaml', 'payment_method.yaml']);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
|
||||
$address = new Address();
|
||||
$address->setFirstName('John');
|
||||
$address->setLastName('Doe');
|
||||
$address->setCity('New York');
|
||||
$address->setStreet('Avenue');
|
||||
$address->setCountryCode('US');
|
||||
$address->setPostcode('90000');
|
||||
|
||||
$updateCartCommand = new UpdateCart('oliver@doe.com', $address);
|
||||
$updateCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($updateCartCommand);
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
$this->updateCartWithAddress($tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
|
|
@ -124,18 +86,9 @@ final class OrdersTest extends JsonApiTestCase
|
|||
'payment_method.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
|
|
@ -158,18 +111,9 @@ final class OrdersTest extends JsonApiTestCase
|
|||
'payment_method.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
|
|
@ -192,18 +136,9 @@ final class OrdersTest extends JsonApiTestCase
|
|||
'payment_method.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
/** @var OrderInterface $order */
|
||||
$order = $this->get('sylius.repository.order')->findCartByTokenValue($tokenValue);
|
||||
|
|
@ -241,14 +176,7 @@ final class OrdersTest extends JsonApiTestCase
|
|||
'payment_method.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
$this->pickUpCart();
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
|
|
@ -391,17 +319,9 @@ final class OrdersTest extends JsonApiTestCase
|
|||
'payment_method.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
/** @var CountryInterface $country */
|
||||
$country = $fixtures['country_US'];
|
||||
|
|
@ -437,18 +357,9 @@ final class OrdersTest extends JsonApiTestCase
|
|||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
$addItemToCartCommand = new AddItemToCart('MUG_BLUE', 3);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
$commandBus->dispatch($addItemToCartCommand);
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$this->client->request('GET', sprintf('/api/v2/shop/orders/%s', $tokenValue));
|
||||
$itemId = json_decode($this->client->getResponse()->getContent(), true)['items'][0]['id'];
|
||||
|
|
@ -459,18 +370,27 @@ final class OrdersTest extends JsonApiTestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_unprocessable_entity_status_if_tries_to_remove_an_item_that_not_exist_in_the_order(): void
|
||||
public function it_does_not_remove_item_from_the_cart_if_invalid_uri_item_parameter_passed(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
/** @var MessageBusInterface $commandBus */
|
||||
$commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
$commandBus->dispatch($pickupCartCommand);
|
||||
$this->client->request('GET', sprintf('/api/v2/shop/orders/%s', $tokenValue));
|
||||
|
||||
$this->client->request('DELETE', sprintf('/api/v2/shop/orders/%s/items/STRING-INSTEAD-OF-ID', $tokenValue));
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_unprocessable_entity_status_if_tries_to_remove_an_item_that_not_exist_in_the_order(): void
|
||||
{
|
||||
$this->loadFixturesFromFile('channel.yaml');
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$nonExistingOrderItemId = 123;
|
||||
|
||||
$this->client->request('DELETE', sprintf('/api/v2/shop/orders/%s/items/%s', $tokenValue, $nonExistingOrderItemId));
|
||||
|
|
@ -556,4 +476,99 @@ final class OrdersTest extends JsonApiTestCase
|
|||
|
||||
$this->assertResponseCode($response, Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
public function it_returns_unprocessable_entity_status_if_trying_to_assign_shipping_method_to_non_existing_shipment(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'shipping_method.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/shipments/%s', $tokenValue, '1237'),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode(['shippingMethod' => 'api/v2/shop/shipping-methods/UPS'])
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'shop/assign_shipping_method_to_non_existing_shipment_response',
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_unprocessable_entity_status_if_trying_to_change_item_quantity_if_invalid_item_id_passed(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
$this->client->request(
|
||||
method: 'PATCH',
|
||||
uri: sprintf('/api/v2/shop/orders/%s/items/%s', $tokenValue, 'invalid-item-id'),
|
||||
server: ContentType::APPLICATION_JSON_MERGE_PATCH,
|
||||
content: json_encode(['quantity' => 5])
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_return_payment_configuration_if_invalid_payment_id_passed(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
$this->client->request(
|
||||
'GET',
|
||||
sprintf('/api/v2/shop/orders/%s/payments/%s/configuration', $tokenValue, 'invalid-payment-id'),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->commandBus = self::getContainer()->get('sylius.command_bus');
|
||||
}
|
||||
|
||||
private function pickUpCart(): string
|
||||
{
|
||||
$tokenValue = 'nAWw2jewpA';
|
||||
|
||||
$pickupCartCommand = new PickupCart($tokenValue);
|
||||
$pickupCartCommand->setChannelCode('WEB');
|
||||
|
||||
$this->commandBus->dispatch($pickupCartCommand);
|
||||
|
||||
return $tokenValue;
|
||||
}
|
||||
|
||||
private function addItemToCart(string $productVariantCode, int $quantity, string $tokenValue): void
|
||||
{
|
||||
$addItemToCartCommand = new AddItemToCart($productVariantCode, $quantity);
|
||||
$addItemToCartCommand->setOrderTokenValue($tokenValue);
|
||||
|
||||
$this->commandBus->dispatch($addItemToCartCommand);
|
||||
}
|
||||
|
||||
private function updateCartWithAddress(string $tokenValue): void
|
||||
{
|
||||
$address = new Address();
|
||||
$address->setFirstName('John');
|
||||
$address->setLastName('Doe');
|
||||
$address->setCity('New York');
|
||||
$address->setStreet('Avenue');
|
||||
$address->setCountryCode('US');
|
||||
$address->setPostcode('90000');
|
||||
|
||||
$updateCartCommand = new UpdateCart(email: 'sylius@example.com', billingAddress: $address);
|
||||
$updateCartCommand->setOrderTokenValue($tokenValue);
|
||||
|
||||
$this->commandBus->dispatch($updateCartCommand);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
tests/Api/Utils/ContentType.php
Normal file
27
tests/Api/Utils/ContentType.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?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\Tests\Api\Utils;
|
||||
|
||||
class ContentType
|
||||
{
|
||||
public const APPLICATION_JSON = ['CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json'];
|
||||
|
||||
public const APPLICATION_JSON_LD = ['CONTENT_TYPE' => 'application/ld+json', 'HTTP_ACCEPT' => 'application/ld+json'];
|
||||
|
||||
public const APPLICATION_JSON_MERGE_PATCH = ['CONTENT_TYPE' => 'application/merge-patch+json', 'HTTP_ACCEPT' => 'application/ld+json'];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue