diff --git a/tests/Unit/Migration/MigrationManagerTest.php b/tests/Unit/Migration/MigrationManagerTest.php new file mode 100644 index 000000000..0666e55bf --- /dev/null +++ b/tests/Unit/Migration/MigrationManagerTest.php @@ -0,0 +1,258 @@ + */ + 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); + } + } + + 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(); + } + + 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 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']); + } + + 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 { + 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/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); + } +}