diff --git a/src/Sylius/Bundle/ApiBundle/OpenApi/Documentation/ShippingMethodDocumentationModifier.php b/src/Sylius/Bundle/ApiBundle/OpenApi/Documentation/ShippingMethodDocumentationModifier.php index 7cd58f267b8..fa0a86c8456 100644 --- a/src/Sylius/Bundle/ApiBundle/OpenApi/Documentation/ShippingMethodDocumentationModifier.php +++ b/src/Sylius/Bundle/ApiBundle/OpenApi/Documentation/ShippingMethodDocumentationModifier.php @@ -19,7 +19,6 @@ /** @experimental */ final class ShippingMethodDocumentationModifier implements DocumentationModifierInterface { - public const ROUTE_ADMIN_SHIPPING_METHODS = '/admin/shipping-methods'; public const ROUTE_ADMIN_SHIPPING_METHOD = '/admin/shipping-methods/{code}'; @@ -31,7 +30,7 @@ final class ShippingMethodDocumentationModifier implements DocumentationModifier public function __construct( private string $apiRoute, private array $ruleTypes, - private array $shippingMethodCalculators + private array $shippingMethodCalculators, ) { } diff --git a/tests/Api/Admin/OrdersTest.php b/tests/Api/Admin/OrdersTest.php index 9ee25eb63a4..9f4041d5869 100644 --- a/tests/Api/Admin/OrdersTest.php +++ b/tests/Api/Admin/OrdersTest.php @@ -30,7 +30,7 @@ protected function setUp(): void { $this->setUpOrderPlacer(); $this->setUpAdminContext(); - $this->setUpDefaultHeaders(); + $this->setUpDefaultGetHeaders(); parent::setUp(); } diff --git a/tests/Api/Admin/PaymentsTest.php b/tests/Api/Admin/PaymentsTest.php index 8b7b68af736..b563695c0ed 100644 --- a/tests/Api/Admin/PaymentsTest.php +++ b/tests/Api/Admin/PaymentsTest.php @@ -14,25 +14,46 @@ namespace Sylius\Tests\Api\Admin; use Sylius\Tests\Api\JsonApiTestCase; -use Sylius\Tests\Api\Utils\AdminUserLoginTrait; use Sylius\Tests\Api\Utils\OrderPlacerTrait; -use Symfony\Component\HttpFoundation\Response; final class PaymentsTest extends JsonApiTestCase { - use AdminUserLoginTrait; use OrderPlacerTrait; protected function setUp(): void { $this->setUpOrderPlacer(); + $this->setUpAdminContext(); parent::setUp(); } /** @test */ - public function it_gets_payments_of_the_specific_order(): void + public function it_gets_payments(): void + { + $this->setUpDefaultGetHeaders(); + + $this->loadFixturesFromFiles([ + 'authentication/api_administrator.yaml', + 'channel.yaml', + 'cart.yaml', + 'country.yaml', + 'shipping_method.yaml', + 'payment_method.yaml', + ]); + + $this->placeOrder('nAWw2jewpA'); + + $this->requestGet(uri: '/api/v2/admin/payments'); + + $this->assertResponseSuccessful('admin/payment/get_payments_response'); + } + + /** @test */ + public function it_gets_payments_filtered_by_state(): void { + $this->setUpDefaultGetHeaders(); + $this->loadFixturesFromFiles([ 'authentication/api_administrator.yaml', 'channel.yaml', @@ -41,27 +62,67 @@ public function it_gets_payments_of_the_specific_order(): void 'shipping_method.yaml', 'payment_method.yaml', ]); - $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER); - $tokenValue = 'nAWw2jewpA'; + $order = $this->placeOrder('paidOrder'); + $this->payOrder($order); + + $this->placeOrder('unpaidOrder'); + + $this->requestGet(uri: '/api/v2/admin/payments', queryParameters: ['state' => 'new']); - $this->placeOrder($tokenValue); + $this->assertResponseSuccessful('admin/payment/get_payments_filtered_by_state_response'); + } - $this->client->request(method: 'GET', uri: '/api/v2/admin/orders/nAWw2jewpA', server: $header); + /** @test */ + public function it_gets_payments_of_the_specific_order(): void + { + $this->setUpDefaultGetHeaders(); + + $this->loadFixturesFromFiles([ + 'authentication/api_administrator.yaml', + 'channel.yaml', + 'cart.yaml', + 'country.yaml', + 'shipping_method.yaml', + 'payment_method.yaml', + ]); + + $this->placeOrder('nAWw2jewpA'); + + $this->requestGet(uri: '/api/v2/admin/orders/nAWw2jewpA'); $orderResponse = json_decode($this->client->getResponse()->getContent(), true); - $this->client->request( - method: 'GET', - uri: '/api/v2/admin/payments/' . $orderResponse['payments'][0]['id'], - server: $header, - ); - $response = $this->client->getResponse(); - $this->assertResponse($response, 'admin/payment/get_payment_response', Response::HTTP_OK); + $this->requestGet(uri: '/api/v2/admin/payments/' . $orderResponse['payments'][0]['id']); + + $this->assertResponseSuccessful('admin/payment/get_payment_response'); } /** @test */ - public function it_gets_payments(): void + public function it_completes_payment(): void { + $this->setUpDefaultPatchHeaders(); + + $this->loadFixturesFromFiles([ + 'authentication/api_administrator.yaml', + 'channel.yaml', + 'cart.yaml', + 'country.yaml', + 'shipping_method.yaml', + 'payment_method.yaml', + ]); + + $order = $this->placeOrder('nAWw2jewpA'); + + $this->requestPatch(uri: sprintf('/api/v2/admin/payments/%s/complete', $order->getPayments()->first()->getId())); + + $this->assertResponseSuccessful('admin/payment/patch_complete_payment_response'); + } + + /** @test */ + public function it_does_not_complete_payment_if_it_is_not_in_the_new_state(): void + { + $this->setUpDefaultPatchHeaders(); + $this->loadFixturesFromFiles([ 'authentication/api_administrator.yaml', 'channel.yaml', @@ -70,14 +131,12 @@ public function it_gets_payments(): void 'shipping_method.yaml', 'payment_method.yaml', ]); - $tokenValue = 'nAWw2jewpA'; - $this->placeOrder($tokenValue); + $order = $this->placeOrder('nAWw2jewpA'); - $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER); + $this->payOrder($order); + $this->requestPatch(uri: sprintf('/api/v2/admin/payments/%s/complete', $order->getPayments()->first()->getId())); - $this->client->request(method: 'GET', uri: '/api/v2/admin/payments', server: $header); - $response = $this->client->getResponse(); - $this->assertResponse($response, 'admin/payment/get_payments_response', Response::HTTP_OK); + $this->assertResponseUnprocessableEntity('admin/payment/patch_not_complete_payment_response'); } } diff --git a/tests/Api/Admin/ProductReviewsTest.php b/tests/Api/Admin/ProductReviewsTest.php index 43ef2e55d54..3a96cc386c6 100644 --- a/tests/Api/Admin/ProductReviewsTest.php +++ b/tests/Api/Admin/ProductReviewsTest.php @@ -159,7 +159,7 @@ public function it_does_not_allow_to_update_a_product_review_with_invalid_rating [ 'propertyPath' => 'rating', 'message' => 'Review rating must be between 1 and 5.', - ] + ], ], ); } diff --git a/tests/Api/JsonApiTestCase.php b/tests/Api/JsonApiTestCase.php index 4f14919d097..a3291ba4871 100644 --- a/tests/Api/JsonApiTestCase.php +++ b/tests/Api/JsonApiTestCase.php @@ -33,6 +33,9 @@ abstract class JsonApiTestCase extends BaseJsonApiTestCase /** @var array */ private array $defaultGetHeaders = []; + /** @var array */ + private array $defaultPatchHeaders = []; + /** * @param array $data */ @@ -49,7 +52,7 @@ protected function setUpAdminContext(): void $this->isAdminContext = true; } - protected function setUpDefaultHeaders(): void + protected function setUpDefaultGetHeaders(): void { $this->defaultGetHeaders = [ 'HTTP_ACCEPT' => 'application/ld+json', @@ -57,6 +60,14 @@ protected function setUpDefaultHeaders(): void ]; } + protected function setUpDefaultPatchHeaders(): void + { + $this->defaultPatchHeaders = [ + 'HTTP_ACCEPT' => 'application/ld+json', + 'CONTENT_TYPE' => 'application/merge-patch+json', + ]; + } + protected function get(string $id): ?object { if (property_exists(static::class, 'container')) { @@ -87,23 +98,24 @@ protected function headerBuilder(): HeadersBuilder */ protected function requestGet(string $uri, array $queryParameters = [], array $headers = []): Crawler { - if ($this->isAdminContext) { - $headers = array_merge($this->headerBuilder()->withAdminUserAuthorization('api@example.com')->build(), $headers); - } - if (!empty($this->defaultGetHeaders)) { $headers = array_merge($this->defaultGetHeaders, $headers); } - $queryStrings = empty($queryParameters) ? '' : http_build_query($queryParameters); + return $this->request('GET', $uri, $queryParameters, $headers); + } - $uri = $queryStrings ? $uri . '?' . $queryStrings : $uri; + /** + * @param array|string> $queryParameters + * @param array $headers + */ + protected function requestPatch(string $uri, array $queryParameters = [], array $headers = []): Crawler + { + if (!empty($this->defaultPatchHeaders)) { + $headers = array_merge($this->defaultPatchHeaders, $headers); + } - return $this->client->request( - method: 'GET', - uri: $uri, - server: $headers, - ); + return $this->request('PATCH', $uri, $queryParameters, $headers); } /** @throws \Exception */ @@ -116,6 +128,16 @@ protected function assertResponseSuccessful(string $filename): void ); } + /** @throws \Exception */ + protected function assertResponseUnprocessableEntity(string $filename): void + { + $this->assertResponse( + $this->client->getResponse(), + $filename, + Response::HTTP_UNPROCESSABLE_ENTITY, + ); + } + /** * @throws \Exception * @@ -156,4 +178,25 @@ protected function assertJsonResponseViolations(Response $response, array $expec $this->assertContains($expectedViolation['message'], $violationMap[$propertyPath], $responseContent); } } + + /** + * @param array|string> $queryParameters + * @param array $headers + */ + protected function request(string $method, string $uri, array $queryParameters = [], array $headers = []): Crawler + { + if ($this->isAdminContext) { + $headers = array_merge($this->headerBuilder()->withAdminUserAuthorization('api@example.com')->build(), $headers); + } + + $queryStrings = empty($queryParameters) ? '' : http_build_query($queryParameters); + + $uri = $queryStrings ? $uri . '?' . $queryStrings : $uri; + + return $this->client->request( + method: $method, + uri: $uri, + server: $headers, + ); + } } diff --git a/tests/Api/Responses/admin/payment/get_payments_filtered_by_state_response.json b/tests/Api/Responses/admin/payment/get_payments_filtered_by_state_response.json new file mode 100644 index 00000000000..e5c11d736d4 --- /dev/null +++ b/tests/Api/Responses/admin/payment/get_payments_filtered_by_state_response.json @@ -0,0 +1,66 @@ +{ + "@context": "\/api\/v2\/contexts\/Payment", + "@id": "\/api\/v2\/admin\/payments", + "@type": "hydra:Collection", + "hydra:member": [ + { + "@id": "\/api\/v2\/admin\/payments\/@integer@", + "@type": "Payment", + "order": "\/api\/v2\/admin\/orders\/unpaidOrder", + "id": @integer@, + "method": { + "@id": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY", + "@type": "PaymentMethod", + "translations": { + "en_US": { + "@id": "\/api\/v2\/admin\/payment-method-translations\/@integer@", + "@type": "PaymentMethodTranslation", + "name": "Cash on delivery" + } + } + }, + "currencyCode": "USD", + "amount": 6500, + "state": "new", + "details": [], + "createdAt": @datetime@, + "updatedAt": @datetime@ + } + ], + "hydra:totalItems": 1, + "hydra:view": { + "@id": "\/api\/v2\/admin\/payments?state=new", + "@type": "hydra:PartialCollectionView" + }, + "hydra:search": { + "@type": "hydra:IriTemplate", + "hydra:template": "\/api\/v2\/admin\/payments{?state,state[],order.channel.code,order.channel.code[]}", + "hydra:variableRepresentation": "BasicRepresentation", + "hydra:mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "state", + "property": "state", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "state[]", + "property": "state", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "order.channel.code", + "property": "order.channel.code", + "required": false + }, + { + "@type": "IriTemplateMapping", + "variable": "order.channel.code[]", + "property": "order.channel.code", + "required": false + } + ] + } +} diff --git a/tests/Api/Responses/admin/payment/patch_complete_payment_response.json b/tests/Api/Responses/admin/payment/patch_complete_payment_response.json new file mode 100644 index 00000000000..2e136356fd0 --- /dev/null +++ b/tests/Api/Responses/admin/payment/patch_complete_payment_response.json @@ -0,0 +1,14 @@ +{ + "@context": "\/api\/v2\/contexts\/Payment", + "@id": "\/api\/v2\/admin\/payments\/@integer@", + "@type": "Payment", + "order": "\/api\/v2\/admin\/orders\/nAWw2jewpA", + "id": @integer@, + "method": "\/api\/v2\/admin\/payment-methods\/CASH_ON_DELIVERY", + "currencyCode": "USD", + "amount": 6500, + "state": "completed", + "details": [], + "createdAt": @string@, + "updatedAt": @string@ +} diff --git a/tests/Api/Responses/admin/payment/patch_not_complete_payment_response.json b/tests/Api/Responses/admin/payment/patch_not_complete_payment_response.json new file mode 100644 index 00000000000..a8bf495c066 --- /dev/null +++ b/tests/Api/Responses/admin/payment/patch_not_complete_payment_response.json @@ -0,0 +1,6 @@ +{ + "@context": "\/api\/v2\/contexts\/Error", + "@type": "hydra:Error", + "hydra:title": "An error occurred", + "hydra:description": "Cannot complete the payment." +} diff --git a/tests/Api/Shop/ProductReviewsTest.php b/tests/Api/Shop/ProductReviewsTest.php index 31b1e6f01a4..292411ba35c 100644 --- a/tests/Api/Shop/ProductReviewsTest.php +++ b/tests/Api/Shop/ProductReviewsTest.php @@ -142,8 +142,8 @@ public function it_prevents_from_creating_a_product_review_without_email(): void [ 'propertyPath' => 'email', 'message' => 'Please enter your email.', - ] - ] + ], + ], ); } diff --git a/tests/Api/Utils/OrderPlacerTrait.php b/tests/Api/Utils/OrderPlacerTrait.php index 444d938a1fb..929d2d2bc8f 100644 --- a/tests/Api/Utils/OrderPlacerTrait.php +++ b/tests/Api/Utils/OrderPlacerTrait.php @@ -24,6 +24,7 @@ use Sylius\Component\Core\OrderPaymentTransitions; use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Order\OrderTransitions; +use Sylius\Component\Payment\PaymentTransitions; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Stamp\HandledStamp; use Webmozart\Assert\Assert; @@ -163,8 +164,11 @@ protected function payOrder(OrderInterface $order): OrderInterface $stateMachineFactory = $this->get('sm.factory'); - $stateMachine = $stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH); - $stateMachine->apply(OrderPaymentTransitions::TRANSITION_PAY); + $orderStateMachine = $stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH); + $orderStateMachine->apply(OrderPaymentTransitions::TRANSITION_PAY); + + $paymentStateMachine = $stateMachineFactory->get($order->getLastPayment(), PaymentTransitions::GRAPH); + $paymentStateMachine->apply(PaymentTransitions::TRANSITION_COMPLETE); $objectManager->flush();