| Current Path : /home/rtorresani/www/vendor/magento/framework/Setup/Test/Unit/Patch/ |
| Current File : //home/rtorresani/www/vendor/magento/framework/Setup/Test/Unit/Patch/PatchApplierTest.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Framework\Setup\Test\Unit\Patch;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Module\ModuleResource;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchApplier;
use Magento\Framework\Setup\Patch\PatchBackwardCompatability;
use Magento\Framework\Setup\Patch\PatchFactory;
use Magento\Framework\Setup\Patch\PatchHistory;
use Magento\Framework\Setup\Patch\PatchInterface;
use Magento\Framework\Setup\Patch\PatchReader;
use Magento\Framework\Setup\Patch\PatchRegistry;
use Magento\Framework\Setup\Patch\PatchRegistryFactory;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\SetupInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class PatchApplierTest extends TestCase
{
/**
* @var PatchRegistryFactory|MockObject
*/
private $patchRegistryFactoryMock;
/**
* @var PatchReader|MockObject
*/
private $dataPatchReaderMock;
/**
* @var PatchReader|MockObject
*/
private $schemaPatchReaderMock;
/**
* @var ResourceConnection|MockObject
*/
private $resourceConnectionMock;
/**
* @var ModuleResource|MockObject
*/
private $moduleResourceMock;
/**
* @var PatchHistory|MockObject
*/
private $patchHistoryMock;
/**
* @var PatchFactory|MockObject
*/
private $patchFactoryMock;
/**
* @var SetupInterface|MockObject
*/
private $schemaSetupMock;
/**
* @var ModuleDataSetupInterface|MockObject
*/
private $moduleDataSetupMock;
/**
* @var ObjectManagerInterface|MockObject
*/
private $objectManagerMock;
/**
* @var PatchApplier
*/
private $patchApllier;
/**
* @var AdapterInterface|MockObject
*/
private $connectionMock;
/**
* @var PatchBackwardCompatability|MockObject
*/
private $patchBackwardCompatability;
protected function setUp(): void
{
$this->patchRegistryFactoryMock = $this->createMock(PatchRegistryFactory::class);
$this->dataPatchReaderMock = $this->createMock(PatchReader::class);
$this->schemaPatchReaderMock = $this->createMock(PatchReader::class);
$this->resourceConnectionMock = $this->createMock(ResourceConnection::class);
$this->moduleResourceMock = $this->createMock(ModuleResource::class);
$this->patchHistoryMock = $this->createMock(PatchHistory::class);
$this->patchFactoryMock = $this->createMock(PatchFactory::class);
$this->schemaSetupMock = $this->getMockForAbstractClass(SchemaSetupInterface::class);
$this->moduleDataSetupMock = $this->getMockForAbstractClass(ModuleDataSetupInterface::class);
$this->objectManagerMock = $this->getMockForAbstractClass(ObjectManagerInterface::class);
$this->connectionMock = $this->getMockForAbstractClass(AdapterInterface::class);
$this->moduleDataSetupMock->expects($this->any())->method('getConnection')->willReturn($this->connectionMock);
$objectManager = new ObjectManager($this);
$this->patchBackwardCompatability = $objectManager->getObject(
PatchBackwardCompatability::class,
[
'moduleResource' => $this->moduleResourceMock
]
);
$this->patchApllier = $objectManager->getObject(
PatchApplier::class,
[
'patchRegistryFactory' => $this->patchRegistryFactoryMock,
'dataPatchReader' => $this->dataPatchReaderMock,
'schemaPatchReader' => $this->schemaPatchReaderMock,
'resourceConnection' => $this->resourceConnectionMock,
'moduleResource' => $this->moduleResourceMock,
'patchHistory' => $this->patchHistoryMock,
'patchFactory' => $this->patchFactoryMock,
'objectManager' => $this->objectManagerMock,
'schemaSetup' => $this->schemaSetupMock,
'moduleDataSetup' => $this->moduleDataSetupMock,
'patchBackwardCompatability' => $this->patchBackwardCompatability
]
);
require_once __DIR__ . '/../_files/data_patch_classes.php';
require_once __DIR__ . '/../_files/schema_patch_classes.php';
}
/**
* @param $moduleName
* @param $dataPatches
* @param $moduleVersionInDb
*
* @dataProvider applyDataPatchDataNewModuleProvider()
*/
public function testApplyDataPatchForNewlyInstalledModule($moduleName, $dataPatches, $moduleVersionInDb)
{
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with($moduleName)
->willReturn($dataPatches);
$this->moduleResourceMock->expects($this->any())->method('getDataVersion')->willReturnMap(
[
[$moduleName, $moduleVersionInDb]
]
);
// phpstan:ignore
$patches = [
\SomeDataPatch::class,
// phpstan:ignore
\OtherDataPatch::class
];
$patchRegistryMock = $this->createAggregateIteratorMock(PatchRegistry::class, $patches, ['registerPatch']);
$patchRegistryMock->expects($this->exactly(2))
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
// phpstan:ignore "Class SomeDataPatch not found."
$patch1 = $this->createMock(\SomeDataPatch::class);
$patch1->expects($this->once())->method('apply');
$patch1->expects($this->once())->method('getAliases')->willReturn([]);
// phpstan:ignore "Class OtherDataPatch not found."
$patch2 = $this->createMock(\OtherDataPatch::class);
$patch2->expects($this->once())->method('apply');
$patch2->expects($this->once())->method('getAliases')->willReturn([]);
$this->objectManagerMock->expects($this->any())->method('create')->willReturnMap(
[
// phpstan:ignore
['\\' . \SomeDataPatch::class, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch1],
// phpstan:ignore
['\\' . \OtherDataPatch::class, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch2],
]
);
$this->connectionMock->expects($this->exactly(2))->method('beginTransaction');
$this->connectionMock->expects($this->exactly(2))->method('commit');
$this->patchHistoryMock->expects($this->any())->method('fixPatch')->willReturnMap(
[
[get_class($patch1)],
[get_class($patch2)],
]
);
$this->patchApllier->applyDataPatch($moduleName);
}
/**
* @param $moduleName
* @param $dataPatches
* @param $moduleVersionInDb
*
* @dataProvider applyDataPatchDataNewModuleProvider()
*/
public function testApplyDataPatchForAlias($moduleName, $dataPatches, $moduleVersionInDb)
{
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with($moduleName)
->willReturn($dataPatches);
$this->moduleResourceMock->expects($this->any())->method('getDataVersion')->willReturnMap(
[
[$moduleName, $moduleVersionInDb]
]
);
$patch1 = $this->getMockForAbstractClass(DataPatchInterface::class);
$patch1->expects($this->once())->method('getAliases')->willReturn(['PatchAlias']);
$patchClass = get_class($patch1);
$patchRegistryMock = $this->createAggregateIteratorMock(PatchRegistry::class, [$patchClass], ['registerPatch']);
$patchRegistryMock->expects($this->any())
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
$this->objectManagerMock->expects($this->any())->method('create')->willReturnMap(
[
['\\' . $patchClass, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch1],
]
);
$this->patchApllier->applyDataPatch($moduleName);
}
/**
* @return array
*/
public function applyDataPatchDataNewModuleProvider()
{
return [
'newly installed module' => [
'moduleName' => 'Module1',
'dataPatches' => [
// phpstan:ignore
\SomeDataPatch::class,
// phpstan:ignore
\OtherDataPatch::class
],
'moduleVersionInDb' => null,
],
];
}
/**
* @param $moduleName
* @param $dataPatches
* @param $moduleVersionInDb
*
* @dataProvider applyDataPatchDataInstalledModuleProvider()
*/
public function testApplyDataPatchForInstalledModule($moduleName, $dataPatches, $moduleVersionInDb)
{
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with($moduleName)
->willReturn($dataPatches);
$this->moduleResourceMock->expects($this->any())->method('getDataVersion')->willReturnMap(
[
[$moduleName, $moduleVersionInDb]
]
);
// phpstan:ignore
$patches = [
\SomeDataPatch::class,
// phpstan:ignore
\OtherDataPatch::class
];
$patchRegistryMock = $this->createAggregateIteratorMock(
PatchRegistry::class,
$patches,
['registerPatch']
);
$patchRegistryMock->expects(self::exactly(2))
->method('registerPatch');
$this->patchRegistryFactoryMock->expects(self::any())
->method('create')
->willReturn($patchRegistryMock);
// phpstan:ignore "Class SomeDataPatch not found."
$patch1 = $this->createMock(\SomeDataPatch::class);
$patch1->expects(self::never())->method('apply');
$patch1->expects(self::any())->method('getAliases')->willReturn([]);
// phpstan:ignore "Class OtherDataPatch not found."
$patch2 = $this->createMock(\OtherDataPatch::class);
$patch2->expects(self::once())->method('apply');
$patch2->expects(self::any())->method('getAliases')->willReturn([]);
$this->objectManagerMock->expects(self::any())->method('create')->willReturnMap(
[
// phpstan:ignore
['\\' . \SomeDataPatch::class, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch1],
// phpstan:ignore
['\\' . \OtherDataPatch::class, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch2],
]
);
$this->connectionMock->expects(self::exactly(1))->method('beginTransaction');
$this->connectionMock->expects(self::exactly(1))->method('commit');
$this->patchHistoryMock->expects(self::exactly(2))->method('fixPatch');
$this->patchApllier->applyDataPatch($moduleName);
}
/**
* @return array
*/
public function applyDataPatchDataInstalledModuleProvider()
{
return [
'upgrade module iwth only OtherDataPatch' => [
'moduleName' => 'Module1',
'dataPatches' => [
// phpstan:ignore
\SomeDataPatch::class,
// phpstan:ignore
\OtherDataPatch::class
],
'moduleVersionInDb' => '2.0.0',
]
];
}
/**
* @param $moduleName
* @param $dataPatches
* @param $moduleVersionInDb
*
*
* @dataProvider applyDataPatchDataInstalledModuleProvider()
*/
public function testApplyDataPatchRollback($moduleName, $dataPatches, $moduleVersionInDb)
{
$this->expectException('Exception');
$this->expectExceptionMessage('Patch Apply Error');
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with($moduleName)
->willReturn($dataPatches);
$this->moduleResourceMock->expects($this->any())->method('getDataVersion')->willReturnMap(
[
[$moduleName, $moduleVersionInDb]
]
);
// phpstan:ignore
$patches = [
\SomeDataPatch::class,
// phpstan:ignore
\OtherDataPatch::class
];
$patchRegistryMock = $this->createAggregateIteratorMock(PatchRegistry::class, $patches, ['registerPatch']);
$patchRegistryMock->expects($this->exactly(2))
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
// phpstan:ignore "Class SomeDataPatch not found."
$patch1 = $this->createMock(\SomeDataPatch::class);
$patch1->expects($this->never())->method('apply');
// phpstan:ignore "Class OtherDataPatch not found."
$patch2 = $this->createMock(\OtherDataPatch::class);
$exception = new \Exception('Patch Apply Error');
$patch2->expects($this->once())->method('apply')->willThrowException($exception);
$this->objectManagerMock->expects($this->any())->method('create')->willReturnMap(
[
// phpstan:ignore
['\\' . \SomeDataPatch::class, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch1],
// phpstan:ignore
['\\' . \OtherDataPatch::class, ['moduleDataSetup' => $this->moduleDataSetupMock], $patch2],
]
);
$this->connectionMock->expects($this->exactly(1))->method('beginTransaction');
$this->connectionMock->expects($this->never())->method('commit');
$this->connectionMock->expects($this->exactly(1))->method('rollback');
$this->patchHistoryMock->expects($this->exactly(1))->method('fixPatch');
$this->patchApllier->applyDataPatch($moduleName);
}
public function testNonDataPatchApply()
{
$this->expectException('Exception');
$this->expectExceptionMessageMatches('"Patch [a-zA-Z0-9\_]+ should implement DataPatchInterface"');
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with('module1')
->willReturn([\stdClass::class]);
$patchRegistryMock = $this->createAggregateIteratorMock(
PatchRegistry::class,
[\stdClass::class],
['registerPatch']
);
$patchRegistryMock->expects($this->exactly(1))
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
$this->objectManagerMock->expects($this->any())->method('create')->willReturnMap(
[
[
'\\' . \stdClass::class,
['moduleDataSetup' => $this->moduleDataSetupMock],
$this->createMock(\stdClass::class)
],
]
);
$this->patchApllier->applyDataPatch('module1');
}
public function testNonTransactionablePatch()
{
// phpstan:ignore "Class NonTransactionableDataPatch not found."
$patches = [\NonTransactionableDataPatch::class];
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with('module1')
->willReturn($patches);
$patchRegistryMock = $this->createAggregateIteratorMock(
PatchRegistry::class,
$patches,
['registerPatch']
);
$patchRegistryMock->expects($this->exactly(1))
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
$patch1 = $this->createMock($patches[0]);
$patch1->expects($this->exactly(1))->method('apply');
$this->connectionMock->expects($this->never())->method('beginTransaction');
$this->connectionMock->expects($this->never())->method('commit');
$this->connectionMock->expects($this->never())->method('rollback');
$this->patchHistoryMock->expects($this->once())->method('fixPatch')->with(get_class($patch1));
$this->objectManagerMock->expects($this->any())->method('create')->willReturnMap(
[
[
'\\' . $patches[0],
['moduleDataSetup' => $this->moduleDataSetupMock],
$patch1
],
]
);
$this->patchApllier->applyDataPatch('module1');
}
/**
* @param $moduleName
* @param $schemaPatches
* @param $moduleVersionInDb
*
* @dataProvider schemaPatchDataProvider()
*/
public function testSchemaPatchAplly($moduleName, $schemaPatches, $moduleVersionInDb)
{
$this->schemaPatchReaderMock->expects($this->once())
->method('read')
->with($moduleName)
->willReturn($schemaPatches);
$this->moduleResourceMock->expects($this->any())->method('getDbVersion')->willReturnMap(
[
[$moduleName, $moduleVersionInDb]
]
);
// phpstan:ignore
$patches = [
\SomeSchemaPatch::class,
// phpstan:ignore
\OtherSchemaPatch::class
];
$patchRegistryMock = $this->createAggregateIteratorMock(PatchRegistry::class, $patches, ['registerPatch']);
$patchRegistryMock->expects($this->exactly(2))
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
// phpstan:ignore "Class SomeSchemaPatch not found."
$patch1 = $this->createMock(\SomeSchemaPatch::class);
$patch1->expects($this->never())->method('apply');
$patch1->expects($this->any())->method('getAliases')->willReturn([]);
// phpstan:ignore "Class OtherSchemaPatch not found."
$patch2 = $this->createMock(\OtherSchemaPatch::class);
$patch2->expects($this->once())->method('apply');
$patch2->expects($this->any())->method('getAliases')->willReturn([]);
$this->patchFactoryMock->expects($this->any())->method('create')->willReturnMap(
[
// phpstan:ignore
[\SomeSchemaPatch::class, ['schemaSetup' => $this->schemaSetupMock], $patch1],
// phpstan:ignore
[\OtherSchemaPatch::class, ['schemaSetup' => $this->schemaSetupMock], $patch2],
]
);
$this->connectionMock->expects($this->never())->method('beginTransaction');
$this->connectionMock->expects($this->never())->method('commit');
$this->patchHistoryMock->expects($this->exactly(2))->method('fixPatch');
$this->patchApllier->applySchemaPatch($moduleName);
}
/**
* @param $moduleName
* @param $schemaPatches
* @param $moduleVersionInDb
*
* @dataProvider schemaPatchDataProvider()
*/
public function testSchemaPatchApplyForPatchAlias($moduleName, $schemaPatches, $moduleVersionInDb)
{
$this->schemaPatchReaderMock->expects($this->once())
->method('read')
->with($moduleName)
->willReturn($schemaPatches);
$this->moduleResourceMock->expects($this->any())->method('getDbVersion')->willReturnMap(
[
[$moduleName, $moduleVersionInDb]
]
);
$patch1 = $this->getMockForAbstractClass(PatchInterface::class);
$patch1->expects($this->once())->method('getAliases')->willReturn(['PatchAlias']);
$patchClass = get_class($patch1);
$patchRegistryMock = $this->createAggregateIteratorMock(PatchRegistry::class, [$patchClass], ['registerPatch']);
$patchRegistryMock->expects($this->any())
->method('registerPatch');
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
$this->patchFactoryMock->expects($this->any())->method('create')->willReturn($patch1);
$this->patchApllier->applySchemaPatch($moduleName);
}
public function testRevertDataPatches()
{
// phpstan:ignore "Class RevertableDataPatch not found."
$patches = [\RevertableDataPatch::class];
$this->dataPatchReaderMock->expects($this->once())
->method('read')
->with('module1')
->willReturn($patches);
$patchRegistryMock = $this->createAggregateIteratorMock(
PatchRegistry::class,
$patches,
['registerPatch', 'getReverseIterator']
);
$patchRegistryMock->expects($this->exactly(1))
->method('registerPatch');
$patchRegistryMock->expects($this->once())->method('getReverseIterator')
->willReturn(array_reverse($patches));
$this->patchRegistryFactoryMock->expects($this->any())
->method('create')
->willReturn($patchRegistryMock);
$patch1 = $this->createMock($patches[0]);
$patch1->expects($this->exactly(1))->method('revert');
$this->connectionMock->expects($this->once())->method('beginTransaction');
$this->connectionMock->expects($this->once())->method('commit');
$this->connectionMock->expects($this->never())->method('rollback');
$this->patchHistoryMock->expects($this->once())->method('revertPatchFromHistory')->with(get_class($patch1));
$this->objectManagerMock->expects($this->any())->method('create')->willReturnMap(
[
[
'\\' . $patches[0],
['moduleDataSetup' => $this->moduleDataSetupMock],
$patch1
],
]
);
$this->patchApllier->revertDataPatches('module1');
}
/**
* @return array
*/
public function schemaPatchDataProvider()
{
return [
'upgrade module iwth only OtherSchemaPatch' => [
'moduleName' => 'Module1',
'schemaPatches' => [
// phpstan:ignore
\SomeSchemaPatch::class,
// phpstan:ignore
\OtherSchemaPatch::class
],
'moduleVersionInDb' => '2.0.0',
]
];
}
/**
* Create mock of class that implements IteratorAggregate
*
* @param string $className
* @param array $items
* @param array $methods
* @return MockObject|\IteratorAggregate
* @throws \Exception
*/
private function createAggregateIteratorMock($className, array $items = [], array $methods = [])
{
if (!in_array(ltrim(\IteratorAggregate::class, '\\'), class_implements($className))) {
throw new \Exception('Mock possible only for classes that implement IteratorAggregate interface.');
}
/**
* PHPUnit\Framework\MockObject\MockObject
*/
$someIterator = $this->createMock(\ArrayIterator::class);
$mockIteratorAggregate = $this->getMockBuilder($className)
->disableOriginalConstructor()
->setMethods(array_merge($methods, ['getIterator']))
->getMock();
$mockIteratorAggregate->expects($this->any())->method('getIterator')->willReturn($someIterator);
$iterator = new \ArrayIterator($items);
$someIterator->expects($this->any())
->method('rewind')
->willReturnCallback(
function () use ($iterator) {
$iterator->rewind();
}
);
$someIterator->expects($this->any())
->method('current')
->willReturnCallback(
function () use ($iterator) {
return $iterator->current();
}
);
$someIterator->expects($this->any())
->method('key')
->willReturnCallback(
function () use ($iterator) {
return $iterator->key();
}
);
$someIterator->expects($this->any())
->method('next')
->willReturnCallback(
function () use ($iterator) {
$iterator->next();
}
);
$someIterator->expects($this->any())
->method('valid')
->willReturnCallback(
function () use ($iterator) {
return $iterator->valid();
}
);
return $mockIteratorAggregate;
}
}