From e5327997bc3c54924bd8d3a157930dd517eb3cd2 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 29 Apr 2024 17:32:21 +0200 Subject: [PATCH 1/3] test(chat): Add a test that editing old messages is blocked Signed-off-by: Joas Schilling --- appinfo/info.xml | 1 + lib/Command/Developer/AgeChatMessages.php | 112 ++++++++++++++++++ lib/Command/Developer/UpdateDocs.php | 3 +- .../features/bootstrap/FeatureContext.php | 21 +++- .../features/chat-1/edit-message.feature | 5 + 5 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 lib/Command/Developer/AgeChatMessages.php diff --git a/appinfo/info.xml b/appinfo/info.xml index dd5894a1ce9..e654fdf9272 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -87,6 +87,7 @@ OCA\Talk\Command\Bot\State OCA\Talk\Command\Bot\Setup OCA\Talk\Command\Bot\Uninstall + OCA\Talk\Command\Developer\AgeChatMessages OCA\Talk\Command\Developer\UpdateDocs OCA\Talk\Command\Monitor\Calls OCA\Talk\Command\Monitor\HasActiveCalls diff --git a/lib/Command/Developer/AgeChatMessages.php b/lib/Command/Developer/AgeChatMessages.php new file mode 100644 index 00000000000..345ed0b0921 --- /dev/null +++ b/lib/Command/Developer/AgeChatMessages.php @@ -0,0 +1,112 @@ +config->getSystemValue('debug', false) === true; + } + + protected function configure(): void { + $this + ->setName('talk:developer:age-chat-messages') + ->setDescription('Artificially ages chat messages in the given conversation, so deletion and other things can be tested') + ->addArgument( + 'token', + InputArgument::REQUIRED, + 'Token of the room to manipulate' + ) + ->addOption( + 'hours', + null, + InputOption::VALUE_REQUIRED, + 'Number of hours to age all chat messages', + 24 + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $token = $input->getArgument('token'); + $hours = (int) $input->getOption('hours'); + if ($hours < 1) { + $output->writeln('Invalid age: ' . $hours . ''); + return 1; + } + + try { + $room = $this->manager->getRoomByToken($token); + } catch (RoomNotFoundException) { + $output->writeln('Room not found: ' . $token . ''); + return 1; + } + + $update = $this->connection->getQueryBuilder(); + $update->update('comments') + ->set('creation_timestamp', $update->createParameter('creation_timestamp')) + ->set('expire_date', $update->createParameter('expire_date')) + ->set('meta_data', $update->createParameter('meta_data')) + ->where($update->expr()->eq('id', $update->createParameter('id'))); + + $query = $this->connection->getQueryBuilder(); + $query->select('id', 'creation_timestamp', 'expire_date', 'meta_data') + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter('chat'))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($room->getId()))); + + $result = $query->executeQuery(); + while ($row = $result->fetch()) { + $creationTimestamp = new \DateTime($row['creation_timestamp']); + $creationTimestamp->sub(new \DateInterval('PT' . $hours . 'H')); + + $expireDate = null; + if ($row['expire_date']) { + $expireDate = new \DateTime($row['expire_date']); + $expireDate->sub(new \DateInterval('PT' . $hours . 'H')); + } + + $metaData = 'null'; + if ($row['meta_data'] !== 'null') { + $metaData = json_decode($row['meta_data'], true); + if (isset($metaData['last_edited_time'])) { + $metaData['last_edited_time'] -= $hours * 3600; + } + $metaData = json_encode($metaData); + } + + $update->setParameter('id', $row['id']); + $update->setParameter('creation_timestamp', $creationTimestamp, IQueryBuilder::PARAM_DATE); + $update->setParameter('expire_date', $expireDate, IQueryBuilder::PARAM_DATE); + $update->setParameter('meta_data', $metaData); + $update->executeStatement(); + } + $result->closeCursor(); + + return 0; + } +} diff --git a/lib/Command/Developer/UpdateDocs.php b/lib/Command/Developer/UpdateDocs.php index f1f469e8390..17133498db7 100644 --- a/lib/Command/Developer/UpdateDocs.php +++ b/lib/Command/Developer/UpdateDocs.php @@ -41,7 +41,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $info = $this->appManager->getAppInfo('spreed'); $documentation = "# Talk occ commands\n\n"; foreach ($info['commands'] as $namespace) { - if ($namespace === self::class) { + if ($namespace === self::class + || $namespace === AgeChatMessages::class) { continue; } diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index c90a9711bc3..8d1a259074a 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -2004,7 +2004,7 @@ public function userSendsMessageToRoom(string $user, string $sendingMode, string * @param string $statusCode * @param string $apiVersion */ - public function userEditsMessageToRoom(string $user, string $oldMessage, string $identifier, string $newMessage, string $statusCode, string $apiVersion = 'v1') { + public function userEditsMessageToRoom(string $user, string $oldMessage, string $identifier, string $newMessage, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null) { $oldMessage = substr($oldMessage, 1, -1); $oldMessage = str_replace('\n', "\n", $oldMessage); $messageId = self::$textToMessageId[$oldMessage]; @@ -2020,8 +2020,15 @@ public function userEditsMessageToRoom(string $user, string $oldMessage, string $this->assertStatusCode($this->response, $statusCode); sleep(1); // make sure Postgres manages the order of the messages - self::$textToMessageId[$newMessage] = $messageId; - self::$messageIdToText[$messageId] = $newMessage; + if ($statusCode === 200 || $statusCode === 202) { + self::$textToMessageId[$newMessage] = $messageId; + self::$messageIdToText[$messageId] = $newMessage; + } elseif ($formData instanceof TableNode) { + Assert::assertEquals( + $formData->getRowsHash(), + $this->getDataFromResponse($this->response), + ); + } } /** @@ -3876,6 +3883,14 @@ public function userSetsTheRecordingConsentToXWithStatusCode(string $user, int $ $this->assertStatusCode($this->response, $statusCode); } + /** + * @Given /^aging messages (\d+) hours in room "([^"]*)"$/ + */ + public function occAgeChatMessages(int $hours, string $identifier): void { + $this->runOcc(['talk:developer:age-chat-messages', '--hours', $hours, self::$identifierToToken[$identifier]]); + $this->theCommandWasSuccessful(); + } + /** * @Given /^the following recording consent is recorded for (room|user) "([^"]*)"$/ */ diff --git a/tests/integration/features/chat-1/edit-message.feature b/tests/integration/features/chat-1/edit-message.feature index 8d54e8a0513..c6a1a79556d 100644 --- a/tests/integration/features/chat-1/edit-message.feature +++ b/tests/integration/features/chat-1/edit-message.feature @@ -58,6 +58,11 @@ Feature: chat-1/edit-message Then user "participant1" sees the following messages in room "room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | lastEditActorType | lastEditActorId | lastEditActorDisplayName | | room | deleted_users | deleted_users | | Message 1 - Edit 2 | [] | | deleted_users | deleted_users | | + When aging messages 6 hours in room "room" + And user "participant1" edits message "Message 1 - Edit 1" in room "room" to "Message 1 - Edit 2" with 200 + When aging messages 24 hours in room "room" + And user "participant1" edits message "Message 1 - Edit 2" in room "room" to "Message 1 - Edit Too old" with 400 + | error | age | Scenario: Editing a caption Given user "participant1" creates room "room" (v4) From 0a713328e944f2d47d5f15001b29b210bc0deb94 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 29 Apr 2024 17:35:46 +0200 Subject: [PATCH 2/3] test(CI): Enable debug on CI Signed-off-by: Joas Schilling --- .github/workflows/integration-mysql.yml | 1 + .github/workflows/integration-oci.yml | 1 + .github/workflows/integration-pgsql.yml | 1 + .github/workflows/integration-sqlite.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/integration-mysql.yml b/.github/workflows/integration-mysql.yml index 49a3cbff547..ee20c691bbb 100644 --- a/.github/workflows/integration-mysql.yml +++ b/.github/workflows/integration-mysql.yml @@ -134,6 +134,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ config:system:set debug --value=true --type=boolean ./occ config:system:set hashing_default_password --value=true --type=boolean ./occ app:enable --force ${{ env.APP_NAME }} ./occ app:enable --force call_summary_bot diff --git a/.github/workflows/integration-oci.yml b/.github/workflows/integration-oci.yml index eaba42531cc..78605aa0f0b 100644 --- a/.github/workflows/integration-oci.yml +++ b/.github/workflows/integration-oci.yml @@ -146,6 +146,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=oci --database-name=XE --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=owncloud --admin-user admin --admin-pass admin + ./occ config:system:set debug --value=true --type=boolean ./occ config:system:set hashing_default_password --value=true --type=boolean ./occ app:enable --force ${{ env.APP_NAME }} ./occ app:enable --force call_summary_bot diff --git a/.github/workflows/integration-pgsql.yml b/.github/workflows/integration-pgsql.yml index 82610c9ba25..2bf5dcaf220 100644 --- a/.github/workflows/integration-pgsql.yml +++ b/.github/workflows/integration-pgsql.yml @@ -139,6 +139,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ config:system:set debug --value=true --type=boolean ./occ config:system:set hashing_default_password --value=true --type=boolean ./occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu" ./occ config:system:set memcache.distributed --value="\\OC\\Memcache\\APCu" diff --git a/.github/workflows/integration-sqlite.yml b/.github/workflows/integration-sqlite.yml index fcb28dc9376..590d2a71699 100644 --- a/.github/workflows/integration-sqlite.yml +++ b/.github/workflows/integration-sqlite.yml @@ -125,6 +125,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ config:system:set debug --value=true --type=boolean ./occ config:system:set hashing_default_password --value=true --type=boolean ./occ app:enable --force ${{ env.APP_NAME }} ./occ app:enable --force call_summary_bot From 1f8ab2aede63b9ea3eb763ef5feb7b565970a10e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 30 Apr 2024 08:38:51 +0200 Subject: [PATCH 3/3] feat(CI): Add age functionality to cheats app Signed-off-by: Joas Schilling --- .../spreedcheats/appinfo/routes.php | 1 + .../lib/Controller/ApiController.php | 73 ++++++++++++++++--- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/tests/integration/spreedcheats/appinfo/routes.php b/tests/integration/spreedcheats/appinfo/routes.php index f18432cf294..e4bd7eeef3c 100644 --- a/tests/integration/spreedcheats/appinfo/routes.php +++ b/tests/integration/spreedcheats/appinfo/routes.php @@ -9,5 +9,6 @@ return [ 'ocs' => [ ['name' => 'Api#resetSpreed', 'url' => '/', 'verb' => 'DELETE'], + ['name' => 'Api#ageChat', 'url' => '/age', 'verb' => 'POST'], ], ]; diff --git a/tests/integration/spreedcheats/lib/Controller/ApiController.php b/tests/integration/spreedcheats/lib/Controller/ApiController.php index 370dafdac2a..e05e079512d 100644 --- a/tests/integration/spreedcheats/lib/Controller/ApiController.php +++ b/tests/integration/spreedcheats/lib/Controller/ApiController.php @@ -8,6 +8,7 @@ namespace OCA\SpreedCheats\Controller; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -16,22 +17,14 @@ use OCP\Share\IShare; class ApiController extends OCSController { - /** @var IDBConnection */ - private $db; - - public function __construct(string $appName, + public function __construct( + string $appName, IRequest $request, - IDBConnection $db + protected IDBConnection $db, ) { parent::__construct($appName, $request); - $this->db = $db; } - /** - * @NoCSRFRequired - * - * @return DataResponse - */ public function resetSpreed(): DataResponse { $delete = $this->db->getQueryBuilder(); $delete->delete('talk_attachments')->executeStatement(); @@ -110,4 +103,62 @@ public function resetSpreed(): DataResponse { return new DataResponse(); } + + public function ageChat(string $token, int $hours): DataResponse { + $query = $this->db->getQueryBuilder(); + $query->select('id') + ->from('talk_rooms') + ->where($query->expr()->eq('token', $query->createNamedParameter($token))); + + $result = $query->executeQuery(); + $roomId = (int) $result->fetchOne(); + $result->closeCursor(); + + if (!$roomId) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + + $update = $this->db->getQueryBuilder(); + $update->update('comments') + ->set('creation_timestamp', $update->createParameter('creation_timestamp')) + ->set('expire_date', $update->createParameter('expire_date')) + ->set('meta_data', $update->createParameter('meta_data')) + ->where($update->expr()->eq('id', $update->createParameter('id'))); + + $query = $this->db->getQueryBuilder(); + $query->select('id', 'creation_timestamp', 'expire_date', 'meta_data') + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter('chat'))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($roomId))); + + $result = $query->executeQuery(); + while ($row = $result->fetch()) { + $creationTimestamp = new \DateTime($row['creation_timestamp']); + $creationTimestamp->sub(new \DateInterval('PT' . $hours . 'H')); + + $expireDate = null; + if ($row['expire_date']) { + $expireDate = new \DateTime($row['expire_date']); + $expireDate->sub(new \DateInterval('PT' . $hours . 'H')); + } + + $metaData = 'null'; + if ($row['meta_data'] !== 'null') { + $metaData = json_decode($row['meta_data'], true); + if (isset($metaData['last_edited_time'])) { + $metaData['last_edited_time'] -= $hours * 3600; + } + $metaData = json_encode($metaData); + } + + $update->setParameter('id', $row['id']); + $update->setParameter('creation_timestamp', $creationTimestamp, IQueryBuilder::PARAM_DATE); + $update->setParameter('expire_date', $expireDate, IQueryBuilder::PARAM_DATE); + $update->setParameter('meta_data', $metaData); + $update->executeStatement(); + } + $result->closeCursor(); + + return new DataResponse(); + } }