From 70d3daaab4741bbe7e6cbea4eca7c4517c206f73 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 3 Mar 2024 23:49:26 -0500 Subject: [PATCH 01/10] First draft of the migrate command Ported from phinx replacing io calls and argument parsing. Also pull in the argument parsing from the current console commands. --- src/Command/MigrateCommand.php | 239 ++++++++++++++++++ src/MigrationsPlugin.php | 2 + tests/TestCase/Command/MigrateCommandTest.php | 163 ++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 src/Command/MigrateCommand.php create mode 100644 tests/TestCase/Command/MigrateCommandTest.php diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php new file mode 100644 index 00000000..60e3a82c --- /dev/null +++ b/src/Command/MigrateCommand.php @@ -0,0 +1,239 @@ + + */ + use EventDispatcherTrait; + + /** + * The default name added to the application command list + * + * @return string + */ + public static function defaultName(): string + { + return 'migrations migrate'; + } + + /** + * Configure the option parser + * + * @param \Cake\Console\ConsoleOptionParser $parser The option parser to configure + * @return \Cake\Console\ConsoleOptionParser + */ + public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser->setDescription([ + 'Apply migrations to a SQL datasource', + '', + 'Will run all available migrations, optionally up to a specific version', + '', + 'migrations migrate --connection secondary', + 'migrations migrate --connection secondary --target 003', + ])->addOption('plugin', [ + 'short' => 'p', + 'help' => 'The plugin to run migrations for', + ])->addOption('connection', [ + 'short' => 'c', + 'help' => 'The datasource connection to use', + 'default' => 'default', + ])->addOption('source', [ + 'short' => 's', + 'help' => 'The folder where your migrations are', + ])->addOption('target', [ + 'short' => 't', + 'help' => 'The target version to migrate to.', + ])->addOption('date', [ + 'short' => 'd', + 'help' => 'The date to migrate to', + ])->addOption('fake', [ + 'help' => "Mark any migrations selected as run, but don't actually execute them", + 'boolean' => true, + ])->addOption('no-lock', [ + 'help' => 'If present, no lock file will be generated after migrating', + 'boolean' => true, + ]); + + return $parser; + } + + /** + * Generate a configuration object for the migrations operation. + * + * @param \Cake\Console\Arguments $args The console arguments + * @return \Migrations\Config\Config The generated config instance. + */ + protected function getConfig(Arguments $args): Config + { + $folder = (string)$args->getOption('source'); + + // Get the filepath for migrations and seeds(not implemented yet) + $dir = ROOT . '/config/' . $folder; + if (defined('CONFIG')) { + $dir = CONFIG . $folder; + } + $plugin = $args->getOption('plugin'); + if ($plugin && is_string($plugin)) { + $dir = Plugin::path($plugin) . 'config/' . $folder; + } + + // Get the phinxlog table name. Plugins have separate migration history. + // The names and separate table history is something we could change in the future. + $table = 'phinxlog'; + if ($plugin && is_string($plugin)) { + $prefix = Inflector::underscore($plugin) . '_'; + $prefix = str_replace(['\\', '/', '.'], '_', $prefix); + $table = $prefix . $table; + } + $templatePath = dirname(__DIR__) . DS . 'templates' . DS; + $connectionName = (string)$args->getOption('connection'); + + // TODO this all needs to go away. But first Environment and Manager need to work + // with Cake's ConnectionManager. + $connectionConfig = ConnectionManager::getConfig($connectionName); + if (!$connectionConfig) { + throw new StopException("Could not find connection `{$connectionName}`"); + } + + /** @var array $connectionConfig */ + $adapter = $connectionConfig['scheme'] ?? null; + $adapterConfig = [ + 'adapter' => $adapter, + 'connection' => $connectionName, + 'database' => $connectionConfig['database'], + 'migration_table' => $table, + 'dryrun' => $args->getOption('dry-run'), + ]; + + $configData = [ + 'paths' => [ + 'migrations' => $dir, + ], + 'templates' => [ + 'file' => $templatePath . 'Phinx/create.php.template', + ], + 'migration_base_class' => 'Migrations\AbstractMigration', + 'environment' => $adapterConfig, + // TODO do we want to support the DI container in migrations? + ]; + + return new Config($configData); + } + + /** + * Get the migration manager for the current CLI options and application configuration. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The command io. + * @return \Migrations\Migration\Manager + */ + protected function getManager(Arguments $args, ConsoleIo $io): Manager + { + $config = $this->getConfig($args); + + return new Manager($config, $io); + } + + /** + * Execute the command. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io): ?int + { + $event = $this->dispatchEvent('Migration.beforeMigrate'); + if ($event->isStopped()) { + return $event->getResult() ? self::CODE_SUCCESS : self::CODE_ERROR; + } + $result = $this->executeMigrations($args, $io); + $this->dispatchEvent('Migration.afterMigrate'); + + return $result; + } + + /** + * Execute migrations based on console inputs. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null The exit code or null for success + */ + protected function executeMigrations(Arguments $args, ConsoleIo $io): ?int + { + $version = $args->getOption('target') !== null ? (int)$args->getOption('target') : null; + $date = $args->getOption('date'); + $fake = (bool)$args->getOption('fake'); + + $manager = $this->getManager($args, $io); + $config = $manager->getConfig(); + + $versionOrder = $config->getVersionOrder(); + $io->out('using connection ' . (string)$args->getOption('connection')); + $io->out('ordering by ' . $versionOrder . ' time'); + + if ($fake) { + $io->out('warning performing fake migrations'); + } + + try { + // run the migrations + $start = microtime(true); + if ($date !== null) { + $manager->migrateToDateTime(new DateTime((string)$date), $fake); + } else { + $manager->migrate($version, $fake); + } + $end = microtime(true); + } catch (Exception $e) { + $io->err('' . $e->getMessage() . ''); + $io->out($e->getTraceAsString(), 1, ConsoleIo::VERBOSE); + + return self::CODE_ERROR; + } catch (Throwable $e) { + $io->err('' . $e->getMessage() . ''); + $io->out($e->getTraceAsString(), 1, ConsoleIo::VERBOSE); + + return self::CODE_ERROR; + } + + $io->out(''); + $io->out('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + + return self::CODE_SUCCESS; + } +} diff --git a/src/MigrationsPlugin.php b/src/MigrationsPlugin.php index ef4d8ea2..1343eb43 100644 --- a/src/MigrationsPlugin.php +++ b/src/MigrationsPlugin.php @@ -22,6 +22,7 @@ use Migrations\Command\BakeMigrationDiffCommand; use Migrations\Command\BakeMigrationSnapshotCommand; use Migrations\Command\BakeSeedCommand; +use Migrations\Command\MigrateCommand; use Migrations\Command\MigrationsCacheBuildCommand; use Migrations\Command\MigrationsCacheClearCommand; use Migrations\Command\MigrationsCommand; @@ -92,6 +93,7 @@ public function console(CommandCollection $commands): CommandCollection if (Configure::read('Migrations.backend') == 'builtin') { $classes = [ StatusCommand::class, + MigrateCommand::class, ]; if (class_exists(SimpleBakeCommand::class)) { $classes[] = BakeMigrationCommand::class; diff --git a/tests/TestCase/Command/MigrateCommandTest.php b/tests/TestCase/Command/MigrateCommandTest.php new file mode 100644 index 00000000..a8bde9a4 --- /dev/null +++ b/tests/TestCase/Command/MigrateCommandTest.php @@ -0,0 +1,163 @@ +exec('migrations migrate --help'); + + $this->assertExitSuccess(); + $this->assertOutputContains('Apply migrations to a SQL datasource'); + } + + /** + * Test that migrating without the `--no-lock` option will dispatch a dump shell + * + * @return void + */ + public function testMigrateWithLock() + { + $this->markTestIncomplete('not done here'); + $argv = [ + '-c', + 'test', + ]; + + $this->command = $this->getMockCommand('MigrationsMigrateCommand'); + + $this->command->expects($this->once()) + ->method('executeCommand'); + + $this->command->run($argv, $this->getMockIo()); + } + + /** + * Test that migrating with the `--no-lock` option will not dispatch a dump shell + * + * @return void + */ + public function testMigrateWithNoLock() + { + $this->markTestIncomplete('not done here'); + $argv = [ + '-c', + 'test', + '--no-lock', + ]; + + $this->command = $this->getMockCommand('MigrationsMigrateCommand'); + + $this->command->expects($this->never()) + ->method('executeCommand'); + + $this->command->run($argv, $this->getMockIo()); + } + + /** + * Test that rolling back without the `--no-lock` option will dispatch a dump shell + * + * @return void + */ + public function testRollbackWithLock() + { + $this->markTestIncomplete('not done here'); + $argv = [ + '-c', + 'test', + ]; + + $this->command = $this->getMockCommand('MigrationsRollbackCommand'); + + $this->command->expects($this->once()) + ->method('executeCommand'); + + $this->command->run($argv, $this->getMockIo()); + } + + /** + * Test that rolling back with the `--no-lock` option will not dispatch a dump shell + * + * @return void + */ + public function testRollbackWithNoLock() + { + $this->markTestIncomplete('not done here'); + $argv = [ + '-c', + 'test', + '--no-lock', + ]; + + $this->command = $this->getMockCommand('MigrationsRollbackCommand'); + + $this->command->expects($this->never()) + ->method('executeCommand'); + + $this->command->run($argv, $this->getMockIo()); + } + + public function testMigrate() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateSource() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateSourceInvalid() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateDate() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateDateNotFound() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateTarget() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateTargetNotFound() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigrateFake() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigratePlugin() + { + $this->markTestIncomplete('not done here'); + } + + public function testMigratePluginInvalid() + { + $this->markTestIncomplete('not done here'); + } +} From ec5c656ecfa81fa41930b39ccb039f1c9cdf7173 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 9 Mar 2024 00:41:53 -0500 Subject: [PATCH 02/10] Get a few more tests passing --- src/Command/MigrateCommand.php | 36 +++++++++++++++++- src/Command/StatusCommand.php | 2 +- src/Config/ConfigInterface.php | 2 + tests/TestCase/Command/MigrateCommandTest.php | 37 +++++++++++++------ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php index 60e3a82c..c4ceb653 100644 --- a/src/Command/MigrateCommand.php +++ b/src/Command/MigrateCommand.php @@ -25,6 +25,7 @@ use DateTime; use Exception; use Migrations\Config\Config; +use Migrations\Config\ConfigInterface; use Migrations\Migration\Manager; use Throwable; @@ -72,6 +73,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'default' => 'default', ])->addOption('source', [ 'short' => 's', + 'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER, 'help' => 'The folder where your migrations are', ])->addOption('target', [ 'short' => 't', @@ -98,7 +100,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar */ protected function getConfig(Arguments $args): Config { - $folder = (string)$args->getOption('source'); + $folder = $args->getOption('source'); // Get the filepath for migrations and seeds(not implemented yet) $dir = ROOT . '/config/' . $folder; @@ -234,6 +236,38 @@ protected function executeMigrations(Arguments $args, ConsoleIo $io): ?int $io->out(''); $io->out('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + // Run dump command to generate lock file + /* TODO(mark) port this in + if ( + isset($this->argv[1]) && in_array($this->argv[1], ['migrate', 'rollback'], true) && + !in_array('--no-lock', $this->argv, true) && + $exitCode === 0 + ) { + $newArgs = []; + if (!empty($args->getOption('connection'))) { + $newArgs[] = '-c'; + $newArgs[] = $args->getOption('connection'); + } + + if (!empty($args->getOption('plugin'))) { + $newArgs[] = '-p'; + $newArgs[] = $args->getOption('plugin'); + } + + $io->out(''); + $io->out('Dumps the current schema of the database to be used while baking a diff'); + $io->out(''); + + $dumpExitCode = $this->executeCommand(MigrationsDumpCommand::class, $newArgs, $io); + } + + if (isset($dumpExitCode) && $exitCode === 0 && $dumpExitCode !== 0) { + $exitCode = 1; + } + + return $exitCode; + */ + return self::CODE_SUCCESS; } } diff --git a/src/Command/StatusCommand.php b/src/Command/StatusCommand.php index 196f04b7..7cb4d3f3 100644 --- a/src/Command/StatusCommand.php +++ b/src/Command/StatusCommand.php @@ -96,7 +96,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar */ protected function getConfig(Arguments $args): Config { - $folder = (string)$args->getOption('source'); + $folder = $args->getOption('source') ?? 'Migrations'; // Get the filepath for migrations and seeds(not implemented yet) $dir = ROOT . '/config/' . $folder; diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php index 46824edb..9af398f1 100644 --- a/src/Config/ConfigInterface.php +++ b/src/Config/ConfigInterface.php @@ -18,6 +18,8 @@ */ interface ConfigInterface extends ArrayAccess { + public const DEFAULT_MIGRATION_FOLDER = 'Migrations'; + /** * Returns the configuration for the current environment. * diff --git a/tests/TestCase/Command/MigrateCommandTest.php b/tests/TestCase/Command/MigrateCommandTest.php index a8bde9a4..d4b3b94c 100644 --- a/tests/TestCase/Command/MigrateCommandTest.php +++ b/tests/TestCase/Command/MigrateCommandTest.php @@ -3,6 +3,7 @@ namespace Migrations\Test\TestCase\Command; +use Cake\Console\ConsoleOutput; use Cake\Console\TestSuite\ConsoleIntegrationTestTrait; use Cake\Core\Configure; use Cake\TestSuite\TestCase; @@ -26,24 +27,38 @@ public function testHelp() } /** - * Test that migrating without the `--no-lock` option will dispatch a dump shell + * Test that running with no migrations is successful * * @return void */ - public function testMigrateWithLock() + public function testMigrateNoMigrationsSuccess() { - $this->markTestIncomplete('not done here'); - $argv = [ - '-c', - 'test', - ]; + $this->exec('migrations migrate -c test -s Missing'); + $this->assertExitSuccess(); - $this->command = $this->getMockCommand('MigrationsMigrateCommand'); + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('All Done'); - $this->command->expects($this->once()) - ->method('executeCommand'); + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(0, $table->find()->all()->toArray()); + } - $this->command->run($argv, $this->getMockIo()); + /** + * Test that running with a no-op migrations is successful + * + * @return void + */ + public function testMigrateWithSourceMigration() + { + $this->exec('migrations migrate -c test -s Migrations'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('Running MarkMigratedTest'); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(1, $table->find()->all()->toArray()); } /** From 9404577f26071f77a63e180118e33c8cc8a21ffd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 10 Mar 2024 00:37:44 -0500 Subject: [PATCH 03/10] Get all the no-lock test cases added --- src/Command/MigrateCommand.php | 1 + src/Migration/Manager.php | 2 +- tests/TestCase/Command/MigrateCommandTest.php | 204 +++++++++++++----- 3 files changed, 150 insertions(+), 57 deletions(-) diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php index c4ceb653..b1ac414d 100644 --- a/src/Command/MigrateCommand.php +++ b/src/Command/MigrateCommand.php @@ -206,6 +206,7 @@ protected function executeMigrations(Arguments $args, ConsoleIo $io): ?int $versionOrder = $config->getVersionOrder(); $io->out('using connection ' . (string)$args->getOption('connection')); + $io->out('using paths ' . implode(', ', $config->getMigrationPaths())); $io->out('ordering by ' . $versionOrder . ' time'); if ($fake) { diff --git a/src/Migration/Manager.php b/src/Migration/Manager.php index 9e5af57e..39c8c60a 100644 --- a/src/Migration/Manager.php +++ b/src/Migration/Manager.php @@ -852,7 +852,7 @@ function ($phpFile) { )); } - $io->out("Running $class."); + $io->verbose("Constructing $class."); $input = new ArgvInput(); $output = new OutputAdapter($io); diff --git a/tests/TestCase/Command/MigrateCommandTest.php b/tests/TestCase/Command/MigrateCommandTest.php index d4b3b94c..8741daf8 100644 --- a/tests/TestCase/Command/MigrateCommandTest.php +++ b/tests/TestCase/Command/MigrateCommandTest.php @@ -6,6 +6,8 @@ use Cake\Console\ConsoleOutput; use Cake\Console\TestSuite\ConsoleIntegrationTestTrait; use Cake\Core\Configure; +use Cake\Core\Exception\MissingPluginException; +use Cake\Database\Exception\DatabaseException; use Cake\TestSuite\TestCase; class MigrateCommandTest extends TestCase @@ -16,6 +18,13 @@ public function setUp(): void { parent::setUp(); Configure::write('Migrations.backend', 'builtin'); + + $table = $this->fetchTable('Phinxlog'); + try { + $table->deleteAll('1=1'); + } catch (DatabaseException $e) { + //debug($e->getMessage()); + } } public function testHelp() @@ -31,11 +40,12 @@ public function testHelp() * * @return void */ - public function testMigrateNoMigrationsSuccess() + public function testMigrateNoMigrationSource() { $this->exec('migrations migrate -c test -s Missing'); $this->assertExitSuccess(); + $this->assertOutputContains('using paths ' . ROOT . '/config/Missing'); $this->assertOutputContains('using connection test'); $this->assertOutputContains('All Done'); @@ -43,6 +53,23 @@ public function testMigrateNoMigrationsSuccess() $this->assertCount(0, $table->find()->all()->toArray()); } + /** + * Test that source parameter defaults to Migrations + */ + public function testMigrateSourceDefault() + { + $this->exec('migrations migrate -c test'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('MarkMigratedTest: migrated'); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(2, $table->find()->all()->toArray()); + } + /** * Test that running with a no-op migrations is successful * @@ -50,22 +77,136 @@ public function testMigrateNoMigrationsSuccess() */ public function testMigrateWithSourceMigration() { - $this->exec('migrations migrate -c test -s Migrations'); + $this->exec('migrations migrate -c test -s ShouldExecute'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/config/ShouldExecute'); + $this->assertOutputContains('ShouldExecuteMigration: migrated'); + $this->assertOutputContains('ShouldNotExecuteMigration: skipped '); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(1, $table->find()->all()->toArray()); + } + + /** + * Test that migrations only run to a certain date + */ + public function testMigrateDate() + { + $this->exec('migrations migrate -c test --date 2020-01-01'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('MarkMigratedTest: migrated'); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(1, $table->find()->all()->toArray()); + } + + /** + * Test output for dates with no matching migrations + */ + public function testMigrateDateNotFound() + { + $this->exec('migrations migrate -c test --date 2000-01-01'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputNotContains('MarkMigratedTest'); + $this->assertOutputContains('No migrations to run'); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(0, $table->find()->all()->toArray()); + } + + /** + * + * Test advancing migrations with an offset. + */ + public function testMigrateTarget() + { + $this->exec('migrations migrate -c test --target 20150416223600'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('MarkMigratedTest: migrated'); + $this->assertOutputNotContains('MarkMigratedTestSecond'); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(1, $table->find()->all()->toArray()); + } + + public function testMigrateTargetNotFound() + { + $this->exec('migrations migrate -c test --target 99'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputNotContains('MarkMigratedTest'); + $this->assertOutputNotContains('MarkMigratedTestSecond'); + $this->assertOutputContains('warning 99 is not a valid version'); + $this->assertOutputContains('All Done'); + + $table = $this->fetchTable('Phinxlog'); + $this->assertCount(0, $table->find()->all()->toArray()); + } + + public function testMigrateFakeAll() + { + $this->exec('migrations migrate -c test --fake'); $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('Running MarkMigratedTest'); + $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('warning performing fake migrations'); + $this->assertOutputContains('MarkMigratedTest: migrated'); + $this->assertOutputContains('MarkMigratedTestSecond: migrated'); $this->assertOutputContains('All Done'); $table = $this->fetchTable('Phinxlog'); + $this->assertCount(2, $table->find()->all()->toArray()); + } + + public function testMigratePlugin() + { + $this->loadPlugins(['Migrator']); + $this->exec('migrations migrate -c test --plugin Migrator'); + $this->assertExitSuccess(); + + $this->assertOutputContains('using connection test'); + $this->assertOutputContains('using paths ' . ROOT . '/Plugin/Migrator/config/Migrations'); + $this->assertOutputContains('Migrator: migrated'); + $this->assertOutputContains('All Done'); + + // Migration tracking table is plugin specific + $table = $this->fetchTable('MigratorPhinxlog'); $this->assertCount(1, $table->find()->all()->toArray()); } + public function testMigratePluginInvalid() + { + try { + $this->exec('migrations migrate -c test --plugin NotThere'); + $this->fail('Should raise an error or exit with an error'); + } catch (MissingPluginException $e) { + $this->assertTrue(true); + } + } + /** * Test that migrating with the `--no-lock` option will not dispatch a dump shell * * @return void - */ + * / public function testMigrateWithNoLock() { $this->markTestIncomplete('not done here'); @@ -87,7 +228,7 @@ public function testMigrateWithNoLock() * Test that rolling back without the `--no-lock` option will dispatch a dump shell * * @return void - */ + * / public function testRollbackWithLock() { $this->markTestIncomplete('not done here'); @@ -108,7 +249,7 @@ public function testRollbackWithLock() * Test that rolling back with the `--no-lock` option will not dispatch a dump shell * * @return void - */ + * / public function testRollbackWithNoLock() { $this->markTestIncomplete('not done here'); @@ -125,54 +266,5 @@ public function testRollbackWithNoLock() $this->command->run($argv, $this->getMockIo()); } - - public function testMigrate() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateSource() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateSourceInvalid() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateDate() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateDateNotFound() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateTarget() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateTargetNotFound() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigrateFake() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigratePlugin() - { - $this->markTestIncomplete('not done here'); - } - - public function testMigratePluginInvalid() - { - $this->markTestIncomplete('not done here'); - } + */ } From d5b1ddf1b311505ac0c8404dae5a1c345deb9042 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sun, 10 Mar 2024 23:57:09 -0400 Subject: [PATCH 04/10] Get remaining failing tests passing. There are a few warnings to clean up as well. --- src/Command/MigrateCommand.php | 31 +------------------ tests/TestCase/Command/CompletionTest.php | 2 +- tests/TestCase/Command/MigrateCommandTest.php | 16 ++++++---- tests/TestCase/Command/StatusCommandTest.php | 11 +++++-- tests/TestCase/MigrationsTest.php | 10 ++++-- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php index b1ac414d..dabbbbbd 100644 --- a/src/Command/MigrateCommand.php +++ b/src/Command/MigrateCommand.php @@ -238,36 +238,7 @@ protected function executeMigrations(Arguments $args, ConsoleIo $io): ?int $io->out('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); // Run dump command to generate lock file - /* TODO(mark) port this in - if ( - isset($this->argv[1]) && in_array($this->argv[1], ['migrate', 'rollback'], true) && - !in_array('--no-lock', $this->argv, true) && - $exitCode === 0 - ) { - $newArgs = []; - if (!empty($args->getOption('connection'))) { - $newArgs[] = '-c'; - $newArgs[] = $args->getOption('connection'); - } - - if (!empty($args->getOption('plugin'))) { - $newArgs[] = '-p'; - $newArgs[] = $args->getOption('plugin'); - } - - $io->out(''); - $io->out('Dumps the current schema of the database to be used while baking a diff'); - $io->out(''); - - $dumpExitCode = $this->executeCommand(MigrationsDumpCommand::class, $newArgs, $io); - } - - if (isset($dumpExitCode) && $exitCode === 0 && $dumpExitCode !== 0) { - $exitCode = 1; - } - - return $exitCode; - */ + // TODO(mark) port in logic from src/Command/MigrationsCommand.php : 142:164 return self::CODE_SUCCESS; } diff --git a/tests/TestCase/Command/CompletionTest.php b/tests/TestCase/Command/CompletionTest.php index afcceebc..7a92ed49 100644 --- a/tests/TestCase/Command/CompletionTest.php +++ b/tests/TestCase/Command/CompletionTest.php @@ -44,7 +44,7 @@ public function testMigrationsSubcommands() { $this->exec('completion subcommands migrations.migrations'); $expected = [ - 'orm-cache-build orm-cache-clear create dump mark_migrated migrate rollback seed status', + 'migrate orm-cache-build orm-cache-clear create dump mark_migrated rollback seed status', ]; $actual = $this->_out->messages(); $this->assertEquals($expected, $actual); diff --git a/tests/TestCase/Command/MigrateCommandTest.php b/tests/TestCase/Command/MigrateCommandTest.php index 8741daf8..7f7523bc 100644 --- a/tests/TestCase/Command/MigrateCommandTest.php +++ b/tests/TestCase/Command/MigrateCommandTest.php @@ -19,11 +19,16 @@ public function setUp(): void parent::setUp(); Configure::write('Migrations.backend', 'builtin'); - $table = $this->fetchTable('Phinxlog'); try { + $table = $this->fetchTable('Phinxlog'); + $table->deleteAll('1=1'); + } catch (DatabaseException $e) { + } + + try { + $table = $this->fetchTable('MigratorPhinxlog'); $table->deleteAll('1=1'); } catch (DatabaseException $e) { - //debug($e->getMessage()); } } @@ -206,7 +211,7 @@ public function testMigratePluginInvalid() * Test that migrating with the `--no-lock` option will not dispatch a dump shell * * @return void - * / + */ public function testMigrateWithNoLock() { $this->markTestIncomplete('not done here'); @@ -228,7 +233,7 @@ public function testMigrateWithNoLock() * Test that rolling back without the `--no-lock` option will dispatch a dump shell * * @return void - * / + */ public function testRollbackWithLock() { $this->markTestIncomplete('not done here'); @@ -249,7 +254,7 @@ public function testRollbackWithLock() * Test that rolling back with the `--no-lock` option will not dispatch a dump shell * * @return void - * / + */ public function testRollbackWithNoLock() { $this->markTestIncomplete('not done here'); @@ -266,5 +271,4 @@ public function testRollbackWithNoLock() $this->command->run($argv, $this->getMockIo()); } - */ } diff --git a/tests/TestCase/Command/StatusCommandTest.php b/tests/TestCase/Command/StatusCommandTest.php index d5b126da..05487437 100644 --- a/tests/TestCase/Command/StatusCommandTest.php +++ b/tests/TestCase/Command/StatusCommandTest.php @@ -6,6 +6,7 @@ use Cake\Console\TestSuite\ConsoleIntegrationTestTrait; use Cake\Core\Configure; use Cake\Core\Exception\MissingPluginException; +use Cake\Database\Exception\DatabaseException; use Cake\TestSuite\TestCase; class StatusCommandTest extends TestCase @@ -16,6 +17,12 @@ public function setUp(): void { parent::setUp(); Configure::write('Migrations.backend', 'builtin'); + + $table = $this->fetchTable('Phinxlog'); + try { + $table->deleteAll('1=1'); + } catch (DatabaseException $e) { + } } public function testHelp(): void @@ -43,9 +50,9 @@ public function testExecuteSimpleJson(): void assert(isset($this->_out)); $output = $this->_out->messages(); - $parsed = json_decode($output[1], true); + $parsed = json_decode($output[0], true); $this->assertTrue(is_array($parsed)); - $this->assertCount(1, $parsed); + $this->assertCount(2, $parsed); $this->assertArrayHasKey('id', $parsed[0]); $this->assertArrayHasKey('status', $parsed[0]); $this->assertArrayHasKey('name', $parsed[0]); diff --git a/tests/TestCase/MigrationsTest.php b/tests/TestCase/MigrationsTest.php index 5e97cdcd..b25c2c1f 100644 --- a/tests/TestCase/MigrationsTest.php +++ b/tests/TestCase/MigrationsTest.php @@ -581,6 +581,11 @@ public function testOverrideOptions() 'id' => '20150416223600', 'name' => 'MarkMigratedTest', ], + [ + 'status' => 'down', + 'id' => '20240309223600', + 'name' => 'MarkMigratedTestSecond', + ], ]; $this->assertEquals($expected, $result); @@ -588,18 +593,19 @@ public function testOverrideOptions() $this->assertTrue($migrate); $result = $this->migrations->status(['source' => 'Migrations']); $expected[0]['status'] = 'up'; + $expected[1]['status'] = 'up'; $this->assertEquals($expected, $result); $rollback = $this->migrations->rollback(['source' => 'Migrations']); $this->assertTrue($rollback); $result = $this->migrations->status(['source' => 'Migrations']); - $expected[0]['status'] = 'down'; + $expected[0]['status'] = 'up'; + $expected[1]['status'] = 'down'; $this->assertEquals($expected, $result); $migrate = $this->migrations->markMigrated(20150416223600, ['source' => 'Migrations']); $this->assertTrue($migrate); $result = $this->migrations->status(['source' => 'Migrations']); - $expected[0]['status'] = 'up'; $this->assertEquals($expected, $result); } From 5f26d5c60a725c376247430b27d94b7c02ca53a9 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 11 Mar 2024 00:29:07 -0400 Subject: [PATCH 05/10] Fix phpcs, psalm and phpstan --- src/Command/MigrateCommand.php | 2 +- src/Command/StatusCommand.php | 5 +++-- tests/TestCase/Command/MigrateCommandTest.php | 2 -- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php index dabbbbbd..310b67b5 100644 --- a/src/Command/MigrateCommand.php +++ b/src/Command/MigrateCommand.php @@ -100,7 +100,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar */ protected function getConfig(Arguments $args): Config { - $folder = $args->getOption('source'); + $folder = (string)$args->getOption('source'); // Get the filepath for migrations and seeds(not implemented yet) $dir = ROOT . '/config/' . $folder; diff --git a/src/Command/StatusCommand.php b/src/Command/StatusCommand.php index 7cb4d3f3..8562949a 100644 --- a/src/Command/StatusCommand.php +++ b/src/Command/StatusCommand.php @@ -22,6 +22,7 @@ use Cake\Datasource\ConnectionManager; use Cake\Utility\Inflector; use Migrations\Config\Config; +use Migrations\Config\ConfigInterface; use Migrations\Migration\Manager; /** @@ -77,7 +78,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar ])->addOption('source', [ 'short' => 's', 'help' => 'The folder under src/Config that migrations are in', - 'default' => 'Migrations', + 'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER, ])->addOption('format', [ 'short' => 'f', 'help' => 'The output format: text or json. Defaults to text.', @@ -96,7 +97,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar */ protected function getConfig(Arguments $args): Config { - $folder = $args->getOption('source') ?? 'Migrations'; + $folder = (string)$args->getOption('source'); // Get the filepath for migrations and seeds(not implemented yet) $dir = ROOT . '/config/' . $folder; diff --git a/tests/TestCase/Command/MigrateCommandTest.php b/tests/TestCase/Command/MigrateCommandTest.php index 7f7523bc..d96845af 100644 --- a/tests/TestCase/Command/MigrateCommandTest.php +++ b/tests/TestCase/Command/MigrateCommandTest.php @@ -3,7 +3,6 @@ namespace Migrations\Test\TestCase\Command; -use Cake\Console\ConsoleOutput; use Cake\Console\TestSuite\ConsoleIntegrationTestTrait; use Cake\Core\Configure; use Cake\Core\Exception\MissingPluginException; @@ -131,7 +130,6 @@ public function testMigrateDateNotFound() } /** - * * Test advancing migrations with an offset. */ public function testMigrateTarget() From f53bbfb7ab7efea17b859dc645f26be5f7d9e1a2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 11 Mar 2024 12:17:26 -0400 Subject: [PATCH 06/10] Add migration file --- .../20240309223600_mark_migrated_test_second.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/test_app/config/Migrations/20240309223600_mark_migrated_test_second.php diff --git a/tests/test_app/config/Migrations/20240309223600_mark_migrated_test_second.php b/tests/test_app/config/Migrations/20240309223600_mark_migrated_test_second.php new file mode 100644 index 00000000..44230805 --- /dev/null +++ b/tests/test_app/config/Migrations/20240309223600_mark_migrated_test_second.php @@ -0,0 +1,14 @@ + Date: Mon, 11 Mar 2024 12:18:57 -0400 Subject: [PATCH 07/10] Add additional URLs that are required. --- phpunit.xml.dist | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d86e5b48..aef2017f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -37,16 +37,19 @@ From 6247225eeebd5847ff9efacbb9d19353109707d8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 11 Mar 2024 13:50:27 -0400 Subject: [PATCH 08/10] Make paths work on windows --- tests/TestCase/Command/MigrateCommandTest.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/TestCase/Command/MigrateCommandTest.php b/tests/TestCase/Command/MigrateCommandTest.php index d96845af..c205ffe3 100644 --- a/tests/TestCase/Command/MigrateCommandTest.php +++ b/tests/TestCase/Command/MigrateCommandTest.php @@ -49,7 +49,7 @@ public function testMigrateNoMigrationSource() $this->exec('migrations migrate -c test -s Missing'); $this->assertExitSuccess(); - $this->assertOutputContains('using paths ' . ROOT . '/config/Missing'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Missing'); $this->assertOutputContains('using connection test'); $this->assertOutputContains('All Done'); @@ -66,7 +66,7 @@ public function testMigrateSourceDefault() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Migrations'); $this->assertOutputContains('MarkMigratedTest: migrated'); $this->assertOutputContains('All Done'); @@ -85,7 +85,7 @@ public function testMigrateWithSourceMigration() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/ShouldExecute'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'ShouldExecute'); $this->assertOutputContains('ShouldExecuteMigration: migrated'); $this->assertOutputContains('ShouldNotExecuteMigration: skipped '); $this->assertOutputContains('All Done'); @@ -103,7 +103,7 @@ public function testMigrateDate() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Migrations'); $this->assertOutputContains('MarkMigratedTest: migrated'); $this->assertOutputContains('All Done'); @@ -120,7 +120,7 @@ public function testMigrateDateNotFound() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Migrations'); $this->assertOutputNotContains('MarkMigratedTest'); $this->assertOutputContains('No migrations to run'); $this->assertOutputContains('All Done'); @@ -138,7 +138,7 @@ public function testMigrateTarget() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Migrations'); $this->assertOutputContains('MarkMigratedTest: migrated'); $this->assertOutputNotContains('MarkMigratedTestSecond'); $this->assertOutputContains('All Done'); @@ -153,7 +153,7 @@ public function testMigrateTargetNotFound() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Migrations'); $this->assertOutputNotContains('MarkMigratedTest'); $this->assertOutputNotContains('MarkMigratedTestSecond'); $this->assertOutputContains('warning 99 is not a valid version'); @@ -169,7 +169,7 @@ public function testMigrateFakeAll() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'config' . DS . 'Migrations'); $this->assertOutputContains('warning performing fake migrations'); $this->assertOutputContains('MarkMigratedTest: migrated'); $this->assertOutputContains('MarkMigratedTestSecond: migrated'); @@ -186,7 +186,7 @@ public function testMigratePlugin() $this->assertExitSuccess(); $this->assertOutputContains('using connection test'); - $this->assertOutputContains('using paths ' . ROOT . '/Plugin/Migrator/config/Migrations'); + $this->assertOutputContains('using paths ' . ROOT . DS . 'Plugin' . DS . 'Migrator' . DS . 'config' . DS . 'Migrations'); $this->assertOutputContains('Migrator: migrated'); $this->assertOutputContains('All Done'); From 5e48c0ed659640baa52bbcc6cee9e7119b38bfe6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 11 Mar 2024 13:59:13 -0400 Subject: [PATCH 09/10] Path normalization --- src/Command/MigrateCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php index 310b67b5..b44b7feb 100644 --- a/src/Command/MigrateCommand.php +++ b/src/Command/MigrateCommand.php @@ -103,13 +103,13 @@ protected function getConfig(Arguments $args): Config $folder = (string)$args->getOption('source'); // Get the filepath for migrations and seeds(not implemented yet) - $dir = ROOT . '/config/' . $folder; + $dir = ROOT . DS . 'config' . DS . $folder; if (defined('CONFIG')) { $dir = CONFIG . $folder; } $plugin = $args->getOption('plugin'); if ($plugin && is_string($plugin)) { - $dir = Plugin::path($plugin) . 'config/' . $folder; + $dir = Plugin::path($plugin) . 'config' . DS . $folder; } // Get the phinxlog table name. Plugins have separate migration history. From 005ca4ecc976f2e7efe7a9d20e135ac1f37f24ae Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 12 Mar 2024 13:35:43 -0400 Subject: [PATCH 10/10] Fix mysql tests, phpcs and phpstan/psalm --- src/Db/Adapter/AdapterInterface.php | 10 ++++++++-- src/Db/Adapter/AdapterWrapper.php | 6 +++--- src/Db/Adapter/PhinxAdapter.php | 14 +++++++++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Db/Adapter/AdapterInterface.php b/src/Db/Adapter/AdapterInterface.php index 742373e6..96d4f538 100644 --- a/src/Db/Adapter/AdapterInterface.php +++ b/src/Db/Adapter/AdapterInterface.php @@ -9,6 +9,7 @@ namespace Migrations\Db\Adapter; use Cake\Console\ConsoleIo; +use Cake\Database\Connection; use Cake\Database\Query; use Cake\Database\Query\DeleteQuery; use Cake\Database\Query\InsertQuery; @@ -21,8 +22,6 @@ /** * Adapter Interface. - * - * @method \PDO getConnection() */ interface AdapterInterface { @@ -520,4 +519,11 @@ public function setIo(ConsoleIo $io); * @return \Cake\Console\ConsoleIo $io The io instance to use */ public function getIo(): ?ConsoleIo; + + /** + * Get the Connection for this adapter. + * + * @return \Cake\Database\Connection The connection + */ + public function getConnection(): Connection; } diff --git a/src/Db/Adapter/AdapterWrapper.php b/src/Db/Adapter/AdapterWrapper.php index e9b77aef..42e4f83e 100644 --- a/src/Db/Adapter/AdapterWrapper.php +++ b/src/Db/Adapter/AdapterWrapper.php @@ -9,6 +9,7 @@ namespace Migrations\Db\Adapter; use Cake\Console\ConsoleIo; +use Cake\Database\Connection; use Cake\Database\Query; use Cake\Database\Query\DeleteQuery; use Cake\Database\Query\InsertQuery; @@ -17,7 +18,6 @@ use Migrations\Db\Literal; use Migrations\Db\Table\Column; use Migrations\Db\Table\Table; -use PDO; use Phinx\Migration\MigrationInterface; /** @@ -430,9 +430,9 @@ public function castToBool($value): mixed } /** - * @return \PDO + * @return \Cake\Database\Connection */ - public function getConnection(): PDO + public function getConnection(): Connection { return $this->getAdapter()->getConnection(); } diff --git a/src/Db/Adapter/PhinxAdapter.php b/src/Db/Adapter/PhinxAdapter.php index e4a33d85..f3cf451d 100644 --- a/src/Db/Adapter/PhinxAdapter.php +++ b/src/Db/Adapter/PhinxAdapter.php @@ -8,6 +8,7 @@ namespace Migrations\Db\Adapter; +use Cake\Database\Connection; use Cake\Database\Query; use Cake\Database\Query\DeleteQuery; use Cake\Database\Query\InsertQuery; @@ -32,7 +33,6 @@ use Migrations\Db\Table\ForeignKey; use Migrations\Db\Table\Index; use Migrations\Db\Table\Table; -use PDO; use Phinx\Db\Action\Action as PhinxAction; use Phinx\Db\Action\AddColumn as PhinxAddColumn; use Phinx\Db\Action\AddForeignKey as PhinxAddForeignKey; @@ -747,9 +747,9 @@ public function castToBool($value): mixed } /** - * @return \PDO + * @return \Cake\Database\Connection */ - public function getConnection(): PDO + public function getConnection(): Connection { return $this->adapter->getConnection(); } @@ -812,4 +812,12 @@ public function getDeleteBuilder(): DeleteQuery { return $this->adapter->getDeleteBuilder(); } + + /** + * @inheritDoc + */ + public function getCakeConnection(): Connection + { + return $this->adapter->getConnection(); + } }