aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Repair
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Repair')
-rw-r--r--tests/lib/Repair/CleanTagsTest.php195
-rw-r--r--tests/lib/Repair/ClearFrontendCachesTest.php52
-rw-r--r--tests/lib/Repair/ClearGeneratedAvatarCacheTest.php61
-rw-r--r--tests/lib/Repair/NC29/SanitizeAccountPropertiesJobTest.php116
-rw-r--r--tests/lib/Repair/NC29/SanitizeAccountPropertiesTest.php43
-rw-r--r--tests/lib/Repair/OldGroupMembershipSharesTest.php133
-rw-r--r--tests/lib/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php237
-rw-r--r--tests/lib/Repair/Owncloud/CleanPreviewsTest.php110
-rw-r--r--tests/lib/Repair/Owncloud/UpdateLanguageCodesTest.php156
-rw-r--r--tests/lib/Repair/RepairCollationTest.php83
-rw-r--r--tests/lib/Repair/RepairDavSharesTest.php178
-rw-r--r--tests/lib/Repair/RepairInvalidSharesTest.php191
-rw-r--r--tests/lib/Repair/RepairMimeTypesTest.php293
13 files changed, 1848 insertions, 0 deletions
diff --git a/tests/lib/Repair/CleanTagsTest.php b/tests/lib/Repair/CleanTagsTest.php
new file mode 100644
index 00000000000..04afabb87d9
--- /dev/null
+++ b/tests/lib/Repair/CleanTagsTest.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Repair\CleanTags;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\IUserManager;
+use OCP\Migration\IOutput;
+use OCP\Server;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * Tests for the cleaning the tags tables
+ *
+ * @group DB
+ *
+ * @see \OC\Repair\CleanTags
+ */
+class CleanTagsTest extends \Test\TestCase {
+
+ private ?int $createdFile = null;
+ private CleanTags $repair;
+ private IDBConnection $connection;
+
+ private IUserManager&MockObject $userManager;
+ private IOutput&MockObject $outputMock;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->outputMock = $this->getMockBuilder(IOutput::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->userManager = $this->getMockBuilder(IUserManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->connection = Server::get(IDBConnection::class);
+ $this->repair = new CleanTags($this->connection, $this->userManager);
+ $this->cleanUpTables();
+ }
+
+ protected function tearDown(): void {
+ $this->cleanUpTables();
+
+ parent::tearDown();
+ }
+
+ protected function cleanUpTables() {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('vcategory')
+ ->executeStatement();
+
+ $qb->delete('vcategory_to_object')
+ ->executeStatement();
+
+ $qb->delete('filecache')
+ ->runAcrossAllShards()
+ ->executeStatement();
+ }
+
+ public function testRun(): void {
+ $cat1 = $this->addTagCategory('TestRepairCleanTags', 'files'); // Retained
+ $cat2 = $this->addTagCategory('TestRepairCleanTags2', 'files'); // Deleted: Category will be empty
+ $this->addTagCategory('TestRepairCleanTags3', 'files'); // Deleted: Category is empty
+ $cat3 = $this->addTagCategory('TestRepairCleanTags', 'contacts'); // Retained
+
+ $this->addTagEntry($this->getFileID(), $cat2, 'files'); // Retained
+ $this->addTagEntry($this->getFileID() + 1, $cat1, 'files'); // Deleted: File is NULL
+ $this->addTagEntry(9999999, $cat3, 'contacts'); // Retained
+ $this->addTagEntry($this->getFileID(), $cat3 + 1, 'files'); // Deleted: Category is NULL
+
+ $this->assertEntryCount('vcategory_to_object', 4, 'Assert tag entries count before repair step');
+ $this->assertEntryCount('vcategory', 4, 'Assert tag categories count before repair step');
+
+ self::invokePrivate($this->repair, 'deleteOrphanFileEntries', [$this->outputMock]);
+ $this->assertEntryCount('vcategory_to_object', 3, 'Assert tag entries count after cleaning file entries');
+ $this->assertEntryCount('vcategory', 4, 'Assert tag categories count after cleaning file entries');
+
+ self::invokePrivate($this->repair, 'deleteOrphanTagEntries', [$this->outputMock]);
+ $this->assertEntryCount('vcategory_to_object', 2, 'Assert tag entries count after cleaning tag entries');
+ $this->assertEntryCount('vcategory', 4, 'Assert tag categories count after cleaning tag entries');
+
+ self::invokePrivate($this->repair, 'deleteOrphanCategoryEntries', [$this->outputMock]);
+ $this->assertEntryCount('vcategory_to_object', 2, 'Assert tag entries count after cleaning category entries');
+ $this->assertEntryCount('vcategory', 2, 'Assert tag categories count after cleaning category entries');
+
+
+ $this->addTagCategory('TestRepairCleanTags', 'contacts', 'userExists'); // Retained
+ $this->assertEntryCount('vcategory', 3, 'Assert tag categories count before cleaning categories by users');
+
+ $this->userManager->expects($this->exactly(2))
+ ->method('userExists')
+ ->willReturnMap([
+ ['userExists', true],
+ ['TestRepairCleanTags', false],
+ ]);
+
+ self::invokePrivate($this->repair, 'deleteOrphanTags', [$this->outputMock]);
+ $this->assertEntryCount('vcategory', 1, 'Assert tag categories count after cleaning categories by users');
+ }
+
+ /**
+ * @param string $tableName
+ * @param int $expected
+ * @param string $message
+ */
+ protected function assertEntryCount($tableName, $expected, $message = '') {
+ $qb = $this->connection->getQueryBuilder();
+ $result = $qb->select($qb->func()->count('*'))
+ ->from($tableName)
+ ->executeQuery();
+
+ $this->assertEquals($expected, $result->fetchOne(), $message);
+ }
+
+ /**
+ * Adds a new tag category to the database
+ *
+ * @param string $category
+ * @param string $type
+ * @param string $user
+ * @return int
+ */
+ protected function addTagCategory($category, $type, $user = 'TestRepairCleanTags') {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('vcategory')
+ ->values([
+ 'uid' => $qb->createNamedParameter($user),
+ 'category' => $qb->createNamedParameter($category),
+ 'type' => $qb->createNamedParameter($type),
+ ])
+ ->executeStatement();
+
+ return $qb->getLastInsertId();
+ }
+
+ /**
+ * Adds a new tag entry to the database
+ * @param int $objectId
+ * @param int $category
+ * @param string $type
+ */
+ protected function addTagEntry($objectId, $category, $type) {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('vcategory_to_object')
+ ->values([
+ 'objid' => $qb->createNamedParameter($objectId, IQueryBuilder::PARAM_INT),
+ 'categoryid' => $qb->createNamedParameter($category, IQueryBuilder::PARAM_INT),
+ 'type' => $qb->createNamedParameter($type),
+ ])
+ ->executeStatement();
+ }
+
+ /**
+ * Gets the last fileid from the file cache
+ * @return int
+ */
+ protected function getFileID() {
+ if ($this->createdFile !== null) {
+ return $this->createdFile;
+ }
+
+ $qb = $this->connection->getQueryBuilder();
+
+ // We create a new file entry and delete it after the test again
+ $fileName = $this->getUniqueID('TestRepairCleanTags', 12);
+ $qb->insert('filecache')
+ ->values([
+ 'storage' => $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT),
+ 'path' => $qb->createNamedParameter($fileName),
+ 'path_hash' => $qb->createNamedParameter(md5($fileName)),
+ ])
+ ->executeStatement();
+ $fileName = $this->getUniqueID('TestRepairCleanTags', 12);
+ $qb->insert('filecache')
+ ->values([
+ 'storage' => $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT),
+ 'path' => $qb->createNamedParameter($fileName),
+ 'path_hash' => $qb->createNamedParameter(md5($fileName)),
+ ])
+ ->executeStatement();
+
+ $this->createdFile = $qb->getLastInsertId();
+ return $this->createdFile;
+ }
+}
diff --git a/tests/lib/Repair/ClearFrontendCachesTest.php b/tests/lib/Repair/ClearFrontendCachesTest.php
new file mode 100644
index 00000000000..3e5927565fe
--- /dev/null
+++ b/tests/lib/Repair/ClearFrontendCachesTest.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Repair\ClearFrontendCaches;
+use OC\Template\JSCombiner;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\Migration\IOutput;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class ClearFrontendCachesTest extends \Test\TestCase {
+
+ private ICacheFactory&MockObject $cacheFactory;
+ private JSCombiner&MockObject $jsCombiner;
+ private IOutput&MockObject $outputMock;
+
+ protected ClearFrontendCaches $repair;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->outputMock = $this->createMock(IOutput::class);
+
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->jsCombiner = $this->createMock(JSCombiner::class);
+
+ $this->repair = new ClearFrontendCaches($this->cacheFactory, $this->jsCombiner);
+ }
+
+
+ public function testRun(): void {
+ $imagePathCache = $this->createMock(ICache::class);
+ $imagePathCache->expects($this->once())
+ ->method('clear')
+ ->with('');
+ $this->jsCombiner->expects($this->once())
+ ->method('resetCache');
+ $this->cacheFactory->expects($this->once())
+ ->method('createDistributed')
+ ->with('imagePath')
+ ->willReturn($imagePathCache);
+
+ $this->repair->run($this->outputMock);
+ $this->assertTrue(true);
+ }
+}
diff --git a/tests/lib/Repair/ClearGeneratedAvatarCacheTest.php b/tests/lib/Repair/ClearGeneratedAvatarCacheTest.php
new file mode 100644
index 00000000000..43203d489e6
--- /dev/null
+++ b/tests/lib/Repair/ClearGeneratedAvatarCacheTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Avatar\AvatarManager;
+use OC\Repair\ClearGeneratedAvatarCache;
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class ClearGeneratedAvatarCacheTest extends \Test\TestCase {
+
+ private AvatarManager&MockObject $avatarManager;
+ private IConfig&MockObject $config;
+ private IJobList&MockObject $jobList;
+
+ protected ClearGeneratedAvatarCache $repair;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->avatarManager = $this->createMock(AvatarManager::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->jobList = $this->createMock(IJobList::class);
+
+ $this->repair = new ClearGeneratedAvatarCache($this->config, $this->avatarManager, $this->jobList);
+ }
+
+ public static function shouldRunDataProvider(): array {
+ return [
+ ['11.0.0.0', true],
+ ['15.0.0.3', true],
+ ['13.0.5.2', true],
+ ['12.0.0.0', true],
+ ['26.0.0.1', true],
+ ['15.0.0.2', true],
+ ['13.0.0.0', true],
+ ['27.0.0.5', false]
+ ];
+ }
+
+ /**
+ *
+ * @param string $from
+ * @param boolean $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('shouldRunDataProvider')]
+ public function testShouldRun($from, $expected): void {
+ $this->config->expects($this->any())
+ ->method('getSystemValueString')
+ ->with('version', '0.0.0.0')
+ ->willReturn($from);
+
+ $this->assertEquals($expected, $this->invokePrivate($this->repair, 'shouldRun'));
+ }
+}
diff --git a/tests/lib/Repair/NC29/SanitizeAccountPropertiesJobTest.php b/tests/lib/Repair/NC29/SanitizeAccountPropertiesJobTest.php
new file mode 100644
index 00000000000..2a4f6e9ecf1
--- /dev/null
+++ b/tests/lib/Repair/NC29/SanitizeAccountPropertiesJobTest.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Repair\NC29;
+
+use InvalidArgumentException;
+use OCP\Accounts\IAccount;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\IAccountProperty;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IUser;
+use OCP\IUserManager;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class SanitizeAccountPropertiesJobTest extends TestCase {
+
+ private IUserManager&MockObject $userManager;
+ private IAccountManager&MockObject $accountManager;
+ private LoggerInterface&MockObject $logger;
+
+ private SanitizeAccountPropertiesJob $job;
+
+ protected function setUp(): void {
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->accountManager = $this->createMock(IAccountManager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->job = new SanitizeAccountPropertiesJob(
+ $this->createMock(ITimeFactory::class),
+ $this->userManager,
+ $this->accountManager,
+ $this->logger,
+ );
+ }
+
+ public function testParallel() {
+ self::assertFalse($this->job->getAllowParallelRuns());
+ }
+
+ public function testRun(): void {
+ $users = [
+ $this->createMock(IUser::class),
+ $this->createMock(IUser::class),
+ $this->createMock(IUser::class),
+ ];
+ $this->userManager
+ ->expects(self::once())
+ ->method('callForSeenUsers')
+ ->willReturnCallback(fn ($fn) => array_map($fn, $users));
+
+ $property = $this->createMock(IAccountProperty::class);
+ $property->expects(self::once())->method('getName')->willReturn(IAccountManager::PROPERTY_PHONE);
+ $property->expects(self::once())->method('getScope')->willReturn(IAccountManager::SCOPE_LOCAL);
+
+ $account1 = $this->createMock(IAccount::class);
+ $account1->expects(self::once())
+ ->method('getProperty')
+ ->with(IAccountManager::PROPERTY_PHONE)
+ ->willReturn($property);
+ $account1->expects(self::once())
+ ->method('setProperty')
+ ->with(IAccountManager::PROPERTY_PHONE, '', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
+ $account1->expects(self::once())
+ ->method('jsonSerialize')
+ ->willReturn([
+ IAccountManager::PROPERTY_DISPLAYNAME => [],
+ IAccountManager::PROPERTY_PHONE => [],
+ ]);
+
+ $account2 = $this->createMock(IAccount::class);
+ $account2->expects(self::never())
+ ->method('getProperty');
+ $account2->expects(self::once())
+ ->method('jsonSerialize')
+ ->willReturn([
+ IAccountManager::PROPERTY_DISPLAYNAME => [],
+ IAccountManager::PROPERTY_PHONE => [],
+ ]);
+
+ $account3 = $this->createMock(IAccount::class);
+ $account3->expects(self::never())
+ ->method('getProperty');
+ $account3->expects(self::once())
+ ->method('jsonSerialize')
+ ->willReturn([
+ IAccountManager::PROPERTY_DISPLAYNAME => [],
+ ]);
+
+ $this->accountManager
+ ->expects(self::exactly(3))
+ ->method('getAccount')
+ ->willReturnMap([
+ [$users[0], $account1],
+ [$users[1], $account2],
+ [$users[2], $account3],
+ ]);
+ $valid = false;
+ $this->accountManager->expects(self::exactly(3))
+ ->method('updateAccount')
+ ->willReturnCallback(function (IAccount $account) use (&$account1, &$valid): void {
+ if (!$valid && $account === $account1) {
+ $valid = true;
+ throw new InvalidArgumentException(IAccountManager::PROPERTY_PHONE);
+ }
+ });
+
+ self::invokePrivate($this->job, 'run', [null]);
+ }
+}
diff --git a/tests/lib/Repair/NC29/SanitizeAccountPropertiesTest.php b/tests/lib/Repair/NC29/SanitizeAccountPropertiesTest.php
new file mode 100644
index 00000000000..d0d33eb2817
--- /dev/null
+++ b/tests/lib/Repair/NC29/SanitizeAccountPropertiesTest.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Repair\NC29;
+
+use OCP\BackgroundJob\IJobList;
+use OCP\Migration\IOutput;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class SanitizeAccountPropertiesTest extends TestCase {
+
+ private IJobList&MockObject $jobList;
+ private SanitizeAccountProperties $repairStep;
+
+ protected function setUp(): void {
+ $this->jobList = $this->createMock(IJobList::class);
+
+ $this->repairStep = new SanitizeAccountProperties($this->jobList);
+ }
+
+ public function testGetName(): void {
+ self::assertStringContainsString('Validate account properties', $this->repairStep->getName());
+ }
+
+ public function testRun(): void {
+ $this->jobList->expects(self::once())
+ ->method('add')
+ ->with(SanitizeAccountPropertiesJob::class, null);
+
+ $output = $this->createMock(IOutput::class);
+ $output->expects(self::once())
+ ->method('info')
+ ->with(self::matchesRegularExpression('/queued background/i'));
+
+ $this->repairStep->run($output);
+ }
+}
diff --git a/tests/lib/Repair/OldGroupMembershipSharesTest.php b/tests/lib/Repair/OldGroupMembershipSharesTest.php
new file mode 100644
index 00000000000..099290b18ed
--- /dev/null
+++ b/tests/lib/Repair/OldGroupMembershipSharesTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Repair\OldGroupMembershipShares;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use OCP\Migration\IOutput;
+use OCP\Server;
+use OCP\Share\IShare;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * Class OldGroupMembershipSharesTest
+ *
+ * @group DB
+ *
+ * @package Test\Repair
+ */
+class OldGroupMembershipSharesTest extends \Test\TestCase {
+
+ private IDBConnection $connection;
+ private IGroupManager&MockObject $groupManager;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->groupManager = $this->getMockBuilder(IGroupManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->connection = Server::get(IDBConnection::class);
+
+ $this->deleteAllShares();
+ }
+
+ protected function tearDown(): void {
+ $this->deleteAllShares();
+
+ parent::tearDown();
+ }
+
+ protected function deleteAllShares() {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('share')->executeStatement();
+ }
+
+ public function testRun(): void {
+ $repair = new OldGroupMembershipShares(
+ $this->connection,
+ $this->groupManager
+ );
+
+ $this->groupManager->expects($this->exactly(2))
+ ->method('isInGroup')
+ ->willReturnMap([
+ ['member', 'group', true],
+ ['not-a-member', 'group', false],
+ ]);
+
+ $parent = $this->createShare(IShare::TYPE_GROUP, 'group', null);
+ $group2 = $this->createShare(IShare::TYPE_GROUP, 'group2', $parent);
+ $user1 = $this->createShare(IShare::TYPE_USER, 'user1', $parent);
+
+ // \OC\Share\Constant::$shareTypeGroupUserUnique === 2
+ $member = $this->createShare(2, 'member', $parent);
+ $notAMember = $this->createShare(2, 'not-a-member', $parent);
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->executeQuery();
+ $rows = $result->fetchAll();
+ $this->assertEquals([['id' => $parent], ['id' => $group2], ['id' => $user1], ['id' => $member], ['id' => $notAMember]], $rows);
+ $result->closeCursor();
+
+ /** @var IOutput | \PHPUnit\Framework\MockObject\MockObject $outputMock */
+ $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $repair->run($outputMock);
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->executeQuery();
+ $rows = $result->fetchAll();
+ $this->assertEquals([['id' => $parent], ['id' => $group2], ['id' => $user1], ['id' => $member]], $rows);
+ $result->closeCursor();
+ }
+
+ /**
+ * @param string $shareType
+ * @param string $shareWith
+ * @param null|int $parent
+ * @return int
+ */
+ protected function createShare($shareType, $shareWith, $parent) {
+ $qb = $this->connection->getQueryBuilder();
+ $shareValues = [
+ 'share_type' => $qb->expr()->literal($shareType),
+ 'share_with' => $qb->expr()->literal($shareWith),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal(1),
+ 'stime' => $qb->expr()->literal(time()),
+ 'expiration' => $qb->expr()->literal('2015-09-25 00:00:00'),
+ ];
+
+ if ($parent) {
+ $shareValues['parent'] = $qb->expr()->literal($parent);
+ }
+
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values($shareValues)
+ ->executeStatement();
+
+ return $qb->getLastInsertId();
+ }
+}
diff --git a/tests/lib/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php b/tests/lib/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php
new file mode 100644
index 00000000000..fc88ee5d226
--- /dev/null
+++ b/tests/lib/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace Test\Repair\Owncloud;
+
+use OC\Repair\Owncloud\CleanPreviewsBackgroundJob;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IUserManager;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class CleanPreviewsBackgroundJobTest extends TestCase {
+
+ private IRootFolder&MockObject $rootFolder;
+ private LoggerInterface&MockObject $logger;
+ private IJobList&MockObject $jobList;
+ private ITimeFactory&MockObject $timeFactory;
+ private IUserManager&MockObject $userManager;
+ private CleanPreviewsBackgroundJob $job;
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->rootFolder = $this->createMock(IRootFolder::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->jobList = $this->createMock(IJobList::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+
+ $this->userManager->expects($this->any())->method('userExists')->willReturn(true);
+
+ $this->job = new CleanPreviewsBackgroundJob(
+ $this->rootFolder,
+ $this->logger,
+ $this->jobList,
+ $this->timeFactory,
+ $this->userManager
+ );
+ }
+
+ public function testCleanupPreviewsUnfinished(): void {
+ $userFolder = $this->createMock(Folder::class);
+ $userRoot = $this->createMock(Folder::class);
+ $thumbnailFolder = $this->createMock(Folder::class);
+
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->equalTo('myuid'))
+ ->willReturn($userFolder);
+
+ $userFolder->method('getParent')->willReturn($userRoot);
+
+ $userRoot->method('get')
+ ->with($this->equalTo('thumbnails'))
+ ->willReturn($thumbnailFolder);
+
+ $previewFolder1 = $this->createMock(Folder::class);
+
+ $previewFolder1->expects($this->once())
+ ->method('delete');
+
+ $thumbnailFolder->method('getDirectoryListing')
+ ->willReturn([$previewFolder1]);
+ $thumbnailFolder->expects($this->never())
+ ->method('delete');
+
+ $this->timeFactory->method('getTime')->willReturnOnConsecutiveCalls(100, 200);
+
+ $this->jobList->expects($this->once())
+ ->method('add')
+ ->with(
+ $this->equalTo(CleanPreviewsBackgroundJob::class),
+ $this->equalTo(['uid' => 'myuid'])
+ );
+
+ $loggerCalls = [];
+ $this->logger->expects($this->exactly(2))
+ ->method('info')
+ ->willReturnCallback(function () use (&$loggerCalls): void {
+ $loggerCalls[] = func_get_args();
+ });
+
+ $this->job->run(['uid' => 'myuid']);
+ self::assertEquals([
+ ['Started preview cleanup for myuid', []],
+ ['New preview cleanup scheduled for myuid', []],
+ ], $loggerCalls);
+ }
+
+ public function testCleanupPreviewsFinished(): void {
+ $userFolder = $this->createMock(Folder::class);
+ $userRoot = $this->createMock(Folder::class);
+ $thumbnailFolder = $this->createMock(Folder::class);
+
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->equalTo('myuid'))
+ ->willReturn($userFolder);
+
+ $userFolder->method('getParent')->willReturn($userRoot);
+
+ $userRoot->method('get')
+ ->with($this->equalTo('thumbnails'))
+ ->willReturn($thumbnailFolder);
+
+ $previewFolder1 = $this->createMock(Folder::class);
+
+ $previewFolder1->expects($this->once())
+ ->method('delete');
+
+ $thumbnailFolder->method('getDirectoryListing')
+ ->willReturn([$previewFolder1]);
+
+ $this->timeFactory->method('getTime')->willReturnOnConsecutiveCalls(100, 101);
+
+ $this->jobList->expects($this->never())
+ ->method('add');
+
+ $loggerCalls = [];
+ $this->logger->expects($this->exactly(2))
+ ->method('info')
+ ->willReturnCallback(function () use (&$loggerCalls): void {
+ $loggerCalls[] = func_get_args();
+ });
+
+ $thumbnailFolder->expects($this->once())
+ ->method('delete');
+
+ $this->job->run(['uid' => 'myuid']);
+ self::assertEquals([
+ ['Started preview cleanup for myuid', []],
+ ['Preview cleanup done for myuid', []],
+ ], $loggerCalls);
+ }
+
+
+ public function testNoUserFolder(): void {
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->equalTo('myuid'))
+ ->willThrowException(new NotFoundException());
+
+ $loggerCalls = [];
+ $this->logger->expects($this->exactly(2))
+ ->method('info')
+ ->willReturnCallback(function () use (&$loggerCalls): void {
+ $loggerCalls[] = func_get_args();
+ });
+
+ $this->job->run(['uid' => 'myuid']);
+ self::assertEquals([
+ ['Started preview cleanup for myuid', []],
+ ['Preview cleanup done for myuid', []],
+ ], $loggerCalls);
+ }
+
+ public function testNoThumbnailFolder(): void {
+ $userFolder = $this->createMock(Folder::class);
+ $userRoot = $this->createMock(Folder::class);
+
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->equalTo('myuid'))
+ ->willReturn($userFolder);
+
+ $userFolder->method('getParent')->willReturn($userRoot);
+
+ $userRoot->method('get')
+ ->with($this->equalTo('thumbnails'))
+ ->willThrowException(new NotFoundException());
+
+ $loggerCalls = [];
+ $this->logger->expects($this->exactly(2))
+ ->method('info')
+ ->willReturnCallback(function () use (&$loggerCalls): void {
+ $loggerCalls[] = func_get_args();
+ });
+
+ $this->job->run(['uid' => 'myuid']);
+ self::assertEquals([
+ ['Started preview cleanup for myuid', []],
+ ['Preview cleanup done for myuid', []],
+ ], $loggerCalls);
+ }
+
+ public function testNotPermittedToDelete(): void {
+ $userFolder = $this->createMock(Folder::class);
+ $userRoot = $this->createMock(Folder::class);
+ $thumbnailFolder = $this->createMock(Folder::class);
+
+ $this->rootFolder->method('getUserFolder')
+ ->with($this->equalTo('myuid'))
+ ->willReturn($userFolder);
+
+ $userFolder->method('getParent')->willReturn($userRoot);
+
+ $userRoot->method('get')
+ ->with($this->equalTo('thumbnails'))
+ ->willReturn($thumbnailFolder);
+
+ $previewFolder1 = $this->createMock(Folder::class);
+
+ $previewFolder1->expects($this->once())
+ ->method('delete')
+ ->willThrowException(new NotPermittedException());
+
+ $thumbnailFolder->method('getDirectoryListing')
+ ->willReturn([$previewFolder1]);
+
+ $this->timeFactory->method('getTime')->willReturnOnConsecutiveCalls(100, 101);
+
+ $this->jobList->expects($this->never())
+ ->method('add');
+
+ $thumbnailFolder->expects($this->once())
+ ->method('delete')
+ ->willThrowException(new NotPermittedException());
+
+ $loggerCalls = [];
+ $this->logger->expects($this->exactly(2))
+ ->method('info')
+ ->willReturnCallback(function () use (&$loggerCalls): void {
+ $loggerCalls[] = func_get_args();
+ });
+
+ $this->job->run(['uid' => 'myuid']);
+ self::assertEquals([
+ ['Started preview cleanup for myuid', []],
+ ['Preview cleanup done for myuid', []],
+ ], $loggerCalls);
+ }
+}
diff --git a/tests/lib/Repair/Owncloud/CleanPreviewsTest.php b/tests/lib/Repair/Owncloud/CleanPreviewsTest.php
new file mode 100644
index 00000000000..e5a4441a4fa
--- /dev/null
+++ b/tests/lib/Repair/Owncloud/CleanPreviewsTest.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace Test\Repair\Owncloud;
+
+use OC\Repair\Owncloud\CleanPreviews;
+use OC\Repair\Owncloud\CleanPreviewsBackgroundJob;
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Migration\IOutput;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class CleanPreviewsTest extends TestCase {
+
+ private IJobList&MockObject $jobList;
+ private IUserManager&MockObject $userManager;
+ private IConfig&MockObject $config;
+
+ /** @var CleanPreviews */
+ private $repair;
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->jobList = $this->createMock(IJobList::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->repair = new CleanPreviews(
+ $this->jobList,
+ $this->userManager,
+ $this->config
+ );
+ }
+
+ public function testGetName(): void {
+ $this->assertSame('Add preview cleanup background jobs', $this->repair->getName());
+ }
+
+ public function testRun(): void {
+ $user1 = $this->createMock(IUser::class);
+ $user1->method('getUID')
+ ->willReturn('user1');
+ $user2 = $this->createMock(IUser::class);
+ $user2->method('getUID')
+ ->willReturn('user2');
+
+ $this->userManager->expects($this->once())
+ ->method('callForSeenUsers')
+ ->willReturnCallback(function (\Closure $function) use (&$user1, $user2): void {
+ $function($user1);
+ $function($user2);
+ });
+
+ $jobListCalls = [];
+ $this->jobList->expects($this->exactly(2))
+ ->method('add')
+ ->willReturnCallback(function () use (&$jobListCalls): void {
+ $jobListCalls[] = func_get_args();
+ });
+
+ $this->config->expects($this->once())
+ ->method('getAppValue')
+ ->with(
+ $this->equalTo('core'),
+ $this->equalTo('previewsCleanedUp'),
+ $this->equalTo(false)
+ )->willReturn(false);
+ $this->config->expects($this->once())
+ ->method('setAppValue')
+ ->with(
+ $this->equalTo('core'),
+ $this->equalTo('previewsCleanedUp'),
+ $this->equalTo(1)
+ );
+
+ $this->repair->run($this->createMock(IOutput::class));
+ $this->assertEqualsCanonicalizing([
+ [CleanPreviewsBackgroundJob::class, ['uid' => 'user1']],
+ [CleanPreviewsBackgroundJob::class, ['uid' => 'user2']],
+ ], $jobListCalls);
+ }
+
+
+ public function testRunAlreadyDone(): void {
+ $this->userManager->expects($this->never())
+ ->method($this->anything());
+
+ $this->jobList->expects($this->never())
+ ->method($this->anything());
+
+ $this->config->expects($this->once())
+ ->method('getAppValue')
+ ->with(
+ $this->equalTo('core'),
+ $this->equalTo('previewsCleanedUp'),
+ $this->equalTo(false)
+ )->willReturn('1');
+ $this->config->expects($this->never())
+ ->method('setAppValue');
+
+ $this->repair->run($this->createMock(IOutput::class));
+ }
+}
diff --git a/tests/lib/Repair/Owncloud/UpdateLanguageCodesTest.php b/tests/lib/Repair/Owncloud/UpdateLanguageCodesTest.php
new file mode 100644
index 00000000000..a3eb163b0d6
--- /dev/null
+++ b/tests/lib/Repair/Owncloud/UpdateLanguageCodesTest.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair\Owncloud;
+
+use OC\Repair\Owncloud\UpdateLanguageCodes;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Server;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+/**
+ * Class UpdateLanguageCodesTest
+ *
+ * @group DB
+ *
+ * @package Test\Repair
+ */
+class UpdateLanguageCodesTest extends TestCase {
+
+ protected IDBConnection $connection;
+ private IConfig&MockObject $config;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->connection = Server::get(IDBConnection::class);
+ $this->config = $this->createMock(IConfig::class);
+ }
+
+ public function testRun(): void {
+ $users = [
+ ['userid' => 'user1', 'configvalue' => 'fi_FI'],
+ ['userid' => 'user2', 'configvalue' => 'de'],
+ ['userid' => 'user3', 'configvalue' => 'fi'],
+ ['userid' => 'user4', 'configvalue' => 'ja'],
+ ['userid' => 'user5', 'configvalue' => 'bg_BG'],
+ ['userid' => 'user6', 'configvalue' => 'ja'],
+ ['userid' => 'user7', 'configvalue' => 'th_TH'],
+ ['userid' => 'user8', 'configvalue' => 'th_TH'],
+ ];
+
+ // insert test data
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('preferences')
+ ->values([
+ 'userid' => $qb->createParameter('userid'),
+ 'appid' => $qb->createParameter('appid'),
+ 'configkey' => $qb->createParameter('configkey'),
+ 'configvalue' => $qb->createParameter('configvalue'),
+ ]);
+ foreach ($users as $user) {
+ $qb->setParameters([
+ 'userid' => $user['userid'],
+ 'appid' => 'core',
+ 'configkey' => 'lang',
+ 'configvalue' => $user['configvalue'],
+ ])->executeStatement();
+ }
+
+ // check if test data is written to DB
+ $qb = $this->connection->getQueryBuilder();
+ $result = $qb->select(['userid', 'configvalue'])
+ ->from('preferences')
+ ->where($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+ ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+ ->orderBy('userid')
+ ->executeQuery();
+
+ $rows = $result->fetchAll();
+ $result->closeCursor();
+
+ $this->assertSame($users, $rows, 'Asserts that the entries are the ones from the test data set');
+
+ $expectedOutput = [
+ ['Changed 1 setting(s) from "bg_BG" to "bg" in preferences table.'],
+ ['Changed 0 setting(s) from "cs_CZ" to "cs" in preferences table.'],
+ ['Changed 1 setting(s) from "fi_FI" to "fi" in preferences table.'],
+ ['Changed 0 setting(s) from "hu_HU" to "hu" in preferences table.'],
+ ['Changed 0 setting(s) from "nb_NO" to "nb" in preferences table.'],
+ ['Changed 0 setting(s) from "sk_SK" to "sk" in preferences table.'],
+ ['Changed 2 setting(s) from "th_TH" to "th" in preferences table.'],
+ ];
+ $outputMessages = [];
+ /** @var IOutput&MockObject $outputMock */
+ $outputMock = $this->createMock(IOutput::class);
+ $outputMock->expects($this->exactly(7))
+ ->method('info')
+ ->willReturnCallback(function () use (&$outputMessages): void {
+ $outputMessages[] = func_get_args();
+ });
+
+ $this->config->expects($this->once())
+ ->method('getSystemValueString')
+ ->with('version', '0.0.0')
+ ->willReturn('12.0.0.13');
+
+ // run repair step
+ $repair = new UpdateLanguageCodes($this->connection, $this->config);
+ $repair->run($outputMock);
+
+ // check if test data is correctly modified in DB
+ $qb = $this->connection->getQueryBuilder();
+ $result = $qb->select(['userid', 'configvalue'])
+ ->from('preferences')
+ ->where($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+ ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+ ->orderBy('userid')
+ ->executeQuery();
+
+ $rows = $result->fetchAll();
+ $result->closeCursor();
+
+ // value has changed for one user
+ $users[0]['configvalue'] = 'fi';
+ $users[4]['configvalue'] = 'bg';
+ $users[6]['configvalue'] = 'th';
+ $users[7]['configvalue'] = 'th';
+ $this->assertSame($users, $rows, 'Asserts that the entries are updated correctly.');
+
+ // remove test data
+ foreach ($users as $user) {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('preferences')
+ ->where($qb->expr()->eq('userid', $qb->createNamedParameter($user['userid'])))
+ ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter('core')))
+ ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang')))
+ ->andWhere($qb->expr()->eq('configvalue', $qb->createNamedParameter($user['configvalue']), IQueryBuilder::PARAM_STR))
+ ->executeStatement();
+ }
+ self::assertEquals($expectedOutput, $outputMessages);
+ }
+
+ public function testSecondRun(): void {
+ /** @var IOutput&MockObject $outputMock */
+ $outputMock = $this->createMock(IOutput::class);
+ $outputMock->expects($this->never())
+ ->method('info');
+
+ $this->config->expects($this->once())
+ ->method('getSystemValueString')
+ ->with('version', '0.0.0')
+ ->willReturn('12.0.0.14');
+
+ // run repair step
+ $repair = new UpdateLanguageCodes($this->connection, $this->config);
+ $repair->run($outputMock);
+ }
+}
diff --git a/tests/lib/Repair/RepairCollationTest.php b/tests/lib/Repair/RepairCollationTest.php
new file mode 100644
index 00000000000..3c51325562d
--- /dev/null
+++ b/tests/lib/Repair/RepairCollationTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\DB\ConnectionAdapter;
+use OC\Repair\Collation;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Server;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class TestCollationRepair extends Collation {
+ /**
+ * @param IDBConnection $connection
+ * @return string[]
+ */
+ public function getAllNonUTF8BinTables(IDBConnection $connection) {
+ return parent::getAllNonUTF8BinTables($connection);
+ }
+}
+
+/**
+ * Tests for the converting of MySQL tables to InnoDB engine
+ *
+ * @group DB
+ *
+ * @see \OC\Repair\RepairMimeTypes
+ */
+class RepairCollationTest extends TestCase {
+
+ private TestCollationRepair $repair;
+ private ConnectionAdapter $connection;
+ private string $tableName;
+ private IConfig $config;
+
+ private LoggerInterface&MockObject $logger;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->connection = Server::get(ConnectionAdapter::class);
+ $this->config = Server::get(IConfig::class);
+ if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) {
+ $this->markTestSkipped('Test only relevant on MySql');
+ }
+
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $dbPrefix = $this->config->getSystemValueString('dbtableprefix');
+ $this->tableName = $this->getUniqueID($dbPrefix . '_collation_test');
+ $this->connection->prepare("CREATE TABLE $this->tableName(text VARCHAR(16)) COLLATE utf8_unicode_ci")->execute();
+
+ $this->repair = new TestCollationRepair($this->config, $this->logger, $this->connection, false);
+ }
+
+ protected function tearDown(): void {
+ $this->connection->getInner()->createSchemaManager()->dropTable($this->tableName);
+ parent::tearDown();
+ }
+
+ public function testCollationConvert(): void {
+ $tables = $this->repair->getAllNonUTF8BinTables($this->connection);
+ $this->assertGreaterThanOrEqual(1, count($tables));
+
+ $outputMock = $this->getMockBuilder(IOutput::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->repair->run($outputMock);
+
+ $tables = $this->repair->getAllNonUTF8BinTables($this->connection);
+ $this->assertCount(0, $tables);
+ }
+}
diff --git a/tests/lib/Repair/RepairDavSharesTest.php b/tests/lib/Repair/RepairDavSharesTest.php
new file mode 100644
index 00000000000..73e71d75055
--- /dev/null
+++ b/tests/lib/Repair/RepairDavSharesTest.php
@@ -0,0 +1,178 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Repair\RepairDavShares;
+use OCP\DB\IResult;
+use OCP\DB\QueryBuilder\IExpressionBuilder;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use OCP\Migration\IOutput;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+use function in_array;
+
+class RepairDavSharesTest extends TestCase {
+
+ private IOutput&MockObject $output;
+ private IConfig&MockObject $config;
+ private IDBConnection&MockObject $dbc;
+ private LoggerInterface&MockObject $logger;
+ private IGroupManager&MockObject $groupManager;
+ private RepairDavShares $repair;
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->output = $this->createMock(IOutput::class);
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->dbc = $this->createMock(IDBConnection::class);
+ $this->groupManager = $this->createMock(IGroupManager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->repair = new RepairDavShares(
+ $this->config,
+ $this->dbc,
+ $this->groupManager,
+ $this->logger
+ );
+ }
+
+ public function testRun(): void {
+ $this->config->expects($this->any())
+ ->method('getSystemValueString')
+ ->with('version', '0.0.0')
+ ->willReturn('20.0.2');
+
+ $this->output->expects($this->atLeastOnce())
+ ->method('info');
+
+ $existingGroups = [
+ 'Innocent',
+ 'Wants Repair',
+ 'Well förmed',
+ 'family+friends',
+ 'family friends',
+ ];
+
+ $shareResultData = [
+ [
+ // No update, nothing to escape
+ 'id' => 0,
+ 'principaluri' => 'principals/groups/Innocent',
+ ],
+ [
+ // Update
+ 'id' => 1,
+ 'principaluri' => 'principals/groups/Wants Repair',
+ ],
+ [
+ // No update, already proper
+ 'id' => 2,
+ 'principaluri' => 'principals/groups/Well+f%C3%B6rmed',
+ ],
+ [
+ // No update, unknown group
+ 'id' => 3,
+ 'principaluri' => 'principals/groups/Not known',
+ ],
+ [
+ // No update, unknown group
+ 'id' => 4,
+ 'principaluri' => 'principals/groups/Also%2F%2FNot%23Known',
+ ],
+ [
+ // No update, group exists in both forms
+ 'id' => 5,
+ 'principaluri' => 'principals/groups/family+friends',
+ ],
+ [
+ // No update, already proper
+ 'id' => 6,
+ 'principaluri' => 'principals/groups/family%2Bfriends',
+ ],
+ [
+ // Update
+ 'id' => 7,
+ 'principaluri' => 'principals/groups/family friends',
+ ],
+ ];
+
+ $shareResults = $this->createMock(IResult::class);
+ $shareResults->expects($this->any())
+ ->method('fetch')
+ ->willReturnCallback(function () use (&$shareResultData) {
+ return array_pop($shareResultData);
+ });
+
+ $expressionBuilder = $this->createMock(IExpressionBuilder::class);
+
+ $selectMock = $this->createMock(IQueryBuilder::class);
+ $selectMock->expects($this->any())
+ ->method('expr')
+ ->willReturn($expressionBuilder);
+ $selectMock->expects($this->once())
+ ->method('select')
+ ->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('from')
+ ->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('where')
+ ->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('execute')
+ ->willReturn($shareResults);
+
+ $updateCalls = [];
+ $updateMock = $this->createMock(IQueryBuilder::class);
+ $updateMock->expects($this->any())
+ ->method('expr')
+ ->willReturn($expressionBuilder);
+ $updateMock->expects($this->once())
+ ->method('update')
+ ->willReturnSelf();
+ $updateMock->expects($this->any())
+ ->method('set')
+ ->willReturnSelf();
+ $updateMock->expects($this->once())
+ ->method('where')
+ ->willReturnSelf();
+ $updateMock->expects($this->exactly(4))
+ ->method('setParameter')
+ ->willReturnCallback(function () use (&$updateCalls, &$updateMock) {
+ $updateCalls[] = func_get_args();
+ return $updateMock;
+ });
+ $updateMock->expects($this->exactly(2))
+ ->method('execute');
+
+ $this->dbc->expects($this->atLeast(2))
+ ->method('getQueryBuilder')
+ ->willReturnOnConsecutiveCalls($selectMock, $updateMock);
+
+ $this->groupManager->expects($this->any())
+ ->method('groupExists')
+ ->willReturnCallback(function (string $gid) use ($existingGroups) {
+ return in_array($gid, $existingGroups);
+ });
+
+ $this->repair->run($this->output);
+ self::assertEquals([
+ ['updatedPrincipalUri', 'principals/groups/' . urlencode('family friends'), null],
+ ['shareId', 7, null],
+ ['updatedPrincipalUri', 'principals/groups/' . urlencode('Wants Repair'), null],
+ ['shareId', 1, null]
+ ], $updateCalls);
+ }
+}
diff --git a/tests/lib/Repair/RepairInvalidSharesTest.php b/tests/lib/Repair/RepairInvalidSharesTest.php
new file mode 100644
index 00000000000..72103976da5
--- /dev/null
+++ b/tests/lib/Repair/RepairInvalidSharesTest.php
@@ -0,0 +1,191 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Repair\RepairInvalidShares;
+use OCP\Constants;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Server;
+use OCP\Share\IShare;
+use Test\TestCase;
+
+/**
+ * Tests for repairing invalid shares
+ *
+ * @group DB
+ *
+ * @see \OC\Repair\RepairInvalidShares
+ */
+class RepairInvalidSharesTest extends TestCase {
+
+ private RepairInvalidShares $repair;
+ private IDBConnection $connection;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $config = $this->getMockBuilder(IConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $config->expects($this->any())
+ ->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('12.0.0.0');
+
+ $this->connection = Server::get(IDBConnection::class);
+ $this->deleteAllShares();
+
+ $this->repair = new RepairInvalidShares($config, $this->connection);
+ }
+
+ protected function tearDown(): void {
+ $this->deleteAllShares();
+
+ parent::tearDown();
+ }
+
+ protected function deleteAllShares() {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('share')->executeStatement();
+ }
+
+ /**
+ * Test remove shares where the parent share does not exist anymore
+ */
+ public function testSharesNonExistingParent(): void {
+ $qb = $this->connection->getQueryBuilder();
+ $shareValues = [
+ 'share_type' => $qb->expr()->literal(IShare::TYPE_USER),
+ 'share_with' => $qb->expr()->literal('recipientuser1'),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal(1),
+ 'stime' => $qb->expr()->literal(time()),
+ 'expiration' => $qb->expr()->literal('2015-09-25 00:00:00')
+ ];
+
+ // valid share
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values($shareValues)
+ ->executeStatement();
+ $parent = $qb->getLastInsertId();
+
+ // share with existing parent
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values(array_merge($shareValues, [
+ 'parent' => $qb->expr()->literal($parent),
+ ]))->executeStatement();
+ $validChild = $qb->getLastInsertId();
+
+ // share with non-existing parent
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values(array_merge($shareValues, [
+ 'parent' => $qb->expr()->literal($parent + 100),
+ ]))->executeStatement();
+ $invalidChild = $qb->getLastInsertId();
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->executeQuery();
+ $rows = $result->fetchAll();
+ $this->assertEquals([['id' => $parent], ['id' => $validChild], ['id' => $invalidChild]], $rows);
+ $result->closeCursor();
+
+ /** @var IOutput | \PHPUnit\Framework\MockObject\MockObject $outputMock */
+ $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->repair->run($outputMock);
+
+ $query = $this->connection->getQueryBuilder();
+ $result = $query->select('id')
+ ->from('share')
+ ->orderBy('id', 'ASC')
+ ->executeQuery();
+ $rows = $result->fetchAll();
+ $this->assertEquals([['id' => $parent], ['id' => $validChild]], $rows);
+ $result->closeCursor();
+ }
+
+ public static function fileSharePermissionsProvider(): array {
+ return [
+ // unchanged for folder
+ [
+ 'folder',
+ 31,
+ 31,
+ ],
+ // unchanged for read-write + share
+ [
+ 'file',
+ Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_SHARE,
+ Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_SHARE,
+ ],
+ // fixed for all perms
+ [
+ 'file',
+ Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE | Constants::PERMISSION_SHARE,
+ Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_SHARE,
+ ],
+ ];
+ }
+
+ /**
+ * Test adjusting file share permissions
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('fileSharePermissionsProvider')]
+ public function testFileSharePermissions($itemType, $testPerms, $expectedPerms): void {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values([
+ 'share_type' => $qb->expr()->literal(IShare::TYPE_LINK),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal($itemType),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal($testPerms),
+ 'stime' => $qb->expr()->literal(time()),
+ ])
+ ->executeStatement();
+
+ /** @var IOutput | \PHPUnit\Framework\MockObject\MockObject $outputMock */
+ $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->repair->run($outputMock);
+
+ $results = $this->connection->getQueryBuilder()
+ ->select('*')
+ ->from('share')
+ ->orderBy('permissions', 'ASC')
+ ->executeQuery()
+ ->fetchAll();
+
+ $this->assertCount(1, $results);
+
+ $updatedShare = $results[0];
+
+ $this->assertEquals($expectedPerms, $updatedShare['permissions']);
+ }
+}
diff --git a/tests/lib/Repair/RepairMimeTypesTest.php b/tests/lib/Repair/RepairMimeTypesTest.php
new file mode 100644
index 00000000000..0261b56ebe9
--- /dev/null
+++ b/tests/lib/Repair/RepairMimeTypesTest.php
@@ -0,0 +1,293 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Repair;
+
+use OC\Files\Storage\Temporary;
+use OC\Repair\RepairMimeTypes;
+use OCP\Files\IMimeTypeLoader;
+use OCP\IAppConfig;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Server;
+
+/**
+ * Tests for the converting of legacy storages to home storages.
+ *
+ * @group DB
+ *
+ * @see \OC\Repair\RepairMimeTypes
+ */
+class RepairMimeTypesTest extends \Test\TestCase {
+
+ private RepairMimeTypes $repair;
+ private Temporary $storage;
+ private IMimeTypeLoader $mimetypeLoader;
+ private IDBConnection $db;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mimetypeLoader = Server::get(IMimeTypeLoader::class);
+ $this->db = Server::get(IDBConnection::class);
+
+ $config = $this->getMockBuilder(IConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('11.0.0.0');
+
+ $appConfig = $this->getMockBuilder(IAppConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $appConfig->method('getValueString')
+ ->with('files', 'mimetype_version')
+ ->willReturn('11.0.0.0');
+
+ $this->storage = new Temporary([]);
+ $this->storage->getScanner()->scan('');
+
+ $this->repair = new RepairMimeTypes(
+ $config,
+ $appConfig,
+ Server::get(IDBConnection::class),
+ );
+ }
+
+ protected function tearDown(): void {
+ $this->storage->getCache()->clear();
+
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('storages')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($this->storage->getId())));
+ $qb->executeStatement();
+
+ $this->clearMimeTypes();
+
+ parent::tearDown();
+ }
+
+ private function clearMimeTypes() {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('mimetypes');
+ $qb->executeStatement();
+
+ $this->mimetypeLoader->reset();
+ }
+
+ private function addEntries($entries) {
+ // create files for the different extensions, this
+ // will also automatically create the corresponding mime types
+ foreach ($entries as $entry) {
+ $this->storage->getCache()->put(
+ $entry[0],
+ [
+ 'size' => 0,
+ 'mtime' => 0,
+ 'mimetype' => $entry[1]
+ ]
+ );
+ }
+ }
+
+ private function checkEntries($entries) {
+ foreach ($entries as $entry) {
+ $data = $this->storage->getCache()->get($entry[0]);
+ $this->assertEquals($entry[1], $data['mimetype']);
+ }
+ }
+
+ private function renameMimeTypes($currentMimeTypes, $fixedMimeTypes) {
+ $this->addEntries($currentMimeTypes);
+
+ /** @var IOutput | \PHPUnit\Framework\MockObject\MockObject $outputMock */
+ $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->repair->run($outputMock);
+
+ // force mimetype reload
+ $this->mimetypeLoader->reset();
+
+ $this->checkEntries($fixedMimeTypes);
+ }
+
+ /**
+ * Test renaming the additional image mime types
+ */
+ public function testRenameImageTypes(): void {
+ $currentMimeTypes = [
+ ['test.jp2', 'application/octet-stream'],
+ ['test.webp', 'application/octet-stream'],
+ ];
+
+ $fixedMimeTypes = [
+ ['test.jp2', 'image/jp2'],
+ ['test.webp', 'image/webp'],
+ ];
+
+ $this->renameMimeTypes($currentMimeTypes, $fixedMimeTypes);
+ }
+
+ /**
+ * Test renaming the richdocuments additional office mime types
+ */
+ public function testRenameWindowsProgramTypes(): void {
+ $currentMimeTypes = [
+ ['test.htaccess', 'application/octet-stream'],
+ ['.htaccess', 'application/octet-stream'],
+ ['test.bat', 'application/octet-stream'],
+ ['test.cmd', 'application/octet-stream'],
+ ];
+
+ $fixedMimeTypes = [
+ ['test.htaccess', 'text/plain'],
+ ['.htaccess', 'text/plain'],
+ ['test.bat', 'application/x-msdos-program'],
+ ['test.cmd', 'application/cmd'],
+ ];
+
+ $this->renameMimeTypes($currentMimeTypes, $fixedMimeTypes);
+ }
+
+ /**
+ * Test that nothing happens and no error happens when all mimetypes are
+ * already correct and no old ones exist..
+ */
+ public function testDoNothingWhenOnlyNewFiles(): void {
+ $currentMimeTypes = [
+ ['test.doc', 'application/msword'],
+ ['test.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
+ ['test.xls', 'application/vnd.ms-excel'],
+ ['test.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
+ ['test.ppt', 'application/vnd.ms-powerpoint'],
+ ['test.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
+ ['test.apk', 'application/vnd.android.package-archive'],
+ ['test.ttf', 'application/font-sfnt'],
+ ['test.otf', 'application/font-sfnt'],
+ ['test.pfb', 'application/x-font'],
+ ['test.eps', 'application/postscript'],
+ ['test.ps', 'application/postscript'],
+ ['test.arw', 'image/x-dcraw'],
+ ['test.cr2', 'image/x-dcraw'],
+ ['test.dcr', 'image/x-dcraw'],
+ ['test.dng', 'image/x-dcraw'],
+ ['test.erf', 'image/x-dcraw'],
+ ['test.iiq', 'image/x-dcraw'],
+ ['test.k25', 'image/x-dcraw'],
+ ['test.kdc', 'image/x-dcraw'],
+ ['test.mef', 'image/x-dcraw'],
+ ['test.nef', 'image/x-dcraw'],
+ ['test.orf', 'image/x-dcraw'],
+ ['test.pef', 'image/x-dcraw'],
+ ['test.raf', 'image/x-dcraw'],
+ ['test.rw2', 'image/x-dcraw'],
+ ['test.srf', 'image/x-dcraw'],
+ ['test.sr2', 'image/x-dcraw'],
+ ['test.xrf', 'image/x-dcraw'],
+ ['test.DNG', 'image/x-dcraw'],
+ ['test.jp2', 'image/jp2'],
+ ['test.jps', 'image/jpeg'],
+ ['test.MPO', 'image/jpeg'],
+ ['test.webp', 'image/webp'],
+ ['test.conf', 'text/plain'],
+ ['test.cnf', 'text/plain'],
+ ['test.yaml', 'application/yaml'],
+ ['test.yml', 'application/yaml'],
+ ['test.java', 'text/x-java-source'],
+ ['test.class', 'application/java'],
+ ['test.hpp', 'text/x-h'],
+ ['test.rss', 'application/rss+xml'],
+ ['test.rtf', 'text/rtf'],
+ ['test.lwp', 'application/vnd.lotus-wordpro'],
+ ['test.one', 'application/msonenote'],
+ ['test.vsd', 'application/vnd.visio'],
+ ['test.wpd', 'application/vnd.wordperfect'],
+ ['test.htaccess', 'text/plain'],
+ ['.htaccess', 'text/plain'],
+ ['test.bat', 'application/x-msdos-program'],
+ ['test.cmd', 'application/cmd'],
+ ];
+
+ $fixedMimeTypes = [
+ ['test.doc', 'application/msword'],
+ ['test.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
+ ['test.xls', 'application/vnd.ms-excel'],
+ ['test.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
+ ['test.ppt', 'application/vnd.ms-powerpoint'],
+ ['test.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
+ ['test.apk', 'application/vnd.android.package-archive'],
+ ['test.ttf', 'application/font-sfnt'],
+ ['test.otf', 'application/font-sfnt'],
+ ['test.pfb', 'application/x-font'],
+ ['test.eps', 'application/postscript'],
+ ['test.ps', 'application/postscript'],
+ ['test.arw', 'image/x-dcraw'],
+ ['test.cr2', 'image/x-dcraw'],
+ ['test.dcr', 'image/x-dcraw'],
+ ['test.dng', 'image/x-dcraw'],
+ ['test.erf', 'image/x-dcraw'],
+ ['test.iiq', 'image/x-dcraw'],
+ ['test.k25', 'image/x-dcraw'],
+ ['test.kdc', 'image/x-dcraw'],
+ ['test.mef', 'image/x-dcraw'],
+ ['test.nef', 'image/x-dcraw'],
+ ['test.orf', 'image/x-dcraw'],
+ ['test.pef', 'image/x-dcraw'],
+ ['test.raf', 'image/x-dcraw'],
+ ['test.rw2', 'image/x-dcraw'],
+ ['test.srf', 'image/x-dcraw'],
+ ['test.sr2', 'image/x-dcraw'],
+ ['test.xrf', 'image/x-dcraw'],
+ ['test.DNG', 'image/x-dcraw'],
+ ['test.jp2', 'image/jp2'],
+ ['test.jps', 'image/jpeg'],
+ ['test.MPO', 'image/jpeg'],
+ ['test.webp', 'image/webp'],
+ ['test.conf', 'text/plain'],
+ ['test.cnf', 'text/plain'],
+ ['test.yaml', 'application/yaml'],
+ ['test.yml', 'application/yaml'],
+ ['test.java', 'text/x-java-source'],
+ ['test.class', 'application/java'],
+ ['test.hpp', 'text/x-h'],
+ ['test.rss', 'application/rss+xml'],
+ ['test.rtf', 'text/rtf'],
+ ['test.lwp', 'application/vnd.lotus-wordpro'],
+ ['test.one', 'application/msonenote'],
+ ['test.vsd', 'application/vnd.visio'],
+ ['test.wpd', 'application/vnd.wordperfect'],
+ ['test.htaccess', 'text/plain'],
+ ['.htaccess', 'text/plain'],
+ ['test.bat', 'application/x-msdos-program'],
+ ['test.cmd', 'application/cmd'],
+ ];
+
+ $this->renameMimeTypes($currentMimeTypes, $fixedMimeTypes);
+ }
+
+ /**
+ * Test that mime type renaming does not affect folders
+ */
+ public function testDoNotChangeFolderMimeType(): void {
+ $currentMimeTypes = [
+ ['test.conf', 'httpd/unix-directory'],
+ ['test.cnf', 'httpd/unix-directory'],
+ ];
+
+ $fixedMimeTypes = [
+ ['test.conf', 'httpd/unix-directory'],
+ ['test.cnf', 'httpd/unix-directory'],
+ ];
+
+ $this->renameMimeTypes($currentMimeTypes, $fixedMimeTypes);
+ }
+}