From 9efbc9c1d533fb970a5cd89f154e3a4fb7ff882d Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sun, 19 Dec 2021 18:11:35 -0300 Subject: [PATCH 01/22] Add comments reactions Signed-off-by: Vitor Mattos --- .../Version24000Date20211222112246.php | 96 +++++++++ lib/private/Comments/Comment.php | 16 ++ lib/private/Comments/Manager.php | 183 ++++++++++++++++++ lib/public/Comments/IComment.php | 21 ++ 4 files changed, 316 insertions(+) create mode 100644 core/Migrations/Version24000Date20211222112246.php diff --git a/core/Migrations/Version24000Date20211222112246.php b/core/Migrations/Version24000Date20211222112246.php new file mode 100644 index 00000000000..3a5bb2712b0 --- /dev/null +++ b/core/Migrations/Version24000Date20211222112246.php @@ -0,0 +1,96 @@ + + * + * @author Vitor Mattos + * + * @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 . + * + */ + +namespace OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version24000Date20211222112246 extends SimpleMigrationStep { + private const TABLE_NAME = 'reactions'; + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $comments = $schema->getTable('comments'); + if (!$comments->hasColumn('reactions')) { + $comments->addColumn('reactions', Types::STRING, [ + 'notnull' => false, + 'length' => 4000, + ]); + } + + if (!$schema->hasTable(self::TABLE_NAME)) { + $table = $schema->createTable(self::TABLE_NAME); + $table->addColumn('id', Types::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('parent_id', Types::BIGINT, [ + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('message_id', Types::BIGINT, [ + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('actor_type', 'string', [ + 'notnull' => true, + 'length' => 64, + 'default' => '', + ]); + $table->addColumn('actor_id', 'string', [ + 'notnull' => true, + 'length' => 255, + 'default' => '', + ]); + $table->addColumn('reaction', Types::STRING, [ + 'notnull' => true, + 'length' => 2, + ]); + $table->setPrimaryKey(['id']); + $table->addIndex(['reaction'], 'comment_reaction'); + $table->addIndex(['parent_id'], 'comment_reaction_parent_id'); + $table->addUniqueIndex(['parent_id', 'actor_type', 'actor_id', 'reaction'], 'comment_reaction_unique'); + return $schema; + } + return null; + } +} diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php index 5cf04092101..2b338efc75f 100644 --- a/lib/private/Comments/Comment.php +++ b/lib/private/Comments/Comment.php @@ -44,6 +44,7 @@ class Comment implements IComment { 'referenceId' => null, 'creationDT' => null, 'latestChildDT' => null, + 'reactions' => null, ]; /** @@ -430,6 +431,21 @@ class Comment implements IComment { return $this; } + /** + * @inheritDoc + */ + public function getReactions(): array { + return $this->data['reactions'] ?? []; + } + + /** + * @inheritDoc + */ + public function setReactions(?array $reactions): IComment { + $this->data['reactions'] = $reactions; + return $this; + } + /** * sets the comment data based on an array with keys as taken from the * database. diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index d62db2926fb..7af905c43c9 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -102,6 +102,7 @@ class Manager implements ICommentsManager { } $data['children_count'] = (int)$data['children_count']; $data['reference_id'] = $data['reference_id'] ?? null; + $data['reactions'] = json_decode($data['reactions'], true); return $data; } @@ -899,12 +900,136 @@ class Manager implements ICommentsManager { } if ($affectedRows > 0 && $comment instanceof IComment) { + if ($comment->getVerb() === 'reaction_deleted') { + $this->deleteReaction($comment); + } $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment); } return ($affectedRows > 0); } + private function deleteReaction(IComment $comment): void { + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('reactions') + ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($comment->getParentId()))) + ->andWhere($qb->expr()->eq('message_id', $qb->createNamedParameter($comment->getId()))) + ->executeStatement(); + $this->sumReactions($comment); + } + + /** + * Get comment related with user reaction + * + * @param integer $parentId + * @param string $actorType + * @param string $actorId + * @param string $reaction + * @return IComment + * @throws NotFoundException + * @since 24.0.0 + */ + public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment { + $qb = $this->dbConn->getQueryBuilder(); + $messageId = $qb + ->select('message_id') + ->from('reactions') + ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($parentId))) + ->andWhere($qb->expr()->eq('actor_type', $qb->createNamedParameter($actorType))) + ->andWhere($qb->expr()->eq('actor_id', $qb->createNamedParameter($actorId))) + ->andWhere($qb->expr()->eq('reaction', $qb->createNamedParameter($reaction))) + ->executeQuery() + ->fetchOne(); + if (!$messageId) { + throw new NotFoundException('Comment related with reaction not found'); + } + return $this->get($messageId); + } + + /** + * Retrieve all reactions with specific reaction of a message + * + * @param integer $parentId + * @param string $reaction + * @return IComment[] + * @since 24.0.0 + */ + public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): ?array { + $qb = $this->dbConn->getQueryBuilder(); + $result = $qb + ->select('message_id') + ->from('reactions') + ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($parentId))) + ->andWhere($qb->expr()->eq('reaction', $qb->createNamedParameter($reaction))) + ->executeQuery(); + + $commentIds = []; + while ($data = $result->fetch()) { + $commentIds[] = $data['message_id']; + } + $comments = []; + if ($commentIds) { + $comments = $this->getCommentsOnList($commentIds); + } + + return $comments; + } + + /** + * Retrieve all reactions of a message + * + * @param integer $parentId + * @param string $reaction + * @return IComment[] + * @since 24.0.0 + */ + public function retrieveAllReactions(int $parentId): array { + $qb = $this->dbConn->getQueryBuilder(); + $result = $qb + ->select('message_id') + ->from('reactions') + ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($parentId))) + ->executeQuery(); + + $commentIds = []; + while ($data = $result->fetch()) { + $commentIds[] = $data['message_id']; + } + $comments = []; + if ($commentIds) { + $comments = $this->getCommentsOnList($commentIds); + } + + return $comments; + } + + /** + * Get all comments on list + * + * @param integer[] $objectIds + * @return IComment[] + * @since 24.0.0 + */ + private function getCommentsOnList(array $objectIds): array { + $query = $this->dbConn->getQueryBuilder(); + + $query->select('*') + ->from('comments') + ->where($query->expr()->in('id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY))) + ->orderBy('creation_timestamp', 'DESC') + ->addOrderBy('id', 'DESC'); + + $comments = []; + $result = $query->executeQuery(); + while ($data = $result->fetch()) { + $comment = $this->getCommentFromData($data); + $this->cache($comment); + $comments[] = $comment; + } + $result->closeCursor(); + return $comments; + } + /** * saves the comment permanently * @@ -988,12 +1113,66 @@ class Manager implements ICommentsManager { if ($affectedRows > 0) { $comment->setId((string)$qb->getLastInsertId()); + if ($comment->getVerb() === 'reaction') { + $this->addReaction($comment); + } $this->sendEvent(CommentsEvent::EVENT_ADD, $comment); } return $affectedRows > 0; } + private function addReaction(IComment $comment): void { + $qb = $this->dbConn->getQueryBuilder(); + $qb->insert('reactions') + ->values([ + 'parent_id' => $qb->createNamedParameter($comment->getParentId()), + 'message_id' => $qb->createNamedParameter($comment->getId()), + 'actor_type' => $qb->createNamedParameter($comment->getActorType()), + 'actor_id' => $qb->createNamedParameter($comment->getActorId()), + 'reaction' => $qb->createNamedParameter($comment->getMessage()), + ]) + ->executeStatement(); + $this->sumReactions($comment); + } + + private function sumReactions(IComment $comment): void { + $qb = $this->dbConn->getQueryBuilder(); + + $totalQuery = $this->dbConn->getQueryBuilder(); + $totalQuery + ->selectAlias( + $totalQuery->func()->concat( + $totalQuery->expr()->literal('"'), + 'reaction', + $totalQuery->expr()->literal('":'), + $totalQuery->func()->count('id') + ), + 'total' + ) + ->from('reactions', 'r') + ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($comment->getParentId()))) + ->groupBy('r.reaction'); + + $jsonQuery = $this->dbConn->getQueryBuilder(); + $jsonQuery + ->selectAlias( + $totalQuery->func()->concat( + $jsonQuery->expr()->literal('{'), + $jsonQuery->func()->groupConcat('total'), + $jsonQuery->expr()->literal('}') + ), + 'json' + ) + ->from($jsonQuery->createFunction('(' . $totalQuery->getSQL() . ')'), 'json'); + + $qb + ->update('comments') + ->set('reactions', $jsonQuery->createFunction('(' . $jsonQuery->getSQL() . ')')) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getParentId()))) + ->executeStatement(); + } + /** * updates a Comment data row * @@ -1015,6 +1194,10 @@ class Manager implements ICommentsManager { $result = $this->updateQuery($comment, false); } + if ($comment->getVerb() === 'reaction_deleted') { + $this->deleteReaction($comment); + } + $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment); return $result; diff --git a/lib/public/Comments/IComment.php b/lib/public/Comments/IComment.php index b9747aefb5b..8465eaf49f4 100644 --- a/lib/public/Comments/IComment.php +++ b/lib/public/Comments/IComment.php @@ -278,4 +278,25 @@ interface IComment { * @since 19.0.0 */ public function setReferenceId(?string $referenceId): IComment; + + /** + * Returns the reactions array if exists + * + * The keys is the emoji of reaction and the value is the total. + * + * @return array e.g. ["👍":1] + * @since 24.0.0 + */ + public function getReactions(): array; + + /** + * Set summarized array of reactions by reaction type + * + * The keys is the emoji of reaction and the value is the total. + * + * @param array|null $reactions e.g. ["👍":1] + * @return IComment + * @since 24.0.0 + */ + public function setReactions(?array $reactions): IComment; } From 1dda03e1b59a2da7e9c08511120db20c825afeae Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 10 Jan 2022 10:27:00 -0300 Subject: [PATCH 02/22] Rename vars Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 7af905c43c9..599c4e106d7 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -909,13 +909,13 @@ class Manager implements ICommentsManager { return ($affectedRows > 0); } - private function deleteReaction(IComment $comment): void { + private function deleteReaction(IComment $reaction): void { $qb = $this->dbConn->getQueryBuilder(); $qb->delete('reactions') - ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($comment->getParentId()))) - ->andWhere($qb->expr()->eq('message_id', $qb->createNamedParameter($comment->getId()))) + ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($reaction->getParentId()))) + ->andWhere($qb->expr()->eq('message_id', $qb->createNamedParameter($reaction->getId()))) ->executeStatement(); - $this->sumReactions($comment); + $this->sumReactions($reaction->getParentId()); } /** @@ -1122,21 +1122,21 @@ class Manager implements ICommentsManager { return $affectedRows > 0; } - private function addReaction(IComment $comment): void { + private function addReaction(IComment $reaction): void { $qb = $this->dbConn->getQueryBuilder(); $qb->insert('reactions') ->values([ - 'parent_id' => $qb->createNamedParameter($comment->getParentId()), - 'message_id' => $qb->createNamedParameter($comment->getId()), - 'actor_type' => $qb->createNamedParameter($comment->getActorType()), - 'actor_id' => $qb->createNamedParameter($comment->getActorId()), - 'reaction' => $qb->createNamedParameter($comment->getMessage()), + 'parent_id' => $qb->createNamedParameter($reaction->getParentId()), + 'message_id' => $qb->createNamedParameter($reaction->getId()), + 'actor_type' => $qb->createNamedParameter($reaction->getActorType()), + 'actor_id' => $qb->createNamedParameter($reaction->getActorId()), + 'reaction' => $qb->createNamedParameter($reaction->getMessage()), ]) ->executeStatement(); - $this->sumReactions($comment); + $this->sumReactions($reaction->getParentId()); } - private function sumReactions(IComment $comment): void { + private function sumReactions(string $parentId): void { $qb = $this->dbConn->getQueryBuilder(); $totalQuery = $this->dbConn->getQueryBuilder(); @@ -1151,7 +1151,7 @@ class Manager implements ICommentsManager { 'total' ) ->from('reactions', 'r') - ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($comment->getParentId()))) + ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($parentId))) ->groupBy('r.reaction'); $jsonQuery = $this->dbConn->getQueryBuilder(); @@ -1169,7 +1169,7 @@ class Manager implements ICommentsManager { $qb ->update('comments') ->set('reactions', $jsonQuery->createFunction('(' . $jsonQuery->getSQL() . ')')) - ->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getParentId()))) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($parentId))) ->executeStatement(); } From a7c0868a4cc7a0f041484800ad56901f11086459 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sun, 9 Jan 2022 19:10:20 -0300 Subject: [PATCH 03/22] Prevent violate constraint Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 599c4e106d7..382ffbb9028 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -1123,16 +1123,35 @@ class Manager implements ICommentsManager { } private function addReaction(IComment $reaction): void { + // Prevent violate constraint $qb = $this->dbConn->getQueryBuilder(); - $qb->insert('reactions') - ->values([ - 'parent_id' => $qb->createNamedParameter($reaction->getParentId()), - 'message_id' => $qb->createNamedParameter($reaction->getId()), - 'actor_type' => $qb->createNamedParameter($reaction->getActorType()), - 'actor_id' => $qb->createNamedParameter($reaction->getActorId()), - 'reaction' => $qb->createNamedParameter($reaction->getMessage()), - ]) - ->executeStatement(); + $qb->select($qb->func()->count('*')) + ->from('reactions') + ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($reaction->getParentId()))) + ->andWhere($qb->expr()->eq('actor_type', $qb->createNamedParameter($reaction->getActorType()))) + ->andWhere($qb->expr()->eq('actor_id', $qb->createNamedParameter($reaction->getActorId()))) + ->andWhere($qb->expr()->eq('reaction', $qb->createNamedParameter($reaction->getMessage()))); + $result = $qb->executeQuery(); + $exists = (int) $result->fetchOne(); + if (!$exists) { + $qb = $this->dbConn->getQueryBuilder(); + try { + $qb->insert('reactions') + ->values([ + 'parent_id' => $qb->createNamedParameter($reaction->getParentId()), + 'message_id' => $qb->createNamedParameter($reaction->getId()), + 'actor_type' => $qb->createNamedParameter($reaction->getActorType()), + 'actor_id' => $qb->createNamedParameter($reaction->getActorId()), + 'reaction' => $qb->createNamedParameter($reaction->getMessage()), + ]) + ->executeStatement(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + 'app' => 'core_comments', + ]); + } + } $this->sumReactions($reaction->getParentId()); } From afe5b6dd8aa9a0eb761434231327e8daecb1d79e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Tue, 11 Jan 2022 12:20:13 -0300 Subject: [PATCH 04/22] Prevent query error when use subquery Signed-off-by: Vitor Mattos --- build/psalm-baseline.xml | 4 ---- lib/private/DB/QueryBuilder/QueryBuilder.php | 4 ++-- lib/public/DB/QueryBuilder/IQueryBuilder.php | 4 ++-- tests/lib/DB/QueryBuilder/QueryBuilderTest.php | 9 ++++++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 485a3fe706c..f02a11ec7ea 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -4387,10 +4387,6 @@ - - $query->createFunction('(' . $subQuery->getSQL() . ')') - $subQuery->createFunction('(' . $subSubQuery->getSQL() . ')') - $this->userToNotify diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index a362ff8016e..de326a2a317 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -694,7 +694,7 @@ class QueryBuilder implements IQueryBuilder { * ->from('users', 'u') * * - * @param string $from The table. + * @param string|IQueryFunction $from The table. * @param string|null $alias The alias of the table. * * @return $this This QueryBuilder instance. @@ -1303,7 +1303,7 @@ class QueryBuilder implements IQueryBuilder { /** * Returns the table name quoted and with database prefix as needed by the implementation * - * @param string $table + * @param string|IQueryFunction $table * @return string */ public function getTableName($table) { diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php index 7829696970c..669003246d9 100644 --- a/lib/public/DB/QueryBuilder/IQueryBuilder.php +++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php @@ -470,7 +470,7 @@ interface IQueryBuilder { * ->from('users', 'u') * * - * @param string $from The table. + * @param string|IQueryFunction $from The table. * @param string|null $alias The alias of the table. * * @return $this This QueryBuilder instance. @@ -994,7 +994,7 @@ interface IQueryBuilder { /** * Returns the table name quoted and with database prefix as needed by the implementation * - * @param string $table + * @param string|IQueryFunction $table * @return string * @since 9.0.0 */ diff --git a/tests/lib/DB/QueryBuilder/QueryBuilderTest.php b/tests/lib/DB/QueryBuilder/QueryBuilderTest.php index 19278504707..d94fda4852c 100644 --- a/tests/lib/DB/QueryBuilder/QueryBuilderTest.php +++ b/tests/lib/DB/QueryBuilder/QueryBuilderTest.php @@ -1204,6 +1204,9 @@ class QueryBuilderTest extends \Test\TestCase { } public function dataGetTableName() { + $config = $this->createMock(SystemConfig::class); + $logger = $this->createMock(ILogger::class); + $qb = new QueryBuilder(\OC::$server->getDatabaseConnection(), $config, $logger); return [ ['*PREFIX*table', null, '`*PREFIX*table`'], ['*PREFIX*table', true, '`*PREFIX*table`'], @@ -1212,13 +1215,17 @@ class QueryBuilderTest extends \Test\TestCase { ['table', null, '`*PREFIX*table`'], ['table', true, '`*PREFIX*table`'], ['table', false, '`table`'], + + [$qb->createFunction('(' . $qb->select('*')->from('table')->getSQL() . ')'), null, '(SELECT * FROM `*PREFIX*table`)'], + [$qb->createFunction('(' . $qb->select('*')->from('table')->getSQL() . ')'), true, '(SELECT * FROM `*PREFIX*table`)'], + [$qb->createFunction('(' . $qb->select('*')->from('table')->getSQL() . ')'), false, '(SELECT * FROM `*PREFIX*table`)'], ]; } /** * @dataProvider dataGetTableName * - * @param string $tableName + * @param string|IQueryFunction $tableName * @param bool $automatic * @param string $expected */ From 425b5cf081576d4af85cb33b5d8a3540b7ec6fd5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Tue, 11 Jan 2022 12:20:51 -0300 Subject: [PATCH 05/22] Add unit tests Signed-off-by: Vitor Mattos --- tests/lib/Comments/ManagerTest.php | 265 ++++++++++++++++++++++++++++- 1 file changed, 261 insertions(+), 4 deletions(-) diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index 355b1af1347..a0033011cb5 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -32,6 +32,8 @@ class ManagerTest extends TestCase { $sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*comments`'); $this->connection->prepare($sql)->execute(); + $sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*reactions`'); + $this->connection->prepare($sql)->execute(); } protected function addDatabaseEntry($parentId, $topmostParentId, $creationDT = null, $latestChildDT = null, $objectId = null) { @@ -469,14 +471,20 @@ class ManagerTest extends TestCase { $manager->get($id); } - public function testSaveNew() { + /** + * @dataProvider providerTestSaveNew + */ + public function testSaveNew(string $message, string $actorId, string $verb, ?string $parentId): IComment { $manager = $this->getManager(); $comment = new Comment(); $comment - ->setActor('users', 'alice') + ->setActor('users', $actorId) ->setObject('files', 'file64') - ->setMessage('very beautiful, I am impressed!') - ->setVerb('comment'); + ->setMessage($message) + ->setVerb($verb); + if ($parentId) { + $comment->setParentId($parentId); + } $saveSuccessful = $manager->save($comment); $this->assertTrue($saveSuccessful); @@ -487,6 +495,13 @@ class ManagerTest extends TestCase { $loadedComment = $manager->get($comment->getId()); $this->assertSame($comment->getMessage(), $loadedComment->getMessage()); $this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $loadedComment->getCreationDateTime()->getTimestamp()); + return $comment; + } + + public function providerTestSaveNew(): array { + return [ + ['very beautiful, I am impressed!', 'alice', 'comment', null] + ]; } public function testSaveUpdate() { @@ -859,6 +874,78 @@ class ManagerTest extends TestCase { $this->assertTrue(is_string($manager->resolveDisplayName('planet', 'neptune'))); } + /** + * @dataProvider providerTestReactionAddAndDelete + * + * @return void + */ + public function testReactionAddAndDelete(array $comments, array $reactionsExpected) { + $manager = $this->getManager(); + $buffer = []; + foreach ($comments as $commentData) { + [$message, $actorId, $verb, $parentText] = $commentData; + $parentId = null; + if ($parentText) { + $parentId = (string) $buffer[$parentText]->getId(); + } + $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); + if (!$parentId) { + $buffer[$comment->getMessage()] = $comment; + } + } + $comment = end($buffer); + if ($comment->getParentId()) { + $parent = $manager->get($comment->getParentId()); + $this->assertEqualsCanonicalizing($reactionsExpected, $parent->getReactions()); + } + } + + public function providerTestReactionAddAndDelete(): array { + return[ + [ + [ + ['message', 'alice', 'comment', null], + ], [], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ], ['👍' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message'], + ], ['👍' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ], ['👍' => 2], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ['👍', 'frank', 'reaction_deleted', 'message'], + ], ['👍' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ['👍', 'alice', 'reaction_deleted', 'message'], + ['👍', 'frank', 'reaction_deleted', 'message'], + ], [], + ], + ]; + } public function testResolveDisplayNameInvalidType() { $this->expectException(\InvalidArgumentException::class); @@ -872,4 +959,174 @@ class ManagerTest extends TestCase { $manager->registerDisplayNameResolver('planet', $planetClosure); $this->assertTrue(is_string($manager->resolveDisplayName(1337, 'neptune'))); } + + /** + * @dataProvider providerTestRetrieveAllReactions + */ + public function testRetrieveAllReactions(array $comments, array $expected) { + $manager = $this->getManager(); + + $buffer = []; + foreach ($comments as $commentData) { + [$message, $actorId, $verb, $parentText] = $commentData; + $parentId = null; + if ($parentText) { + $parentId = (string) $buffer[$parentText]->getId(); + } + $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); + if (!$parentId) { + $buffer[$comment->getMessage()] = $comment; + } + } + $all = $manager->retrieveAllReactions($buffer['message']->getId()); + $actual = array_map(function ($row) { + return [ + 'message' => $row->getMessage(), + 'actorId' => $row->getActorId(), + ]; + }, $all); + $this->assertEqualsCanonicalizing($expected, $actual); + } + + public function providerTestRetrieveAllReactions(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ], + [], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ], + [ + ['👍', 'alice'], + ['👍', 'frank'], + ], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ], + [ + ['👍', 'alice'], + ['👍', 'frank'], + ], + ], + ]; + } + + /** + * @dataProvider providerTestRetrieveAllReactionsWithSpecificReaction + */ + public function testRetrieveAllReactionsWithSpecificReaction(array $comments, string $reaction, array $expected) { + $manager = $this->getManager(); + + $buffer = []; + foreach ($comments as $commentData) { + [$message, $actorId, $verb, $parentText] = $commentData; + $parentId = null; + if ($parentText) { + $parentId = (string) $buffer[$parentText]->getId(); + } + $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); + if (!$parentId) { + $buffer[$comment->getMessage()] = $comment; + } + } + $all = $manager->retrieveAllReactionsWithSpecificReaction($buffer['message']->getId(), $reaction); + $actual = array_map(function ($row) { + return [ + 'message' => $row->getMessage(), + 'actorId' => $row->getActorId(), + ]; + }, $all); + $this->assertEqualsCanonicalizing($expected, $actual); + } + + public function providerTestRetrieveAllReactionsWithSpecificReaction(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ], + '👎', + [], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ], + '👍', + [ + ['👍', 'alice'], + ['👍', 'frank'], + ], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ['👎', 'alice', 'reaction', 'message'], + ['👍', 'frank', 'reaction', 'message'], + ], + '👎', + [ + ['👎', 'alice'], + ], + ], + ]; + } + + /** + * @dataProvider providerTestGetReactionComment + */ + public function testGetReactionComment(array $comments, $expected) { + $manager = $this->getManager(); + + $buffer = []; + foreach ($comments as $comment) { + [$message, $actorId, $verb, $parentText] = $comment; + $parentId = null; + if ($parentText) { + $parentId = (string) $buffer[$parentText]->getId(); + } + $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); + if (!$parentId) { + $buffer[$comment->getMessage()] = $comment; + } + } + $actual = $manager->getReactionComment($comment->getParentId(), $comment->getActorType(), $comment->getActorId(), $comment->getMessage()); + $this->assertEquals($expected[0], $actual->getMessage()); + $this->assertEquals($expected[1], $actual->getActorId()); + $this->assertEquals($expected[2], $actual->getVerb()); + $this->assertEquals($buffer[$expected[3]]->getId(), $actual->getParentId()); + } + + public function providerTestGetReactionComment(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ], + ['👍', 'alice', 'reaction', 'message'], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👎', 'alice', 'reaction', 'message'], + ], + ['👎', 'alice', 'reaction', 'message'], + ], + ]; + } } From b6d9e0542df4524b79e94a941663c57b2968ef7e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Tue, 11 Jan 2022 13:31:32 -0300 Subject: [PATCH 06/22] Fix column size to work with Oracle. Add unit tests Signed-off-by: Vitor Mattos --- tests/lib/DB/QueryBuilder/QueryBuilderTest.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/lib/DB/QueryBuilder/QueryBuilderTest.php b/tests/lib/DB/QueryBuilder/QueryBuilderTest.php index d94fda4852c..fab080eec86 100644 --- a/tests/lib/DB/QueryBuilder/QueryBuilderTest.php +++ b/tests/lib/DB/QueryBuilder/QueryBuilderTest.php @@ -28,6 +28,7 @@ use OC\DB\QueryBuilder\Literal; use OC\DB\QueryBuilder\Parameter; use OC\DB\QueryBuilder\QueryBuilder; use OC\SystemConfig; +use OCP\DB\QueryBuilder\IQueryFunction; use OCP\IDBConnection; use OCP\ILogger; @@ -506,7 +507,13 @@ class QueryBuilderTest extends \Test\TestCase { } public function dataFrom() { + $config = $this->createMock(SystemConfig::class); + $logger = $this->createMock(ILogger::class); + $qb = new QueryBuilder(\OC::$server->getDatabaseConnection(), $config, $logger); return [ + [$qb->createFunction('(' . $qb->select('*')->from('test')->getSQL() . ')'), 'q', null, null, [ + ['table' => '(SELECT * FROM `*PREFIX*test`)', 'alias' => '`q`'] + ], '(SELECT * FROM `*PREFIX*test`) `q`'], ['data', null, null, null, [['table' => '`*PREFIX*data`', 'alias' => null]], '`*PREFIX*data`'], ['data', 't', null, null, [['table' => '`*PREFIX*data`', 'alias' => '`t`']], '`*PREFIX*data` `t`'], ['data1', null, 'data2', null, [ @@ -523,9 +530,9 @@ class QueryBuilderTest extends \Test\TestCase { /** * @dataProvider dataFrom * - * @param string $table1Name + * @param string|IQueryFunction $table1Name * @param string $table1Alias - * @param string $table2Name + * @param string|IQueryFunction $table2Name * @param string $table2Alias * @param array $expectedQueryPart * @param string $expectedQuery From 9a2736bbc9826ac66c90a315769d2d16c4c8b0d9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Tue, 11 Jan 2022 13:31:32 -0300 Subject: [PATCH 07/22] Fix column size to work with Oracle. Add unit tests Signed-off-by: Vitor Mattos --- core/Migrations/Version24000Date20211222112246.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/Migrations/Version24000Date20211222112246.php b/core/Migrations/Version24000Date20211222112246.php index 3a5bb2712b0..a265bebcd86 100644 --- a/core/Migrations/Version24000Date20211222112246.php +++ b/core/Migrations/Version24000Date20211222112246.php @@ -71,19 +71,19 @@ class Version24000Date20211222112246 extends SimpleMigrationStep { 'length' => 11, 'unsigned' => true, ]); - $table->addColumn('actor_type', 'string', [ + $table->addColumn('actor_type', Types::STRING, [ 'notnull' => true, 'length' => 64, 'default' => '', ]); - $table->addColumn('actor_id', 'string', [ + $table->addColumn('actor_id', Types::STRING, [ 'notnull' => true, - 'length' => 255, + 'length' => 64, 'default' => '', ]); $table->addColumn('reaction', Types::STRING, [ 'notnull' => true, - 'length' => 2, + 'length' => 32, ]); $table->setPrimaryKey(['id']); $table->addIndex(['reaction'], 'comment_reaction'); From 898e87fe1a32be516ae1b33e4bdc283283e2e75a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 12 Jan 2022 10:43:34 -0300 Subject: [PATCH 08/22] Rename vars and move logic to method Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 382ffbb9028..5c7e532f177 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -969,7 +969,7 @@ class Manager implements ICommentsManager { } $comments = []; if ($commentIds) { - $comments = $this->getCommentsOnList($commentIds); + $comments = $this->getCommentsById($commentIds); } return $comments; @@ -996,9 +996,7 @@ class Manager implements ICommentsManager { $commentIds[] = $data['message_id']; } $comments = []; - if ($commentIds) { - $comments = $this->getCommentsOnList($commentIds); - } + $comments = $this->getCommentsById($commentIds); return $comments; } @@ -1006,16 +1004,19 @@ class Manager implements ICommentsManager { /** * Get all comments on list * - * @param integer[] $objectIds + * @param integer[] $commentIds * @return IComment[] * @since 24.0.0 */ - private function getCommentsOnList(array $objectIds): array { + private function getCommentsById(array $commentIds): array { + if (!$commentIds) { + return []; + } $query = $this->dbConn->getQueryBuilder(); $query->select('*') ->from('comments') - ->where($query->expr()->in('id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY))) + ->where($query->expr()->in('id', $query->createNamedParameter($commentIds, IQueryBuilder::PARAM_STR_ARRAY))) ->orderBy('creation_timestamp', 'DESC') ->addOrderBy('id', 'DESC'); From b845042a67586938764b0495d5a28ba86f68c2f2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 12 Jan 2022 10:44:09 -0300 Subject: [PATCH 09/22] Update composer autoload and change string to constant Signed-off-by: Vitor Mattos --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index d8d8dc0fb6b..5dec8296bcf 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -993,6 +993,7 @@ return array( 'OC\\Core\\Migrations\\Version23000Date20211213203940' => $baseDir . '/core/Migrations/Version23000Date20211213203940.php', 'OC\\Core\\Migrations\\Version24000Date20211210141942' => $baseDir . '/core/Migrations/Version24000Date20211210141942.php', 'OC\\Core\\Migrations\\Version24000Date20211230140012' => $baseDir . '/core/Migrations/Version24000Date20211230140012.php', + 'OC\\Core\\Migrations\\Version24000Date20211222112246' => $baseDir . '/core/Migrations/Version24000Date20211222112246.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index a9ce260d050..3f14749850b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1022,6 +1022,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version23000Date20211213203940' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20211213203940.php', 'OC\\Core\\Migrations\\Version24000Date20211210141942' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211210141942.php', 'OC\\Core\\Migrations\\Version24000Date20211230140012' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211230140012.php', + 'OC\\Core\\Migrations\\Version24000Date20211222112246' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211222112246.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', From 52affa9ddd422fec22c4275b307fc87e5f91059f Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 12 Jan 2022 13:31:38 -0300 Subject: [PATCH 10/22] Skip if dont support 4 bytes UTF8 Signed-off-by: Vitor Mattos --- tests/lib/Comments/ManagerTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index a0033011cb5..e588630a483 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -2,6 +2,7 @@ namespace Test\Comments; +use Doctrine\DBAL\Platforms\MySQLPlatform; use OC\Comments\Comment; use OC\Comments\Manager; use OCP\AppFramework\Utility\ITimeFactory; @@ -874,12 +875,21 @@ class ManagerTest extends TestCase { $this->assertTrue(is_string($manager->resolveDisplayName('planet', 'neptune'))); } + private function skipIfNotSupport4ByteUTF() { + // 4 byte UTF doesn't work on mysql + $params = \OC::$server->get(\OC\DB\Connection::class)->getParams(); + if (\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySQLPlatform && $params['charset'] !== 'utf8mb4') { + $this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8'); + } + } + /** * @dataProvider providerTestReactionAddAndDelete * * @return void */ public function testReactionAddAndDelete(array $comments, array $reactionsExpected) { + $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); $buffer = []; foreach ($comments as $commentData) { @@ -964,6 +974,7 @@ class ManagerTest extends TestCase { * @dataProvider providerTestRetrieveAllReactions */ public function testRetrieveAllReactions(array $comments, array $expected) { + $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); $buffer = []; @@ -1026,6 +1037,7 @@ class ManagerTest extends TestCase { * @dataProvider providerTestRetrieveAllReactionsWithSpecificReaction */ public function testRetrieveAllReactionsWithSpecificReaction(array $comments, string $reaction, array $expected) { + $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); $buffer = []; @@ -1090,6 +1102,7 @@ class ManagerTest extends TestCase { * @dataProvider providerTestGetReactionComment */ public function testGetReactionComment(array $comments, $expected) { + $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); $buffer = []; From 1ce894a50c2720a5881b22533d730062b7b4b426 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 13 Jan 2022 11:08:52 -0300 Subject: [PATCH 11/22] Disable reactions if database don't support utf8mb4 Fix column size Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 21 ++++++++++++++++++++- tests/lib/Comments/ManagerTest.php | 5 +---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 5c7e532f177..534de6a937b 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -102,7 +102,9 @@ class Manager implements ICommentsManager { } $data['children_count'] = (int)$data['children_count']; $data['reference_id'] = $data['reference_id'] ?? null; - $data['reactions'] = json_decode($data['reactions'], true); + if ($this->supportReactions()) { + $data['reactions'] = json_decode($data['reactions'], true); + } return $data; } @@ -910,6 +912,9 @@ class Manager implements ICommentsManager { } private function deleteReaction(IComment $reaction): void { + if (!$this->supportReactions()) { + return; + } $qb = $this->dbConn->getQueryBuilder(); $qb->delete('reactions') ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($reaction->getParentId()))) @@ -930,6 +935,9 @@ class Manager implements ICommentsManager { * @since 24.0.0 */ public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment { + if (!$this->supportReactions()) { + throw new NotFoundException('The database does not support reactions'); + } $qb = $this->dbConn->getQueryBuilder(); $messageId = $qb ->select('message_id') @@ -955,6 +963,7 @@ class Manager implements ICommentsManager { * @since 24.0.0 */ public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): ?array { + $this->throwIfNotSupportReactions(); $qb = $this->dbConn->getQueryBuilder(); $result = $qb ->select('message_id') @@ -975,6 +984,10 @@ class Manager implements ICommentsManager { return $comments; } + public function supportReactions(): bool { + return $this->dbConn->supports4ByteText(); + } + /** * Retrieve all reactions of a message * @@ -984,6 +997,9 @@ class Manager implements ICommentsManager { * @since 24.0.0 */ public function retrieveAllReactions(int $parentId): array { + if (!$this->supportReactions()) { + return []; + } $qb = $this->dbConn->getQueryBuilder(); $result = $qb ->select('message_id') @@ -1124,6 +1140,9 @@ class Manager implements ICommentsManager { } private function addReaction(IComment $reaction): void { + if (!$this->supportReactions()) { + return; + } // Prevent violate constraint $qb = $this->dbConn->getQueryBuilder(); $qb->select($qb->func()->count('*')) diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index e588630a483..2a837a02abf 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -2,7 +2,6 @@ namespace Test\Comments; -use Doctrine\DBAL\Platforms\MySQLPlatform; use OC\Comments\Comment; use OC\Comments\Manager; use OCP\AppFramework\Utility\ITimeFactory; @@ -876,9 +875,7 @@ class ManagerTest extends TestCase { } private function skipIfNotSupport4ByteUTF() { - // 4 byte UTF doesn't work on mysql - $params = \OC::$server->get(\OC\DB\Connection::class)->getParams(); - if (\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySQLPlatform && $params['charset'] !== 'utf8mb4') { + if (!$this->getManager()->supportReactions()) { $this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8'); } } From f7cd9956127be66dfd3141e1f05b6d3cfaceea05 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 14 Jan 2022 11:14:52 -0300 Subject: [PATCH 12/22] Order the reaction summary, improvements on validations Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 51 +++++++++++++----- tests/lib/Comments/ManagerTest.php | 84 ++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 13 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 534de6a937b..2e9acb6ca24 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -41,6 +41,7 @@ use OCP\IConfig; use OCP\IDBConnection; use OCP\IUser; use OCP\IInitialStateService; +use OCP\PreConditionNotMetException; use OCP\Util; use Psr\Log\LoggerInterface; @@ -136,6 +137,10 @@ class Manager implements ICommentsManager { throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); } + if ($comment->getVerb() === 'reaction' && strlen($comment->getMessage()) > 8) { + throw new \UnexpectedValueException('Reactions cannot be longer than 8 bytes'); + } + if ($comment->getId() === '') { $comment->setChildrenCount(0); $comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC'))); @@ -912,9 +917,6 @@ class Manager implements ICommentsManager { } private function deleteReaction(IComment $reaction): void { - if (!$this->supportReactions()) { - return; - } $qb = $this->dbConn->getQueryBuilder(); $qb->delete('reactions') ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($reaction->getParentId()))) @@ -926,18 +928,20 @@ class Manager implements ICommentsManager { /** * Get comment related with user reaction * + * Throws PreConditionNotMetException when the system haven't the minimum requirements to + * use reactions + * * @param integer $parentId * @param string $actorType * @param string $actorId * @param string $reaction * @return IComment * @throws NotFoundException + * @throws PreConditionNotMetException * @since 24.0.0 */ public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment { - if (!$this->supportReactions()) { - throw new NotFoundException('The database does not support reactions'); - } + $this->throwIfNotSupportReactions(); $qb = $this->dbConn->getQueryBuilder(); $messageId = $qb ->select('message_id') @@ -984,22 +988,40 @@ class Manager implements ICommentsManager { return $comments; } + /** + * Support reactions + * + * @return boolean + * @since 24.0.0 + */ public function supportReactions(): bool { return $this->dbConn->supports4ByteText(); } + /** + * @throws PreConditionNotMetException + * @since 24.0.0 + */ + private function throwIfNotSupportReactions() { + if (!$this->supportReactions()) { + throw new PreConditionNotMetException('The database does not support reactions'); + } + } + /** * Retrieve all reactions of a message * + * Throws PreConditionNotMetException when the system haven't the minimum requirements to + * use reactions + * * @param integer $parentId * @param string $reaction + * @throws PreConditionNotMetException * @return IComment[] * @since 24.0.0 */ public function retrieveAllReactions(int $parentId): array { - if (!$this->supportReactions()) { - return []; - } + $this->throwIfNotSupportReactions(); $qb = $this->dbConn->getQueryBuilder(); $result = $qb ->select('message_id') @@ -1058,9 +1080,13 @@ class Manager implements ICommentsManager { * Throws NotFoundException when a comment that is to be updated does not * exist anymore at this point of time. * + * Throws PreConditionNotMetException when the system haven't the minimum requirements to + * use reactions + * * @param IComment $comment * @return bool * @throws NotFoundException + * @throws PreConditionNotMetException * @since 9.0.0 */ public function save(IComment $comment) { @@ -1140,9 +1166,6 @@ class Manager implements ICommentsManager { } private function addReaction(IComment $reaction): void { - if (!$this->supportReactions()) { - return; - } // Prevent violate constraint $qb = $this->dbConn->getQueryBuilder(); $qb->select($qb->func()->count('*')) @@ -1191,7 +1214,9 @@ class Manager implements ICommentsManager { ) ->from('reactions', 'r') ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($parentId))) - ->groupBy('r.reaction'); + ->groupBy('r.reaction') + ->orderBy($totalQuery->func()->count('id'), 'DESC') + ->setMaxResults(200); $jsonQuery = $this->dbConn->getQueryBuilder(); $jsonQuery diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index 2a837a02abf..9e34cce6476 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -1139,4 +1139,88 @@ class ManagerTest extends TestCase { ], ]; } + + /** + * @dataProvider providerTestReactionMessageSize + */ + public function testReactionMessageSize($reactionString, $valid) { + if (!$valid) { + $this->expectException(\UnexpectedValueException::class); + } + + $manager = $this->getManager(); + $comment = new Comment(); + $comment->setMessage($reactionString) + ->setVerb('reaction') + ->setActor('users', 'alice') + ->setObject('files', 'file64'); + $status = $manager->save($comment); + $this->assertTrue($status); + } + + public function providerTestReactionMessageSize(): array { + return [ + ['a', true], + ['1', true], + ['12345678', true], + ['123456789', false], + ['👍', true], + ['👍👍', true], + ['👍🏽', true], + ['👍🏽👍', false], + ['👍🏽👍🏽', false], + ]; + } + + /** + * @dataProvider providerTestReactionsSummarizeOrdered + */ + public function testReactionsSummarizeOrdered(array $comments, $expected) { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + $buffer = []; + foreach ($comments as $comment) { + [$message, $actorId, $verb, $parentText] = $comment; + $parentId = null; + if ($parentText) { + $parentId = (string) $buffer[$parentText]->getId(); + } + $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); + if (!$parentId) { + $buffer[$comment->getMessage()] = $comment; + } + } + $actual = $manager->get($comment->getParentId()); + $this->assertSame($expected, $actual->getReactions()); + } + + public function providerTestReactionsSummarizeOrdered(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ['👍', 'alice', 'reaction', 'message'], + ], + ['👍' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['👎', 'John', 'reaction', 'message'], + ['👍', 'Paul', 'reaction', 'message'], + ['👍', 'Peter', 'reaction', 'message'], + ['💜', 'Matthew', 'reaction', 'message'], + ['💜', 'Mark', 'reaction', 'message'], + ['💜', 'Luke', 'reaction', 'message'], + ], + [ + + '💜' => 3, + '👍' => 2, + '👎' => 1, + ], + ], + ]; + } } From f071b4dfbbd5fccae9b7b07b9a13ed71ddc91ce4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 14 Jan 2022 15:56:47 -0300 Subject: [PATCH 13/22] Fix groupConcat and ordering on Oracle Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 9 +++++---- .../DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php | 2 +- .../QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php | 4 ++-- .../FunctionBuilder/PgSqlFunctionBuilder.php | 2 +- .../FunctionBuilder/SqliteFunctionBuilder.php | 2 +- lib/public/DB/QueryBuilder/IFunctionBuilder.php | 3 ++- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 2e9acb6ca24..410fcf26f4d 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -1210,20 +1210,21 @@ class Manager implements ICommentsManager { $totalQuery->expr()->literal('":'), $totalQuery->func()->count('id') ), - 'total' + 'colonseparatedvalue' ) + ->selectAlias($totalQuery->func()->count('id'), 'total') ->from('reactions', 'r') ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($parentId))) ->groupBy('r.reaction') - ->orderBy($totalQuery->func()->count('id'), 'DESC') + ->orderBy('total', 'DESC') ->setMaxResults(200); $jsonQuery = $this->dbConn->getQueryBuilder(); $jsonQuery ->selectAlias( - $totalQuery->func()->concat( + $jsonQuery->func()->concat( $jsonQuery->expr()->literal('{'), - $jsonQuery->func()->groupConcat('total'), + $jsonQuery->func()->groupConcat('colonseparatedvalue', ',', $jsonQuery->getColumnName('total') . ' DESC'), $jsonQuery->expr()->literal('}') ), 'json' diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index 03630ea14a3..ba59f42ce5d 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -59,7 +59,7 @@ class FunctionBuilder implements IFunctionBuilder { return new QueryFunction(sprintf('CONCAT(%s)', implode(', ', $list))); } - public function groupConcat($expr, ?string $separator = ','): IQueryFunction { + public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction { $separator = $this->connection->quote($separator); return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ' SEPARATOR ' . $separator . ')'); } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index 43ecf599eba..77f14a2dda4 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -82,8 +82,8 @@ class OCIFunctionBuilder extends FunctionBuilder { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - public function groupConcat($expr, ?string $separator = ','): IQueryFunction { - $orderByClause = ' WITHIN GROUP(ORDER BY NULL)'; + public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = 'NULL'): IQueryFunction { + $orderByClause = ' WITHIN GROUP(ORDER BY ' . $orderBy . ')'; if (is_null($separator)) { return new QueryFunction('LISTAGG(' . $this->helper->quoteColumnName($expr) . ')' . $orderByClause); } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php index 444f6aa83a4..3a1d9a56734 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php @@ -37,7 +37,7 @@ class PgSqlFunctionBuilder extends FunctionBuilder { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - public function groupConcat($expr, ?string $separator = ','): IQueryFunction { + public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction { $castedExpression = $this->queryBuilder->expr()->castColumn($expr, IQueryBuilder::PARAM_STR); if (is_null($separator)) { diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php index fe700075a82..cfcabc9eb1a 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -36,7 +36,7 @@ class SqliteFunctionBuilder extends FunctionBuilder { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - public function groupConcat($expr, ?string $separator = ','): IQueryFunction { + public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction { $separator = $this->connection->quote($separator); return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')'); } diff --git a/lib/public/DB/QueryBuilder/IFunctionBuilder.php b/lib/public/DB/QueryBuilder/IFunctionBuilder.php index 04c5cbd07bd..9b0c7254250 100644 --- a/lib/public/DB/QueryBuilder/IFunctionBuilder.php +++ b/lib/public/DB/QueryBuilder/IFunctionBuilder.php @@ -62,10 +62,11 @@ interface IFunctionBuilder { * * @param string|IQueryFunction $expr The expression to group * @param string|null $separator The separator + * @param string|null $orderBy Option only used to make compatible with Oracle database if is necessary use order. The default value is null and the Oracle don't will respect the order by of query * @return IQueryFunction * @since 24.0.0 */ - public function groupConcat($expr, ?string $separator = ','): IQueryFunction; + public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction; /** * Takes a substring from the input string From 189f9f96ce8a93e228acc5821099d8e870576292 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 17 Jan 2022 11:56:24 +0100 Subject: [PATCH 14/22] Limit the summary and sort it afterwards Signed-off-by: Joas Schilling --- lib/private/Comments/Manager.php | 21 +++++++++++++------ tests/lib/Comments/ManagerTest.php | 33 ++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 410fcf26f4d..95fb7f24e53 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -104,7 +104,18 @@ class Manager implements ICommentsManager { $data['children_count'] = (int)$data['children_count']; $data['reference_id'] = $data['reference_id'] ?? null; if ($this->supportReactions()) { - $data['reactions'] = json_decode($data['reactions'], true); + $list = json_decode($data['reactions'], true); + // Ordering does not work on the database with group concat and Oracle, + // So we simply sort on the output. + if (is_array($list)) { + uasort($list, static function($a, $b) { + if ($a === $b) { + return 0; + } + return ($a > $b) ? -1 : 1; + }); + } + $data['reactions'] = $list; } return $data; } @@ -1033,10 +1044,8 @@ class Manager implements ICommentsManager { while ($data = $result->fetch()) { $commentIds[] = $data['message_id']; } - $comments = []; - $comments = $this->getCommentsById($commentIds); - return $comments; + return $this->getCommentsById($commentIds); } /** @@ -1217,14 +1226,14 @@ class Manager implements ICommentsManager { ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($parentId))) ->groupBy('r.reaction') ->orderBy('total', 'DESC') - ->setMaxResults(200); + ->setMaxResults(20); $jsonQuery = $this->dbConn->getQueryBuilder(); $jsonQuery ->selectAlias( $jsonQuery->func()->concat( $jsonQuery->expr()->literal('{'), - $jsonQuery->func()->groupConcat('colonseparatedvalue', ',', $jsonQuery->getColumnName('total') . ' DESC'), + $jsonQuery->func()->groupConcat('colonseparatedvalue', ','), $jsonQuery->expr()->literal('}') ), 'json' diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index 9e34cce6476..cba314e880b 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -1175,7 +1175,7 @@ class ManagerTest extends TestCase { /** * @dataProvider providerTestReactionsSummarizeOrdered */ - public function testReactionsSummarizeOrdered(array $comments, $expected) { + public function testReactionsSummarizeOrdered(array $comments, array $expected, bool $isFullMatch) { $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); @@ -1192,7 +1192,13 @@ class ManagerTest extends TestCase { } } $actual = $manager->get($comment->getParentId()); - $this->assertSame($expected, $actual->getReactions()); + + if ($isFullMatch) { + $this->assertSame($expected, $actual->getReactions()); + } else { + $subResult = array_slice($actual->getReactions(), 0, count($expected)); + $this->assertSame($expected, $subResult); + } } public function providerTestReactionsSummarizeOrdered(): array { @@ -1203,11 +1209,31 @@ class ManagerTest extends TestCase { ['👍', 'alice', 'reaction', 'message'], ], ['👍' => 1], + true, ], [ [ ['message', 'alice', 'comment', null], ['👎', 'John', 'reaction', 'message'], + ['💼', 'Luke', 'reaction', 'message'], + ['📋', 'Luke', 'reaction', 'message'], + ['🚀', 'Luke', 'reaction', 'message'], + ['🖤', 'Luke', 'reaction', 'message'], + ['😜', 'Luke', 'reaction', 'message'], + ['🌖', 'Luke', 'reaction', 'message'], + ['💖', 'Luke', 'reaction', 'message'], + ['📥', 'Luke', 'reaction', 'message'], + ['🐉', 'Luke', 'reaction', 'message'], + ['☕', 'Luke', 'reaction', 'message'], + ['🐄', 'Luke', 'reaction', 'message'], + ['🐕', 'Luke', 'reaction', 'message'], + ['🐈', 'Luke', 'reaction', 'message'], + ['🛂', 'Luke', 'reaction', 'message'], + ['🕸', 'Luke', 'reaction', 'message'], + ['🏰', 'Luke', 'reaction', 'message'], + ['⚙️', 'Luke', 'reaction', 'message'], + ['🚨', 'Luke', 'reaction', 'message'], + ['👥', 'Luke', 'reaction', 'message'], ['👍', 'Paul', 'reaction', 'message'], ['👍', 'Peter', 'reaction', 'message'], ['💜', 'Matthew', 'reaction', 'message'], @@ -1215,11 +1241,10 @@ class ManagerTest extends TestCase { ['💜', 'Luke', 'reaction', 'message'], ], [ - '💜' => 3, '👍' => 2, - '👎' => 1, ], + false, ], ]; } From d850dc02207769ef13897867a11083e420f115c4 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 17 Jan 2022 11:58:50 +0100 Subject: [PATCH 15/22] Remove order from groupConcat as it is not working everywhere Signed-off-by: Joas Schilling --- lib/private/Comments/Manager.php | 2 +- .../DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php | 2 +- .../DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php | 4 ++-- .../DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php | 2 +- .../DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php | 2 +- lib/public/DB/QueryBuilder/IFunctionBuilder.php | 3 +-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 95fb7f24e53..a4afffe2cd4 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -1233,7 +1233,7 @@ class Manager implements ICommentsManager { ->selectAlias( $jsonQuery->func()->concat( $jsonQuery->expr()->literal('{'), - $jsonQuery->func()->groupConcat('colonseparatedvalue', ','), + $jsonQuery->func()->groupConcat('colonseparatedvalue'), $jsonQuery->expr()->literal('}') ), 'json' diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index ba59f42ce5d..03630ea14a3 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -59,7 +59,7 @@ class FunctionBuilder implements IFunctionBuilder { return new QueryFunction(sprintf('CONCAT(%s)', implode(', ', $list))); } - public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction { + public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $separator = $this->connection->quote($separator); return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ' SEPARATOR ' . $separator . ')'); } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index 77f14a2dda4..43ecf599eba 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -82,8 +82,8 @@ class OCIFunctionBuilder extends FunctionBuilder { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = 'NULL'): IQueryFunction { - $orderByClause = ' WITHIN GROUP(ORDER BY ' . $orderBy . ')'; + public function groupConcat($expr, ?string $separator = ','): IQueryFunction { + $orderByClause = ' WITHIN GROUP(ORDER BY NULL)'; if (is_null($separator)) { return new QueryFunction('LISTAGG(' . $this->helper->quoteColumnName($expr) . ')' . $orderByClause); } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php index 3a1d9a56734..444f6aa83a4 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php @@ -37,7 +37,7 @@ class PgSqlFunctionBuilder extends FunctionBuilder { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction { + public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $castedExpression = $this->queryBuilder->expr()->castColumn($expr, IQueryBuilder::PARAM_STR); if (is_null($separator)) { diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php index cfcabc9eb1a..fe700075a82 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -36,7 +36,7 @@ class SqliteFunctionBuilder extends FunctionBuilder { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction { + public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $separator = $this->connection->quote($separator); return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')'); } diff --git a/lib/public/DB/QueryBuilder/IFunctionBuilder.php b/lib/public/DB/QueryBuilder/IFunctionBuilder.php index 9b0c7254250..04c5cbd07bd 100644 --- a/lib/public/DB/QueryBuilder/IFunctionBuilder.php +++ b/lib/public/DB/QueryBuilder/IFunctionBuilder.php @@ -62,11 +62,10 @@ interface IFunctionBuilder { * * @param string|IQueryFunction $expr The expression to group * @param string|null $separator The separator - * @param string|null $orderBy Option only used to make compatible with Oracle database if is necessary use order. The default value is null and the Oracle don't will respect the order by of query * @return IQueryFunction * @since 24.0.0 */ - public function groupConcat($expr, ?string $separator = ',', ?string $orderBy = null): IQueryFunction; + public function groupConcat($expr, ?string $separator = ','): IQueryFunction; /** * Takes a substring from the input string From e0b89cd576a4e3ce6811807e0c02273097bfac79 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 17 Jan 2022 12:03:27 +0100 Subject: [PATCH 16/22] Only check mb_strlen() Signed-off-by: Joas Schilling --- lib/private/Comments/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index a4afffe2cd4..84e1a6a3cb4 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -148,7 +148,7 @@ class Manager implements ICommentsManager { throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); } - if ($comment->getVerb() === 'reaction' && strlen($comment->getMessage()) > 8) { + if ($comment->getVerb() === 'reaction' && mb_strlen($comment->getMessage()) > 8) { throw new \UnexpectedValueException('Reactions cannot be longer than 8 bytes'); } From ecb98f772ef309678c983d4f5e1ed9988e9b6fcc Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 17 Jan 2022 12:03:44 +0100 Subject: [PATCH 17/22] Throw if trying to save reaction without support Signed-off-by: Joas Schilling --- lib/private/Comments/Manager.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 84e1a6a3cb4..eae84c14335 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -1099,6 +1099,10 @@ class Manager implements ICommentsManager { * @since 9.0.0 */ public function save(IComment $comment) { + if ($comment->getVerb() === 'reaction') { + $this->throwIfNotSupportReactions(); + } + if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') { $result = $this->insert($comment); } else { From c9f2f0077745da8d0570b387557076ce9c948dc4 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 17 Jan 2022 12:11:49 +0100 Subject: [PATCH 18/22] Fix CS Signed-off-by: Joas Schilling --- lib/private/Comments/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index eae84c14335..a8c4e2bf09e 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -108,7 +108,7 @@ class Manager implements ICommentsManager { // Ordering does not work on the database with group concat and Oracle, // So we simply sort on the output. if (is_array($list)) { - uasort($list, static function($a, $b) { + uasort($list, static function ($a, $b) { if ($a === $b) { return 0; } From b9f74584a1e808019235d349fbae3f1fd1bdd711 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 17 Jan 2022 09:14:56 -0300 Subject: [PATCH 19/22] Fix check after change from string to mb_string Signed-off-by: Vitor Mattos --- lib/private/Comments/Manager.php | 4 ++-- tests/lib/Comments/ManagerTest.php | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index a8c4e2bf09e..e87ac5cd5cc 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -148,8 +148,8 @@ class Manager implements ICommentsManager { throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); } - if ($comment->getVerb() === 'reaction' && mb_strlen($comment->getMessage()) > 8) { - throw new \UnexpectedValueException('Reactions cannot be longer than 8 bytes'); + if ($comment->getVerb() === 'reaction' && mb_strlen($comment->getMessage()) > 2) { + throw new \UnexpectedValueException('Reactions cannot be longer than 2 chars (emoji with skin tone have two chars)'); } if ($comment->getId() === '') { diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index cba314e880b..d1d9a8a05d4 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -1144,6 +1144,7 @@ class ManagerTest extends TestCase { * @dataProvider providerTestReactionMessageSize */ public function testReactionMessageSize($reactionString, $valid) { + $this->skipIfNotSupport4ByteUTF(); if (!$valid) { $this->expectException(\UnexpectedValueException::class); } @@ -1162,8 +1163,8 @@ class ManagerTest extends TestCase { return [ ['a', true], ['1', true], - ['12345678', true], - ['123456789', false], + ['12', true], + ['123', false], ['👍', true], ['👍👍', true], ['👍🏽', true], From ab630f2cabbd15dbbf15cad65a4e4cf0cb230665 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 19 Jan 2022 07:56:01 -0300 Subject: [PATCH 20/22] Fix conflict Signed-off-by: Vitor Mattos --- lib/composer/composer/autoload_classmap.php | 2 +- lib/composer/composer/autoload_static.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 5dec8296bcf..73aaa10f731 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -992,8 +992,8 @@ return array( 'OC\\Core\\Migrations\\Version23000Date20211203110726' => $baseDir . '/core/Migrations/Version23000Date20211203110726.php', 'OC\\Core\\Migrations\\Version23000Date20211213203940' => $baseDir . '/core/Migrations/Version23000Date20211213203940.php', 'OC\\Core\\Migrations\\Version24000Date20211210141942' => $baseDir . '/core/Migrations/Version24000Date20211210141942.php', - 'OC\\Core\\Migrations\\Version24000Date20211230140012' => $baseDir . '/core/Migrations/Version24000Date20211230140012.php', 'OC\\Core\\Migrations\\Version24000Date20211222112246' => $baseDir . '/core/Migrations/Version24000Date20211222112246.php', + 'OC\\Core\\Migrations\\Version24000Date20211230140012' => $baseDir . '/core/Migrations/Version24000Date20211230140012.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 3f14749850b..96e70bed1a3 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1021,8 +1021,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version23000Date20211203110726' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20211203110726.php', 'OC\\Core\\Migrations\\Version23000Date20211213203940' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20211213203940.php', 'OC\\Core\\Migrations\\Version24000Date20211210141942' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211210141942.php', - 'OC\\Core\\Migrations\\Version24000Date20211230140012' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211230140012.php', 'OC\\Core\\Migrations\\Version24000Date20211222112246' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211222112246.php', + 'OC\\Core\\Migrations\\Version24000Date20211230140012' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20211230140012.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', From 7bb01b6ccc6ee3707d61be94ff70b9454f404288 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 21 Jan 2022 08:40:29 -0300 Subject: [PATCH 21/22] Refactor and fixes on tests Signed-off-by: Vitor Mattos --- tests/lib/Comments/ManagerTest.php | 259 +++++++++++++++-------------- 1 file changed, 135 insertions(+), 124 deletions(-) diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index d1d9a8a05d4..23a9346909a 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -472,12 +472,13 @@ class ManagerTest extends TestCase { } /** - * @dataProvider providerTestSaveNew + * @dataProvider providerTestSave */ - public function testSaveNew(string $message, string $actorId, string $verb, ?string $parentId): IComment { + public function testSave(string $message, string $actorId, string $verb, ?string $parentId, ?string $id = ''): IComment { $manager = $this->getManager(); $comment = new Comment(); $comment + ->setId($id) ->setActor('users', $actorId) ->setObject('files', 'file64') ->setMessage($message) @@ -498,7 +499,7 @@ class ManagerTest extends TestCase { return $comment; } - public function providerTestSaveNew(): array { + public function providerTestSave(): array { return [ ['very beautiful, I am impressed!', 'alice', 'comment', null] ]; @@ -883,24 +884,16 @@ class ManagerTest extends TestCase { /** * @dataProvider providerTestReactionAddAndDelete * + * @param IComment[] $comments + * @param array $reactionsExpected * @return void */ public function testReactionAddAndDelete(array $comments, array $reactionsExpected) { $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); - $buffer = []; - foreach ($comments as $commentData) { - [$message, $actorId, $verb, $parentText] = $commentData; - $parentId = null; - if ($parentText) { - $parentId = (string) $buffer[$parentText]->getId(); - } - $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); - if (!$parentId) { - $buffer[$comment->getMessage()] = $comment; - } - } - $comment = end($buffer); + + $processedComments = $this->proccessComments($comments); + $comment = end($processedComments); if ($comment->getParentId()) { $parent = $manager->get($comment->getParentId()); $this->assertEqualsCanonicalizing($reactionsExpected, $parent->getReactions()); @@ -917,38 +910,38 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], ], ['👍' => 1], ], [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'alice', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'alice', 'reaction', 'message#alice'], ], ['👍' => 1], ], [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], ], ['👍' => 2], ], [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], - ['👍', 'frank', 'reaction_deleted', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction_deleted', 'message#alice'], ], ['👍' => 1], ], [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], - ['👍', 'alice', 'reaction_deleted', 'message'], - ['👍', 'frank', 'reaction_deleted', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], + ['👍', 'alice', 'reaction_deleted', 'message#alice'], + ['👍', 'frank', 'reaction_deleted', 'message#alice'], ], [], ], ]; @@ -968,25 +961,38 @@ class ManagerTest extends TestCase { } /** - * @dataProvider providerTestRetrieveAllReactions + * @param array $data + * @return IComment[] */ - public function testRetrieveAllReactions(array $comments, array $expected) { - $this->skipIfNotSupport4ByteUTF(); - $manager = $this->getManager(); - - $buffer = []; - foreach ($comments as $commentData) { - [$message, $actorId, $verb, $parentText] = $commentData; + private function proccessComments(array $data): array { + /** @var IComment[] */ + $comments = []; + foreach ($data as $comment) { + [$message, $actorId, $verb, $parentText] = $comment; $parentId = null; if ($parentText) { - $parentId = (string) $buffer[$parentText]->getId(); + $parentId = (string) $comments[$parentText]->getId(); } - $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); - if (!$parentId) { - $buffer[$comment->getMessage()] = $comment; + $id = ''; + if ($verb === 'reaction_deleted') { + $id = $comments[$message . '#' . $actorId]->getId(); } + $comment = $this->testSave($message, $actorId, $verb, $parentId, $id); + $comments[$comment->getMessage() . '#' . $comment->getActorId()] = $comment; } - $all = $manager->retrieveAllReactions($buffer['message']->getId()); + return $comments; + } + + /** + * @dataProvider providerTestRetrieveAllReactions + */ + public function testRetrieveAllReactions(array $comments, array $expected) { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + $processedComments = $this->proccessComments($comments); + $comment = reset($processedComments); + $all = $manager->retrieveAllReactions($comment->getId()); $actual = array_map(function ($row) { return [ 'message' => $row->getMessage(), @@ -1007,8 +1013,8 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], ], [ ['👍', 'alice'], @@ -1018,9 +1024,9 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], ], [ ['👍', 'alice'], @@ -1037,19 +1043,9 @@ class ManagerTest extends TestCase { $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); - $buffer = []; - foreach ($comments as $commentData) { - [$message, $actorId, $verb, $parentText] = $commentData; - $parentId = null; - if ($parentText) { - $parentId = (string) $buffer[$parentText]->getId(); - } - $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); - if (!$parentId) { - $buffer[$comment->getMessage()] = $comment; - } - } - $all = $manager->retrieveAllReactionsWithSpecificReaction($buffer['message']->getId(), $reaction); + $processedComments = $this->proccessComments($comments); + $comment = reset($processedComments); + $all = $manager->retrieveAllReactionsWithSpecificReaction($comment->getId(), $reaction); $actual = array_map(function ($row) { return [ 'message' => $row->getMessage(), @@ -1071,8 +1067,8 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], ], '👍', [ @@ -1083,9 +1079,9 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], - ['👎', 'alice', 'reaction', 'message'], - ['👍', 'frank', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], + ['👎', 'alice', 'reaction', 'message#alice'], + ['👍', 'frank', 'reaction', 'message#alice'], ], '👎', [ @@ -1098,44 +1094,68 @@ class ManagerTest extends TestCase { /** * @dataProvider providerTestGetReactionComment */ - public function testGetReactionComment(array $comments, $expected) { + public function testGetReactionComment(array $comments, array $expected, bool $notFound) { $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); - $buffer = []; - foreach ($comments as $comment) { - [$message, $actorId, $verb, $parentText] = $comment; - $parentId = null; - if ($parentText) { - $parentId = (string) $buffer[$parentText]->getId(); - } - $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); - if (!$parentId) { - $buffer[$comment->getMessage()] = $comment; - } + $processedComments = $this->proccessComments($comments); + + $keys = ['message', 'actorId', 'verb', 'parent']; + $expected = array_combine($keys, $expected); + + if ($notFound) { + $this->expectException(\OCP\Comments\NotFoundException::class); } + $comment = $processedComments[$expected['message'] . '#' . $expected['actorId']]; $actual = $manager->getReactionComment($comment->getParentId(), $comment->getActorType(), $comment->getActorId(), $comment->getMessage()); - $this->assertEquals($expected[0], $actual->getMessage()); - $this->assertEquals($expected[1], $actual->getActorId()); - $this->assertEquals($expected[2], $actual->getVerb()); - $this->assertEquals($buffer[$expected[3]]->getId(), $actual->getParentId()); + if (!$notFound) { + $this->assertEquals($expected['message'], $actual->getMessage()); + $this->assertEquals($expected['actorId'], $actual->getActorId()); + $this->assertEquals($expected['verb'], $actual->getVerb()); + $this->assertEquals($processedComments[$expected['parent']]->getId(), $actual->getParentId()); + } } public function providerTestGetReactionComment(): array { return [ [ [ - ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], + ['message', 'Matthew', 'comment', null], + ['👍', 'Matthew', 'reaction', 'message#Matthew'], + ['👍', 'Mark', 'reaction', 'message#Matthew'], + ['👍', 'Luke', 'reaction', 'message#Matthew'], + ['👍', 'John', 'reaction', 'message#Matthew'], ], - ['👍', 'alice', 'reaction', 'message'], + ['👍', 'Matthew', 'reaction', 'message#Matthew'], + false, ], [ [ - ['message', 'alice', 'comment', null], - ['👎', 'alice', 'reaction', 'message'], + ['message', 'Matthew', 'comment', null], + ['👍', 'Matthew', 'reaction', 'message#Matthew'], + ['👍', 'Mark', 'reaction', 'message#Matthew'], + ['👍', 'Luke', 'reaction', 'message#Matthew'], + ['👍', 'John', 'reaction', 'message#Matthew'], ], - ['👎', 'alice', 'reaction', 'message'], + ['👍', 'Mark', 'reaction', 'message#Matthew'], + false, + ], + [ + [ + ['message', 'Matthew', 'comment', null], + ['👎', 'Matthew', 'reaction', 'message#Matthew'], + ], + ['👎', 'Matthew', 'reaction', 'message#Matthew'], + false, + ], + [ + [ + ['message', 'Matthew', 'comment', null], + ['👎', 'Matthew', 'reaction', 'message#Matthew'], + ['👎', 'Matthew', 'reaction_deleted', 'message#Matthew'], + ], + ['👎', 'Matthew', 'reaction', 'message#Matthew'], + true, ], ]; } @@ -1180,18 +1200,9 @@ class ManagerTest extends TestCase { $this->skipIfNotSupport4ByteUTF(); $manager = $this->getManager(); - $buffer = []; - foreach ($comments as $comment) { - [$message, $actorId, $verb, $parentText] = $comment; - $parentId = null; - if ($parentText) { - $parentId = (string) $buffer[$parentText]->getId(); - } - $comment = $this->testSaveNew($message, $actorId, $verb, $parentId); - if (!$parentId) { - $buffer[$comment->getMessage()] = $comment; - } - } + + $processedComments = $this->proccessComments($comments); + $comment = end($processedComments); $actual = $manager->get($comment->getParentId()); if ($isFullMatch) { @@ -1207,7 +1218,7 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👍', 'alice', 'reaction', 'message'], + ['👍', 'alice', 'reaction', 'message#alice'], ], ['👍' => 1], true, @@ -1215,31 +1226,31 @@ class ManagerTest extends TestCase { [ [ ['message', 'alice', 'comment', null], - ['👎', 'John', 'reaction', 'message'], - ['💼', 'Luke', 'reaction', 'message'], - ['📋', 'Luke', 'reaction', 'message'], - ['🚀', 'Luke', 'reaction', 'message'], - ['🖤', 'Luke', 'reaction', 'message'], - ['😜', 'Luke', 'reaction', 'message'], - ['🌖', 'Luke', 'reaction', 'message'], - ['💖', 'Luke', 'reaction', 'message'], - ['📥', 'Luke', 'reaction', 'message'], - ['🐉', 'Luke', 'reaction', 'message'], - ['☕', 'Luke', 'reaction', 'message'], - ['🐄', 'Luke', 'reaction', 'message'], - ['🐕', 'Luke', 'reaction', 'message'], - ['🐈', 'Luke', 'reaction', 'message'], - ['🛂', 'Luke', 'reaction', 'message'], - ['🕸', 'Luke', 'reaction', 'message'], - ['🏰', 'Luke', 'reaction', 'message'], - ['⚙️', 'Luke', 'reaction', 'message'], - ['🚨', 'Luke', 'reaction', 'message'], - ['👥', 'Luke', 'reaction', 'message'], - ['👍', 'Paul', 'reaction', 'message'], - ['👍', 'Peter', 'reaction', 'message'], - ['💜', 'Matthew', 'reaction', 'message'], - ['💜', 'Mark', 'reaction', 'message'], - ['💜', 'Luke', 'reaction', 'message'], + ['👎', 'John', 'reaction', 'message#alice'], + ['💼', 'Luke', 'reaction', 'message#alice'], + ['📋', 'Luke', 'reaction', 'message#alice'], + ['🚀', 'Luke', 'reaction', 'message#alice'], + ['🖤', 'Luke', 'reaction', 'message#alice'], + ['😜', 'Luke', 'reaction', 'message#alice'], + ['🌖', 'Luke', 'reaction', 'message#alice'], + ['💖', 'Luke', 'reaction', 'message#alice'], + ['📥', 'Luke', 'reaction', 'message#alice'], + ['🐉', 'Luke', 'reaction', 'message#alice'], + ['☕', 'Luke', 'reaction', 'message#alice'], + ['🐄', 'Luke', 'reaction', 'message#alice'], + ['🐕', 'Luke', 'reaction', 'message#alice'], + ['🐈', 'Luke', 'reaction', 'message#alice'], + ['🛂', 'Luke', 'reaction', 'message#alice'], + ['🕸', 'Luke', 'reaction', 'message#alice'], + ['🏰', 'Luke', 'reaction', 'message#alice'], + ['⚙️', 'Luke', 'reaction', 'message#alice'], + ['🚨', 'Luke', 'reaction', 'message#alice'], + ['👥', 'Luke', 'reaction', 'message#alice'], + ['👍', 'Paul', 'reaction', 'message#alice'], + ['👍', 'Peter', 'reaction', 'message#alice'], + ['💜', 'Matthew', 'reaction', 'message#alice'], + ['💜', 'Mark', 'reaction', 'message#alice'], + ['💜', 'Luke', 'reaction', 'message#alice'], ], [ '💜' => 3, From 1a1bdd9bc4e90153ef87381b908e0289ca74bc53 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 21 Jan 2022 09:15:49 -0300 Subject: [PATCH 22/22] Fix affected unit test Signed-off-by: Vitor Mattos --- apps/dav/tests/unit/Comments/CommentsNodeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dav/tests/unit/Comments/CommentsNodeTest.php b/apps/dav/tests/unit/Comments/CommentsNodeTest.php index 1738e1b53f9..f085ace9d89 100644 --- a/apps/dav/tests/unit/Comments/CommentsNodeTest.php +++ b/apps/dav/tests/unit/Comments/CommentsNodeTest.php @@ -404,6 +404,7 @@ class CommentsNodeTest extends \Test\TestCase { $ns . 'objectId' => '1848', $ns . 'referenceId' => 'ref', $ns . 'isUnread' => null, + $ns . 'reactions' => [], ]; $this->commentsManager->expects($this->exactly(2))