From 159decf760d8c8b094f45663812db12f75c9c32b Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sun, 3 Nov 2019 23:16:17 +0100 Subject: [PATCH] implement spreadsheet io test --- .../IO/Excel/ExcelDatabaseMapperTest.php | 496 +++++++++++++++++- tests/Utils/IO/Excel/backup.db | Bin 0 -> 16384 bytes tests/Utils/IO/Excel/insert.ods | Bin 0 -> 10737 bytes tests/Utils/IO/Excel/insert.xls | Bin 0 -> 8704 bytes tests/Utils/IO/Excel/insert.xlsx | Bin 0 -> 6996 bytes tests/Utils/IO/Excel/update.ods | Bin 0 -> 10279 bytes tests/Utils/IO/Excel/update.xls | Bin 0 -> 8192 bytes tests/Utils/IO/Excel/update.xlsx | Bin 0 -> 6127 bytes 8 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 tests/Utils/IO/Excel/backup.db create mode 100644 tests/Utils/IO/Excel/insert.ods create mode 100644 tests/Utils/IO/Excel/insert.xls create mode 100644 tests/Utils/IO/Excel/insert.xlsx create mode 100644 tests/Utils/IO/Excel/update.ods create mode 100644 tests/Utils/IO/Excel/update.xls create mode 100644 tests/Utils/IO/Excel/update.xlsx diff --git a/tests/Utils/IO/Excel/ExcelDatabaseMapperTest.php b/tests/Utils/IO/Excel/ExcelDatabaseMapperTest.php index a57ae8b24..0626eb1ab 100644 --- a/tests/Utils/IO/Excel/ExcelDatabaseMapperTest.php +++ b/tests/Utils/IO/Excel/ExcelDatabaseMapperTest.php @@ -14,13 +14,505 @@ declare(strict_types=1); namespace phpOMS\tests\Utils\IO\Excel; +use phpOMS\DataStorage\Database\Connection\SQLiteConnection; +use phpOMS\DataStorage\Database\Query\Builder; +use phpOMS\Utils\IO\Excel\ExcelDatabaseMapper; +use tests\Autoloader; +use phpOMS\Utils\StringUtils; + /** * @internal */ class ExcelDatabaseMapperTest extends \PHPUnit\Framework\TestCase { - public function testPlaceholder() : void + protected $sqlite; + + protected function setUp() : void { - self::markTestIncomplete(); + if (!\extension_loaded('pdo_sqlite')) { + $this->markTestSkipped( + 'The SQLite extension is not available.' + ); + + return; + } + + if (\file_exists(__DIR__ . '/spreadsheet.db')) { + \unlink(__DIR__ . '/spreadsheet.db'); + } + + \copy(__DIR__ . '/backup.db', __DIR__ . '/spreadsheet.db'); + + $this->sqlite = new SQLiteConnection(['db' => 'sqlite', 'database' => __DIR__ . '/spreadsheet.db']); + } + + protected function tearDown(): void + { + if (\file_exists(__DIR__ . '/spreadsheet.db')) { + \unlink(__DIR__ . '/spreadsheet.db'); + } + } + + public function testInsertOds() : void + { + Autoloader::addPath(__DIR__ . '/../../../../../Resources/'); + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.ods'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + } + + public function testInsertXls() : void + { + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.xls'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + } + + public function testInsertXlsx() : void + { + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.xlsx'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + } + + public function testUpdateOds() : void + { + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.ods'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/update.ods'); + $mapper->update(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '9.1', 'bool' => '1', 'varchar' => 'Line 2 updated', 'datetime' => '43831'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '9.123', 'bool' => '0', 'varchar' => 'Line 4 updated', 'datetime' => '43831'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '9.1', 'bool' => '1', 'varchar' => 'Line 2 updated', 'datetime' => '43831'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '9.123', 'bool' => '0', 'varchar' => 'Line 4 updated', 'datetime' => '43831'], + ], + $data + ); + } + + public function testUpdateXls() : void + { + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.xls'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/update.xls'); + $mapper->update(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '9.1', 'bool' => '1', 'varchar' => 'Line 2 updated', 'datetime' => '43831'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '9.123', 'bool' => '0', 'varchar' => 'Line 4 updated', 'datetime' => '43831'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '9.1', 'bool' => '1', 'varchar' => 'Line 2 updated', 'datetime' => '43831'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '9.123', 'bool' => '0', 'varchar' => 'Line 4 updated', 'datetime' => '43831'], + ], + $data + ); + } + + public function testUpdateXlsx() : void + { + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.xlsx'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/update.xlsx'); + $mapper->update(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '9.1', 'bool' => '1', 'varchar' => 'Line 2 updated', 'datetime' => '43831'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '9.123', 'bool' => '0', 'varchar' => 'Line 4 updated', 'datetime' => '43831'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '9.1', 'bool' => '1', 'varchar' => 'Line 2 updated', 'datetime' => '43831'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '9.123', 'bool' => '0', 'varchar' => 'Line 4 updated', 'datetime' => '43831'], + ], + $data + ); + } + + public function testSelectOds() : void + { + if (\file_exists(__DIR__ . '/select.ods')) { + \unlink(__DIR__ . '/select.ods'); + } + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.ods'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/select.ods'); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('int', 'decimal', 'bool', 'varchar', 'datetime')->from('insert_1'); + + $mapper->select([$builder]); + + self::assertTrue($this->compareSelectInsertSheet(__DIR__ . '/select.ods', __DIR__ . '/insert.ods')); + + if (\file_exists(__DIR__ . '/select.ods')) { + \unlink(__DIR__ . '/select.ods'); + } + } + + public function testSelectXls() : void + { + if (\file_exists(__DIR__ . '/select.xls')) { + \unlink(__DIR__ . '/select.xls'); + } + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.xls'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/select.xls'); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('int', 'decimal', 'bool', 'varchar', 'datetime')->from('insert_1'); + + $mapper->select([$builder]); + + self::assertTrue($this->compareSelectInsertSheet(__DIR__ . '/select.xls', __DIR__ . '/insert.xls')); + + if (\file_exists(__DIR__ . '/select.xls')) { + \unlink(__DIR__ . '/select.xls'); + } + } + + public function testSelectXlsx() : void + { + if (\file_exists(__DIR__ . '/select.xlsx')) { + \unlink(__DIR__ . '/select.xlsx'); + } + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/insert.xlsx'); + $mapper->insert(); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_1.*')->from('insert_1')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('insert_2.*')->from('insert_2')->execute()->fetchAll(\PDO::FETCH_ASSOC); + self::assertEquals( + [ + ['id' => '1', 'int' => '2', 'decimal' => '2.0', 'bool' => '1', 'varchar' => 'Line 1', 'datetime' => '43631'], + ['id' => '2', 'int' => '4', 'decimal' => '2.1', 'bool' => '0', 'varchar' => 'Line 2', 'datetime' => '42170'], + ['id' => '3', 'int' => '6', 'decimal' => '2.12', 'bool' => '1', 'varchar' => 'Line 3', 'datetime' => '40426'], + ['id' => '4', 'int' => '8', 'decimal' => '2.123', 'bool' => '0', 'varchar' => 'Line 4', 'datetime' => '40428'], + ], + $data + ); + + $mapper = new ExcelDatabaseMapper($this->sqlite, __DIR__ . '/select.xlsx'); + + $builder = new Builder($this->sqlite, true); + $data = $builder->select('int', 'decimal', 'bool', 'varchar', 'datetime')->from('insert_1'); + + $mapper->select([$builder]); + + self::assertTrue($this->compareSelectInsertSheet(__DIR__ . '/select.xlsx', __DIR__ . '/insert.xlsx')); + + if (\file_exists(__DIR__ . '/select.xlsx')) { + \unlink(__DIR__ . '/select.xlsx'); + } + } + + private function compareSelectInsertSheet(string $pathSelect, string $pathInsert) : bool + { + $reader1 = null; + if (StringUtils::endsWith($pathSelect, '.xlsx')) { + $reader1 = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); + } elseif (StringUtils::endsWith($pathSelect, '.ods')) { + $reader1 = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); + } else { + $reader1 = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); + } + + $reader2 = null; + if (StringUtils::endsWith($pathInsert, '.xlsx')) { + $reader2 = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); + } elseif (StringUtils::endsWith($pathInsert, '.ods')) { + $reader2 = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); + } else { + $reader2 = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); + } + + $reader1->setReadDataOnly(true); + $reader2->setReadDataOnly(true); + + $sheet1 = $reader1->load($pathSelect); + $sheet2 = $reader2->load($pathInsert); + + $tables = $sheet1->getSheetCount(); + for ($i = 0; $i < $tables; ++$i) { + $sheet1->setActiveSheetIndex($i); + $sheet2->setActiveSheetIndex($i); + + $workSheet1 = $sheet1->getSheet($i); + $workSheet2 = $sheet2->getSheet($i); + + $titles = []; + + // get column titles + $column = 1; + while (!empty($value = $workSheet1->getCellByColumnAndRow($column, 1)->getCalculatedValue())) { + $titles[] = $value; + ++$column; + } + + $columns = \count($titles); + + $line = 1; + while (!empty($row = $workSheet1->getCellByColumnAndRow(1, $line)->getCalculatedValue())) { + for ($j = 1; $j <= $columns; ++$j) { + if (($v1 = $workSheet1->getCellByColumnAndRow($j, $line)->getCalculatedValue()) != ($v2 = $workSheet2->getCellByColumnAndRow($j, $line)->getCalculatedValue())) { + return false; + } + } + + ++$line; + } + } + + return true; } } diff --git a/tests/Utils/IO/Excel/backup.db b/tests/Utils/IO/Excel/backup.db new file mode 100644 index 0000000000000000000000000000000000000000..4625a267725d85341df6802c9daccf51a0b6a935 GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/tests/Utils/IO/Excel/insert.xlsx b/tests/Utils/IO/Excel/insert.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..03e0087f04658d00cc8ea88541a99a5416cc89eb GIT binary patch literal 6996 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

d?&*#|YaO>am={)Rn?+3E7 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