diff --git a/src/Command/Migration/DownCommand.php b/src/Command/Migration/DownCommand.php index 85cdae3..e608545 100644 --- a/src/Command/Migration/DownCommand.php +++ b/src/Command/Migration/DownCommand.php @@ -35,7 +35,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int // check any executed migration foreach (array_reverse($migrations) as $migration) { if ($migration->getState()->getStatus() === State::STATUS_EXECUTED) { - $exist = true; break; } } @@ -62,7 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->eventDispatcher->dispatch(new BeforeMigrate()); try { - $migrator->rollback(); + $migration = $migrator->rollback(); if (!$migration instanceof MigrationInterface) { throw new \Exception('Migration not found'); } diff --git a/src/Command/Migration/GenerateCommand.php b/src/Command/Migration/GenerateCommand.php index b962040..32612a2 100644 --- a/src/Command/Migration/GenerateCommand.php +++ b/src/Command/Migration/GenerateCommand.php @@ -63,10 +63,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'If you want to create new empty migration, use migrate/create' ); - /** @var QuestionHelper $qaHelper */ - $qaHelper = $this->getHelper('question'); - if ($input->isInteractive() && $input instanceof StreamableInputInterface) { + /** @var QuestionHelper $qaHelper */ + $qaHelper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Would you like to create empty migration right now? (Y/n)', true); $answer = $qaHelper->ask($input, $output, $question); if (!$answer) { diff --git a/src/Factory/RepositoryContainer.php b/src/Factory/RepositoryContainer.php index 0e09c44..f3e3b5c 100644 --- a/src/Factory/RepositoryContainer.php +++ b/src/Factory/RepositoryContainer.php @@ -15,7 +15,6 @@ final class RepositoryContainer implements ContainerInterface { - private ContainerInterface $rootContainer; private ORMInterface $orm; private bool $rolesBuilt = false; @@ -25,7 +24,6 @@ final class RepositoryContainer implements ContainerInterface public function __construct(ContainerInterface $rootContainer) { - $this->rootContainer = $rootContainer; $this->orm = $rootContainer->get(ORMInterface::class); } diff --git a/tests/Command/CycleDependencyProxyTest.php b/tests/Command/CycleDependencyProxyTest.php new file mode 100644 index 0000000..62dd112 --- /dev/null +++ b/tests/Command/CycleDependencyProxyTest.php @@ -0,0 +1,129 @@ +createMock(DatabaseProviderInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(DatabaseProviderInterface::class) + ->willReturn($databaseProvider); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($databaseProvider, $proxy->getDatabaseProvider()); + } + + public function testGetMigrationConfig(): void + { + $config = new MigrationConfig(); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(MigrationConfig::class) + ->willReturn($config); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($config, $proxy->getMigrationConfig()); + } + + public function testGetMigrator(): void + { + $migrator = new Migrator( + new MigrationConfig(), + $this->createMock(DatabaseProviderInterface::class), + $this->createMock(RepositoryInterface::class) + ); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(Migrator::class) + ->willReturn($migrator); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($migrator, $proxy->getMigrator()); + } + + public function testGetOrm(): void + { + $orm = $this->createMock(ORMInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(ORMInterface::class) + ->willReturn($orm); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($orm, $proxy->getORM()); + } + + public function testGetSchema(): void + { + $schema = $this->createMock(SchemaInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(SchemaInterface::class) + ->willReturn($schema); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($schema, $proxy->getSchema()); + } + + public function testGetSchemaProvider(): void + { + $schemaProvider = $this->createMock(SchemaProviderInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(SchemaProviderInterface::class) + ->willReturn($schemaProvider); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($schemaProvider, $proxy->getSchemaProvider()); + } + + public function testGetSchemaConveyor(): void + { + $schemaConveyor = $this->createMock(SchemaConveyorInterface::class); + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->once()) + ->method('get') + ->with(SchemaConveyorInterface::class) + ->willReturn($schemaConveyor); + + $proxy = new CycleDependencyProxy($container); + + $this->assertSame($schemaConveyor, $proxy->getSchemaConveyor()); + } +} diff --git a/tests/Command/Migration/CreateCommandTest.php b/tests/Command/Migration/CreateCommandTest.php new file mode 100644 index 0000000..e48bc0a --- /dev/null +++ b/tests/Command/Migration/CreateCommandTest.php @@ -0,0 +1,66 @@ + 'Test\\Migration']); + + $database = $this->createMock(DatabaseInterface::class); + $database->expects($this->once())->method('getName')->willReturn('testDatabase'); + + $databaseProvider = $this->createMock(DatabaseProviderInterface::class); + $databaseProvider->expects($this->once())->method('database')->willReturn($database); + + $repository = $this->createMock(RepositoryInterface::class); + $repository + ->expects($this->once()) + ->method('registerMigration') + ->with( + 'testDatabase_foo', + $this->callback(static fn (string $name): bool => \str_contains($name, 'OrmTestDatabase')), + $this->callback( + static fn (string $name): bool => + \str_contains($name, 'OrmTestDatabase') && + \str_contains($name, 'namespace Test\\Migration') && + \str_contains($name, 'use Cycle\\Migrations\\Migration') && + \str_contains($name, 'protected const DATABASE = \'testDatabase\'') && + \str_contains($name, 'public function up(): void') && + \str_contains($name, 'public function down(): void') + ) + ); + + $command = new CreateCommand(new CycleDependencyProxy(new SimpleContainer([ + DatabaseProviderInterface::class => $databaseProvider, + Migrator::class => new Migrator( + $config, + $this->createMock(DatabaseProviderInterface::class), + $repository + ), + MigrationConfig::class => $config, + ]))); + + $output = new FakeOutput(); + $code = $command->run(new ArrayInput(['name' => 'foo']), $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('New migration file has been created', $output->getBuffer()); + } +} diff --git a/tests/Command/Migration/DownCommandTest.php b/tests/Command/Migration/DownCommandTest.php new file mode 100644 index 0000000..c50c2d0 --- /dev/null +++ b/tests/Command/Migration/DownCommandTest.php @@ -0,0 +1,94 @@ +createMock(RepositoryInterface::class); + $repository->expects($this->once())->method('getMigrations')->willReturn([]); + + $migrator = new Migrator( + new MigrationConfig(), + $this->createMock(DatabaseProviderInterface::class), + $repository + ); + + $output = new FakeOutput(); + $command = new DownCommand( + new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => new MigrationConfig(), + ])), + $this->createMock(EventDispatcherInterface::class) + ); + $code = $command->run(new ArrayInput([]), $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('No migration found for rollback', $output->getBuffer()); + } + + public function testExecute(): void + { + $migration = new FakeMigration(); + $migration = $migration->withState(new State('test', new \DateTimeImmutable(), State::STATUS_PENDING)); + + $repository = $this->createMock(RepositoryInterface::class); + $repository->expects($this->exactly(5))->method('getMigrations')->willReturn([$migration]); + + $db = new DatabaseManager(new DatabaseConfig([ + 'default' => 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])); + + $migrator = new Migrator(new MigrationConfig(), $db, $repository); + $migrator->configure(); + + $promise = new CycleDependencyProxy(new SimpleContainer([ + DatabaseProviderInterface::class => $db, + Migrator::class => $migrator, + MigrationConfig::class => new MigrationConfig(), + ])); + + $input = new ArrayInput([]); + $input->setInteractive(false); + + $command = new UpCommand($promise, $this->createMock(EventDispatcherInterface::class)); + $command->run($input, new NullOutput()); + + $command = new DownCommand($promise, $this->createMock(EventDispatcherInterface::class)); + $output = new FakeOutput(); + $code = $command->run($input, $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Total 1 migration(s) found', $output->getBuffer()); + $this->assertStringContainsString('test: pending', $output->getBuffer()); + } +} diff --git a/tests/Command/Migration/GenerateCommandTest.php b/tests/Command/Migration/GenerateCommandTest.php new file mode 100644 index 0000000..30c6186 --- /dev/null +++ b/tests/Command/Migration/GenerateCommandTest.php @@ -0,0 +1,138 @@ +withState(new State('test', new \DateTimeImmutable(), State::STATUS_PENDING)); + + $repository = $this->createMock(RepositoryInterface::class); + $repository->expects($this->exactly(1))->method('getMigrations')->willReturn([$migration]); + + $migrator = new Migrator( + new MigrationConfig(), + new DatabaseManager(new DatabaseConfig([ + 'default' => 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])), + $repository + ); + $migrator->configure(); + + $output = new FakeOutput(); + $command = new GenerateCommand(new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => new MigrationConfig(), + ]))); + $code = $command->run(new ArrayInput([]), $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Outstanding migrations found, run `migrate/up` first.', $output->getBuffer()); + } + + public function testExecuteWithoutChanges(): void + { + $repository = $this->createMock(RepositoryInterface::class); + $repository->expects($this->exactly(2))->method('getMigrations')->willReturn([]); + + $migrator = new Migrator( + new MigrationConfig(), + new DatabaseManager(new DatabaseConfig([ + 'default' => 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])), + $repository + ); + $migrator->configure(); + + $output = new FakeOutput(); + $command = new GenerateCommand(new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => new MigrationConfig(), + SchemaConveyorInterface::class => $this->createMock(SchemaConveyorInterface::class), + DatabaseProviderInterface::class => $this->createMock(DatabaseProviderInterface::class), + ]))); + + $input = new ArrayInput([]); + $input->setInteractive(false); + $code = $command->run($input, $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Added 0 file(s)', $output->getBuffer()); + $this->assertStringContainsString( + 'If you want to create new empty migration, use migrate/create', + $output->getBuffer() + ); + } + + public function testExecute(): void + { + $migration = new FakeMigration(); + $migration = $migration->withState(new State('test', new \DateTimeImmutable(), State::STATUS_PENDING)); + + $repository = $this->createMock(RepositoryInterface::class); + $repository->expects($this->exactly(2))->method('getMigrations')->willReturnOnConsecutiveCalls( + [], + [$migration] + ); + + $migrator = new Migrator( + new MigrationConfig(), + new DatabaseManager(new DatabaseConfig([ + 'default' => 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])), + $repository + ); + $migrator->configure(); + + $output = new FakeOutput(); + $command = new GenerateCommand(new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => new MigrationConfig(), + SchemaConveyorInterface::class => $this->createMock(SchemaConveyorInterface::class), + DatabaseProviderInterface::class => $this->createMock(DatabaseProviderInterface::class), + ]))); + + $input = new ArrayInput([]); + $input->setInteractive(false); + $code = $command->run($input, $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Added 1 file(s)', $output->getBuffer()); + $this->assertStringContainsString('test', $output->getBuffer()); + } +} diff --git a/tests/Command/Migration/ListCommandTest.php b/tests/Command/Migration/ListCommandTest.php new file mode 100644 index 0000000..99c9a4a --- /dev/null +++ b/tests/Command/Migration/ListCommandTest.php @@ -0,0 +1,60 @@ +withState(new State('test', new \DateTimeImmutable(), State::STATUS_PENDING)); + + $repository = $this->createMock(RepositoryInterface::class); + $repository->expects($this->exactly(1))->method('getMigrations')->willReturn([$migration]); + + $migrator = new Migrator( + new MigrationConfig(), + new DatabaseManager(new DatabaseConfig([ + 'default' => 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])), + $repository + ); + $migrator->configure(); + + $output = new FakeOutput(); + $command = new ListCommand( + new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => new MigrationConfig(), + ])), + ); + $code = $command->run(new ArrayInput([]), $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Total 1 migration(s) found', $output->getBuffer()); + $this->assertStringContainsString('test [pending]', $output->getBuffer()); + } +} diff --git a/tests/Command/Migration/UpCommandTest.php b/tests/Command/Migration/UpCommandTest.php new file mode 100644 index 0000000..c47e802 --- /dev/null +++ b/tests/Command/Migration/UpCommandTest.php @@ -0,0 +1,95 @@ +createMock(RepositoryInterface::class); + $repository->expects($this->once())->method('getMigrations')->willReturn([]); + + $migrator = new Migrator( + $config, + $this->createMock(DatabaseProviderInterface::class), + $repository + ); + + $output = new FakeOutput(); + $command = new UpCommand( + new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => $config, + ])), + $this->createMock(EventDispatcherInterface::class) + ); + $code = $command->run(new ArrayInput([]), $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('No migration found for execute', $output->getBuffer()); + } + + public function testExecute(): void + { + $config = new MigrationConfig(['safe' => true]); + + $migration = new FakeMigration(); + $migration = $migration->withState(new State('test', new \DateTimeImmutable(), State::STATUS_PENDING)); + + $repository = $this->createMock(RepositoryInterface::class); + $repository->expects($this->exactly(3))->method('getMigrations')->willReturn([$migration]); + + $migrator = new Migrator( + new MigrationConfig(), + new DatabaseManager(new DatabaseConfig([ + 'default' => 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])), + $repository + ); + $migrator->configure(); + + $output = new FakeOutput(); + $command = new UpCommand( + new CycleDependencyProxy(new SimpleContainer([ + Migrator::class => $migrator, + MigrationConfig::class => $config, + ])), + $this->createMock(EventDispatcherInterface::class) + ); + + $input = new ArrayInput([]); + $input->setInteractive(false); + $code = $command->run($input, $output); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Migration to be applied:', $output->getBuffer()); + $this->assertStringContainsString('test: executed', $output->getBuffer()); + } +} diff --git a/tests/Command/Schema/SchemaClearBaseCommandTest.php b/tests/Command/Schema/SchemaClearCommandTest.php similarity index 95% rename from tests/Command/Schema/SchemaClearBaseCommandTest.php rename to tests/Command/Schema/SchemaClearCommandTest.php index 69ea718..9b3347c 100644 --- a/tests/Command/Schema/SchemaClearBaseCommandTest.php +++ b/tests/Command/Schema/SchemaClearCommandTest.php @@ -13,7 +13,7 @@ use Yiisoft\Yii\Cycle\Command\Schema\SchemaClearCommand; use Yiisoft\Yii\Cycle\Schema\SchemaProviderInterface; -final class SchemaClearBaseCommandTest extends TestCase +final class SchemaClearCommandTest extends TestCase { public function testExecute(): void { diff --git a/tests/Command/Schema/SchemaCommandTest.php b/tests/Command/Schema/SchemaCommandTest.php index 0a3af6a..21c9bb1 100644 --- a/tests/Command/Schema/SchemaCommandTest.php +++ b/tests/Command/Schema/SchemaCommandTest.php @@ -7,12 +7,12 @@ use Cycle\ORM\SchemaInterface; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\Output; use Symfony\Component\Console\Output\OutputInterface; use Yiisoft\Test\Support\Container\SimpleContainer; use Yiisoft\Yii\Console\ExitCode; use Yiisoft\Yii\Cycle\Command\CycleDependencyProxy; use Yiisoft\Yii\Cycle\Command\Schema\SchemaCommand; +use Yiisoft\Yii\Cycle\Tests\Command\Stub\FakeOutput; final class SchemaCommandTest extends TestCase { @@ -20,23 +20,7 @@ final class SchemaCommandTest extends TestCase protected function setUp(): void { - $this->output = new class () extends Output { - private string $buffer = ''; - - protected function doWrite(string $message, bool $newline): void - { - $this->buffer .= $message; - - if ($newline) { - $this->buffer .= \PHP_EOL; - } - } - - public function getBuffer(): string - { - return $this->buffer; - } - }; + $this->output = new FakeOutput(); } public function testExecuteUndefinedRoles(): void @@ -59,13 +43,7 @@ public function testExecuteGetRoles(): void $schema = $this->getMockBuilder(SchemaInterface::class)->getMock(); $schema->expects($this->any())->method('getRoles')->willReturn(['foo', 'bar']); $schema->expects($this->any())->method('define')->willReturnCallback( - function (string $role, int $property) { - if ($property === SchemaInterface::ROLE) { - return $role; - } - - return null; - } + fn (string $role, int $property): ?string => $property === SchemaInterface::ROLE ? $role : null ); $container = new SimpleContainer([SchemaInterface::class => $schema]); diff --git a/tests/Command/Schema/SchemaPhpCommandTest.php b/tests/Command/Schema/SchemaPhpCommandTest.php new file mode 100644 index 0000000..77dcaf9 --- /dev/null +++ b/tests/Command/Schema/SchemaPhpCommandTest.php @@ -0,0 +1,99 @@ +output = new FakeOutput(); + } + + public function testExecuteWithoutFile(): void + { + $schema = $this->createMock(SchemaInterface::class); + $schema->expects($this->any())->method('getRoles')->willReturn(['foo', 'bar']); + $schema->expects($this->any())->method('define')->willReturnCallback( + fn (string $role, int $property): ?string => $property === SchemaInterface::ROLE ? $role : null + ); + + $container = new SimpleContainer([SchemaInterface::class => $schema]); + $promise = new CycleDependencyProxy($container); + $command = new SchemaPhpCommand(new Aliases(), $promise); + + $code = $command->run(new ArrayInput([]), $this->output); + $result = $this->output->getBuffer(); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString('Schema::ROLE => \'foo\'', $result); + $this->assertStringContainsString('Schema::ROLE => \'bar\'', $result); + } + + public function testExecuteWithFile(): void + { + $file = \dirname(__DIR__) . '/Stub/schema.php'; + + $schema = $this->createMock(SchemaInterface::class); + $schema->expects($this->any())->method('getRoles')->willReturn(['foo', 'bar']); + $schema->expects($this->any())->method('define')->willReturnCallback( + fn (string $role, int $property): ?string => $property === SchemaInterface::ROLE ? $role : null + ); + + $container = new SimpleContainer([SchemaInterface::class => $schema]); + $promise = new CycleDependencyProxy($container); + $command = new SchemaPhpCommand(new Aliases(), $promise); + + $code = $command->run(new ArrayInput(['file' => $file]), $this->output); + $result = $this->output->getBuffer(); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString(sprintf('Destination: %s', $file), $result); + + $this->assertStringContainsString('Schema::ROLE => \'foo\'', file_get_contents($file)); + $this->assertStringContainsString('Schema::ROLE => \'bar\'', file_get_contents($file)); + + \unlink($file); + } + + public function testExecuteWithFileAndAlias(): void + { + $file = \dirname(__DIR__) . '/Stub/alias-schema.php'; + + $schema = $this->createMock(SchemaInterface::class); + $schema->expects($this->any())->method('getRoles')->willReturn(['foo', 'bar']); + $schema->expects($this->any())->method('define')->willReturnCallback( + fn (string $role, int $property): ?string => $property === SchemaInterface::ROLE ? $role : null + ); + + $container = new SimpleContainer([SchemaInterface::class => $schema]); + $promise = new CycleDependencyProxy($container); + $command = new SchemaPhpCommand(new Aliases([ + '@test' => \dirname(__DIR__) . '/Stub', + ]), $promise); + + $code = $command->run(new ArrayInput(['file' => '@test/alias-schema.php']), $this->output); + $result = $this->output->getBuffer(); + + $this->assertSame(ExitCode::OK, $code); + $this->assertStringContainsString(sprintf('Destination: %s', $file), $result); + + $this->assertStringContainsString('Schema::ROLE => \'foo\'', file_get_contents($file)); + $this->assertStringContainsString('Schema::ROLE => \'bar\'', file_get_contents($file)); + + \unlink($file); + } +} diff --git a/tests/Command/Stub/FakeMigration.php b/tests/Command/Stub/FakeMigration.php new file mode 100644 index 0000000..04e5aa0 --- /dev/null +++ b/tests/Command/Stub/FakeMigration.php @@ -0,0 +1,20 @@ +buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + } + + public function getBuffer(): string + { + return $this->buffer; + } +} diff --git a/tests/Feature/Data/EntityReaderTest.php b/tests/Feature/Data/EntityReaderTest.php deleted file mode 100644 index 433ac4a..0000000 --- a/tests/Feature/Data/EntityReaderTest.php +++ /dev/null @@ -1,131 +0,0 @@ -fillFixtures(); - - $reader = new EntityReader( - $this->select('user'), - ); - - self::assertEquals(self::FIXTURES_USER[0], (array)$reader->readOne()); - } - - /** - * Test {@see EntityReader::read()} - */ - public function testRead(): void - { - $this->fillFixtures(); - - $reader = new EntityReader( - $this->select('user'), - ); - - $result = $reader->read(); - self::assertEquals( - \array_map(static fn (array $data): \stdClass => (object) $data, self::FIXTURES_USER), - $result, - ); - } - - /** - * Test {@see EntityReader::withSort()} - */ - public function testWithSort(): void - { - $this->fillFixtures(); - - $reader = (new EntityReader( - $this->select('user'), - )) - // Reverse order - ->withSort(Sort::only(['id'])->withOrderString('-id')); - - $result = $reader->read(); - self::assertEquals( - \array_map(static fn (array $data): object => (object) $data, \array_reverse(self::FIXTURES_USER)), - $result, - ); - self::assertSame('-id', $reader->getSort()->getOrderAsString()); - } - - /** - * Test {@see EntityReader::count()} - */ - public function testCount(): void - { - $this->fillFixtures(); - - $reader = new EntityReader( - $this->select('user'), - ); - - self::assertSame(count(self::FIXTURES_USER), $reader->count()); - } - - /** - * Test {@see EntityReader::count()} with limit. The limit option mustn't affect the count result. - */ - public function testCountWithLimit(): void - { - $this->fillFixtures(); - - $reader = (new EntityReader( - $this->select('user'), - ))->withLimit(1); - - self::assertSame(count(self::FIXTURES_USER), $reader->count()); - } - - /** - * Test {@see EntityReader::withLimit()} - */ - public function testLimit(): void - { - $this->fillFixtures(); - - $reader = (new EntityReader( - $this->select('user'), - )) - ->withLimit(2); - - self::assertEquals( - [(object)self::FIXTURES_USER[0], (object)self::FIXTURES_USER[1]], - $reader->read(), - ); - } - - /** - * Test {@see EntityReader::withLimit()} and {@see EntityReader::withOffset()} - */ - public function testLimitOffset(): void - { - $this->fillFixtures(); - - $reader = (new EntityReader( - $this->select('user'), - )) - ->withLimit(2)->withOffset(1); - - self::assertEquals( - [(object)self::FIXTURES_USER[1], (object)self::FIXTURES_USER[2]], - $reader->read(), - ); - } -} diff --git a/tests/Feature/Data/Reader/EntityReaderTest.php b/tests/Feature/Data/Reader/EntityReaderTest.php new file mode 100644 index 0000000..7e23590 --- /dev/null +++ b/tests/Feature/Data/Reader/EntityReaderTest.php @@ -0,0 +1,195 @@ +fillFixtures(); + + $reader = new EntityReader($this->select('user')); + + self::assertEquals(self::FIXTURES_USER[0], (array)$reader->readOne()); + } + + /** + * @covers ::read + */ + public function testRead(): void + { + $this->fillFixtures(); + + $reader = new EntityReader($this->select('user')); + + $result = $reader->read(); + self::assertEquals( + \array_map(static fn (array $data): \stdClass => (object) $data, self::FIXTURES_USER), + $result, + ); + } + + /** + * @covers ::withSort + */ + public function testWithSort(): void + { + $this->fillFixtures(); + + $reader = (new EntityReader( + $this->select('user'), + )) + // Reverse order + ->withSort(Sort::only(['id'])->withOrderString('-id')); + + $result = $reader->read(); + self::assertEquals( + \array_map(static fn (array $data): object => (object) $data, \array_reverse(self::FIXTURES_USER)), + $result, + ); + self::assertSame('-id', $reader->getSort()->getOrderAsString()); + } + + /** + * @covers ::count + */ + public function testCount(): void + { + $this->fillFixtures(); + + $reader = new EntityReader($this->select('user')); + + self::assertSame(count(self::FIXTURES_USER), $reader->count()); + } + + /** + * @covers ::count + * The limit option mustn't affect the count result. + */ + public function testCountWithLimit(): void + { + $this->fillFixtures(); + + $reader = (new EntityReader( + $this->select('user'), + ))->withLimit(1); + + self::assertSame(count(self::FIXTURES_USER), $reader->count()); + } + + /** + * @covers ::withLimit + */ + public function testLimit(): void + { + $this->fillFixtures(); + + $reader = (new EntityReader( + $this->select('user'), + )) + ->withLimit(2); + + self::assertEquals( + [(object)self::FIXTURES_USER[0], (object)self::FIXTURES_USER[1]], + $reader->read(), + ); + } + + /** + * @covers ::withLimit + */ + public function testLimitException(): void + { + $this->expectException(\InvalidArgumentException::class); + (new EntityReader($this->select('user')))->withLimit(-1); + } + + /** + * @covers ::withLimit + * @covers ::withOffset + */ + public function testLimitOffset(): void + { + $this->fillFixtures(); + + $reader = (new EntityReader( + $this->select('user'), + )) + ->withLimit(2)->withOffset(1); + + self::assertEquals( + [(object)self::FIXTURES_USER[1], (object)self::FIXTURES_USER[2]], + $reader->read(), + ); + } + + /** + * @covers ::withFilter + */ + public function testFilter(): void + { + $this->fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new Equals('id', 2)); + + self::assertEquals([(object)self::FIXTURES_USER[1]], $reader->read()); + } + + /** + * @covers ::withFilterHandlers + */ + public function testFilterHandlers(): void + { + $default = [ + 'and' => new FilterHandler\AllHandler(), + 'or' => new FilterHandler\AnyHandler(), + '=' => new FilterHandler\EqualsHandler(), + '>' => new FilterHandler\GreaterThanHandler(), + '>=' => new FilterHandler\GreaterThanOrEqualHandler(), + 'in' => new FilterHandler\InHandler(), + '<' => new FilterHandler\LessThanHandler(), + '<=' => new FilterHandler\LessThanOrEqualHandler(), + 'like' => new FilterHandler\LikeHandler(), + ]; + $custom = $this->createMock(FilterHandler\CompareHandler::class); + $custom->method('getOperator')->willReturn('custom'); + + $reader = new EntityReader($this->select('user')); + $ref = new \ReflectionProperty(EntityReader::class, 'filterHandlers'); + $ref->setAccessible(true); + + self::assertEquals($default, $ref->getValue($reader)); + $reader = $reader->withFilterHandlers($custom); + self::assertEquals($default + ['custom' => $custom], $ref->getValue($reader)); + } + + /** + * @covers ::getSql + */ + public function testGetSql(): void + { + $expected = 'SELECT "user"."id" AS "c0", "user"."email" AS "c1", "user"."balance" AS "c2" + FROM "user" AS "user" LIMIT 2 OFFSET 1'; + + $reader = (new EntityReader($this->select('user')))->withLimit(2)->withOffset(1); + + self::assertEquals( + \preg_replace('/\s+/', '', $expected), + \preg_replace('/\s+/', '', $reader->getSql()) + ); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/AllHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/AllHandlerTest.php new file mode 100644 index 0000000..e7e1ea9 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/AllHandlerTest.php @@ -0,0 +1,24 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter((new All())->withCriteriaArray([ + ['=', 'balance', '100.0'], + ['=', 'email', 'seed@beat'], + ])); + + $this->assertEquals([(object)self::FIXTURES_USER[2]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/AnyHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/AnyHandlerTest.php new file mode 100644 index 0000000..20f7dd0 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/AnyHandlerTest.php @@ -0,0 +1,24 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter((new Any())->withCriteriaArray([ + ['=', 'id', 2], + ['=', 'id', 3], + ])); + + $this->assertEquals([(object)self::FIXTURES_USER[1], (object)self::FIXTURES_USER[2]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/EqualsHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/EqualsHandlerTest.php new file mode 100644 index 0000000..10e4970 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/EqualsHandlerTest.php @@ -0,0 +1,21 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new Equals('id', 2)); + + $this->assertEquals([(object)self::FIXTURES_USER[1]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/GreaterThanHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/GreaterThanHandlerTest.php new file mode 100644 index 0000000..4d8f2a9 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/GreaterThanHandlerTest.php @@ -0,0 +1,21 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new GreaterThan('balance', 499)); + + $this->assertEquals([(object)self::FIXTURES_USER[3]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/GreaterThanOrEqualHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/GreaterThanOrEqualHandlerTest.php new file mode 100644 index 0000000..caeb305 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/GreaterThanOrEqualHandlerTest.php @@ -0,0 +1,22 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user'))) + ->withFilter(new GreaterThanOrEqual('balance', 500)); + + $this->assertEquals([(object)self::FIXTURES_USER[3]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/InHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/InHandlerTest.php new file mode 100644 index 0000000..546e767 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/InHandlerTest.php @@ -0,0 +1,21 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new In('id', [2, 3])); + + $this->assertEquals([(object)self::FIXTURES_USER[1], (object)self::FIXTURES_USER[2]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/LessThanHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/LessThanHandlerTest.php new file mode 100644 index 0000000..48a5a91 --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/LessThanHandlerTest.php @@ -0,0 +1,21 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new LessThan('balance', 1.1)); + + $this->assertEquals([(object)self::FIXTURES_USER[1]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/LessThanOrEqualHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/LessThanOrEqualHandlerTest.php new file mode 100644 index 0000000..7c3a7ce --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/LessThanOrEqualHandlerTest.php @@ -0,0 +1,21 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new LessThanOrEqual('balance', 1.0)); + + $this->assertEquals([(object)self::FIXTURES_USER[1]], $reader->read()); + } +} diff --git a/tests/Feature/Data/Reader/FilterHandler/LikeHandlerTest.php b/tests/Feature/Data/Reader/FilterHandler/LikeHandlerTest.php new file mode 100644 index 0000000..b30f72a --- /dev/null +++ b/tests/Feature/Data/Reader/FilterHandler/LikeHandlerTest.php @@ -0,0 +1,21 @@ +fillFixtures(); + + $reader = (new EntityReader($this->select('user')))->withFilter(new Like('email', 'seed@%')); + + $this->assertEquals([(object)self::FIXTURES_USER[2]], $reader->read()); + } +} diff --git a/tests/Feature/Data/EntityWriterTest.php b/tests/Feature/Data/Writer/EntityWriterTest.php similarity index 93% rename from tests/Feature/Data/EntityWriterTest.php rename to tests/Feature/Data/Writer/EntityWriterTest.php index 94bcffb..a631236 100644 --- a/tests/Feature/Data/EntityWriterTest.php +++ b/tests/Feature/Data/Writer/EntityWriterTest.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Cycle\Tests\Feature\Data; +namespace Yiisoft\Yii\Cycle\Tests\Feature\Data\Writer; use Cycle\ORM\EntityManagerInterface; use Yiisoft\Yii\Cycle\Data\Reader\EntityReader; use Yiisoft\Yii\Cycle\Data\Writer\EntityWriter; +use Yiisoft\Yii\Cycle\Tests\Feature\Data\BaseData; /** * @covers \Yiisoft\Yii\Cycle\Data\Writer\EntityWriter diff --git a/tests/Feature/Factory/MigratorFactoryTest.php b/tests/Feature/Factory/MigratorFactoryTest.php new file mode 100644 index 0000000..d093707 --- /dev/null +++ b/tests/Feature/Factory/MigratorFactoryTest.php @@ -0,0 +1,55 @@ + 'default', + 'databases' => ['default' => ['connection' => 'sqlite']], + 'connections' => [ + 'sqlite' => new SQLiteDriverConfig(connection: new MemoryConnectionConfig()), + ], + ])); + + $this->assertFalse($db->database('default')->hasTable($defaultConfig->getTable())); + + $factory = new MigratorFactory(); + $migrator = $factory(new SimpleContainer([ + MigrationConfig::class => $defaultConfig, + DatabaseManager::class => $db, + FactoryInterface::class => $this->createMock(FactoryInterface::class), + ])); + + $configRef = new \ReflectionProperty($migrator, 'config'); + $configRef->setAccessible(true); + + $dbalRef = new \ReflectionProperty($migrator, 'dbal'); + $dbalRef->setAccessible(true); + + $repositoryRef = new \ReflectionProperty($migrator, 'repository'); + $repositoryRef->setAccessible(true); + + $this->assertTrue($db->database('default')->hasTable($defaultConfig->getTable())); + $this->assertSame($defaultConfig, $configRef->getValue($migrator)); + $this->assertSame($db, $dbalRef->getValue($migrator)); + $this->assertInstanceOf(FileRepository::class, $repositoryRef->getValue($migrator)); + } +} diff --git a/tests/Unit/Factory/CycleDynamicFactoryTest.php b/tests/Unit/Factory/CycleDynamicFactoryTest.php new file mode 100644 index 0000000..8b1d98f --- /dev/null +++ b/tests/Unit/Factory/CycleDynamicFactoryTest.php @@ -0,0 +1,27 @@ + $this->createMock(ConnectionConfig::class), + ]))); + + $this->assertInstanceOf( + FakeDriverConfig::class, + $factory->make(FakeDriverConfig::class, ['driver' => 'foo']) + ); + } +} diff --git a/tests/Unit/Factory/MigrationConfigFactoryTest.php b/tests/Unit/Factory/MigrationConfigFactoryTest.php new file mode 100644 index 0000000..93cd407 --- /dev/null +++ b/tests/Unit/Factory/MigrationConfigFactoryTest.php @@ -0,0 +1,34 @@ + 'test/foo/bar']); + + $this->assertEquals( + new MigrationConfig(['directory' => 'test/foo/bar']), + $factory(new SimpleContainer([Aliases::class => new Aliases()])) + ); + } + + public function testInvoke(): void + { + $factory = new MigrationConfigFactory(['directory' => '@test/foo/bar']); + + $this->assertEquals( + new MigrationConfig(['directory' => 'src/test/app/foo/bar']), + $factory(new SimpleContainer([Aliases::class => new Aliases(['@test' => 'src/test/app'])])) + ); + } +} diff --git a/tests/Unit/Factory/RepositoryContainerTest.php b/tests/Unit/Factory/RepositoryContainerTest.php new file mode 100644 index 0000000..8802d90 --- /dev/null +++ b/tests/Unit/Factory/RepositoryContainerTest.php @@ -0,0 +1,113 @@ +schema = new Schema([ + 'foo' => [ + Schema::ENTITY => FakeEntity::class, + Schema::REPOSITORY => FakeRepository::class, + ], + ]); + } + + public function testGetNotFoundException(): void + { + $orm = $this->createMock(ORMInterface::class); + $orm->expects($this->once())->method('getSchema')->willReturn(new Schema([])); + + $container = new RepositoryContainer(new SimpleContainer([ORMInterface::class => $orm])); + + $this->expectException(NotFoundException::class); + $container->get(FakeRepository::class); + } + + public function testGetNotInstantiableClassException(): void + { + $container = new RepositoryContainer(new SimpleContainer([ + ORMInterface::class => $this->createMock(ORMInterface::class), + ])); + + $this->expectException(NotInstantiableClassException::class); + $container->get('foo'); + } + + public function testMakeAndGet(): void + { + $orm = $this->createMock(ORMInterface::class); + $orm->expects($this->once())->method('getSchema')->willReturn($this->schema); + $orm->expects($this->once()) + ->method('getRepository') + ->with('foo') + ->willReturn(new FakeRepository($this->createMock(Select::class))); + + $container = new RepositoryContainer(new SimpleContainer([ORMInterface::class => $orm])); + $instancesRef = new \ReflectionProperty($container, 'instances'); + $instancesRef->setAccessible(true); + + $this->assertSame([], $instancesRef->getValue($container)); + $this->assertInstanceOf(FakeRepository::class, $container->get(FakeRepository::class)); + } + + public function testGetFromInstances(): void + { + $orm = $this->createMock(ORMInterface::class); + $orm->expects($this->once())->method('getSchema')->willReturn($this->schema); + $orm->expects($this->once()) + ->method('getRepository') + ->with('foo') + ->willReturn(new FakeRepository($this->createMock(Select::class))); + + $container = new RepositoryContainer(new SimpleContainer([ORMInterface::class => $orm])); + $instancesRef = new \ReflectionProperty($container, 'instances'); + $instancesRef->setAccessible(true); + + $this->assertSame([], $instancesRef->getValue($container)); + $repository = $container->get(FakeRepository::class); + $this->assertInstanceOf(FakeRepository::class, $repository); + + $this->assertSame([FakeRepository::class => $repository], $instancesRef->getValue($container)); + $this->assertSame($repository, $container->get(FakeRepository::class)); + } + + public function testHasNonInterface(): void + { + $orm = $this->createMock(ORMInterface::class); + $orm->expects($this->never())->method('getSchema'); + + $container = new RepositoryContainer(new SimpleContainer([ORMInterface::class => $orm])); + + $this->assertFalse($container->has('foo')); + } + + public function testHas(): void + { + $orm = $this->createMock(ORMInterface::class); + $orm->expects($this->once())->method('getSchema')->willReturn($this->schema); + + $container = new RepositoryContainer(new SimpleContainer([ORMInterface::class => $orm])); + + $repository = $this->createMock(RepositoryInterface::class); + $this->assertTrue($container->has(FakeRepository::class)); + $this->assertFalse($container->has($repository::class)); + } +} diff --git a/tests/Unit/Stub/FakeEntity.php b/tests/Unit/Stub/FakeEntity.php new file mode 100644 index 0000000..82a3062 --- /dev/null +++ b/tests/Unit/Stub/FakeEntity.php @@ -0,0 +1,9 @@ +