diff options
Diffstat (limited to 'tests/lib/Repair')
-rw-r--r-- | tests/lib/Repair/CleanTagsTest.php | 195 | ||||
-rw-r--r-- | tests/lib/Repair/ClearFrontendCachesTest.php | 52 | ||||
-rw-r--r-- | tests/lib/Repair/ClearGeneratedAvatarCacheTest.php | 61 | ||||
-rw-r--r-- | tests/lib/Repair/NC29/SanitizeAccountPropertiesJobTest.php | 116 | ||||
-rw-r--r-- | tests/lib/Repair/NC29/SanitizeAccountPropertiesTest.php | 43 | ||||
-rw-r--r-- | tests/lib/Repair/OldGroupMembershipSharesTest.php | 133 | ||||
-rw-r--r-- | tests/lib/Repair/Owncloud/CleanPreviewsBackgroundJobTest.php | 237 | ||||
-rw-r--r-- | tests/lib/Repair/Owncloud/CleanPreviewsTest.php | 110 | ||||
-rw-r--r-- | tests/lib/Repair/Owncloud/UpdateLanguageCodesTest.php | 156 | ||||
-rw-r--r-- | tests/lib/Repair/RepairCollationTest.php | 83 | ||||
-rw-r--r-- | tests/lib/Repair/RepairDavSharesTest.php | 178 | ||||
-rw-r--r-- | tests/lib/Repair/RepairInvalidSharesTest.php | 191 | ||||
-rw-r--r-- | tests/lib/Repair/RepairMimeTypesTest.php | 293 |
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); + } +} |