magento2-docker/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CartPromotionsTest.php

622 lines
24 KiB
PHP
Executable File

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\GraphQl\Quote;
use Magento\Catalog\Api\CategoryLinkManagementInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\SalesRule\Api\RuleRepositoryInterface;
use Magento\SalesRule\Model\ResourceModel\Rule\Collection;
use Magento\SalesRule\Model\Rule;
use Magento\Tax\Model\ClassModel as TaxClassModel;
use Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory as TaxClassCollectionFactory;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
/**
* Test cases for applying cart promotions to items in cart
*/
class CartPromotionsTest extends GraphQlAbstract
{
/**
* @var float
*/
private const EPSILON = 0.0000000001;
/**
* Test adding single cart rule to multiple products in a cart
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/SalesRule/_files/rules_category.php
*/
public function testCartPromotionSingleCartRule()
{
$skus =['simple1', 'simple2'];
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
/** @var Product $prod2 */
$prod1 = $productRepository->get('simple1');
$prod2 = $productRepository->get('simple2');
$categoryId = 66;
/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */
$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class);
foreach ($skus as $sku) {
$categoryLinkManagement->assignProductToCategories(
$sku,
[$categoryId]
);
}
/** @var Collection $ruleCollection */
$ruleCollection = $objectManager->get(Collection::class);
$ruleLabels = [];
/** @var Rule $rule */
foreach ($ruleCollection as $rule) {
$ruleLabels = $rule->getStoreLabels();
}
$qty = 2;
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
$this->assertCount(2, $response['cart']['items']);
$productsInCart = [$prod1, $prod2];
//validating the line item prices, quantity and discount
$this->assertLineItemDiscountPrices($response, $productsInCart, $qty, $ruleLabels);
//total discount on the cart which is the sum of the individual row discounts
$this->assertEquals($response['cart']['prices']['discounts'][0]['amount']['value'], 21.98);
}
/**
* Assert the row total discounts and individual discount break down and cart rule labels
*
* @param $response
* @param $productsInCart
* @param $qty
* @param $ruleLabels
*/
private function assertLineItemDiscountPrices($response, $productsInCart, $qty, $ruleLabels)
{
$productsInResponse = array_map(null, $response['cart']['items'], $productsInCart);
$count = count($productsInCart);
for ($itemIndex = 0; $itemIndex < $count; $itemIndex++) {
$this->assertNotEmpty($productsInResponse[$itemIndex]);
$this->assertResponseFields(
$productsInResponse[$itemIndex][0],
[
'quantity' => $qty,
'prices' => [
'row_total' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty],
'row_total_including_tax' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty],
'total_item_discount' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty*0.5],
'discounts' => [
0 =>[
'amount' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty*0.5],
'label' => $ruleLabels[0]
]
]
],
]
);
}
}
/**
* Apply multiple cart rules to multiple products in a cart
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/SalesRule/_files/rules_category.php
* @magentoApiDataFixture Magento/SalesRule/_files/cart_rule_10_percent_off_qty_more_than_2_items.php
* @magentoApiDataFixture Magento/SalesRule/_files/cart_rule_free_shipping.php
*/
public function testCartPromotionsMultipleCartRules()
{
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
/** @var Product $prod2 */
$prod1 = $productRepository->get('simple1');
$prod2 = $productRepository->get('simple2');
$productsInCart = [$prod1, $prod2];
$skus =['simple1', 'simple2'];
$categoryId = 66;
/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */
$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class);
foreach ($skus as $sku) {
$categoryLinkManagement->assignProductToCategories(
$sku,
[$categoryId]
);
}
/** @var Collection $ruleCollection */
$ruleCollection = $objectManager->get(Collection::class);
$ruleLabels = [];
/** @var Rule $rule */
foreach ($ruleCollection as $rule) {
$ruleLabels[] = $rule->getStoreLabels();
}
$qty = 2;
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$this->setShippingAddressOnCart($cartId);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
$this->assertCount(2, $response['cart']['items']);
//validating the individual discounts per line item and total discounts per line item
$productsInResponse = array_map(null, $response['cart']['items'], $productsInCart);
$count = count($productsInCart);
for ($itemIndex = 0; $itemIndex < $count; $itemIndex++) {
$this->assertNotEmpty($productsInResponse[$itemIndex]);
$lineItemDiscount = $productsInResponse[$itemIndex][0]['prices']['discounts'];
$expectedTotalDiscountValue = ($productsInCart[$itemIndex]->getSpecialPrice()*$qty*0.5) +
($productsInCart[$itemIndex]->getSpecialPrice()*$qty*0.5*0.1);
$this->assertEqualsWithDelta(
$productsInCart[$itemIndex]->getSpecialPrice()*$qty*0.5,
current($lineItemDiscount)['amount']['value'],
self::EPSILON
);
$this->assertEquals('TestRule_Label', current($lineItemDiscount)['label']);
$lineItemDiscountValue = next($lineItemDiscount)['amount']['value'];
$this->assertEqualsWithDelta(
round($productsInCart[$itemIndex]->getSpecialPrice()*$qty*0.5)*0.1,
$lineItemDiscountValue,
self::EPSILON
);
$this->assertEquals('10% off with two items_Label', end($lineItemDiscount)['label']);
$actualTotalDiscountValue = $lineItemDiscount[0]['amount']['value']+$lineItemDiscount[1]['amount']['value'];
$this->assertEquals(round($expectedTotalDiscountValue, 2), $actualTotalDiscountValue);
//removing the elements from the response so that the rest of the response values can be compared
unset($productsInResponse[$itemIndex][0]['prices']['discounts']);
unset($productsInResponse[$itemIndex][0]['prices']['total_item_discount']);
$this->assertResponseFields(
$productsInResponse[$itemIndex][0],
[
'quantity' => $qty,
'prices' => [
'row_total' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty],
'row_total_including_tax' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty]
],
]
);
}
$this->assertEquals($response['cart']['prices']['discounts'][0]['amount']['value'], 24.18);
}
/**
* Apply cart rules to multiple products in a cart with taxes
* Tax settings : Including and Excluding tax for Price Display and Shopping cart display
* Discount on Prices Includes Tax
* Tax rate = 7.5%
* Cart rule to apply 50% for products assigned to a specific category
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php
* @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_calculation_price_and_cart_display_settings.php
* @magentoApiDataFixture Magento/SalesRule/_files/rules_category.php
*/
public function testCartPromotionsCartRulesWithTaxes()
{
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
/** @var Product $prod2 */
$prod1 = $productRepository->get('simple1');
$prod2 = $productRepository->get('simple2');
$productsInCart = [$prod1, $prod2];
$skus =['simple1', 'simple2'];
/** @var TaxClassCollectionFactory $taxClassCollectionFactory */
$taxClassCollectionFactory = $objectManager->get(TaxClassCollectionFactory::class);
$taxClassCollection = $taxClassCollectionFactory->create();
/** @var TaxClassModel $taxClass */
$taxClassCollection->addFieldToFilter('class_type', TaxClassModel::TAX_CLASS_TYPE_PRODUCT);
$taxClass = $taxClassCollection->getFirstItem();
foreach ($productsInCart as $product) {
$product->setCustomAttribute('tax_class_id', $taxClass->getClassId());
$productRepository->save($product);
}
$categoryId = 66;
/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */
$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class);
foreach ($skus as $sku) {
$categoryLinkManagement->assignProductToCategories(
$sku,
[$categoryId]
);
}
$qty = 1;
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$this->setShippingAddressOnCart($cartId);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
$this->assertCount(2, $response['cart']['items']);
$productsInResponse = array_map(null, $response['cart']['items'], $productsInCart);
$count = count($productsInCart);
for ($itemIndex = 0; $itemIndex < $count; $itemIndex++) {
$this->assertNotEmpty($productsInResponse[$itemIndex]);
$rowTotalIncludingTax = round(
$productsInCart[$itemIndex]->getSpecialPrice()*$qty +
$productsInCart[$itemIndex]->getSpecialPrice()*$qty*.075,
2
);
$this->assertResponseFields(
$productsInResponse[$itemIndex][0],
[
'quantity' => $qty,
'prices' => [
// row_total is the line item price without the tax
'row_total' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty],
// row_total including tax is the price + price * tax rate
'row_total_including_tax' => ['value' => $rowTotalIncludingTax],
// discount from cart rule after tax is applied : 50% of row_total_including_tax
'total_item_discount' => ['value' => round($rowTotalIncludingTax/2, 2)],
'discounts' => [
0 =>[
'amount' => ['value' => round($rowTotalIncludingTax/2, 2)],
'label' => 'TestRule_Label'
]
]
],
]
);
}
// checking the total discount on the entire cart
$this->assertEquals($response['cart']['prices']['discounts'][0]['amount']['value'], 11.82);
}
/**
* Apply cart rule with a fixed discount when specific coupon code
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php
*/
public function testCartPromotionsWithCoupons()
{
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
/** @var Product $prod2 */
$prod1 = $productRepository->get('simple1');
$prod2 = $productRepository->get('simple2');
$productsInCart = [$prod1, $prod2];
$skus =['simple1', 'simple2'];
/** @var Collection $ruleCollection */
$ruleCollection = $objectManager->get(Collection::class);
$ruleLabels = [];
/** @var Rule $rule */
foreach ($ruleCollection as $rule) {
$ruleLabels = $rule->getStoreLabels();
}
$qty = 2;
// coupon code obtained from the fixture
$couponCode = '2?ds5!2d';
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$this->applyCouponsToCart($cartId, $couponCode);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
$this->assertCount(2, $response['cart']['items']);
$productsInResponse = array_map(null, $response['cart']['items'], $productsInCart);
$count = count($productsInCart);
for ($itemIndex = 0; $itemIndex < $count; $itemIndex++) {
$this->assertNotEmpty($productsInResponse[$itemIndex]);
$sumOfPricesForBothProducts = 43.96;
$rowTotal = ($productsInCart[$itemIndex]->getSpecialPrice()*$qty);
$this->assertResponseFields(
$productsInResponse[$itemIndex][0],
[
'quantity' => $qty,
'prices' => [
'row_total' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty],
'row_total_including_tax' => ['value' => $productsInCart[$itemIndex]->getSpecialPrice()*$qty],
'total_item_discount' => ['value' => round(($rowTotal/$sumOfPricesForBothProducts)*5, 2)],
'discounts' => [
0 =>[
'amount' => ['value' => round(($rowTotal/$sumOfPricesForBothProducts)*5, 2)],
'label' => $ruleLabels[0]
]
]
],
]
);
}
$this->assertEquals($response['cart']['prices']['discounts'][0]['amount']['value'], 5);
}
/**
* If no discount is applicable to the cart, row total discount should be zero and no rule label shown
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/SalesRule/_files/buy_3_get_1_free.php
*/
public function testCartPromotionsWhenNoDiscountIsAvailable()
{
$skus =['simple1', 'simple2'];
$qty = 2;
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
$this->assertCount(2, $response['cart']['items']);
foreach ($response['cart']['items'] as $cartItems) {
$this->assertEquals(0, $cartItems['prices']['total_item_discount']['value']);
$this->assertNull($cartItems['prices']['discounts']);
}
}
/**
* Validating if the discount label in the response shows the default value if no label is available on cart rule
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/SalesRule/_files/cart_rule_10_percent_off.php
*/
public function testCartPromotionsWithNoRuleLabels()
{
$skus =['simple1', 'simple2'];
$qty = 1;
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
//total items added to cart
$this->assertCount(2, $response['cart']['items']);
//checking the default label for individual line item when cart rule doesn't have a label set
foreach ($response['cart']['items'] as $cartItem) {
$this->assertEquals('Discount', $cartItem['prices']['discounts'][0]['label']);
}
}
/**
* Test fixed discount cannot be higher than products price
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php
*/
public function testCartPromotionsFixedDiscountNotHigherThanProductsPrice()
{
/** @var ProductRepositoryInterface $productRepository */
$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
/** @var Product $prod2 */
$prod1 = $productRepository->get('simple1');
$prod2 = $productRepository->get('simple2');
$productsInCart = [$prod1, $prod2];
$skus =['simple1', 'simple2'];
$qty = 2;
$sumOfPricesForBothProducts = 43.96;
$couponCode = '2?ds5!2d';
/** @var RuleRepositoryInterface $ruleRepository */
$ruleRepository = Bootstrap::getObjectManager()->get(RuleRepositoryInterface::class);
/** @var Collection $ruleCollection */
$ruleCollection = Bootstrap::getObjectManager()->get(Collection::class);
$ruleLabels = [];
/** @var Rule $rule */
foreach ($ruleCollection as $rule) {
$ruleLabels = $rule->getStoreLabels();
$salesRule = $ruleRepository->getById($rule->getRuleId());
$salesRule->setDiscountAmount(50);
$ruleRepository->save($salesRule);
}
$cartId = $this->createEmptyCart();
$this->addMultipleSimpleProductsToCart($cartId, $qty, $skus[0], $skus[1]);
$this->applyCouponsToCart($cartId, $couponCode);
$query = $this->getCartItemPricesQuery($cartId);
$response = $this->graphQlMutation($query);
$this->assertCount(2, $response['cart']['items']);
$productsInResponse = array_map(null, $response['cart']['items'], $productsInCart);
$count = count($productsInCart);
for ($itemIndex = 0; $itemIndex < $count; $itemIndex++) {
$this->assertNotEmpty($productsInResponse[$itemIndex]);
$rowTotal = ($productsInCart[$itemIndex]->getSpecialPrice()*$qty);
$this->assertResponseFields(
$productsInResponse[$itemIndex][0],
[
'quantity' => $qty,
'prices' => [
'row_total' => ['value' => $rowTotal],
'row_total_including_tax' => ['value' => $rowTotal],
'total_item_discount' => ['value' => $rowTotal],
'discounts' => [
0 =>[
'amount' => ['value' => $rowTotal],
'label' => $ruleLabels[0]
]
]
],
]
);
}
$this->assertEquals(
$response['cart']['prices']['discounts'][0]['amount']['value'],
$sumOfPricesForBothProducts
);
}
/**
* Apply coupon to the cart
*
* @param string $cartId
* @param string $couponCode
*/
private function applyCouponsToCart(string $cartId, string $couponCode)
{
$query = <<<QUERY
mutation {
applyCouponToCart(input: {cart_id: "$cartId", coupon_code: "$couponCode"}) {
cart {
applied_coupons {
code
}
}
}
}
QUERY;
$response = $this->graphQlMutation($query);
self::assertArrayHasKey('applyCouponToCart', $response);
self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupons'][0]['code']);
}
/**
* @param string $cartId
* @return string
*/
private function getCartItemPricesQuery(string $cartId): string
{
return <<<QUERY
{
cart(cart_id:"{$cartId}"){
items{
quantity
prices{
row_total{
value
}
row_total_including_tax{
value
}
total_item_discount{value}
discounts{
amount{value}
label
}
}
}
prices{
discounts{
amount{value}
}
}
}
}
QUERY;
}
/**
* @return string
*/
private function createEmptyCart(): string
{
$query = <<<QUERY
mutation {
createEmptyCart
}
QUERY;
$response = $this->graphQlMutation($query);
$cartId = $response['createEmptyCart'];
return $cartId;
}
/**
* @param string $cartId
* @param int $sku1
* @param int $qty
* @param string $sku2
*/
private function addMultipleSimpleProductsToCart(string $cartId, int $qty, string $sku1, string $sku2): void
{
$query = <<<QUERY
mutation {
addSimpleProductsToCart(input: {
cart_id: "{$cartId}",
cart_items: [
{
data: {
quantity: $qty
sku: "$sku1"
}
}
{
data: {
quantity: $qty
sku: "$sku2"
}
}
]
}
) {
cart {
items {
product{sku}
quantity
}
}
}
}
QUERY;
$response = $this->graphQlMutation($query);
self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']);
self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][0]['quantity']);
self::assertEquals($sku1, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']);
self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][1]['quantity']);
self::assertEquals($sku2, $response['addSimpleProductsToCart']['cart']['items'][1]['product']['sku']);
}
/**
* Set shipping address for the region for which tax rule is set
*
* @param string $cartId
* @return void
*/
private function setShippingAddressOnCart(string $cartId) :void
{
$query = <<<QUERY
mutation {
setShippingAddressesOnCart(
input: {
cart_id: "$cartId"
shipping_addresses: [
{
address: {
firstname: "John"
lastname: "Doe"
company: "Magento"
street: ["test street 1", "test street 2"]
city: "Montgomery"
region: "AL"
postcode: "36043"
country_code: "US"
telephone: "88776655"
save_in_address_book: false
}
}
]
}
) {
cart {
shipping_addresses {
city
region{label}
}
}
}
}
QUERY;
$response = $this->graphQlMutation($query);
self::assertEquals(
'Montgomery',
$response['setShippingAddressesOnCart']['cart']['shipping_addresses'][0]['city']
);
self::assertEquals(
'Alabama',
$response['setShippingAddressesOnCart']['cart']['shipping_addresses'][0]['region']['label']
);
}
}