diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php index 4bd13d6264cb0..7cf77449dcdbe 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php @@ -46,7 +46,7 @@ public function execute() $import->uploadSource(), $this->_objectManager->create(\Magento\Framework\Filesystem::class) ->getDirectoryWrite(DirectoryList::ROOT), - $data[$import::FIELD_FIELD_SEPARATOR] + $this->getSourceAdapterOptions($data) ); $this->processValidationResult($import->validateSource($source), $resultBlock); } catch (\Magento\Framework\Exception\LocalizedException $e) { @@ -120,6 +120,19 @@ private function getImport() return $this->import; } + /** + * Returns options for source adapter + * + * @param $data + * @return array + */ + protected function getSourceAdapterOptions($data) + { + return [ + 'delimiter' => $data[Import::FIELD_FIELD_SEPARATOR] ?: ',' + ]; + } + /** * Add error message to Result block and allow 'Import' button * diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php index 60544a781b34a..17653c67c299e 100644 --- a/app/code/Magento/ImportExport/Model/Import.php +++ b/app/code/Magento/ImportExport/Model/Import.php @@ -267,7 +267,7 @@ protected function _getSourceAdapter($sourceFile) return \Magento\ImportExport\Model\Import\Adapter::findAdapterFor( $sourceFile, $this->_filesystem->getDirectoryWrite(DirectoryList::ROOT), - $this->getData(self::FIELD_FIELD_SEPARATOR) + $this->getSourceAdapterOptions() ); } @@ -785,4 +785,16 @@ public function getDeletedItemsCount() { return $this->_getEntityAdapter()->getDeletedItemsCount(); } + + /** + * Returns options for source adapter + * + * @return array + */ + protected function getSourceAdapterOptions() + { + return [ + 'delimiter' => $this->getData(self::FIELD_FIELD_SEPARATOR) ?: ',' + ]; + } } diff --git a/app/code/Magento/ImportExport/Model/Import/Adapter.php b/app/code/Magento/ImportExport/Model/Import/Adapter.php index 0242ae6096316..5d3e483e08f81 100644 --- a/app/code/Magento/ImportExport/Model/Import/Adapter.php +++ b/app/code/Magento/ImportExport/Model/Import/Adapter.php @@ -5,7 +5,11 @@ */ namespace Magento\ImportExport\Model\Import; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Filesystem\Directory\Write; +use Magento\ImportExport\Model\Import\Source\FileFactory; +use Magento\ImportExport\Model\Import\Source\FileParser\CorruptedFileException; +use Magento\ImportExport\Model\Import\Source\FileParser\UnsupportedPathException; /** * Import adapter model @@ -14,6 +18,15 @@ */ class Adapter { + /** @var FileFactory */ + private $fileSourceFactory; + + public function __construct(FileFactory $fileSourceFactory) + { + $this->fileSourceFactory = $fileSourceFactory; + } + + /** * Adapter factory. Checks for availability, loads and create instance of import adapter object. * @@ -25,29 +38,12 @@ class Adapter * @return AbstractSource * * @throws \Magento\Framework\Exception\LocalizedException + * @deprecated + * @see Adapter::createSourceByPath() */ public static function factory($type, $directory, $source, $options = null) { - if (!is_string($type) || !$type) { - throw new \Magento\Framework\Exception\LocalizedException( - __('The adapter type must be a non-empty string.') - ); - } - $adapterClass = 'Magento\ImportExport\Model\Import\Source\\' . ucfirst(strtolower($type)); - - if (!class_exists($adapterClass)) { - throw new \Magento\Framework\Exception\LocalizedException( - __('\'%1\' file extension is not supported', $type) - ); - } - $adapter = new $adapterClass($source, $directory, $options); - - if (!$adapter instanceof AbstractSource) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Adapter must be an instance of \Magento\ImportExport\Model\Import\AbstractSource') - ); - } - return $adapter; + return self::createBackwardCompatibleInstance()->createSourceByPath($source, $options); } /** @@ -58,9 +54,72 @@ public static function factory($type, $directory, $source, $options = null) * @param mixed $options OPTIONAL Adapter constructor options * * @return AbstractSource + * @deprecated + * @see Adapter::createSourceByPath() */ public static function findAdapterFor($source, $directory, $options = null) { - return self::factory(pathinfo($source, PATHINFO_EXTENSION), $directory, $source, $options); + return self::createBackwardCompatibleInstance()->createSourceByPath($source, $options); + } + + + + /** + * Finds source for import by file path + * + * @param $path + * @param $options + * + * @return AbstractSource + */ + public function createSourceByPath($path, $options = []) + { + try { + $options = $this->mapBackwardCompatibleFileParserOption($options); + $parser = $this->fileSourceFactory->createFromFilePath($path, $options); + } catch (UnsupportedPathException $e) { + $this->throwUnsupportedFileException($path); + } catch (CorruptedFileException $e) { + $this->throwUnsupportedFileException($path); + } + + return $parser; + } + + + private function extractFileExtension($path) + { + $fileName = basename($path); + + if (strpos($path, '.') === false) { + return $fileName; + } + + $fileExtension = substr($fileName, strrpos($fileName, '.') + 1); + return $fileExtension; + } + + private function throwUnsupportedFileException($path) + { + $fileExtension = $this->extractFileExtension($path); + throw new \Magento\Framework\Exception\LocalizedException( + __('\'%1\' file extension is not supported', $fileExtension) + ); + } + + private function mapBackwardCompatibleFileParserOption($options) + { + if (!is_array($options)) { + $options = ['delimiter' => $options ?? ',']; + } + return $options; + } + + /** + * @return self + */ + private static function createBackwardCompatibleInstance() + { + return ObjectManager::getInstance()->create(self::class); } } diff --git a/app/code/Magento/ImportExport/Model/Import/Source/Csv.php b/app/code/Magento/ImportExport/Model/Import/Source/Csv.php index ad95ca4d45cc4..41b926ae8c7f3 100644 --- a/app/code/Magento/ImportExport/Model/Import/Source/Csv.php +++ b/app/code/Magento/ImportExport/Model/Import/Source/Csv.php @@ -7,6 +7,8 @@ /** * CSV import adapter + * + * @deprecated */ class Csv extends \Magento\ImportExport\Model\Import\AbstractSource { diff --git a/app/code/Magento/ImportExport/Model/Import/Source/File.php b/app/code/Magento/ImportExport/Model/Import/Source/File.php new file mode 100644 index 0000000000000..af0d3c7a3c5b4 --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/File.php @@ -0,0 +1,31 @@ +parser = $parser; + parent::__construct($this->parser->getColumnNames()); + } + + protected function _getNextRow() + { + return $this->parser->fetchRow(); + } + + public function rewind() + { + $this->parser->reset(); + parent::rewind(); + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileFactory.php b/app/code/Magento/ImportExport/Model/Import/Source/FileFactory.php new file mode 100644 index 0000000000000..868271fe53625 --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileFactory.php @@ -0,0 +1,53 @@ +parserFactory = $parserFactory; + $this->objectManager = $objectManager; + } + + /** + * Creates file source from file path + * + * @param string $path + * @param array $options + * @return AbstractSource + */ + public function createFromFilePath($path, array $options = []) + { + return $this->createFromFileParser( + $this->parserFactory->create($path, $options) + ); + } + + /** + * Creates file source from file parser + * + * @param ParserInterface $parser + * @return AbstractSource + */ + public function createFromFileParser(ParserInterface $parser) + { + return $this->objectManager->create(File::class, ['parser' => $parser]); + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CompositeParserFactory.php b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CompositeParserFactory.php new file mode 100644 index 0000000000000..ef53135166bb8 --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CompositeParserFactory.php @@ -0,0 +1,62 @@ +parserFactories = []; + + foreach ($parserFactories as $parserFactory) { + $this->addParserFactory($parserFactory); + } + } + + public function create($path, array $options = []) + { + foreach ($this->parserFactories as $parserFactory) { + $parser = $this->createParserIfSupported($parserFactory, $path, $options); + + if ($parser === false) { + continue; + } + + return $parser; + } + + $this->thereWasNoParserFound($path); + } + + public function addParserFactory(ParserFactoryInterface $parserFactory) + { + $this->parserFactories[] = $parserFactory; + } + + private function thereWasNoParserFound($path) + { + throw new UnsupportedPathException($path); + } + + private function createParserIfSupported(ParserFactoryInterface $parserFactory, $path, array $options) + { + try { + $parser = $parserFactory->create($path, $options); + } catch (UnsupportedPathException $e) { + return false; + } + + return $parser; + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CorruptedFileException.php b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CorruptedFileException.php new file mode 100644 index 0000000000000..6c91267849421 --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CorruptedFileException.php @@ -0,0 +1,24 @@ +fileName = $fileName; + parent::__construct($message ?: sprintf('File "%s" is corrupted', $fileName)); + } + + + public function getFileName() + { + return $this->fileName; + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CsvParser.php b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CsvParser.php new file mode 100644 index 0000000000000..9d277f3d3ac28 --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CsvParser.php @@ -0,0 +1,113 @@ + ',', + 'enclosure' => '"', + 'escape' => '\\', + 'null' => null + ]; + + /** + * File reader + * + * @var Filesystem\File\ReadInterface + */ + private $file; + + /** + * Columns of CSV file + * + * @var string[] + */ + private $columns; + + public function __construct( + Filesystem\File\ReadInterface $file, + $options = [] + ) { + $this->file = $file; + $this->options = $options + $this->options; + $this->columns = $this->fetchCsvLine(); + + if ($this->columns === false) { + throw new \InvalidArgumentException('CSV file should contain at least 1 row'); + } + } + + public function getColumnNames() + { + return $this->columns; + } + + public function fetchRow() + { + $row = $this->fetchCsvLine(); + + if ($row === false) { + return false; + } + + return $this->mapRowData($row); + } + + public function reset() + { + $this->file->seek(0); + $this->fetchCsvLine(); + } + + private function fetchCsvLine() + { + return $this->file->readCsv( + 0, + $this->options['delimiter'], + $this->options['enclosure'], + $this->options['escape'] + ); + } + + private function mapRowData($row) + { + $result = []; + foreach ($this->columns as $index => $column) { + $result[] = $this->mapNullValue( + $this->extractRowValue($row, $index) + ); + } + return $result; + } + + public function __destruct() + { + $this->file->close(); + } + + private function mapNullValue($value) + { + if ($value === $this->options['null']) { + $value = null; + } + return $value; + } + + private function extractRowValue($row, $index) + { + return (isset($row[$index]) ? $row[$index] : ''); + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CsvParserFactory.php b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CsvParserFactory.php new file mode 100644 index 0000000000000..5e2a6296904dd --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/CsvParserFactory.php @@ -0,0 +1,59 @@ +filesystem = $filesystem; + $this->objectManager = $objectManager; + } + + public function create($filePath, array $options = []) + { + $directoryCode = $options['directory_code'] ?? DirectoryList::ROOT; + + if (substr($filePath, -4) !== '.csv') { + throw new UnsupportedPathException($filePath); + } + + $directory = $this->filesystem->getDirectoryRead($directoryCode); + $filePath = $directory->getRelativePath($filePath); + + if (!$directory->isFile($filePath)) { + throw new \InvalidArgumentException(sprintf('File "%s" does not exists', $filePath)); + } + + return $this->objectManager->create( + CsvParser::class, + [ + 'file' => $directory->openFile($filePath), + 'options' => $options + ] + ); + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileParser/ParserFactoryInterface.php b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/ParserFactoryInterface.php new file mode 100644 index 0000000000000..6f0a5340392fd --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/ParserFactoryInterface.php @@ -0,0 +1,24 @@ +path = $path; + } + + public function getPath() + { + return $this->path; + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/FileParser/ZipParserFactory.php b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/ZipParserFactory.php new file mode 100644 index 0000000000000..503ac87d257ce --- /dev/null +++ b/app/code/Magento/ImportExport/Model/Import/Source/FileParser/ZipParserFactory.php @@ -0,0 +1,118 @@ +filesystem = $filesystem; + $this->parserFactory = $parserFactory; + $this->isZipAvailable = $isZipAvailable ?? extension_loaded('zip'); + } + + public function create($path, array $options = []) + { + $this->assertZipFileIsParsable($path); + + $sourceDirectory = $this->filesystem->getDirectoryRead(DirectoryList::ROOT); + $writeDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::TMP); + + $path = $sourceDirectory->getRelativePath($path); + $this->assertFileExistance($path, $sourceDirectory); + + $zip = new \ZipArchive(); + + if ($zip->open($sourceDirectory->getAbsolutePath($path)) !== true) { + throw new CorruptedFileException($path); + } + + $files = $this->fetchFileNamesFromZipFile($zip); + + foreach ($files as $file) { + $destinationFilename = $this->extractFileIntoDirectory($zip, $writeDirectory, $file); + + try { + $parser = $this->parserFactory->create( + $destinationFilename, + ['directory_code' => DirectoryList::TMP] + $options + ); + } catch (UnsupportedPathException $e) { + continue; + } finally { + $writeDirectory->delete($destinationFilename); + } + + return $parser; + } + + throw new UnsupportedPathException($path); + } + + private function assertZipFileIsParsable($path) + { + if (substr($path, -4) !== '.zip') { + throw new UnsupportedPathException($path); + } + + if (!$this->isZipAvailable) { + throw new UnsupportedPathException($path, 'Zip extension is not available'); + } + } + + private function assertFileExistance($path, $directory) + { + if (!$directory->isFile($path)) { + throw new UnsupportedPathException($path); + } + } + + private function fetchFileNamesFromZipFile(\ZipArchive $zip) + { + $files = []; + + for ($fileIndex = 0; $fileIndex < $zip->numFiles; $fileIndex++) { + $fileName = $zip->getNameIndex($fileIndex); + if ($this->isDirectoryZipEntry($fileName) || !$fileName) { + continue; + } + + $files[] = $fileName; + } + + return $files; + } + + private function isDirectoryZipEntry($fileName) + { + return substr($fileName, -1) === '/'; + } + + private function extractFileIntoDirectory( + \ZipArchive $zip, + Filesystem\Directory\WriteInterface $writeDirectory, + $fileName + ) { + $destinationFilename = 'tmp-' . uniqid() . '-' . basename($fileName); + + $destinationFileWriter = $writeDirectory->openFile($destinationFilename); + $destinationFileWriter->write($zip->getFromName($fileName)); + $destinationFileWriter->close(); + + return $destinationFilename; + } +} diff --git a/app/code/Magento/ImportExport/Model/Import/Source/Zip.php b/app/code/Magento/ImportExport/Model/Import/Source/Zip.php index b7fafc43ca4ed..ff92e035cb0da 100644 --- a/app/code/Magento/ImportExport/Model/Import/Source/Zip.php +++ b/app/code/Magento/ImportExport/Model/Import/Source/Zip.php @@ -7,6 +7,8 @@ /** * Zip import adapter. + * + * @deprecated */ class Zip extends Csv { diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/AdapterTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/AdapterTest.php index 63a7d3c2078b6..b01f9df54f631 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/AdapterTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/AdapterTest.php @@ -5,33 +5,157 @@ */ namespace Magento\ImportExport\Test\Unit\Model\Import; -use Magento\ImportExport\Model\Import\Adapter as Adapter; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\ImportExport\Model\Import\Adapter; +use Magento\ImportExport\Model\Import\Source\FileFactory; +use Magento\ImportExport\Model\Import\Source\FileParser\CsvParserFactory; +use Magento\ImportExport\Model\Import\Source\FileParser\ParserFactoryInterface; +use Magento\ImportExport\Model\Import\Source\FileParser\ZipParserFactory; +use Magento\ImportExport\Test\Unit\Model\Import\Source\FileParser\FakeObjectManager; +use Magento\ImportExport\Test\Unit\Model\Import\Source\FileParser\FakeParser; +use Magento\ImportExport\Test\Unit\Model\Import\Source\FileParser\FakeParserFactory; class AdapterTest extends \PHPUnit_Framework_TestCase { - /** - * @var Adapter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $adapter; + public function testFactory() + { + $this->markTestSkipped('Skipped because factory method has static modifier'); + } - protected function setUp() + public function testFindAdapterFor() + { + $this->markTestSkipped('Skipped because findAdapterFor method has static modifier'); + } + + public function testWhenFileIsNotSupported_FileExtensionRelatedExceptionIsThrown() { - $this->adapter = $this->getMock( - \Magento\ImportExport\Model\Import\Adapter::class, - [], - [], - '', - false + $adapter = new Adapter( + $this->createFileFactory() ); + + $this->setExpectedException(LocalizedException::class, '\'xyz\' file extension is not supported'); + + $adapter->createSourceByPath('file.xyz'); } - public function testFactory() + public function testWhenFileIsNotSupportedAndFileNameHasNoExtension_LocalizedExceptionWithBasenameIsThrown() { - $this->markTestSkipped('Skipped because factory method has static modifier'); + $adapter = new Adapter( + $this->createFileFactory() + ); + + $this->setExpectedException(LocalizedException::class, '\'file\' file extension is not supported'); + + $adapter->createSourceByPath('file'); } - public function testFindAdapterFor() + public function testWhenFileAdapterIsNotSupported_FileExtensionRelatedExceptionIsThrown() { - $this->markTestSkipped('Skipped because findAdapterFor method has static modifier'); + $adapter = new Adapter( + $this->createFileFactory( + new ZipParserFactory( + $this->createTestFilesystem(), + new FakeParserFactory() + ) + ) + ); + + $this->setExpectedException(LocalizedException::class, '\'zip\' file extension is not supported'); + + $adapter->createSourceByPath('corrupted.zip'); + } + + public function testWhenParserIsAvailable_FileSourceIsReturned() + { + $adapter = new Adapter( + $this->createFileFactory( + new FakeParserFactory( + new FakeParser(['column1', 'column2']) + ) + ) + ); + + $source = $adapter->createSourceByPath('test.csv'); + $this->assertSame(['column1', 'column2'], $source->getColNames()); + } + + public function testWhenParserOptionsAreProvided_FileSourceIsReadCorrectly() + { + $adapter = new Adapter( + $this->createFileFactory( + new CsvParserFactory( + $this->createTestFilesystem(), + new FakeObjectManager() + ) + ) + ); + + $source = $adapter->createSourceByPath( + 'test_options.csv', + [ + 'delimiter' => '|', + 'enclosure' => ';' + ] + ); + + $this->assertSame(['column1', 'column2', 'column3'], $source->getColNames()); + } + + public function testWhenParserOptionIsProvidedAsString_FileSourceIsReadCorrectly() + { + $adapter = new Adapter( + $this->createFileFactory( + new CsvParserFactory( + $this->createTestFilesystem(), + new FakeObjectManager() + ) + ) + ); + + $source = $adapter->createSourceByPath( + 'test_options.csv', + '|' + ); + + $this->assertSame(['column1', ';column2;', 'column3'], $source->getColNames()); + } + + public function testWhenParserOptionIsProvidedAsNull_FileSourceIsReadCorrectly() + { + $adapter = new Adapter( + $this->createFileFactory( + new CsvParserFactory( + $this->createTestFilesystem(), + new FakeObjectManager() + ) + ) + ); + + $source = $adapter->createSourceByPath( + 'test.csv', + null + ); + + $this->assertSame(['column1', 'column2', 'column3'], $source->getColNames()); + } + + + private function createTestFilesystem() + { + return new Filesystem( + new DirectoryList(__DIR__ . '/Source/FileParser/_files'), + new Filesystem\Directory\ReadFactory(new Filesystem\DriverPool()), + new Filesystem\Directory\WriteFactory(new Filesystem\DriverPool()) + ); + } + + private function createFileFactory(ParserFactoryInterface $parserFactory = null) + { + return new FileFactory( + $parserFactory ?? new FakeParserFactory(), + new FakeObjectManager() + ); } } diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileFactoryTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileFactoryTest.php new file mode 100644 index 0000000000000..03268bc027993 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileFactoryTest.php @@ -0,0 +1,77 @@ +setExpectedException(UnsupportedPathException::class, 'Path "test.csv" is not supported'); + + $fileFactory = new FileFactory( + new FakeParserFactory(), + new FakeObjectManager() + ); + + $fileFactory->createFromFilePath('test.csv'); + } + + public function testGivenParserFactoryConfiguredWhenSourceIsCreatedByPathThenRightInstanceIsCreated() + { + $fileFactory = new FileFactory( + new FakeParserFactory(new FakeParser(['column1', 'column2'])), + new FakeObjectManager() + ); + + $this->assertSame( + ['column1', 'column2'], + $fileFactory->createFromFilePath('test.csv')->getColNames() + ); + } + + public function testWhenSourceIsCreatedByPathWithCsvOptionsThenOptionsArePassedToParser() + { + $objectManager = new FakeObjectManager(); + $filesystem = new Filesystem( + new DirectoryList(__DIR__ . '/FileParser/_files'), + new Filesystem\Directory\ReadFactory(new Filesystem\DriverPool()), + new Filesystem\Directory\WriteFactory(new Filesystem\DriverPool()) + ); + + $fileFactory = new FileFactory( + new CsvParserFactory( + $filesystem, + $objectManager + ), + $objectManager + ); + + $file = $fileFactory->createFromFilePath('test_options.csv', ['delimiter' => '|', 'enclosure' => ';']); + + $this->assertSame( + ['column1', 'column2', 'column3'], + $file->getColNames() + ); + } + + public function testWhenSourceIsCreatedWithParserThenFileIsCreated() + { + $fileFactory = new FileFactory(new FakeParserFactory(), new FakeObjectManager()); + $file = $fileFactory->createFromFileParser(new FakeParser(['column1', 'column2'])); + $this->assertSame(['column1', 'column2'], $file->getColNames()); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CompositeParserFactoryTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CompositeParserFactoryTest.php new file mode 100644 index 0000000000000..fff6800712061 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CompositeParserFactoryTest.php @@ -0,0 +1,62 @@ +setExpectedException(UnsupportedPathException::class, 'Path "file.csv" is not supported'); + + $compositeFactory = new CompositeParserFactory(); + $compositeFactory->create('file.csv'); + } + + public function testWhenPathIsNotSupportedByAnyFactoryThenNoParserIsCreated() + { + $this->setExpectedException(UnsupportedPathException::class, 'Path "file.zip" is not supported'); + + $compositeFactory = new CompositeParserFactory(); + $compositeFactory->addParserFactory(new FakeParserFactory(['file.csv' => new FakeParser()])); + $compositeFactory->create('file.zip'); + } + + public function testWhenPathIsSupportedByFirstFactoryThenParserIsReturned() + { + $expectedParser = new FakeParser(); + + $compositeFactory = new CompositeParserFactory(); + $compositeFactory->addParserFactory(new FakeParserFactory($expectedParser)); + $compositeFactory->addParserFactory(new FakeParserFactory(new FakeParser())); + + $this->assertSame($expectedParser, $compositeFactory->create('file.csv')); + } + + public function testWhenPathIsSupportedBySecondFactoryThenParserIsReturned() + { + $expectedParser = new FakeParser(); + + $compositeFactory = new CompositeParserFactory(); + $compositeFactory->addParserFactory(new FakeParserFactory()); + $compositeFactory->addParserFactory(new FakeParserFactory(['file.csv' => $expectedParser])); + + $this->assertSame($expectedParser, $compositeFactory->create('file.csv')); + } + + + public function testWhenFactoryIsProvidedViaConstructorThenParserIsReturned() + { + $expectedParser = new FakeParser(); + + $compositeFactory = new CompositeParserFactory([new FakeParserFactory($expectedParser)]); + + $this->assertSame($expectedParser, $compositeFactory->create('file.csv')); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CorruptedPathExceptionTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CorruptedPathExceptionTest.php new file mode 100644 index 0000000000000..abcb4a5a897b5 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CorruptedPathExceptionTest.php @@ -0,0 +1,40 @@ +assertEmpty($exception->getFileName()); + } + + public function testWhenFileNameIsProvidedThenFileNameCanBeRetrievedLater() + { + $exception = new CorruptedFileException('file.csv'); + + $this->assertSame('file.csv', $exception->getFileName()); + } + + public function testWhenMessageIsProvidedThenMessageCanBeRetrievedLater() + { + $exception = new CorruptedFileException('', 'My message'); + + $this->assertSame('My message', $exception->getMessage()); + } + + public function testWhenNoMessageIsProvidedThenMessageIsGeneratedFromPath() + { + $exception = new CorruptedFileException('file.csv'); + + $this->assertSame('File "file.csv" is corrupted', $exception->getMessage()); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CsvParserFactoryTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CsvParserFactoryTest.php new file mode 100644 index 0000000000000..17023016800b3 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CsvParserFactoryTest.php @@ -0,0 +1,114 @@ +setExpectedException(FileParser\UnsupportedPathException::class); + + $factory = $this->createCsvParserFactory(); + $factory->create('test.zip'); + } + + public function testWhenFileDoesNotExistThenNoParserIsCreated() + { + $this->setExpectedException( + \InvalidArgumentException::class, + 'File "non_existing_file.csv" does not exists' + ); + + $factory = $this->createCsvParserFactory(); + $factory->create('non_existing_file.csv'); + } + + public function testWhenFileIsNotAccesibleThenNoParserIsCreated() + { + $this->setExpectedException( + \InvalidArgumentException::class, + 'File "test.csv" does not exists' + ); + + $factory = $this->createCsvParserFactory(tempnam(sys_get_temp_dir(), 'non_created')); + $factory->create('test.csv'); + } + + public function testWhenValidFileIsProvidedThenParserIsCreated() + { + $factory = $this->createCsvParserFactory(); + + $this->assertCsvFile( + ['column1', 'column2', 'column3'], + $factory->create('test.csv') + ); + } + + public function testWhenAbsolutePathIsProvidedThenParserIsCreated() + { + $factory = $this->createCsvParserFactory(); + + $this->assertCsvFile( + ['column1', 'column2', 'column3'], + $factory->create(__DIR__ . '/_files/test.csv') + ); + } + + public function testWhenCustomDirectoryIsProvidedThenParserIsCreatedFromIt() + { + $factory = $this->createCsvParserFactory(); + + $this->assertCsvFile( + ['column1', 'column2'], + $factory->create('test.csv', ['directory_code' => DirectoryList::TMP]) + ); + } + + public function testWhenCustomCSVOptionsProvidedThenParserIsCreatedFromIt() + { + $factory = $this->createCsvParserFactory(); + + $this->assertCsvFile( + ['column1', 'column2', 'column3'], + $factory->create( + 'test_options.csv', + [ + 'delimiter' => '|', + 'enclosure' => ';' + ] + ) + ); + } + + private function createTestFilesystem($baseDirectory = null) + { + $baseDirectory = $baseDirectory ?? __DIR__ . '/_files'; + + return new Filesystem( + new DirectoryList($baseDirectory), + new Filesystem\Directory\ReadFactory(new Filesystem\DriverPool()), + new Filesystem\Directory\WriteFactory(new Filesystem\DriverPool()) + ); + } + + private function createCsvParserFactory($baseDirectory = null) + { + return new FileParser\CsvParserFactory( + $this->createTestFilesystem($baseDirectory), + new FakeObjectManager() + ); + } + + private function assertCsvFile($expectedColumns, FileParser\CsvParser $csvParser) + { + $this->assertSame($expectedColumns, $csvParser->getColumnNames()); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CsvParserTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CsvParserTest.php new file mode 100644 index 0000000000000..768485edca3af --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/CsvParserTest.php @@ -0,0 +1,175 @@ +setExpectedException(\InvalidArgumentException::class, 'CSV file should contain at least 1 row'); + + $this->createParser(new FakeFile([])); + } + + public function testWhenValidFileIsProvidedThenReadColumnsFromFileHeader() + { + $parser = $this->createParser(new FakeFile([ + 'column1,column2,column3', + 'row1value1,row1value2,row1value3', + ])); + + $this->assertSame(['column1', 'column2', 'column3'], $parser->getColumnNames()); + } + + public function testWhenValidFileIsProvidedThenRowsAreFetchedAndHeaderIsSkipped() + { + $parser = $this->createParser(new FakeFile([ + 'column1,column2,column3', + 'row1value1,row1value2,row1value3', + 'row2value1,row2value2,row2value3', + ])); + + $this->assertParsedFileContent( + [ + ['row1value1', 'row1value2', 'row1value3'], + ['row2value1', 'row2value2', 'row2value3'] + ], + $parser + ); + } + + public function testWhenEndOfFileIsReachedThenNoMoreRowsCanBeFetched() + { + $parser = $this->createParser(new FakeFile([ + 'column1,column2,column3', + 'row1value1,row1value2,row1value3', + 'row2value1,row2value2,row2value3', + ])); + + $this->skipRows(2, $parser); + + $this->assertFalse($parser->fetchRow()); + } + + public function testWhenFileWasAlreadyReadThenResetAllowsToReadItFromStart() + { + $parser = $this->createParser(new FakeFile([ + 'column1,column2,column3', + 'row1value1,row1value2,row1value3', + 'row2value1,row2value2,row2value3', + ])); + + $this->skipRows(2, $parser); + $parser->reset(); + + $this->assertParsedFileContent( + [ + ['row1value1', 'row1value2', 'row1value3'], + ['row2value1', 'row2value2', 'row2value3'] + ], + $parser + ); + } + + public function testWhenCustomDelimiterIsSpecifiedThenDataIsParsedUsingThisDelimiter() + { + $parser = $this->createParser( + new FakeFile([ + 'column1|column2|column3', + 'row1value1|row1value2|row1value3', + 'row2value1|row2value2|row2value3' + ]), + ['delimiter' => '|'] + ); + + $this->assertParsedFileContent( + [ + ['row1value1', 'row1value2', 'row1value3'], + ['row2value1', 'row2value2', 'row2value3'] + ], + $parser + ); + } + + public function testWhenFileHasMissingRowValuesThenFetchedRowValuesAreSameAsNumberOfColumns() + { + $parser = $this->createParser(new FakeFile([ + 'column1,column2,column3', + 'row1value1', + 'row2value1,row2value2,row2value3', + 'row3value1,row3value2', + 'row4value1,row4value2,row4value3,row4value4', + ])); + + $this->assertParsedFileContent( + [ + ['row1value1', '', ''], + ['row2value1', 'row2value2', 'row2value3'], + ['row3value1', 'row3value2', ''], + ['row4value1', 'row4value2', 'row4value3'] + ], + $parser + ); + } + + + public function testWhenNullPlaceholderIsSetThenFetchedRowValueIsReplacedWithNull() + { + $parser = $this->createParser( + new FakeFile([ + 'column1,column2,column3', + 'row1value1,row1value2,row1value3', + 'row2value1,row2value2,row2value3', + ]), + ['null' => 'row2value2'] + ); + + $this->assertParsedFileContent( + [ + ['row1value1', 'row1value2', 'row1value3'], + ['row2value1', null, 'row2value3'] + ], + $parser + ); + } + + public function testWhenParserIsDestroyedThenInternalFileDescriptorIsClosed() + { + $csvFile = new FakeFile([ + 'column1,column2,column3' + ]); + + $parser = $this->createParser($csvFile); + unset($parser); + + $this->assertFalse($csvFile->isOpen()); + } + + private function assertParsedFileContent($expectedCsvStructure, $parser) + { + $actualCsvStructure = []; + while ($row = $parser->fetchRow()) { + $actualCsvStructure[] = $row; + } + + $this->assertSame($expectedCsvStructure, $actualCsvStructure); + } + + private function skipRows($numberOfRows, FileParser\CsvParser $parser) + { + for ($i = 0; $i < $numberOfRows; $i++) { + $parser->fetchRow(); + } + } + + private function createParser($file, $options = []) + { + return new FileParser\CsvParser($file, $options); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeFile.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeFile.php new file mode 100644 index 0000000000000..f7abcc14d672f --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeFile.php @@ -0,0 +1,89 @@ +lines = $lines; + $this->pointer = 0; + $this->isOpen = true; + } + + public function isOpen() + { + return $this->isOpen; + } + + public function readCsv($length = 0, $delimiter = ',', $enclosure = '"', $escape = '\\') + { + if ($this->isEndOfFile()) { + return false; + } + + return str_getcsv($this->readFileLine($length), $delimiter, $enclosure, $escape); + } + + private function readFileLine($length) + { + return $this->truncateLineToLength($this->lines[$this->pointer++], $length); + } + + private function isEndOfFile() + { + return !isset($this->lines[$this->pointer]); + } + + private function truncateLineToLength($line, $length) + { + if ($length > 0 && strlen($line) > $length) { + $line = substr($line, 0, $length); + } + return $line; + } + public function close() + { + $this->isOpen = false; + } + + public function read($length) + { + return ''; + } + + public function readLine($length, $ending = null) + { + return $this->readFileLine($length); + } + + public function tell() + { + return $this->pointer; + } + + public function seek($length, $whence = SEEK_SET) + { + $this->pointer = $length; + } + + public function eof() + { + return $this->isEndOfFile(); + } + + public function stat() + { + return []; + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeObjectManager.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeObjectManager.php new file mode 100644 index 0000000000000..537db6d710f9a --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeObjectManager.php @@ -0,0 +1,61 @@ +resolveConstructorArguments($type, $arguments); + return new $type(...$arguments); + } + + public function get($type) + { + if (!isset($this->instances[$type])) { + $this->instances[$type] = $this->create($type); + } + + return $this->instances[$type]; + } + + public function configure(array $configuration) + { + // Fake object manager is not configurable + } + + private function resolveConstructorArguments($type, array $arguments) + { + $constructorSignature = new \ReflectionMethod($type, '__construct'); + $resolvedArguments = []; + foreach ($constructorSignature->getParameters() as $parameter) { + $arguments = $this->assertParameterValue($arguments, $parameter); + + $resolvedArguments[] = $arguments[$parameter->getName()] ?? $parameter->getDefaultValue(); + } + + return $resolvedArguments; + } + + private function assertParameterValue(array $arguments, $parameter): array + { + if (!isset($arguments[$parameter->getName()]) && !$parameter->isDefaultValueAvailable()) { + new \RuntimeException( + sprintf( + 'Cannot instantiate %s without default value for constructor argument $%s', + $parameter->getDeclaringClass()->getName(), + $parameter->getName() + ) + ); + } + return $arguments; + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeParser.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeParser.php new file mode 100644 index 0000000000000..f27e4d988470c --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeParser.php @@ -0,0 +1,48 @@ +columns = $columns; + $this->rows = $rows; + $this->position = 0; + } + + + public function getColumnNames() + { + return $this->columns; + } + + public function fetchRow() + { + if ($this->isEndOfRows()) { + return false; + } + + return $this->rows[$this->position++]; + } + + public function reset() + { + $this->position = 0; + } + + private function isEndOfRows() + { + return !isset($this->rows[$this->position]); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeParserFactory.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeParserFactory.php new file mode 100644 index 0000000000000..4f86c88ab3663 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/FakeParserFactory.php @@ -0,0 +1,54 @@ +parser = $parser; + } + + public function create($path, array $options = []) + { + if ($this->isParserMap()) { + return $this->findParserInMap($path); + } + + return $this->parser; + } + + private function findParserInMap($path) + { + $path = $this->trimTemporaryFilePrefix($path); + + if (!isset($this->parser[$path])) { + throw new UnsupportedPathException($path); + } + + return $this->parser[$path]; + } + + private function isParserMap() + { + return is_array($this->parser); + } + + private function trimTemporaryFilePrefix($path) + { + if (strpos($path, 'tmp-') === 0) { + $path = preg_replace('/tmp-[a-z0-9]+-/i', '', $path); + } + + return $path; + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/UnsupportedPathExceptionTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/UnsupportedPathExceptionTest.php new file mode 100644 index 0000000000000..c6a05d0f47888 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/UnsupportedPathExceptionTest.php @@ -0,0 +1,40 @@ +assertEmpty($exception->getPath()); + } + + public function testWhenFileNameIsProvidedThenFileNameCanBeRetrievedLater() + { + $exception = new UnsupportedPathException('file.csv'); + + $this->assertSame('file.csv', $exception->getPath()); + } + + public function testWhenMessageIsProvidedThenMessageCanBeRetrievedLater() + { + $exception = new UnsupportedPathException('', 'My message'); + + $this->assertSame('My message', $exception->getMessage()); + } + + public function testWhenNoMessageIsProvidedThenMessageIsGeneratedFromPath() + { + $exception = new UnsupportedPathException('file.csv'); + + $this->assertSame('Path "file.csv" is not supported', $exception->getMessage()); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/ZipParserFactoryTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/ZipParserFactoryTest.php new file mode 100644 index 0000000000000..c968a9202c179 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/ZipParserFactoryTest.php @@ -0,0 +1,151 @@ +setExpectedException(FileParser\UnsupportedPathException::class, 'Zip extension is not available'); + + $parser = new FileParser\ZipParserFactory( + $this->createTestFilesystem(), + new FakeParserFactory(), + false + ); + + $parser->create('file.zip'); + } + + public function testWhenCsvFileIsProvidedThenParserIsNotCreated() + { + $this->setExpectedException(FileParser\UnsupportedPathException::class, 'Path "file.csv" is not supported'); + + $parser = $this->createZipParserFactory(); + $parser->create('file.csv'); + } + + public function testWhenCorruptedZipFileIsProvidedThenParserIsNotCreated() + { + $this->setExpectedException(FileParser\CorruptedFileException::class); + + $parser = $this->createZipParserFactory(); + $parser->create('corrupted.zip'); + } + + public function testWhenZipFileDoesNotExistsThenParserIsNotCreated() + { + $this->setExpectedException(FileParser\UnsupportedPathException::class, 'Path "unknown.zip" is not supported'); + + $parser = $this->createZipParserFactory(); + $parser->create('unknown.zip'); + } + + + public function testWhenEmptyZipFileIsProvidedThenParserIsNotCreated() + { + $this->setExpectedException(FileParser\UnsupportedPathException::class, 'Path "empty.zip" is not supported'); + + $parserFactory = $this->createZipParserFactory(); + $parserFactory->create('empty.zip'); + } + + public function testWhenProperZipFileIsProvidedThenFirstFileIsParsed() + { + $expectedParser = new FakeParser(); + + $parserFactory = new FileParser\ZipParserFactory( + $this->createTestFilesystem(), + new FakeParserFactory([ + 'test.csv' => $expectedParser + ]) + ); + + $this->assertSame( + $expectedParser, + $parserFactory->create('complete.zip') + ); + } + + public function testWhenProperZipFileWithAbsolutePathIsProvidedThenFirstFileIsParsed() + { + $expectedParser = new FakeParser(); + + $parserFactory = $this->createZipParserFactory( + new FakeParserFactory([ + 'test.csv' => $expectedParser + ]) + ); + + $this->assertSame( + $expectedParser, + $parserFactory->create(__DIR__ . '/_files/complete.zip') + ); + } + + public function testWhenProperZipFileIsProvidedThenSecondFileIsParsed() + { + $expectedParser = new FakeParser(); + + $parserFactory = $this->createZipParserFactory( + new FakeParserFactory([ + 'test.tsv' => $expectedParser + ]) + ); + + $this->assertSame( + $expectedParser, + $parserFactory->create('complete.zip') + ); + } + + public function testWhenProperZipFileIsProvidedWithOptionsThenCustomCsvFileIsParsed() + { + $parserFactory = $this->createZipParserFactory( + new FileParser\CsvParserFactory( + $this->createTestFilesystem(), + new FakeObjectManager() + ) + ); + + $parser = $parserFactory->create( + 'custom_option.zip', + [ + 'delimiter' => '|', + 'enclosure' => ';' + ] + ); + + $this->assertSame( + ['column1', 'column2', 'column3'], + $parser->getColumnNames() + ); + } + + private function createTestFilesystem() + { + return new Filesystem( + new DirectoryList(__DIR__ . '/_files'), + new Filesystem\Directory\ReadFactory(new Filesystem\DriverPool()), + new Filesystem\Directory\WriteFactory(new Filesystem\DriverPool()) + ); + } + + private function createZipParserFactory($parserFactory = null): FileParser\ZipParserFactory + { + $parser = new FileParser\ZipParserFactory( + $this->createTestFilesystem(), + $parserFactory ?: new FakeParserFactory() + ); + + return $parser; + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/complete.zip b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/complete.zip new file mode 100644 index 0000000000000..b4c17bb771e8a Binary files /dev/null and b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/complete.zip differ diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/corrupted.zip b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/corrupted.zip new file mode 100644 index 0000000000000..567e7db20f304 Binary files /dev/null and b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/corrupted.zip differ diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/custom_option.zip b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/custom_option.zip new file mode 100644 index 0000000000000..98f29d6425ce8 Binary files /dev/null and b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/custom_option.zip differ diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/empty.zip b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/empty.zip new file mode 100644 index 0000000000000..15cb0ecb3e219 Binary files /dev/null and b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/empty.zip differ diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/test.csv b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/test.csv new file mode 100644 index 0000000000000..3acca666172ea --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/test.csv @@ -0,0 +1 @@ +column1,column2,column3 diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/test_options.csv b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/test_options.csv new file mode 100644 index 0000000000000..e1a3763d1e399 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/test_options.csv @@ -0,0 +1 @@ +column1|;column2;|column3 diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/var/tmp/test.csv b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/var/tmp/test.csv new file mode 100644 index 0000000000000..18288adde1528 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileParser/_files/var/tmp/test.csv @@ -0,0 +1 @@ +column1,column2 diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileTest.php new file mode 100644 index 0000000000000..2c9ca0641cb14 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Source/FileTest.php @@ -0,0 +1,110 @@ +assertSame(['column1', 'column2'], $file->getColNames()); + } + + public function testWhenEmptyFileParserIsProvidedThenIteratorIsNotValid() + { + $file = new File(new FakeParser( + ['column1', 'column2'] + )); + $file->rewind(); + $this->assertFalse($file->valid()); + } + + public function testWhenSomeDataInFileParserIsProvidedThenIteratorIsValid() + { + $file = new File(new FakeParser( + ['column1', 'column2'], + [ + ['value1', 'value2'] + ] + )); + + $file->rewind(); + $this->assertTrue($file->valid()); + } + + public function testWhenSomeDataInFileParserIsProvidedThenIteratorRowIsReturned() + { + $file = new File(new FakeParser( + ['column1', 'column2'], + [ + ['value1', 'value2'] + ] + )); + + $file->rewind(); + + $this->assertSame( + [ + 'column1' => 'value1', + 'column2' => 'value2' + ], + $file->current() + ); + } + + public function testWhenSpecificPositionIsSetThenProperIteratorRowIsReturned() + { + $file = new File(new FakeParser( + ['column1', 'column2'], + [ + ['wrong', 'wrong'], + ['correct1', 'correct2'], + ['wrong', 'wrong'], + ] + )); + + $file->rewind(); + $file->seek(1); + + $this->assertSame( + [ + 'column1' => 'correct1', + 'column2' => 'correct2' + ], + $file->current() + ); + } + + public function testWhenIteratorIsRewindedThenParserRestarts() + { + $file = new File(new FakeParser( + ['column1', 'column2'], + [ + ['value1.1', 'value2.1'], + ['value1.2', 'value2.2'], + ['value1.3', 'value2.3'] + ] + )); + + $file->rewind(); + $file->next(); + $file->next(); + $file->rewind(); + + $this->assertSame( + [ + 'column1' => 'value1.1', + 'column2' => 'value2.1' + ], + $file->current() + ); + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/_files/test.csv b/app/code/Magento/ImportExport/Test/Unit/Model/Import/_files/test.csv new file mode 100644 index 0000000000000..ef071b0dab0bf --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/_files/test.csv @@ -0,0 +1,4 @@ +column1,"column2" +value1,value2 +3,4 +5 diff --git a/app/code/Magento/ImportExport/etc/di.xml b/app/code/Magento/ImportExport/etc/di.xml index 47acf7a356d93..9b8d959514dde 100644 --- a/app/code/Magento/ImportExport/etc/di.xml +++ b/app/code/Magento/ImportExport/etc/di.xml @@ -10,6 +10,8 @@ + + @@ -17,4 +19,15 @@ + + + + + Magento\ImportExport\Model\Import\Source\FileParser\CsvParserFactory + + Magento\ImportExport\Model\Import\Source\FileParser\ZipParserFactory\Proxy + + + +