From 37e48393797ce2e00a3132f78593f4f5bb7c221f Mon Sep 17 00:00:00 2001 From: Wilmer Arambula <42547589+terabytesoftw@users.noreply.github.com> Date: Wed, 23 Nov 2022 09:16:36 -0300 Subject: [PATCH] Test 2: Move src/TestSupport to tests/ * Move src/TestSupport to tests/. --- .github/workflows/build.yml | 2 +- tests/AbstractCommandTest.php | 755 ++++++++++++++++++ tests/Command/CommandTest.php | 229 ++++++ tests/Command/ParamBuilderTest.php | 2 + tests/Connection/DsnTest.php | 2 + tests/Constraint/CheckConstraintTest.php | 2 + tests/Constraint/ConstraintTest.php | 2 + .../Constraint/DefaultValueConstraintTest.php | 2 + tests/Constraint/ForeignKeyConstraintTest.php | 2 + tests/Constraint/IndexConstraintTest.php | 2 + tests/Driver/PDO/PDODriverTest.php | 2 + tests/Expression/ArrayExpressionTest.php | 2 + tests/Expression/JsonExpressionTest.php | 4 + tests/Provider/BaseCommandProvider.php | 453 +++++++++++ tests/Provider/BaseQueryBuilderProvider.php | 73 ++ tests/Provider/CommandProvider.php | 40 + tests/Provider/QueryBuilderProvider.php | 52 +- tests/Provider/QuoterProvider.php | 93 +-- tests/{ => Query/Helper}/QueryHelperTest.php | 15 +- .../Condition/AndConditionTest.php | 2 + .../Condition/BetweenColumnsConditionTest.php | 6 + .../Condition/BetweenConditionTest.php | 4 + .../Builder/LikeConditionBuilderTest.php | 2 + .../Condition/ExistsConditionTest.php | 3 + .../Condition/HashConditionTest.php | 2 + .../Condition/InConditionTest.php | 12 +- .../Condition/LikeConditionTest.php | 5 + .../Condition/NotConditionTest.php | 4 + .../Condition/SimpleConditionTest.php | 7 + tests/Schema/QuoterTest.php | 8 +- tests/Support/Assert.php | 4 + tests/Support/DbHelper.php | 75 ++ tests/Support/Fixture/customer.sql | 13 + tests/Support/IsOneOfAssert.php | 43 + tests/Support/Stub/Command.php | 4 +- tests/Support/Stub/Connection.php | 2 +- tests/Support/Stub/PDODriver.php | 6 - tests/Support/Stub/Schema.php | 27 +- tests/Support/Stub/Transaction.php | 3 +- tests/Support/TestTrait.php | 36 +- 40 files changed, 1820 insertions(+), 182 deletions(-) create mode 100644 tests/AbstractCommandTest.php create mode 100644 tests/Command/CommandTest.php create mode 100644 tests/Provider/BaseCommandProvider.php create mode 100644 tests/Provider/BaseQueryBuilderProvider.php create mode 100644 tests/Provider/CommandProvider.php rename tests/{ => Query/Helper}/QueryHelperTest.php (93%) create mode 100644 tests/Support/DbHelper.php create mode 100644 tests/Support/Fixture/customer.sql create mode 100644 tests/Support/IsOneOfAssert.php diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7d75e957..a1c7a6aa4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: name: PHP ${{ matrix.php }}-${{ matrix.os }}-db env: - COMPOSER_ROOT_VERSION: dev-master + extensions: pdo, pdo_sqlite runs-on: ${{ matrix.os }} diff --git a/tests/AbstractCommandTest.php b/tests/AbstractCommandTest.php new file mode 100644 index 000000000..f7920d40b --- /dev/null +++ b/tests/AbstractCommandTest.php @@ -0,0 +1,755 @@ +getConnection(); + + $command = $db->createCommand(); + $sql = $command->addCheck('name', 'table', 'id > 0')->getSql(); + + + $this->assertSame( + DbHelper::replaceQuotes( + << 0) + SQL, + $db->getName(), + ), + $sql, + ); + } + + public function testAddColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addColumn('table', 'column', Schema::TYPE_INTEGER)->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddCommentOnColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addCommentOnColumn('customer', 'id', 'Primary key.')->getSql(); + + $this->assertStringContainsString( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddCommentOnTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addCommentOnTable('table', 'comment')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddForeignKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addForeignKey('name', 'table', 'column', 'ref_table', 'ref_column')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + + $sql = $command->addForeignKey('name', 'table', 'column', 'ref_table', 'ref_column', 'CASCADE')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + + $sql = $command + ->addForeignKey('name', 'table', 'column', 'ref_table', 'ref_column', 'CASCADE', 'CASCADE') + ->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddPrimaryKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addPrimaryKey('name', 'table', 'column')->getSql(); + + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + + $sql = $command->addPrimaryKey('name', 'table', ['column1', 'column2'])->getSql(); + + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAddUnique(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->addUnique('name', 'table', 'column')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + + $sql = $command->addUnique('name', 'table', ['column1', 'column2'])->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testAlterColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->alterColumn('table', 'column', Schema::TYPE_INTEGER)->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testBindValues(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $values = ['int' => 1, 'string' => 'str']; + $command->bindValues($values); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(2, $bindedValues); + + $param = new Param('str', 99); + $command->bindValues(['param' => $param]); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(3, $bindedValues); + $this->assertSame($param, $bindedValues['param']); + $this->assertNotEquals($param, $bindedValues['int']); + + /* Replace test */ + $command->bindValues(['int' => $param]); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(3, $bindedValues); + $this->assertSame($param, $bindedValues['int']); + } + + public function testCache(): void + { + $db = $this->getConnection(); + + $tagDependency = new TagDependency('tag'); + $command = $db->createCommand(); + $command->cache(100, $tagDependency); + + $this->assertInstanceOf(CommandInterface::class, $command); + $this->assertSame(100, Assert::getInaccessibleProperty($command, 'queryCacheDuration')); + $this->assertSame($tagDependency, Assert::getInaccessibleProperty($command, 'queryCacheDependency')); + } + + public function testConstruct(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->assertEmpty($command->getSql()); + + $sql = <<createCommand($sql, [':name' => 'John']); + + $this->assertSame($sql, $command->getSql()); + $this->assertSame([':name' => 'John'], $command->getParams()); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::createIndex() + */ + public function testCreateIndex( + string $name, + string $table, + array|string $column, + string $indexType, + string $indexMethod, + string $expected, + ): void { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $sql = $command->createIndex($name, $table, $column, $indexType, $indexMethod)->getSql(); + + $this->assertSame($expected, $sql); + } + + public function testCreateView(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $sql = $command->createView( + 'view', + <<getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDataReaderCreationException(): void + { + $db = $this->getConnection(); + + $this->expectException(InvalidParamException::class); + $this->expectExceptionMessage('The PDOStatement cannot be null.'); + + $sql = 'SELECT * FROM {{customer}}'; + new DataReader($db->createCommand($sql)); + } + + public function testDelete(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->delete('table', ['column' => 'value'])->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropCheck(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropCheck('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropColumn('table', 'column')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropCommentFromColumn(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropCommentFromColumn('table', 'column')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropCommentFromTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropCommentFromTable('table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropForeingKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropForeignKey('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropIndex(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropIndex('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropPrimaryKey(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropPrimaryKey('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropTable('table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropView(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropView('view')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testDropUnique(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->dropUnique('name', 'table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testExecuteWithSqlEmtpy(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->assertSame(0, $command->execute()); + } + + public function testGetParams(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $values = [ + 'int' => 1, + 'string' => 'str', + ]; + $command->bindValues($values); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(2, $bindedValues); + + $param = new Param('str', 99); + $command->bindValues(['param' => $param]); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(3, $bindedValues); + $this->assertEquals($param, $bindedValues['param']); + $this->assertNotEquals($param, $bindedValues['int']); + + /* Replace test */ + $command->bindValues(['int' => $param]); + $bindedValues = $command->getParams(false); + + $this->assertIsArray($bindedValues); + $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues); + $this->assertCount(3, $bindedValues); + $this->assertEquals($param, $bindedValues['int']); + } + + /** + * Test command getRawSql. + * + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::rawSql() + * + * @throws Exception + * @throws InvalidConfigException + * @throws NotSupportedException + * + * {@see https://github.com/yiisoft/yii2/issues/8592} + */ + public function testGetRawSql(string $sql, array $params, string $expectedRawSql): void + { + $db = $this->getConnection(); + + $command = $db->createCommand($sql, $params); + + $this->assertSame($expectedRawSql, $command->getRawSql()); + } + + public function testGetSetSql(): void + { + $db = $this->getConnection(); + + $sql = <<createCommand($sql); + $this->assertSame($sql, $command->getSql()); + + $sql2 = <<setSql($sql2); + $this->assertSame($sql2, $command->getSql()); + } + + public function testLastInsertIdException(): void + { + $db = $this->getConnection(); + + $db->close(); + + $this->expectException(InvalidCallException::class); + + $db->getLastInsertID(); + } + + public function testNoCache(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand()->noCache(); + + $this->assertSame(-1, Assert::getInaccessibleProperty($command, 'queryCacheDuration')); + $this->assertInstanceOf(CommandInterface::class, $command); + } + + public function testPrepareCancel(): void + { + $db = $this->getConnection('customer'); + + $command = $db->createCommand(); + $command->setSql( + <<assertNull($command->getPdoStatement()); + + $command->prepare(); + + $this->assertNotNull($command->getPdoStatement()); + + $command->cancel(); + + $this->assertNull($command->getPdoStatement()); + } + + public function testRenameColumn(): void + { + $db = $this->getConnection(); + + $sql = $db->createCommand()->renameColumn('table', 'oldname', 'newname')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testSetRawSql(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $command->setRawSql( + <<assertSame('SELECT 123', $command->getRawSql()); + } + + public function testSetRetryHandler(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $handler = static fn (): bool => true; + + Assert::invokeMethod($command, 'setRetryHandler', [$handler]); + + $this->assertSame($handler, Assert::getInaccessibleProperty($command, 'retryHandler')); + } + + public function testTruncateTable(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + $sql = $command->truncateTable('table')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + protected function performAndCompareUpsertResult(ConnectionPDOInterface $db, array $data): void + { + $params = $data['params']; + $expected = $data['expected'] ?? $params[1]; + + $command = $db->createCommand(); + + call_user_func_array([$command, 'upsert'], $params); + + $command->execute(); + + $actual = (new Query($db)) + ->select(['email', 'address' => new Expression($this->upsertTestCharCast), 'status']) + ->from('T_upsert') + ->one(); + $this->assertEquals($expected, $actual, $this->upsertTestCharCast); + } +} diff --git a/tests/Command/CommandTest.php b/tests/Command/CommandTest.php new file mode 100644 index 000000000..3a4824859 --- /dev/null +++ b/tests/Command/CommandTest.php @@ -0,0 +1,229 @@ +getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\DDLQueryBuilder does not support adding default value constraints.' + ); + + $command->addDefaultValue('name', 'table', 'column', 'value'); + } + + public function testBatchInsert(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema() is not supported by core-db.' + ); + + $command->batchInsert('table', ['column1', 'column2'], [['value1', 'value2'], ['value3', 'value4']]); + } + + public function testCheckIntegrity(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\DDLQueryBuilder does not support enabling/disabling integrity check.' + ); + + $command->checkIntegrity('schema', 'table')->execute(); + } + + public function testCreateTable(): void + { + $this->db = $this->getConnection(); + + $command = $this->db->createCommand(); + + $expected = << $this->primaryKey(5), + 'name' => $this->string(255)->notNull(), + 'email' => $this->string(255)->notNull(), + 'address' => $this->string(255)->notNull(), + 'status' => $this->integer()->notNull(), + 'profile_id' => $this->integer()->notNull(), + 'created_at' => $this->timestamp()->notNull(), + 'updated_at' => $this->timestamp()->notNull(), + ]; + $options = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; + $sql = $command->createTable('test_table', $columns, $options)->getSql(); + + Assert::equalsWithoutLE($expected, $sql); + } + + public function testDropDefaultValue(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\DDLQueryBuilder does not support dropping default value constraints.' + ); + + $command->dropDefaultValue('column', 'table'); + } + + public function testExecute(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute() is not supported by core-db.' + ); + + $command->createTable('customer', ['id' => 'pk'])->execute(); + } + + public function testInsert(): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Schema::loadTableSchema() is not supported by core-db.' + ); + + $command->insert('customer', ['email' => 't1@example.com', 'name' => 'test', 'address' => 'test address']); + } + + public function testQuery(): void + { + $db = $this->getConnection('customer'); + + $command = $db->createCommand(); + $command->setSql( + <<expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute() is not supported by core-db.' + ); + + $command->query(); + } + + public function testQueryAll(): void + { + $db = $this->getConnection('customer'); + + $command = $db->createCommand(); + $command->setSql( + <<expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute() is not supported by core-db.' + ); + + $command->queryAll(); + } + + public function testRenameTable(): void + { + $db = $this->getConnection(); + + $sql = $db->createCommand()->renameTable('table', 'newname')->getSql(); + + $this->assertSame( + DbHelper::replaceQuotes( + <<getName(), + ), + $sql, + ); + } + + public function testResetSequence(): void + { + $db = $this->getConnection(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\DMLQueryBuilder does not support resetting sequence.' + ); + + $db->createCommand()->resetSequence('table', 5); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert() + * + * @throws Exception + * @throws InvalidConfigException + * @throws NotSupportedException + */ + public function testUpsert(array $firstData, array $secondData): void + { + $db = $this->getConnection(); + + $command = $db->createCommand(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + 'Yiisoft\Db\Tests\Support\Stub\DMLQueryBuilder does not support upsert.' + ); + + $command->upsert('table', $firstData); + } +} diff --git a/tests/Command/ParamBuilderTest.php b/tests/Command/ParamBuilderTest.php index b2bf774e3..5885b0c9f 100644 --- a/tests/Command/ParamBuilderTest.php +++ b/tests/Command/ParamBuilderTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class ParamBuilderTest extends TestCase { diff --git a/tests/Connection/DsnTest.php b/tests/Connection/DsnTest.php index 6e81b53f2..a0a09083e 100644 --- a/tests/Connection/DsnTest.php +++ b/tests/Connection/DsnTest.php @@ -9,6 +9,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class DsnTest extends TestCase { diff --git a/tests/Constraint/CheckConstraintTest.php b/tests/Constraint/CheckConstraintTest.php index 50e66cd46..08df3a220 100644 --- a/tests/Constraint/CheckConstraintTest.php +++ b/tests/Constraint/CheckConstraintTest.php @@ -9,6 +9,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class CheckConstraintTest extends TestCase { diff --git a/tests/Constraint/ConstraintTest.php b/tests/Constraint/ConstraintTest.php index c15e6400c..0256dd377 100644 --- a/tests/Constraint/ConstraintTest.php +++ b/tests/Constraint/ConstraintTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class ConstraintTest extends TestCase { diff --git a/tests/Constraint/DefaultValueConstraintTest.php b/tests/Constraint/DefaultValueConstraintTest.php index 01a847b19..cb9268441 100644 --- a/tests/Constraint/DefaultValueConstraintTest.php +++ b/tests/Constraint/DefaultValueConstraintTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class DefaultValueConstraintTest extends TestCase { diff --git a/tests/Constraint/ForeignKeyConstraintTest.php b/tests/Constraint/ForeignKeyConstraintTest.php index 6b6606ea5..426dcb2c0 100644 --- a/tests/Constraint/ForeignKeyConstraintTest.php +++ b/tests/Constraint/ForeignKeyConstraintTest.php @@ -9,6 +9,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class ForeignKeyConstraintTest extends TestCase { diff --git a/tests/Constraint/IndexConstraintTest.php b/tests/Constraint/IndexConstraintTest.php index 72bdaa49d..6c8393529 100644 --- a/tests/Constraint/IndexConstraintTest.php +++ b/tests/Constraint/IndexConstraintTest.php @@ -9,6 +9,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class IndexConstraintTest extends TestCase { diff --git a/tests/Driver/PDO/PDODriverTest.php b/tests/Driver/PDO/PDODriverTest.php index 7bd42f68e..de3fca56f 100644 --- a/tests/Driver/PDO/PDODriverTest.php +++ b/tests/Driver/PDO/PDODriverTest.php @@ -11,6 +11,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class PDODriverTest extends TestCase { diff --git a/tests/Expression/ArrayExpressionTest.php b/tests/Expression/ArrayExpressionTest.php index f680633f0..900054f2c 100644 --- a/tests/Expression/ArrayExpressionTest.php +++ b/tests/Expression/ArrayExpressionTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class ArrayExpressionTest extends TestCase { diff --git a/tests/Expression/JsonExpressionTest.php b/tests/Expression/JsonExpressionTest.php index 60b5b0eb3..05cfe0ec4 100644 --- a/tests/Expression/JsonExpressionTest.php +++ b/tests/Expression/JsonExpressionTest.php @@ -10,8 +10,12 @@ use Yiisoft\Db\Query\Query; use Yiisoft\Db\Tests\Support\TestTrait; +use function json_encode; + /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class JsonExpressionTest extends TestCase { diff --git a/tests/Provider/BaseCommandProvider.php b/tests/Provider/BaseCommandProvider.php new file mode 100644 index 000000000..ec27e1c3c --- /dev/null +++ b/tests/Provider/BaseCommandProvider.php @@ -0,0 +1,453 @@ + [ + 'type', + ['int_col', 'float_col', 'char_col', 'bool_col'], + 'values' => [ + ['0', '0.0', 'test string', true], + [false, 0, 'test string2', false], + ], + 'expected' => DbHelper::replaceQuotes( + <<getName(), + ), + 'expectedParams' => [ + ':qp0' => 0, + ':qp1' => 0.0, + ':qp2' => 'test string', + ':qp3' => true, + ':qp4' => 0, + ':qp5' => 0.0, + ':qp6' => 'test string2', + ':qp7' => false, + ], + 2, + ], + 'issue11242' => [ + 'type', + ['int_col', 'float_col', 'char_col', 'bool_col'], + 'values' => [[1.0, 1.1, 'Kyiv {{city}}, Ukraine', true]], + /** + * {@see https://github.com/yiisoft/yii2/issues/11242} + * + * Make sure curly bracelets (`{{..}}`) in values will not be escaped + */ + 'expected' => DbHelper::replaceQuotes( + <<getName(), + ), + 'expectedParams' => [ + ':qp0' => 1, + ':qp1' => 1.1, + ':qp2' => 'Kyiv {{city}}, Ukraine', + ':qp3' => true, + ], + ], + 'wrongBehavior' => [ + '{{%type}}', + ['{{%type}}.[[int_col]]', '[[float_col]]', 'char_col', 'bool_col'], + 'values' => [['0', '0.0', 'Kyiv {{city}}, Ukraine', false]], + /** + * Test covers potentially wrong behavior and marks it as expected!. + * + * In case table name or table column is passed with curly or square bracelets, QueryBuilder can not + * determine the table schema and typecast values properly. + * TODO: make it work. Impossible without BC breaking for public methods. + */ + 'expected' => DbHelper::replaceQuotes( + <<getName(), + ), + 'expectedParams' => [ + ':qp0' => '0', + ':qp1' => '0.0', + ':qp2' => 'Kyiv {{city}}, Ukraine', + ':qp3' => false, + ], + ], + 'batchInsert binds params from expression' => [ + '{{%type}}', + ['int_col', 'float_col', 'char_col', 'bool_col'], + /** + * This example is completely useless. This feature of batchInsert is intended to be used with complex + * expression objects, such as JsonExpression. + */ + 'values' => [[new Expression(':exp1', [':exp1' => 42]), 1, 'test', false]], + 'expected' => DbHelper::replaceQuotes( + <<getName(), + ), + 'expectedParams' => [ + ':exp1' => 42, + ':qp1' => 1.0, + ':qp2' => 'test', + ':qp3' => false, + ], + ], + ]; + } + + public function createIndex(ConnectionPDOInterface $db): array + { + return [ + [ + 'name', + 'table', + 'column', + '', + '', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'name', + 'table', + ['column1', 'column2'], + '', + '', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'name', + 'table', + ['column1', 'column2'], + QueryBuilder::INDEX_UNIQUE, + '', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'name', + 'table', + ['column1', 'column2'], + 'FULLTEXT', + '', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'name', + 'table', + ['column1', 'column2'], + 'SPATIAL', + '', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'name', + 'table', + ['column1', 'column2'], + 'BITMAP', + '', + DbHelper::replaceQuotes( + <<getName(), + ), + ], + ]; + } + + public function rawSql(): array + { + return [ + [ + << 1], + << 1], + << null], + << 1, 'basePrefix' => 2], + << false], + << new Expression(implode(', ', [1, 2]))], + << 'test'], + [], + [], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'table', + ['name' => 'test'], + ['id' => 1], + [], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'table', + ['name' => 'test'], + ['id' => 1], + ['id' => 'integer'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'table', + ['name' => 'test'], + ['id' => 1], + ['id' => 'string'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'table', + ['name' => 'test'], + ['id' => 1], + ['id' => 'boolean'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + [ + 'table', + ['name' => 'test'], + ['id' => 1], + ['id' => 'float'], + DbHelper::replaceQuotes( + <<getName(), + ), + ], + ]; + } + + public function upsert(ConnectionPDOInterface $db): array + { + return [ + 'regular values' => [ + ['params' => ['T_upsert', ['email' => 'foo@example.com', 'address' => 'Earth', 'status' => 3]]], + ['params' => ['T_upsert', ['email' => 'foo@example.com', 'address' => 'Universe', 'status' => 1]]], + ], + 'regular values with update part' => [ + ['params' => [ + 'T_upsert', + ['email' => 'foo@example.com', 'address' => 'Earth', 'status' => 3], + ['address' => 'Moon', 'status' => 2], + ], + ], + [ + 'params' => [ + 'T_upsert', + ['email' => 'foo@example.com', 'address' => 'Universe', 'status' => 1], + ['address' => 'Moon', 'status' => 2], + ], + 'expected' => ['email' => 'foo@example.com', 'address' => 'Moon', 'status' => 2], + ], + ], + 'regular values without update part' => [ + ['params' => ['T_upsert', ['email' => 'foo@example.com', 'address' => 'Earth', 'status' => 3], false]], + [ + 'params' => [ + 'T_upsert', + ['email' => 'foo@example.com', 'address' => 'Universe', 'status' => 1], + false, + ], + 'expected' => ['email' => 'foo@example.com', 'address' => 'Earth', 'status' => 3], + ], + ], + 'query' => [ + [ + 'params' => [ + 'T_upsert', + (new query($db)) + ->select(['email', 'address', 'status' => new Expression('1')]) + ->from('customer') + ->where(['name' => 'user1']) + ->limit(1), + ], + 'expected' => ['email' => 'user1@example.com', 'address' => 'address1', 'status' => 1], + ], + [ + 'params' => [ + 'T_upsert', + (new query($db)) + ->select(['email', 'address', 'status' => new Expression('2')]) + ->from('customer') + ->where(['name' => 'user1']) + ->limit(1), + ], + 'expected' => ['email' => 'user1@example.com', 'address' => 'address1', 'status' => 2], + ], + ], + 'query with update part' => [ + [ + 'params' => [ + 'T_upsert', + (new query($db)) + ->select(['email', 'address', 'status' => new Expression('1')]) + ->from('customer') + ->where(['name' => 'user1']) + ->limit(1), + ['address' => 'Moon', 'status' => 2], + ], + 'expected' => ['email' => 'user1@example.com', 'address' => 'address1', 'status' => 1], + ], + [ + 'params' => [ + 'T_upsert', + (new query($db)) + ->select(['email', 'address', 'status' => new Expression('3')]) + ->from('customer') + ->where(['name' => 'user1']) + ->limit(1), + ['address' => 'Moon', 'status' => 2], + ], + 'expected' => ['email' => 'user1@example.com', 'address' => 'Moon', 'status' => 2], + ], + ], + 'query without update part' => [ + [ + 'params' => [ + 'T_upsert', + (new query($db)) + ->select(['email', 'address', 'status' => new Expression('1')]) + ->from('customer') + ->where(['name' => 'user1']) + ->limit(1), + false, + ], + 'expected' => ['email' => 'user1@example.com', 'address' => 'address1', 'status' => 1], + ], + [ + 'params' => [ + 'T_upsert', + (new query($db)) + ->select(['email', 'address', 'status' => new Expression('2')]) + ->from('customer') + ->where(['name' => 'user1']) + ->limit(1), + false, + ], + 'expected' => ['email' => 'user1@example.com', 'address' => 'address1', 'status' => 1], + ], + ], + ]; + } +} diff --git a/tests/Provider/BaseQueryBuilderProvider.php b/tests/Provider/BaseQueryBuilderProvider.php new file mode 100644 index 000000000..9c94ff1bf --- /dev/null +++ b/tests/Provider/BaseQueryBuilderProvider.php @@ -0,0 +1,73 @@ +getname(), + ), + ], + [ + ['table1'], + DbHelper::replaceQuotes( + <<getname(), + ), + ], + [ + new Expression('table2'), + << 'table3'], + DbHelper::replaceQuotes( + <<getname(), + ), + ], + [ + ['alias' => new Expression('table4')], + << new Expression('func(:param1, :param2)', ['param1' => 'A', 'param2' => 'B'])], + DbHelper::replaceQuotes( + <<getname(), + ), + ['param1' => 'A', 'param2' => 'B'], + ], + ]; + } +} diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php new file mode 100644 index 000000000..8b6ea1610 --- /dev/null +++ b/tests/Provider/CommandProvider.php @@ -0,0 +1,40 @@ +createIndex($this->getConnection()); + } + + public function rawSql(): array + { + $commandProvider = new BaseCommandProvider(); + + return $commandProvider->rawSql(); + } + + public function update(): array + { + $commandProvider = new BaseCommandProvider(); + + return $commandProvider->update($this->getConnection()); + } + + public function upsert(): array + { + $commandProvider = new BaseCommandProvider(); + + return $commandProvider->upsert($this->getConnection()); + } +} diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index cbac129c9..c505c65c7 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -4,56 +4,16 @@ namespace Yiisoft\Db\Tests\Provider; -use Yiisoft\Db\Expression\Expression; +use Yiisoft\Db\Tests\Support\TestTrait; final class QueryBuilderProvider { + use TestTrait; + public function buildFrom(): array { - return [ - [ - 'table1', - << 'table3'], - << new Expression('table4')], - << new Expression('func(:param1, :param2)', ['param1' => 'A', 'param2' => 'B'])], - << 'A', 'param2' => 'B'], - ], - ]; + $baseQueryBuilderProvider = new BaseQueryBuilderProvider(); + + return $baseQueryBuilderProvider->buildFrom($this->getConnection()); } } diff --git a/tests/Provider/QuoterProvider.php b/tests/Provider/QuoterProvider.php index 943909181..f7d999539 100644 --- a/tests/Provider/QuoterProvider.php +++ b/tests/Provider/QuoterProvider.php @@ -12,57 +12,9 @@ final class QuoterProvider public function columnName(): array { return [ - ['column', 'column'], - ['`column`', '`column`'], - ['[[column]]', '[[column]]'], - ['{{column}}', '{{column}}'], - ]; - } - - /** - * @return string[][] - */ - public function ensureColumnName(): array - { - return [ + ['test', '[test]'], + ['[test]', '[test]'], ['*', '*'], - ['`*`', '`*`'], - ['[[*]]', '[*]'], - ['{{*}}', '{*}'], - ['table.column', 'column'], - ['`table`.`column`', '`column`'], - ['[[table]].[[column]]', 'column'], - ['{{table}}.{{column}}', '{column}'], - ]; - } - - /** - * @return string[][] - */ - public function ensureNameQuoted(): array - { - return [ - ['name', '{name}'], - ['`name`', '{name}'], - ['[[name]]', '{name}'], - ['{{name}}', '{name}'], - ['table.name', '{table.name}'], - ['`table`.`name`', '{table.name}'], - ['[[table]].[[name]]', '{table.name}'], - ['{{table}}.{{name}}', '{table}.{name}'], - ]; - } - - /** - * @return string[][] - */ - public function simpleColumnName(): array - { - return [ - ['column', 'column'], - ['`column`', '`column`'], - ['[[column]]', '[[column]]'], - ['{{column}}', '{{column}}'], ]; } @@ -72,10 +24,12 @@ public function simpleColumnName(): array public function simpleTableName(): array { return [ - ['table', 'table'], - ['`table`', '`table`'], - ['[[table]]', '[[table]]'], - ['{{table}}', '{{table}}'], + ['test', '[test]', ], + ['te`st', '[te`st]', ], + ['te\'st', '[te\'st]', ], + ['te"st', '[te"st]', ], + ['current-table-name', '[current-table-name]'], + ['[current-table-name]', '[current-table-name]'], ]; } @@ -85,11 +39,10 @@ public function simpleTableName(): array public function tableName(): array { return [ - ['table', 'table'], - ['`table`', '`table`'], - ['(table)', '(table)'], - ['[[table]]', '[[table]]'], - ['{{table}}', '{{table}}'], + ['test', '[test]'], + ['test.test', '[test].[test]'], + ['[test]', '[test]'], + ['[test].[test]', '[test].[test]'], ]; } @@ -99,7 +52,12 @@ public function tableName(): array public function tableNameParts(): array { return [ - ['`schema`.`table`', ['schema', 'table']], + ['animal', 'animal',], + ['dbo.animal', 'animal', 'dbo'], + ['[dbo].[animal]', 'animal', 'dbo'], + ['[other].[animal2]', 'animal2', 'other'], + ['other.[animal2]', 'animal2', 'other'], + ['other.animal2', 'animal2', 'other'], ]; } @@ -109,9 +67,9 @@ public function tableNameParts(): array public function unquoteSimpleColumnName(): array { return [ - ['`column`', 'column'], - ['[[column]]', '[column]'], - ['{{column}}', '{column}'], + ['test', 'test'], + ['[test]', 'test'], + ['*', '*'], ]; } @@ -121,9 +79,12 @@ public function unquoteSimpleColumnName(): array public function unquoteSimpleTableName(): array { return [ - ['`table`', 'table'], - ['[[table]]', '[table]'], - ['{{table}}', '{table}'], + ['[test]', 'test'], + ['[te`st]', 'te`st'], + ['[te\'st]', 'te\'st'], + ['[te"st]', 'te"st'], + ['[current-table-name]', 'current-table-name'], + ['[current-table-name]', 'current-table-name'], ]; } } diff --git a/tests/QueryHelperTest.php b/tests/Query/Helper/QueryHelperTest.php similarity index 93% rename from tests/QueryHelperTest.php rename to tests/Query/Helper/QueryHelperTest.php index 1761ef653..28faeba36 100644 --- a/tests/QueryHelperTest.php +++ b/tests/Query/Helper/QueryHelperTest.php @@ -2,15 +2,17 @@ declare(strict_types=1); -namespace Yiisoft\Db\Tests; +namespace Yiisoft\Db\Tests\Query\Helper; use PHPUnit\Framework\TestCase; -use Yiisoft\Db\Connection\ConnectionInterface; use Yiisoft\Db\Exception\InvalidArgumentException; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Query\Helper\QueryHelper; use Yiisoft\Db\Schema\Quoter; +/** + * @psalm-suppress PropertyNotSetInConstructor + */ final class QueryHelperTest extends TestCase { public function tablesNameDataProvider(): array @@ -32,8 +34,6 @@ public function tablesNameDataProvider(): array */ public function testCleanUpTableNames(array $tables, string $prefixDatabase, array $expected): void { - $connection = $this->createConnectionMock(); - $this->assertEquals( $expected, $this->createQueryHelper()->cleanUpTableNames($tables, new Quoter('"', '"')) @@ -42,8 +42,6 @@ public function testCleanUpTableNames(array $tables, string $prefixDatabase, arr public function testCleanUpTableNamesException(): void { - $connection = $this->createConnectionMock(); - $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('To use Expression in from() method, pass it in array format with alias.'); $this->createQueryHelper()->cleanUpTableNames( @@ -145,9 +143,4 @@ private function createQueryHelper(): QueryHelper { return new QueryHelper(); } - - private function createConnectionMock(): ConnectionInterface - { - return $this->createMock(ConnectionInterface::class); - } } diff --git a/tests/QueryBuilder/Condition/AndConditionTest.php b/tests/QueryBuilder/Condition/AndConditionTest.php index b9e9509d8..c8dfa02b0 100644 --- a/tests/QueryBuilder/Condition/AndConditionTest.php +++ b/tests/QueryBuilder/Condition/AndConditionTest.php @@ -9,6 +9,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class AndConditionTest extends TestCase { diff --git a/tests/QueryBuilder/Condition/BetweenColumnsConditionTest.php b/tests/QueryBuilder/Condition/BetweenColumnsConditionTest.php index 2eb681d76..2a9ba16d7 100644 --- a/tests/QueryBuilder/Condition/BetweenColumnsConditionTest.php +++ b/tests/QueryBuilder/Condition/BetweenColumnsConditionTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class BetweenColumnsConditionTest extends TestCase { @@ -40,6 +42,7 @@ public function testFromArrayDefinitionExceptionWithoutOperands(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'between' requires three operands."); + BetweenColumnsCondition::fromArrayDefinition('between', []); } @@ -49,6 +52,7 @@ public function testFromArrayDefinitionExceptionOperandsValue(): void $this->expectExceptionMessage( "Operator 'between' requires value to be array, int, string, Iterator or ExpressionInterface." ); + BetweenColumnsCondition::fromArrayDefinition('between', [false, 'min_value', 'max_value']); } @@ -58,6 +62,7 @@ public function testFromArrayDefinitionExceptionOperandsIntervalStartColumn(): v $this->expectExceptionMessage( "Operator 'between' requires interval start column to be string or ExpressionInterface." ); + BetweenColumnsCondition::fromArrayDefinition('between', [42, false, 'max_value']); } @@ -67,6 +72,7 @@ public function testFromArrayDefinitionExceptionOperandsIntervalEndColumn(): voi $this->expectExceptionMessage( "Operator 'between' requires interval end column to be string or ExpressionInterface." ); + BetweenColumnsCondition::fromArrayDefinition('between', [42, 'min_value', false]); } } diff --git a/tests/QueryBuilder/Condition/BetweenConditionTest.php b/tests/QueryBuilder/Condition/BetweenConditionTest.php index 94ccbbc0b..7e9419b4b 100644 --- a/tests/QueryBuilder/Condition/BetweenConditionTest.php +++ b/tests/QueryBuilder/Condition/BetweenConditionTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class BetweenConditionTest extends TestCase { @@ -37,6 +39,7 @@ public function testFromArrayDefinitionExceptionWithoutOperands(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'between' requires three operands."); + BetweenCondition::fromArrayDefinition('between', []); } @@ -44,6 +47,7 @@ public function testFromArrayDefinitionExceptionColumns(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'between' requires column to be string or ExpressionInterface."); + BetweenCondition::fromArrayDefinition('between', [1, 'min_value', 'max_value']); } } diff --git a/tests/QueryBuilder/Condition/Builder/LikeConditionBuilderTest.php b/tests/QueryBuilder/Condition/Builder/LikeConditionBuilderTest.php index 7ae4b7bd2..70390dd95 100644 --- a/tests/QueryBuilder/Condition/Builder/LikeConditionBuilderTest.php +++ b/tests/QueryBuilder/Condition/Builder/LikeConditionBuilderTest.php @@ -12,6 +12,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class LikeConditionBuilderTest extends TestCase { diff --git a/tests/QueryBuilder/Condition/ExistsConditionTest.php b/tests/QueryBuilder/Condition/ExistsConditionTest.php index 9f79e553a..383d1ee79 100644 --- a/tests/QueryBuilder/Condition/ExistsConditionTest.php +++ b/tests/QueryBuilder/Condition/ExistsConditionTest.php @@ -12,6 +12,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class ExistsConditionTest extends TestCase { @@ -43,6 +45,7 @@ public function testFromArrayDefinitionExceptionQuery(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Sub query for EXISTS operator must be a Query object.'); + ExistsCondition::fromArrayDefinition('EXISTS', []); } } diff --git a/tests/QueryBuilder/Condition/HashConditionTest.php b/tests/QueryBuilder/Condition/HashConditionTest.php index 7f485feb2..70e1a757d 100644 --- a/tests/QueryBuilder/Condition/HashConditionTest.php +++ b/tests/QueryBuilder/Condition/HashConditionTest.php @@ -9,6 +9,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class HashConditionTest extends TestCase { diff --git a/tests/QueryBuilder/Condition/InConditionTest.php b/tests/QueryBuilder/Condition/InConditionTest.php index 4d5d0452a..fcc9e8279 100644 --- a/tests/QueryBuilder/Condition/InConditionTest.php +++ b/tests/QueryBuilder/Condition/InConditionTest.php @@ -5,10 +5,13 @@ namespace Yiisoft\Db\Tests\QueryBuilder\Condition; use PHPUnit\Framework\TestCase; +use Yiisoft\Db\Exception\InvalidArgumentException; use Yiisoft\Db\QueryBuilder\Condition\InCondition; /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class InConditionTest extends TestCase { @@ -32,24 +35,27 @@ public function testFromArrayDefinition(): void public function testFromArrayDefinitionException(): void { - $this->expectException(\Yiisoft\Db\Exception\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'IN' requires two operands."); + InCondition::fromArrayDefinition('IN', []); } public function testFromArrayDefinitionExceptionColumn(): void { - $this->expectException(\Yiisoft\Db\Exception\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'IN' requires column to be string, array or Iterator."); + InCondition::fromArrayDefinition('IN', [1, [1, 2, 3]]); } public function testFromArrayDefinitionExceptionValues(): void { - $this->expectException(\Yiisoft\Db\Exception\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( "Operator 'IN' requires values to be array, Iterator, int or QueryInterface." ); + InCondition::fromArrayDefinition('IN', ['id', false]); } } diff --git a/tests/QueryBuilder/Condition/LikeConditionTest.php b/tests/QueryBuilder/Condition/LikeConditionTest.php index 7ea3a23fd..e8b071d2c 100644 --- a/tests/QueryBuilder/Condition/LikeConditionTest.php +++ b/tests/QueryBuilder/Condition/LikeConditionTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class LikeConditionTest extends TestCase { @@ -35,6 +37,7 @@ public function testFromArrayDefinitionException(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'LIKE' requires two operands."); + LikeCondition::fromArrayDefinition('LIKE', []); } @@ -42,6 +45,7 @@ public function testFromArrayDefinitionExceptionColumn(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'LIKE' requires column to be string or Expression."); + LikeCondition::fromArrayDefinition('LIKE', [false, 'test']); } @@ -51,6 +55,7 @@ public function testFromArrayDefinitionExceptionValue(): void $this->expectExceptionMessage( "Operator 'LIKE' requires value to be string, array, Iterator or ExpressionInterface." ); + LikeCondition::fromArrayDefinition('LIKE', ['id', false]); } diff --git a/tests/QueryBuilder/Condition/NotConditionTest.php b/tests/QueryBuilder/Condition/NotConditionTest.php index fe96de2bd..1bc02060e 100644 --- a/tests/QueryBuilder/Condition/NotConditionTest.php +++ b/tests/QueryBuilder/Condition/NotConditionTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class NotConditionTest extends TestCase { @@ -31,6 +33,7 @@ public function testFromArrayDefinitionException(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'NOT' requires exactly one operand."); + NotCondition::fromArrayDefinition('NOT', []); } @@ -40,6 +43,7 @@ public function testFromArrayDefinitionExceptionCondition(): void $this->expectExceptionMessage( "Operator 'NOT' requires condition to be array, string, null or ExpressionInterface." ); + NotCondition::fromArrayDefinition('NOT', [false]); } } diff --git a/tests/QueryBuilder/Condition/SimpleConditionTest.php b/tests/QueryBuilder/Condition/SimpleConditionTest.php index 717ec2dbe..eac923f58 100644 --- a/tests/QueryBuilder/Condition/SimpleConditionTest.php +++ b/tests/QueryBuilder/Condition/SimpleConditionTest.php @@ -10,6 +10,8 @@ /** * @group db + * + * @psalm-suppress PropertyNotSetInConstructor */ final class SimpleConditionTest extends TestCase { @@ -35,6 +37,7 @@ public function testFromArrayDefinitionColumnException(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator '=' requires two operands."); + SimpleCondition::fromArrayDefinition('=', []); } @@ -42,6 +45,7 @@ public function testFromArrayDefinitionValueException(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Operator 'IN' requires two operands."); + SimpleCondition::fromArrayDefinition('IN', ['column']); } @@ -51,15 +55,18 @@ public function testFromArrayDefinitionExceptionColumn(): void $this->expectExceptionMessage( "Operator '=' requires column to be string, ExpressionInterface or QueryInterface." ); + SimpleCondition::fromArrayDefinition('=', [1, 1]); } public function testNullSecondOperand(): void { $condition = SimpleCondition::fromArrayDefinition('=', ['id', null]); + $this->assertNull($condition->getValue()); $condition2 = new SimpleCondition('name', 'IS NOT', null); + $this->assertSame('IS NOT', $condition2->getOperator()); $this->assertNull($condition2->getValue()); } diff --git a/tests/Schema/QuoterTest.php b/tests/Schema/QuoterTest.php index 65fa0986e..1eef9fe79 100644 --- a/tests/Schema/QuoterTest.php +++ b/tests/Schema/QuoterTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; use Yiisoft\Db\Tests\Support\TestTrait; +use function array_reverse; + /** * @group db * @@ -19,11 +21,11 @@ final class QuoterTest extends TestCase /** * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::tableNameParts() */ - public function testGetTableNameParts(string $tableName, array $expected): void + public function testGetTableNameParts(string $tableName, string ...$expected): void { $db = $this->getConnection(); - $this->assertSame($expected, $db->getQuoter()->getTableNameParts($tableName)); + $this->assertSame($expected, array_reverse($db->getQuoter()->getTableNameParts($tableName))); } /** @@ -37,7 +39,7 @@ public function testQuoteColumnName(string $columnName, string $expected): void } /** - * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::simpleColumnName() + * @dataProvider \Yiisoft\Db\Tests\Provider\QuoterProvider::columnName() */ public function testQuoteSimpleColumnName(string $columnName, string $expected): void { diff --git a/tests/Support/Assert.php b/tests/Support/Assert.php index b942fbabc..b2ba2d44b 100644 --- a/tests/Support/Assert.php +++ b/tests/Support/Assert.php @@ -9,6 +9,9 @@ use ReflectionException; use ReflectionObject; +/** + * @psalm-suppress PropertyNotSetInConstructor + */ final class Assert extends TestCase { /** @@ -47,6 +50,7 @@ public static function getInaccessibleProperty(object $object, string $propertyN $property->setAccessible(true); + /** @psalm-var mixed $result */ $result = $property->getValue($object); if ($revoke) { diff --git a/tests/Support/DbHelper.php b/tests/Support/DbHelper.php new file mode 100644 index 000000000..b71ce3a69 --- /dev/null +++ b/tests/Support/DbHelper.php @@ -0,0 +1,75 @@ +open(); + $lines = explode(';', file_get_contents($fixture)); + + foreach ($lines as $line) { + if (trim($line) !== '') { + $db->getPDO()?->exec($line); + } + } + } + + /** + * Adjust dbms specific escaping. + * + * @param string $sql string SQL statement to adjust. + * @param string $driverName string DBMS name. + * + * @return mixed + */ + public static function replaceQuotes(string $sql, string $driverName): string + { + return match ($driverName) { + 'mysql', 'sqlite' => str_replace(['[[', ']]'], '`', $sql), + 'oci' => str_replace(['[[', ']]'], '"', $sql), + 'pgsql' => str_replace(['\\[', '\\]'], ['[', ']'], preg_replace('/(\[\[)|((? str_replace(['[[', ']]'], ['[', ']'], $sql), + default => $sql, + }; + } +} diff --git a/tests/Support/Fixture/customer.sql b/tests/Support/Fixture/customer.sql new file mode 100644 index 000000000..354f08f29 --- /dev/null +++ b/tests/Support/Fixture/customer.sql @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS "customer"; +CREATE TABLE "customer" ( + id INTEGER NOT NULL, + email varchar(128) NOT NULL, + name varchar(128), + address text, + status INTEGER DEFAULT 0, + profile_id INTEGER, + PRIMARY KEY (id) +); +INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user1@example.com', 'user1', 'address1', 1, 1); +INSERT INTO "customer" (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); +INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user3@example.com', 'user3', 'address3', 2, 2); diff --git a/tests/Support/IsOneOfAssert.php b/tests/Support/IsOneOfAssert.php new file mode 100644 index 000000000..42a4f5936 --- /dev/null +++ b/tests/Support/IsOneOfAssert.php @@ -0,0 +1,43 @@ +allowedValues as $value) { + $this->allowedValues[] = VarDumper::create($value)->asString(); + } + + $expectedAsString = implode(', ', $allowedValues); + + return "is one of $expectedAsString"; + } + + protected function matches($other): bool + { + return in_array($other, $this->allowedValues); + } +} diff --git a/tests/Support/Stub/Command.php b/tests/Support/Stub/Command.php index a128621f7..20d8d6381 100644 --- a/tests/Support/Stub/Command.php +++ b/tests/Support/Stub/Command.php @@ -13,7 +13,7 @@ final class Command extends CommandPDO implements CommandPDOInterface { public function insertEx(string $table, array $columns): bool|array { - throw new NotSupportedException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . '()' . ' is not supported by core-db.'); } public function queryBuilder(): QueryBuilderInterface @@ -23,6 +23,6 @@ public function queryBuilder(): QueryBuilderInterface protected function internalExecute(string|null $rawSql): void { - throw new NotSupportedException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + throw new NotSupportedException(__METHOD__ . '()' . ' is not supported by core-db.'); } } diff --git a/tests/Support/Stub/Connection.php b/tests/Support/Stub/Connection.php index c0514c56e..651875762 100644 --- a/tests/Support/Stub/Connection.php +++ b/tests/Support/Stub/Connection.php @@ -58,7 +58,7 @@ public function getQueryBuilder(): QueryBuilderInterface public function getQuoter(): QuoterInterface { if ($this->quoter === null) { - $this->quoter = new Quoter('', '', $this->getTablePrefix()); + $this->quoter = new Quoter(['[', ']'], ['[', ']'], $this->getTablePrefix()); } return $this->quoter; diff --git a/tests/Support/Stub/PDODriver.php b/tests/Support/Stub/PDODriver.php index e138aaf04..b80ddf392 100644 --- a/tests/Support/Stub/PDODriver.php +++ b/tests/Support/Stub/PDODriver.php @@ -4,16 +4,10 @@ namespace Yiisoft\Db\Tests\Support\Stub; -use PDO; use Yiisoft\Db\Driver\PDO\PDODriverInterface; final class PDODriver extends \Yiisoft\Db\Driver\PDO\PDODriver implements PDODriverInterface { - public function createConnection(): PDO - { - return parent::createConnection(); - } - public function getDriverName(): string { return 'db'; diff --git a/tests/Support/Stub/Schema.php b/tests/Support/Stub/Schema.php index 8aac93d0d..011105f3d 100644 --- a/tests/Support/Stub/Schema.php +++ b/tests/Support/Stub/Schema.php @@ -10,6 +10,11 @@ use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Schema\TableSchemaInterface; +/** + * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidNullableReturnType + * @psalm-suppress NullableReturnStatement + */ final class Schema extends \Yiisoft\Db\Schema\Schema implements SchemaInterface { public function createColumnSchemaBuilder(string $type, array|int|string $length = null): ColumnSchemaBuilder @@ -19,57 +24,57 @@ public function createColumnSchemaBuilder(string $type, array|int|string $length public function findUniqueIndexes(TableSchemaInterface $table): array { - return $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + return $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } public function getLastInsertID(string $sequenceName = null): string { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function getCacheKey(string $name): array { - throw new NotSupportedException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + return []; } protected function getCacheTag(): string { - throw new NotSupportedException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + return ''; } protected function loadTableChecks(string $tableName): array { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function loadTableDefaultValues(string $tableName): array { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function loadTableForeignKeys(string $tableName): array { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function loadTableIndexes(string $tableName): array { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function loadTablePrimaryKey(string $tableName): Constraint|null { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function loadTableUniques(string $tableName): array { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } protected function loadTableSchema(string $name): TableSchemaInterface|null { - $this->getException(self::class . '::' . __METHOD__ . '()' . ' is not supported by core-db.'); + $this->getException(__METHOD__ . '()' . ' is not supported by core-db.'); } private function getException(string $message): void diff --git a/tests/Support/Stub/Transaction.php b/tests/Support/Stub/Transaction.php index 3c49ae0c0..080504d6f 100644 --- a/tests/Support/Stub/Transaction.php +++ b/tests/Support/Stub/Transaction.php @@ -4,8 +4,9 @@ namespace Yiisoft\Db\Tests\Support\Stub; +use Yiisoft\Db\Driver\PDO\TransactionPDO; use Yiisoft\Db\Transaction\TransactionInterface; -final class Transaction extends \Yiisoft\Db\Driver\PDO\TransactionPDO implements TransactionInterface +final class Transaction extends TransactionPDO implements TransactionInterface { } diff --git a/tests/Support/TestTrait.php b/tests/Support/TestTrait.php index c862d6ecc..64a3e246f 100644 --- a/tests/Support/TestTrait.php +++ b/tests/Support/TestTrait.php @@ -4,11 +4,6 @@ namespace Yiisoft\Db\Tests\Support; -use Yiisoft\Cache\ArrayCache; -use Yiisoft\Cache\Cache; -use Yiisoft\Cache\CacheInterface; -use Yiisoft\Db\Cache\QueryCache; -use Yiisoft\Db\Cache\SchemaCache; use Yiisoft\Db\Driver\PDO\ConnectionPDOInterface; use Yiisoft\Db\Tests\Support\Stub\PDODriver; @@ -18,35 +13,14 @@ trait TestTrait private QueryCache|null $queryCache = null; private SchemaCache|null $schemaCache = null; - protected function getConnection(string $dsn = 'sqlite::memory:'): ConnectionPDOInterface + protected function getConnection(string $fixture = '', string $dsn = 'sqlite::memory:'): ConnectionPDOInterface { - return new Stub\Connection(new PDODriver($dsn), $this->getQueryCache(), $this->getSchemaCache()); - } - - private function getCache(): CacheInterface - { - if ($this->cache === null) { - $this->cache = new Cache(new ArrayCache()); - } - - return $this->cache; - } + $db = new Stub\Connection(new PDODriver($dsn), DbHelper::getQueryCache(), DbHelper::getSchemaCache()); - private function getQueryCache(): QueryCache - { - if ($this->queryCache === null) { - $this->queryCache = new QueryCache($this->getCache()); - } - - return $this->queryCache; - } - - private function getSchemaCache(): SchemaCache - { - if ($this->schemaCache === null) { - $this->schemaCache = new SchemaCache($this->getCache()); + if ($fixture !== '') { + DbHelper::loadFixture($db, __DIR__ . "/Fixture/$fixture.sql"); } - return $this->schemaCache; + return $db; } }