StorageTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bjoern Schiessle <bjoern@schiessle.org>
  6. * @author Björn Schießle <bjoern@schiessle.org>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Julius Härtl <jus@bitgrid.net>
  10. * @author Morris Jobke <hey@morrisjobke.de>
  11. * @author Robin Appelman <robin@icewind.nl>
  12. * @author Roeland Jago Douma <roeland@famdouma.nl>
  13. * @author Stefan Weil <sw@weilnetz.de>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Vincent Petry <vincent@nextcloud.com>
  16. *
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OCA\Files_Trashbin\Tests;
  33. use OC\Files\Filesystem;
  34. use OC\Files\Storage\Common;
  35. use OC\Files\Storage\Local;
  36. use OC\Files\Storage\Temporary;
  37. use OCA\Files_Trashbin\AppInfo\Application;
  38. use OCA\Files_Trashbin\Events\MoveToTrashEvent;
  39. use OCA\Files_Trashbin\Storage;
  40. use OCA\Files_Trashbin\Trash\ITrashManager;
  41. use OCP\AppFramework\Bootstrap\IBootContext;
  42. use OCP\AppFramework\Utility\ITimeFactory;
  43. use OCP\EventDispatcher\IEventDispatcher;
  44. use OCP\Files\Cache\ICache;
  45. use OCP\Files\Folder;
  46. use OCP\Files\IRootFolder;
  47. use OCP\Files\Node;
  48. use OCP\Files\Storage\IStorage;
  49. use OCP\IUserManager;
  50. use OCP\Lock\ILockingProvider;
  51. use OCP\Share\IShare;
  52. use Psr\Log\LoggerInterface;
  53. use Test\Traits\MountProviderTrait;
  54. class TemporaryNoCross extends Temporary {
  55. public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = null) {
  56. return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime);
  57. }
  58. public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
  59. return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
  60. }
  61. }
  62. /**
  63. * Class Storage
  64. *
  65. * @group DB
  66. *
  67. * @package OCA\Files_Trashbin\Tests
  68. */
  69. class StorageTest extends \Test\TestCase {
  70. use MountProviderTrait;
  71. /**
  72. * @var string
  73. */
  74. private $user;
  75. /**
  76. * @var \OC\Files\View
  77. */
  78. private $rootView;
  79. /**
  80. * @var \OC\Files\View
  81. */
  82. private $userView;
  83. // 239 chars so appended timestamp of 12 chars will exceed max length of 250 chars
  84. private const LONG_FILENAME = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt';
  85. // 250 chars
  86. private const MAX_FILENAME = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt';
  87. protected function setUp(): void {
  88. parent::setUp();
  89. \OC_Hook::clear();
  90. \OC::$server->boot();
  91. // register trashbin hooks
  92. $trashbinApp = new Application();
  93. $trashbinApp->boot($this->createMock(IBootContext::class));
  94. $this->user = $this->getUniqueId('user');
  95. \OC::$server->getUserManager()->createUser($this->user, $this->user);
  96. // this will setup the FS
  97. $this->loginAsUser($this->user);
  98. \OCA\Files_Trashbin\Storage::setupStorage();
  99. $this->rootView = new \OC\Files\View('/');
  100. $this->userView = new \OC\Files\View('/' . $this->user . '/files/');
  101. $this->userView->file_put_contents('test.txt', 'foo');
  102. $this->userView->file_put_contents(static::LONG_FILENAME, 'foo');
  103. $this->userView->file_put_contents(static::MAX_FILENAME, 'foo');
  104. $this->userView->mkdir('folder');
  105. $this->userView->file_put_contents('folder/inside.txt', 'bar');
  106. }
  107. protected function tearDown(): void {
  108. \OC\Files\Filesystem::getLoader()->removeStorageWrapper('oc_trashbin');
  109. $this->logout();
  110. $user = \OC::$server->getUserManager()->get($this->user);
  111. if ($user !== null) {
  112. $user->delete();
  113. }
  114. \OC_Hook::clear();
  115. parent::tearDown();
  116. }
  117. /**
  118. * Test that deleting a file puts it into the trashbin.
  119. */
  120. public function testSingleStorageDeleteFile() {
  121. $this->assertTrue($this->userView->file_exists('test.txt'));
  122. $this->userView->unlink('test.txt');
  123. [$storage,] = $this->userView->resolvePath('test.txt');
  124. $storage->getScanner()->scan(''); // make sure we check the storage
  125. $this->assertFalse($this->userView->getFileInfo('test.txt'));
  126. // check if file is in trashbin
  127. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
  128. $this->assertEquals(1, count($results));
  129. $name = $results[0]->getName();
  130. $this->assertEquals('test.txt', substr($name, 0, strrpos($name, '.')));
  131. }
  132. /**
  133. * Test that deleting a folder puts it into the trashbin.
  134. */
  135. public function testSingleStorageDeleteFolder() {
  136. $this->assertTrue($this->userView->file_exists('folder/inside.txt'));
  137. $this->userView->rmdir('folder');
  138. [$storage,] = $this->userView->resolvePath('folder/inside.txt');
  139. $storage->getScanner()->scan(''); // make sure we check the storage
  140. $this->assertFalse($this->userView->getFileInfo('folder'));
  141. // check if folder is in trashbin
  142. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
  143. $this->assertEquals(1, count($results));
  144. $name = $results[0]->getName();
  145. $this->assertEquals('folder', substr($name, 0, strrpos($name, '.')));
  146. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/');
  147. $this->assertEquals(1, count($results));
  148. $name = $results[0]->getName();
  149. $this->assertEquals('inside.txt', $name);
  150. }
  151. /**
  152. * Test that deleting a file with a long filename puts it into the trashbin.
  153. */
  154. public function testSingleStorageDeleteLongFilename() {
  155. $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt';
  156. $this->assertTrue($this->userView->file_exists(static::LONG_FILENAME));
  157. $this->userView->unlink(static::LONG_FILENAME);
  158. [$storage,] = $this->userView->resolvePath(static::LONG_FILENAME);
  159. $storage->getScanner()->scan(''); // make sure we check the storage
  160. $this->assertFalse($this->userView->getFileInfo(static::LONG_FILENAME));
  161. // check if file is in trashbin
  162. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
  163. $this->assertEquals(1, count($results));
  164. $name = $results[0]->getName();
  165. $this->assertEquals($truncatedFilename, substr($name, 0, strrpos($name, '.')));
  166. }
  167. /**
  168. * Test that deleting a file with the max filename length puts it into the trashbin.
  169. */
  170. public function testSingleStorageDeleteMaxLengthFilename() {
  171. $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt';
  172. $this->assertTrue($this->userView->file_exists(static::MAX_FILENAME));
  173. $this->userView->unlink(static::MAX_FILENAME);
  174. [$storage,] = $this->userView->resolvePath(static::MAX_FILENAME);
  175. $storage->getScanner()->scan(''); // make sure we check the storage
  176. $this->assertFalse($this->userView->getFileInfo(static::MAX_FILENAME));
  177. // check if file is in trashbin
  178. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
  179. $this->assertEquals(1, count($results));
  180. $name = $results[0]->getName();
  181. $this->assertEquals($truncatedFilename, substr($name, 0, strrpos($name, '.')));
  182. }
  183. /**
  184. * Test that deleting a file from another mounted storage properly
  185. * lands in the trashbin. This is a cross-storage situation because
  186. * the trashbin folder is in the root storage while the mounted one
  187. * isn't.
  188. */
  189. public function testCrossStorageDeleteFile() {
  190. $storage2 = new Temporary([]);
  191. \OC\Files\Filesystem::mount($storage2, [], $this->user . '/files/substorage');
  192. $this->userView->file_put_contents('substorage/subfile.txt', 'foo');
  193. $storage2->getScanner()->scan('');
  194. $this->assertTrue($storage2->file_exists('subfile.txt'));
  195. $this->userView->unlink('substorage/subfile.txt');
  196. $storage2->getScanner()->scan('');
  197. $this->assertFalse($this->userView->getFileInfo('substorage/subfile.txt'));
  198. $this->assertFalse($storage2->file_exists('subfile.txt'));
  199. // check if file is in trashbin
  200. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
  201. $this->assertEquals(1, count($results));
  202. $name = $results[0]->getName();
  203. $this->assertEquals('subfile.txt', substr($name, 0, strrpos($name, '.')));
  204. }
  205. /**
  206. * Test that deleting a folder from another mounted storage properly
  207. * lands in the trashbin. This is a cross-storage situation because
  208. * the trashbin folder is in the root storage while the mounted one
  209. * isn't.
  210. */
  211. public function testCrossStorageDeleteFolder() {
  212. $storage2 = new Temporary([]);
  213. \OC\Files\Filesystem::mount($storage2, [], $this->user . '/files/substorage');
  214. $this->userView->mkdir('substorage/folder');
  215. $this->userView->file_put_contents('substorage/folder/subfile.txt', 'bar');
  216. $storage2->getScanner()->scan('');
  217. $this->assertTrue($storage2->file_exists('folder/subfile.txt'));
  218. $this->userView->rmdir('substorage/folder');
  219. $storage2->getScanner()->scan('');
  220. $this->assertFalse($this->userView->getFileInfo('substorage/folder'));
  221. $this->assertFalse($storage2->file_exists('folder/subfile.txt'));
  222. // check if folder is in trashbin
  223. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
  224. $this->assertEquals(1, count($results));
  225. $name = $results[0]->getName();
  226. $this->assertEquals('folder', substr($name, 0, strrpos($name, '.')));
  227. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/');
  228. $this->assertEquals(1, count($results));
  229. $name = $results[0]->getName();
  230. $this->assertEquals('subfile.txt', $name);
  231. }
  232. /**
  233. * Test that deleted versions properly land in the trashbin.
  234. */
  235. public function testDeleteVersionsOfFile() {
  236. // trigger a version (multiple would not work because of the expire logic)
  237. $this->userView->file_put_contents('test.txt', 'v1');
  238. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/');
  239. $this->assertEquals(1, count($results));
  240. $this->userView->unlink('test.txt');
  241. // rescan trash storage
  242. [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin');
  243. $rootStorage->getScanner()->scan('');
  244. // check if versions are in trashbin
  245. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
  246. $this->assertEquals(1, count($results));
  247. $name = $results[0]->getName();
  248. $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
  249. // versions deleted
  250. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/');
  251. $this->assertEquals(0, count($results));
  252. }
  253. /**
  254. * Test that deleted versions properly land in the trashbin.
  255. */
  256. public function testDeleteVersionsOfFolder() {
  257. // trigger a version (multiple would not work because of the expire logic)
  258. $this->userView->file_put_contents('folder/inside.txt', 'v1');
  259. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/');
  260. $this->assertEquals(1, count($results));
  261. $this->userView->rmdir('folder');
  262. // rescan trash storage
  263. [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin');
  264. $rootStorage->getScanner()->scan('');
  265. // check if versions are in trashbin
  266. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
  267. $this->assertEquals(1, count($results));
  268. $name = $results[0]->getName();
  269. $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d')));
  270. // check if versions are in trashbin
  271. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/');
  272. $this->assertEquals(1, count($results));
  273. $name = $results[0]->getName();
  274. $this->assertEquals('inside.txt.v', substr($name, 0, strlen('inside.txt.v')));
  275. // versions deleted
  276. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/');
  277. $this->assertEquals(0, count($results));
  278. }
  279. /**
  280. * Test that deleted versions properly land in the trashbin when deleting as share recipient.
  281. */
  282. public function testDeleteVersionsOfFileAsRecipient() {
  283. $this->userView->mkdir('share');
  284. // trigger a version (multiple would not work because of the expire logic)
  285. $this->userView->file_put_contents('share/test.txt', 'v1');
  286. $this->userView->file_put_contents('share/test.txt', 'v2');
  287. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/');
  288. $this->assertEquals(1, count($results));
  289. $recipientUser = $this->getUniqueId('recipient_');
  290. \OC::$server->getUserManager()->createUser($recipientUser, $recipientUser);
  291. $node = \OC::$server->getUserFolder($this->user)->get('share');
  292. $share = \OC::$server->getShareManager()->newShare();
  293. $share->setNode($node)
  294. ->setShareType(IShare::TYPE_USER)
  295. ->setSharedBy($this->user)
  296. ->setSharedWith($recipientUser)
  297. ->setPermissions(\OCP\Constants::PERMISSION_ALL);
  298. $share = \OC::$server->getShareManager()->createShare($share);
  299. \OC::$server->getShareManager()->acceptShare($share, $recipientUser);
  300. $this->loginAsUser($recipientUser);
  301. // delete as recipient
  302. $recipientView = new \OC\Files\View('/' . $recipientUser . '/files');
  303. $recipientView->unlink('share/test.txt');
  304. // rescan trash storage for both users
  305. [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin');
  306. $rootStorage->getScanner()->scan('');
  307. // check if versions are in trashbin for both users
  308. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
  309. $this->assertEquals(1, count($results), 'Versions in owner\'s trashbin');
  310. $name = $results[0]->getName();
  311. $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
  312. $results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions');
  313. $this->assertEquals(1, count($results), 'Versions in recipient\'s trashbin');
  314. $name = $results[0]->getName();
  315. $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
  316. // versions deleted
  317. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/');
  318. $this->assertEquals(0, count($results));
  319. }
  320. /**
  321. * Test that deleted versions properly land in the trashbin when deleting as share recipient.
  322. */
  323. public function testDeleteVersionsOfFolderAsRecipient() {
  324. $this->userView->mkdir('share');
  325. $this->userView->mkdir('share/folder');
  326. // trigger a version (multiple would not work because of the expire logic)
  327. $this->userView->file_put_contents('share/folder/test.txt', 'v1');
  328. $this->userView->file_put_contents('share/folder/test.txt', 'v2');
  329. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/folder/');
  330. $this->assertEquals(1, count($results));
  331. $recipientUser = $this->getUniqueId('recipient_');
  332. \OC::$server->getUserManager()->createUser($recipientUser, $recipientUser);
  333. $node = \OC::$server->getUserFolder($this->user)->get('share');
  334. $share = \OC::$server->getShareManager()->newShare();
  335. $share->setNode($node)
  336. ->setShareType(IShare::TYPE_USER)
  337. ->setSharedBy($this->user)
  338. ->setSharedWith($recipientUser)
  339. ->setPermissions(\OCP\Constants::PERMISSION_ALL);
  340. $share = \OC::$server->getShareManager()->createShare($share);
  341. \OC::$server->getShareManager()->acceptShare($share, $recipientUser);
  342. $this->loginAsUser($recipientUser);
  343. // delete as recipient
  344. $recipientView = new \OC\Files\View('/' . $recipientUser . '/files');
  345. $recipientView->rmdir('share/folder');
  346. // rescan trash storage
  347. [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin');
  348. $rootStorage->getScanner()->scan('');
  349. // check if versions are in trashbin for owner
  350. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
  351. $this->assertEquals(1, count($results));
  352. $name = $results[0]->getName();
  353. $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d')));
  354. // check if file versions are in trashbin for owner
  355. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/');
  356. $this->assertEquals(1, count($results));
  357. $name = $results[0]->getName();
  358. $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
  359. // check if versions are in trashbin for recipient
  360. $results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions');
  361. $this->assertEquals(1, count($results));
  362. $name = $results[0]->getName();
  363. $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d')));
  364. // check if file versions are in trashbin for recipient
  365. $results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions/' . $name . '/');
  366. $this->assertEquals(1, count($results));
  367. $name = $results[0]->getName();
  368. $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
  369. // versions deleted
  370. $results = $this->rootView->getDirectoryContent($recipientUser . '/files_versions/share/folder/');
  371. $this->assertEquals(0, count($results));
  372. }
  373. /**
  374. * Test that versions are not auto-trashed when moving a file between
  375. * storages. This is because rename() between storages would call
  376. * unlink() which should NOT trigger the version deletion logic.
  377. */
  378. public function testKeepFileAndVersionsWhenMovingFileBetweenStorages() {
  379. $storage2 = new Temporary([]);
  380. \OC\Files\Filesystem::mount($storage2, [], $this->user . '/files/substorage');
  381. // trigger a version (multiple would not work because of the expire logic)
  382. $this->userView->file_put_contents('test.txt', 'v1');
  383. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
  384. $this->assertEquals(0, count($results));
  385. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/');
  386. $this->assertEquals(1, count($results));
  387. // move to another storage
  388. $this->userView->rename('test.txt', 'substorage/test.txt');
  389. $this->assertTrue($this->userView->file_exists('substorage/test.txt'));
  390. // rescan trash storage
  391. [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin');
  392. $rootStorage->getScanner()->scan('');
  393. // versions were moved too
  394. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/substorage');
  395. $this->assertEquals(1, count($results));
  396. // check that nothing got trashed by the rename's unlink() call
  397. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
  398. $this->assertEquals(0, count($results));
  399. // check that versions were moved and not trashed
  400. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/');
  401. $this->assertEquals(0, count($results));
  402. }
  403. /**
  404. * Test that versions are not auto-trashed when moving a file between
  405. * storages. This is because rename() between storages would call
  406. * unlink() which should NOT trigger the version deletion logic.
  407. */
  408. public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() {
  409. $storage2 = new Temporary([]);
  410. \OC\Files\Filesystem::mount($storage2, [], $this->user . '/files/substorage');
  411. // trigger a version (multiple would not work because of the expire logic)
  412. $this->userView->file_put_contents('folder/inside.txt', 'v1');
  413. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
  414. $this->assertEquals(0, count($results));
  415. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/');
  416. $this->assertEquals(1, count($results));
  417. // move to another storage
  418. $this->userView->rename('folder', 'substorage/folder');
  419. $this->assertTrue($this->userView->file_exists('substorage/folder/inside.txt'));
  420. // rescan trash storage
  421. [$rootStorage,] = $this->rootView->resolvePath($this->user . '/files_trashbin');
  422. $rootStorage->getScanner()->scan('');
  423. // versions were moved too
  424. $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/substorage/folder/');
  425. $this->assertEquals(1, count($results));
  426. // check that nothing got trashed by the rename's unlink() call
  427. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
  428. $this->assertEquals(0, count($results));
  429. // check that versions were moved and not trashed
  430. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/');
  431. $this->assertEquals(0, count($results));
  432. }
  433. /**
  434. * Delete should fail if the source file can't be deleted.
  435. */
  436. public function testSingleStorageDeleteFileFail() {
  437. /**
  438. * @var \OC\Files\Storage\Temporary | \PHPUnit\Framework\MockObject\MockObject $storage
  439. */
  440. $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
  441. ->setConstructorArgs([[]])
  442. ->setMethods(['rename', 'unlink', 'moveFromStorage'])
  443. ->getMock();
  444. $storage->expects($this->any())
  445. ->method('rename')
  446. ->willReturn(false);
  447. $storage->expects($this->any())
  448. ->method('moveFromStorage')
  449. ->willReturn(false);
  450. $storage->expects($this->any())
  451. ->method('unlink')
  452. ->willReturn(false);
  453. $cache = $storage->getCache();
  454. Filesystem::mount($storage, [], '/' . $this->user);
  455. $storage->mkdir('files');
  456. $this->userView->file_put_contents('test.txt', 'foo');
  457. $this->assertTrue($storage->file_exists('files/test.txt'));
  458. $this->assertFalse($this->userView->unlink('test.txt'));
  459. $this->assertTrue($storage->file_exists('files/test.txt'));
  460. $this->assertTrue($cache->inCache('files/test.txt'));
  461. // file should not be in the trashbin
  462. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
  463. $this->assertEquals(0, count($results));
  464. }
  465. /**
  466. * Delete should fail if the source folder can't be deleted.
  467. */
  468. public function testSingleStorageDeleteFolderFail() {
  469. /**
  470. * @var \OC\Files\Storage\Temporary | \PHPUnit\Framework\MockObject\MockObject $storage
  471. */
  472. $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
  473. ->setConstructorArgs([[]])
  474. ->setMethods(['rename', 'unlink', 'rmdir'])
  475. ->getMock();
  476. $storage->expects($this->any())
  477. ->method('rmdir')
  478. ->willReturn(false);
  479. $cache = $storage->getCache();
  480. Filesystem::mount($storage, [], '/' . $this->user);
  481. $storage->mkdir('files');
  482. $this->userView->mkdir('folder');
  483. $this->userView->file_put_contents('folder/test.txt', 'foo');
  484. $this->assertTrue($storage->file_exists('files/folder/test.txt'));
  485. $this->assertFalse($this->userView->rmdir('files/folder'));
  486. $this->assertTrue($storage->file_exists('files/folder'));
  487. $this->assertTrue($storage->file_exists('files/folder/test.txt'));
  488. $this->assertTrue($cache->inCache('files/folder'));
  489. $this->assertTrue($cache->inCache('files/folder/test.txt'));
  490. // file should not be in the trashbin
  491. $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
  492. $this->assertEquals(0, count($results));
  493. }
  494. /**
  495. * @dataProvider dataTestShouldMoveToTrash
  496. */
  497. public function testShouldMoveToTrash($mountPoint, $path, $userExists, $appDisablesTrash, $expected) {
  498. $fileID = 1;
  499. $cache = $this->createMock(ICache::class);
  500. $cache->expects($this->any())->method('getId')->willReturn($fileID);
  501. $tmpStorage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
  502. ->disableOriginalConstructor()->getMock($cache);
  503. $tmpStorage->expects($this->any())->method('getCache')->willReturn($cache);
  504. $userManager = $this->getMockBuilder(IUserManager::class)
  505. ->disableOriginalConstructor()->getMock();
  506. $userManager->expects($this->any())
  507. ->method('userExists')->willReturn($userExists);
  508. $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
  509. $eventDispatcher = $this->createMock(IEventDispatcher::class);
  510. $rootFolder = $this->createMock(IRootFolder::class);
  511. $userFolder = $this->createMock(Folder::class);
  512. $node = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock();
  513. $trashManager = $this->createMock(ITrashManager::class);
  514. $event = $this->getMockBuilder(MoveToTrashEvent::class)->disableOriginalConstructor()->getMock();
  515. $event->expects($this->any())->method('shouldMoveToTrashBin')->willReturn(!$appDisablesTrash);
  516. $userFolder->expects($this->any())->method('getById')->with($fileID)->willReturn([$node]);
  517. $rootFolder->expects($this->any())->method('getUserFolder')->willReturn($userFolder);
  518. $storage = $this->getMockBuilder(Storage::class)
  519. ->setConstructorArgs(
  520. [
  521. ['mountPoint' => $mountPoint, 'storage' => $tmpStorage],
  522. $trashManager,
  523. $userManager,
  524. $logger,
  525. $eventDispatcher,
  526. $rootFolder
  527. ]
  528. )->setMethods(['createMoveToTrashEvent'])->getMock();
  529. $storage->expects($this->any())->method('createMoveToTrashEvent')->with($node)
  530. ->willReturn($event);
  531. $this->assertSame($expected,
  532. $this->invokePrivate($storage, 'shouldMoveToTrash', [$path])
  533. );
  534. }
  535. public function dataTestShouldMoveToTrash() {
  536. return [
  537. ['/schiesbn/', '/files/test.txt', true, false, true],
  538. ['/schiesbn/', '/files/test.txt', false, false, false],
  539. ['/schiesbn/', '/test.txt', true, false, false],
  540. ['/schiesbn/', '/test.txt', false, false, false],
  541. // other apps disables the trashbin
  542. ['/schiesbn/', '/files/test.txt', true, true, false],
  543. ['/schiesbn/', '/files/test.txt', false, true, false],
  544. ];
  545. }
  546. /**
  547. * Test that deleting a file doesn't error when nobody is logged in
  548. */
  549. public function testSingleStorageDeleteFileLoggedOut() {
  550. $this->logout();
  551. if (!$this->userView->file_exists('test.txt')) {
  552. $this->markTestSkipped('Skipping since the current home storage backend requires the user to logged in');
  553. } else {
  554. $this->userView->unlink('test.txt');
  555. $this->addToAssertionCount(1);
  556. }
  557. }
  558. public function testTrashbinCollision() {
  559. $this->userView->file_put_contents('test.txt', 'foo');
  560. $this->userView->file_put_contents('folder/test.txt', 'bar');
  561. $timeFactory = $this->createMock(ITimeFactory::class);
  562. $timeFactory->method('getTime')
  563. ->willReturn(1000);
  564. $lockingProvider = \OC::$server->getLockingProvider();
  565. $this->overwriteService(ITimeFactory::class, $timeFactory);
  566. $this->userView->unlink('test.txt');
  567. $this->assertTrue($this->rootView->file_exists('/' . $this->user . '/files_trashbin/files/test.txt.d1000'));
  568. /** @var \OC\Files\Storage\Storage $trashStorage */
  569. [$trashStorage, $trashInternalPath] = $this->rootView->resolvePath('/' . $this->user . '/files_trashbin/files/test.txt.d1000');
  570. /// simulate a concurrent delete
  571. $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  572. $this->userView->unlink('folder/test.txt');
  573. $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  574. $this->assertTrue($this->rootView->file_exists($this->user . '/files_trashbin/files/test.txt.d1001'));
  575. $this->assertEquals('foo', $this->rootView->file_get_contents($this->user . '/files_trashbin/files/test.txt.d1000'));
  576. $this->assertEquals('bar', $this->rootView->file_get_contents($this->user . '/files_trashbin/files/test.txt.d1001'));
  577. }
  578. public function testMoveFromStoragePreserveFileId() {
  579. if (!$this->userView->getMount('')->getStorage()->instanceOfStorage(Local::class)) {
  580. $this->markTestSkipped("Skipping on non-local users storage");
  581. }
  582. $this->userView->file_put_contents('test.txt', 'foo');
  583. $fileId = $this->userView->getFileInfo('test.txt')->getId();
  584. $externalStorage = new TemporaryNoCross([]);
  585. $externalStorage->getScanner()->scan('');
  586. Filesystem::mount($externalStorage, [], "/" . $this->user . "/files/storage");
  587. $this->assertTrue($this->userView->rename('test.txt', 'storage/test.txt'));
  588. $this->assertTrue($externalStorage->file_exists('test.txt'));
  589. $this->assertEquals($fileId, $this->userView->getFileInfo('storage/test.txt')->getId());
  590. }
  591. }