diff --git a/app/code/Magento/Backup/Test/Unit/Controller/Adminhtml/Index/RollbackTest.php b/app/code/Magento/Backup/Test/Unit/Controller/Adminhtml/Index/RollbackTest.php
new file mode 100644
index 0000000000000..9f306cfc576d8
--- /dev/null
+++ b/app/code/Magento/Backup/Test/Unit/Controller/Adminhtml/Index/RollbackTest.php
@@ -0,0 +1,288 @@
+objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class)
+ ->getMock();
+ $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class)
+ ->setMethods(['initForward', 'setDispatched', 'isAjax'])
+ ->getMockForAbstractClass();
+ $this->responseMock = $this->getMockBuilder(\Magento\Framework\App\ResponseInterface::class)
+ ->setMethods(['setRedirect', 'representJson'])
+ ->getMockForAbstractClass();
+ $this->backupModelFactoryMock = $this->getMockBuilder(\Magento\Backup\Model\BackupFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $this->backupModelMock = $this->getMockBuilder(\Magento\Backup\Model\Backup::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getTime', 'exists', 'getSize', 'output', 'validateUserPassword'])
+ ->getMock();
+ $this->backupResourceModelMock = $this->getMockBuilder(\Magento\Backup\Model\ResourceModel\Db::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->dataHelperMock = $this->getMockBuilder(\Magento\Backup\Helper\Data::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['isRollbackAllowed', 'getBackupsDir', 'invalidateCache'])
+ ->getMock();
+ $this->fileFactoryMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->resultRedirectFactoryMock =
+ $this->getMockBuilder(\Magento\Backend\Model\View\Result\RedirectFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $this->resultRedirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->resultForwardMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Forward::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->backupFactoryMock = $this->getMockBuilder(\Magento\Framework\Backup\Factory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $this->backupManagerMock = $this->getMockBuilder(\Magento\Framework\Backup\BackupInterface::class)
+ ->setMethods(['setName'])
+ ->getMockForAbstractClass();
+ $this->objectManager = new ObjectManager($this);
+ $this->context = $this->objectManager->getObject(
+ \Magento\Backend\App\Action\Context::class,
+ [
+ 'objectManager' => $this->objectManagerMock,
+ 'request' => $this->requestMock,
+ 'response' => $this->responseMock,
+ 'resultRedirectFactory' => $this->resultRedirectFactoryMock
+ ]
+ );
+ $this->rollbackController = $this->objectManager->getObject(
+ \Magento\Backup\Controller\Adminhtml\Index\Rollback::class,
+ [
+ 'context' => $this->context,
+ 'backupFactory' => $this->backupFactoryMock,
+ 'backupModelFactory' => $this->backupModelFactoryMock,
+ 'fileFactory' => $this->fileFactoryMock
+ ]
+ );
+ }
+
+ public function testExecuteRollbackDisabled()
+ {
+ $rollbackAllowed = false;
+
+ $this->dataHelperMock->expects($this->once())
+ ->method('isRollbackAllowed')
+ ->willReturn($rollbackAllowed);
+ $this->objectManagerMock->expects($this->once())
+ ->method('get')
+ ->with(\Magento\Backup\Helper\Data::class)
+ ->willReturn($this->dataHelperMock);
+
+ $this->assertSame($this->responseMock, $this->rollbackController->execute());
+ }
+
+ public function testExecuteBackupNotFound()
+ {
+ $rollbackAllowed = true;
+ $isAjax = true;
+ $time = 0;
+ $type = 'db';
+ $exists = false;
+
+ $this->dataHelperMock->expects($this->once())
+ ->method('isRollbackAllowed')
+ ->willReturn($rollbackAllowed);
+ $this->objectManagerMock->expects($this->atLeastOnce())
+ ->method('get')
+ ->with(\Magento\Backup\Helper\Data::class)
+ ->willReturn($this->dataHelperMock);
+ $this->requestMock->expects($this->once())
+ ->method('isAjax')
+ ->willReturn($isAjax);
+ $this->backupModelMock->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturn($time);
+ $this->backupModelMock->expects($this->any())
+ ->method('exists')
+ ->willReturn($exists);
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap(
+ [
+ ['time', null, $time],
+ ['type', null, $type]
+ ]
+ );
+ $this->backupModelFactoryMock->expects($this->once())
+ ->method('create')
+ ->with($time, $type)
+ ->willReturn($this->backupModelMock);
+
+ $this->assertSame($this->responseMock, $this->rollbackController->execute());
+ }
+
+ public function testExecute()
+ {
+ $rollbackAllowed = true;
+ $isAjax = true;
+ $time = 1;
+ $type = 'db';
+ $exists = true;
+ $passwordValid = true;
+
+ $this->dataHelperMock->expects($this->once())
+ ->method('isRollbackAllowed')
+ ->willReturn($rollbackAllowed);
+ $this->objectManagerMock->expects($this->any())
+ ->method('get')
+ ->with(\Magento\Backup\Helper\Data::class)
+ ->willReturn($this->dataHelperMock);
+ $this->requestMock->expects($this->once())
+ ->method('isAjax')
+ ->willReturn($isAjax);
+ $this->backupModelMock->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturn($time);
+ $this->backupModelMock->expects($this->any())
+ ->method('exists')
+ ->willReturn($exists);
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap(
+ [
+ ['time', null, $time],
+ ['type', null, $type]
+ ]
+ );
+ $this->backupModelFactoryMock->expects($this->once())
+ ->method('create')
+ ->with($time, $type)
+ ->willReturn($this->backupModelMock);
+ $this->backupManagerMock->expects($this->once())
+ ->method('setBackupExtension')
+ ->willReturn($this->backupManagerMock);
+ $this->backupManagerMock->expects($this->once())
+ ->method('setTime')
+ ->willReturn($this->backupManagerMock);
+ $this->backupManagerMock->expects($this->once())
+ ->method('setBackupsDir')
+ ->willReturn($this->backupManagerMock);
+ $this->backupManagerMock->expects($this->once())
+ ->method('setName')
+ ->willReturn($this->backupManagerMock);
+ $this->backupManagerMock->expects($this->once())
+ ->method('setResourceModel')
+ ->willReturn($this->backupManagerMock);
+ $this->backupFactoryMock->expects($this->once())
+ ->method('create')
+ ->with($type)
+ ->willReturn($this->backupManagerMock);
+ $this->objectManagerMock->expects($this->at(2))
+ ->method('create')
+ ->with(\Magento\Backup\Model\ResourceModel\Db::class, [])
+ ->willReturn($this->backupResourceModelMock);
+ $this->objectManagerMock->expects($this->at(3))
+ ->method('create')
+ ->with(\Magento\Backup\Model\Backup::class, [])
+ ->willReturn($this->backupModelMock);
+ $this->backupModelMock->expects($this->once())
+ ->method('validateUserPassword')
+ ->willReturn($passwordValid);
+
+ $this->rollbackController->execute();
+ }
+}
diff --git a/app/code/Magento/Developer/Model/Css/PreProcessor/FileGenerator/PublicationDecorator.php b/app/code/Magento/Developer/Model/Css/PreProcessor/FileGenerator/PublicationDecorator.php
index 57af124ba1182..ab7b46ed35d7e 100644
--- a/app/code/Magento/Developer/Model/Css/PreProcessor/FileGenerator/PublicationDecorator.php
+++ b/app/code/Magento/Developer/Model/Css/PreProcessor/FileGenerator/PublicationDecorator.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Developer\Model\Css\PreProcessor\FileGenerator;
+use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\State;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Filesystem;
use Magento\Framework\View\Asset\Repository;
use Magento\Framework\App\View\Asset\Publisher;
@@ -36,6 +39,11 @@ class PublicationDecorator extends RelatedGenerator
*/
private $hasRelatedPublishing;
+ /**
+ * @var State
+ */
+ private $state;
+
/**
* Constructor
*
@@ -66,12 +74,27 @@ public function __construct(
protected function generateRelatedFile($relatedFileId, LocalInterface $asset)
{
$relatedAsset = parent::generateRelatedFile($relatedFileId, $asset);
- if ($this->hasRelatedPublishing
- || WorkflowType::CLIENT_SIDE_COMPILATION === $this->scopeConfig->getValue(WorkflowType::CONFIG_NAME_PATH)
- ) {
+ $isClientSideCompilation =
+ $this->getState()->getMode() !== State::MODE_PRODUCTION
+ && WorkflowType::CLIENT_SIDE_COMPILATION === $this->scopeConfig->getValue(WorkflowType::CONFIG_NAME_PATH);
+
+ if ($this->hasRelatedPublishing || $isClientSideCompilation) {
$this->assetPublisher->publish($relatedAsset);
}
return $relatedAsset;
}
+
+ /**
+ * @return State
+ * @deprecated
+ */
+ private function getState()
+ {
+ if (null === $this->state) {
+ $this->state = ObjectManager::getInstance()->get(State::class);
+ }
+
+ return $this->state;
+ }
}
diff --git a/app/code/Magento/Developer/Model/View/Asset/PreProcessor/PreprocessorStrategy.php b/app/code/Magento/Developer/Model/View/Asset/PreProcessor/PreprocessorStrategy.php
index a3051ce443b19..aa40db410a4ce 100644
--- a/app/code/Magento/Developer/Model/View/Asset/PreProcessor/PreprocessorStrategy.php
+++ b/app/code/Magento/Developer/Model/View/Asset/PreProcessor/PreprocessorStrategy.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Developer\Model\View\Asset\PreProcessor;
+use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\State;
use Magento\Framework\View\Asset\PreProcessor;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Developer\Model\Config\Source\WorkflowType;
@@ -31,6 +34,11 @@ class PreprocessorStrategy implements PreProcessorInterface
*/
private $scopeConfig;
+ /**
+ * @var State
+ */
+ private $state;
+
/**
* Constructor
*
@@ -56,10 +64,26 @@ public function __construct(
*/
public function process(PreProcessor\Chain $chain)
{
- if (WorkflowType::CLIENT_SIDE_COMPILATION === $this->scopeConfig->getValue(WorkflowType::CONFIG_NAME_PATH)) {
+ $isClientSideCompilation =
+ $this->getState()->getMode() !== State::MODE_PRODUCTION
+ && WorkflowType::CLIENT_SIDE_COMPILATION === $this->scopeConfig->getValue(WorkflowType::CONFIG_NAME_PATH);
+
+ if ($isClientSideCompilation) {
$this->frontendCompilation->process($chain);
} else {
$this->alternativeSource->process($chain);
}
}
+
+ /**
+ * @return State
+ * @deprecated
+ */
+ private function getState()
+ {
+ if (null === $this->state) {
+ $this->state = ObjectManager::getInstance()->get(State::class);
+ }
+ return $this->state;
+ }
}
diff --git a/app/code/Magento/Developer/Model/View/Page/Config/RendererFactory.php b/app/code/Magento/Developer/Model/View/Page/Config/RendererFactory.php
index c6e68be05acc9..3301d4b553c63 100644
--- a/app/code/Magento/Developer/Model/View/Page/Config/RendererFactory.php
+++ b/app/code/Magento/Developer/Model/View/Page/Config/RendererFactory.php
@@ -6,6 +6,8 @@
namespace Magento\Developer\Model\View\Page\Config;
use Magento\Developer\Model\Config\Source\WorkflowType;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\State;
use Magento\Store\Model\ScopeInterface;
/**
@@ -58,7 +60,9 @@ public function __construct(
*/
public function create(array $data = [])
{
- $renderer = $this->scopeConfig->getValue(WorkflowType::CONFIG_NAME_PATH, ScopeInterface::SCOPE_STORE);
+ $renderer = $this->objectManager->get(State::class)->getMode() === State::MODE_PRODUCTION ?
+ WorkflowType::SERVER_SIDE_COMPILATION :
+ $this->scopeConfig->getValue(WorkflowType::CONFIG_NAME_PATH, ScopeInterface::SCOPE_STORE);
return $this->objectManager->create(
$this->rendererTypes[$renderer],
diff --git a/app/code/Magento/Developer/Test/Unit/Model/Css/PreProcessor/FileGenerator/PublicationDecoratorTest.php b/app/code/Magento/Developer/Test/Unit/Model/Css/PreProcessor/FileGenerator/PublicationDecoratorTest.php
index cddab4d253acc..8233437cf927f 100644
--- a/app/code/Magento/Developer/Test/Unit/Model/Css/PreProcessor/FileGenerator/PublicationDecoratorTest.php
+++ b/app/code/Magento/Developer/Test/Unit/Model/Css/PreProcessor/FileGenerator/PublicationDecoratorTest.php
@@ -5,71 +5,131 @@
*/
namespace Magento\Developer\Test\Unit\Model\Css\PreProcessor\FileGenerator;
+use Magento\Framework\App\State;
+use Magento\Framework\App\View\Asset\Publisher;
+use Magento\Framework\Css\PreProcessor\Instruction\Import;
use Magento\Framework\Filesystem;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Css\PreProcessor\File\Temporary;
use Magento\Developer\Model\Css\PreProcessor\FileGenerator\PublicationDecorator;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\View\Asset\File;
+use Magento\Framework\View\Asset\LocalInterface;
+use Magento\Framework\View\Asset\Repository;
/**
- * Class PublicationDecoratorTest
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class PublicationDecoratorTest extends \PHPUnit_Framework_TestCase
{
/**
- * Calls generate method to access protected method generateRelatedFile
+ * @var PublicationDecorator
*/
- public function testGenerateRelatedFile()
+ private $model;
+
+ /**
+ * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $filesystemMock;
+
+ /**
+ * @var Temporary|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $temporaryFileMock;
+
+ /**
+ * @var Publisher|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $publisherMock;
+
+ /**
+ * @var Repository|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $assetRepositoryMock;
+
+ /**
+ * @var File|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $relatedAssetMock;
+
+ /**
+ * @var Import|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $importGeneratorMock;
+
+ /**
+ * @var LocalInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $localAssetMock;
+
+ /**
+ * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $scopeConfigMock;
+
+ /**
+ * @var State|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $stateMock;
+
+ protected function setUp()
{
- $filesystemMock = $this->getMockBuilder(Filesystem::class)
+ $this->filesystemMock = $this->getMockBuilder(Filesystem::class)
->disableOriginalConstructor()
->getMock();
- $fileTemporaryMock = $this->getMockBuilder(Temporary::class)
+ $this->temporaryFileMock = $this->getMockBuilder(Temporary::class)
->disableOriginalConstructor()
->getMock();
-
- $publisherMock = $this->getMockBuilder(\Magento\Framework\App\View\Asset\Publisher::class)
+ $this->publisherMock = $this->getMockBuilder(Publisher::class)
->disableOriginalConstructor()
->getMock();
- $assetRepoMock = $this->getMockBuilder(\Magento\Framework\View\Asset\Repository::class)
+ $this->assetRepositoryMock = $this->getMockBuilder(Repository::class)
->disableOriginalConstructor()
->getMock();
- $relatedAssetMock = $this->getMockBuilder(\Magento\Framework\View\Asset\File::class)
+ $this->relatedAssetMock = $this->getMockBuilder(File::class)
->disableOriginalConstructor()
->getMock();
- $importGeneratorMock = $this->getMockBuilder(\Magento\Framework\Css\PreProcessor\Instruction\Import::class)
+ $this->importGeneratorMock = $this->getMockBuilder(Import::class)
->disableOriginalConstructor()
->getMock();
- $localAssetMock = $this->getMockBuilder(\Magento\Framework\View\Asset\LocalInterface::class)
+ $this->localAssetMock = $this->getMockBuilder(LocalInterface::class)
->disableOriginalConstructor()
->getMock();
- $scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)
+ $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)
->getMockForAbstractClass();
+ $this->stateMock = $this->getMockBuilder(State::class)
+ ->disableOriginalConstructor()
+ ->getMock();
- $relatedFileId = 'file_id';
+ $this->model = (new ObjectManager($this))->getObject(PublicationDecorator::class, [
+ 'filesystem' => $this->filesystemMock,
+ 'assetRepository' => $this->assetRepositoryMock,
+ 'temporaryFile' => $this->temporaryFileMock,
+ 'assetPublisher' => $this->publisherMock,
+ 'scopeConfig' => $this->scopeConfigMock,
+ 'state' => $this->stateMock,
+ 'hasRelatedPublishing' => true
+ ]);
+ }
- $relatedFiles = [[$relatedFileId, $localAssetMock]];
+ /**
+ * Calls generate method to access protected method generateRelatedFile
+ */
+ public function testGenerateRelatedFile()
+ {
+ $relatedFileId = 'file_id';
+ $relatedFiles = [[$relatedFileId, $this->localAssetMock]];
- $importGeneratorMock->expects(self::any())
+ $this->importGeneratorMock->expects($this->any())
->method('getRelatedFiles')
- ->will(self::onConsecutiveCalls($relatedFiles, []));
-
- $assetRepoMock->expects(self::any())
+ ->willReturnOnConsecutiveCalls($relatedFiles, []);
+ $this->assetRepositoryMock->expects($this->any())
->method('createRelated')
- ->willReturn($relatedAssetMock);
-
- $publisherMock->expects(self::once())
+ ->willReturn($this->relatedAssetMock);
+ $this->publisherMock->expects($this->once())
->method('publish')
- ->with($relatedAssetMock);
-
- $model = new PublicationDecorator(
- $filesystemMock,
- $assetRepoMock,
- $fileTemporaryMock,
- $publisherMock,
- $scopeConfigMock,
- true
- );
-
- $model->generate($importGeneratorMock);
+ ->with($this->relatedAssetMock);
+
+ $this->model->generate($this->importGeneratorMock);
}
}
diff --git a/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/PreprocessorStrategyTest.php b/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/PreprocessorStrategyTest.php
index 25fbe112e7dc8..d27fa0642d061 100644
--- a/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/PreprocessorStrategyTest.php
+++ b/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/PreprocessorStrategyTest.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Developer\Test\Unit\Model\View\Asset\PreProcessor;
+use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\State;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Framework\View\Asset\PreProcessor\Chain;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Developer\Model\Config\Source\WorkflowType;
@@ -16,6 +19,7 @@
* Class PreprocessorStrategyTest
*
* @see \Magento\Developer\Model\View\Asset\PreProcessor\PreprocessorStrategy
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class PreprocessorStrategyTest extends \PHPUnit_Framework_TestCase
{
@@ -39,6 +43,16 @@ class PreprocessorStrategyTest extends \PHPUnit_Framework_TestCase
*/
private $scopeConfigMock;
+ /**
+ * @var State|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $stateMock;
+
+ /**
+ * @var \Magento\Framework\App\ObjectManager|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $objectMangerMock;
+
/**
* Set up
*/
@@ -51,12 +65,19 @@ protected function setUp()
->getMock();
$this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)
->getMockForAbstractClass();
+ $this->stateMock = $this->getMockBuilder(State::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->objectMangerMock = $this->getMockBuilder(\Magento\Framework\App\ObjectManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
- $this->preprocessorStrategy = new PreprocessorStrategy(
- $this->alternativeSourceMock,
- $this->frontendCompilationMock,
- $this->scopeConfigMock
- );
+ $this->preprocessorStrategy = (new ObjectManager($this))->getObject(PreprocessorStrategy::class, [
+ 'alternativeSource' => $this->alternativeSourceMock,
+ 'frontendCompilation' => $this->frontendCompilationMock,
+ 'scopeConfig' => $this->scopeConfigMock,
+ 'state' => $this->stateMock,
+ ]);
}
/**
@@ -70,13 +91,36 @@ public function testProcessClientSideCompilation()
->method('getValue')
->with(WorkflowType::CONFIG_NAME_PATH)
->willReturn(WorkflowType::CLIENT_SIDE_COMPILATION);
-
$this->frontendCompilationMock->expects(self::once())
->method('process')
->with($chainMock);
+ $this->alternativeSourceMock->expects(self::never())
+ ->method('process');
+ $this->stateMock->expects($this->atLeastOnce())
+ ->method('getMode')
+ ->willReturn(State::MODE_DEVELOPER);
+
+ $this->preprocessorStrategy->process($chainMock);
+ }
+
+ public function testProcessClientSideCompilationWithDefaultMode()
+ {
+ $chainMock = $this->getChainMock();
+ $this->scopeConfigMock->expects(self::once())
+ ->method('getValue')
+ ->with(WorkflowType::CONFIG_NAME_PATH)
+ ->willReturn(WorkflowType::CLIENT_SIDE_COMPILATION);
+ $this->frontendCompilationMock->expects(self::once())
+ ->method('process')
+ ->with($chainMock);
$this->alternativeSourceMock->expects(self::never())
->method('process');
+ $this->stateMock->expects($this->once())
+ ->method('getMode')
+ ->willReturn(State::MODE_DEFAULT);
+
+ \Magento\Framework\App\ObjectManager::setInstance($this->objectMangerMock);
$this->preprocessorStrategy->process($chainMock);
}
@@ -88,17 +132,18 @@ public function testProcessAlternativeSource()
{
$chainMock = $this->getChainMock();
- $this->scopeConfigMock->expects(self::once())
+ $this->scopeConfigMock->expects($this->never())
->method('getValue')
->with(WorkflowType::CONFIG_NAME_PATH)
->willReturn('off');
-
$this->alternativeSourceMock->expects(self::once())
->method('process')
->with($chainMock);
-
$this->frontendCompilationMock->expects(self::never())
->method('process');
+ $this->stateMock->expects($this->atLeastOnce())
+ ->method('getMode')
+ ->willReturn(State::MODE_PRODUCTION);
$this->preprocessorStrategy->process($chainMock);
}
diff --git a/app/code/Magento/Developer/Test/Unit/Model/View/Page/Config/RendererFactoryTest.php b/app/code/Magento/Developer/Test/Unit/Model/View/Page/Config/RendererFactoryTest.php
index 06ebf3208b017..94305fca9f9aa 100644
--- a/app/code/Magento/Developer/Test/Unit/Model/View/Page/Config/RendererFactoryTest.php
+++ b/app/code/Magento/Developer/Test/Unit/Model/View/Page/Config/RendererFactoryTest.php
@@ -6,51 +6,82 @@
namespace Magento\Developer\Test\Unit\Model\View\Page\Config;
use Magento\Developer\Model\Config\Source\WorkflowType;
+use Magento\Developer\Model\View\Page\Config\RendererFactory;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\State;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\View\Page\Config\RendererInterface;
use Magento\Store\Model\ScopeInterface;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+/**
+ * Class RendererFactoryTest
+ */
class RendererFactoryTest extends \PHPUnit_Framework_TestCase
{
- const RENDERER_TYPE = 'renderer_type';
+ /**
+ * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $objectManagerMock;
- const RENDERER_INSTANCE_NAME = 'renderer';
+ /**
+ * @var RendererInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $rendererMock;
- public function testCreate()
+ /**
+ * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $configMock;
+
+ /**
+ * @var State|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $stateMock;
+
+ /**
+ * @var RendererFactory
+ */
+ private $model;
+
+ protected function setUp()
{
- // Set up mocks
- $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class)
+ $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)
+ ->getMockForAbstractClass();
+ $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class)
+ ->getMockForAbstractClass();
+ $this->rendererMock = $this->getMockBuilder(RendererInterface::class)
->disableOriginalConstructor()
->getMock();
- $configMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+ $this->stateMock = $this->getMockBuilder(State::class)
->disableOriginalConstructor()
->getMock();
- $rendererMock = $this->getMockBuilder(\Magento\Framework\View\Page\Config\RendererInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $createArgs = [1,2,3];
- $objectManagerMock->expects($this->once())
+ $this->objectManagerMock->expects($this->any())
+ ->method('get')
+ ->with(State::class)
+ ->willReturn($this->stateMock);
+
+ $this->model = (new ObjectManagerHelper($this))->getObject(RendererFactory::class, [
+ 'objectManager' => $this->objectManagerMock,
+ 'scopeConfig' => $this->configMock,
+ 'rendererTypes' => ['renderer_type' => 'renderer'],
+ ]);
+ }
+
+ public function testCreate()
+ {
+ $this->stateMock->expects($this->once())
+ ->method('getMode');
+ $this->objectManagerMock->expects($this->once())
->method('create')
- ->with(self::RENDERER_INSTANCE_NAME, $createArgs)
- ->willReturn($rendererMock);
- $configMock->expects($this->once())
+ ->with('renderer', [])
+ ->willReturn($this->rendererMock);
+ $this->configMock->expects($this->once())
->method('getValue')
->with(WorkflowType::CONFIG_NAME_PATH, ScopeInterface::SCOPE_STORE)
- ->willReturn(self::RENDERER_TYPE);
-
- // Set up System Under Test
- $rendererTypes = [
- self::RENDERER_TYPE => self::RENDERER_INSTANCE_NAME
- ];
- $sutArgs = [
- 'objectManager' => $objectManagerMock,
- 'scopeConfig' => $configMock,
- 'rendererTypes' => $rendererTypes
- ];
-
- $model = $objectManager->getObject(\Magento\Developer\Model\View\Page\Config\RendererFactory::class, $sutArgs);
-
- // Test
- $this->assertSame($rendererMock, $model->create($createArgs));
+ ->willReturn('renderer_type');
+
+ $this->assertSame($this->rendererMock, $this->model->create());
}
}
diff --git a/app/code/Magento/Sales/Api/Data/CommentInterface.php b/app/code/Magento/Sales/Api/Data/CommentInterface.php
new file mode 100644
index 0000000000000..d7021dc9f9546
--- /dev/null
+++ b/app/code/Magento/Sales/Api/Data/CommentInterface.php
@@ -0,0 +1,44 @@
+extensionAttributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\InvoiceCreationArgumentsExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/ItemCreation.php b/app/code/Magento/Sales/Model/Order/Invoice/ItemCreation.php
new file mode 100644
index 0000000000000..abc19c3aaa73d
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/ItemCreation.php
@@ -0,0 +1,57 @@
+orderItemId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOrderItemId($orderItemId)
+ {
+ $this->orderItemId = $orderItemId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQty()
+ {
+ return $this->qty;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setQty($qty)
+ {
+ $this->qty = $qty;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Notifier.php b/app/code/Magento/Sales/Model/Order/Invoice/Notifier.php
new file mode 100644
index 0000000000000..93755b2df176f
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/Notifier.php
@@ -0,0 +1,41 @@
+senders = $senders;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function notify(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\InvoiceInterface $invoice,
+ \Magento\Sales\Api\Data\InvoiceCommentCreationInterface $comment = null,
+ $forceSyncMode = false
+ ) {
+ foreach ($this->senders as $sender) {
+ $sender->send($order, $invoice, $comment, $forceSyncMode);
+ }
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/NotifierInterface.php b/app/code/Magento/Sales/Model/Order/Invoice/NotifierInterface.php
new file mode 100644
index 0000000000000..76f9add8c2df7
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/NotifierInterface.php
@@ -0,0 +1,32 @@
+eventManager = $context->getEventDispatcher();
+ }
+
+ /**
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice
+ * @param bool $capture
+ *
+ * @return \Magento\Sales\Api\Data\OrderInterface
+ */
+ public function execute(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\InvoiceInterface $invoice,
+ $capture
+ ) {
+ $this->calculateOrderItemsTotals(
+ $invoice->getItems()
+ );
+
+ if ($this->canCapture($order, $invoice)) {
+ if ($capture) {
+ $invoice->capture();
+ } else {
+ $invoice->setCanVoidFlag(false);
+
+ $invoice->pay();
+ }
+ } elseif (!$order->getPayment()->getMethodInstance()->isGateway() || !$capture) {
+ if (!$order->getPayment()->getIsTransactionPending()) {
+ $invoice->setCanVoidFlag(false);
+
+ $invoice->pay();
+ }
+ }
+
+ $this->calculateOrderTotals($order, $invoice);
+
+ if (null === $invoice->getState()) {
+ $invoice->setState(\Magento\Sales\Model\Order\Invoice::STATE_OPEN);
+ }
+
+ $this->eventManager->dispatch(
+ 'sales_order_invoice_register',
+ ['invoice' => $invoice, 'order' => $order]
+ );
+
+ return $order;
+ }
+
+ /**
+ * Calculates totals of Order Items according to given Invoice Items.
+ *
+ * @param \Magento\Sales\Api\Data\InvoiceItemInterface[] $items
+ *
+ * @return void
+ */
+ private function calculateOrderItemsTotals($items)
+ {
+ foreach ($items as $item) {
+ if ($item->isDeleted()) {
+ continue;
+ }
+
+ if ($item->getQty() > 0) {
+ $item->register();
+ } else {
+ $item->isDeleted(true);
+ }
+ }
+ }
+
+ /**
+ * Checks Invoice capture action availability.
+ *
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice
+ *
+ * @return bool
+ */
+ private function canCapture(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\InvoiceInterface $invoice
+ ) {
+ return $invoice->getState() != \Magento\Sales\Model\Order\Invoice::STATE_CANCELED &&
+ $invoice->getState() != \Magento\Sales\Model\Order\Invoice::STATE_PAID &&
+ $order->getPayment()->canCapture();
+ }
+
+ /**
+ * Calculates Order totals according to given Invoice.
+ *
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice
+ *
+ * @return void
+ */
+ private function calculateOrderTotals(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\InvoiceInterface $invoice
+ ) {
+ $order->setTotalInvoiced(
+ $order->getTotalInvoiced() + $invoice->getGrandTotal()
+ );
+ $order->setBaseTotalInvoiced(
+ $order->getBaseTotalInvoiced() + $invoice->getBaseGrandTotal()
+ );
+
+ $order->setSubtotalInvoiced(
+ $order->getSubtotalInvoiced() + $invoice->getSubtotal()
+ );
+ $order->setBaseSubtotalInvoiced(
+ $order->getBaseSubtotalInvoiced() + $invoice->getBaseSubtotal()
+ );
+
+ $order->setTaxInvoiced(
+ $order->getTaxInvoiced() + $invoice->getTaxAmount()
+ );
+ $order->setBaseTaxInvoiced(
+ $order->getBaseTaxInvoiced() + $invoice->getBaseTaxAmount()
+ );
+
+ $order->setDiscountTaxCompensationInvoiced(
+ $order->getDiscountTaxCompensationInvoiced() + $invoice->getDiscountTaxCompensationAmount()
+ );
+ $order->setBaseDiscountTaxCompensationInvoiced(
+ $order->getBaseDiscountTaxCompensationInvoiced() + $invoice->getBaseDiscountTaxCompensationAmount()
+ );
+
+ $order->setShippingTaxInvoiced(
+ $order->getShippingTaxInvoiced() + $invoice->getShippingTaxAmount()
+ );
+ $order->setBaseShippingTaxInvoiced(
+ $order->getBaseShippingTaxInvoiced() + $invoice->getBaseShippingTaxAmount()
+ );
+
+ $order->setShippingInvoiced(
+ $order->getShippingInvoiced() + $invoice->getShippingAmount()
+ );
+ $order->setBaseShippingInvoiced(
+ $order->getBaseShippingInvoiced() + $invoice->getBaseShippingAmount()
+ );
+
+ $order->setDiscountInvoiced(
+ $order->getDiscountInvoiced() + $invoice->getDiscountAmount()
+ );
+ $order->setBaseDiscountInvoiced(
+ $order->getBaseDiscountInvoiced() + $invoice->getBaseDiscountAmount()
+ );
+
+ $order->setBaseTotalInvoicedCost(
+ $order->getBaseTotalInvoicedCost() + $invoice->getBaseCost()
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php
new file mode 100644
index 0000000000000..cecdd2702c939
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php
@@ -0,0 +1,149 @@
+paymentHelper = $paymentHelper;
+ $this->invoiceResource = $invoiceResource;
+ $this->globalConfig = $globalConfig;
+ $this->eventManager = $eventManager;
+ }
+
+ /**
+ * Sends order invoice email to the customer.
+ *
+ * Email will be sent immediately in two cases:
+ *
+ * - if asynchronous email sending is disabled in global settings
+ * - if $forceSyncMode parameter is set to TRUE
+ *
+ * Otherwise, email will be sent later during running of
+ * corresponding cron job.
+ *
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice
+ * @param \Magento\Sales\Api\Data\InvoiceCommentCreationInterface|null $comment
+ * @param bool $forceSyncMode
+ *
+ * @return bool
+ */
+ public function send(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\InvoiceInterface $invoice,
+ \Magento\Sales\Api\Data\InvoiceCommentCreationInterface $comment = null,
+ $forceSyncMode = false
+ ) {
+ $invoice->setSendEmail(true);
+
+ if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) {
+ $transport = [
+ 'order' => $order,
+ 'invoice' => $invoice,
+ 'comment' => $comment ? $comment->getComment() : '',
+ 'billing' => $order->getBillingAddress(),
+ 'payment_html' => $this->getPaymentHtml($order),
+ 'store' => $order->getStore(),
+ 'formattedShippingAddress' => $this->getFormattedShippingAddress($order),
+ 'formattedBillingAddress' => $this->getFormattedBillingAddress($order)
+ ];
+
+ $this->eventManager->dispatch(
+ 'email_invoice_set_template_vars_before',
+ ['sender' => $this, 'transport' => $transport]
+ );
+
+ $this->templateContainer->setTemplateVars($transport);
+
+ if ($this->checkAndSend($order)) {
+ $invoice->setEmailSent(true);
+
+ $this->invoiceResource->saveAttribute($invoice, ['send_email', 'email_sent']);
+
+ return true;
+ }
+ } else {
+ $invoice->setEmailSent(null);
+
+ $this->invoiceResource->saveAttribute($invoice, 'email_sent');
+ }
+
+ $this->invoiceResource->saveAttribute($invoice, 'send_email');
+
+ return false;
+ }
+
+ /**
+ * Returns payment info block as HTML.
+ *
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ *
+ * @return string
+ */
+ private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order)
+ {
+ return $this->paymentHelper->getInfoBlockHtml(
+ $order->getPayment(),
+ $this->identityContainer->getStore()->getStoreId()
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/SenderInterface.php b/app/code/Magento/Sales/Model/Order/Invoice/SenderInterface.php
new file mode 100644
index 0000000000000..30f677018eb1a
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/SenderInterface.php
@@ -0,0 +1,29 @@
+invoiceService = $invoiceService;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @param array $items
+ * @param InvoiceCommentCreationInterface|null $comment
+ * @param bool|false $appendComment
+ * @param InvoiceCreationArgumentsInterface|null $arguments
+ * @return InvoiceInterface
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function create(
+ OrderInterface $order,
+ $items = [],
+ InvoiceCommentCreationInterface $comment = null,
+ $appendComment = false,
+ InvoiceCreationArgumentsInterface $arguments = null
+ ) {
+
+ $invoiceItems = $this->itemsToArray($items);
+ $invoice = $this->invoiceService->prepareInvoice($order, $invoiceItems);
+
+ if ($comment) {
+ $invoice->addComment(
+ $comment->getComment(),
+ $appendComment,
+ $comment->getIsVisibleOnFront()
+ );
+ }
+
+ return $invoice;
+ }
+
+ /**
+ * Convert Items To Array
+ *
+ * @param InvoiceItemCreationInterface[] $items
+ * @return array
+ */
+ private function itemsToArray($items = [])
+ {
+ $invoiceItems = [];
+ foreach ($items as $item) {
+ $invoiceItems[$item->getOrderItemId()] = $item->getQty();
+ }
+ return $invoiceItems;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/InvoiceNotifierInterface.php b/app/code/Magento/Sales/Model/Order/InvoiceNotifierInterface.php
new file mode 100644
index 0000000000000..f055bbd4f8cfd
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/InvoiceNotifierInterface.php
@@ -0,0 +1,31 @@
+orderValidator = $orderValidator;
+ }
+
+ /**
+ * @param InvoiceInterface $invoice
+ * @param OrderInterface $order
+ * @return array
+ */
+ public function validate(InvoiceInterface $invoice, OrderInterface $order)
+ {
+ $messages = $this->checkQtyAvailability($invoice, $order);
+
+ if (!$this->orderValidator->canInvoice($order)) {
+ $messages[] = __(
+ 'An invoice cannot be created when an order has a status of %1.',
+ $order->getStatus()
+ );
+ }
+ return $messages;
+ }
+
+ /**
+ * Check qty availability
+ *
+ * @param InvoiceInterface $invoice
+ * @param OrderInterface $order
+ * @return array
+ */
+ private function checkQtyAvailability(InvoiceInterface $invoice, OrderInterface $order)
+ {
+ $messages = [];
+ $qtys = $this->getInvoiceQty($invoice);
+
+ $totalQty = 0;
+ if ($qtys) {
+ /** @var \Magento\Sales\Model\Order\Item $orderItem */
+ foreach ($order->getItems() as $orderItem) {
+ if (isset($qtys[$orderItem->getId()])) {
+ if ($qtys[$orderItem->getId()] > $orderItem->getQtyToInvoice() && !$orderItem->isDummy()) {
+ $messages[] = __(
+ 'The quantity to invoice must not be greater than the uninvoiced quantity'
+ . ' for product SKU "%1".',
+ $orderItem->getSku()
+ );
+ }
+ $totalQty += $qtys[$orderItem->getId()];
+ unset($qtys[$orderItem->getId()]);
+ }
+ }
+ }
+ if ($qtys) {
+ $messages[] = __('The invoice contains one or more items that are not part of the original order.');
+ } elseif ($totalQty <= 0) {
+ $messages[] = __('You can\'t create an invoice without products.');
+ }
+ return $messages;
+ }
+
+ /**
+ * @param InvoiceInterface $invoice
+ * @return array
+ */
+ private function getInvoiceQty(InvoiceInterface $invoice)
+ {
+ $qtys = [];
+ /** @var InvoiceItemInterface $item */
+ foreach ($invoice->getItems() as $item) {
+ $qtys[$item->getOrderItemId()] = $item->getQty();
+ }
+ return $qtys;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/InvoiceValidatorInterface.php b/app/code/Magento/Sales/Model/Order/InvoiceValidatorInterface.php
new file mode 100644
index 0000000000000..64b2f98dfe37e
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/InvoiceValidatorInterface.php
@@ -0,0 +1,25 @@
+getState() === Order::STATE_PAYMENT_REVIEW ||
+ $order->getState() === Order::STATE_HOLDED ||
+ $order->getState() === Order::STATE_CANCELED ||
+ $order->getState() === Order::STATE_COMPLETE ||
+ $order->getState() === Order::STATE_CLOSED
+ ) {
+ return false;
+ };
+ /** @var \Magento\Sales\Model\Order\Item $item */
+ foreach ($order->getItems() as $item) {
+ if ($item->getQtyToInvoice() > 0 && !$item->getLockedDoInvoice()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php b/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php
new file mode 100644
index 0000000000000..d0dcc38af642a
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php
@@ -0,0 +1,23 @@
+payOperation = $payOperation;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function pay(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\InvoiceInterface $invoice,
+ $capture
+ ) {
+ return $this->payOperation->execute($order, $invoice, $capture);
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php b/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php
new file mode 100644
index 0000000000000..0e4b193169da8
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/PaymentAdapterInterface.php
@@ -0,0 +1,26 @@
+getBaseGrandTotal() || $order->canCreditmemo()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if order should be in closed state
+ *
+ * @param OrderInterface $order
+ * @param array $arguments
+ * @return bool
+ */
+ private function isOrderClosed(OrderInterface $order, $arguments)
+ {
+ /** @var $order Order|OrderInterface */
+ $forceCreditmemo = in_array(self::FORCED_CREDITMEMO, $arguments);
+ if (floatval($order->getTotalRefunded()) || !$order->getTotalRefunded() && $forceCreditmemo) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if order is processing
+ *
+ * @param OrderInterface $order
+ * @param array $arguments
+ * @return bool
+ */
+ private function isOrderProcessing(OrderInterface $order, $arguments)
+ {
+ /** @var $order Order|OrderInterface */
+ if ($order->getState() == Order::STATE_NEW && in_array(self::IN_PROGRESS, $arguments)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns initial state for order
+ *
+ * @param OrderInterface $order
+ * @return string
+ */
+ private function getInitialOrderState(OrderInterface $order)
+ {
+ return $order->getState() === Order::STATE_PROCESSING ? Order::STATE_PROCESSING : Order::STATE_NEW;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @param array $arguments
+ * @return string
+ */
+ public function getStateForOrder(OrderInterface $order, array $arguments = [])
+ {
+ /** @var $order Order|OrderInterface */
+ $orderState = $this->getInitialOrderState($order);
+ if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice() && !$order->canShip()) {
+ if ($this->isOrderComplete($order)) {
+ $orderState = Order::STATE_COMPLETE;
+ } elseif ($this->isOrderClosed($order, $arguments)) {
+ $orderState = Order::STATE_CLOSED;
+ }
+ }
+ if ($this->isOrderProcessing($order, $arguments)) {
+ $orderState = Order::STATE_PROCESSING;
+ }
+ return $orderState;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/OrderInvoice.php b/app/code/Magento/Sales/Model/OrderInvoice.php
new file mode 100644
index 0000000000000..4a343e559d877
--- /dev/null
+++ b/app/code/Magento/Sales/Model/OrderInvoice.php
@@ -0,0 +1,182 @@
+resourceConnection = $resourceConnection;
+ $this->orderRepository = $orderRepository;
+ $this->invoiceDocumentFactory = $invoiceDocumentFactory;
+ $this->invoiceValidator = $invoiceValidator;
+ $this->paymentAdapter = $paymentAdapter;
+ $this->orderStateResolver = $orderStateResolver;
+ $this->config = $config;
+ $this->invoiceRepository = $invoiceRepository;
+ $this->notifierInterface = $notifierInterface;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @param int $orderId
+ * @param bool $capture
+ * @param array $items
+ * @param bool $notify
+ * @param bool $appendComment
+ * @param \Magento\Sales\Api\Data\InvoiceCommentCreationInterface|null $comment
+ * @param \Magento\Sales\Api\Data\InvoiceCreationArgumentsInterface|null $arguments
+ * @return int
+ * @throws \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
+ * @throws \Magento\Sales\Api\Exception\CouldNotInvoiceExceptionInterface
+ * @throws \Magento\Framework\Exception\InputException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @throws \DomainException
+ */
+ public function execute(
+ $orderId,
+ $capture = false,
+ array $items = [],
+ $notify = false,
+ $appendComment = false,
+ InvoiceCommentCreationInterface $comment = null,
+ InvoiceCreationArgumentsInterface $arguments = null
+ ) {
+ $connection = $this->resourceConnection->getConnection('sales');
+ $order = $this->orderRepository->get($orderId);
+ $invoice = $this->invoiceDocumentFactory->create(
+ $order,
+ $items,
+ $comment,
+ ($appendComment && $notify),
+ $arguments
+ );
+ $errorMessages = $this->invoiceValidator->validate($invoice, $order);
+ if (!empty($errorMessages)) {
+ throw new \Magento\Sales\Exception\DocumentValidationException(
+ __("Invoice Document Validation Error(s):\n" . implode("\n", $errorMessages))
+ );
+ }
+ $connection->beginTransaction();
+ try {
+ $order = $this->paymentAdapter->pay($order, $invoice, $capture);
+ $order->setState(
+ $this->orderStateResolver->getStateForOrder($order, [OrderStateResolverInterface::IN_PROGRESS])
+ );
+ $order->setStatus($this->config->getStateDefaultStatus($order->getState()));
+ $invoice->setState(\Magento\Sales\Model\Order\Invoice::STATE_PAID);
+ $this->invoiceRepository->save($invoice);
+ $this->orderRepository->save($order);
+ $connection->commit();
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ $connection->rollBack();
+ throw new \Magento\Sales\Exception\CouldNotInvoiceException(
+ __('Could not save an invoice, see error log for details')
+ );
+ }
+ if ($notify) {
+ if (!$appendComment) {
+ $comment = null;
+ }
+ $this->notifierInterface->notify($order, $invoice, $comment);
+ }
+ return $invoice->getEntityId();
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php
index 5d1f70310cdf7..ac66d4dc32f4c 100644
--- a/app/code/Magento/Sales/Model/Service/InvoiceService.php
+++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php
@@ -143,8 +143,10 @@ public function prepareInvoice(Order $order, array $qtys = [])
$qty = $orderItem->getQtyOrdered() ? $orderItem->getQtyOrdered() : 1;
} elseif (isset($qtys[$orderItem->getId()])) {
$qty = (double) $qtys[$orderItem->getId()];
- } else {
+ } elseif (empty($qtys)) {
$qty = $orderItem->getQtyToInvoice();
+ } else {
+ $qty = 0;
}
$totalQty += $qty;
$this->setInvoiceItemQuantity($item, $qty);
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/PayOperationTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/PayOperationTest.php
new file mode 100644
index 0000000000000..b97f2955f2f15
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/PayOperationTest.php
@@ -0,0 +1,444 @@
+orderMock = $this->getMockForAbstractClass(
+ \Magento\Sales\Api\Data\OrderInterface::class,
+ [],
+ '',
+ false,
+ false,
+ true,
+ [
+ 'getPayment',
+ 'setTotalInvoiced',
+ 'getTotalInvoiced',
+ 'setBaseTotalInvoiced',
+ 'getBaseTotalInvoiced',
+ 'setSubtotalInvoiced',
+ 'getSubtotalInvoiced',
+ 'setBaseSubtotalInvoiced',
+ 'getBaseSubtotalInvoiced',
+ 'setTaxInvoiced',
+ 'getTaxInvoiced',
+ 'setBaseTaxInvoiced',
+ 'getBaseTaxInvoiced',
+ 'setDiscountTaxCompensationInvoiced',
+ 'getDiscountTaxCompensationInvoiced',
+ 'setBaseDiscountTaxCompensationInvoiced',
+ 'getBaseDiscountTaxCompensationInvoiced',
+ 'setShippingTaxInvoiced',
+ 'getShippingTaxInvoiced',
+ 'setBaseShippingTaxInvoiced',
+ 'getBaseShippingTaxInvoiced',
+ 'setShippingInvoiced',
+ 'getShippingInvoiced',
+ 'setBaseShippingInvoiced',
+ 'getBaseShippingInvoiced',
+ 'setDiscountInvoiced',
+ 'getDiscountInvoiced',
+ 'setBaseDiscountInvoiced',
+ 'getBaseDiscountInvoiced',
+ 'setBaseTotalInvoicedCost',
+ 'getBaseTotalInvoicedCost'
+ ]
+ );
+ $this->orderMock->expects($this->any())
+ ->method('getTotalInvoiced')
+ ->willReturn(43);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseTotalInvoiced')
+ ->willReturn(43);
+ $this->orderMock->expects($this->any())
+ ->method('getSubtotalInvoiced')
+ ->willReturn(22);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseSubtotalInvoiced')
+ ->willReturn(22);
+ $this->orderMock->expects($this->any())
+ ->method('getTaxInvoiced')
+ ->willReturn(15);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseTaxInvoiced')
+ ->willReturn(15);
+ $this->orderMock->expects($this->any())
+ ->method('getDiscountTaxCompensationInvoiced')
+ ->willReturn(11);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseDiscountTaxCompensationInvoiced')
+ ->willReturn(11);
+ $this->orderMock->expects($this->any())
+ ->method('getShippingTaxInvoiced')
+ ->willReturn(12);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseShippingTaxInvoiced')
+ ->willReturn(12);
+ $this->orderMock->expects($this->any())
+ ->method('getShippingInvoiced')
+ ->willReturn(28);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseShippingInvoiced')
+ ->willReturn(28);
+ $this->orderMock->expects($this->any())
+ ->method('getDiscountInvoiced')
+ ->willReturn(19);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseDiscountInvoiced')
+ ->willReturn(19);
+ $this->orderMock->expects($this->any())
+ ->method('getBaseTotalInvoicedCost')
+ ->willReturn(31);
+
+ $this->invoiceMock = $this->getMockForAbstractClass(
+ \Magento\Sales\Api\Data\InvoiceInterface::class,
+ [],
+ '',
+ false,
+ false,
+ true,
+ [
+ 'getItems',
+ 'getState',
+ 'capture',
+ 'setCanVoidFlag',
+ 'pay',
+ 'getGrandTotal',
+ 'getBaseGrandTotal',
+ 'getSubtotal',
+ 'getBaseSubtotal',
+ 'getTaxAmount',
+ 'getBaseTaxAmount',
+ 'getDiscountTaxCompensationAmount',
+ 'getBaseDiscountTaxCompensationAmount',
+ 'getShippingTaxAmount',
+ 'getBaseShippingTaxAmount',
+ 'getShippingAmount',
+ 'getBaseShippingAmount',
+ 'getDiscountAmount',
+ 'getBaseDiscountAmount',
+ 'getBaseCost'
+ ]
+ );
+ $this->invoiceMock->expects($this->any())
+ ->method('getGrandTotal')
+ ->willReturn(43);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseGrandTotal')
+ ->willReturn(43);
+ $this->invoiceMock->expects($this->any())
+ ->method('getSubtotal')
+ ->willReturn(22);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseSubtotal')
+ ->willReturn(22);
+ $this->invoiceMock->expects($this->any())
+ ->method('getTaxAmount')
+ ->willReturn(15);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseTaxAmount')
+ ->willReturn(15);
+ $this->invoiceMock->expects($this->any())
+ ->method('getDiscountTaxCompensationAmount')
+ ->willReturn(11);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseDiscountTaxCompensationAmount')
+ ->willReturn(11);
+ $this->invoiceMock->expects($this->any())
+ ->method('getShippingTaxAmount')
+ ->willReturn(12);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseShippingTaxAmount')
+ ->willReturn(12);
+ $this->invoiceMock->expects($this->any())
+ ->method('getShippingAmount')
+ ->willReturn(28);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseShippingAmount')
+ ->willReturn(28);
+ $this->invoiceMock->expects($this->any())
+ ->method('getDiscountAmount')
+ ->willReturn(19);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseDiscountAmount')
+ ->willReturn(19);
+ $this->invoiceMock->expects($this->any())
+ ->method('getBaseCost')
+ ->willReturn(31);
+
+ $this->contextMock = $this->getMock(
+ \Magento\Framework\Model\Context::class,
+ [],
+ [],
+ '',
+ false
+ );
+
+ $this->invoiceItemMock = $this->getMockForAbstractClass(
+ \Magento\Sales\Api\Data\InvoiceItemInterface::class,
+ [],
+ '',
+ false,
+ false,
+ true,
+ [
+ 'isDeleted',
+ 'register'
+ ]
+ );
+ $this->invoiceItemMock->expects($this->any())
+ ->method('isDeleted')
+ ->willReturn(false);
+ $this->invoiceItemMock->expects($this->any())
+ ->method('getQty')
+ ->willReturn(1);
+
+ $this->orderPaymentMock = $this->getMockForAbstractClass(
+ \Magento\Sales\Api\Data\OrderPaymentInterface::class,
+ [],
+ '',
+ false,
+ false,
+ true,
+ [
+ 'canCapture',
+ 'getMethodInstance',
+ 'getIsTransactionPending'
+ ]
+ );
+ $this->orderMock->expects($this->any())
+ ->method('getPayment')
+ ->willReturn($this->orderPaymentMock);
+
+ $this->eventManagerMock = $this->getMockForAbstractClass(
+ \Magento\Framework\Event\ManagerInterface::class,
+ [],
+ '',
+ false,
+ false,
+ true,
+ []
+ );
+ $this->contextMock->expects($this->any())
+ ->method('getEventDispatcher')
+ ->willReturn($this->eventManagerMock);
+
+ $this->paymentMethodMock = $this->getMockForAbstractClass(
+ \Magento\Payment\Model\MethodInterface::class,
+ [],
+ '',
+ false,
+ false,
+ true,
+ []
+ );
+ $this->orderPaymentMock->expects($this->any())
+ ->method('getMethodInstance')
+ ->willReturn($this->paymentMethodMock);
+
+ $this->subject = new \Magento\Sales\Model\Order\Invoice\PayOperation(
+ $this->contextMock
+ );
+ }
+
+ /**
+ * @param bool|null $canCapture
+ * @param bool|null $isOnline
+ * @param bool|null $isGateway
+ * @param bool|null $isTransactionPending
+ *
+ * @dataProvider payDataProvider
+ *
+ * @return void
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testExecute($canCapture, $isOnline, $isGateway, $isTransactionPending)
+ {
+ $this->invoiceMock->expects($this->any())
+ ->method('getItems')
+ ->willReturn([$this->invoiceItemMock]);
+
+ if ($canCapture) {
+ $this->invoiceMock->expects($this->any())
+ ->method('getState')
+ ->willReturn(\Magento\Sales\Model\Order\Invoice::STATE_OPEN);
+
+ $this->orderPaymentMock->expects($this->any())
+ ->method('canCapture')
+ ->willReturn(true);
+
+ if ($isOnline) {
+ $this->invoiceMock->expects($this->once())
+ ->method('capture');
+ } else {
+ $this->invoiceMock->expects($this->never())
+ ->method('capture');
+
+ $this->invoiceMock->expects($this->once())
+ ->method('setCanVoidFlag')
+ ->with(false);
+
+ $this->invoiceMock->expects($this->once())
+ ->method('pay');
+ }
+ } else {
+ $this->paymentMethodMock->expects($this->any())
+ ->method('isGateway')
+ ->willReturn($isGateway);
+
+ $this->orderPaymentMock->expects($this->any())
+ ->method('getIsTransactionPending')
+ ->willReturn($isTransactionPending);
+
+ $this->invoiceMock->expects($this->never())
+ ->method('capture');
+
+ if ((!$isGateway || !$isOnline) && !$isTransactionPending) {
+ $this->invoiceMock->expects($this->once())
+ ->method('setCanVoidFlag')
+ ->with(false);
+
+ $this->invoiceMock->expects($this->once())
+ ->method('pay');
+ }
+ }
+
+ $this->orderMock->expects($this->once())
+ ->method('setTotalInvoiced')
+ ->with(86);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseTotalInvoiced')
+ ->with(86);
+ $this->orderMock->expects($this->once())
+ ->method('setSubtotalInvoiced')
+ ->with(44);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseSubtotalInvoiced')
+ ->with(44);
+ $this->orderMock->expects($this->once())
+ ->method('setTaxInvoiced')
+ ->with(30);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseTaxInvoiced')
+ ->with(30);
+ $this->orderMock->expects($this->once())
+ ->method('setDiscountTaxCompensationInvoiced')
+ ->with(22);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseDiscountTaxCompensationInvoiced')
+ ->with(22);
+ $this->orderMock->expects($this->once())
+ ->method('setShippingTaxInvoiced')
+ ->with(24);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseShippingTaxInvoiced')
+ ->with(24);
+ $this->orderMock->expects($this->once())
+ ->method('setShippingInvoiced')
+ ->with(56);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseShippingInvoiced')
+ ->with(56);
+ $this->orderMock->expects($this->once())
+ ->method('setDiscountInvoiced')
+ ->with(38);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseDiscountInvoiced')
+ ->with(38);
+ $this->orderMock->expects($this->once())
+ ->method('setBaseTotalInvoicedCost')
+ ->with(62);
+
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with(
+ 'sales_order_invoice_register',
+ [
+ 'invoice' => $this->invoiceMock,
+ 'order' => $this->orderMock
+ ]
+ );
+
+ $this->assertEquals(
+ $this->orderMock,
+ $this->subject->execute(
+ $this->orderMock,
+ $this->invoiceMock,
+ $isOnline
+ )
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function payDataProvider()
+ {
+ return [
+ 'Invoice can capture, online' => [
+ true, true, null, null
+ ],
+ 'Invoice can capture, offline' => [
+ true, false, null, null
+ ],
+ 'Invoice can not capture, online, is not gateway, transaction is not pending' => [
+ false, true, false, false
+ ],
+ 'Invoice can not capture, offline, gateway, transaction is not pending' => [
+ false, false, true, false
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php
new file mode 100644
index 0000000000000..0c72e8265b467
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php
@@ -0,0 +1,359 @@
+orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
+ ->setMethods(['getStoreId'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->storeMock->expects($this->any())
+ ->method('getStoreId')
+ ->willReturn(1);
+ $this->orderMock->expects($this->any())
+ ->method('getStore')
+ ->willReturn($this->storeMock);
+
+ $this->senderMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Sender::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['send', 'sendCopyTo'])
+ ->getMock();
+
+ $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Invoice::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setSendEmail', 'setEmailSent'])
+ ->getMock();
+
+ $this->commentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceCommentCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->commentMock->expects($this->any())
+ ->method('getComment')
+ ->willReturn('Comment text');
+
+ $this->addressMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock->expects($this->any())
+ ->method('getBillingAddress')
+ ->willReturn($this->addressMock);
+ $this->orderMock->expects($this->any())
+ ->method('getShippingAddress')
+ ->willReturn($this->addressMock);
+
+ $this->globalConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->paymentInfoMock = $this->getMockBuilder(\Magento\Payment\Model\Info::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock->expects($this->any())
+ ->method('getPayment')
+ ->willReturn($this->paymentInfoMock);
+
+ $this->paymentHelperMock = $this->getMockBuilder(\Magento\Payment\Helper\Data::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->paymentHelperMock->expects($this->any())
+ ->method('getInfoBlockHtml')
+ ->with($this->paymentInfoMock, 1)
+ ->willReturn('Payment Info Block');
+
+ $this->invoiceResourceMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Invoice::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->addressRendererMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address\Renderer::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->addressRendererMock->expects($this->any())
+ ->method('format')
+ ->with($this->addressMock, 'html')
+ ->willReturn('Formatted address');
+
+ $this->templateContainerMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Container\Template::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->identityContainerMock = $this->getMockBuilder(
+ \Magento\Sales\Model\Order\Email\Container\InvoiceIdentity::class
+ )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->identityContainerMock->expects($this->any())
+ ->method('getStore')
+ ->willReturn($this->storeMock);
+
+ $this->senderBuilderFactoryMock = $this->getMockBuilder(
+ \Magento\Sales\Model\Order\Email\SenderBuilderFactory::class
+ )
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+
+ $this->subject = new \Magento\Sales\Model\Order\Invoice\Sender\EmailSender(
+ $this->templateContainerMock,
+ $this->identityContainerMock,
+ $this->senderBuilderFactoryMock,
+ $this->loggerMock,
+ $this->addressRendererMock,
+ $this->paymentHelperMock,
+ $this->invoiceResourceMock,
+ $this->globalConfigMock,
+ $this->eventManagerMock
+ );
+ }
+
+ /**
+ * @param int $configValue
+ * @param bool $forceSyncMode
+ * @param bool $isComment
+ * @param bool $emailSendingResult
+ *
+ * @dataProvider sendDataProvider
+ *
+ * @return void
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testSend($configValue, $forceSyncMode, $isComment, $emailSendingResult)
+ {
+ $this->globalConfigMock->expects($this->once())
+ ->method('getValue')
+ ->with('sales_email/general/async_sending')
+ ->willReturn($configValue);
+
+ if (!$isComment) {
+ $this->commentMock = null;
+ }
+
+ $this->invoiceMock->expects($this->once())
+ ->method('setSendEmail')
+ ->with(true);
+
+ if (!$configValue || $forceSyncMode) {
+ $transport = [
+ 'order' => $this->orderMock,
+ 'invoice' => $this->invoiceMock,
+ 'comment' => $isComment ? 'Comment text' : '',
+ 'billing' => $this->addressMock,
+ 'payment_html' => 'Payment Info Block',
+ 'store' => $this->storeMock,
+ 'formattedShippingAddress' => 'Formatted address',
+ 'formattedBillingAddress' => 'Formatted address'
+ ];
+
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with(
+ 'email_invoice_set_template_vars_before',
+ [
+ 'sender' => $this->subject,
+ 'transport' => $transport
+ ]
+ );
+
+ $this->templateContainerMock->expects($this->once())
+ ->method('setTemplateVars')
+ ->with($transport);
+
+ $this->identityContainerMock->expects($this->once())
+ ->method('isEnabled')
+ ->willReturn($emailSendingResult);
+
+ if ($emailSendingResult) {
+ $this->senderBuilderFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->senderMock);
+
+ $this->senderMock->expects($this->once())
+ ->method('send');
+
+ $this->senderMock->expects($this->once())
+ ->method('sendCopyTo');
+
+ $this->invoiceMock->expects($this->once())
+ ->method('setEmailSent')
+ ->with(true);
+
+ $this->invoiceResourceMock->expects($this->once())
+ ->method('saveAttribute')
+ ->with($this->invoiceMock, ['send_email', 'email_sent']);
+
+ $this->assertTrue(
+ $this->subject->send(
+ $this->orderMock,
+ $this->invoiceMock,
+ $this->commentMock,
+ $forceSyncMode
+ )
+ );
+ } else {
+ $this->invoiceResourceMock->expects($this->once())
+ ->method('saveAttribute')
+ ->with($this->invoiceMock, 'send_email');
+
+ $this->assertFalse(
+ $this->subject->send(
+ $this->orderMock,
+ $this->invoiceMock,
+ $this->commentMock,
+ $forceSyncMode
+ )
+ );
+ }
+ } else {
+ $this->invoiceMock->expects($this->once())
+ ->method('setEmailSent')
+ ->with(null);
+
+ $this->invoiceResourceMock->expects($this->at(0))
+ ->method('saveAttribute')
+ ->with($this->invoiceMock, 'email_sent');
+ $this->invoiceResourceMock->expects($this->at(1))
+ ->method('saveAttribute')
+ ->with($this->invoiceMock, 'send_email');
+
+ $this->assertFalse(
+ $this->subject->send(
+ $this->orderMock,
+ $this->invoiceMock,
+ $this->commentMock,
+ $forceSyncMode
+ )
+ );
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function sendDataProvider()
+ {
+ return [
+ 'Successful sync sending with comment' => [0, false, true, true],
+ 'Successful sync sending without comment' => [0, false, false, true],
+ 'Failed sync sending with comment' => [0, false, true, false],
+ 'Successful forced sync sending with comment' => [1, true, true, true],
+ 'Async sending' => [1, false, false, false]
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceDocumentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceDocumentFactoryTest.php
new file mode 100644
index 0000000000000..4247dc9567304
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceDocumentFactoryTest.php
@@ -0,0 +1,114 @@
+invoiceServiceMock = $this->getMockBuilder(InvoiceService::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceMock = $this->getMockBuilder(InvoiceInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['addComment'])
+ ->getMockForAbstractClass();
+
+ $this->itemMock = $this->getMockBuilder(InvoiceItemCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->commentMock = $this->getMockBuilder(InvoiceCommentCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceDocumentFactory = new InvoiceDocumentFactory($this->invoiceServiceMock);
+ }
+
+ public function testCreate()
+ {
+ $orderId = 10;
+ $orderQty = 3;
+ $comment = "Comment!";
+
+ $this->itemMock->expects($this->once())
+ ->method('getOrderItemId')
+ ->willReturn($orderId);
+
+ $this->itemMock->expects($this->once())
+ ->method('getQty')
+ ->willReturn($orderQty);
+
+ $this->invoiceMock->expects($this->once())
+ ->method('addComment')
+ ->with($comment, null, null)
+ ->willReturnSelf();
+
+ $this->invoiceServiceMock->expects($this->once())
+ ->method('prepareInvoice')
+ ->with($this->orderMock, [$orderId => $orderQty])
+ ->willReturn($this->invoiceMock);
+
+ $this->commentMock->expects($this->once())
+ ->method('getComment')
+ ->willReturn($comment);
+
+ $this->commentMock->expects($this->once())
+ ->method('getIsVisibleOnFront')
+ ->willReturn(false);
+
+ $this->assertEquals(
+ $this->invoiceMock,
+ $this->invoiceDocumentFactory->create($this->orderMock, [$this->itemMock], $this->commentMock)
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceValidatorTest.php
new file mode 100644
index 0000000000000..4acbf55b1964c
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceValidatorTest.php
@@ -0,0 +1,205 @@
+objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->orderValidatorMock = $this->getMockBuilder(\Magento\Sales\Model\Order\OrderValidatorInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['canInvoice'])
+ ->getMockForAbstractClass();
+
+ $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getStatus'])
+ ->getMockForAbstractClass();
+
+ $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getTotalQty', 'getItems'])
+ ->getMockForAbstractClass();
+
+ $this->model = $this->objectManager->getObject(
+ \Magento\Sales\Model\Order\InvoiceValidator::class,
+ ['orderValidator' => $this->orderValidatorMock]
+ );
+ }
+
+ public function testValidate()
+ {
+ $expectedResult = [];
+ $invoiceItemMock = $this->getInvoiceItemMock(1, 1);
+ $this->invoiceMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$invoiceItemMock]);
+
+ $orderItemMock = $this->getOrderItemMock(1, 1, true);
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$orderItemMock]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('canInvoice')
+ ->with($this->orderMock)
+ ->willReturn(true);
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->invoiceMock, $this->orderMock)
+ );
+ }
+
+ public function testValidateCanNotInvoiceOrder()
+ {
+ $orderStatus = 'Test Status';
+ $expectedResult = [__('An invoice cannot be created when an order has a status of %1.', $orderStatus)];
+ $invoiceItemMock = $this->getInvoiceItemMock(1, 1);
+ $this->invoiceMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$invoiceItemMock]);
+
+ $orderItemMock = $this->getOrderItemMock(1, 1, true);
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$orderItemMock]);
+ $this->orderMock->expects($this->once())
+ ->method('getStatus')
+ ->willReturn($orderStatus);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('canInvoice')
+ ->with($this->orderMock)
+ ->willReturn(false);
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->invoiceMock, $this->orderMock)
+ );
+ }
+
+ public function testValidateInvoiceQtyBiggerThanOrder()
+ {
+ $orderItemId = 1;
+ $message = 'The quantity to invoice must not be greater than the uninvoiced quantity for product SKU "%1".';
+ $expectedResult = [__($message, $orderItemId)];
+ $invoiceItemMock = $this->getInvoiceItemMock($orderItemId, 2);
+ $this->invoiceMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$invoiceItemMock]);
+
+ $orderItemMock = $this->getOrderItemMock($orderItemId, 1, false);
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$orderItemMock]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('canInvoice')
+ ->with($this->orderMock)
+ ->willReturn(true);
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->invoiceMock, $this->orderMock)
+ );
+ }
+
+ public function testValidateNoOrderItems()
+ {
+ $expectedResult = [__('The invoice contains one or more items that are not part of the original order.')];
+ $invoiceItemMock = $this->getInvoiceItemMock(1, 1);
+ $this->invoiceMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$invoiceItemMock]);
+
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('canInvoice')
+ ->with($this->orderMock)
+ ->willReturn(true);
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->invoiceMock, $this->orderMock)
+ );
+ }
+
+ public function testValidateNoInvoiceItems()
+ {
+ $expectedResult = [__('You can\'t create an invoice without products.')];
+ $orderItemId = 1;
+ $invoiceItemMock = $this->getInvoiceItemMock($orderItemId, 0);
+ $this->invoiceMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$invoiceItemMock]);
+
+ $orderItemMock = $this->getOrderItemMock($orderItemId, 1, false);
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([$orderItemMock]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('canInvoice')
+ ->with($this->orderMock)
+ ->willReturn(true);
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->invoiceMock, $this->orderMock)
+ );
+ }
+
+ private function getInvoiceItemMock($orderItemId, $qty)
+ {
+ $invoiceItemMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceItemInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getOrderItemId', 'getQty'])
+ ->getMockForAbstractClass();
+ $invoiceItemMock->expects($this->once())->method('getOrderItemId')->willReturn($orderItemId);
+ $invoiceItemMock->expects($this->once())->method('getQty')->willReturn($qty);
+ return $invoiceItemMock;
+ }
+
+ private function getOrderItemMock($id, $qtyToInvoice, $isDummy)
+ {
+ $orderItemMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderItemInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getQtyToInvoice', 'isDummy', 'getSku'])
+ ->getMockForAbstractClass();
+ $orderItemMock->expects($this->any())->method('getId')->willReturn($id);
+ $orderItemMock->expects($this->any())->method('getQtyToInvoice')->willReturn($qtyToInvoice);
+ $orderItemMock->expects($this->any())->method('isDummy')->willReturn($isDummy);
+ $orderItemMock->expects($this->any())->method('getSku')->willReturn($id);
+ return $orderItemMock;
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/OrderValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/OrderValidatorTest.php
new file mode 100644
index 0000000000000..5fb0d0a644eb7
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/OrderValidatorTest.php
@@ -0,0 +1,146 @@
+objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getStatus', 'getItems'])
+ ->getMockForAbstractClass();
+
+ $this->orderItemMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderItemInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getQtyToInvoice', 'getLockedDoInvoice'])
+ ->getMockForAbstractClass();
+
+ $this->model = new \Magento\Sales\Model\Order\OrderValidator();
+ }
+
+ /**
+ * @param string $state
+ *
+ * @dataProvider canInvoiceWrongStateDataProvider
+ */
+ public function testCanInvoiceWrongState($state)
+ {
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn($state);
+ $this->orderMock->expects($this->never())
+ ->method('getItems');
+ $this->assertEquals(
+ false,
+ $this->model->canInvoice($this->orderMock)
+ );
+ }
+
+ /**
+ * Data provider for testCanInvoiceWrongState
+ * @return array
+ */
+ public function canInvoiceWrongStateDataProvider()
+ {
+ return [
+ [Order::STATE_PAYMENT_REVIEW],
+ [Order::STATE_HOLDED],
+ [Order::STATE_CANCELED],
+ [Order::STATE_COMPLETE],
+ [Order::STATE_CLOSED],
+ ];
+ }
+
+ public function testCanInvoiceNoItems()
+ {
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([]);
+
+ $this->assertEquals(
+ false,
+ $this->model->canInvoice($this->orderMock)
+ );
+ }
+
+ /**
+ * @param float $qtyToInvoice
+ * @param bool|null $itemLockedDoInvoice
+ * @param bool $expectedResult
+ *
+ * @dataProvider canInvoiceDataProvider
+ */
+ public function testCanInvoice($qtyToInvoice, $itemLockedDoInvoice, $expectedResult)
+ {
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $items = [$this->orderItemMock];
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn($items);
+ $this->orderItemMock->expects($this->any())
+ ->method('getQtyToInvoice')
+ ->willReturn($qtyToInvoice);
+ $this->orderItemMock->expects($this->any())
+ ->method('getLockedDoInvoice')
+ ->willReturn($itemLockedDoInvoice);
+
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->canInvoice($this->orderMock)
+ );
+ }
+
+ /**
+ * Data provider for testCanInvoice
+ *
+ * @return array
+ */
+ public function canInvoiceDataProvider()
+ {
+ return [
+ [0, null, false],
+ [-1, null, false],
+ [1, true, false],
+ [0.5, false, true],
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php
new file mode 100644
index 0000000000000..2da2f4bba3f1a
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentAdapterTest.php
@@ -0,0 +1,70 @@
+orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->payOperationMock =$this->getMockBuilder(\Magento\Sales\Model\Order\Invoice\PayOperation::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->subject = new \Magento\Sales\Model\Order\PaymentAdapter(
+ $this->payOperationMock
+ );
+ }
+
+ public function testPay()
+ {
+ $isOnline = true;
+
+ $this->payOperationMock->expects($this->once())
+ ->method('execute')
+ ->with($this->orderMock, $this->invoiceMock, $isOnline)
+ ->willReturn($this->orderMock);
+
+ $this->assertEquals(
+ $this->orderMock,
+ $this->subject->pay(
+ $this->orderMock,
+ $this->invoiceMock,
+ $isOnline
+ )
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/StateResolverTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/StateResolverTest.php
new file mode 100644
index 0000000000000..5d1958238b027
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/StateResolverTest.php
@@ -0,0 +1,82 @@
+orderMock = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderStateResolver = new StateResolver();
+ }
+
+ public function testStateComplete()
+ {
+ $this->assertEquals(Order::STATE_COMPLETE, $this->orderStateResolver->getStateForOrder($this->orderMock));
+ }
+
+ public function testStateClosed()
+ {
+ $this->orderMock->expects($this->once())
+ ->method('getBaseGrandTotal')
+ ->willReturn(100);
+
+ $this->orderMock->expects($this->once())
+ ->method('canCreditmemo')
+ ->willReturn(false);
+
+ $this->orderMock->expects($this->once())
+ ->method('getTotalRefunded')
+ ->willReturn(10.99);
+
+ $this->assertEquals(Order::STATE_CLOSED, $this->orderStateResolver->getStateForOrder($this->orderMock));
+ }
+
+ public function testStateNew()
+ {
+ $this->orderMock->expects($this->once())
+ ->method('isCanceled')
+ ->willReturn(true);
+ $this->assertEquals(Order::STATE_NEW, $this->orderStateResolver->getStateForOrder($this->orderMock));
+ }
+
+ public function testStateProcessing()
+ {
+ $arguments = [StateResolver::IN_PROGRESS];
+ $this->orderMock->expects($this->once())
+ ->method('isCanceled')
+ ->willReturn(true);
+
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn(Order::STATE_NEW);
+
+ $this->assertEquals(
+ Order::STATE_PROCESSING,
+ $this->orderStateResolver->getStateForOrder($this->orderMock, $arguments)
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderInvoiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderInvoiceTest.php
new file mode 100644
index 0000000000000..3a3ac33afa8e2
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/OrderInvoiceTest.php
@@ -0,0 +1,393 @@
+resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceDocumentFactoryMock = $this->getMockBuilder(InvoiceDocumentFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceValidatorMock = $this->getMockBuilder(InvoiceValidatorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->configMock = $this->getMockBuilder(OrderConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceRepositoryMock = $this->getMockBuilder(InvoiceRepository::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->notifierInterfaceMock = $this->getMockBuilder(NotifierInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceCommentCreationMock = $this->getMockBuilder(InvoiceCommentCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceCreationArgumentsMock = $this->getMockBuilder(InvoiceCreationArgumentsInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock = $this->getMockBuilder(OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->invoiceMock = $this->getMockBuilder(InvoiceInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->adapterInterface = $this->getMockBuilder(AdapterInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderInvoice = new OrderInvoice(
+ $this->resourceConnectionMock,
+ $this->orderRepositoryMock,
+ $this->invoiceDocumentFactoryMock,
+ $this->invoiceValidatorMock,
+ $this->paymentAdapterMock,
+ $this->orderStateResolverMock,
+ $this->configMock,
+ $this->invoiceRepositoryMock,
+ $this->notifierInterfaceMock,
+ $this->loggerMock
+ );
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testOrderInvoice($orderId, $capture, $items, $notify, $appendComment)
+ {
+ $this->resourceConnectionMock->expects($this->once())
+ ->method('getConnection')
+ ->with('sales')
+ ->willReturn($this->adapterInterface);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->willReturn($this->orderMock);
+
+ $this->invoiceDocumentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock,
+ $items,
+ $this->invoiceCommentCreationMock,
+ ($appendComment && $notify),
+ $this->invoiceCreationArgumentsMock
+ )->willReturn($this->invoiceMock);
+
+ $this->invoiceValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->invoiceMock, $this->orderMock)
+ ->willReturn([]);
+
+ $this->paymentAdapterMock->expects($this->once())
+ ->method('pay')
+ ->with($this->orderMock, $this->invoiceMock, $capture)
+ ->willReturn($this->orderMock);
+
+ $this->orderStateResolverMock->expects($this->once())
+ ->method('getStateForOrder')
+ ->with($this->orderMock, [OrderStateResolverInterface::IN_PROGRESS])
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $this->orderMock->expects($this->once())
+ ->method('setState')
+ ->with(Order::STATE_PROCESSING)
+ ->willReturnSelf();
+
+ $this->orderMock->expects($this->once())
+ ->method('getState')
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $this->configMock->expects($this->once())
+ ->method('getStateDefaultStatus')
+ ->with(Order::STATE_PROCESSING)
+ ->willReturn('Processing');
+
+ $this->orderMock->expects($this->once())
+ ->method('setStatus')
+ ->with('Processing')
+ ->willReturnSelf();
+
+ $this->invoiceMock->expects($this->once())
+ ->method('setState')
+ ->with(\Magento\Sales\Model\Order\Invoice::STATE_PAID)
+ ->willReturnSelf();
+
+ $this->invoiceRepositoryMock->expects($this->once())
+ ->method('save')
+ ->with($this->invoiceMock)
+ ->willReturn($this->invoiceMock);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('save')
+ ->with($this->orderMock)
+ ->willReturn($this->orderMock);
+
+ if ($notify) {
+ $this->notifierInterfaceMock->expects($this->once())
+ ->method('notify')
+ ->with($this->orderMock, $this->invoiceMock, $this->invoiceCommentCreationMock);
+ }
+
+ $this->invoiceMock->expects($this->once())
+ ->method('getEntityId')
+ ->willReturn(2);
+
+ $this->assertEquals(
+ 2,
+ $this->orderInvoice->execute(
+ $orderId,
+ $capture,
+ $items,
+ $notify,
+ $appendComment,
+ $this->invoiceCommentCreationMock,
+ $this->invoiceCreationArgumentsMock
+ )
+ );
+ }
+
+ /**
+ * @expectedException \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
+ */
+ public function testDocumentValidationException()
+ {
+ $orderId = 1;
+ $capture = true;
+ $items = [1 => 2];
+ $notify = true;
+ $appendComment = true;
+ $errorMessages = ['error1', 'error2'];
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->willReturn($this->orderMock);
+
+ $this->invoiceDocumentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock,
+ $items,
+ $this->invoiceCommentCreationMock,
+ ($appendComment && $notify),
+ $this->invoiceCreationArgumentsMock
+ )->willReturn($this->invoiceMock);
+
+ $this->invoiceValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->invoiceMock, $this->orderMock)
+ ->willReturn($errorMessages);
+
+ $this->orderInvoice->execute(
+ $orderId,
+ $capture,
+ $items,
+ $notify,
+ $appendComment,
+ $this->invoiceCommentCreationMock,
+ $this->invoiceCreationArgumentsMock
+ );
+ }
+
+ /**
+ * @expectedException \Magento\Sales\Api\Exception\CouldNotInvoiceExceptionInterface
+ */
+ public function testCouldNotInvoiceException()
+ {
+ $orderId = 1;
+ $items = [1 => 2];
+ $capture = true;
+ $notify = true;
+ $appendComment = true;
+ $this->resourceConnectionMock->expects($this->once())
+ ->method('getConnection')
+ ->with('sales')
+ ->willReturn($this->adapterInterface);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->willReturn($this->orderMock);
+
+ $this->invoiceDocumentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock,
+ $items,
+ $this->invoiceCommentCreationMock,
+ ($appendComment && $notify),
+ $this->invoiceCreationArgumentsMock
+ )->willReturn($this->invoiceMock);
+
+ $this->invoiceValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->invoiceMock, $this->orderMock)
+ ->willReturn([]);
+ $e = new \Exception;
+
+ $this->paymentAdapterMock->expects($this->once())
+ ->method('pay')
+ ->with($this->orderMock, $this->invoiceMock, $capture)
+ ->willThrowException($e);
+
+ $this->loggerMock->expects($this->once())
+ ->method('critical')
+ ->with($e);
+
+ $this->adapterInterface->expects($this->once())
+ ->method('rollBack');
+
+ $this->orderInvoice->execute(
+ $orderId,
+ $capture,
+ $items,
+ $notify,
+ $appendComment,
+ $this->invoiceCommentCreationMock,
+ $this->invoiceCreationArgumentsMock
+ );
+ }
+
+ public function dataProvider()
+ {
+ return [
+ 'TestWithNotifyTrue' => [1, true, [1 => 2], true, true],
+ 'TestWithNotifyFalse' => [1, true, [1 => 2], false, true]
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index 78dd17256703a..bd5fcf871d11b 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -49,7 +49,9 @@
+
+
@@ -61,9 +63,12 @@
+
+
+
+
-
@@ -81,6 +86,10 @@
+
+
+
+
@@ -894,4 +903,11 @@
sales
+
+
+
+ - Magento\Sales\Model\Order\Invoice\Sender\EmailSender
+
+
+
diff --git a/app/code/Magento/Sales/etc/webapi.xml b/app/code/Magento/Sales/etc/webapi.xml
index d6554a14d20cf..8d1b1fda5bc31 100644
--- a/app/code/Magento/Sales/etc/webapi.xml
+++ b/app/code/Magento/Sales/etc/webapi.xml
@@ -253,4 +253,10 @@
+
+
+
+
+
+
diff --git a/dev/tests/api-functional/phpunit.xml.dist b/dev/tests/api-functional/phpunit.xml.dist
index 99754bb22cb8a..443cb704f1cae 100644
--- a/dev/tests/api-functional/phpunit.xml.dist
+++ b/dev/tests/api-functional/phpunit.xml.dist
@@ -35,7 +35,7 @@
-
+
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php
new file mode 100644
index 0000000000000..85a034a5caa43
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php
@@ -0,0 +1,93 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+ $this->invoiceRepository = $this->objectManager->get(
+ \Magento\Sales\Api\InvoiceRepositoryInterface::class
+ );
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Sales/_files/order_new.php
+ */
+ public function testInvoiceCreate()
+ {
+ /** @var \Magento\Sales\Model\Order $existingOrder */
+ $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+ ->loadByIncrementId('100000001');
+
+ $serviceInfo = [
+ 'rest' => [
+ 'resourcePath' => '/V1/order/' . $existingOrder->getId() . '/invoice',
+ 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
+ ],
+ 'soap' => [
+ 'service' => self::SERVICE_READ_NAME,
+ 'serviceVersion' => self::SERVICE_VERSION,
+ 'operation' => self::SERVICE_READ_NAME . 'execute'
+ ]
+ ];
+
+ $requestData = [
+ 'orderId' => $existingOrder->getId(),
+ 'items' => [],
+ 'comment' => [
+ 'comment' => 'Test Comment',
+ 'is_visible_on_front' => 1
+ ]
+ ];
+
+ /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */
+ foreach ($existingOrder->getAllItems() as $item) {
+ $requestData['items'][] = [
+ 'order_item_id' => $item->getItemId(),
+ 'qty' => $item->getQtyOrdered()
+ ];
+ }
+
+ $result = $this->_webApiCall($serviceInfo, $requestData);
+
+ $this->assertNotEmpty($result);
+
+ try {
+ $this->invoiceRepository->get($result);
+ } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ $this->fail('Failed asserting that Invoice was created');
+ }
+
+ /** @var \Magento\Sales\Model\Order $updatedOrder */
+ $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+ ->loadByIncrementId('100000001');
+
+ $this->assertNotEquals(
+ $existingOrder->getStatus(),
+ $updatedOrder->getStatus(),
+ 'Failed asserting that Order status was changed'
+ );
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php
index 91d722f8ef4eb..d624fafa3143a 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Attribute/CustomAttribute.php
@@ -8,6 +8,9 @@
use Magento\Mtf\Client\Locator;
use Magento\Mtf\Client\Element\SimpleElement;
+use Magento\Mtf\Client\DriverInterface;
+use Magento\Mtf\Client\ElementInterface;
+use Magento\Mtf\System\Event\EventManagerInterface;
/**
* Catalog product custom attribute element.
@@ -15,25 +18,45 @@
class CustomAttribute extends SimpleElement
{
/**
- * Attribute input selector;
+ * Attribute input selector.
*
* @var string
*/
- protected $inputSelector = '[name="product[%s]"]';
+ private $inputSelector = '[name="product[%s]"]';
+
+ /**
+ * Locator for data grid.
+ *
+ * @var string
+ */
+ private $dataGrid = '[data-role="grid"]';
/**
* Attribute class to element type reference.
*
* @var array
*/
- protected $classReference = [
- 'admin__control-text' => null,
- 'textarea' => null,
- 'hasDatepicker' => 'datepicker',
- 'admin__control-select' => 'select',
- 'admin__control-multiselect' => 'multiselect',
- 'admin__actions-switch-checkbox' => 'switcher'
- ];
+ private $classReferences = [];
+
+ /**
+ * Constructor
+ *
+ * @param DriverInterface $driver
+ * @param EventManagerInterface $eventManager
+ * @param Locator $locator
+ * @param ElementInterface $context
+ * @param array $classReferences
+ */
+ public function __construct(
+ DriverInterface $driver,
+ EventManagerInterface $eventManager,
+ Locator $locator,
+ ElementInterface $context,
+ array $classReferences
+ ) {
+ parent::__construct($driver, $eventManager, $locator, $context);
+ $this->classReferences = $classReferences;
+ }
/**
* Set attribute value.
@@ -48,7 +71,11 @@ public function setValue($data)
$element = $this->getElementByClass($this->getElementClass($code));
$value = is_array($data) ? $data['value'] : $data;
if ($value !== null) {
- $this->find(sprintf($this->inputSelector, $code), Locator::SELECTOR_CSS, $element)->setValue($value);
+ $this->find(
+ str_replace('%code%', $code, $element['selector']),
+ Locator::SELECTOR_CSS,
+ $element['type']
+ )->setValue($value);
}
}
@@ -66,17 +93,17 @@ public function getValue()
}
/**
- * Get element type by class.
+ * Get element by class.
*
* @param string $class
- * @return string
+ * @return array|null
*/
private function getElementByClass($class)
{
$element = null;
- foreach ($this->classReference as $key => $reference) {
+ foreach (array_keys($this->classReferences) as $key) {
if (strpos($class, $key) !== false) {
- $element = $reference;
+ return $this->classReferences[$class];
}
}
return $element;
@@ -90,7 +117,9 @@ private function getElementByClass($class)
*/
private function getElementClass($code)
{
- return $this->find(sprintf($this->inputSelector, $code), Locator::SELECTOR_CSS)->getAttribute('class');
+ return $this->find($this->dataGrid)->isVisible()
+ ? 'dynamicRows'
+ : $this->find(sprintf($this->inputSelector, $code), Locator::SELECTOR_CSS)->getAttribute('class');
}
/**
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple/CustomAttribute.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple/CustomAttribute.php
index a28a65de925a7..f8be03d37846e 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple/CustomAttribute.php
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple/CustomAttribute.php
@@ -20,7 +20,7 @@ class CustomAttribute extends DataSource
*
* @var CatalogProductAttribute
*/
- protected $attribute;
+ private $attribute;
/**
* @constructor
@@ -54,7 +54,7 @@ public function __construct(FixtureFactory $fixtureFactory, array $params, $data
* @param CatalogProductAttribute $attribute
* @return string|null
*/
- protected function getDefaultAttributeValue(CatalogProductAttribute $attribute)
+ private function getDefaultAttributeValue(CatalogProductAttribute $attribute)
{
$data = $attribute->getData();
$value = '';
@@ -91,7 +91,7 @@ public function getAttribute()
* @param CatalogProductAttribute $attribute
* @return string
*/
- protected function createAttributeCode(CatalogProductAttribute $attribute)
+ private function createAttributeCode(CatalogProductAttribute $attribute)
{
$label = $attribute->getFrontendLabel();
return strtolower(preg_replace('@[\W\s]+@', '_', $label));
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml
index d61ef9004b2f1..2a94a12c0dbdf 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml
@@ -174,6 +174,12 @@
Fixed Product Tax
attr_fpt_code_%isolation%
Fixed_Product_Tax_Storeview
+
+ - United States
+ - 10
+ - All Websites USD
+ - *
+
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml
index 2356222387920..82a7c263b1ca9 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/etc/di.xml
@@ -156,4 +156,34 @@
low
+
+
+
+ -
+
- [name="product[%code%]"]
+ - null
+
+ -
+
- [name="product[%code%]"]
+ - null
+
+ -
+
- [name="product[%code%]"]
+ - datepicker
+
+ -
+
- [name="product[%code%]"]
+ - select
+
+ -
+
- [name="product[%code%]"]
+ - multiselect
+
+ -
+
- [name="product[%code%]"]
+ - switcher
+
+
+
+
diff --git a/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/Fpt.php b/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/Fpt.php
new file mode 100644
index 0000000000000..e059563ff0fc4
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Weee/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/Fpt.php
@@ -0,0 +1,70 @@
+ [
+ 'type' => 'select',
+ 'selector' => '[name$="[country]"]'
+ ],
+ 'website' => [
+ 'type' => 'select',
+ 'selector' => '[name$="[website_id]"]'
+ ],
+ 'tax' => [
+ 'type' => 'input',
+ 'selector' => '[name$="[value]"]'
+ ],
+ 'state' => [
+ 'type' => 'select',
+ 'selector' => '[name$="[state]"]'
+ ]
+ ];
+
+ /**
+ * Fill Fixed Product Tax form.
+ *
+ * @param string|array $value
+ * @return void
+ */
+ public function setValue($value)
+ {
+ if ($this->find($this->buttonFormLocator)->isVisible()) {
+ $this->find($this->buttonFormLocator)->click();
+ }
+ foreach ((array)$value as $name => $data) {
+ $element = $this->find(
+ $this->fields[$name]['selector'],
+ Locator::SELECTOR_CSS,
+ $this->fields[$name]['type']
+ );
+
+ if ($element->isVisible()) {
+ $element->setValue($data);
+ }
+ }
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Weee/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Weee/Test/etc/di.xml
index 2097566cc2463..30be698de32ea 100644
--- a/dev/tests/functional/tests/app/Magento/Weee/Test/etc/di.xml
+++ b/dev/tests/functional/tests/app/Magento/Weee/Test/etc/di.xml
@@ -6,9 +6,19 @@
*/
-->
-
-
- high
-
-
+
+
+ high
+
+
+
+
+
+ -
+
- [data-role="grid"]
+ - \Magento\Weee\Test\Block\Adminhtml\Product\Edit\Section\ProductDetails\Fpt
+
+
+
+
diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_new.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_new.php
new file mode 100644
index 0000000000000..246d48feaafe3
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_new.php
@@ -0,0 +1,24 @@
+create('Magento\Sales\Model\Order')
+ ->loadByIncrementId('100000001');
+
+$order->setState(
+ \Magento\Sales\Model\Order::STATE_NEW
+);
+
+$order->setStatus(
+ $order->getConfig()->getStateDefaultStatus(
+ \Magento\Sales\Model\Order::STATE_NEW
+ )
+);
+
+$order->save();
diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_new_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_new_rollback.php
new file mode 100644
index 0000000000000..7603ca732c71f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_new_rollback.php
@@ -0,0 +1,6 @@
+_useFtp ? new \Magento\Framework\Backup\Filesystem\Rollback\Ftp(
- $this
- ) : new \Magento\Framework\Backup\Filesystem\Rollback\Fs(
- $this
- );
+ $rollbackWorker = $this->_useFtp ? $this->getRollBackFtp() : $this->getRollBackFs();
$rollbackWorker->run();
$this->_lastOperationSucceed = true;
@@ -299,4 +307,36 @@ protected function _getTarTmpPath()
$tmpName = '~tmp-' . microtime(true) . '.tar';
return $this->getBackupsDir() . '/' . $tmpName;
}
+
+ /**
+ * @return \Magento\Framework\Backup\Filesystem\Rollback\Ftp
+ * @deprecated
+ */
+ protected function getRollBackFtp()
+ {
+ if (!$this->rollBackFtp) {
+ $this->rollBackFtp = ObjectManager::getInstance()->create(
+ \Magento\Framework\Backup\Filesystem\Rollback\Ftp::class,
+ [$this]
+ );
+ }
+
+ return $this->rollBackFtp;
+ }
+
+ /**
+ * @return \Magento\Framework\Backup\Filesystem\Rollback\Fs
+ * @deprecated
+ */
+ protected function getRollBackFs()
+ {
+ if (!$this->rollBackFs) {
+ $this->rollBackFs = ObjectManager::getInstance()->create(
+ \Magento\Framework\Backup\Filesystem\Rollback\Fs::class,
+ [$this]
+ );
+ }
+
+ return $this->rollBackFs;
+ }
}
diff --git a/lib/internal/Magento/Framework/Backup/Filesystem/Helper.php b/lib/internal/Magento/Framework/Backup/Filesystem/Helper.php
index 355f10226622a..2587cd543818a 100644
--- a/lib/internal/Magento/Framework/Backup/Filesystem/Helper.php
+++ b/lib/internal/Magento/Framework/Backup/Filesystem/Helper.php
@@ -87,10 +87,12 @@ public function getInfo($path, $infoOptions = self::INFO_ALL, $skipFiles = [])
$info = [];
if ($infoOptions & self::INFO_READABLE) {
$info['readable'] = true;
+ $info['readableMeta'] = [];
}
if ($infoOptions & self::INFO_WRITABLE) {
$info['writable'] = true;
+ $info['writableMeta'] = [];
}
if ($infoOptions & self::INFO_SIZE) {
@@ -111,10 +113,12 @@ public function getInfo($path, $infoOptions = self::INFO_ALL, $skipFiles = [])
if ($infoOptions & self::INFO_WRITABLE && !$item->isWritable()) {
$info['writable'] = false;
+ $info['writableMeta'][] = $item->getPathname();
}
if ($infoOptions & self::INFO_READABLE && !$item->isReadable()) {
$info['readable'] = false;
+ $info['readableMeta'][] = $item->getPathname();
}
if ($infoOptions & self::INFO_SIZE && !$item->isDir()) {
diff --git a/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php b/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php
index 0339195bbb96a..fe627b3dd59dc 100644
--- a/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php
+++ b/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Framework\Backup\Filesystem\Rollback;
+use Magento\Framework\App\ObjectManager;
+
/**
* Rollback worker for rolling back via local filesystem
*
@@ -12,6 +14,11 @@
*/
class Fs extends AbstractRollback
{
+ /**
+ * @var \Magento\Framework\Backup\Filesystem\Helper
+ */
+ private $fsHelper;
+
/**
* Files rollback implementation via local filesystem
*
@@ -30,7 +37,7 @@ public function run()
);
}
- $fsHelper = new \Magento\Framework\Backup\Filesystem\Helper();
+ $fsHelper = $this->getFsHelper();
$filesInfo = $fsHelper->getInfo(
$this->_snapshot->getRootDir(),
@@ -39,6 +46,15 @@ public function run()
);
if (!$filesInfo['writable']) {
+ if (!empty($filesInfo['writableMeta'])) {
+ throw new \Magento\Framework\Backup\Exception\NotEnoughPermissions(
+ new \Magento\Framework\Phrase(
+ 'You need write permissions for: %1',
+ [implode(', ', $filesInfo['writableMeta'])]
+ )
+ );
+ }
+
throw new \Magento\Framework\Backup\Exception\NotEnoughPermissions(
new \Magento\Framework\Phrase('Unable to make rollback because not all files are writable')
);
@@ -59,4 +75,17 @@ public function run()
$fsHelper->rm($this->_snapshot->getRootDir(), $this->_snapshot->getIgnorePaths());
$archiver->unpack($snapshotPath, $this->_snapshot->getRootDir());
}
+
+ /**
+ * @return \Magento\Framework\Backup\Filesystem\Helper
+ * @deprecated
+ */
+ private function getFsHelper()
+ {
+ if (!$this->fsHelper) {
+ $this->fsHelper = ObjectManager::getInstance()->get(\Magento\Framework\Backup\Filesystem\Helper::class);
+ }
+
+ return $this->fsHelper;
+ }
}
diff --git a/lib/internal/Magento/Framework/Backup/Test/Unit/Filesystem/Rollback/FsTest.php b/lib/internal/Magento/Framework/Backup/Test/Unit/Filesystem/Rollback/FsTest.php
new file mode 100644
index 0000000000000..dbd830a8fc9f2
--- /dev/null
+++ b/lib/internal/Magento/Framework/Backup/Test/Unit/Filesystem/Rollback/FsTest.php
@@ -0,0 +1,111 @@
+backupPath = '/some/test/path';
+ $this->rootDir = '/';
+ $this->ignorePaths = [];
+
+ $this->objectManager = new ObjectManager($this);
+ $this->snapshotMock = $this->getMockBuilder(\Magento\Framework\Backup\Filesystem::class)
+ ->setMethods(['getBackupPath', 'getRootDir', 'getIgnorePaths'])
+ ->getMock();
+ $this->snapshotMock->expects($this->any())
+ ->method('getBackupPath')
+ ->willReturn($this->backupPath);
+ $this->snapshotMock->expects($this->any())
+ ->method('getRootDir')
+ ->willReturn($this->rootDir);
+ $this->snapshotMock->expects($this->any())
+ ->method('getIgnorePaths')
+ ->willReturn($this->ignorePaths);
+ $this->fsHelperMock = $this->getMockBuilder(\Magento\Framework\Backup\Filesystem\Helper::class)
+ ->setMethods(['getInfo', 'rm'])
+ ->getMock();
+ $this->fs = $this->objectManager->getObject(
+ \Magento\Framework\Backup\Filesystem\Rollback\Fs::class,
+ [
+ 'snapshotObject' => $this->snapshotMock,
+ 'fsHelper' => $this->fsHelperMock
+ ]
+ );
+
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Backup\Exception\NotEnoughPermissions
+ * @expectedExceptionMessage You need write permissions for: test1, test2
+ */
+ public function testRunNotEnoughPermissions()
+ {
+ $fsInfo = [
+ 'writable' => false,
+ 'writableMeta' => ['test1', 'test2']
+ ];
+
+ $this->fsHelperMock->expects($this->once())
+ ->method('getInfo')
+ ->willReturn($fsInfo);
+ $this->fs->run();
+ }
+
+ public function testRun()
+ {
+ $fsInfo = ['writable' => true];
+
+ $this->fsHelperMock->expects($this->once())
+ ->method('getInfo')
+ ->willReturn($fsInfo);
+ $this->fsHelperMock->expects($this->once())
+ ->method('rm')
+ ->with($this->rootDir, $this->ignorePaths);
+
+ $this->fs->run();
+ }
+}
diff --git a/lib/internal/Magento/Framework/Backup/Test/Unit/Filesystem/Rollback/_files/ioMock.php b/lib/internal/Magento/Framework/Backup/Test/Unit/Filesystem/Rollback/_files/ioMock.php
new file mode 100644
index 0000000000000..3ac56872e87b9
--- /dev/null
+++ b/lib/internal/Magento/Framework/Backup/Test/Unit/Filesystem/Rollback/_files/ioMock.php
@@ -0,0 +1,28 @@
+objectManager = new ObjectManager($this);
+ $this->fsMock = $this->getMockBuilder(\Magento\Framework\Backup\Filesystem\Rollback\Fs::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->ftpMock = $this->getMockBuilder(\Magento\Framework\Backup\Filesystem\Rollback\Ftp::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->snapshotMock = $this->getMockBuilder(\Magento\Framework\Backup\Filesystem::class)
+ ->getMock();
+ $this->filesystem = $this->objectManager->getObject(
+ \Magento\Framework\Backup\Filesystem::class,
+ [
+ 'rollBackFtp' => $this->ftpMock,
+ 'rollBackFs' => $this->fsMock
+ ]
+ );
+ }
+
+ public function testRollback()
+ {
+ $this->assertTrue($this->filesystem->rollback());
+ }
+}
diff --git a/lib/internal/Magento/Framework/Backup/Test/Unit/MediaTest.php b/lib/internal/Magento/Framework/Backup/Test/Unit/MediaTest.php
index ef382be2d4b52..a53d3fded0a41 100644
--- a/lib/internal/Magento/Framework/Backup/Test/Unit/MediaTest.php
+++ b/lib/internal/Magento/Framework/Backup/Test/Unit/MediaTest.php
@@ -5,10 +5,17 @@
*/
namespace Magento\Framework\Backup\Test\Unit;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
require_once __DIR__ . '/_files/io.php';
class MediaTest extends \PHPUnit_Framework_TestCase
{
+ /**
+ * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
+ */
+ private $objectManager;
+
/**
* @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -24,6 +31,11 @@ class MediaTest extends \PHPUnit_Framework_TestCase
*/
protected $_backupDbMock;
+ /**
+ * @var \Magento\Framework\Backup\Filesystem\Rollback\Fs
+ */
+ private $fsMock;
+
public static function setUpBeforeClass()
{
require __DIR__ . '/_files/app_dirs.php';
@@ -36,6 +48,7 @@ public static function tearDownAfterClass()
protected function setUp()
{
+ $this->objectManager = new ObjectManager($this);
$this->_backupDbMock = $this->getMock(\Magento\Framework\Backup\Db::class, [], [], '', false);
$this->_backupDbMock->expects($this->any())->method('setBackupExtension')->will($this->returnSelf());
@@ -69,6 +82,8 @@ protected function setUp()
)->will(
$this->returnValue($this->_backupDbMock)
);
+
+ $this->fsMock = $this->getMock(\Magento\Framework\Backup\Filesystem\Rollback\Fs::class, [], [], '', false);
}
/**
@@ -81,7 +96,14 @@ public function testAction($action)
$rootDir = str_replace('\\', '/', TESTS_TEMP_DIR) . '/Magento/Backup/data';
- $model = new \Magento\Framework\Backup\Media($this->_filesystemMock, $this->_backupFactoryMock);
+ $model = $this->objectManager->getObject(
+ \Magento\Framework\Backup\Media::class,
+ [
+ 'filesystem' => $this->_filesystemMock,
+ 'backupFactory' => $this->_backupFactoryMock,
+ 'rollBackFs' => $this->fsMock
+ ]
+ );
$model->setRootDir($rootDir);
$model->setBackupsDir($rootDir);
$model->{$action}();
diff --git a/lib/internal/Magento/Framework/Backup/Test/Unit/NomediaTest.php b/lib/internal/Magento/Framework/Backup/Test/Unit/NomediaTest.php
index 6a045a9974f84..228ea9a5569b3 100644
--- a/lib/internal/Magento/Framework/Backup/Test/Unit/NomediaTest.php
+++ b/lib/internal/Magento/Framework/Backup/Test/Unit/NomediaTest.php
@@ -5,10 +5,17 @@
*/
namespace Magento\Framework\Backup\Test\Unit;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
require_once __DIR__ . '/_files/io.php';
class NomediaTest extends \PHPUnit_Framework_TestCase
{
+ /**
+ * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
+ */
+ private $objectManager;
+
/**
* @var \Magento\Framework\Filesystem
*/
@@ -24,6 +31,11 @@ class NomediaTest extends \PHPUnit_Framework_TestCase
*/
protected $_backupDbMock;
+ /**
+ * @var \Magento\Framework\Backup\Filesystem\Rollback\Fs
+ */
+ private $fsMock;
+
public static function setUpBeforeClass()
{
require __DIR__ . '/_files/app_dirs.php';
@@ -36,6 +48,7 @@ public static function tearDownAfterClass()
protected function setUp()
{
+ $this->objectManager = new ObjectManager($this);
$this->_backupDbMock = $this->getMock(\Magento\Framework\Backup\Db::class, [], [], '', false);
$this->_backupDbMock->expects($this->any())->method('setBackupExtension')->will($this->returnSelf());
@@ -69,6 +82,8 @@ protected function setUp()
)->will(
$this->returnValue($this->_backupDbMock)
);
+
+ $this->fsMock = $this->getMock(\Magento\Framework\Backup\Filesystem\Rollback\Fs::class, [], [], '', false);
}
/**
@@ -81,7 +96,14 @@ public function testAction($action)
$rootDir = TESTS_TEMP_DIR . '/Magento/Backup/data';
- $model = new \Magento\Framework\Backup\Nomedia($this->_filesystemMock, $this->_backupFactoryMock);
+ $model = $this->objectManager->getObject(
+ \Magento\Framework\Backup\Nomedia::class,
+ [
+ 'filesystem' => $this->_filesystemMock,
+ 'backupFactory' => $this->_backupFactoryMock,
+ 'rollBackFs' => $this->fsMock
+ ]
+ );
$model->setRootDir($rootDir);
$model->setBackupsDir($rootDir);
$model->{$action}();
diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php
index 0a46a17c10b22..25486562257ca 100644
--- a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php
+++ b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php
@@ -40,7 +40,7 @@ public function __construct(
}
/**
- * Check if directory is writable
+ * Check if directory or file is writable
*
* @param string $path
* @return void
@@ -49,7 +49,9 @@ public function __construct(
protected function assertWritable($path)
{
if ($this->isWritable($path) === false) {
- $path = $this->getAbsolutePath($this->path, $path);
+ $path = (!$this->driver->isFile($path))
+ ? $this->getAbsolutePath($this->path, $path)
+ : $this->getAbsolutePath($path);
throw new FileSystemException(new \Magento\Framework\Phrase('The path "%1" is not writable', [$path]));
}
}
@@ -240,12 +242,13 @@ public function isWritable($path = null)
* @param string $path
* @param string $mode
* @return \Magento\Framework\Filesystem\File\WriteInterface
+ * @throws \Magento\Framework\Exception\FileSystemException
*/
public function openFile($path, $mode = 'w')
{
$folder = dirname($path);
$this->create($folder);
- $this->assertWritable($folder);
+ $this->assertWritable($this->isExist($path) ? $path : $folder);
$absolutePath = $this->driver->getAbsolutePath($this->path, $path);
return $this->fileFactory->create($absolutePath, $this->driver, $mode);
}
diff --git a/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/WriteTest.php b/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/WriteTest.php
index 5c93c890f04de..1a84fb6a69a6e 100644
--- a/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/WriteTest.php
+++ b/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/WriteTest.php
@@ -121,6 +121,17 @@ public function testCreateSymlinkTargetDirectoryExists()
$this->assertTrue($this->write->createSymlink($sourcePath, $destinationFile, $targetDir));
}
+ /**
+ * @expectedException \Magento\Framework\Exception\FileSystemException
+ */
+ public function testOpenFileNonWritable()
+ {
+ $targetPath = '/path/to/target.file';
+ $this->driver->expects($this->once())->method('isExists')->willReturn(true);
+ $this->driver->expects($this->once())->method('isWritable')->willReturn(false);
+ $this->write->openFile($targetPath);
+ }
+
/**
* Assert is file expectation
*