HEX
Server: Apache
System: Linux webm019.cluster128.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
User: petsclubcc (68997)
PHP: 5.4.45
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/petsclubcc/prestashop1.6/tests/Integration/classes/CartGetOrderTotalTest.php
<?php

/*
* 2007-2017 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
*  @author PrestaShop SA <contact@prestashop.com>
*  @copyright  2007-2017 PrestaShop SA
*  @license    http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
*  International Registered Trademark & Property of PrestaShop SA
*/

namespace PrestaShop\PrestaShop\Tests\Integration\Classes;

use PrestaShop\PrestaShop\Tests\TestCase\IntegrationTestCase;
use PHPUnit_Framework_Assert as Assert;
use PrestaShop\PrestaShop\Tests\Helper\DatabaseDump;
use Exception;
use Address;
use Carrier;
use Cart;
use CartRule;
use Configuration;
use Context;
use Currency;
use Db;
use Order;
use Product;
use Tools;
use Tax;
use TaxRulesGroup;
use TaxRule;

class CartGetOrderTotalTest extends IntegrationTestCase
{
    private static $dump;
    private static $id_address;

    public static function setUpBeforeClass()
    {
        parent::setUpBeforeClass();

        // Save the database to restore it later: we're not the only test running so let's leave things
        // the way we found them.
        self::$dump = DatabaseDump::create();

        // Some tests might have cleared the configuration
        Configuration::loadConfiguration();

        // Context needs a currency but doesn't set it by itself, use default one.
        Context::getContext()->currency = new Currency(self::getCurrencyId());

        // We'll base all our computations on the invoice address
        Configuration::updateValue('PS_TAX_ADDRESS_TYPE', 'id_address_invoice');

        // We don't care about stock, abstract this away by allowing ordering out of stock products
        Configuration::updateValue('PS_ORDER_OUT_OF_STOCK', true);

        // Create the address only once
        self::$id_address = self::makeAddress()->id;
    }

    public static function tearDownAfterClass()
    {
        // After the test, we restore the database in the state it was
        // before we started.
        self::$dump->restore();
    }

    /**
     * The private static methods below are used to setup the initial conditions
     * for our tests.
     * They should probably be refactored out of the test itself, but since they perform
     * tasks specifically designed for this test (and maybe misleading if used out of context),
     * I'm leaving them here for now.
     *
     * Methods starting with get should cache their result for performance,
     * methods starting with make should create a new object each time.
     */

