163 lines
6.2 KiB
PHP
163 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KupShop\ProductReservationBundle\Tests;
|
|
|
|
use KupShop\CatalogBundle\ProductList\ProductList;
|
|
use KupShop\KupShopBundle\Context\UserContext;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\ProductReservationBundle\Util\ProductReservationUtil;
|
|
use Query\Operator;
|
|
|
|
class ProductReservationTest extends \DatabaseTestCase
|
|
{
|
|
private ProductReservationUtil $productReservationUtil;
|
|
private ProductList $productList;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->productReservationUtil = $this->get(ProductReservationUtil::class);
|
|
$this->productList = $this->get(ProductList::class);
|
|
}
|
|
|
|
public function testCreateProductReservation(): void
|
|
{
|
|
$this->assertNotEmpty(
|
|
$this->productReservationUtil->createProductReservation(3, null, 5, ProductReservationUtil::TYPE_RETAIL_RESERVE)
|
|
);
|
|
|
|
$this->assertEquals(5, $this->productReservationUtil->getProductReservedQuantity(3), 'Assert that reservation quantity is same as we set');
|
|
}
|
|
|
|
public function testCreateProductReservationWithInvalidType(): void
|
|
{
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
|
|
$this->productReservationUtil->createProductReservation(3, null, 5, 'nesmysl');
|
|
}
|
|
|
|
/** @dataProvider data_testProductsReservationMultiFetch */
|
|
public function testProductsReservationMultiFetch(int $productId, ?int $quantity): void
|
|
{
|
|
if ($quantity !== null) {
|
|
$this->productReservationUtil->createProductReservation($productId, null, $quantity, ProductReservationUtil::TYPE_RETAIL_RESERVE);
|
|
}
|
|
|
|
$products = $this->productList->andSpec(Operator::equals(['p.id' => $productId]))
|
|
->getProducts();
|
|
$products->fetchReservations();
|
|
|
|
$product = $products->current();
|
|
|
|
if ($quantity !== null) {
|
|
$this->assertNotEmpty($product->reservations);
|
|
$this->assertEquals($quantity, array_sum(array_map(fn ($x) => $x['quantity'], $product->reservations)));
|
|
|
|
return;
|
|
}
|
|
|
|
$this->assertEmpty($product->reservations);
|
|
}
|
|
|
|
public function data_testProductsReservationMultiFetch(): iterable
|
|
{
|
|
yield 'Multi fetch for product with existing reservation' => [3, 5];
|
|
yield 'Multi fetch for product with zero quantity reservation' => [3, 0];
|
|
yield 'Multi fetch for product without existing reservation' => [3, null];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider data_testProductReservationIsAppliedOnProduct
|
|
*/
|
|
public function testProductReservationIsAppliedOnProduct(int $productId, ?int $variationId, int $quantity, string $reservationType, bool $withIsTypeB2B, int $expectedInStore): void
|
|
{
|
|
if ($withIsTypeB2B) {
|
|
Contexts::clear();
|
|
$this->mockUserContextWithIsTypeCallable(fn () => true);
|
|
}
|
|
|
|
$this->productReservationUtil->createProductReservation($productId, $variationId, $quantity, $reservationType);
|
|
|
|
$product = $variationId ? new \Variation($productId, $variationId) : new \Product($productId);
|
|
$product->createFromDB();
|
|
|
|
$this->assertEquals($expectedInStore, $product->inStore);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider data_testProductReservationIsAppliedOnProduct
|
|
*/
|
|
public function testProductReservationIsAppliedOnProductList(int $productId, ?int $variationId, int $quantity, string $reservationType, bool $withIsTypeB2B, int $expectedInStore): void
|
|
{
|
|
if ($withIsTypeB2B) {
|
|
Contexts::clear();
|
|
$this->mockUserContextWithIsTypeCallable(fn () => true);
|
|
}
|
|
|
|
$this->productReservationUtil->createProductReservation($productId, $variationId, $quantity, $reservationType);
|
|
|
|
$productList = $this->productList;
|
|
|
|
// reset `resultModifiers` to remove call of `fetchDeliveryText` that changes inStore value to max(inStore, 0), so negative pieces are removed
|
|
$reflectionClass = new \ReflectionClass($productList);
|
|
$property = $reflectionClass->getProperty('resultModifiers');
|
|
$property->setAccessible(true);
|
|
$property->setValue($productList, []);
|
|
|
|
$product = $productList
|
|
->andSpec(Operator::equals(['p.id' => $productId]))
|
|
->fetchVariations(true, false)
|
|
->getProducts()
|
|
->current();
|
|
|
|
if ($variationId) {
|
|
$this->assertEquals($expectedInStore, $product->variations[$variationId]['in_store']);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->assertEquals($expectedInStore, $product->inStore);
|
|
}
|
|
|
|
public function data_testProductReservationIsAppliedOnProduct(): iterable
|
|
{
|
|
yield 'Product store is 19 pcs, we are creating reservation for 10 pcs so expected store should be 9 pcs' => [9, null, 10, ProductReservationUtil::TYPE_RESERVATION, false, 9];
|
|
yield 'Product store is 0 pcs, we are creating reservation for 5 pcs so expected store should be -5 pcs' => [8, null, 5, ProductReservationUtil::TYPE_RESERVATION, false, -5];
|
|
|
|
yield 'Variation store is 2 pcs, we are creating reservation for 2 pcs so expected store should be 0 pcs' => [2, 9, 2, ProductReservationUtil::TYPE_RESERVATION, false, 0];
|
|
|
|
yield 'Variation store is 2 pcs, we are creating reservation for 2 pcs, but type TYPE_RETAIL_RESERVE is not active so expected store is still 2 pcs' => [2, 9, 2, ProductReservationUtil::TYPE_RETAIL_RESERVE, false, 2];
|
|
yield 'Variation store is 2 pcs, we are creating reservation for 2 pcs and type TYPE_RETAIL_RESERVE is active so expected store is 0 pcs' => [2, 9, 2, ProductReservationUtil::TYPE_RETAIL_RESERVE, true, 0];
|
|
}
|
|
|
|
private function mockUserContextWithIsTypeCallable(callable $fn): void
|
|
{
|
|
$mock = $this->autowire(UserContextMock::class);
|
|
$mock->_setIsTypeCallable($fn);
|
|
|
|
$this->set(UserContext::class, $mock);
|
|
}
|
|
}
|
|
|
|
class UserContextMock extends UserContext
|
|
{
|
|
private ?\Closure $_isTypeCallable = null;
|
|
|
|
public function _setIsTypeCallable(?callable $isTypeCallable): void
|
|
{
|
|
$this->_isTypeCallable = $isTypeCallable;
|
|
}
|
|
|
|
public function isType(string $type): bool
|
|
{
|
|
if ($this->_isTypeCallable) {
|
|
return ($this->_isTypeCallable)($type);
|
|
}
|
|
|
|
return parent::isType($type);
|
|
}
|
|
}
|