diff options
Diffstat (limited to 'apps/files_trashbin/tests/StorageTest.php')
-rw-r--r-- | apps/files_trashbin/tests/StorageTest.php | 408 |
1 files changed, 245 insertions, 163 deletions
diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index 0e23ea6a3ba..c58ddec97dd 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -1,45 +1,47 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Stefan Weil <sw@weilnetz.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ - namespace OCA\Files_Trashbin\Tests; -use OC\Files\Storage\Temporary; use OC\Files\Filesystem; +use OC\Files\Storage\Common; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OCA\Files_Trashbin\AppInfo\Application; use OCA\Files_Trashbin\Events\MoveToTrashEvent; use OCA\Files_Trashbin\Storage; +use OCA\Files_Trashbin\Trash\ITrashManager; +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Constants; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Cache\ICache; +use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Node; -use OCP\ILogger; +use OCP\Files\Storage\IStorage; use OCP\IUserManager; -use Symfony\Component\EventDispatcher\EventDispatcher; +use OCP\Lock\ILockingProvider; +use OCP\Server; +use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\Traits\MountProviderTrait; + +class TemporaryNoCross extends Temporary { + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, ?bool $preserveMtime = null): bool { + return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime); + } + + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } +} /** * Class Storage @@ -49,48 +51,52 @@ use Symfony\Component\EventDispatcher\EventDispatcher; * @package OCA\Files_Trashbin\Tests */ class StorageTest extends \Test\TestCase { - /** - * @var string - */ - private $user; + use MountProviderTrait; - /** - * @var \OC\Files\View - */ - private $rootView; + private string $user; + private View $rootView; + private View $userView; - /** - * @var \OC\Files\View - */ - private $userView; + // 239 chars so appended timestamp of 12 chars will exceed max length of 250 chars + private const LONG_FILENAME = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; + // 250 chars + private const MAX_FILENAME = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; - protected function setUp() { + protected function setUp(): void { parent::setUp(); \OC_Hook::clear(); - \OCA\Files_Trashbin\Trashbin::registerHooks(); + \OC::$server->boot(); + + // register trashbin hooks + $trashbinApp = new Application(); + $trashbinApp->boot($this->createMock(IBootContext::class)); $this->user = $this->getUniqueId('user'); - \OC::$server->getUserManager()->createUser($this->user, $this->user); + Server::get(IUserManager::class)->createUser($this->user, $this->user); // this will setup the FS $this->loginAsUser($this->user); - \OCA\Files_Trashbin\Storage::setupStorage(); + Storage::setupStorage(); - $this->rootView = new \OC\Files\View('/'); - $this->userView = new \OC\Files\View('/' . $this->user . '/files/'); + $this->rootView = new View('/'); + $this->userView = new View('/' . $this->user . '/files/'); $this->userView->file_put_contents('test.txt', 'foo'); + $this->userView->file_put_contents(static::LONG_FILENAME, 'foo'); + $this->userView->file_put_contents(static::MAX_FILENAME, 'foo'); $this->userView->mkdir('folder'); $this->userView->file_put_contents('folder/inside.txt', 'bar'); } - protected function tearDown() { - \OC\Files\Filesystem::getLoader()->removeStorageWrapper('oc_trashbin'); + protected function tearDown(): void { + Filesystem::getLoader()->removeStorageWrapper('oc_trashbin'); $this->logout(); - $user = \OC::$server->getUserManager()->get($this->user); - if ($user !== null) { $user->delete(); } + $user = Server::get(IUserManager::class)->get($this->user); + if ($user !== null) { + $user->delete(); + } \OC_Hook::clear(); parent::tearDown(); } @@ -98,16 +104,16 @@ class StorageTest extends \Test\TestCase { /** * Test that deleting a file puts it into the trashbin. */ - public function testSingleStorageDeleteFile() { + public function testSingleStorageDeleteFile(): void { $this->assertTrue($this->userView->file_exists('test.txt')); $this->userView->unlink('test.txt'); - list($storage,) = $this->userView->resolvePath('test.txt'); + [$storage,] = $this->userView->resolvePath('test.txt'); $storage->getScanner()->scan(''); // make sure we check the storage $this->assertFalse($this->userView->getFileInfo('test.txt')); // check if file is in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('test.txt', substr($name, 0, strrpos($name, '.'))); } @@ -115,16 +121,16 @@ class StorageTest extends \Test\TestCase { /** * Test that deleting a folder puts it into the trashbin. */ - public function testSingleStorageDeleteFolder() { + public function testSingleStorageDeleteFolder(): void { $this->assertTrue($this->userView->file_exists('folder/inside.txt')); $this->userView->rmdir('folder'); - list($storage,) = $this->userView->resolvePath('folder/inside.txt'); + [$storage,] = $this->userView->resolvePath('folder/inside.txt'); $storage->getScanner()->scan(''); // make sure we check the storage $this->assertFalse($this->userView->getFileInfo('folder')); // check if folder is in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('folder', substr($name, 0, strrpos($name, '.'))); @@ -135,14 +141,52 @@ class StorageTest extends \Test\TestCase { } /** + * Test that deleting a file with a long filename puts it into the trashbin. + */ + public function testSingleStorageDeleteLongFilename(): void { + $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; + + $this->assertTrue($this->userView->file_exists(static::LONG_FILENAME)); + $this->userView->unlink(static::LONG_FILENAME); + [$storage,] = $this->userView->resolvePath(static::LONG_FILENAME); + $storage->getScanner()->scan(''); // make sure we check the storage + $this->assertFalse($this->userView->getFileInfo(static::LONG_FILENAME)); + + // check if file is in trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertCount(1, $results); + $name = $results[0]->getName(); + $this->assertEquals($truncatedFilename, substr($name, 0, strrpos($name, '.'))); + } + + /** + * Test that deleting a file with the max filename length puts it into the trashbin. + */ + public function testSingleStorageDeleteMaxLengthFilename(): void { + $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; + + $this->assertTrue($this->userView->file_exists(static::MAX_FILENAME)); + $this->userView->unlink(static::MAX_FILENAME); + [$storage,] = $this->userView->resolvePath(static::MAX_FILENAME); + $storage->getScanner()->scan(''); // make sure we check the storage + $this->assertFalse($this->userView->getFileInfo(static::MAX_FILENAME)); + + // check if file is in trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertCount(1, $results); + $name = $results[0]->getName(); + $this->assertEquals($truncatedFilename, substr($name, 0, strrpos($name, '.'))); + } + + /** * Test that deleting a file from another mounted storage properly * lands in the trashbin. This is a cross-storage situation because * the trashbin folder is in the root storage while the mounted one * isn't. */ - public function testCrossStorageDeleteFile() { - $storage2 = new Temporary(array()); - \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + public function testCrossStorageDeleteFile(): void { + $storage2 = new Temporary([]); + Filesystem::mount($storage2, [], $this->user . '/files/substorage'); $this->userView->file_put_contents('substorage/subfile.txt', 'foo'); $storage2->getScanner()->scan(''); @@ -155,7 +199,7 @@ class StorageTest extends \Test\TestCase { // check if file is in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('subfile.txt', substr($name, 0, strrpos($name, '.'))); } @@ -166,9 +210,9 @@ class StorageTest extends \Test\TestCase { * the trashbin folder is in the root storage while the mounted one * isn't. */ - public function testCrossStorageDeleteFolder() { - $storage2 = new Temporary(array()); - \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + public function testCrossStorageDeleteFolder(): void { + $storage2 = new Temporary([]); + Filesystem::mount($storage2, [], $this->user . '/files/substorage'); $this->userView->mkdir('substorage/folder'); $this->userView->file_put_contents('substorage/folder/subfile.txt', 'bar'); @@ -182,12 +226,12 @@ class StorageTest extends \Test\TestCase { // check if folder is in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('folder', substr($name, 0, strrpos($name, '.'))); $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('subfile.txt', $name); } @@ -195,9 +239,7 @@ class StorageTest extends \Test\TestCase { /** * Test that deleted versions properly land in the trashbin. */ - public function testDeleteVersionsOfFile() { - \OCA\Files_Versions\Hooks::connectHooks(); - + public function testDeleteVersionsOfFile(): void { // trigger a version (multiple would not work because of the expire logic) $this->userView->file_put_contents('test.txt', 'v1'); @@ -207,113 +249,108 @@ class StorageTest extends \Test\TestCase { $this->userView->unlink('test.txt'); // rescan trash storage - list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin'); $rootStorage->getScanner()->scan(''); // check if versions are in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v'))); // versions deleted $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** * Test that deleted versions properly land in the trashbin. */ - public function testDeleteVersionsOfFolder() { - \OCA\Files_Versions\Hooks::connectHooks(); - + public function testDeleteVersionsOfFolder(): void { // trigger a version (multiple would not work because of the expire logic) $this->userView->file_put_contents('folder/inside.txt', 'v1'); $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $this->userView->rmdir('folder'); // rescan trash storage - list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin'); $rootStorage->getScanner()->scan(''); // check if versions are in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d'))); // check if versions are in trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('inside.txt.v', substr($name, 0, strlen('inside.txt.v'))); // versions deleted $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** * Test that deleted versions properly land in the trashbin when deleting as share recipient. */ - public function testDeleteVersionsOfFileAsRecipient() { - \OCA\Files_Versions\Hooks::connectHooks(); - + public function testDeleteVersionsOfFileAsRecipient(): void { $this->userView->mkdir('share'); // trigger a version (multiple would not work because of the expire logic) $this->userView->file_put_contents('share/test.txt', 'v1'); $this->userView->file_put_contents('share/test.txt', 'v2'); $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $recipientUser = $this->getUniqueId('recipient_'); - \OC::$server->getUserManager()->createUser($recipientUser, $recipientUser); + Server::get(IUserManager::class)->createUser($recipientUser, $recipientUser); - $node = \OC::$server->getUserFolder($this->user)->get('share'); - $share = \OC::$server->getShareManager()->newShare(); + $node = Server::get(IRootFolder::class)->getUserFolder($this->user)->get('share'); + $share = Server::get(\OCP\Share\IManager::class)->newShare(); $share->setNode($node) - ->setShareType(\OCP\Share::SHARE_TYPE_USER) + ->setShareType(IShare::TYPE_USER) ->setSharedBy($this->user) ->setSharedWith($recipientUser) - ->setPermissions(\OCP\Constants::PERMISSION_ALL); - \OC::$server->getShareManager()->createShare($share); + ->setPermissions(Constants::PERMISSION_ALL); + $share = Server::get(\OCP\Share\IManager::class)->createShare($share); + Server::get(\OCP\Share\IManager::class)->acceptShare($share, $recipientUser); $this->loginAsUser($recipientUser); // delete as recipient - $recipientView = new \OC\Files\View('/' . $recipientUser . '/files'); + $recipientView = new View('/' . $recipientUser . '/files'); $recipientView->unlink('share/test.txt'); // rescan trash storage for both users - list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin'); $rootStorage->getScanner()->scan(''); // check if versions are in trashbin for both users $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions'); - $this->assertEquals(1, count($results), 'Versions in owner\'s trashbin'); + $this->assertCount(1, $results, 'Versions in owner\'s trashbin'); $name = $results[0]->getName(); $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v'))); $results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions'); - $this->assertEquals(1, count($results), 'Versions in recipient\'s trashbin'); + $this->assertCount(1, $results, 'Versions in recipient\'s trashbin'); $name = $results[0]->getName(); $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v'))); // versions deleted $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** * Test that deleted versions properly land in the trashbin when deleting as share recipient. */ - public function testDeleteVersionsOfFolderAsRecipient() { - \OCA\Files_Versions\Hooks::connectHooks(); - + public function testDeleteVersionsOfFolderAsRecipient(): void { $this->userView->mkdir('share'); $this->userView->mkdir('share/folder'); // trigger a version (multiple would not work because of the expire logic) @@ -321,57 +358,57 @@ class StorageTest extends \Test\TestCase { $this->userView->file_put_contents('share/folder/test.txt', 'v2'); $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/folder/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $recipientUser = $this->getUniqueId('recipient_'); - \OC::$server->getUserManager()->createUser($recipientUser, $recipientUser); - - $node = \OC::$server->getUserFolder($this->user)->get('share'); - $share = \OC::$server->getShareManager()->newShare(); + Server::get(IUserManager::class)->createUser($recipientUser, $recipientUser); + $node = Server::get(IRootFolder::class)->getUserFolder($this->user)->get('share'); + $share = Server::get(\OCP\Share\IManager::class)->newShare(); $share->setNode($node) - ->setShareType(\OCP\Share::SHARE_TYPE_USER) + ->setShareType(IShare::TYPE_USER) ->setSharedBy($this->user) ->setSharedWith($recipientUser) - ->setPermissions(\OCP\Constants::PERMISSION_ALL); - \OC::$server->getShareManager()->createShare($share); + ->setPermissions(Constants::PERMISSION_ALL); + $share = Server::get(\OCP\Share\IManager::class)->createShare($share); + Server::get(\OCP\Share\IManager::class)->acceptShare($share, $recipientUser); $this->loginAsUser($recipientUser); // delete as recipient - $recipientView = new \OC\Files\View('/' . $recipientUser . '/files'); + $recipientView = new View('/' . $recipientUser . '/files'); $recipientView->rmdir('share/folder'); // rescan trash storage - list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin'); $rootStorage->getScanner()->scan(''); // check if versions are in trashbin for owner $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d'))); // check if file versions are in trashbin for owner $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v'))); // check if versions are in trashbin for recipient $results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d'))); // check if file versions are in trashbin for recipient $results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions/' . $name . '/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); $name = $results[0]->getName(); $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v'))); // versions deleted $results = $this->rootView->getDirectoryContent($recipientUser . '/files_versions/share/folder/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** @@ -379,40 +416,38 @@ class StorageTest extends \Test\TestCase { * storages. This is because rename() between storages would call * unlink() which should NOT trigger the version deletion logic. */ - public function testKeepFileAndVersionsWhenMovingFileBetweenStorages() { - \OCA\Files_Versions\Hooks::connectHooks(); - - $storage2 = new Temporary(array()); - \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + public function testKeepFileAndVersionsWhenMovingFileBetweenStorages(): void { + $storage2 = new Temporary([]); + Filesystem::mount($storage2, [], $this->user . '/files/substorage'); // trigger a version (multiple would not work because of the expire logic) $this->userView->file_put_contents('test.txt', 'v1'); $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); // move to another storage $this->userView->rename('test.txt', 'substorage/test.txt'); $this->assertTrue($this->userView->file_exists('substorage/test.txt')); // rescan trash storage - list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin'); $rootStorage->getScanner()->scan(''); // versions were moved too $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/substorage'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); // check that nothing got trashed by the rename's unlink() call $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); // check that versions were moved and not trashed $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** @@ -420,63 +455,61 @@ class StorageTest extends \Test\TestCase { * storages. This is because rename() between storages would call * unlink() which should NOT trigger the version deletion logic. */ - public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() { - \OCA\Files_Versions\Hooks::connectHooks(); - - $storage2 = new Temporary(array()); - \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages(): void { + $storage2 = new Temporary([]); + Filesystem::mount($storage2, [], $this->user . '/files/substorage'); // trigger a version (multiple would not work because of the expire logic) $this->userView->file_put_contents('folder/inside.txt', 'v1'); $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); // move to another storage $this->userView->rename('folder', 'substorage/folder'); $this->assertTrue($this->userView->file_exists('substorage/folder/inside.txt')); // rescan trash storage - list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin'); $rootStorage->getScanner()->scan(''); // versions were moved too $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/substorage/folder/'); - $this->assertEquals(1, count($results)); + $this->assertCount(1, $results); // check that nothing got trashed by the rename's unlink() call $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); // check that versions were moved and not trashed $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** * Delete should fail if the source file can't be deleted. */ - public function testSingleStorageDeleteFileFail() { + public function testSingleStorageDeleteFileFail(): void { /** - * @var \OC\Files\Storage\Temporary | \PHPUnit_Framework_MockObject_MockObject $storage + * @var Temporary&MockObject $storage */ - $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + $storage = $this->getMockBuilder(Temporary::class) ->setConstructorArgs([[]]) - ->setMethods(['rename', 'unlink', 'moveFromStorage']) + ->onlyMethods(['rename', 'unlink', 'moveFromStorage']) ->getMock(); $storage->expects($this->any()) ->method('rename') - ->will($this->returnValue(false)); + ->willReturn(false); $storage->expects($this->any()) ->method('moveFromStorage') - ->will($this->returnValue(false)); + ->willReturn(false); $storage->expects($this->any()) ->method('unlink') - ->will($this->returnValue(false)); + ->willReturn(false); $cache = $storage->getCache(); @@ -490,24 +523,24 @@ class StorageTest extends \Test\TestCase { // file should not be in the trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } /** * Delete should fail if the source folder can't be deleted. */ - public function testSingleStorageDeleteFolderFail() { + public function testSingleStorageDeleteFolderFail(): void { /** - * @var \OC\Files\Storage\Temporary | \PHPUnit_Framework_MockObject_MockObject $storage + * @var Temporary&MockObject $storage */ - $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + $storage = $this->getMockBuilder(Temporary::class) ->setConstructorArgs([[]]) - ->setMethods(['rename', 'unlink', 'rmdir']) + ->onlyMethods(['rename', 'unlink', 'rmdir']) ->getMock(); $storage->expects($this->any()) ->method('rmdir') - ->will($this->returnValue(false)); + ->willReturn(false); $cache = $storage->getCache(); @@ -524,43 +557,46 @@ class StorageTest extends \Test\TestCase { // file should not be in the trashbin $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); - $this->assertEquals(0, count($results)); + $this->assertCount(0, $results); } - /** - * @dataProvider dataTestShouldMoveToTrash - */ - public function testShouldMoveToTrash($mountPoint, $path, $userExists, $appDisablesTrash, $expected) { + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestShouldMoveToTrash')] + public function testShouldMoveToTrash(string $mountPoint, string $path, bool $userExists, bool $appDisablesTrash, bool $expected): void { $fileID = 1; $cache = $this->createMock(ICache::class); $cache->expects($this->any())->method('getId')->willReturn($fileID); - $tmpStorage = $this->getMockBuilder('\OC\Files\Storage\Temporary') - ->disableOriginalConstructor()->getMock($cache); + $tmpStorage = $this->createMock(Temporary::class); $tmpStorage->expects($this->any())->method('getCache')->willReturn($cache); $userManager = $this->getMockBuilder(IUserManager::class) ->disableOriginalConstructor()->getMock(); $userManager->expects($this->any()) ->method('userExists')->willReturn($userExists); - $logger = $this->getMockBuilder(ILogger::class)->getMock(); - $eventDispatcher = $this->getMockBuilder(EventDispatcher::class) - ->disableOriginalConstructor()->getMock(); + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + $eventDispatcher = $this->createMock(IEventDispatcher::class); $rootFolder = $this->createMock(IRootFolder::class); + $userFolder = $this->createMock(Folder::class); $node = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); + $trashManager = $this->createMock(ITrashManager::class); $event = $this->getMockBuilder(MoveToTrashEvent::class)->disableOriginalConstructor()->getMock(); $event->expects($this->any())->method('shouldMoveToTrashBin')->willReturn(!$appDisablesTrash); + $userFolder->expects($this->any())->method('getById')->with($fileID)->willReturn([$node]); $rootFolder->expects($this->any())->method('getById')->with($fileID)->willReturn([$node]); + $rootFolder->expects($this->any())->method('getUserFolder')->willReturn($userFolder); $storage = $this->getMockBuilder(Storage::class) ->setConstructorArgs( [ ['mountPoint' => $mountPoint, 'storage' => $tmpStorage], + $trashManager, $userManager, $logger, $eventDispatcher, $rootFolder ] - )->setMethods(['createMoveToTrashEvent'])->getMock(); + ) + ->onlyMethods(['createMoveToTrashEvent']) + ->getMock(); $storage->expects($this->any())->method('createMoveToTrashEvent')->with($node) ->willReturn($event); @@ -568,10 +604,9 @@ class StorageTest extends \Test\TestCase { $this->assertSame($expected, $this->invokePrivate($storage, 'shouldMoveToTrash', [$path]) ); - } - public function dataTestShouldMoveToTrash() { + public static function dataTestShouldMoveToTrash(): array { return [ ['/schiesbn/', '/files/test.txt', true, false, true], ['/schiesbn/', '/files/test.txt', false, false, false], @@ -586,13 +621,60 @@ class StorageTest extends \Test\TestCase { /** * Test that deleting a file doesn't error when nobody is logged in */ - public function testSingleStorageDeleteFileLoggedOut() { + public function testSingleStorageDeleteFileLoggedOut(): void { $this->logout(); if (!$this->userView->file_exists('test.txt')) { $this->markTestSkipped('Skipping since the current home storage backend requires the user to logged in'); } else { $this->userView->unlink('test.txt'); + $this->addToAssertionCount(1); } } + + public function testTrashbinCollision(): void { + $this->userView->file_put_contents('test.txt', 'foo'); + $this->userView->file_put_contents('folder/test.txt', 'bar'); + + $timeFactory = $this->createMock(ITimeFactory::class); + $timeFactory->method('getTime') + ->willReturn(1000); + + $lockingProvider = Server::get(ILockingProvider::class); + + $this->overwriteService(ITimeFactory::class, $timeFactory); + + $this->userView->unlink('test.txt'); + + $this->assertTrue($this->rootView->file_exists('/' . $this->user . '/files_trashbin/files/test.txt.d1000')); + + /** @var \OC\Files\Storage\Storage $trashStorage */ + [$trashStorage, $trashInternalPath] = $this->rootView->resolvePath('/' . $this->user . '/files_trashbin/files/test.txt.d1000'); + + /// simulate a concurrent delete + $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + + $this->userView->unlink('folder/test.txt'); + + $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + + $this->assertTrue($this->rootView->file_exists($this->user . '/files_trashbin/files/test.txt.d1001')); + + $this->assertEquals('foo', $this->rootView->file_get_contents($this->user . '/files_trashbin/files/test.txt.d1000')); + $this->assertEquals('bar', $this->rootView->file_get_contents($this->user . '/files_trashbin/files/test.txt.d1001')); + } + + public function testMoveFromStoragePreserveFileId(): void { + $this->userView->file_put_contents('test.txt', 'foo'); + $fileId = $this->userView->getFileInfo('test.txt')->getId(); + + $externalStorage = new TemporaryNoCross([]); + $externalStorage->getScanner()->scan(''); + Filesystem::mount($externalStorage, [], '/' . $this->user . '/files/storage'); + + $this->assertTrue($this->userView->rename('test.txt', 'storage/test.txt')); + $this->assertTrue($externalStorage->file_exists('test.txt')); + + $this->assertEquals($fileId, $this->userView->getFileInfo('storage/test.txt')->getId()); + } } |