From 446c44768520eb8710b1238ae226c1a2cac0ce3a Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:14:40 +0400 Subject: [PATCH 1/7] [#482] Expand migration package unit test coverage --- tests/Unit/Migration/MigrationManagerTest.php | 263 ++++++++++++++++++ tests/Unit/Migration/MigrationTableTest.php | 42 +++ .../Templates/MigrationTemplateTest.php | 43 +++ 3 files changed, 348 insertions(+) create mode 100644 tests/Unit/Migration/MigrationManagerTest.php create mode 100644 tests/Unit/Migration/MigrationTableTest.php create mode 100644 tests/Unit/Migration/Templates/MigrationTemplateTest.php diff --git a/tests/Unit/Migration/MigrationManagerTest.php b/tests/Unit/Migration/MigrationManagerTest.php new file mode 100644 index 000000000..a6b5fa650 --- /dev/null +++ b/tests/Unit/Migration/MigrationManagerTest.php @@ -0,0 +1,263 @@ +has('database')) { + config()->import(new Setup('config', 'database', true)); + } + + config()->set('database.default', 'sqlite'); + + $this->migrationDir = base_dir() . DS . 'migrations'; + if (!is_dir($this->migrationDir)) { + mkdir($this->migrationDir, 0777, true); + } + + $files = glob($this->migrationDir . DS . '*.php'); + $this->existingMigrationFiles = is_array($files) ? $files : []; + } + + public function tearDown(): void + { + $files = glob($this->migrationDir . DS . '*.php'); + $currentFiles = is_array($files) ? $files : []; + $createdByTest = array_diff($currentFiles, $this->existingMigrationFiles); + + foreach ($createdByTest as $file) { + if (is_file($file)) { + @unlink($file); + } + } + + Database::execute('DROP TABLE IF EXISTS alpha_table'); + Database::execute('DROP TABLE IF EXISTS beta_table'); + Database::execute('DROP TABLE IF EXISTS gamma_table'); + Database::execute('DROP TABLE IF EXISTS ' . MigrationTable::TABLE); + + parent::tearDown(); + } + + public function testConstructorThrowsWhenMigrationDirectoryIsMissing(): void + { + if (is_dir($this->migrationDir)) { + @rmdir($this->migrationDir); + } + + $this->expectException(FileSystemException::class); + $this->expectExceptionMessage('The directory ' . $this->migrationDir . ' does not exists.'); + + try { + new MigrationManager(); + } finally { + if (!is_dir($this->migrationDir)) { + mkdir($this->migrationDir, 0777, true); + } + } + } + + public function testGenerateMigrationThrowsForUnsupportedAction(): void + { + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('The action `sync`, is not supported'); + + $manager->generateMigration('users', 'sync'); + } + + public function testGenerateMigrationCreatesFileAndReturnsMigrationName(): void + { + $manager = new MigrationManager(); + + $migrationName = $manager->generateMigration('Users', 'create'); + + $this->assertMatchesRegularExpression('/^create_table_users_\d+$/', $migrationName); + $this->assertFileExists($this->migrationDir . DS . $migrationName . '.php'); + } + + public function testApplyMigrationsThrowsForUnsupportedDriver(): void + { + $manager = new MigrationManager(); + + $db = Mockery::mock(Database::class); + $db->shouldReceive('getConfigs')->andReturn(['driver' => 'sqlserver']); + $this->setPrivateProperty($manager, 'db', $db); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('The driver `sqlserver` is not supported.'); + + $manager->applyMigrations(MigrationManager::UPGRADE); + } + + public function testApplyMigrationsThrowsForWrongDirection(): void + { + $manager = new MigrationManager(); + + $db = Mockery::mock(Database::class); + $db->shouldReceive('getConfigs')->andReturn(['driver' => 'sqlite']); + $this->setPrivateProperty($manager, 'db', $db); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Migration direction can only be [up] or [down]'); + + $manager->applyMigrations('sideways'); + } + + public function testApplyMigrationsUpgradeRunsPendingMigrationsInAscendingOrder(): void + { + $this->createMigrationsTable(); + $this->createValidMigrationFile('create_table_alpha_table_1001', 'alpha_table'); + $this->createValidMigrationFile('create_table_beta_table_1002', 'beta_table'); + + $manager = new MigrationManager(); + $migrated = $manager->applyMigrations(MigrationManager::UPGRADE); + + $this->assertSame(2, $migrated); + $this->assertTrue($this->tableExists('alpha_table')); + $this->assertTrue($this->tableExists('beta_table')); + + $entries = Database::query('SELECT migration FROM ' . MigrationTable::TABLE . ' ORDER BY migration ASC'); + $this->assertCount(2, $entries); + $this->assertSame('create_table_alpha_table_1001', $entries[0]['migration']); + $this->assertSame('create_table_beta_table_1002', $entries[1]['migration']); + } + + public function testApplyMigrationsUpgradeThrowsWhenNothingToMigrate(): void + { + $this->createMigrationsTable(); + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Nothing to migrate'); + + $manager->applyMigrations(MigrationManager::UPGRADE); + } + + public function testApplyMigrationsUpgradeThrowsForInvalidMigrationClass(): void + { + $this->createMigrationsTable(); + $this->createInvalidMigrationFile('create_table_gamma_table_1003'); + + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Migration class `create_table_gamma_table_1003` must extend QtMigration'); + + $manager->applyMigrations(MigrationManager::UPGRADE); + } + + public function testApplyMigrationsDowngradeThrowsWhenMigrationTableMissing(): void + { + Database::execute('DROP TABLE IF EXISTS ' . MigrationTable::TABLE); + + $manager = new MigrationManager(); + + $this->expectException(\Quantum\Database\Exceptions\DatabaseException::class); + $this->expectExceptionMessage('The table `migrations` does not exists'); + + $manager->applyMigrations(MigrationManager::DOWNGRADE); + } + + public function testApplyMigrationsDowngradeThrowsWhenNoEntriesExist(): void + { + Database::execute('CREATE TABLE IF NOT EXISTS ' . MigrationTable::TABLE . ' ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + migration VARCHAR(255), + applied_at DATETIME DEFAULT CURRENT_TIMESTAMP + )'); + + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Nothing to migrate'); + + $manager->applyMigrations(MigrationManager::DOWNGRADE); + } + + public function testApplyMigrationsDowngradeRespectsStepAndOrder(): void + { + $this->createMigrationsTable(); + $this->createValidMigrationFile('create_table_alpha_table_2001', 'alpha_table'); + $this->createValidMigrationFile('create_table_beta_table_2002', 'beta_table'); + + $manager = new MigrationManager(); + $manager->applyMigrations(MigrationManager::UPGRADE); + + $migrated = $manager->applyMigrations(MigrationManager::DOWNGRADE, 1); + $this->assertSame(1, $migrated); + + $this->assertTrue($this->tableExists('alpha_table')); + $this->assertFalse($this->tableExists('beta_table')); + + $entries = Database::query('SELECT migration FROM ' . MigrationTable::TABLE . ' ORDER BY migration ASC'); + $this->assertCount(1, $entries); + $this->assertSame('create_table_alpha_table_2001', $entries[0]['migration']); + } + + private function createValidMigrationFile(string $className, string $tableName): void + { + $content = "migrationDir . DS . $className . '.php', $content); + } + + private function createInvalidMigrationFile(string $className): void + { + $content = "migrationDir . DS . $className . '.php', $content); + } + + private function tableExists(string $table): bool + { + try { + Database::query('SELECT 1 FROM ' . $table . ' LIMIT 1'); + } catch (\Exception $e) { + return false; + } + + return true; + } + + private function createMigrationsTable(): void + { + Database::execute('CREATE TABLE IF NOT EXISTS ' . MigrationTable::TABLE . ' ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + migration VARCHAR(255), + applied_at DATETIME DEFAULT CURRENT_TIMESTAMP + )'); + } +} diff --git a/tests/Unit/Migration/MigrationTableTest.php b/tests/Unit/Migration/MigrationTableTest.php new file mode 100644 index 000000000..948334415 --- /dev/null +++ b/tests/Unit/Migration/MigrationTableTest.php @@ -0,0 +1,42 @@ +shouldReceive('addColumn')->once()->with('id', Type::INT, 11)->andReturnSelf(); + $table->shouldReceive('autoIncrement')->once()->andReturnSelf(); + $table->shouldReceive('addColumn')->once()->with('migration', Type::VARCHAR, 255)->andReturnSelf(); + $table->shouldReceive('addColumn')->once()->with('applied_at', Type::TIMESTAMP)->andReturnSelf(); + $table->shouldReceive('default')->once()->with('CURRENT_TIMESTAMP', false)->andReturnSelf(); + + $tableFactory = Mockery::mock(TableFactory::class); + $tableFactory->shouldReceive('create')->once()->with(MigrationTable::TABLE)->andReturn($table); + + $migrationTable = new MigrationTable(); + $migrationTable->up($tableFactory); + + $this->assertTrue(true); + } + + public function testDownDropsMigrationTable(): void + { + $tableFactory = Mockery::mock(TableFactory::class); + $tableFactory->shouldReceive('drop')->once()->with(MigrationTable::TABLE)->andReturn(true); + + $migrationTable = new MigrationTable(); + $migrationTable->down($tableFactory); + + $this->assertTrue(true); + } +} diff --git a/tests/Unit/Migration/Templates/MigrationTemplateTest.php b/tests/Unit/Migration/Templates/MigrationTemplateTest.php new file mode 100644 index 000000000..e8a382b6f --- /dev/null +++ b/tests/Unit/Migration/Templates/MigrationTemplateTest.php @@ -0,0 +1,43 @@ +assertStringContainsString('class Create_table_users_1001 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->create('users')", $template); + $this->assertStringContainsString("\$tableFactory->drop('users')", $template); + } + + public function testAlterTemplateContainsExpectedOperations(): void + { + $template = MigrationTemplate::alter('alter_table_users_1002', 'users'); + + $this->assertStringContainsString('class Alter_table_users_1002 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->get('users')", $template); + } + + public function testRenameTemplateContainsExpectedOperations(): void + { + $template = MigrationTemplate::rename('rename_table_users_1003', 'users'); + + $this->assertStringContainsString('class Rename_table_users_1003 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->rename('users', \$newName)", $template); + $this->assertStringContainsString("\$tableFactory->rename(\$newName, 'users')", $template); + } + + public function testDropTemplateContainsExpectedOperations(): void + { + $template = MigrationTemplate::drop('drop_table_users_1004', 'users'); + + $this->assertStringContainsString('class Drop_table_users_1004 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->drop('users')", $template); + } +} From bfc8bb7ad163dd6969f0964216d2c50c06b27a0a Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:33:12 +0400 Subject: [PATCH 2/7] Revert "[#482] Expand migration package unit test coverage" This reverts commit 446c44768520eb8710b1238ae226c1a2cac0ce3a. --- tests/Unit/Migration/MigrationManagerTest.php | 263 ------------------ tests/Unit/Migration/MigrationTableTest.php | 42 --- .../Templates/MigrationTemplateTest.php | 43 --- 3 files changed, 348 deletions(-) delete mode 100644 tests/Unit/Migration/MigrationManagerTest.php delete mode 100644 tests/Unit/Migration/MigrationTableTest.php delete mode 100644 tests/Unit/Migration/Templates/MigrationTemplateTest.php diff --git a/tests/Unit/Migration/MigrationManagerTest.php b/tests/Unit/Migration/MigrationManagerTest.php deleted file mode 100644 index a6b5fa650..000000000 --- a/tests/Unit/Migration/MigrationManagerTest.php +++ /dev/null @@ -1,263 +0,0 @@ -has('database')) { - config()->import(new Setup('config', 'database', true)); - } - - config()->set('database.default', 'sqlite'); - - $this->migrationDir = base_dir() . DS . 'migrations'; - if (!is_dir($this->migrationDir)) { - mkdir($this->migrationDir, 0777, true); - } - - $files = glob($this->migrationDir . DS . '*.php'); - $this->existingMigrationFiles = is_array($files) ? $files : []; - } - - public function tearDown(): void - { - $files = glob($this->migrationDir . DS . '*.php'); - $currentFiles = is_array($files) ? $files : []; - $createdByTest = array_diff($currentFiles, $this->existingMigrationFiles); - - foreach ($createdByTest as $file) { - if (is_file($file)) { - @unlink($file); - } - } - - Database::execute('DROP TABLE IF EXISTS alpha_table'); - Database::execute('DROP TABLE IF EXISTS beta_table'); - Database::execute('DROP TABLE IF EXISTS gamma_table'); - Database::execute('DROP TABLE IF EXISTS ' . MigrationTable::TABLE); - - parent::tearDown(); - } - - public function testConstructorThrowsWhenMigrationDirectoryIsMissing(): void - { - if (is_dir($this->migrationDir)) { - @rmdir($this->migrationDir); - } - - $this->expectException(FileSystemException::class); - $this->expectExceptionMessage('The directory ' . $this->migrationDir . ' does not exists.'); - - try { - new MigrationManager(); - } finally { - if (!is_dir($this->migrationDir)) { - mkdir($this->migrationDir, 0777, true); - } - } - } - - public function testGenerateMigrationThrowsForUnsupportedAction(): void - { - $manager = new MigrationManager(); - - $this->expectException(MigrationException::class); - $this->expectExceptionMessage('The action `sync`, is not supported'); - - $manager->generateMigration('users', 'sync'); - } - - public function testGenerateMigrationCreatesFileAndReturnsMigrationName(): void - { - $manager = new MigrationManager(); - - $migrationName = $manager->generateMigration('Users', 'create'); - - $this->assertMatchesRegularExpression('/^create_table_users_\d+$/', $migrationName); - $this->assertFileExists($this->migrationDir . DS . $migrationName . '.php'); - } - - public function testApplyMigrationsThrowsForUnsupportedDriver(): void - { - $manager = new MigrationManager(); - - $db = Mockery::mock(Database::class); - $db->shouldReceive('getConfigs')->andReturn(['driver' => 'sqlserver']); - $this->setPrivateProperty($manager, 'db', $db); - - $this->expectException(MigrationException::class); - $this->expectExceptionMessage('The driver `sqlserver` is not supported.'); - - $manager->applyMigrations(MigrationManager::UPGRADE); - } - - public function testApplyMigrationsThrowsForWrongDirection(): void - { - $manager = new MigrationManager(); - - $db = Mockery::mock(Database::class); - $db->shouldReceive('getConfigs')->andReturn(['driver' => 'sqlite']); - $this->setPrivateProperty($manager, 'db', $db); - - $this->expectException(MigrationException::class); - $this->expectExceptionMessage('Migration direction can only be [up] or [down]'); - - $manager->applyMigrations('sideways'); - } - - public function testApplyMigrationsUpgradeRunsPendingMigrationsInAscendingOrder(): void - { - $this->createMigrationsTable(); - $this->createValidMigrationFile('create_table_alpha_table_1001', 'alpha_table'); - $this->createValidMigrationFile('create_table_beta_table_1002', 'beta_table'); - - $manager = new MigrationManager(); - $migrated = $manager->applyMigrations(MigrationManager::UPGRADE); - - $this->assertSame(2, $migrated); - $this->assertTrue($this->tableExists('alpha_table')); - $this->assertTrue($this->tableExists('beta_table')); - - $entries = Database::query('SELECT migration FROM ' . MigrationTable::TABLE . ' ORDER BY migration ASC'); - $this->assertCount(2, $entries); - $this->assertSame('create_table_alpha_table_1001', $entries[0]['migration']); - $this->assertSame('create_table_beta_table_1002', $entries[1]['migration']); - } - - public function testApplyMigrationsUpgradeThrowsWhenNothingToMigrate(): void - { - $this->createMigrationsTable(); - $manager = new MigrationManager(); - - $this->expectException(MigrationException::class); - $this->expectExceptionMessage('Nothing to migrate'); - - $manager->applyMigrations(MigrationManager::UPGRADE); - } - - public function testApplyMigrationsUpgradeThrowsForInvalidMigrationClass(): void - { - $this->createMigrationsTable(); - $this->createInvalidMigrationFile('create_table_gamma_table_1003'); - - $manager = new MigrationManager(); - - $this->expectException(MigrationException::class); - $this->expectExceptionMessage('Migration class `create_table_gamma_table_1003` must extend QtMigration'); - - $manager->applyMigrations(MigrationManager::UPGRADE); - } - - public function testApplyMigrationsDowngradeThrowsWhenMigrationTableMissing(): void - { - Database::execute('DROP TABLE IF EXISTS ' . MigrationTable::TABLE); - - $manager = new MigrationManager(); - - $this->expectException(\Quantum\Database\Exceptions\DatabaseException::class); - $this->expectExceptionMessage('The table `migrations` does not exists'); - - $manager->applyMigrations(MigrationManager::DOWNGRADE); - } - - public function testApplyMigrationsDowngradeThrowsWhenNoEntriesExist(): void - { - Database::execute('CREATE TABLE IF NOT EXISTS ' . MigrationTable::TABLE . ' ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - migration VARCHAR(255), - applied_at DATETIME DEFAULT CURRENT_TIMESTAMP - )'); - - $manager = new MigrationManager(); - - $this->expectException(MigrationException::class); - $this->expectExceptionMessage('Nothing to migrate'); - - $manager->applyMigrations(MigrationManager::DOWNGRADE); - } - - public function testApplyMigrationsDowngradeRespectsStepAndOrder(): void - { - $this->createMigrationsTable(); - $this->createValidMigrationFile('create_table_alpha_table_2001', 'alpha_table'); - $this->createValidMigrationFile('create_table_beta_table_2002', 'beta_table'); - - $manager = new MigrationManager(); - $manager->applyMigrations(MigrationManager::UPGRADE); - - $migrated = $manager->applyMigrations(MigrationManager::DOWNGRADE, 1); - $this->assertSame(1, $migrated); - - $this->assertTrue($this->tableExists('alpha_table')); - $this->assertFalse($this->tableExists('beta_table')); - - $entries = Database::query('SELECT migration FROM ' . MigrationTable::TABLE . ' ORDER BY migration ASC'); - $this->assertCount(1, $entries); - $this->assertSame('create_table_alpha_table_2001', $entries[0]['migration']); - } - - private function createValidMigrationFile(string $className, string $tableName): void - { - $content = "migrationDir . DS . $className . '.php', $content); - } - - private function createInvalidMigrationFile(string $className): void - { - $content = "migrationDir . DS . $className . '.php', $content); - } - - private function tableExists(string $table): bool - { - try { - Database::query('SELECT 1 FROM ' . $table . ' LIMIT 1'); - } catch (\Exception $e) { - return false; - } - - return true; - } - - private function createMigrationsTable(): void - { - Database::execute('CREATE TABLE IF NOT EXISTS ' . MigrationTable::TABLE . ' ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - migration VARCHAR(255), - applied_at DATETIME DEFAULT CURRENT_TIMESTAMP - )'); - } -} diff --git a/tests/Unit/Migration/MigrationTableTest.php b/tests/Unit/Migration/MigrationTableTest.php deleted file mode 100644 index 948334415..000000000 --- a/tests/Unit/Migration/MigrationTableTest.php +++ /dev/null @@ -1,42 +0,0 @@ -shouldReceive('addColumn')->once()->with('id', Type::INT, 11)->andReturnSelf(); - $table->shouldReceive('autoIncrement')->once()->andReturnSelf(); - $table->shouldReceive('addColumn')->once()->with('migration', Type::VARCHAR, 255)->andReturnSelf(); - $table->shouldReceive('addColumn')->once()->with('applied_at', Type::TIMESTAMP)->andReturnSelf(); - $table->shouldReceive('default')->once()->with('CURRENT_TIMESTAMP', false)->andReturnSelf(); - - $tableFactory = Mockery::mock(TableFactory::class); - $tableFactory->shouldReceive('create')->once()->with(MigrationTable::TABLE)->andReturn($table); - - $migrationTable = new MigrationTable(); - $migrationTable->up($tableFactory); - - $this->assertTrue(true); - } - - public function testDownDropsMigrationTable(): void - { - $tableFactory = Mockery::mock(TableFactory::class); - $tableFactory->shouldReceive('drop')->once()->with(MigrationTable::TABLE)->andReturn(true); - - $migrationTable = new MigrationTable(); - $migrationTable->down($tableFactory); - - $this->assertTrue(true); - } -} diff --git a/tests/Unit/Migration/Templates/MigrationTemplateTest.php b/tests/Unit/Migration/Templates/MigrationTemplateTest.php deleted file mode 100644 index e8a382b6f..000000000 --- a/tests/Unit/Migration/Templates/MigrationTemplateTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertStringContainsString('class Create_table_users_1001 extends QtMigration', $template); - $this->assertStringContainsString("\$tableFactory->create('users')", $template); - $this->assertStringContainsString("\$tableFactory->drop('users')", $template); - } - - public function testAlterTemplateContainsExpectedOperations(): void - { - $template = MigrationTemplate::alter('alter_table_users_1002', 'users'); - - $this->assertStringContainsString('class Alter_table_users_1002 extends QtMigration', $template); - $this->assertStringContainsString("\$tableFactory->get('users')", $template); - } - - public function testRenameTemplateContainsExpectedOperations(): void - { - $template = MigrationTemplate::rename('rename_table_users_1003', 'users'); - - $this->assertStringContainsString('class Rename_table_users_1003 extends QtMigration', $template); - $this->assertStringContainsString("\$tableFactory->rename('users', \$newName)", $template); - $this->assertStringContainsString("\$tableFactory->rename(\$newName, 'users')", $template); - } - - public function testDropTemplateContainsExpectedOperations(): void - { - $template = MigrationTemplate::drop('drop_table_users_1004', 'users'); - - $this->assertStringContainsString('class Drop_table_users_1004 extends QtMigration', $template); - $this->assertStringContainsString("\$tableFactory->drop('users')", $template); - } -} From aca382a36bb86b1e0b95efd9a02d8e6af87f217f Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:36:44 +0400 Subject: [PATCH 3/7] [#482] Add migration template unit tests --- .../Templates/MigrationTemplateTest.php | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/Unit/Migration/Templates/MigrationTemplateTest.php diff --git a/tests/Unit/Migration/Templates/MigrationTemplateTest.php b/tests/Unit/Migration/Templates/MigrationTemplateTest.php new file mode 100644 index 000000000..e8a382b6f --- /dev/null +++ b/tests/Unit/Migration/Templates/MigrationTemplateTest.php @@ -0,0 +1,43 @@ +assertStringContainsString('class Create_table_users_1001 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->create('users')", $template); + $this->assertStringContainsString("\$tableFactory->drop('users')", $template); + } + + public function testAlterTemplateContainsExpectedOperations(): void + { + $template = MigrationTemplate::alter('alter_table_users_1002', 'users'); + + $this->assertStringContainsString('class Alter_table_users_1002 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->get('users')", $template); + } + + public function testRenameTemplateContainsExpectedOperations(): void + { + $template = MigrationTemplate::rename('rename_table_users_1003', 'users'); + + $this->assertStringContainsString('class Rename_table_users_1003 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->rename('users', \$newName)", $template); + $this->assertStringContainsString("\$tableFactory->rename(\$newName, 'users')", $template); + } + + public function testDropTemplateContainsExpectedOperations(): void + { + $template = MigrationTemplate::drop('drop_table_users_1004', 'users'); + + $this->assertStringContainsString('class Drop_table_users_1004 extends QtMigration', $template); + $this->assertStringContainsString("\$tableFactory->drop('users')", $template); + } +} From be5fc83a21714763ad0de55b3028bd8af56b3356 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:40:57 +0400 Subject: [PATCH 4/7] [#482] Add migration table unit tests --- tests/Unit/Migration/MigrationTableTest.php | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/Unit/Migration/MigrationTableTest.php diff --git a/tests/Unit/Migration/MigrationTableTest.php b/tests/Unit/Migration/MigrationTableTest.php new file mode 100644 index 000000000..948334415 --- /dev/null +++ b/tests/Unit/Migration/MigrationTableTest.php @@ -0,0 +1,42 @@ +shouldReceive('addColumn')->once()->with('id', Type::INT, 11)->andReturnSelf(); + $table->shouldReceive('autoIncrement')->once()->andReturnSelf(); + $table->shouldReceive('addColumn')->once()->with('migration', Type::VARCHAR, 255)->andReturnSelf(); + $table->shouldReceive('addColumn')->once()->with('applied_at', Type::TIMESTAMP)->andReturnSelf(); + $table->shouldReceive('default')->once()->with('CURRENT_TIMESTAMP', false)->andReturnSelf(); + + $tableFactory = Mockery::mock(TableFactory::class); + $tableFactory->shouldReceive('create')->once()->with(MigrationTable::TABLE)->andReturn($table); + + $migrationTable = new MigrationTable(); + $migrationTable->up($tableFactory); + + $this->assertTrue(true); + } + + public function testDownDropsMigrationTable(): void + { + $tableFactory = Mockery::mock(TableFactory::class); + $tableFactory->shouldReceive('drop')->once()->with(MigrationTable::TABLE)->andReturn(true); + + $migrationTable = new MigrationTable(); + $migrationTable->down($tableFactory); + + $this->assertTrue(true); + } +} From 14034ba4aa83c6f2f1002cd41a45cd3b5c5bbad1 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:53:09 +0400 Subject: [PATCH 5/7] [#482] Add migration manager core validation tests --- tests/Unit/Migration/MigrationManagerTest.php | 118 ++++++++++++++++++ tests/Unit/Migration/MigrationTableTest.php | 42 ------- 2 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 tests/Unit/Migration/MigrationManagerTest.php delete mode 100644 tests/Unit/Migration/MigrationTableTest.php diff --git a/tests/Unit/Migration/MigrationManagerTest.php b/tests/Unit/Migration/MigrationManagerTest.php new file mode 100644 index 000000000..02f3529a7 --- /dev/null +++ b/tests/Unit/Migration/MigrationManagerTest.php @@ -0,0 +1,118 @@ + */ + private array $existingMigrationFiles = []; + + public function setUp(): void + { + parent::setUp(); + + if (!config()->has('database')) { + config()->import(new Setup('config', 'database', true)); + } + + config()->set('database.default', 'sqlite'); + + $this->migrationDir = base_dir() . DS . 'migrations'; + if (!is_dir($this->migrationDir)) { + mkdir($this->migrationDir, 0777, true); + } + + $files = glob($this->migrationDir . DS . '*.php'); + $this->existingMigrationFiles = is_array($files) ? $files : []; + } + + public function tearDown(): void + { + $files = glob($this->migrationDir . DS . '*.php'); + $currentFiles = is_array($files) ? $files : []; + $createdByTest = array_diff($currentFiles, $this->existingMigrationFiles); + + foreach ($createdByTest as $file) { + if (is_file($file)) { + @unlink($file); + } + } + + parent::tearDown(); + } + + public function testConstructorThrowsWhenMigrationDirectoryIsMissing(): void + { + if (is_dir($this->migrationDir)) { + @rmdir($this->migrationDir); + } + + $this->expectException(FileSystemException::class); + $this->expectExceptionMessage('The directory ' . $this->migrationDir . ' does not exists.'); + + try { + new MigrationManager(); + } finally { + if (!is_dir($this->migrationDir)) { + mkdir($this->migrationDir, 0777, true); + } + } + } + + public function testGenerateMigrationThrowsForUnsupportedAction(): void + { + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('The action `sync`, is not supported'); + + $manager->generateMigration('users', 'sync'); + } + + public function testGenerateMigrationCreatesFileAndReturnsMigrationName(): void + { + $manager = new MigrationManager(); + + $migrationName = $manager->generateMigration('Users', 'create'); + + $this->assertMatchesRegularExpression('/^create_table_users_\d+$/', $migrationName); + $this->assertFileExists($this->migrationDir . DS . $migrationName . '.php'); + } + + public function testApplyMigrationsThrowsForUnsupportedDriver(): void + { + $manager = new MigrationManager(); + + $db = Mockery::mock(Database::class); + $db->shouldReceive('getConfigs')->andReturn(['driver' => 'sqlserver']); + $this->setPrivateProperty($manager, 'db', $db); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('The driver `sqlserver` is not supported.'); + + $manager->applyMigrations(MigrationManager::UPGRADE); + } + + public function testApplyMigrationsThrowsForWrongDirection(): void + { + $manager = new MigrationManager(); + + $db = Mockery::mock(Database::class); + $db->shouldReceive('getConfigs')->andReturn(['driver' => 'sqlite']); + $this->setPrivateProperty($manager, 'db', $db); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Migration direction can only be [up] or [down]'); + + $manager->applyMigrations('sideways'); + } +} diff --git a/tests/Unit/Migration/MigrationTableTest.php b/tests/Unit/Migration/MigrationTableTest.php deleted file mode 100644 index 948334415..000000000 --- a/tests/Unit/Migration/MigrationTableTest.php +++ /dev/null @@ -1,42 +0,0 @@ -shouldReceive('addColumn')->once()->with('id', Type::INT, 11)->andReturnSelf(); - $table->shouldReceive('autoIncrement')->once()->andReturnSelf(); - $table->shouldReceive('addColumn')->once()->with('migration', Type::VARCHAR, 255)->andReturnSelf(); - $table->shouldReceive('addColumn')->once()->with('applied_at', Type::TIMESTAMP)->andReturnSelf(); - $table->shouldReceive('default')->once()->with('CURRENT_TIMESTAMP', false)->andReturnSelf(); - - $tableFactory = Mockery::mock(TableFactory::class); - $tableFactory->shouldReceive('create')->once()->with(MigrationTable::TABLE)->andReturn($table); - - $migrationTable = new MigrationTable(); - $migrationTable->up($tableFactory); - - $this->assertTrue(true); - } - - public function testDownDropsMigrationTable(): void - { - $tableFactory = Mockery::mock(TableFactory::class); - $tableFactory->shouldReceive('drop')->once()->with(MigrationTable::TABLE)->andReturn(true); - - $migrationTable = new MigrationTable(); - $migrationTable->down($tableFactory); - - $this->assertTrue(true); - } -} From 9fdcdef86ddbf3baf330dd2c3ca8833b005bad1f Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:59:02 +0400 Subject: [PATCH 6/7] [#482] Add migration manager upgrade and downgrade execution tests --- tests/Unit/Migration/MigrationManagerTest.php | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Migration/MigrationManagerTest.php b/tests/Unit/Migration/MigrationManagerTest.php index 02f3529a7..650087f38 100644 --- a/tests/Unit/Migration/MigrationManagerTest.php +++ b/tests/Unit/Migration/MigrationManagerTest.php @@ -4,9 +4,10 @@ use Quantum\Migration\Exceptions\MigrationException; use Quantum\Storage\Exceptions\FileSystemException; +use Quantum\Migration\MigrationManager; +use Quantum\Migration\MigrationTable; use Quantum\Tests\Unit\AppTestCase; use Quantum\Database\Database; -use Quantum\Migration\MigrationManager; use Quantum\Loader\Setup; use Mockery; @@ -47,6 +48,10 @@ public function tearDown(): void } } + Database::execute('DROP TABLE IF EXISTS alpha_table'); + Database::execute('DROP TABLE IF EXISTS beta_table'); + Database::execute('DROP TABLE IF EXISTS ' . MigrationTable::TABLE); + parent::tearDown(); } @@ -115,4 +120,81 @@ public function testApplyMigrationsThrowsForWrongDirection(): void $manager->applyMigrations('sideways'); } + + public function testApplyMigrationsUpgradeRunsPendingMigrationsInAscendingOrder(): void + { + $this->createMigrationsTable(); + $this->createValidMigrationFile('create_table_alpha_table_1001', 'alpha_table'); + $this->createValidMigrationFile('create_table_beta_table_1002', 'beta_table'); + + $manager = new MigrationManager(); + $migrated = $manager->applyMigrations(MigrationManager::UPGRADE); + + $this->assertSame(2, $migrated); + $this->assertTrue($this->tableExists('alpha_table')); + $this->assertTrue($this->tableExists('beta_table')); + + $entries = Database::query('SELECT migration FROM ' . MigrationTable::TABLE . ' ORDER BY migration ASC'); + $this->assertCount(2, $entries); + $this->assertSame('create_table_alpha_table_1001', $entries[0]['migration']); + $this->assertSame('create_table_beta_table_1002', $entries[1]['migration']); + } + + public function testApplyMigrationsDowngradeRespectsStepAndOrder(): void + { + $this->createMigrationsTable(); + $this->createValidMigrationFile('create_table_alpha_table_2001', 'alpha_table'); + $this->createValidMigrationFile('create_table_beta_table_2002', 'beta_table'); + + $manager = new MigrationManager(); + $manager->applyMigrations(MigrationManager::UPGRADE); + + $migrated = $manager->applyMigrations(MigrationManager::DOWNGRADE, 1); + $this->assertSame(1, $migrated); + + $this->assertTrue($this->tableExists('alpha_table')); + $this->assertFalse($this->tableExists('beta_table')); + + $entries = Database::query('SELECT migration FROM ' . MigrationTable::TABLE . ' ORDER BY migration ASC'); + $this->assertCount(1, $entries); + $this->assertSame('create_table_alpha_table_2001', $entries[0]['migration']); + } + + private function createValidMigrationFile(string $className, string $tableName): void + { + $content = "migrationDir . DS . $className . '.php', $content); + } + + private function tableExists(string $table): bool + { + try { + Database::query('SELECT 1 FROM ' . $table . ' LIMIT 1'); + } catch (\Exception $e) { + return false; + } + + return true; + } + + private function createMigrationsTable(): void + { + Database::execute('CREATE TABLE IF NOT EXISTS ' . MigrationTable::TABLE . ' ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + migration VARCHAR(255), + applied_at DATETIME DEFAULT CURRENT_TIMESTAMP + )'); + } } From 43d3c6babd18a0320f173bf28514b5a1d8e643d8 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:07:42 +0400 Subject: [PATCH 7/7] [#482] Add migration manager failure path coverage tests --- tests/Unit/Migration/MigrationManagerTest.php | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/Unit/Migration/MigrationManagerTest.php b/tests/Unit/Migration/MigrationManagerTest.php index 650087f38..0666e55bf 100644 --- a/tests/Unit/Migration/MigrationManagerTest.php +++ b/tests/Unit/Migration/MigrationManagerTest.php @@ -160,6 +160,54 @@ public function testApplyMigrationsDowngradeRespectsStepAndOrder(): void $this->assertSame('create_table_alpha_table_2001', $entries[0]['migration']); } + public function testApplyMigrationsUpgradeThrowsWhenNothingToMigrate(): void + { + $this->createMigrationsTable(); + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Nothing to migrate'); + + $manager->applyMigrations(MigrationManager::UPGRADE); + } + + public function testApplyMigrationsUpgradeThrowsForInvalidMigrationClass(): void + { + $this->createMigrationsTable(); + $this->createInvalidMigrationFile('create_table_gamma_table_1003'); + + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Migration class `create_table_gamma_table_1003` must extend QtMigration'); + + $manager->applyMigrations(MigrationManager::UPGRADE); + } + + public function testApplyMigrationsDowngradeThrowsWhenMigrationTableMissing(): void + { + Database::execute('DROP TABLE IF EXISTS ' . MigrationTable::TABLE); + + $manager = new MigrationManager(); + + $this->expectException(\Quantum\Database\Exceptions\DatabaseException::class); + $this->expectExceptionMessage('The table `migrations` does not exists'); + + $manager->applyMigrations(MigrationManager::DOWNGRADE); + } + + public function testApplyMigrationsDowngradeThrowsWhenNoEntriesExist(): void + { + $this->createMigrationsTable(); + + $manager = new MigrationManager(); + + $this->expectException(MigrationException::class); + $this->expectExceptionMessage('Nothing to migrate'); + + $manager->applyMigrations(MigrationManager::DOWNGRADE); + } + private function createValidMigrationFile(string $className, string $tableName): void { $content = "migrationDir . DS . $className . '.php', $content); } + private function createInvalidMigrationFile(string $className): void + { + $content = "migrationDir . DS . $className . '.php', $content); + } + private function tableExists(string $table): bool { try {