    private static function deactivateCurrentCartRules()
    {
        Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'cart_rule SET active = 0');
    }

    private static function getLanguageId()
    {
        return (int)Context::getContext()->language->id;
    }

    private static function getDefaultLanguageId()
    {
        return Configuration::get('PS_LANG_DEFAULT');
    }

    private static function getCurrencyId()
    {
        return Configuration::get('PS_CURRENCY_DEFAULT');
    }

    private static function getCountryId()
    {
        return Configuration::get('PS_COUNTRY_DEFAULT');
    }

    private static function setRoundingMode($modeStr)
    {
        $mode = null;

        switch ($modeStr) {
            case 'up':
                $mode = PS_ROUND_UP;
                break;
            case 'down':
                $mode = PS_ROUND_DOWN;
                break;
            case 'half_up':
                $mode = PS_ROUND_HALF_UP;
                break;
            case 'half_down':
                $mode = PS_ROUND_HALF_DOWN;
                break;
            case 'half_even':
                $mode = PS_ROUND_HALF_DOWN;
                break;
            case 'hald_odd':
                $mode = PS_ROUND_HALF_ODD;
                break;
            default:
                throw new Exception(sprintf('Unknown rounding mode `%s`.', $modeStr));
        }

        Configuration::set('PS_PRICE_ROUND_MODE', $mode);

        return $mode;
    }

    private static function setRoundingType($typeStr)
    {
        $type = null;

        switch ($typeStr) {
            case 'item':
                $type = Order::ROUND_ITEM;
                break;
            case 'line':
                $type = Order::ROUND_LINE;
                break;
            case 'total':
                $type = Order::ROUND_TOTAL;
                break;
            default:
                throw new Exception(sprintf('Unknown rounding type `%s`.', $typeStr));
        }

        Configuration::set('PS_ROUND_TYPE', $type);

        return $type;
    }

    private static function setRoundingDecimals($nInt)
    {
        Configuration::set('PS_PRICE_DISPLAY_PRECISION', $nInt);

        return $nInt;
    }

    /**
     * $rate is e.g. 5.5, 20...
     * This is cached by $rate.
     */
    private static function getIdTax($rate)
    {
        static $taxes = array();

        $name = $rate.'% TAX';

        if (!array_key_exists($name, $taxes)) {
            $tax = new Tax(null, self::getDefaultLanguageId());
            $tax->name = $name;
            $tax->rate = $rate;
            $tax->active = true;
            Assert::assertTrue((bool)$tax->save()); // casting because actually returns 1, but not the point here.
            $taxes[$name] = $tax->id;
        }

        return $taxes[$name];
    }

    /**
     * This is cached by $rate.
     */
    private static function getIdTaxRulesGroup($rate)
    {
        static $groups = array();

        $name = $rate.'% TRG';

        if (!array_key_exists($name, $groups)) {
            $taxRulesGroup = new TaxRulesGroup(null, self::getDefaultLanguageId());
            $taxRulesGroup->name = $name;
            $taxRulesGroup->active = true;
            Assert::assertTrue((bool)$taxRulesGroup->save());

            $taxRule = new TaxRule(null, self::getDefaultLanguageId());
            $taxRule->id_tax = self::getIdTax($rate);
            $taxRule->id_country = self::getCountryId();
            $taxRule->id_tax_rules_group = $taxRulesGroup->id;

            Assert::assertTrue($taxRule->save());

            $groups[$name] = $taxRulesGroup->id;
        }

        return $groups[$name];
    }

    /**
     * This is cached by $name.
     */
    private static function makeProduct($name, $price, $id_tax_rules_group)
    {
        $product = new Product(null, false, self::getDefaultLanguageId());
        $product->id_tax_rules_group = $id_tax_rules_group;
        $product->name = $name;
        $product->price = $price;
        $product->link_rewrite = Tools::link_rewrite($name);
        Assert::assertTrue($product->save());
        return $product;
    }

    private static function makeAddress()
    {
        $address = new Address();
        $address->id_country = self::getCountryId();
        $address->firstname = 'Unit';
        $address->lastname = 'Tester';
        $address->address1 = '55 rue Raspail';
        $address->alias = microtime().getmypid();
        $address->city = 'Levallois';
        Assert::assertTrue($address->save());
        return $address;
    }

    private static function makeCart()
    {
        $cart = new Cart(null, self::getDefaultLanguageId());
        $cart->id_currency = self::getCurrencyId();
        $cart->id_address_invoice = self::$id_address;
        Assert::assertTrue($cart->save());
        Context::getContext()->cart = $cart;
        return $cart;
    }

    /**
     * null $shippingCost is interpreted as free shipping
     * Carriers are cached by $name.
     */
    private static function getIdCarrier($name, $shippingCost = null, $id_tax_rules_group = null)
    {
        static $carriers = array();

        if (!array_key_exists($name, $carriers)) {
            $carrier = new Carrier(null, self::getDefaultLanguageId());

            $carrier->name = $name;
            $carrier->delay = '28 days later';

            if (null === $shippingCost) {
                $carrier->is_free = true;
            } else {
                $carrier->range_behavior = false; // take highest range
                $carrier->shipping_method = Carrier::SHIPPING_METHOD_PRICE;
            }

            $carrier->shipping_handling = false;

            Assert::assertTrue($carrier->save());

            if (null !== $id_tax_rules_group) {
                $carrier->setTaxRulesGroup($id_tax_rules_group);
            }

            if (null !== $shippingCost) {
                // Populate one range
                Db::getInstance()->execute('INSERT INTO '._DB_PREFIX_.'range_price (id_carrier, delimiter1, delimiter2) VALUES (
                    '.(int)$carrier->id.',
                    0,1
                )');

                $id_range_price = Db::getInstance()->Insert_ID();
                Assert::assertGreaterThan(0, $id_range_price);

                // apply our shippingCost to all zones
                Db::getInstance()->execute(
                    'INSERT INTO '._DB_PREFIX_.'delivery (id_carrier, id_range_price, id_range_weight, id_zone, price)
                     SELECT '.(int)$carrier->id.', '.(int)$id_range_price.', 0, id_zone, '.(float)$shippingCost.'
                     FROM '._DB_PREFIX_.'zone'
                );

                // enable all zones
                Db::getInstance()->execute(
                    'INSERT INTO '._DB_PREFIX_.'carrier_zone (id_carrier, id_zone)
                     SELECT '.(int)$carrier->id.', id_zone FROM '._DB_PREFIX_.'zone'
                );
            }

            $carriers[$name] = $carrier->id;
        }

        return $carriers[$name];
    }

    private static function makeCartRule($amount, $type)
    {
        $cartRule = new CartRule(null, self::getDefaultLanguageId());

        $cartRule->name = $amount.' '.$type.' Cart Rule';

        $date_from = new \DateTime();
        $date_to = new \DateTime();

        $date_from->modify('-2 days');
        $date_to->modify('+2 days');

        $cartRule->date_from = $date_from->format('Y-m-d H:i:s');
        $cartRule->date_to = $date_to->format('Y-m-d H:i:s');

        $cartRule->quantity = 1;
        $cartRule->quantity_per_user;

        if ($type === 'before tax') {
            $cartRule->reduction_amount = $amount;
            $cartRule->reduction_tax = false;
        } elseif ($type === 'after tax') {
            $cartRule->reduction_amount = $amount;
            $cartRule->reduction_tax = true;
        } elseif ($type === '%') {
            $cartRule->reduction_percent = $amount;
        } else {
            throw new Exception(sprintf("Invalid CartRule type `%s`.", $type));
        }

        Assert::assertTrue($cartRule->save());

        return $cartRule;
    }

    /**
     * End of setup, real tests start here.
     */

    /**
     * Provide sensible defaults for tests that don't specify them.
     */
    public function setUp()
    {
        self::setRoundingType('line');
        self::setRoundingMode('half_up');
        self::setRoundingDecimals(2);
        // Pre-existing cart rules might mess up our test
        self::deactivateCurrentCartRules();
        // Something might have disabled CartRules :)
        Configuration::set('PS_CART_RULE_FEATURE_ACTIVE', true);
        Configuration::set('PS_ATCP_SHIPWRAP', false);
    }

    public function testBasicOnlyProducts()
    {
        $product = self::makeProduct('Hello Product', 10, self::getIdTaxRulesGroup(20));
        $cart = self::makeCart();

        $cart->updateQty(1, $product->id);

        $this->assertEquals(10, $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS));
        $this->assertEquals(12, $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS));
    }

    public function testCartBothWithFreeCarrier()
    {
        $product = self::makeProduct('Hello Product', 10, self::getIdTaxRulesGroup(20));
        $cart = self::makeCart();

        $id_carrier = self::getIdCarrier('free');

        $cart->updateQty(1, $product->id);
        $this->assertEquals(10, $cart->getOrderTotal(false, Cart::BOTH, null, $id_carrier));
        $this->assertEquals(12, $cart->getOrderTotal(true, Cart::BOTH, null, $id_carrier));
    }

    public function testCartBothWithPaidCarrier()
    {
        $product = self::makeProduct('Hello Product', 10, self::getIdTaxRulesGroup(10));
        $cart = self::makeCart();

        $id_carrier = self::getIdCarrier('costs 2', 2, self::getIdTaxRulesGroup(10));

        $cart->updateQty(1, $product->id);
        $this->assertEquals(12, $cart->getOrderTotal(false, Cart::BOTH, null, $id_carrier));
        $this->assertEquals(13.2, $cart->getOrderTotal(true, Cart::BOTH, null, $id_carrier));
    }

    public function testBasicRoundTypeLine()
    {
        self::setRoundingType('line');

        $product_a = self::makeProduct('A Product', 1.236, self::getIdTaxRulesGroup(20));
        $product_b = self::makeProduct('B Product', 2.345, self::getIdTaxRulesGroup(20));

        $cart = self::makeCart();

        $cart->updateQty(1, $product_a->id);
        $cart->updateQty(1, $product_b->id);

        $this->assertEquals(3.59, $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS));
        $this->assertEquals(4.29, $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS));
    }

    public function testBasicRoundTypeTotal()
    {
        self::setRoundingType('total');

        $product_a = self::makeProduct('A Product', 1.236, self::getIdTaxRulesGroup(20));
        $product_b = self::makeProduct('B Product', 2.345, self::getIdTaxRulesGroup(20));

        $cart = self::makeCart();

        $cart->updateQty(1, $product_a->id);
        $cart->updateQty(1, $product_b->id);

        $this->assertEquals(3.58, $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS));
        $this->assertEquals(4.30, $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS));
    }

    public function testBasicCartRuleAmountBeforeTax()
    {
        $id_carrier = self::getIdCarrier('free');

        $product = self::makeProduct('Yo Product', 10, self::getIdTaxRulesGroup(20));


        self::makeCartRule(5, 'before tax')->id;
        $cart = self::makeCart();

        $cart->updateQty(1, $product->id);

        // Control the result without the CartRule
        $this->assertEquals(10, $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS));

        // Check that the CartRule is applied
        $this->assertEquals(5, $cart->getOrderTotal(false, Cart::BOTH, null, $id_carrier));
        $this->assertEquals(6, $cart->getOrderTotal(true, Cart::BOTH, null, $id_carrier));
    }

    /**
     * This test checks that if PS_ATCP_SHIPWRAP is set to true then:
     * - the shipping cost of the carrier is understood as tax included instead of tax excluded
     * - the tax excluded shipping cost is deduced from the tax included shipping cost
     * 	 by removing the average tax rate of the cart
     */
    public function testAverageTaxOfCartProducts_ShippingTax()
    {
        Configuration::set('PS_ATCP_SHIPWRAP', true);

        $highProduct = self::makeProduct('High Product', 10, self::getIdTaxRulesGroup(20));
        $lowProduct = self::makeProduct('Low Product', 10, self::getIdTaxRulesGroup(10));
        $cart = self::makeCart();

        $id_carrier = self::getIdCarrier('costs 5 with tax', 5, null);

        $cart->updateQty(1, $highProduct->id);
        $cart->updateQty(3, $lowProduct->id);

        $preTax = round(5 / (1 + (3 * 10 + 1 * 20) / (4 * 100)), 2);

        $this->assertEquals($preTax, $cart->getOrderTotal(false, Cart::ONLY_SHIPPING, null, $id_carrier));
        $this->assertEquals(5, $cart->getOrderTotal(true, Cart::ONLY_SHIPPING, null, $id_carrier));
    }
}