diff options
Diffstat (limited to 'tests/lib/AppFramework/Db')
-rw-r--r-- | tests/lib/AppFramework/Db/EntityTest.php | 176 | ||||
-rw-r--r-- | tests/lib/AppFramework/Db/MapperTest.php | 300 | ||||
-rw-r--r-- | tests/lib/AppFramework/Db/MapperTestUtility.php | 206 | ||||
-rw-r--r-- | tests/lib/AppFramework/Db/QBMapperDBTest.php | 160 | ||||
-rw-r--r-- | tests/lib/AppFramework/Db/QBMapperTest.php | 188 | ||||
-rw-r--r-- | tests/lib/AppFramework/Db/TransactionalTest.php | 79 |
6 files changed, 460 insertions, 649 deletions
diff --git a/tests/lib/AppFramework/Db/EntityTest.php b/tests/lib/AppFramework/Db/EntityTest.php index 17234849a2d..eab081e6ac6 100644 --- a/tests/lib/AppFramework/Db/EntityTest.php +++ b/tests/lib/AppFramework/Db/EntityTest.php @@ -1,29 +1,15 @@ <?php /** - * ownCloud - App Framework - * - * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace Test\AppFramework\Db; use OCP\AppFramework\Db\Entity; +use OCP\DB\Types; use PHPUnit\Framework\Constraint\IsType; /** @@ -42,21 +28,46 @@ use PHPUnit\Framework\Constraint\IsType; * @method void setTrueOrFalse(bool $trueOrFalse) * @method bool getAnotherBool() * @method bool isAnotherBool() - * @method void setAnotherBool(bool $anotherBool) + * @method string getLongText() + * @method void setLongText(string $longText) + * @method \DateTime getTime() + * @method void setTime(\DateTime $time) + * @method \DateTimeImmutable getDatetime() + * @method void setDatetime(\DateTimeImmutable $datetime) */ class TestEntity extends Entity { - protected $name; protected $email; protected $testId; + protected $smallInt; + protected $bigInt; protected $preName; protected $trueOrFalse; protected $anotherBool; - - public function __construct($name = null) { - $this->addType('testId', 'integer'); + protected $text; + protected $longText; + protected $time; + protected $datetime; + + public function __construct( + protected $name = null, + ) { + $this->addType('testId', Types::INTEGER); + $this->addType('smallInt', Types::SMALLINT); + $this->addType('bigInt', Types::BIGINT); + $this->addType('anotherBool', Types::BOOLEAN); + $this->addType('text', Types::TEXT); + $this->addType('longText', Types::BLOB); + $this->addType('time', Types::TIME); + $this->addType('datetime', Types::DATETIME_IMMUTABLE); + + // Legacy types $this->addType('trueOrFalse', 'bool'); - $this->addType('anotherBool', 'boolean'); - $this->name = $name; + $this->addType('legacyInt', 'int'); + $this->addType('doubleNowFloat', 'double'); + } + + public function setAnotherBool(bool $anotherBool): void { + parent::setAnotherBool($anotherBool); } } @@ -70,7 +81,7 @@ class EntityTest extends \Test\TestCase { } - public function testResetUpdatedFields() { + public function testResetUpdatedFields(): void { $entity = new TestEntity(); $entity->setId(3); $entity->resetUpdatedFields(); @@ -79,19 +90,21 @@ class EntityTest extends \Test\TestCase { } - public function testFromRow() { + public function testFromRow(): void { $row = [ 'pre_name' => 'john', - 'email' => 'john@something.com' + 'email' => 'john@something.com', + 'another_bool' => 1, ]; $this->entity = TestEntity::fromRow($row); $this->assertEquals($row['pre_name'], $this->entity->getPreName()); $this->assertEquals($row['email'], $this->entity->getEmail()); + $this->assertEquals($row['another_bool'], $this->entity->getAnotherBool()); } - public function testGetSetId() { + public function testGetSetId(): void { $id = 3; $this->entity->setId(3); @@ -99,28 +112,28 @@ class EntityTest extends \Test\TestCase { } - public function testColumnToPropertyNoReplacement() { + public function testColumnToPropertyNoReplacement(): void { $column = 'my'; $this->assertEquals('my', $this->entity->columnToProperty($column)); } - public function testColumnToProperty() { + public function testColumnToProperty(): void { $column = 'my_attribute'; $this->assertEquals('myAttribute', $this->entity->columnToProperty($column)); } - public function testPropertyToColumnNoReplacement() { + public function testPropertyToColumnNoReplacement(): void { $property = 'my'; $this->assertEquals('my', $this->entity->propertyToColumn($property)); } - public function testSetterMarksFieldUpdated() { + public function testSetterMarksFieldUpdated(): void { $this->entity->setId(3); $this->assertContains('id', array_keys($this->entity->getUpdatedFields())); @@ -128,7 +141,7 @@ class EntityTest extends \Test\TestCase { - public function testCallShouldOnlyWorkForGetterSetter() { + public function testCallShouldOnlyWorkForGetterSetter(): void { $this->expectException(\BadFunctionCallException::class); $this->entity->something(); @@ -136,21 +149,21 @@ class EntityTest extends \Test\TestCase { - public function testGetterShouldFailIfAttributeNotDefined() { + public function testGetterShouldFailIfAttributeNotDefined(): void { $this->expectException(\BadFunctionCallException::class); $this->entity->getTest(); } - public function testSetterShouldFailIfAttributeNotDefined() { + public function testSetterShouldFailIfAttributeNotDefined(): void { $this->expectException(\BadFunctionCallException::class); $this->entity->setTest(); } - public function testFromRowShouldNotAssignEmptyArray() { + public function testFromRowShouldNotAssignEmptyArray(): void { $row = []; $entity2 = new TestEntity(); @@ -159,7 +172,7 @@ class EntityTest extends \Test\TestCase { } - public function testIdGetsConvertedToInt() { + public function testIdGetsConvertedToInt(): void { $row = ['id' => '4']; $this->entity = TestEntity::fromRow($row); @@ -167,7 +180,7 @@ class EntityTest extends \Test\TestCase { } - public function testSetType() { + public function testSetType(): void { $row = ['testId' => '4']; $this->entity = TestEntity::fromRow($row); @@ -175,7 +188,7 @@ class EntityTest extends \Test\TestCase { } - public function testFromParams() { + public function testFromParams(): void { $params = [ 'testId' => 4, 'email' => 'john@doe' @@ -188,7 +201,7 @@ class EntityTest extends \Test\TestCase { $this->assertTrue($entity instanceof TestEntity); } - public function testSlugify() { + public function testSlugify(): void { $entity = new TestEntity(); $entity->setName('Slugify this!'); $this->assertEquals('slugify-this', $entity->slugify('name')); @@ -197,45 +210,98 @@ class EntityTest extends \Test\TestCase { } - public function testSetterCasts() { + public static function dataSetterCasts(): array { + return [ + ['Id', '3', 3], + ['smallInt', '3', 3], + ['bigInt', '' . PHP_INT_MAX, PHP_INT_MAX], + ['trueOrFalse', 0, false], + ['trueOrFalse', 1, true], + ['anotherBool', 0, false], + ['anotherBool', 1, true], + ['text', 33, '33'], + ['longText', PHP_INT_MAX, '' . PHP_INT_MAX], + ]; + } + + + #[\PHPUnit\Framework\Attributes\DataProvider('dataSetterCasts')] + public function testSetterCasts(string $field, mixed $in, mixed $out): void { $entity = new TestEntity(); - $entity->setId('3'); - $this->assertSame(3, $entity->getId()); + $entity->{'set' . $field}($in); + $this->assertSame($out, $entity->{'get' . $field}()); } - public function testSetterDoesNotCastOnNull() { + public function testSetterDoesNotCastOnNull(): void { $entity = new TestEntity(); $entity->setId(null); $this->assertSame(null, $entity->getId()); } + public function testSetterConvertsResourcesToStringProperly(): void { + $string = 'Definitely a string'; + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $string); + rewind($stream); + + $entity = new TestEntity(); + $entity->setLongText($stream); + fclose($stream); + $this->assertSame($string, $entity->getLongText()); + } + + public function testSetterConvertsDatetime() { + $entity = new TestEntity(); + $entity->setDatetime('2024-08-19 15:26:00'); + $this->assertEquals(new \DateTimeImmutable('2024-08-19 15:26:00'), $entity->getDatetime()); + } + + public function testSetterDoesNotConvertNullOnDatetime() { + $entity = new TestEntity(); + $entity->setDatetime(null); + $this->assertNull($entity->getDatetime()); + } + + public function testSetterConvertsTime() { + $entity = new TestEntity(); + $entity->setTime('15:26:00'); + $this->assertEquals(new \DateTime('15:26:00'), $entity->getTime()); + } - public function testGetFieldTypes() { + public function testGetFieldTypes(): void { $entity = new TestEntity(); $this->assertEquals([ - 'id' => 'integer', - 'testId' => 'integer', - 'trueOrFalse' => 'bool', - 'anotherBool' => 'boolean', + 'id' => Types::INTEGER, + 'testId' => Types::INTEGER, + 'smallInt' => Types::SMALLINT, + 'bigInt' => Types::BIGINT, + 'anotherBool' => Types::BOOLEAN, + 'text' => Types::TEXT, + 'longText' => Types::BLOB, + 'time' => Types::TIME, + 'datetime' => Types::DATETIME_IMMUTABLE, + 'trueOrFalse' => Types::BOOLEAN, + 'legacyInt' => Types::INTEGER, + 'doubleNowFloat' => Types::FLOAT, ], $entity->getFieldTypes()); } - public function testGetItInt() { + public function testGetItInt(): void { $entity = new TestEntity(); $entity->setId(3); - $this->assertEquals('integer', gettype($entity->getId())); + $this->assertEquals(Types::INTEGER, gettype($entity->getId())); } - public function testFieldsNotMarkedUpdatedIfNothingChanges() { + public function testFieldsNotMarkedUpdatedIfNothingChanges(): void { $entity = new TestEntity('hey'); $entity->setName('hey'); $this->assertEquals(0, count($entity->getUpdatedFields())); } - public function testIsGetter() { + public function testIsGetter(): void { $entity = new TestEntity(); $entity->setTrueOrFalse(false); $entity->setAnotherBool(false); @@ -244,7 +310,7 @@ class EntityTest extends \Test\TestCase { } - public function testIsGetterShoudFailForOtherType() { + public function testIsGetterShoudFailForOtherType(): void { $this->expectException(\BadFunctionCallException::class); $entity = new TestEntity(); diff --git a/tests/lib/AppFramework/Db/MapperTest.php b/tests/lib/AppFramework/Db/MapperTest.php deleted file mode 100644 index e5a4b63b7a3..00000000000 --- a/tests/lib/AppFramework/Db/MapperTest.php +++ /dev/null @@ -1,300 +0,0 @@ -<?php - -/** - * ownCloud - App Framework - * - * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - * - */ - -namespace Test\AppFramework\Db; - -use OCP\AppFramework\Db\DoesNotExistException; -use OCP\AppFramework\Db\Entity; -use OCP\AppFramework\Db\Mapper; -use OCP\AppFramework\Db\MultipleObjectsReturnedException; -use OCP\IDBConnection; - -/** - * @method integer getId() - * @method void setId(integer $id) - * @method string getEmail() - * @method void setEmail(string $email) - * @method string getPreName() - * @method void setPreName(string $preName) - */ -class Example extends Entity { - protected $preName; - protected $email; -}; - - -class ExampleMapper extends Mapper { - public function __construct(IDBConnection $db) { - parent::__construct($db, 'table'); - } - public function find($table, $id) { - return $this->findOneQuery($table, $id); - } - public function findOneEntity($table, $id) { - return $this->findEntity($table, $id); - } - public function findAllEntities($table) { - return $this->findEntities($table); - } - public function mapRow($row) { - return $this->mapRowToEntity($row); - } - public function execSql($sql, $params) { - return $this->execute($sql, $params); - } -} - - -class MapperTest extends MapperTestUtility { - - /** - * @var Mapper - */ - private $mapper; - - protected function setUp(): void { - parent::setUp(); - $this->mapper = new ExampleMapper($this->db); - } - - - public function testMapperShouldSetTableName() { - $this->assertEquals('*PREFIX*table', $this->mapper->getTableName()); - } - - - public function testFindQuery() { - $sql = 'hi'; - $params = ['jo']; - $rows = [ - ['hi'] - ]; - $this->setMapperResult($sql, $params, $rows); - $this->mapper->find($sql, $params); - } - - public function testFindEntity() { - $sql = 'hi'; - $params = ['jo']; - $rows = [ - ['pre_name' => 'hi'] - ]; - $this->setMapperResult($sql, $params, $rows, null, null, true); - $this->mapper->findOneEntity($sql, $params); - } - - public function testFindNotFound() { - $sql = 'hi'; - $params = ['jo']; - $rows = []; - $this->setMapperResult($sql, $params, $rows); - $this->expectException(DoesNotExistException::class); - $this->mapper->find($sql, $params); - } - - public function testFindEntityNotFound() { - $sql = 'hi'; - $params = ['jo']; - $rows = []; - $this->setMapperResult($sql, $params, $rows, null, null, true); - $this->expectException(DoesNotExistException::class); - $this->mapper->findOneEntity($sql, $params); - } - - public function testFindMultiple() { - $sql = 'hi'; - $params = ['jo']; - $rows = [ - ['jo'], ['ho'] - ]; - $this->setMapperResult($sql, $params, $rows, null, null, true); - $this->expectException(MultipleObjectsReturnedException::class); - $this->mapper->find($sql, $params); - } - - public function testFindEntityMultiple() { - $sql = 'hi'; - $params = ['jo']; - $rows = [ - ['jo'], ['ho'] - ]; - $this->setMapperResult($sql, $params, $rows, null, null, true); - $this->expectException(MultipleObjectsReturnedException::class); - $this->mapper->findOneEntity($sql, $params); - } - - - public function testDelete() { - $sql = 'DELETE FROM `*PREFIX*table` WHERE `id` = ?'; - $params = [2]; - - $this->setMapperResult($sql, $params, [], null, null, true); - $entity = new Example(); - $entity->setId($params[0]); - - $this->mapper->delete($entity); - } - - - public function testCreate() { - $this->db->expects($this->once()) - ->method('lastInsertId') - ->with($this->equalTo('*PREFIX*table')) - ->willReturn(3); - $this->mapper = new ExampleMapper($this->db); - - $sql = 'INSERT INTO `*PREFIX*table`(`pre_name`,`email`) ' . - 'VALUES(?,?)'; - $params = ['john', 'my@email']; - $entity = new Example(); - $entity->setPreName($params[0]); - $entity->setEmail($params[1]); - - $this->setMapperResult($sql, $params, [], null, null, true); - - $this->mapper->insert($entity); - } - - - public function testCreateShouldReturnItemWithCorrectInsertId() { - $this->db->expects($this->once()) - ->method('lastInsertId') - ->with($this->equalTo('*PREFIX*table')) - ->willReturn(3); - $this->mapper = new ExampleMapper($this->db); - - $sql = 'INSERT INTO `*PREFIX*table`(`pre_name`,`email`) ' . - 'VALUES(?,?)'; - $params = ['john', 'my@email']; - $entity = new Example(); - $entity->setPreName($params[0]); - $entity->setEmail($params[1]); - - $this->setMapperResult($sql, $params); - - $result = $this->mapper->insert($entity); - - $this->assertEquals(3, $result->getId()); - } - - - public function testAssocParameters() { - $sql = 'test'; - $params = [':test' => 1, ':a' => 2]; - - $this->setMapperResult($sql, $params); - $this->mapper->execSql($sql, $params); - } - - - public function testUpdate() { - $sql = 'UPDATE `*PREFIX*table` ' . - 'SET ' . - '`pre_name` = ?,'. - '`email` = ? ' . - 'WHERE `id` = ?'; - - $params = ['john', 'my@email', 1]; - $entity = new Example(); - $entity->setPreName($params[0]); - $entity->setEmail($params[1]); - $entity->setId($params[2]); - - $this->setMapperResult($sql, $params, [], null, null, true); - - $this->mapper->update($entity); - } - - - public function testUpdateNoId() { - $params = ['john', 'my@email']; - $entity = new Example(); - $entity->setPreName($params[0]); - $entity->setEmail($params[1]); - - $this->expectException(\InvalidArgumentException::class); - - $this->mapper->update($entity); - } - - - public function testUpdateNothingChangedNoQuery() { - $params = ['john', 'my@email']; - $entity = new Example(); - $entity->setId(3); - $entity->setEmail($params[1]); - $entity->resetUpdatedFields(); - - $this->db->expects($this->never()) - ->method('prepare'); - - $this->mapper->update($entity); - } - - - public function testMapRowToEntity() { - $entity1 = $this->mapper->mapRow(['pre_name' => 'test1', 'email' => 'test2']); - $entity2 = new Example(); - $entity2->setPreName('test1'); - $entity2->setEmail('test2'); - $entity2->resetUpdatedFields(); - $this->assertEquals($entity2, $entity1); - } - - public function testFindEntities() { - $sql = 'hi'; - $rows = [ - ['pre_name' => 'hi'] - ]; - $entity = new Example(); - $entity->setPreName('hi'); - $entity->resetUpdatedFields(); - $this->setMapperResult($sql, [], $rows, null, null, true); - $result = $this->mapper->findAllEntities($sql); - $this->assertEquals([$entity], $result); - } - - public function testFindEntitiesNotFound() { - $sql = 'hi'; - $rows = []; - $this->setMapperResult($sql, [], $rows); - $result = $this->mapper->findAllEntities($sql); - $this->assertEquals([], $result); - } - - public function testFindEntitiesMultiple() { - $sql = 'hi'; - $rows = [ - ['pre_name' => 'jo'], ['email' => 'ho'] - ]; - $entity1 = new Example(); - $entity1->setPreName('jo'); - $entity1->resetUpdatedFields(); - $entity2 = new Example(); - $entity2->setEmail('ho'); - $entity2->resetUpdatedFields(); - $this->setMapperResult($sql, [], $rows); - $result = $this->mapper->findAllEntities($sql); - $this->assertEquals([$entity1, $entity2], $result); - } -} diff --git a/tests/lib/AppFramework/Db/MapperTestUtility.php b/tests/lib/AppFramework/Db/MapperTestUtility.php deleted file mode 100644 index e17b875e4c4..00000000000 --- a/tests/lib/AppFramework/Db/MapperTestUtility.php +++ /dev/null @@ -1,206 +0,0 @@ -<?php - -/** - * ownCloud - App Framework - * - * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - * - */ - -namespace Test\AppFramework\Db; - -use OCP\DB\IPreparedStatement; -use OCP\DB\IResult; - -/** - * Simple utility class for testing mappers - */ -abstract class MapperTestUtility extends \Test\TestCase { - protected $db; - private $statement; - private $queryAt; - private $prepareAt; - private $fetchAt; - private $iterators; - - - /** - * Run this function before the actual test to either set or initialize the - * db. After this the db can be accessed by using $this->db - */ - protected function setUp(): void { - parent::setUp(); - - $this->db = $this->getMockBuilder( - '\OCP\IDBConnection') - ->disableOriginalConstructor() - ->getMock(); - - $this->statement = $this->createMock(IPreparedStatement::class); - $this->queryAt = 0; - $this->prepareAt = 0; - $this->iterators = []; - $this->fetchAt = 0; - } - - /** - * Checks if an array is associative - * @param array $array - * @return bool true if associative - */ - private function isAssocArray(array $array) { - return array_values($array) !== $array; - } - - /** - * Returns the correct PDO constant based on the value type - * @param $value - * @return int PDO constant - */ - private function getPDOType($value) { - switch (gettype($value)) { - case 'integer': - return \PDO::PARAM_INT; - case 'boolean': - return \PDO::PARAM_BOOL; - default: - return \PDO::PARAM_STR; - } - } - - /** - * Create mocks and set expected results for database queries - * @param string $sql the sql query that you expect to receive - * @param array $arguments the expected arguments for the prepare query - * method - * @param array $returnRows the rows that should be returned for the result - * of the database query. If not provided, it wont be assumed that fetch - * will be called on the result - */ - protected function setMapperResult($sql, $arguments = [], $returnRows = [], - $limit = null, $offset = null, $expectClose = false) { - if ($limit === null && $offset === null) { - $this->db->expects($this->at($this->prepareAt)) - ->method('prepare') - ->with($this->equalTo($sql)) - ->will(($this->returnValue($this->statement))); - } elseif ($limit !== null && $offset === null) { - $this->db->expects($this->at($this->prepareAt)) - ->method('prepare') - ->with($this->equalTo($sql), $this->equalTo($limit)) - ->will(($this->returnValue($this->statement))); - } elseif ($limit === null && $offset !== null) { - $this->db->expects($this->at($this->prepareAt)) - ->method('prepare') - ->with($this->equalTo($sql), - $this->equalTo(null), - $this->equalTo($offset)) - ->will(($this->returnValue($this->statement))); - } else { - $this->db->expects($this->at($this->prepareAt)) - ->method('prepare') - ->with($this->equalTo($sql), - $this->equalTo($limit), - $this->equalTo($offset)) - ->will(($this->returnValue($this->statement))); - } - - $this->iterators[] = new ArgumentIterator($returnRows); - - $iterators = $this->iterators; - $fetchAt = $this->fetchAt; - - $this->statement->expects($this->any()) - ->method('fetch') - ->willReturnCallback( - function () use ($iterators, $fetchAt) { - $iterator = $iterators[$fetchAt]; - $result = $iterator->next(); - - if ($result === false) { - $fetchAt++; - } - - $this->queryAt++; - - return $result; - } - ); - - if ($this->isAssocArray($arguments)) { - foreach ($arguments as $key => $argument) { - $pdoConstant = $this->getPDOType($argument); - $this->statement->expects($this->at($this->queryAt)) - ->method('bindValue') - ->with($this->equalTo($key), - $this->equalTo($argument), - $this->equalTo($pdoConstant)); - $this->queryAt++; - } - } else { - $index = 1; - foreach ($arguments as $argument) { - $pdoConstant = $this->getPDOType($argument); - $this->statement->expects($this->at($this->queryAt)) - ->method('bindValue') - ->with($this->equalTo($index), - $this->equalTo($argument), - $this->equalTo($pdoConstant)); - $index++; - $this->queryAt++; - } - } - - $this->statement->expects($this->at($this->queryAt)) - ->method('execute') - ->willReturnCallback(function ($sql, $p = null, $o = null, $s = null) { - return $this->createMock(IResult::class); - }); - $this->queryAt++; - - - - if ($expectClose) { - $closing = $this->at($this->queryAt); - } else { - $closing = $this->any(); - } - $this->statement->expects($closing)->method('closeCursor'); - $this->queryAt++; - - $this->prepareAt++; - $this->fetchAt++; - } -} - - -class ArgumentIterator { - private $arguments; - - public function __construct($arguments) { - $this->arguments = $arguments; - } - - public function next() { - $result = array_shift($this->arguments); - if ($result === null) { - return false; - } else { - return $result; - } - } -} diff --git a/tests/lib/AppFramework/Db/QBMapperDBTest.php b/tests/lib/AppFramework/Db/QBMapperDBTest.php new file mode 100644 index 00000000000..614f1099644 --- /dev/null +++ b/tests/lib/AppFramework/Db/QBMapperDBTest.php @@ -0,0 +1,160 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\AppFramework\Db; + +use Doctrine\DBAL\Schema\SchemaException; +use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\Types; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Server; +use Test\TestCase; + +/** + * @method void setTime(?\DateTime $time) + * @method ?\DateTime getTime() + * @method void setDatetime(?\DateTimeImmutable $datetime) + * @method ?\DateTimeImmutable getDatetime() + */ +class QBDBTestEntity extends Entity { + protected ?\DateTime $time = null; + protected ?\DateTimeImmutable $datetime = null; + + public function __construct() { + $this->addType('time', Types::TIME); + $this->addType('datetime', Types::DATETIME_IMMUTABLE); + } +} + +/** + * Class QBDBTestMapper + * + * @package Test\AppFramework\Db + */ +class QBDBTestMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'testing', QBDBTestEntity::class); + } + + public function getParameterTypeForPropertyForTest(Entity $entity, string $property) { + return parent::getParameterTypeForProperty($entity, $property); + } + + public function getById(int $id): QBDBTestEntity { + $qb = $this->db->getQueryBuilder(); + $query = $qb + ->select('*') + ->from($this->tableName) + ->where( + $qb->expr()->eq('id', $qb->createPositionalParameter($id, IQueryBuilder::PARAM_INT)), + ); + return $this->findEntity($query); + } +} + +/** + * Test real database handling (serialization) + * @group DB + */ +class QBMapperDBTest extends TestCase { + /** @var \Doctrine\DBAL\Connection|IDBConnection */ + protected $connection; + protected $schemaSetup = false; + + protected function setUp(): void { + parent::setUp(); + + $this->connection = Server::get(IDBConnection::class); + $this->prepareTestingTable(); + } + + public function testInsertDateTime(): void { + $mapper = new QBDBTestMapper($this->connection); + $entity = new QBDBTestEntity(); + $entity->setTime(new \DateTime('2003-01-01 12:34:00')); + $entity->setDatetime(new \DateTimeImmutable('2000-01-01 23:45:00')); + + $result = $mapper->insert($entity); + $this->assertNotNull($result->getId()); + } + + public function testRetrieveDateTime(): void { + $time = new \DateTime('2000-01-01 01:01:00'); + $datetime = new \DateTimeImmutable('2000-01-01 02:02:00'); + + $mapper = new QBDBTestMapper($this->connection); + $entity = new QBDBTestEntity(); + $entity->setTime($time); + $entity->setDatetime($datetime); + + $result = $mapper->insert($entity); + $this->assertNotNull($result->getId()); + + $dbEntity = $mapper->getById($result->getId()); + $this->assertEquals($time->format('H:i:s'), $dbEntity->getTime()->format('H:i:s')); + $this->assertEquals($datetime->format('Y-m-d H:i:s'), $dbEntity->getDatetime()->format('Y-m-d H:i:s')); + // The date is not saved for "time" + $this->assertNotEquals($time->format('Y'), $dbEntity->getTime()->format('Y')); + } + + public function testUpdateDateTime(): void { + $time = new \DateTime('2000-01-01 01:01:00'); + $datetime = new \DateTimeImmutable('2000-01-01 02:02:00'); + + $mapper = new QBDBTestMapper($this->connection); + $entity = new QBDBTestEntity(); + $entity->setTime('now'); + $entity->setDatetime('now'); + + /** @var QBDBTestEntity */ + $entity = $mapper->insert($entity); + $this->assertNotNull($entity->getId()); + + // Update the values + $entity->setTime($time); + $entity->setDatetime($datetime); + $mapper->update($entity); + + $dbEntity = $mapper->getById($entity->getId()); + $this->assertEquals($time->format('H:i:s'), $dbEntity->getTime()->format('H:i:s')); + $this->assertEquals($datetime->format('Y-m-d H:i:s'), $dbEntity->getDatetime()->format('Y-m-d H:i:s')); + } + + protected function prepareTestingTable(): void { + if ($this->schemaSetup) { + $this->connection->getQueryBuilder()->delete('testing')->executeStatement(); + } + + $prefix = Server::get(IConfig::class)->getSystemValueString('dbtableprefix', 'oc_'); + $schema = $this->connection->createSchema(); + try { + $schema->getTable($prefix . 'testing'); + $this->connection->getQueryBuilder()->delete('testing')->executeStatement(); + } catch (SchemaException $e) { + $this->schemaSetup = true; + $table = $schema->createTable($prefix . 'testing'); + $table->addColumn('id', Types::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + ]); + + $table->addColumn('time', Types::TIME, [ + 'notnull' => false, + ]); + + $table->addColumn('datetime', Types::DATETIME_IMMUTABLE, [ + 'notnull' => false, + ]); + + $table->setPrimaryKey(['id']); + $this->connection->migrateToSchema($schema); + } + } +} diff --git a/tests/lib/AppFramework/Db/QBMapperTest.php b/tests/lib/AppFramework/Db/QBMapperTest.php index 7865979ef41..0f18ef3f204 100644 --- a/tests/lib/AppFramework/Db/QBMapperTest.php +++ b/tests/lib/AppFramework/Db/QBMapperTest.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright 2019, Marius David Wieschollek <git.public@mdns.eu> - * - * @author Marius David Wieschollek <git.public@mdns.eu> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace Test\AppFramework\Db; @@ -27,7 +11,9 @@ use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IExpressionBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\Types; use OCP\IDBConnection; +use PHPUnit\Framework\MockObject\MockObject; /** * @method bool getBoolProp() @@ -40,6 +26,8 @@ use OCP\IDBConnection; * @method void setBooleanProp(bool $booleanProp) * @method integer getIntegerProp() * @method void setIntegerProp(integer $integerProp) + * @method ?\DateTimeImmutable getDatetimeProp() + * @method void setDatetimeProp(?\DateTimeImmutable $datetime) */ class QBTestEntity extends Entity { protected $intProp; @@ -47,18 +35,20 @@ class QBTestEntity extends Entity { protected $stringProp; protected $integerProp; protected $booleanProp; + protected $jsonProp; + protected $datetimeProp; public function __construct() { $this->addType('intProp', 'int'); $this->addType('boolProp', 'bool'); - $this->addType('stringProp', 'string'); - $this->addType('integerProp', 'integer'); - $this->addType('booleanProp', 'boolean'); + $this->addType('stringProp', Types::STRING); + $this->addType('integerProp', Types::INTEGER); + $this->addType('booleanProp', Types::BOOLEAN); + $this->addType('jsonProp', Types::JSON); + $this->addType('datetimeProp', Types::DATETIME_IMMUTABLE); } } -; - /** * Class QBTestMapper * @@ -69,7 +59,7 @@ class QBTestMapper extends QBMapper { parent::__construct($db, 'table'); } - public function getParameterTypeForPropertyForTest(Entity $entity, string $property): int { + public function getParameterTypeForPropertyForTest(Entity $entity, string $property) { return parent::getParameterTypeForProperty($entity, $property); } } @@ -81,25 +71,10 @@ class QBTestMapper extends QBMapper { */ class QBMapperTest extends \Test\TestCase { - /** - * @var \PHPUnit\Framework\MockObject\MockObject|IDBConnection - */ - protected $db; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|IQueryBuilder - */ - protected $qb; - - /** - * @var \PHPUnit\Framework\MockObject\MockObject|IExpressionBuilder - */ - protected $expr; - - /** - * @var \Test\AppFramework\Db\QBTestMapper - */ - protected $mapper; + protected IDBConnection&MockObject $db; + protected IQueryBuilder&MockObject $qb; + protected IExpressionBuilder&MockObject $expr; + protected QBTestMapper $mapper; /** * @throws \ReflectionException @@ -109,11 +84,11 @@ class QBMapperTest extends \Test\TestCase { ->disableOriginalConstructor() ->getMock(); - $this->qb = $this->getMockBuilder(IQueryBuilder:: class) + $this->qb = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); - $this->expr = $this->getMockBuilder(IExpressionBuilder:: class) + $this->expr = $this->getMockBuilder(IExpressionBuilder::class) ->disableOriginalConstructor() ->getMock(); @@ -125,45 +100,60 @@ class QBMapperTest extends \Test\TestCase { $this->mapper = new QBTestMapper($this->db); } - - public function testInsertEntityParameterTypeMapping() { + + public function testInsertEntityParameterTypeMapping(): void { + $datetime = new \DateTimeImmutable(); $entity = new QBTestEntity(); $entity->setIntProp(123); $entity->setBoolProp(true); $entity->setStringProp('string'); $entity->setIntegerProp(456); $entity->setBooleanProp(false); + $entity->setDatetimeProp($datetime); $intParam = $this->qb->createNamedParameter('int_prop', IQueryBuilder::PARAM_INT); $boolParam = $this->qb->createNamedParameter('bool_prop', IQueryBuilder::PARAM_BOOL); $stringParam = $this->qb->createNamedParameter('string_prop', IQueryBuilder::PARAM_STR); $integerParam = $this->qb->createNamedParameter('integer_prop', IQueryBuilder::PARAM_INT); $booleanParam = $this->qb->createNamedParameter('boolean_prop', IQueryBuilder::PARAM_BOOL); - - $this->qb->expects($this->exactly(5)) + $datetimeParam = $this->qb->createNamedParameter('datetime_prop', IQueryBuilder::PARAM_DATETIME_IMMUTABLE); + + $createNamedParameterCalls = [ + [123, IQueryBuilder::PARAM_INT, null], + [true, IQueryBuilder::PARAM_BOOL, null], + ['string', IQueryBuilder::PARAM_STR, null], + [456, IQueryBuilder::PARAM_INT, null], + [false, IQueryBuilder::PARAM_BOOL, null], + [$datetime, IQueryBuilder::PARAM_DATETIME_IMMUTABLE, null], + ]; + $this->qb->expects($this->exactly(6)) ->method('createNamedParameter') - ->withConsecutive( - [$this->equalTo(123), $this->equalTo(IQueryBuilder::PARAM_INT)], - [$this->equalTo(true), $this->equalTo(IQueryBuilder::PARAM_BOOL)], - [$this->equalTo('string'), $this->equalTo(IQueryBuilder::PARAM_STR)], - [$this->equalTo(456), $this->equalTo(IQueryBuilder::PARAM_INT)], - [$this->equalTo(false), $this->equalTo(IQueryBuilder::PARAM_BOOL)] - ); - $this->qb->expects($this->exactly(5)) + ->willReturnCallback(function () use (&$createNamedParameterCalls): void { + $expected = array_shift($createNamedParameterCalls); + $this->assertEquals($expected, func_get_args()); + }); + + $setValueCalls = [ + ['int_prop', $intParam], + ['bool_prop', $boolParam], + ['string_prop', $stringParam], + ['integer_prop', $integerParam], + ['boolean_prop', $booleanParam], + ['datetime_prop', $datetimeParam], + ]; + $this->qb->expects($this->exactly(6)) ->method('setValue') - ->withConsecutive( - [$this->equalTo('int_prop'), $this->equalTo($intParam)], - [$this->equalTo('bool_prop'), $this->equalTo($boolParam)], - [$this->equalTo('string_prop'), $this->equalTo($stringParam)], - [$this->equalTo('integer_prop'), $this->equalTo($integerParam)], - [$this->equalTo('boolean_prop'), $this->equalTo($booleanParam)] - ); + ->willReturnCallback(function () use (&$setValueCalls): void { + $expected = array_shift($setValueCalls); + $this->assertEquals($expected, func_get_args()); + }); $this->mapper->insert($entity); } - - public function testUpdateEntityParameterTypeMapping() { + + public function testUpdateEntityParameterTypeMapping(): void { + $datetime = new \DateTimeImmutable(); $entity = new QBTestEntity(); $entity->setId(789); $entity->setIntProp(123); @@ -171,6 +161,8 @@ class QBMapperTest extends \Test\TestCase { $entity->setStringProp('string'); $entity->setIntegerProp(456); $entity->setBooleanProp(false); + $entity->setJsonProp(['hello' => 'world']); + $entity->setDatetimeProp($datetime); $idParam = $this->qb->createNamedParameter('id', IQueryBuilder::PARAM_INT); $intParam = $this->qb->createNamedParameter('int_prop', IQueryBuilder::PARAM_INT); @@ -178,27 +170,41 @@ class QBMapperTest extends \Test\TestCase { $stringParam = $this->qb->createNamedParameter('string_prop', IQueryBuilder::PARAM_STR); $integerParam = $this->qb->createNamedParameter('integer_prop', IQueryBuilder::PARAM_INT); $booleanParam = $this->qb->createNamedParameter('boolean_prop', IQueryBuilder::PARAM_BOOL); - - $this->qb->expects($this->exactly(6)) + $jsonParam = $this->qb->createNamedParameter('json_prop', IQueryBuilder::PARAM_JSON); + $datetimeParam = $this->qb->createNamedParameter('datetime_prop', IQueryBuilder::PARAM_DATETIME_IMMUTABLE); + + $createNamedParameterCalls = [ + [123, IQueryBuilder::PARAM_INT, null], + [true, IQueryBuilder::PARAM_BOOL, null], + ['string', IQueryBuilder::PARAM_STR, null], + [456, IQueryBuilder::PARAM_INT, null], + [false, IQueryBuilder::PARAM_BOOL, null], + [['hello' => 'world'], IQueryBuilder::PARAM_JSON, null], + [$datetime, IQueryBuilder::PARAM_DATETIME_IMMUTABLE, null], + [789, IQueryBuilder::PARAM_INT, null], + ]; + $this->qb->expects($this->exactly(8)) ->method('createNamedParameter') - ->withConsecutive( - [$this->equalTo(123), $this->equalTo(IQueryBuilder::PARAM_INT)], - [$this->equalTo(true), $this->equalTo(IQueryBuilder::PARAM_BOOL)], - [$this->equalTo('string'), $this->equalTo(IQueryBuilder::PARAM_STR)], - [$this->equalTo(456), $this->equalTo(IQueryBuilder::PARAM_INT)], - [$this->equalTo(false), $this->equalTo(IQueryBuilder::PARAM_BOOL)], - [$this->equalTo(789), $this->equalTo(IQueryBuilder::PARAM_INT)] - ); - - $this->qb->expects($this->exactly(5)) + ->willReturnCallback(function () use (&$createNamedParameterCalls): void { + $expected = array_shift($createNamedParameterCalls); + $this->assertEquals($expected, func_get_args()); + }); + + $setCalls = [ + ['int_prop', $intParam], + ['bool_prop', $boolParam], + ['string_prop', $stringParam], + ['integer_prop', $integerParam], + ['boolean_prop', $booleanParam], + ['json_prop', $datetimeParam], + ['datetime_prop', $datetimeParam], + ]; + $this->qb->expects($this->exactly(7)) ->method('set') - ->withConsecutive( - [$this->equalTo('int_prop'), $this->equalTo($intParam)], - [$this->equalTo('bool_prop'), $this->equalTo($boolParam)], - [$this->equalTo('string_prop'), $this->equalTo($stringParam)], - [$this->equalTo('integer_prop'), $this->equalTo($integerParam)], - [$this->equalTo('boolean_prop'), $this->equalTo($booleanParam)] - ); + ->willReturnCallback(function () use (&$setCalls): void { + $expected = array_shift($setCalls); + $this->assertEquals($expected, func_get_args()); + }); $this->expr->expects($this->once()) ->method('eq') @@ -208,8 +214,8 @@ class QBMapperTest extends \Test\TestCase { $this->mapper->update($entity); } - - public function testGetParameterTypeForProperty() { + + public function testGetParameterTypeForProperty(): void { $entity = new QBTestEntity(); $intType = $this->mapper->getParameterTypeForPropertyForTest($entity, 'intProp'); @@ -227,6 +233,12 @@ class QBMapperTest extends \Test\TestCase { $stringType = $this->mapper->getParameterTypeForPropertyForTest($entity, 'stringProp'); $this->assertEquals(IQueryBuilder::PARAM_STR, $stringType, 'String type property mapping incorrect'); + $jsonType = $this->mapper->getParameterTypeForPropertyForTest($entity, 'jsonProp'); + $this->assertEquals(IQueryBuilder::PARAM_JSON, $jsonType, 'JSON type property mapping incorrect'); + + $datetimeType = $this->mapper->getParameterTypeForPropertyForTest($entity, 'datetimeProp'); + $this->assertEquals(IQueryBuilder::PARAM_DATETIME_IMMUTABLE, $datetimeType, 'DateTimeImmutable type property mapping incorrect'); + $unknownType = $this->mapper->getParameterTypeForPropertyForTest($entity, 'someProp'); $this->assertEquals(IQueryBuilder::PARAM_STR, $unknownType, 'Unknown type property mapping incorrect'); } diff --git a/tests/lib/AppFramework/Db/TransactionalTest.php b/tests/lib/AppFramework/Db/TransactionalTest.php new file mode 100644 index 00000000000..72a3d9ae59f --- /dev/null +++ b/tests/lib/AppFramework/Db/TransactionalTest.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace lib\AppFramework\Db; + +use OCP\AppFramework\Db\TTransactional; +use OCP\IDBConnection; +use PHPUnit\Framework\MockObject\MockObject; +use RuntimeException; +use Test\TestCase; + +class TransactionalTest extends TestCase { + /** @var IDBConnection|MockObject */ + private IDBConnection $db; + + protected function setUp(): void { + parent::setUp(); + + $this->db = $this->createMock(IDBConnection::class); + } + + public function testAtomicRollback(): void { + $test = new class($this->db) { + use TTransactional; + + public function __construct( + private IDBConnection $db, + ) { + } + + public function fail(): void { + $this->atomic(function (): void { + throw new RuntimeException('nope'); + }, $this->db); + } + }; + $this->db->expects(self::once()) + ->method('beginTransaction'); + $this->db->expects(self::once()) + ->method('rollback'); + $this->db->expects(self::never()) + ->method('commit'); + $this->expectException(RuntimeException::class); + + $test->fail(); + } + + public function testAtomicCommit(): void { + $test = new class($this->db) { + use TTransactional; + + public function __construct( + private IDBConnection $db, + ) { + } + + public function succeed(): int { + return $this->atomic(function () { + return 3; + }, $this->db); + } + }; + $this->db->expects(self::once()) + ->method('beginTransaction'); + $this->db->expects(self::never()) + ->method('rollback'); + $this->db->expects(self::once()) + ->method('commit'); + + $result = $test->succeed(); + + self::assertEquals(3, $result); + } +} |