Skip to content

Commit

Permalink
Merge pull request #120 from AmpersandHQ/rework-pr-92
Browse files Browse the repository at this point in the history
Fix `is_in_stock` not being handled properly with `back_to_stock` on refunds
  • Loading branch information
convenient authored Aug 8, 2023
2 parents d26ec20 + 77d7d91 commit f21992d
Show file tree
Hide file tree
Showing 7 changed files with 504 additions and 19 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"vendor/bin/mtest 'php bin/magento --version'"
],
"docker-run-codeception": [
"URL=\"http://0.0.0.0:1234/\" MYSQL_USER=\"root\" MYSQL_HOST=\"0.0.0.0\" MYSQL_DB=\"magento\" MYSQL_PORT=\"1235\" ./dev/run-codeception.sh"
"TEST_GROUP=$TEST_GROUP URL=\"http://0.0.0.0:1234/\" MYSQL_USER=\"root\" MYSQL_HOST=\"0.0.0.0\" MYSQL_DB=\"magento\" MYSQL_PORT=\"1235\" ./dev/run-codeception.sh"
],
"docker-run-unit-tests": [
"vendor/bin/mtest 'vendor/bin/phpunit -c /var/www/html/dev/tests/unit/phpunit.xml.dist --testsuite Unit --debug'"
Expand Down
90 changes: 90 additions & 0 deletions dev/MagentoTests/Helper/IntegrationHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php
namespace Ampersand\DisableStockReservation\Test\Helper;

use Magento\TestFramework\Helper\Bootstrap;
use Magento\InventoryIndexer\Model\ResourceModel\GetStockItemDataCache;
use Magento\InventorySales\Model\GetStockBySalesChannelCache;
use Magento\CatalogInventory\Model\StockRegistryStorage;
use Magento\InventoryIndexer\Model\GetStockItemData\CacheStorage;
use Magento\InventoryConfiguration\Plugin\InventoryConfiguration\Model\GetLegacyStockItemCache;

class IntegrationHelper
{
public static function clearCaches()
{
self::clearSalesChannelCache();
self::clearGetStockItemDataCache();
self::clearGetLegacyStockItemCache();
self::clearStockRegistryStorage();
}

public static function clearStockRegistryStorage()
{
if (class_exists(StockRegistryStorage::class)) {
$storage = Bootstrap::getObjectManager()->get(StockRegistryStorage::class);
$storage->clean();
}
}

public static function clearSalesChannelCache()
{
if (class_exists(GetStockBySalesChannelCache::class)) {
$getStockBySalesChannelCache = Bootstrap::getObjectManager()->get(GetStockBySalesChannelCache::class);
$ref = new \ReflectionObject($getStockBySalesChannelCache);
try {
$refProperty = $ref->getProperty('channelCodes');
} catch (\ReflectionException $exception) {
$refProperty = $ref->getParentClass()->getProperty('channelCodes');
}
$refProperty->setAccessible(true);
$refProperty->setValue($getStockBySalesChannelCache, []);
}
}

public static function clearGetStockItemDataCache()
{
if (class_exists(GetStockItemDataCache::class) && class_exists(CacheStorage::class)) {
$cacheStorage = Bootstrap::getObjectManager()->get(CacheStorage::class);
if (method_exists($cacheStorage, '_resetState')) {
$cacheStorage->_resetState();
} else {
$ref = new \ReflectionObject($cacheStorage);
try {
$refProperty = $ref->getProperty('cachedItemData');
} catch (\ReflectionException $exception) {
$refProperty = $ref->getParentClass()->getProperty('cachedItemData');
}
$refProperty->setAccessible(true);
$refProperty->setValue($cacheStorage, [[]]);
}
} elseif (class_exists(GetStockItemDataCache::class)) {
$getStockItemDataCache = Bootstrap::getObjectManager()->get(GetStockItemDataCache::class);
$ref = new \ReflectionObject($getStockItemDataCache);
try {
$refProperty = $ref->getProperty('stockItemData');
} catch (\ReflectionException $exception) {
$refProperty = $ref->getParentClass()->getProperty('stockItemData');
}
$refProperty->setAccessible(true);
$refProperty->setValue($getStockItemDataCache, []);
}
}

public static function clearGetLegacyStockItemCache()
{
if (class_exists(GetLegacyStockItemCache::class)) {
$object = Bootstrap::getObjectManager()->get(GetLegacyStockItemCache::class);
$ref = new \ReflectionObject($object);
try {
$refProperty = $ref->getProperty('legacyStockItemsBySku');
} catch (\ReflectionException $exception) {
if (!$ref->getParentClass()) {
return;
}
$refProperty = $ref->getParentClass()->getProperty('legacyStockItemsBySku');
}
$refProperty->setAccessible(true);
$refProperty->setValue($object, []);
}
}
}
14 changes: 3 additions & 11 deletions dev/MagentoTests/Integration/MultipleSourceInventoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
use PHPUnit\Framework\TestCase;
use Magento\InventorySalesApi\Api\StockResolverInterface;
use Magento\InventorySales\Model\GetStockBySalesChannelCache;
require_once __DIR__ . '/../Helper/IntegrationHelper.php';
use Ampersand\DisableStockReservation\Test\Helper\IntegrationHelper as TestHelper;

class MultipleSourceInventoryTest extends TestCase
{
Expand Down Expand Up @@ -219,17 +221,7 @@ private function setStockItemConfigIsDecimal(string $sku, int $stockId): void
*/
private function clearSalesChannelCache(): void
{
if (class_exists(GetStockBySalesChannelCache::class)) {
$getStockBySalesChannelCache = $this->objectManager->get(GetStockBySalesChannelCache::class);
$ref = new \ReflectionObject($getStockBySalesChannelCache);
try {
$refProperty = $ref->getProperty('channelCodes');
} catch (\ReflectionException $exception) {
$refProperty = $ref->getParentClass()->getProperty('channelCodes');
}
$refProperty->setAccessible(true);
$refProperty->setValue($getStockBySalesChannelCache, []);
}
TestHelper::clearCaches();

$stockId = $this->objectManager->get(StockResolverInterface::class)
->execute(SalesChannelInterface::TYPE_WEBSITE, 'eu_website')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
namespace Ampersand\DisableStockReservation\Test\Integration\Refund;

require_once __DIR__ . '/../../Helper/IntegrationHelper.php';
use Ampersand\DisableStockReservation\Test\Helper\IntegrationHelper as TestHelper;
use Magento\TestFramework\Helper\Bootstrap;
use TddWizard\Fixtures\Catalog\ProductBuilder;
use TddWizard\Fixtures\Catalog\ProductFixture;
use TddWizard\Fixtures\Sales\ShipmentBuilder;
use TddWizard\Fixtures\Sales\InvoiceBuilder;
use TddWizard\Fixtures\Checkout\CustomerCheckout;
use TddWizard\Fixtures\Checkout\CartBuilder;
use TddWizard\Fixtures\Customer\CustomerFixture;
use TddWizard\Fixtures\Customer\CustomerBuilder;
use TddWizard\Fixtures\Customer\AddressBuilder;

/**
* @magentoAppArea adminhtml
*/
class ProductGoesBackInStockWhenRefundedTest extends \Magento\TestFramework\TestCase\AbstractBackendController
{
/** @var ProductFixture */
private $productFixture;

/** @var CustomerFixture */
private $customerFixture;


/**
* @dataProvider productBackInStockHandlingDataProvider
*/
public function testProductBackInStockHandling($backToStock, $expectedStockData)
{
/**
* Create a customer and login
*/
$this->customerFixture = new CustomerFixture(CustomerBuilder::aCustomer()->withAddresses(
AddressBuilder::anAddress()->asDefaultBilling(),
AddressBuilder::anAddress()->asDefaultShipping()
)->build());
$this->customerFixture->login();

/**
* Create a product with 5 qty
*/
$this->productFixture = new ProductFixture(
ProductBuilder::aSimpleProduct()
->withPrice(10)
->withStockQty(5)
->withIsInStock(true)
->build()
);

$stockItem = $this->getStockItem($this->productFixture->getSku());
$this->assertTrue($stockItem->getIsInStock(), 'Product should be created in stock');
$this->assertEquals(5, $stockItem->getQty(), 'Product should be created qty=5');
$this->assertEquals(
5,
$this->getSource($this->productFixture->getSku())->getQuantity(),
'The product source should have qty=0 when it is created'
);

/**
* Order 5 qty
*/
$checkout = CustomerCheckout::fromCart(
CartBuilder::forCurrentSession()
->withSimpleProduct(
$this->productFixture->getSku(),
5
)
->build()
);
$order = $checkout
->withPaymentMethodCode('checkmo')
->placeOrder();
$this->assertGreaterThan(0, strlen($order->getIncrementId()), 'the order does not have a valid increment_id');
$this->assertIsNumeric($order->getId(), 'the order does not have an entity_id');

$this->assertEquals(
0,
$this->getSource($this->productFixture->getSku())->getQuantity(),
'The product source should have qty=0 after the order'
);
$stockItem = $this->getStockItem($this->productFixture->getSku());
$this->assertEquals(0, $stockItem->getQty(), 'Product should go qty=0 after purchase');
$this->assertFalse($stockItem->getIsInStock(), 'Product should go is_in_stock=0 after purchase');

/**
* Invoice and ship
*/
$this->assertTrue($order->canInvoice(), 'The order cannot have an invoice created');
$invoice = InvoiceBuilder::forOrder($order)->build();
$this->assertTrue($order->canShip(), 'The order cannot be shipped');
$shipment = ShipmentBuilder::forOrder($order)->build();
$this->assertGreaterThan(0, strlen($shipment->getIncrementId()), 'the shipment does not have a valid increment_id');
$order->save();

/**
* Create a credit memo with return_to_stock (aka setBacKToStock)
*/
$this->assertTrue($order->canCreditmemo(), 'The order cannot have a credit memo created');

/** @var $item \Magento\Sales\Model\Order\Item */
$item = $order->getAllItems()[0];
$this->getRequest()->setParam(
'order_id',
$order->getId()
);
$payload = [
'do_offline' => 1,
'comment_text' => '',
'shipping_amount' => 0,
'adjustment_positive' => 0,
'adjustment_negative' => 0
];
if ($backToStock) {
$payload['items'] = [
$item->getId() => [
'back_to_stock' => $backToStock,
'qty' => 5
]
];
}

$this->getRequest()->setPostValue(
'creditmemo',
$payload
);
$this->getRequest()->setMethod('POST');
$this->dispatch('backend/sales/order_creditmemo/save');
$this->assertEquals(1, count($this->getSessionMessages()), 'We should only have 1 session message');
$this->assertSessionMessages(
$this->equalTo(
['You created the credit memo.'],
\Magento\Framework\Message\MessageInterface::TYPE_SUCCESS
)
);

$stockItem = $this->getStockItem($this->productFixture->getSku());
$this->assertEquals(
$expectedStockData['is_in_stock'],
$stockItem->getIsInStock(),
'is_in_stock does not match expected'
);
$this->assertEquals(
$expectedStockData['qty'],
$stockItem->getQty(),
'qty does not match expected'
);

$this->assertEquals(
$expectedStockData['qty'],
$this->getSource($this->productFixture->getSku())->getQuantity()
);
}

/**
* @param $sku
* @return mixed|null
*/
private function getSource($sku)
{
/** @var \Magento\InventoryApi\Api\GetSourceItemsBySkuInterface $getStockItems */
$getSourceItemsBySku = Bootstrap::getObjectManager()->get(\Magento\InventoryApi\Api\GetSourceItemsBySkuInterface::class);
$sources = $getSourceItemsBySku->execute($this->productFixture->getSku());
$this->assertIsArray($sources);
$this->assertCount(1, $sources);
$source = array_pop($sources);
return $source;
}

/**
* @param $sku
* @return \Magento\CatalogInventory\Api\Data\StockItemInterface\
*/
private function getStockItem($sku)
{
TestHelper::clearCaches();
$registry = Bootstrap::getObjectManager()->create(\Magento\CatalogInventory\Model\StockRegistry::class);
/** @var \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem */
$stockItem = $registry->getStockItemBySku($sku);
$this->assertGreaterThan(0, strlen($stockItem->getItemId()));

TestHelper::clearCaches();
return $stockItem;
}

public function productBackInStockHandlingDataProvider(): array
{
return [
'back_to_stock=true returns items' => [
'back_to_stock' => '1',
'expected_stock_data' => [
'is_in_stock' => true,
'qty' => 5
]
],
'back_to_stock=false does not return items' => [
'back_to_stock' => '0',
'expected_stock_data' => [
'is_in_stock' => false,
'qty' => 0
]
],
];
}

/**
* @return void
*/
protected function tearDown(): void
{
if (isset($this->productFixture)) {
$this->productFixture->rollback();
}
if (isset($this->customerFixture)) {
$this->customerFixture->rollback();;
}
}
}
6 changes: 3 additions & 3 deletions dev/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ composer docker-configure-magento

Run the tests
```
composer docker-run-unit-tests # unit tests
composer docker-run-integration-tests # integration tests
composer docker-run-codeception # codeception rest api tests
composer docker-run-unit-tests # unit tests
composer docker-run-integration-tests # integration tests
TEST_GROUP=2-4-5 composer docker-run-codeception # codeception rest api tests
```

Loading

0 comments on commit f21992d

Please sign in to comment.