diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..15bd17299 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "CI" diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 278be295d..93e19e921 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -26,4 +26,4 @@ on: jobs: coding-standards: name: "Coding Standards" - uses: "doctrine/.github/.github/workflows/coding-standards.yml@4.0.0" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.0.1" diff --git a/.github/workflows/composer-lint.yml b/.github/workflows/composer-lint.yml index 3707dcc02..d40d8a583 100644 --- a/.github/workflows/composer-lint.yml +++ b/.github/workflows/composer-lint.yml @@ -17,4 +17,4 @@ on: jobs: composer-lint: name: "Composer Lint" - uses: "doctrine/.github/.github/workflows/composer-lint.yml@4.0.0" + uses: "doctrine/.github/.github/workflows/composer-lint.yml@5.0.1" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index daf3f7218..51db006b9 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -65,7 +65,7 @@ jobs: run: "composer config minimum-stability dev" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: dependency-versions: "${{ matrix.dependencies }}" composer-options: "--ignore-platform-req=php+" @@ -97,6 +97,8 @@ jobs: path: "reports" - name: "Upload to Codecov" - uses: "codecov/codecov-action@v3" + uses: "codecov/codecov-action@v4" with: directory: reports + env: + CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index 34abfbcfd..3cac620a3 100644 --- a/.github/workflows/release-on-milestone-closed.yml +++ b/.github/workflows/release-on-milestone-closed.yml @@ -8,7 +8,7 @@ on: jobs: release: name: "Git tag, release & create merge-up PR" - uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@4.0.0" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.0.1" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 06417117c..e9b3081f3 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -43,7 +43,7 @@ jobs: extensions: "pdo_sqlite" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: dependency-versions: "${{ matrix.dependencies }}" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 957f21f1c..6d86b7af3 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -20,9 +20,6 @@ parameters: - message: '~^Call to an undefined method Symfony\\Component\\Console\\Output\\OutputInterface\:\:getErrorOutput\(\)\.$~' path: src/Tools/Console/ConsoleLogger.php - - - message: '~^Method Doctrine\\Migrations\\Tests\\Stub\\DoctrineRegistry::getService\(\) should return Doctrine\\Persistence\\ObjectManager but returns Doctrine\\DBAL\\Connection\|Doctrine\\ORM\\EntityManager~' - path: tests/Stub/DoctrineRegistry.php # https://github.com/phpstan/phpstan/issues/5982 - diff --git a/src/AbstractMigration.php b/src/AbstractMigration.php index 098eb620a..e13045c88 100644 --- a/src/AbstractMigration.php +++ b/src/AbstractMigration.php @@ -10,6 +10,7 @@ use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Exception\AbortMigration; +use Doctrine\Migrations\Exception\FrozenMigration; use Doctrine\Migrations\Exception\IrreversibleMigration; use Doctrine\Migrations\Exception\MigrationException; use Doctrine\Migrations\Exception\SkipMigration; @@ -36,6 +37,8 @@ abstract class AbstractMigration /** @var Query[] */ private array $plannedSql = []; + private bool $frozen = false; + public function __construct(Connection $connection, private readonly LoggerInterface $logger) { $this->connection = $connection; @@ -125,6 +128,10 @@ protected function addSql( array $params = [], array $types = [], ): void { + if ($this->frozen) { + throw FrozenMigration::new(); + } + $this->plannedSql[] = new Query($sql, $params, $types); } @@ -134,6 +141,11 @@ public function getSql(): array return $this->plannedSql; } + public function freeze(): void + { + $this->frozen = true; + } + protected function write(string $message): void { $this->logger->notice($message, ['migration' => $this]); diff --git a/src/Exception/FrozenMigration.php b/src/Exception/FrozenMigration.php new file mode 100644 index 000000000..45c0c1a91 --- /dev/null +++ b/src/Exception/FrozenMigration.php @@ -0,0 +1,15 @@ +getOption('allow-empty-diff'); $checkDbPlatform = filter_var($input->getOption('check-database-platform'), FILTER_VALIDATE_BOOLEAN); $fromEmptySchema = $input->getOption('from-empty-schema'); - $namespace = $input->getOption('namespace'); - if ($namespace === '') { - $namespace = null; - } if ($formatted) { if (! class_exists(SqlFormatter::class)) { @@ -120,16 +112,7 @@ protected function execute( } } - $configuration = $this->getDependencyFactory()->getConfiguration(); - - $dirs = $configuration->getMigrationDirectories(); - if ($namespace === null) { - $namespace = key($dirs); - } elseif (! isset($dirs[$namespace])) { - throw new OutOfBoundsException(sprintf('Path not defined for the namespace %s', $namespace)); - } - - assert(is_string($namespace)); + $namespace = $this->getNamespace($input, $output); $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); diff --git a/src/Tools/Console/Command/DoctrineCommand.php b/src/Tools/Console/Command/DoctrineCommand.php index cc908ec88..661f30d54 100644 --- a/src/Tools/Console/Command/DoctrineCommand.php +++ b/src/Tools/Console/Command/DoctrineCommand.php @@ -10,16 +10,22 @@ use Doctrine\Migrations\Tools\Console\ConsoleLogger; use Doctrine\Migrations\Tools\Console\Exception\DependenciesNotSatisfied; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; +use Exception; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use function array_keys; use function assert; +use function count; use function is_string; +use function key; +use function sprintf; /** * The DoctrineCommand class provides base functionality for the other migrations commands to extend from. @@ -138,4 +144,36 @@ private function setNamedEmOrConnection(InputInterface $input): void return; } } + + final protected function getNamespace(InputInterface $input, OutputInterface $output): string + { + $configuration = $this->getDependencyFactory()->getConfiguration(); + + $namespace = $input->getOption('namespace'); + if ($namespace === '') { + $namespace = null; + } + + $dirs = $configuration->getMigrationDirectories(); + if ($namespace === null && count($dirs) === 1) { + $namespace = key($dirs); + } elseif ($namespace === null && count($dirs) > 1) { + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please choose a namespace (defaults to the first one)', + array_keys($dirs), + 0, + ); + $namespace = $helper->ask($input, $output, $question); + $this->io->text(sprintf('You have selected the "%s" namespace', $namespace)); + } + + if (! isset($dirs[$namespace])) { + throw new Exception(sprintf('Path not defined for the namespace "%s"', $namespace)); + } + + assert(is_string($namespace)); + + return $namespace; + } } diff --git a/src/Tools/Console/Command/DumpSchemaCommand.php b/src/Tools/Console/Command/DumpSchemaCommand.php index 50bf81b61..456591e58 100644 --- a/src/Tools/Console/Command/DumpSchemaCommand.php +++ b/src/Tools/Console/Command/DumpSchemaCommand.php @@ -13,10 +13,7 @@ use Symfony\Component\Console\Output\OutputInterface; use function addslashes; -use function assert; use function class_exists; -use function is_string; -use function key; use function sprintf; use function str_contains; @@ -88,15 +85,7 @@ public function execute( } } - $configuration = $this->getDependencyFactory()->getConfiguration(); - - $namespace = $input->getOption('namespace'); - if ($namespace === null) { - $dirs = $configuration->getMigrationDirectories(); - $namespace = key($dirs); - } - - assert(is_string($namespace)); + $namespace = $this->getNamespace($input, $output); $this->checkNoPreviousDumpExistsForNamespace($namespace); diff --git a/src/Tools/Console/Command/GenerateCommand.php b/src/Tools/Console/Command/GenerateCommand.php index ae742675a..74247925b 100644 --- a/src/Tools/Console/Command/GenerateCommand.php +++ b/src/Tools/Console/Command/GenerateCommand.php @@ -4,15 +4,11 @@ namespace Doctrine\Migrations\Tools\Console\Command; -use Exception; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use function assert; -use function is_string; -use function key; use function sprintf; /** @@ -44,23 +40,9 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $configuration = $this->getDependencyFactory()->getConfiguration(); - $migrationGenerator = $this->getDependencyFactory()->getMigrationGenerator(); - $namespace = $input->getOption('namespace'); - if ($namespace === '') { - $namespace = null; - } - - $dirs = $configuration->getMigrationDirectories(); - if ($namespace === null) { - $namespace = key($dirs); - } elseif (! isset($dirs[$namespace])) { - throw new Exception(sprintf('Path not defined for the namespace %s', $namespace)); - } - - assert(is_string($namespace)); + $namespace = $this->getNamespace($input, $output); $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); diff --git a/src/Version/DbalExecutor.php b/src/Version/DbalExecutor.php index df177748c..57712e479 100644 --- a/src/Version/DbalExecutor.php +++ b/src/Version/DbalExecutor.php @@ -145,6 +145,8 @@ private function executeMigration( $this->addSql(new Query($sql)); } + $migration->freeze(); + if (count($this->sql) !== 0) { if (! $configuration->isDryRun()) { $this->executeResult($configuration); diff --git a/tests/AbstractMigrationTest.php b/tests/AbstractMigrationTest.php index 027511f76..24d952b58 100644 --- a/tests/AbstractMigrationTest.php +++ b/tests/AbstractMigrationTest.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Exception\AbortMigration; +use Doctrine\Migrations\Exception\FrozenMigration; use Doctrine\Migrations\Exception\IrreversibleMigration; use Doctrine\Migrations\Exception\SkipMigration; use Doctrine\Migrations\Query\Query; @@ -47,6 +48,15 @@ public function testAddSql(): void self::assertEquals([new Query('SELECT 1', [1], [2])], $this->migration->getSql()); } + public function testThrowFrozenMigrationException(): void + { + $this->expectException(FrozenMigration::class); + $this->expectExceptionMessage('The migration is frozen and cannot be edited anymore.'); + + $this->migration->freeze(); + $this->migration->exposedAddSql('SELECT 1', [1], [2]); + } + public function testWarnIfOutputMessage(): void { $this->migration->warnIf(true, 'Warning was thrown'); diff --git a/tests/Tools/Console/Command/DiffCommandTest.php b/tests/Tools/Console/Command/DiffCommandTest.php index 366f27e3f..c568e1b3f 100644 --- a/tests/Tools/Console/Command/DiffCommandTest.php +++ b/tests/Tools/Console/Command/DiffCommandTest.php @@ -18,10 +18,13 @@ use Doctrine\Migrations\Version\Version; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use function array_map; use function explode; +use function sprintf; use function sys_get_temp_dir; use function trim; @@ -139,6 +142,44 @@ public function testExecutedUnavailableMigrationsCancel(): void self::assertSame(3, $statusCode); } + /** @return array */ + public static function getSelectedNamespace(): array + { + return [ + 'no' => [null, 'FooNs'], + 'first' => [0, 'FooNs'], + 'two' => [1, 'FooNs2'], + ]; + } + + /** @dataProvider getSelectedNamespace */ + public function testExecuteWithMultipleDirectories(int|null $input, string $namespace): void + { + $this->migrationStatusCalculator + ->method('getNewMigrations') + ->willReturn(new AvailableMigrationsList([])); + + $this->migrationStatusCalculator + ->method('getExecutedUnavailableMigrations') + ->willReturn(new ExecutedMigrationsList([])); + + $this->configuration->addMigrationsDirectory('FooNs2', sys_get_temp_dir()); + + $this->diffCommand->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $this->migrationDiffGenerator->expects(self::once())->method('generate'); + + $this->diffCommandTester->setInputs([$input]); + $this->diffCommandTester->execute([]); + + $output = $this->diffCommandTester->getDisplay(true); + + self::assertStringContainsString('Please choose a namespace (defaults to the first one)', $output); + self::assertStringContainsString('[0] FooNs', $output); + self::assertStringContainsString('[1] FooNs2', $output); + self::assertStringContainsString(sprintf('You have selected the "%s" namespace', $namespace), $output); + } + protected function setUp(): void { $this->migrationDiffGenerator = $this->createMock(DiffGenerator::class); diff --git a/tests/Tools/Console/Command/DoctrineCommandTest.php b/tests/Tools/Console/Command/DoctrineCommandTest.php index 9b9520d8e..d192644a9 100644 --- a/tests/Tools/Console/Command/DoctrineCommandTest.php +++ b/tests/Tools/Console/Command/DoctrineCommandTest.php @@ -16,7 +16,10 @@ use Doctrine\Migrations\Tools\Console\Command\DoctrineCommand; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Doctrine\ORM\EntityManager; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -207,4 +210,176 @@ protected function execute(InputInterface $input, OutputInterface $output): int ['interactive' => false], ); } + + public function testNamespaceFromOption(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrations', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + DoctrineCommandTest::assertSame($this->getNamespace($input, $output), 'DoctrineMigrations'); + + return DoctrineCommand::SUCCESS; + } + }; + + $commandTester = new CommandTester($command); + $commandTester->execute( + ['--namespace' => 'DoctrineMigrations'], + ['interactive' => false], + ); + } + + public function testNamespaceUnknown(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrations', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->getNamespace($input, $output); + + return DoctrineCommand::SUCCESS; + } + }; + + $commandTester = new CommandTester($command); + + $this->expectExceptionMessage('Path not defined for the namespace "Unknown"'); + $commandTester->execute( + ['--namespace' => 'Unknown'], + ['interactive' => false], + ); + } + + public function testNamespaceFirstFromDirectories(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrations', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + DoctrineCommandTest::assertSame($this->getNamespace($input, $output), 'DoctrineMigrations'); + + return DoctrineCommand::SUCCESS; + } + }; + + $commandTester = new CommandTester($command); + $commandTester->execute([]); + } + + public function testNamespaceChoiceTwoDirectories(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrationsFirst', sys_get_temp_dir()); + $configuration->addMigrationsDirectory('DoctrineMigrationsTwo', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + DoctrineCommandTest::assertSame($this->getNamespace($input, $output), 'DoctrineMigrationsTwo'); + + return DoctrineCommand::SUCCESS; + } + }; + + $command->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $commandTester = new CommandTester($command); + $commandTester->setInputs([1]); + $commandTester->execute([]); + } } diff --git a/tests/Tools/Console/Command/DumpSchemaCommandTest.php b/tests/Tools/Console/Command/DumpSchemaCommandTest.php index e1d682c65..b37fca641 100644 --- a/tests/Tools/Console/Command/DumpSchemaCommandTest.php +++ b/tests/Tools/Console/Command/DumpSchemaCommandTest.php @@ -18,10 +18,13 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use function array_map; use function explode; +use function sprintf; use function sys_get_temp_dir; use function trim; @@ -62,6 +65,16 @@ public function testExecute(): void ->method('getMigrations') ->willReturn(new AvailableMigrationsSet([])); + $classNameGenerator = $this->createMock(ClassNameGenerator::class); + $classNameGenerator + ->method('generateClassName') + ->with('FooNs') + ->willReturn('FooNs\\Version1234'); + + $this->dependencyFactory->expects(self::any()) + ->method('getClassNameGenerator') + ->willReturn($classNameGenerator); + $this->schemaDumper->expects(self::once()) ->method('dump') ->with('FooNs\\Version1234', ['/foo/'], true, 80); @@ -88,6 +101,40 @@ public function testExecute(): void ); } + /** @return array> */ + public static function getNamespaceSelected(): array + { + return [ + 'no' => [null, 'FooNs'], + 'first' => [0, 'FooNs'], + 'two' => [1, 'FooNs2'], + ]; + } + + /** @dataProvider getNamespaceSelected */ + public function testExecuteWithMultipleDirectories(int|null $input, string $namespace): void + { + $this->migrationRepository->expects(self::once()) + ->method('getMigrations') + ->willReturn(new AvailableMigrationsSet([])); + + $this->configuration->addMigrationsDirectory('FooNs2', sys_get_temp_dir()); + + $this->dumpSchemaCommand->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $this->schemaDumper->expects(self::once())->method('dump'); + + $this->dumpSchemaCommandTester->setInputs([$input]); + $this->dumpSchemaCommandTester->execute([]); + + $output = $this->dumpSchemaCommandTester->getDisplay(true); + + self::assertStringContainsString('Please choose a namespace (defaults to the first one)', $output); + self::assertStringContainsString('[0] FooNs', $output); + self::assertStringContainsString('[1] FooNs2', $output); + self::assertStringContainsString(sprintf('You have selected the "%s" namespace', $namespace), $output); + } + protected function setUp(): void { $this->configuration = new Configuration(); @@ -97,16 +144,6 @@ protected function setUp(): void $this->migrationRepository = $this->createMock(FilesystemMigrationsRepository::class); $this->schemaDumper = $this->createMock(SchemaDumper::class); - $classNameGenerator = $this->createMock(ClassNameGenerator::class); - $classNameGenerator->expects(self::any()) - ->method('generateClassName') - ->with('FooNs') - ->willReturn('FooNs\\Version1234'); - - $this->dependencyFactory->expects(self::any()) - ->method('getClassNameGenerator') - ->willReturn($classNameGenerator); - $this->dependencyFactory->expects(self::any()) ->method('getSchemaDumper') ->willReturn($this->schemaDumper); diff --git a/tests/Tools/Console/Command/GenerateCommandTest.php b/tests/Tools/Console/Command/GenerateCommandTest.php index 6f8a05a01..27cba6a37 100644 --- a/tests/Tools/Console/Command/GenerateCommandTest.php +++ b/tests/Tools/Console/Command/GenerateCommandTest.php @@ -11,10 +11,13 @@ use Doctrine\Migrations\Tools\Console\Command\GenerateCommand; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use function array_map; use function explode; +use function sprintf; use function sys_get_temp_dir; use function trim; @@ -39,6 +42,16 @@ public function testExecute(): void ->with('FooNs\\Version1234') ->willReturn('/path/to/migration.php'); + $classNameGenerator = $this->createMock(ClassNameGenerator::class); + $classNameGenerator->expects(self::once()) + ->method('generateClassName') + ->with('FooNs') + ->willReturn('FooNs\\Version1234'); + + $this->dependencyFactory->expects(self::once()) + ->method('getClassNameGenerator') + ->willReturn($classNameGenerator); + $this->generateCommandTest->execute([]); $output = $this->generateCommandTest->getDisplay(true); @@ -51,6 +64,34 @@ public function testExecute(): void ], array_map(trim(...), explode("\n", trim($output)))); } + /** @return array> */ + public static function getNamespaceSelected(): array + { + return [ + 'no' => [null, 'FooNs'], + 'first' => [0, 'FooNs'], + 'two' => [1, 'FooNs2'], + ]; + } + + /** @dataProvider getNamespaceSelected */ + public function testExecuteWithMultipleDirectories(int|null $input, string $namespace): void + { + $this->configuration->addMigrationsDirectory('FooNs2', sys_get_temp_dir()); + + $this->generateCommand->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $this->generateCommandTest->setInputs([$input]); + $this->generateCommandTest->execute([]); + + $output = $this->generateCommandTest->getDisplay(true); + + self::assertStringContainsString('Please choose a namespace (defaults to the first one)', $output); + self::assertStringContainsString('[0] FooNs', $output); + self::assertStringContainsString('[1] FooNs2', $output); + self::assertStringContainsString(sprintf('You have selected the "%s" namespace', $namespace), $output); + } + protected function setUp(): void { $this->configuration = new Configuration(); @@ -59,16 +100,6 @@ protected function setUp(): void $this->dependencyFactory = $this->createMock(DependencyFactory::class); $this->migrationGenerator = $this->createMock(Generator::class); - $classNameGenerator = $this->createMock(ClassNameGenerator::class); - $classNameGenerator->expects(self::once()) - ->method('generateClassName') - ->with('FooNs') - ->willReturn('FooNs\\Version1234'); - - $this->dependencyFactory->expects(self::once()) - ->method('getClassNameGenerator') - ->willReturn($classNameGenerator); - $this->dependencyFactory->expects(self::any()) ->method('getConfiguration') ->willReturn($this->configuration);