diff options
Diffstat (limited to 'tests/lib/Files')
45 files changed, 12279 insertions, 0 deletions
diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php new file mode 100644 index 00000000000..615bb32f62c --- /dev/null +++ b/tests/lib/Files/Cache/CacheTest.php @@ -0,0 +1,678 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + + +class LongId extends \OC\Files\Storage\Temporary { + public function getId() { + return 'long:' . str_repeat('foo', 50) . parent::getId(); + } +} + +/** + * Class CacheTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class CacheTest extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Temporary $storage ; + */ + protected $storage; + /** + * @var \OC\Files\Storage\Temporary $storage2 ; + */ + protected $storage2; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + protected $cache; + /** + * @var \OC\Files\Cache\Cache $cache2 + */ + protected $cache2; + + public function testGetNumericId() { + $this->assertNotNull($this->cache->getNumericStorageId()); + } + + public function testSimple() { + $file1 = 'foo'; + $file2 = 'foo/bar'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + $data2 = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + + $this->assertFalse($this->cache->inCache($file1)); + $this->assertEquals($this->cache->get($file1), null); + + $id1 = $this->cache->put($file1, $data1); + $this->assertTrue($this->cache->inCache($file1)); + $cacheData1 = $this->cache->get($file1); + foreach ($data1 as $key => $value) { + $this->assertEquals($value, $cacheData1[$key]); + } + $this->assertEquals($cacheData1['mimepart'], 'foo'); + $this->assertEquals($cacheData1['fileid'], $id1); + $this->assertEquals($id1, $this->cache->getId($file1)); + + $this->assertFalse($this->cache->inCache($file2)); + $id2 = $this->cache->put($file2, $data2); + $this->assertTrue($this->cache->inCache($file2)); + $cacheData2 = $this->cache->get($file2); + foreach ($data2 as $key => $value) { + $this->assertEquals($value, $cacheData2[$key]); + } + $this->assertEquals($cacheData1['fileid'], $cacheData2['parent']); + $this->assertEquals($cacheData2['fileid'], $id2); + $this->assertEquals($id2, $this->cache->getId($file2)); + $this->assertEquals($id1, $this->cache->getParentId($file2)); + + $newSize = 1050; + $newId2 = $this->cache->put($file2, array('size' => $newSize)); + $cacheData2 = $this->cache->get($file2); + $this->assertEquals($newId2, $id2); + $this->assertEquals($cacheData2['size'], $newSize); + $this->assertEquals($cacheData1, $this->cache->get($file1)); + + $this->cache->remove($file2); + $this->assertFalse($this->cache->inCache($file2)); + $this->assertEquals($this->cache->get($file2), null); + $this->assertTrue($this->cache->inCache($file1)); + + $this->assertEquals($cacheData1, $this->cache->get($id1)); + } + + public function testPartial() { + $file1 = 'foo'; + + $this->cache->put($file1, array('size' => 10)); + $this->assertEquals(array('size' => 10), $this->cache->get($file1)); + + $this->cache->put($file1, array('mtime' => 15)); + $this->assertEquals(array('size' => 10, 'mtime' => 15), $this->cache->get($file1)); + + $this->cache->put($file1, array('size' => 12)); + $this->assertEquals(array('size' => 12, 'mtime' => 15), $this->cache->get($file1)); + } + + /** + * @dataProvider folderDataProvider + */ + public function testFolder($folder) { + $file2 = $folder.'/bar'; + $file3 = $folder.'/foo'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + $fileData = array(); + $fileData['bar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); + + $this->cache->put($folder, $data1); + $this->cache->put($file2, $fileData['bar']); + $this->cache->put($file3, $fileData['foo']); + + $content = $this->cache->getFolderContents($folder); + $this->assertEquals(count($content), 2); + foreach ($content as $cachedData) { + $data = $fileData[$cachedData['name']]; + foreach ($data as $name => $value) { + $this->assertEquals($value, $cachedData[$name]); + } + } + + $file4 = $folder.'/unkownSize'; + $fileData['unkownSize'] = array('size' => -1, 'mtime' => 25, 'mimetype' => 'foo/file'); + $this->cache->put($file4, $fileData['unkownSize']); + + $this->assertEquals(-1, $this->cache->calculateFolderSize($folder)); + + $fileData['unkownSize'] = array('size' => 5, 'mtime' => 25, 'mimetype' => 'foo/file'); + $this->cache->put($file4, $fileData['unkownSize']); + + $this->assertEquals(1025, $this->cache->calculateFolderSize($folder)); + + $this->cache->remove($file2); + $this->cache->remove($file3); + $this->cache->remove($file4); + $this->assertEquals(0, $this->cache->calculateFolderSize($folder)); + + $this->cache->remove($folder); + $this->assertFalse($this->cache->inCache($folder.'/foo')); + $this->assertFalse($this->cache->inCache($folder.'/bar')); + } + + public function testRemoveRecursive() { + $folderData = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + $fileData = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'text/plain'); + $folders = ['folder', 'folder/subfolder', 'folder/sub2', 'folder/sub2/sub3']; + $files = ['folder/foo.txt', 'folder/bar.txt', 'folder/subfolder/asd.txt', 'folder/sub2/qwerty.txt', 'folder/sub2/sub3/foo.txt']; + + foreach($folders as $folder){ + $this->cache->put($folder, $folderData); + } + foreach ($files as $file) { + $this->cache->put($file, $fileData); + } + + $this->cache->remove('folder'); + foreach ($files as $file) { + $this->assertFalse($this->cache->inCache($file)); + } + } + + public function folderDataProvider() { + + return array( + array('folder'), + // that was too easy, try something harder + array('☺, WHITE SMILING FACE, UTF-8 hex E298BA'), + // what about 4 byte utf-8 + array('😐, NEUTRAL_FACE, UTF-8 hex F09F9890'), + // now the crazy stuff + array(', UNASSIGNED PRIVATE USE, UTF-8 hex EF9890'), + // and my favorite + array('w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ ̀́w͘͢ḩ̵a҉̡͢t ̧̕h́o̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt́͏ ̴̷͠ò̵̶f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝') + ); + } + + public function testEncryptedFolder() { + $file1 = 'folder'; + $file2 = 'folder/bar'; + $file3 = 'folder/foo'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + $fileData = array(); + $fileData['bar'] = array('size' => 1000, 'encrypted' => 1, 'mtime' => 20, 'mimetype' => 'foo/file'); + $fileData['foo'] = array('size' => 20, 'encrypted' => 1, 'mtime' => 25, 'mimetype' => 'foo/file'); + + $this->cache->put($file1, $data1); + $this->cache->put($file2, $fileData['bar']); + $this->cache->put($file3, $fileData['foo']); + + $content = $this->cache->getFolderContents($file1); + $this->assertEquals(count($content), 2); + foreach ($content as $cachedData) { + $data = $fileData[$cachedData['name']]; + } + + $file4 = 'folder/unkownSize'; + $fileData['unkownSize'] = array('size' => -1, 'mtime' => 25, 'mimetype' => 'foo/file'); + $this->cache->put($file4, $fileData['unkownSize']); + + $this->assertEquals(-1, $this->cache->calculateFolderSize($file1)); + + $fileData['unkownSize'] = array('size' => 5, 'mtime' => 25, 'mimetype' => 'foo/file'); + $this->cache->put($file4, $fileData['unkownSize']); + + $this->assertEquals(1025, $this->cache->calculateFolderSize($file1)); + // direct cache entry retrieval returns the original values + $entry = $this->cache->get($file1); + $this->assertEquals(1025, $entry['size']); + + $this->cache->remove($file2); + $this->cache->remove($file3); + $this->cache->remove($file4); + $this->assertEquals(0, $this->cache->calculateFolderSize($file1)); + + $this->cache->remove('folder'); + $this->assertFalse($this->cache->inCache('folder/foo')); + $this->assertFalse($this->cache->inCache('folder/bar')); + } + + public function testRootFolderSizeForNonHomeStorage() { + $dir1 = 'knownsize'; + $dir2 = 'unknownsize'; + $fileData = array(); + $fileData[''] = array('size' => -1, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData[$dir1] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData[$dir2] = array('size' => -1, 'mtime' => 25, 'mimetype' => 'httpd/unix-directory'); + + $this->cache->put('', $fileData['']); + $this->cache->put($dir1, $fileData[$dir1]); + $this->cache->put($dir2, $fileData[$dir2]); + + $this->assertTrue($this->cache->inCache($dir1)); + $this->assertTrue($this->cache->inCache($dir2)); + + // check that root size ignored the unknown sizes + $this->assertEquals(-1, $this->cache->calculateFolderSize('')); + + // clean up + $this->cache->remove(''); + $this->cache->remove($dir1); + $this->cache->remove($dir2); + + $this->assertFalse($this->cache->inCache($dir1)); + $this->assertFalse($this->cache->inCache($dir2)); + } + + function testStatus() { + $this->assertEquals(\OC\Files\Cache\Cache::NOT_FOUND, $this->cache->getStatus('foo')); + $this->cache->put('foo', array('size' => -1)); + $this->assertEquals(\OC\Files\Cache\Cache::PARTIAL, $this->cache->getStatus('foo')); + $this->cache->put('foo', array('size' => -1, 'mtime' => 20, 'mimetype' => 'foo/file')); + $this->assertEquals(\OC\Files\Cache\Cache::SHALLOW, $this->cache->getStatus('foo')); + $this->cache->put('foo', array('size' => 10)); + $this->assertEquals(\OC\Files\Cache\Cache::COMPLETE, $this->cache->getStatus('foo')); + } + + public function putWithAllKindOfQuotesData() { + return [ + ['`backtick`'], + ['´forward´'], + ['\'single\''], + ]; + } + + /** + * @dataProvider putWithAllKindOfQuotesData + * @param $fileName + */ + public function testPutWithAllKindOfQuotes($fileName) { + + $this->assertEquals(\OC\Files\Cache\Cache::NOT_FOUND, $this->cache->get($fileName)); + $this->cache->put($fileName, array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file', 'etag' => $fileName)); + + $cacheEntry = $this->cache->get($fileName); + $this->assertEquals($fileName, $cacheEntry['etag']); + $this->assertEquals($fileName, $cacheEntry['path']); + } + + function testSearch() { + $file1 = 'folder'; + $file2 = 'folder/foobar'; + $file3 = 'folder/foo'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + $fileData = array(); + $fileData['foobar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); + + $this->cache->put($file1, $data1); + $this->cache->put($file2, $fileData['foobar']); + $this->cache->put($file3, $fileData['foo']); + + $this->assertEquals(2, count($this->cache->search('%foo%'))); + $this->assertEquals(1, count($this->cache->search('foo'))); + $this->assertEquals(1, count($this->cache->search('%folder%'))); + $this->assertEquals(1, count($this->cache->search('folder%'))); + $this->assertEquals(3, count($this->cache->search('%'))); + + // case insensitive search should match the same files + $this->assertEquals(2, count($this->cache->search('%Foo%'))); + $this->assertEquals(1, count($this->cache->search('Foo'))); + $this->assertEquals(1, count($this->cache->search('%Folder%'))); + $this->assertEquals(1, count($this->cache->search('Folder%'))); + + $this->assertEquals(3, count($this->cache->searchByMime('foo'))); + $this->assertEquals(2, count($this->cache->searchByMime('foo/file'))); + } + + function testSearchByTag() { + $userId = $this->getUniqueId('user'); + \OC::$server->getUserManager()->createUser($userId, $userId); + $this->loginAsUser($userId); + $user = new \OC\User\User($userId, null); + + $file1 = 'folder'; + $file2 = 'folder/foobar'; + $file3 = 'folder/foo'; + $file4 = 'folder/foo2'; + $file5 = 'folder/foo3'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + $fileData = array(); + $fileData['foobar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); + $fileData['foo2'] = array('size' => 25, 'mtime' => 28, 'mimetype' => 'foo/file'); + $fileData['foo3'] = array('size' => 88, 'mtime' => 34, 'mimetype' => 'foo/file'); + + $id1 = $this->cache->put($file1, $data1); + $id2 = $this->cache->put($file2, $fileData['foobar']); + $id3 = $this->cache->put($file3, $fileData['foo']); + $id4 = $this->cache->put($file4, $fileData['foo2']); + $id5 = $this->cache->put($file5, $fileData['foo3']); + + $tagManager = \OC::$server->getTagManager()->load('files', null, null, $userId); + $this->assertTrue($tagManager->tagAs($id1, 'tag1')); + $this->assertTrue($tagManager->tagAs($id1, 'tag2')); + $this->assertTrue($tagManager->tagAs($id2, 'tag2')); + $this->assertTrue($tagManager->tagAs($id3, 'tag1')); + $this->assertTrue($tagManager->tagAs($id4, 'tag2')); + + // use tag name + $results = $this->cache->searchByTag('tag1', $userId); + + $this->assertEquals(2, count($results)); + + usort($results, function($value1, $value2) { return $value1['name'] >= $value2['name']; }); + + $this->assertEquals('folder', $results[0]['name']); + $this->assertEquals('foo', $results[1]['name']); + + // use tag id + $tags = $tagManager->getTagsForUser($userId); + $this->assertNotEmpty($tags); + $tags = array_filter($tags, function($tag) { return $tag->getName() === 'tag2'; }); + $results = $this->cache->searchByTag(current($tags)->getId(), $userId); + $this->assertEquals(3, count($results)); + + usort($results, function($value1, $value2) { return $value1['name'] >= $value2['name']; }); + + $this->assertEquals('folder', $results[0]['name']); + $this->assertEquals('foo2', $results[1]['name']); + $this->assertEquals('foobar', $results[2]['name']); + + $tagManager->delete('tag1'); + $tagManager->delete('tag2'); + + $this->logout(); + $user = \OC::$server->getUserManager()->get($userId); + if ($user !== null) { $user->delete(); } + } + + function testMove() { + $file1 = 'folder'; + $file2 = 'folder/bar'; + $file3 = 'folder/foo'; + $file4 = 'folder/foo/1'; + $file5 = 'folder/foo/2'; + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar'); + $folderData = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + $this->cache->put($file1, $folderData); + $this->cache->put($file2, $folderData); + $this->cache->put($file3, $folderData); + $this->cache->put($file4, $data); + $this->cache->put($file5, $data); + + /* simulate a second user with a different storage id but the same folder structure */ + $this->cache2->put($file1, $folderData); + $this->cache2->put($file2, $folderData); + $this->cache2->put($file3, $folderData); + $this->cache2->put($file4, $data); + $this->cache2->put($file5, $data); + + $this->cache->move('folder/foo', 'folder/foobar'); + + $this->assertFalse($this->cache->inCache('folder/foo')); + $this->assertFalse($this->cache->inCache('folder/foo/1')); + $this->assertFalse($this->cache->inCache('folder/foo/2')); + + $this->assertTrue($this->cache->inCache('folder/bar')); + $this->assertTrue($this->cache->inCache('folder/foobar')); + $this->assertTrue($this->cache->inCache('folder/foobar/1')); + $this->assertTrue($this->cache->inCache('folder/foobar/2')); + + /* the folder structure of the second user must not change! */ + $this->assertTrue($this->cache2->inCache('folder/bar')); + $this->assertTrue($this->cache2->inCache('folder/foo')); + $this->assertTrue($this->cache2->inCache('folder/foo/1')); + $this->assertTrue($this->cache2->inCache('folder/foo/2')); + + $this->assertFalse($this->cache2->inCache('folder/foobar')); + $this->assertFalse($this->cache2->inCache('folder/foobar/1')); + $this->assertFalse($this->cache2->inCache('folder/foobar/2')); + } + + function testGetIncomplete() { + $file1 = 'folder1'; + $file2 = 'folder2'; + $file3 = 'folder3'; + $file4 = 'folder4'; + $data = array('size' => 10, 'mtime' => 50, 'mimetype' => 'foo/bar'); + + $this->cache->put($file1, $data); + $data['size'] = -1; + $this->cache->put($file2, $data); + $this->cache->put($file3, $data); + $data['size'] = 12; + $this->cache->put($file4, $data); + + $this->assertEquals($file3, $this->cache->getIncomplete()); + } + + function testNonExisting() { + $this->assertFalse($this->cache->get('foo.txt')); + $this->assertEquals(array(), $this->cache->getFolderContents('foo')); + } + + function testGetById() { + $storageId = $this->storage->getId(); + $data = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $id = $this->cache->put('foo', $data); + + if (strlen($storageId) > 64) { + $storageId = md5($storageId); + } + $this->assertEquals(array($storageId, 'foo'), \OC\Files\Cache\Cache::getById($id)); + } + + function testStorageMTime() { + $data = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $this->cache->put('foo', $data); + $cachedData = $this->cache->get('foo'); + $this->assertEquals($data['mtime'], $cachedData['storage_mtime']); //if no storage_mtime is saved, mtime should be used + + $this->cache->put('foo', array('storage_mtime' => 30)); //when setting storage_mtime, mtime is also set + $cachedData = $this->cache->get('foo'); + $this->assertEquals(30, $cachedData['storage_mtime']); + $this->assertEquals(30, $cachedData['mtime']); + + $this->cache->put('foo', array('mtime' => 25)); //setting mtime does not change storage_mtime + $cachedData = $this->cache->get('foo'); + $this->assertEquals(30, $cachedData['storage_mtime']); + $this->assertEquals(25, $cachedData['mtime']); + } + + function testLongId() { + $storage = new LongId(array()); + $cache = $storage->getCache(); + $storageId = $storage->getId(); + $data = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $id = $cache->put('foo', $data); + $this->assertEquals(array(md5($storageId), 'foo'), \OC\Files\Cache\Cache::getById($id)); + } + + /** + * this test show the bug resulting if we have no normalizer installed + */ + public function testWithoutNormalizer() { + // folder name "Schön" with U+00F6 (normalized) + $folderWith00F6 = "\x53\x63\x68\xc3\xb6\x6e"; + + // folder name "Schön" with U+0308 (un-normalized) + $folderWith0308 = "\x53\x63\x68\x6f\xcc\x88\x6e"; + + /** + * @var \OC\Files\Cache\Cache | \PHPUnit_Framework_MockObject_MockObject $cacheMock + */ + $cacheMock = $this->getMock('\OC\Files\Cache\Cache', array('normalize'), array($this->storage), '', true); + + $cacheMock->expects($this->any()) + ->method('normalize') + ->will($this->returnArgument(0)); + + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + // put root folder + $this->assertFalse($cacheMock->get('folder')); + $this->assertGreaterThan(0, $cacheMock->put('folder', $data)); + + // put un-normalized folder + $this->assertFalse($cacheMock->get('folder/' . $folderWith0308)); + $this->assertGreaterThan(0, $cacheMock->put('folder/' . $folderWith0308, $data)); + + // get un-normalized folder by name + $unNormalizedFolderName = $cacheMock->get('folder/' . $folderWith0308); + + // check if database layer normalized the folder name (this should not happen) + $this->assertEquals($folderWith0308, $unNormalizedFolderName['name']); + + // put normalized folder + $this->assertFalse($cacheMock->get('folder/' . $folderWith00F6)); + $this->assertGreaterThan(0, $cacheMock->put('folder/' . $folderWith00F6, $data)); + + // this is our bug, we have two different hashes with the same name (Schön) + $this->assertEquals(2, count($cacheMock->getFolderContents('folder'))); + } + + /** + * this test shows that there is no bug if we use the normalizer + */ + public function testWithNormalizer() { + + if (!class_exists('Patchwork\PHP\Shim\Normalizer')) { + $this->markTestSkipped('The 3rdparty Normalizer extension is not available.'); + return; + } + + // folder name "Schön" with U+00F6 (normalized) + $folderWith00F6 = "\x53\x63\x68\xc3\xb6\x6e"; + + // folder name "Schön" with U+0308 (un-normalized) + $folderWith0308 = "\x53\x63\x68\x6f\xcc\x88\x6e"; + + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + // put root folder + $this->assertFalse($this->cache->get('folder')); + $this->assertGreaterThan(0, $this->cache->put('folder', $data)); + + // put un-normalized folder + $this->assertFalse($this->cache->get('folder/' . $folderWith0308)); + $this->assertGreaterThan(0, $this->cache->put('folder/' . $folderWith0308, $data)); + + // get un-normalized folder by name + $unNormalizedFolderName = $this->cache->get('folder/' . $folderWith0308); + + // check if folder name was normalized + $this->assertEquals($folderWith00F6, $unNormalizedFolderName['name']); + + // put normalized folder + $this->assertInstanceOf('\OCP\Files\Cache\ICacheEntry', $this->cache->get('folder/' . $folderWith00F6)); + $this->assertGreaterThan(0, $this->cache->put('folder/' . $folderWith00F6, $data)); + + // at this point we should have only one folder named "Schön" + $this->assertEquals(1, count($this->cache->getFolderContents('folder'))); + } + + function bogusPathNamesProvider() { + return array( + array('/bogus.txt', 'bogus.txt'), + array('//bogus.txt', 'bogus.txt'), + array('bogus/', 'bogus'), + array('bogus//', 'bogus'), + ); + } + + /** + * Test bogus paths with leading or doubled slashes + * + * @dataProvider bogusPathNamesProvider + */ + public function testBogusPaths($bogusPath, $fixedBogusPath) { + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + // put root folder + $this->assertFalse($this->cache->get('')); + $parentId = $this->cache->put('', $data); + $this->assertGreaterThan(0, $parentId); + + $this->assertGreaterThan(0, $this->cache->put($bogusPath, $data)); + + $newData = $this->cache->get($fixedBogusPath); + $this->assertNotFalse($newData); + + $this->assertEquals($fixedBogusPath, $newData['path']); + // parent is the correct one, resolved properly (they used to not be) + $this->assertEquals($parentId, $newData['parent']); + + $newDataFromBogus = $this->cache->get($bogusPath); + // same entry + $this->assertEquals($newData, $newDataFromBogus); + } + + public function testNoReuseOfFileId() { + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain'); + $this->cache->put('somefile.txt', $data1); + $info = $this->cache->get('somefile.txt'); + $fileId = $info['fileid']; + $this->cache->remove('somefile.txt'); + $data2 = array('size' => 200, 'mtime' => 100, 'mimetype' => 'text/plain'); + $this->cache->put('anotherfile.txt', $data2); + $info2 = $this->cache->get('anotherfile.txt'); + $fileId2 = $info2['fileid']; + $this->assertNotEquals($fileId, $fileId2); + } + + public function escapingProvider() { + return [ + ['foo'], + ['o%'], + ['oth_r'], + ]; + } + + /** + * @param string $name + * @dataProvider escapingProvider + */ + public function testEscaping($name) { + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain'); + $this->cache->put($name, $data); + $this->assertTrue($this->cache->inCache($name)); + $retrievedData = $this->cache->get($name); + foreach ($data as $key => $value) { + $this->assertEquals($value, $retrievedData[$key]); + } + $this->cache->move($name, $name . 'asd'); + $this->assertFalse($this->cache->inCache($name)); + $this->assertTrue($this->cache->inCache($name . 'asd')); + $this->cache->remove($name . 'asd'); + $this->assertFalse($this->cache->inCache($name . 'asd')); + $folderData = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + $this->cache->put($name, $folderData); + $this->cache->put('other', $folderData); + $childs = ['asd', 'bar', 'foo', 'sub/folder']; + $this->cache->put($name . '/sub', $folderData); + $this->cache->put('other/sub', $folderData); + foreach ($childs as $child) { + $this->cache->put($name . '/' . $child, $data); + $this->cache->put('other/' . $child, $data); + $this->assertTrue($this->cache->inCache($name . '/' . $child)); + } + $this->cache->move($name, $name . 'asd'); + foreach ($childs as $child) { + $this->assertTrue($this->cache->inCache($name . 'asd/' . $child)); + $this->assertTrue($this->cache->inCache('other/' . $child)); + } + foreach ($childs as $child) { + $this->cache->remove($name . 'asd/' . $child); + $this->assertFalse($this->cache->inCache($name . 'asd/' . $child)); + $this->assertTrue($this->cache->inCache('other/' . $child)); + } + } + + protected function tearDown() { + if ($this->cache) { + $this->cache->clear(); + } + + parent::tearDown(); + } + + protected function setUp() { + parent::setUp(); + + $this->storage = new \OC\Files\Storage\Temporary(array()); + $this->storage2 = new \OC\Files\Storage\Temporary(array()); + $this->cache = new \OC\Files\Cache\Cache($this->storage); + $this->cache2 = new \OC\Files\Cache\Cache($this->storage2); + } +} diff --git a/tests/lib/Files/Cache/HomeCacheTest.php b/tests/lib/Files/Cache/HomeCacheTest.php new file mode 100644 index 00000000000..a144b8cabb8 --- /dev/null +++ b/tests/lib/Files/Cache/HomeCacheTest.php @@ -0,0 +1,139 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +class DummyUser extends \OC\User\User { + /** + * @var string $home + */ + private $home; + + /** + * @var string $uid + */ + private $uid; + + /** + * @param string $uid + * @param string $home + */ + public function __construct($uid, $home) { + $this->home = $home; + $this->uid = $uid; + } + + /** + * @return string + */ + public function getHome() { + return $this->home; + } + + /** + * @return string + */ + public function getUID() { + return $this->uid; + } +} + +/** + * Class HomeCacheTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class HomeCacheTest extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Home $storage + */ + private $storage; + + /** + * @var \OC\Files\Cache\HomeCache $cache + */ + private $cache; + + /** + * @var \OC\User\User $user + */ + private $user; + + protected function setUp() { + parent::setUp(); + + $this->user = new DummyUser('foo', \OC::$server->getTempManager()->getTemporaryFolder()); + $this->storage = new \OC\Files\Storage\Home(array('user' => $this->user)); + $this->cache = $this->storage->getCache(); + } + + /** + * Tests that the root and files folder size calculation ignores the subdirs + * that have an unknown size. This makes sure that quota calculation still + * works as it's based on the "files" folder size. + */ + public function testRootFolderSizeIgnoresUnknownUpdate() { + $dir1 = 'files/knownsize'; + $dir2 = 'files/unknownsize'; + $fileData = array(); + $fileData[''] = array('size' => -1, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData['files'] = array('size' => -1, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData[$dir1] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData[$dir2] = array('size' => -1, 'mtime' => 25, 'mimetype' => 'httpd/unix-directory'); + + $this->cache->put('', $fileData['']); + $this->cache->put('files', $fileData['files']); + $this->cache->put($dir1, $fileData[$dir1]); + $this->cache->put($dir2, $fileData[$dir2]); + + $this->assertTrue($this->cache->inCache('files')); + $this->assertTrue($this->cache->inCache($dir1)); + $this->assertTrue($this->cache->inCache($dir2)); + + // check that files and root size ignored the unknown sizes + $this->assertEquals(1000, $this->cache->calculateFolderSize('files')); + + // clean up + $this->cache->remove(''); + $this->cache->remove('files'); + $this->cache->remove($dir1); + $this->cache->remove($dir2); + + $this->assertFalse($this->cache->inCache('files')); + $this->assertFalse($this->cache->inCache($dir1)); + $this->assertFalse($this->cache->inCache($dir2)); + } + + public function testRootFolderSizeIsFilesSize() { + $dir1 = 'files'; + $afile = 'test.txt'; + $fileData = array(); + $fileData[''] = array('size' => 1500, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData[$dir1] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'httpd/unix-directory'); + $fileData[$afile] = array('size' => 500, 'mtime' => 20); + + $this->cache->put('', $fileData['']); + $this->cache->put($dir1, $fileData[$dir1]); + + $this->assertTrue($this->cache->inCache($dir1)); + + // check that root size ignored the unknown sizes + $data = $this->cache->get('files'); + $this->assertEquals(1000, $data['size']); + $data = $this->cache->get(''); + $this->assertEquals(1000, $data['size']); + + // clean up + $this->cache->remove(''); + $this->cache->remove($dir1); + + $this->assertFalse($this->cache->inCache($dir1)); + } +} diff --git a/tests/lib/Files/Cache/MoveFromCacheTraitTest.php b/tests/lib/Files/Cache/MoveFromCacheTraitTest.php new file mode 100644 index 00000000000..3d4a55c0722 --- /dev/null +++ b/tests/lib/Files/Cache/MoveFromCacheTraitTest.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright (c) 2016 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use OC\Files\Cache\MoveFromCacheTrait; + +class FallBackCrossCacheMoveCache extends \OC\Files\Cache\Cache { + use MoveFromCacheTrait; +} + +/** + * Class MoveFromCacheTraitTest + * + * @group DB + */ +class MoveFromCacheTraitTest extends CacheTest { + protected function setUp() { + parent::setUp(); + + $this->storage = new \OC\Files\Storage\Temporary(array()); + $this->storage2 = new \OC\Files\Storage\Temporary(array()); + $this->cache = new FallBackCrossCacheMoveCache($this->storage); + $this->cache2 = new FallBackCrossCacheMoveCache($this->storage2); + } +} diff --git a/tests/lib/Files/Cache/ScannerTest.php b/tests/lib/Files/Cache/ScannerTest.php new file mode 100644 index 00000000000..4a93f3ee014 --- /dev/null +++ b/tests/lib/Files/Cache/ScannerTest.php @@ -0,0 +1,319 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; +use OC\Files\Cache\CacheEntry; + +/** + * Class ScannerTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class ScannerTest extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var \OC\Files\Cache\Scanner $scanner + */ + private $scanner; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->storage = new \OC\Files\Storage\Temporary(array()); + $this->scanner = new \OC\Files\Cache\Scanner($this->storage); + $this->cache = new \OC\Files\Cache\Cache($this->storage); + } + + protected function tearDown() { + if ($this->cache) { + $this->cache->clear(); + } + + parent::tearDown(); + } + + function testFile() { + $data = "dummy file data\n"; + $this->storage->file_put_contents('foo.txt', $data); + $this->scanner->scanFile('foo.txt'); + + $this->assertEquals($this->cache->inCache('foo.txt'), true); + $cachedData = $this->cache->get('foo.txt'); + $this->assertEquals($cachedData['size'], strlen($data)); + $this->assertEquals($cachedData['mimetype'], 'text/plain'); + $this->assertNotEquals($cachedData['parent'], -1); //parent folders should be scanned automatically + + $data = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->file_put_contents('foo.png', $data); + $this->scanner->scanFile('foo.png'); + + $this->assertEquals($this->cache->inCache('foo.png'), true); + $cachedData = $this->cache->get('foo.png'); + $this->assertEquals($cachedData['size'], strlen($data)); + $this->assertEquals($cachedData['mimetype'], 'image/png'); + } + + private function fillTestFolders() { + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->mkdir('folder'); + $this->storage->file_put_contents('foo.txt', $textData); + $this->storage->file_put_contents('foo.png', $imgData); + $this->storage->file_put_contents('folder/bar.txt', $textData); + } + + function testFolder() { + $this->fillTestFolders(); + + $this->scanner->scan(''); + $this->assertEquals($this->cache->inCache(''), true); + $this->assertEquals($this->cache->inCache('foo.txt'), true); + $this->assertEquals($this->cache->inCache('foo.png'), true); + $this->assertEquals($this->cache->inCache('folder'), true); + $this->assertEquals($this->cache->inCache('folder/bar.txt'), true); + + $cachedDataText = $this->cache->get('foo.txt'); + $cachedDataText2 = $this->cache->get('foo.txt'); + $cachedDataImage = $this->cache->get('foo.png'); + $cachedDataFolder = $this->cache->get(''); + $cachedDataFolder2 = $this->cache->get('folder'); + + $this->assertEquals($cachedDataImage['parent'], $cachedDataText['parent']); + $this->assertEquals($cachedDataFolder['fileid'], $cachedDataImage['parent']); + $this->assertEquals($cachedDataFolder['size'], $cachedDataImage['size'] + $cachedDataText['size'] + $cachedDataText2['size']); + $this->assertEquals($cachedDataFolder2['size'], $cachedDataText2['size']); + } + + function testShallow() { + $this->fillTestFolders(); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $this->assertEquals($this->cache->inCache(''), true); + $this->assertEquals($this->cache->inCache('foo.txt'), true); + $this->assertEquals($this->cache->inCache('foo.png'), true); + $this->assertEquals($this->cache->inCache('folder'), true); + $this->assertEquals($this->cache->inCache('folder/bar.txt'), false); + + $cachedDataFolder = $this->cache->get(''); + $cachedDataFolder2 = $this->cache->get('folder'); + + $this->assertEquals(-1, $cachedDataFolder['size']); + $this->assertEquals(-1, $cachedDataFolder2['size']); + + $this->scanner->scan('folder', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + + $cachedDataFolder2 = $this->cache->get('folder'); + + $this->assertNotEquals($cachedDataFolder2['size'], -1); + + $this->cache->correctFolderSize('folder'); + + $cachedDataFolder = $this->cache->get(''); + $this->assertNotEquals($cachedDataFolder['size'], -1); + } + + function testBackgroundScan() { + $this->fillTestFolders(); + $this->storage->mkdir('folder2'); + $this->storage->file_put_contents('folder2/bar.txt', 'foobar'); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $this->assertFalse($this->cache->inCache('folder/bar.txt')); + $this->assertFalse($this->cache->inCache('folder/2bar.txt')); + $cachedData = $this->cache->get(''); + $this->assertEquals(-1, $cachedData['size']); + + $this->scanner->backgroundScan(); + + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + + $cachedData = $this->cache->get(''); + $this->assertnotEquals(-1, $cachedData['size']); + + $this->assertFalse($this->cache->getIncomplete()); + } + + public function testReuseExisting() { + $this->fillTestFolders(); + + $this->scanner->scan(''); + $oldData = $this->cache->get(''); + $this->storage->unlink('folder/bar.txt'); + $this->cache->put('folder', array('mtime' => $this->storage->filemtime('folder'), 'storage_mtime' => $this->storage->filemtime('folder'))); + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_SIZE); + $newData = $this->cache->get(''); + $this->assertInternalType('string', $oldData['etag']); + $this->assertInternalType('string', $newData['etag']); + $this->assertNotSame($oldData['etag'], $newData['etag']); + $this->assertEquals($oldData['size'], $newData['size']); + + $oldData = $newData; + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG); + $newData = $this->cache->get(''); + $this->assertSame($oldData['etag'], $newData['etag']); + $this->assertEquals(-1, $newData['size']); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); + $oldData = $this->cache->get(''); + $this->assertNotEquals(-1, $oldData['size']); + $this->scanner->scanFile('', \OC\Files\Cache\Scanner::REUSE_ETAG + \OC\Files\Cache\Scanner::REUSE_SIZE); + $newData = $this->cache->get(''); + $this->assertSame($oldData['etag'], $newData['etag']); + $this->assertEquals($oldData['size'], $newData['size']); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG + \OC\Files\Cache\Scanner::REUSE_SIZE); + $newData = $this->cache->get(''); + $this->assertSame($oldData['etag'], $newData['etag']); + $this->assertEquals($oldData['size'], $newData['size']); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG + \OC\Files\Cache\Scanner::REUSE_SIZE); + $newData = $this->cache->get(''); + $this->assertSame($oldData['etag'], $newData['etag']); + $this->assertEquals($oldData['size'], $newData['size']); + } + + public function testRemovedFile() { + $this->fillTestFolders(); + + $this->scanner->scan(''); + $this->assertTrue($this->cache->inCache('foo.txt')); + $this->storage->unlink('foo.txt'); + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $this->assertFalse($this->cache->inCache('foo.txt')); + } + + public function testRemovedFolder() { + $this->fillTestFolders(); + + $this->scanner->scan(''); + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + $this->storage->rmdir('/folder'); + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $this->assertFalse($this->cache->inCache('folder')); + $this->assertFalse($this->cache->inCache('folder/bar.txt')); + } + + public function testScanRemovedFile() { + $this->fillTestFolders(); + + $this->scanner->scan(''); + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + $this->storage->unlink('folder/bar.txt'); + $this->scanner->scanFile('folder/bar.txt'); + $this->assertFalse($this->cache->inCache('folder/bar.txt')); + } + + public function testETagRecreation() { + $this->fillTestFolders(); + + $this->scanner->scan('folder/bar.txt'); + + // manipulate etag to simulate an empty etag + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG); + /** @var CacheEntry $data0 */ + $data0 = $this->cache->get('folder/bar.txt'); + $this->assertInternalType('string', $data0['etag']); + $data1 = $this->cache->get('folder'); + $this->assertInternalType('string', $data1['etag']); + $data2 = $this->cache->get(''); + $this->assertInternalType('string', $data2['etag']); + $data0['etag'] = ''; + $this->cache->put('folder/bar.txt', $data0->getData()); + + // rescan + $this->scanner->scan('folder/bar.txt', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG); + + // verify cache content + $newData0 = $this->cache->get('folder/bar.txt'); + $this->assertInternalType('string', $newData0['etag']); + $this->assertNotEmpty($newData0['etag']); + } + + public function testRepairParent() { + $this->fillTestFolders(); + $this->scanner->scan(''); + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + $oldFolderId = $this->cache->getId('folder'); + + // delete the folder without removing the childs + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + \OC_DB::executeAudited($sql, array($oldFolderId)); + + $cachedData = $this->cache->get('folder/bar.txt'); + $this->assertEquals($oldFolderId, $cachedData['parent']); + $this->assertFalse($this->cache->inCache('folder')); + + $this->scanner->scan(''); + + $this->assertTrue($this->cache->inCache('folder')); + $newFolderId = $this->cache->getId('folder'); + $this->assertNotEquals($oldFolderId, $newFolderId); + + $cachedData = $this->cache->get('folder/bar.txt'); + $this->assertEquals($newFolderId, $cachedData['parent']); + } + + public function testRepairParentShallow() { + $this->fillTestFolders(); + $this->scanner->scan(''); + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + $oldFolderId = $this->cache->getId('folder'); + + // delete the folder without removing the childs + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + \OC_DB::executeAudited($sql, array($oldFolderId)); + + $cachedData = $this->cache->get('folder/bar.txt'); + $this->assertEquals($oldFolderId, $cachedData['parent']); + $this->assertFalse($this->cache->inCache('folder')); + + $this->scanner->scan('folder', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + + $this->assertTrue($this->cache->inCache('folder')); + $newFolderId = $this->cache->getId('folder'); + $this->assertNotEquals($oldFolderId, $newFolderId); + + $cachedData = $this->cache->get('folder/bar.txt'); + $this->assertEquals($newFolderId, $cachedData['parent']); + } + + /** + * @dataProvider dataTestIsPartialFile + * + * @param string $path + * @param bool $expected + */ + public function testIsPartialFile($path, $expected) { + $this->assertSame($expected, + $this->scanner->isPartialFile($path) + ); + } + + public function dataTestIsPartialFile() { + return [ + ['foo.txt.part', true], + ['/sub/folder/foo.txt.part', true], + ['/sub/folder.part/foo.txt', true], + ['foo.txt', false], + ['/sub/folder/foo.txt', false], + ]; + } + +} diff --git a/tests/lib/Files/Cache/UpdaterLegacyTest.php b/tests/lib/Files/Cache/UpdaterLegacyTest.php new file mode 100644 index 00000000000..7d247968ca9 --- /dev/null +++ b/tests/lib/Files/Cache/UpdaterLegacyTest.php @@ -0,0 +1,304 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use \OC\Files\Filesystem as Filesystem; +use OC\Files\Storage\Temporary; +use OC\Files\View; + +/** + * Class UpdaterLegacyTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class UpdaterLegacyTest extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var \OC\Files\Cache\Scanner $scanner + */ + private $scanner; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + + private static $user; + + protected function setUp() { + parent::setUp(); + + $this->storage = new \OC\Files\Storage\Temporary(array()); + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->mkdir('folder'); + $this->storage->file_put_contents('foo.txt', $textData); + $this->storage->file_put_contents('foo.png', $imgData); + $this->storage->file_put_contents('folder/bar.txt', $textData); + $this->storage->file_put_contents('folder/bar2.txt', $textData); + + $this->scanner = $this->storage->getScanner(); + $this->scanner->scan(''); + $this->cache = $this->storage->getCache(); + + if (!self::$user) { + self::$user = $this->getUniqueID(); + } + + \OC::$server->getUserManager()->createUser(self::$user, 'password'); + $this->loginAsUser(self::$user); + + Filesystem::init(self::$user, '/' . self::$user . '/files'); + + Filesystem::clearMounts(); + Filesystem::mount($this->storage, array(), '/' . self::$user . '/files'); + + \OC_Hook::clear('OC_Filesystem'); + } + + protected function tearDown() { + if ($this->cache) { + $this->cache->clear(); + } + + $result = false; + $user = \OC::$server->getUserManager()->get(self::$user); + if ($user !== null) { $result = $user->delete(); } + $this->assertTrue($result); + + $this->logout(); + parent::tearDown(); + } + + public function testWrite() { + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->cache->put('foo.txt', array('mtime' => 100, 'storage_mtime' => 150)); + $rootCachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); + + $fooCachedData = $this->cache->get('foo.txt'); + Filesystem::file_put_contents('foo.txt', 'asd'); + $cachedData = $this->cache->get('foo.txt'); + $this->assertEquals(3, $cachedData['size']); + $this->assertInternalType('string', $fooCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($fooCachedData['etag'], $cachedData['etag']); + $cachedData = $this->cache->get(''); + $this->assertEquals(2 * $textSize + $imageSize + 3, $cachedData['size']); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $rootCachedData = $cachedData; + + $this->assertFalse($this->cache->inCache('bar.txt')); + Filesystem::file_put_contents('bar.txt', 'asd'); + $this->assertTrue($this->cache->inCache('bar.txt')); + $cachedData = $this->cache->get('bar.txt'); + $this->assertEquals(3, $cachedData['size']); + $mtime = $cachedData['mtime']; + $cachedData = $this->cache->get(''); + $this->assertEquals(2 * $textSize + $imageSize + 2 * 3, $cachedData['size']); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $mtime); + } + + public function testWriteWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $storage2->getScanner()->scan(''); //initialize etags + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + $view = new View('/' . self::$user . '/files'); + $folderCachedData = $view->getFileInfo('folder'); + $substorageCachedData = $cache2->get(''); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $this->assertTrue($cache2->inCache('foo.txt')); + $cachedData = $cache2->get('foo.txt'); + $this->assertEquals(3, $cachedData['size']); + $mtime = $cachedData['mtime']; + + $cachedData = $cache2->get(''); + $this->assertInternalType('string', $substorageCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($substorageCachedData['etag'], $cachedData['etag']); + + $cachedData = $view->getFileInfo('folder'); + $this->assertInternalType('string', $folderCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($folderCachedData['etag'], $cachedData['etag']); + } + + public function testDelete() { + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $rootCachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); + + $this->assertTrue($this->cache->inCache('foo.txt')); + Filesystem::unlink('foo.txt'); + $this->assertFalse($this->cache->inCache('foo.txt')); + $cachedData = $this->cache->get(''); + $this->assertEquals(2 * $textSize + $imageSize, $cachedData['size']); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $cachedData['mtime']); + $rootCachedData = $cachedData; + + Filesystem::mkdir('bar_folder'); + $this->assertTrue($this->cache->inCache('bar_folder')); + $cachedData = $this->cache->get(''); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $rootCachedData = $cachedData; + Filesystem::rmdir('bar_folder'); + $this->assertFalse($this->cache->inCache('bar_folder')); + $cachedData = $this->cache->get(''); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $cachedData['mtime']); + } + + public function testDeleteWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $view = new View('/' . self::$user . '/files'); + $this->assertTrue($cache2->inCache('foo.txt')); + $folderCachedData = $view->getFileInfo('folder'); + $substorageCachedData = $cache2->get(''); + Filesystem::unlink('folder/substorage/foo.txt'); + $this->assertFalse($cache2->inCache('foo.txt')); + + $cachedData = $cache2->get(''); + $this->assertInternalType('string', $substorageCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($substorageCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($substorageCachedData['mtime'], $cachedData['mtime']); + + $cachedData = $view->getFileInfo('folder'); + $this->assertInternalType('string', $folderCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($folderCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($folderCachedData['mtime'], $cachedData['mtime']); + } + + public function testRename() { + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $rootCachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $fooCachedData = $this->cache->get('foo.txt'); + $this->assertFalse($this->cache->inCache('bar.txt')); + Filesystem::rename('foo.txt', 'bar.txt'); + $this->assertFalse($this->cache->inCache('foo.txt')); + $this->assertTrue($this->cache->inCache('bar.txt')); + $cachedData = $this->cache->get('bar.txt'); + $this->assertEquals($fooCachedData['fileid'], $cachedData['fileid']); + $mtime = $cachedData['mtime']; + $cachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + } + + public function testRenameExtension() { + $fooCachedData = $this->cache->get('foo.txt'); + $this->assertEquals('text/plain', $fooCachedData['mimetype']); + Filesystem::rename('foo.txt', 'foo.abcd'); + $fooCachedData = $this->cache->get('foo.abcd'); + $this->assertEquals('application/octet-stream', $fooCachedData['mimetype']); + } + + public function testRenameWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $view = new View('/' . self::$user . '/files'); + $this->assertTrue($cache2->inCache('foo.txt')); + $folderCachedData = $view->getFileInfo('folder'); + $substorageCachedData = $cache2->get(''); + $fooCachedData = $cache2->get('foo.txt'); + Filesystem::rename('folder/substorage/foo.txt', 'folder/substorage/bar.txt'); + $this->assertFalse($cache2->inCache('foo.txt')); + $this->assertTrue($cache2->inCache('bar.txt')); + $cachedData = $cache2->get('bar.txt'); + $this->assertEquals($fooCachedData['fileid'], $cachedData['fileid']); + $mtime = $cachedData['mtime']; + + $cachedData = $cache2->get(''); + $this->assertInternalType('string', $substorageCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($substorageCachedData['etag'], $cachedData['etag']); + // rename can cause mtime change - invalid assert +// $this->assertEquals($mtime, $cachedData['mtime']); + + $cachedData = $view->getFileInfo('folder'); + $this->assertInternalType('string', $folderCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($folderCachedData['etag'], $cachedData['etag']); + // rename can cause mtime change - invalid assert +// $this->assertEquals($mtime, $cachedData['mtime']); + } + + public function testTouch() { + $rootCachedData = $this->cache->get(''); + $fooCachedData = $this->cache->get('foo.txt'); + Filesystem::touch('foo.txt'); + $cachedData = $this->cache->get('foo.txt'); + $this->assertInternalType('string', $fooCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertGreaterThanOrEqual($fooCachedData['mtime'], $cachedData['mtime']); + + $cachedData = $this->cache->get(''); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $cachedData['mtime']); + $rootCachedData = $cachedData; + + $time = 1371006070; + $barCachedData = $this->cache->get('folder/bar.txt'); + $folderCachedData = $this->cache->get('folder'); + $this->cache->put('', ['mtime' => $time - 100]); + Filesystem::touch('folder/bar.txt', $time); + $cachedData = $this->cache->get('folder/bar.txt'); + $this->assertInternalType('string', $barCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($barCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + + $cachedData = $this->cache->get('folder'); + $this->assertInternalType('string', $folderCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($folderCachedData['etag'], $cachedData['etag']); + + $cachedData = $this->cache->get(''); + $this->assertInternalType('string', $rootCachedData['etag']); + $this->assertInternalType('string', $cachedData['etag']); + $this->assertNotSame($rootCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + } + +} diff --git a/tests/lib/Files/Cache/UpdaterTest.php b/tests/lib/Files/Cache/UpdaterTest.php new file mode 100644 index 00000000000..54fb3f3fc97 --- /dev/null +++ b/tests/lib/Files/Cache/UpdaterTest.php @@ -0,0 +1,308 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use OC\Files\Filesystem; +use OC\Files\Storage\Temporary; +use OC\Files\View; + +/** + * Class UpdaterTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class UpdaterTest extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Storage + */ + protected $storage; + + /** + * @var \OC\Files\Cache\Cache + */ + protected $cache; + + /** + * @var \OC\Files\View + */ + protected $view; + + /** + * @var \OC\Files\Cache\Updater + */ + protected $updater; + + protected function setUp() { + parent::setUp(); + + $this->loginAsUser(); + + $this->storage = new Temporary(array()); + $this->updater = $this->storage->getUpdater(); + $this->cache = $this->storage->getCache(); + } + + protected function tearDown() { + Filesystem::clearMounts(); + + $this->logout(); + parent::tearDown(); + } + + public function testNewFile() { + $this->storage->file_put_contents('foo.txt', 'bar'); + $this->assertFalse($this->cache->inCache('foo.txt')); + + $this->updater->update('foo.txt'); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $cached = $this->cache->get('foo.txt'); + $this->assertEquals(3, $cached['size']); + $this->assertEquals('text/plain', $cached['mimetype']); + } + + public function testUpdatedFile() { + $this->storage->file_put_contents('foo.txt', 'bar'); + $this->updater->update('foo.txt'); + + $cached = $this->cache->get('foo.txt'); + $this->assertEquals(3, $cached['size']); + $this->assertEquals('text/plain', $cached['mimetype']); + + $this->storage->file_put_contents('foo.txt', 'qwerty'); + + $cached = $this->cache->get('foo.txt'); + $this->assertEquals(3, $cached['size']); + + $this->updater->update('/foo.txt'); + + $cached = $this->cache->get('foo.txt'); + $this->assertEquals(6, $cached['size']); + } + + public function testParentSize() { + $this->storage->getScanner()->scan(''); + + $parentCached = $this->cache->get(''); + $this->assertEquals(0, $parentCached['size']); + + $this->storage->file_put_contents('foo.txt', 'bar'); + + $parentCached = $this->cache->get(''); + $this->assertEquals(0, $parentCached['size']); + + $this->updater->update('foo.txt'); + + $parentCached = $this->cache->get(''); + $this->assertEquals(3, $parentCached['size']); + + $this->storage->file_put_contents('foo.txt', 'qwerty'); + + $parentCached = $this->cache->get(''); + $this->assertEquals(3, $parentCached['size']); + + $this->updater->update('foo.txt'); + + $parentCached = $this->cache->get(''); + $this->assertEquals(6, $parentCached['size']); + + $this->storage->unlink('foo.txt'); + + $parentCached = $this->cache->get(''); + $this->assertEquals(6, $parentCached['size']); + + $this->updater->remove('foo.txt'); + + $parentCached = $this->cache->get(''); + $this->assertEquals(0, $parentCached['size']); + } + + public function testMove() { + $this->storage->file_put_contents('foo.txt', 'qwerty'); + $this->updater->update('foo.txt'); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $this->assertFalse($this->cache->inCache('bar.txt')); + $cached = $this->cache->get('foo.txt'); + + $this->storage->rename('foo.txt', 'bar.txt'); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $this->assertFalse($this->cache->inCache('bar.txt')); + + $this->updater->renameFromStorage($this->storage, 'foo.txt', 'bar.txt'); + + $this->assertFalse($this->cache->inCache('foo.txt')); + $this->assertTrue($this->cache->inCache('bar.txt')); + + $cachedTarget = $this->cache->get('bar.txt'); + $this->assertEquals($cached['etag'], $cachedTarget['etag']); + $this->assertEquals($cached['mtime'], $cachedTarget['mtime']); + $this->assertEquals($cached['size'], $cachedTarget['size']); + $this->assertEquals($cached['fileid'], $cachedTarget['fileid']); + } + + public function testMoveNonExistingOverwrite() { + $this->storage->file_put_contents('bar.txt', 'qwerty'); + $this->updater->update('bar.txt'); + + $cached = $this->cache->get('bar.txt'); + + $this->updater->renameFromStorage($this->storage, 'foo.txt', 'bar.txt'); + + $this->assertFalse($this->cache->inCache('foo.txt')); + $this->assertTrue($this->cache->inCache('bar.txt')); + + $cachedTarget = $this->cache->get('bar.txt'); + $this->assertEquals($cached['etag'], $cachedTarget['etag']); + $this->assertEquals($cached['mtime'], $cachedTarget['mtime']); + $this->assertEquals($cached['size'], $cachedTarget['size']); + $this->assertEquals($cached['fileid'], $cachedTarget['fileid']); + } + + public function testUpdateStorageMTime() { + $this->storage->mkdir('sub'); + $this->storage->mkdir('sub2'); + $this->storage->file_put_contents('sub/foo.txt', 'qwerty'); + + $this->updater->update('sub'); + $this->updater->update('sub/foo.txt'); + $this->updater->update('sub2'); + + $cachedSourceParent = $this->cache->get('sub'); + $cachedSource = $this->cache->get('sub/foo.txt'); + + $this->storage->rename('sub/foo.txt', 'sub2/bar.txt'); + + // simulate storage having a different mtime + $testmtime = 1433323578; + + // source storage mtime change + $this->storage->touch('sub', $testmtime); + + // target storage mtime change + $this->storage->touch('sub2', $testmtime); + // some storages (like Dropbox) change storage mtime on rename + $this->storage->touch('sub2/bar.txt', $testmtime); + + $this->updater->renameFromStorage($this->storage, 'sub/foo.txt', 'sub2/bar.txt'); + + $cachedTargetParent = $this->cache->get('sub2'); + $cachedTarget = $this->cache->get('sub2/bar.txt'); + + $this->assertEquals($cachedSource['mtime'], $cachedTarget['mtime'], 'file mtime preserved'); + + $this->assertNotEquals($cachedTarget['storage_mtime'], $cachedTarget['mtime'], 'mtime is not storage_mtime for moved file'); + + $this->assertEquals($testmtime, $cachedTarget['storage_mtime'], 'target file storage_mtime propagated'); + $this->assertNotEquals($testmtime, $cachedTarget['mtime'], 'target file mtime changed, not from storage'); + + $this->assertEquals($testmtime, $cachedTargetParent['storage_mtime'], 'target parent storage_mtime propagated'); + $this->assertNotEquals($testmtime, $cachedTargetParent['mtime'], 'target folder mtime changed, not from storage'); + } + + public function testNewFileDisabled() { + $this->storage->file_put_contents('foo.txt', 'bar'); + $this->assertFalse($this->cache->inCache('foo.txt')); + + $this->updater->disable(); + $this->updater->update('/foo.txt'); + + $this->assertFalse($this->cache->inCache('foo.txt')); + } + + public function testMoveCrossStorage() { + $storage2 = new Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/bar'); + $this->storage->file_put_contents('foo.txt', 'qwerty'); + + $this->updater->update('foo.txt'); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $this->assertFalse($cache2->inCache('bar.txt')); + $cached = $this->cache->get('foo.txt'); + + // "rename" + $storage2->file_put_contents('bar.txt', 'qwerty'); + $this->storage->unlink('foo.txt'); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $this->assertFalse($cache2->inCache('bar.txt')); + + $storage2->getUpdater()->renameFromStorage($this->storage, 'foo.txt', 'bar.txt'); + + $this->assertFalse($this->cache->inCache('foo.txt')); + $this->assertTrue($cache2->inCache('bar.txt')); + + $cachedTarget = $cache2->get('bar.txt'); + $this->assertEquals($cached['mtime'], $cachedTarget['mtime']); + $this->assertEquals($cached['size'], $cachedTarget['size']); + $this->assertEquals($cached['etag'], $cachedTarget['etag']); + $this->assertEquals($cached['fileid'], $cachedTarget['fileid']); + } + + public function testMoveFolderCrossStorage() { + $storage2 = new Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/bar'); + $this->storage->mkdir('foo'); + $this->storage->mkdir('foo/bar'); + $this->storage->file_put_contents('foo/foo.txt', 'qwerty'); + $this->storage->file_put_contents('foo/bar.txt', 'foo'); + $this->storage->file_put_contents('foo/bar/bar.txt', 'qwertyuiop'); + + $this->storage->getScanner()->scan(''); + + $this->assertTrue($this->cache->inCache('foo')); + $this->assertTrue($this->cache->inCache('foo/foo.txt')); + $this->assertTrue($this->cache->inCache('foo/bar.txt')); + $this->assertTrue($this->cache->inCache('foo/bar')); + $this->assertTrue($this->cache->inCache('foo/bar/bar.txt')); + $cached = []; + $cached[] = $this->cache->get('foo'); + $cached[] = $this->cache->get('foo/foo.txt'); + $cached[] = $this->cache->get('foo/bar.txt'); + $cached[] = $this->cache->get('foo/bar'); + $cached[] = $this->cache->get('foo/bar/bar.txt'); + + // add extension to trigger the possible mimetype change + $storage2->moveFromStorage($this->storage, 'foo', 'foo.b'); + $storage2->getUpdater()->renameFromStorage($this->storage, 'foo', 'foo.b'); + + $this->assertFalse($this->cache->inCache('foo')); + $this->assertFalse($this->cache->inCache('foo/foo.txt')); + $this->assertFalse($this->cache->inCache('foo/bar.txt')); + $this->assertFalse($this->cache->inCache('foo/bar')); + $this->assertFalse($this->cache->inCache('foo/bar/bar.txt')); + $this->assertTrue($cache2->inCache('foo.b')); + $this->assertTrue($cache2->inCache('foo.b/foo.txt')); + $this->assertTrue($cache2->inCache('foo.b/bar.txt')); + $this->assertTrue($cache2->inCache('foo.b/bar')); + $this->assertTrue($cache2->inCache('foo.b/bar/bar.txt')); + + $cachedTarget = []; + $cachedTarget[] = $cache2->get('foo.b'); + $cachedTarget[] = $cache2->get('foo.b/foo.txt'); + $cachedTarget[] = $cache2->get('foo.b/bar.txt'); + $cachedTarget[] = $cache2->get('foo.b/bar'); + $cachedTarget[] = $cache2->get('foo.b/bar/bar.txt'); + + foreach ($cached as $i => $old) { + $new = $cachedTarget[$i]; + $this->assertEquals($old['mtime'], $new['mtime']); + $this->assertEquals($old['size'], $new['size']); + $this->assertEquals($old['etag'], $new['etag']); + $this->assertEquals($old['fileid'], $new['fileid']); + $this->assertEquals($old['mimetype'], $new['mimetype']); + } + } +} diff --git a/tests/lib/Files/Cache/WatcherTest.php b/tests/lib/Files/Cache/WatcherTest.php new file mode 100644 index 00000000000..3834b5591ff --- /dev/null +++ b/tests/lib/Files/Cache/WatcherTest.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +/** + * Class WatcherTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class WatcherTest extends \Test\TestCase { + + /** + * @var \OC\Files\Storage\Storage[] $storages + */ + private $storages = array(); + + protected function setUp() { + parent::setUp(); + + $this->loginAsUser(); + } + + protected function tearDown() { + foreach ($this->storages as $storage) { + $cache = $storage->getCache(); + $ids = $cache->getAll(); + $cache->clear(); + } + + $this->logout(); + parent::tearDown(); + } + + /** + * @medium + */ + function testWatcher() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = $storage->getWatcher(); + $updater->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE); + + //set the mtime to the past so it can detect an mtime change + $cache->put('', array('storage_mtime' => 10)); + + $this->assertTrue($cache->inCache('folder/bar.txt')); + $this->assertTrue($cache->inCache('folder/bar2.txt')); + + $this->assertFalse($cache->inCache('bar.test')); + $storage->file_put_contents('bar.test', 'foo'); + $updater->checkUpdate(''); + $this->assertTrue($cache->inCache('bar.test')); + $cachedData = $cache->get('bar.test'); + $this->assertEquals(3, $cachedData['size']); + + $cache->put('bar.test', array('storage_mtime' => 10)); + $storage->file_put_contents('bar.test', 'test data'); + + // make sure that PHP can read the new size correctly + clearstatcache(); + + $updater->checkUpdate('bar.test'); + $cachedData = $cache->get('bar.test'); + $this->assertEquals(9, $cachedData['size']); + + $cache->put('folder', array('storage_mtime' => 10)); + + $storage->unlink('folder/bar2.txt'); + $updater->checkUpdate('folder'); + + $this->assertTrue($cache->inCache('folder/bar.txt')); + $this->assertFalse($cache->inCache('folder/bar2.txt')); + } + + /** + * @medium + */ + public function testFileToFolder() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = $storage->getWatcher(); + $updater->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE); + + //set the mtime to the past so it can detect an mtime change + $cache->put('', array('storage_mtime' => 10)); + + $storage->unlink('foo.txt'); + $storage->rename('folder', 'foo.txt'); + $updater->checkUpdate(''); + + $entry = $cache->get('foo.txt'); + $this->assertEquals('httpd/unix-directory', $entry['mimetype']); + $this->assertFalse($cache->inCache('folder')); + $this->assertFalse($cache->inCache('folder/bar.txt')); + + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = $storage->getWatcher(); + $updater->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE); + + //set the mtime to the past so it can detect an mtime change + $cache->put('foo.txt', array('storage_mtime' => 10)); + + $storage->unlink('foo.txt'); + $storage->rename('folder', 'foo.txt'); + $updater->checkUpdate('foo.txt'); + + $entry = $cache->get('foo.txt'); + $this->assertEquals('httpd/unix-directory', $entry['mimetype']); + $this->assertTrue($cache->inCache('foo.txt/bar.txt')); + } + + public function testPolicyNever() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = $storage->getWatcher(); + + //set the mtime to the past so it can detect an mtime change + $cache->put('foo.txt', array('storage_mtime' => 10)); + + $updater->setPolicy(\OC\Files\Cache\Watcher::CHECK_NEVER); + + $storage->file_put_contents('foo.txt', 'q'); + $this->assertFalse($updater->checkUpdate('foo.txt')); + + $cache->put('foo.txt', array('storage_mtime' => 20)); + $storage->file_put_contents('foo.txt', 'w'); + $this->assertFalse($updater->checkUpdate('foo.txt')); + } + + public function testPolicyOnce() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = $storage->getWatcher(); + + //set the mtime to the past so it can detect an mtime change + $cache->put('foo.txt', array('storage_mtime' => 10)); + + $updater->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE); + + $storage->file_put_contents('foo.txt', 'q'); + $this->assertTrue($updater->checkUpdate('foo.txt')); + + $cache->put('foo.txt', array('storage_mtime' => 20)); + $storage->file_put_contents('foo.txt', 'w'); + $this->assertFalse($updater->checkUpdate('foo.txt')); + } + + public function testPolicyAlways() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = $storage->getWatcher(); + + //set the mtime to the past so it can detect an mtime change + $cache->put('foo.txt', array('storage_mtime' => 10)); + + $updater->setPolicy(\OC\Files\Cache\Watcher::CHECK_ALWAYS); + + $storage->file_put_contents('foo.txt', 'q'); + $this->assertTrue($updater->checkUpdate('foo.txt')); + + $cache->put('foo.txt', array('storage_mtime' => 20)); + $storage->file_put_contents('foo.txt', 'w'); + $this->assertTrue($updater->checkUpdate('foo.txt')); + } + + /** + * @param bool $scan + * @return \OC\Files\Storage\Storage + */ + private function getTestStorage($scan = true) { + $storage = new \OC\Files\Storage\Temporary(array()); + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', $textData); + $storage->file_put_contents('foo.png', $imgData); + $storage->file_put_contents('folder/bar.txt', $textData); + $storage->file_put_contents('folder/bar2.txt', $textData); + + if ($scan) { + $scanner = $storage->getScanner(); + $scanner->scan(''); + } + $this->storages[] = $storage; + return $storage; + } +} diff --git a/tests/lib/Files/Cache/Wrapper/CacheJailTest.php b/tests/lib/Files/Cache/Wrapper/CacheJailTest.php new file mode 100644 index 00000000000..6ef6716f721 --- /dev/null +++ b/tests/lib/Files/Cache/Wrapper/CacheJailTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache\Wrapper; + +use Test\Files\Cache\CacheTest; + +/** + * Class CacheJail + * + * @group DB + * + * @package Test\Files\Cache\Wrapper + */ +class CacheJailTest extends CacheTest { + /** + * @var \OC\Files\Cache\Cache $sourceCache + */ + protected $sourceCache; + + public function setUp() { + parent::setUp(); + $this->storage->mkdir('foo'); + $this->sourceCache = $this->cache; + $this->cache = new \OC\Files\Cache\Wrapper\CacheJail($this->sourceCache, 'foo'); + } + + function testSearchOutsideJail() { + $file1 = 'foo/foobar'; + $file2 = 'folder/foobar'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + + $this->sourceCache->put($file1, $data1); + $this->sourceCache->put($file2, $data1); + + $this->assertCount(2, $this->sourceCache->search('%foobar')); + + $result = $this->cache->search('%foobar%'); + $this->assertCount(1, $result); + $this->assertEquals('foobar', $result[0]['path']); + } + + function testClearKeepEntriesOutsideJail() { + $file1 = 'foo/foobar'; + $file2 = 'foo/foobar/asd'; + $file3 = 'folder/foobar'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + $this->sourceCache->put('foo', $data1); + $this->sourceCache->put($file1, $data1); + $this->sourceCache->put($file2, $data1); + $this->sourceCache->put($file3, $data1); + + $this->cache->clear(); + + $this->assertFalse($this->cache->inCache('foobar')); + $this->assertTrue($this->sourceCache->inCache('folder/foobar')); + } + + function testGetById() { + //not supported + $this->assertTrue(true); + } + + function testGetIncomplete() { + //not supported + $this->assertTrue(true); + } +} diff --git a/tests/lib/Files/Cache/Wrapper/CachePermissionsMaskTest.php b/tests/lib/Files/Cache/Wrapper/CachePermissionsMaskTest.php new file mode 100644 index 00000000000..c12b35867ff --- /dev/null +++ b/tests/lib/Files/Cache/Wrapper/CachePermissionsMaskTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache\Wrapper; + +use OCP\Constants; +use Test\Files\Cache\CacheTest; + +/** + * Class CachePermissionsMask + * + * @group DB + * + * @package Test\Files\Cache\Wrapper + */ +class CachePermissionsMaskTest extends CacheTest { + /** + * @var \OC\Files\Cache\Cache $sourceCache + */ + protected $sourceCache; + + public function setUp() { + parent::setUp(); + $this->storage->mkdir('foo'); + $this->sourceCache = $this->cache; + $this->cache = $this->getMaskedCached(Constants::PERMISSION_ALL); + } + + protected function getMaskedCached($mask) { + return new \OC\Files\Cache\Wrapper\CachePermissionsMask($this->sourceCache, $mask); + } + + public function maskProvider() { + return array( + array(Constants::PERMISSION_ALL), + array(Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE), + array(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE), + array(Constants::PERMISSION_READ) + ); + } + + /** + * @dataProvider maskProvider + * @param int $mask + */ + public function testGetMasked($mask) { + $cache = $this->getMaskedCached($mask); + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'permissions' => Constants::PERMISSION_ALL); + $this->sourceCache->put('foo', $data); + $result = $cache->get('foo'); + $this->assertEquals($mask, $result['permissions']); + + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'permissions' => Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE); + $this->sourceCache->put('bar', $data); + $result = $cache->get('bar'); + $this->assertEquals($mask & ~Constants::PERMISSION_DELETE, $result['permissions']); + } + + /** + * @dataProvider maskProvider + * @param int $mask + */ + public function testGetFolderContentMasked($mask) { + $this->storage->mkdir('foo'); + $this->storage->file_put_contents('foo/bar', 'asd'); + $this->storage->file_put_contents('foo/asd', 'bar'); + $this->storage->getScanner()->scan(''); + + $cache = $this->getMaskedCached($mask); + $files = $cache->getFolderContents('foo'); + $this->assertCount(2, $files); + + foreach ($files as $file) { + $this->assertEquals($mask & ~Constants::PERMISSION_CREATE, $file['permissions']); + } + } + + /** + * @dataProvider maskProvider + * @param int $mask + */ + public function testSearchMasked($mask) { + $this->storage->mkdir('foo'); + $this->storage->file_put_contents('foo/bar', 'asd'); + $this->storage->file_put_contents('foo/foobar', 'bar'); + $this->storage->getScanner()->scan(''); + + $cache = $this->getMaskedCached($mask); + $files = $cache->search('%bar'); + $this->assertCount(2, $files); + + foreach ($files as $file) { + $this->assertEquals($mask & ~Constants::PERMISSION_CREATE, $file['permissions']); + } + } +} diff --git a/tests/lib/Files/Config/UserMountCacheTest.php b/tests/lib/Files/Config/UserMountCacheTest.php new file mode 100644 index 00000000000..e7554fc36d9 --- /dev/null +++ b/tests/lib/Files/Config/UserMountCacheTest.php @@ -0,0 +1,375 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Config; + +use OC\DB\QueryBuilder\Literal; +use OC\Files\Mount\MountPoint; +use OC\Log; +use OC\User\Manager; +use OCP\Files\Config\ICachedMountInfo; +use OCP\IDBConnection; +use OCP\IUserManager; +use Test\TestCase; +use Test\Util\User\Dummy; + +/** + * @group DB + */ +class UserMountCacheTest extends TestCase { + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUserManager + */ + private $userManager; + + /** + * @var \OC\Files\Config\UserMountCache + */ + private $cache; + + private $fileIds = []; + + public function setUp() { + $this->fileIds = []; + $this->connection = \OC::$server->getDatabaseConnection(); + $this->userManager = new Manager(null); + $userBackend = new Dummy(); + $userBackend->createUser('u1', ''); + $userBackend->createUser('u2', ''); + $this->userManager->registerBackend($userBackend); + $this->cache = new \OC\Files\Config\UserMountCache($this->connection, $this->userManager, $this->getMock('\OC\Log')); + } + + public function tearDown() { + $builder = $this->connection->getQueryBuilder(); + + $builder->delete('mounts')->execute(); + + $builder = $this->connection->getQueryBuilder(); + + foreach ($this->fileIds as $fileId) { + $builder->delete('filecache') + ->where($builder->expr()->eq('fileid', new Literal($fileId))) + ->execute(); + } + } + + private function getStorage($storageId, $rootId) { + $storageCache = $this->getMockBuilder('\OC\Files\Cache\Storage') + ->disableOriginalConstructor() + ->getMock(); + $storageCache->expects($this->any()) + ->method('getNumericId') + ->will($this->returnValue($storageId)); + + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor() + ->getMock(); + $cache->expects($this->any()) + ->method('getId') + ->will($this->returnValue($rootId)); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor() + ->getMock(); + $storage->expects($this->any()) + ->method('getStorageCache') + ->will($this->returnValue($storageCache)); + $storage->expects($this->any()) + ->method('getCache') + ->will($this->returnValue($cache)); + + return $storage; + } + + private function clearCache() { + $this->invokePrivate($this->cache, 'mountsForUsers', [[]]); + } + + public function testNewMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/asd/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(1, $cachedMounts); + $cachedMount = $cachedMounts[0]; + $this->assertEquals('/asd/', $cachedMount->getMountPoint()); + $this->assertEquals($user, $cachedMount->getUser()); + $this->assertEquals($storage->getCache()->getId(''), $cachedMount->getRootId()); + $this->assertEquals($storage->getStorageCache()->getNumericId(), $cachedMount->getStorageId()); + } + + public function testSameMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/asd/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(1, $cachedMounts); + $cachedMount = $cachedMounts[0]; + $this->assertEquals('/asd/', $cachedMount->getMountPoint()); + $this->assertEquals($user, $cachedMount->getUser()); + $this->assertEquals($storage->getCache()->getId(''), $cachedMount->getRootId()); + $this->assertEquals($storage->getStorageCache()->getNumericId(), $cachedMount->getStorageId()); + } + + public function testRemoveMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/asd/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $this->cache->registerMounts($user, []); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(0, $cachedMounts); + } + + public function testChangeMounts() { + $user = $this->userManager->get('u1'); + + $storage = $this->getStorage(10, 20); + $mount = new MountPoint($storage, '/foo/'); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $this->cache->registerMounts($user, [$mount]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user); + + $this->assertCount(1, $cachedMounts); + $cachedMount = $cachedMounts[0]; + $this->assertEquals('/foo/', $cachedMount->getMountPoint()); + } + + public function testGetMountsForUser() { + $user1 = $this->userManager->get('u1'); + $user2 = $this->userManager->get('u2'); + + $mount1 = new MountPoint($this->getStorage(1, 2), '/foo/'); + $mount2 = new MountPoint($this->getStorage(3, 4), '/bar/'); + + $this->cache->registerMounts($user1, [$mount1, $mount2]); + $this->cache->registerMounts($user2, [$mount2]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForUser($user1); + + $this->assertCount(2, $cachedMounts); + $this->assertEquals('/foo/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals(2, $cachedMounts[0]->getRootId()); + $this->assertEquals(1, $cachedMounts[0]->getStorageId()); + + $this->assertEquals('/bar/', $cachedMounts[1]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[1]->getUser()); + $this->assertEquals(4, $cachedMounts[1]->getRootId()); + $this->assertEquals(3, $cachedMounts[1]->getStorageId()); + } + + public function testGetMountsByStorageId() { + $user1 = $this->userManager->get('u1'); + $user2 = $this->userManager->get('u2'); + + $mount1 = new MountPoint($this->getStorage(1, 2), '/foo/'); + $mount2 = new MountPoint($this->getStorage(3, 4), '/bar/'); + + $this->cache->registerMounts($user1, [$mount1, $mount2]); + $this->cache->registerMounts($user2, [$mount2]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForStorageId(3); + $this->sortMounts($cachedMounts); + + $this->assertCount(2, $cachedMounts); + + $this->assertEquals('/bar/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals(4, $cachedMounts[0]->getRootId()); + $this->assertEquals(3, $cachedMounts[0]->getStorageId()); + + $this->assertEquals('/bar/', $cachedMounts[1]->getMountPoint()); + $this->assertEquals($user2, $cachedMounts[1]->getUser()); + $this->assertEquals(4, $cachedMounts[1]->getRootId()); + $this->assertEquals(3, $cachedMounts[1]->getStorageId()); + } + + public function testGetMountsByRootId() { + $user1 = $this->userManager->get('u1'); + $user2 = $this->userManager->get('u2'); + + $mount1 = new MountPoint($this->getStorage(1, 2), '/foo/'); + $mount2 = new MountPoint($this->getStorage(3, 4), '/bar/'); + + $this->cache->registerMounts($user1, [$mount1, $mount2]); + $this->cache->registerMounts($user2, [$mount2]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForRootId(4); + $this->sortMounts($cachedMounts); + + $this->assertCount(2, $cachedMounts); + + $this->assertEquals('/bar/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals(4, $cachedMounts[0]->getRootId()); + $this->assertEquals(3, $cachedMounts[0]->getStorageId()); + + $this->assertEquals('/bar/', $cachedMounts[1]->getMountPoint()); + $this->assertEquals($user2, $cachedMounts[1]->getUser()); + $this->assertEquals(4, $cachedMounts[1]->getRootId()); + $this->assertEquals(3, $cachedMounts[1]->getStorageId()); + } + + private function sortMounts(&$mounts) { + usort($mounts, function (ICachedMountInfo $a, ICachedMountInfo $b) { + return strcmp($a->getUser()->getUID(), $b->getUser()->getUID()); + }); + } + + private function createCacheEntry($internalPath, $storageId) { + $this->connection->insertIfNotExist('*PREFIX*filecache', [ + 'storage' => $storageId, + 'path' => $internalPath, + 'path_hash' => md5($internalPath), + 'parent' => -1, + 'name' => basename($internalPath), + 'mimetype' => 0, + 'mimepart' => 0, + 'size' => 0, + 'storage_mtime' => 0, + 'encrypted' => 0, + 'unencrypted_size' => 0, + 'etag' => '', + 'permissions' => 31 + ], ['storage', 'path_hash']); + $id = (int)$this->connection->lastInsertId('*PREFIX*filecache'); + $this->fileIds[] = $id; + return $id; + } + + public function testGetMountsForFileIdRootId() { + $user1 = $this->userManager->get('u1'); + + $rootId = $this->createCacheEntry('', 2); + + $mount1 = new MountPoint($this->getStorage(2, $rootId), '/foo/'); + + $this->cache->registerMounts($user1, [$mount1]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForFileId($rootId); + + $this->assertCount(1, $cachedMounts); + + $this->assertEquals('/foo/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals($rootId, $cachedMounts[0]->getRootId()); + $this->assertEquals(2, $cachedMounts[0]->getStorageId()); + } + + public function testGetMountsForFileIdSubFolder() { + $user1 = $this->userManager->get('u1'); + + $rootId = $this->createCacheEntry('', 2); + $fileId = $this->createCacheEntry('/foo/bar', 2); + + $mount1 = new MountPoint($this->getStorage(2, $rootId), '/foo/'); + + $this->cache->registerMounts($user1, [$mount1]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForFileId($fileId); + + $this->assertCount(1, $cachedMounts); + + $this->assertEquals('/foo/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals($rootId, $cachedMounts[0]->getRootId()); + $this->assertEquals(2, $cachedMounts[0]->getStorageId()); + } + + public function testGetMountsForFileIdSubFolderMount() { + $user1 = $this->userManager->get('u1'); + + $this->createCacheEntry('', 2); + $folderId = $this->createCacheEntry('/foo', 2); + $fileId = $this->createCacheEntry('/foo/bar', 2); + + $mount1 = new MountPoint($this->getStorage(2, $folderId), '/foo/'); + + $this->cache->registerMounts($user1, [$mount1]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForFileId($fileId); + + $this->assertCount(1, $cachedMounts); + + $this->assertEquals('/foo/', $cachedMounts[0]->getMountPoint()); + $this->assertEquals($user1, $cachedMounts[0]->getUser()); + $this->assertEquals($folderId, $cachedMounts[0]->getRootId()); + $this->assertEquals(2, $cachedMounts[0]->getStorageId()); + } + + public function testGetMountsForFileIdSubFolderMountOutside() { + $user1 = $this->userManager->get('u1'); + + $this->createCacheEntry('', 2); + $folderId = $this->createCacheEntry('/foo', 2); + $fileId = $this->createCacheEntry('/bar/asd', 2); + + $mount1 = new MountPoint($this->getStorage(2, $folderId), '/foo/'); + + $this->cache->registerMounts($user1, [$mount1]); + + $this->clearCache(); + + $cachedMounts = $this->cache->getMountsForFileId($fileId); + + $this->assertCount(0, $cachedMounts); + } +} diff --git a/tests/lib/Files/EtagTest.php b/tests/lib/Files/EtagTest.php new file mode 100644 index 00000000000..d8e44000f9c --- /dev/null +++ b/tests/lib/Files/EtagTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files; + +use OC\Files\Filesystem; +use OCP\Share; + +/** + * Class EtagTest + * + * @group DB + * + * @package Test\Files + */ +class EtagTest extends \Test\TestCase { + private $datadir; + + private $tmpDir; + + /** + * @var \Test\Util\User\Dummy $userBackend + */ + private $userBackend; + + protected function setUp() { + parent::setUp(); + + \OC_Hook::clear('OC_Filesystem', 'setup'); + $application = new \OCA\Files_Sharing\AppInfo\Application(); + $application->registerMountProviders(); + \OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); + \OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); + + $config = \OC::$server->getConfig(); + $this->datadir = $config->getSystemValue('datadirectory'); + $this->tmpDir = \OC::$server->getTempManager()->getTemporaryFolder(); + $config->setSystemValue('datadirectory', $this->tmpDir); + + $this->userBackend = new \Test\Util\User\Dummy(); + \OC_User::useBackend($this->userBackend); + } + + protected function tearDown() { + \OC::$server->getConfig()->setSystemValue('datadirectory', $this->datadir); + + $this->logout(); + parent::tearDown(); + } + + public function testNewUser() { + $user1 = $this->getUniqueID('user_'); + $this->userBackend->createUser($user1, ''); + + $this->loginAsUser($user1); + Filesystem::mkdir('/folder'); + Filesystem::mkdir('/folder/subfolder'); + Filesystem::file_put_contents('/foo.txt', 'asd'); + Filesystem::file_put_contents('/folder/bar.txt', 'fgh'); + Filesystem::file_put_contents('/folder/subfolder/qwerty.txt', 'jkl'); + + $files = array('/foo.txt', '/folder/bar.txt', '/folder/subfolder', '/folder/subfolder/qwerty.txt'); + $originalEtags = $this->getEtags($files); + + $scanner = new \OC\Files\Utils\Scanner($user1, \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); + $scanner->backgroundScan('/'); + + $newEtags = $this->getEtags($files); + // loop over array and use assertSame over assertEquals to prevent false positives + foreach ($originalEtags as $file => $originalEtag) { + $this->assertSame($originalEtag, $newEtags[$file]); + } + } + + /** + * @param string[] $files + */ + private function getEtags($files) { + $etags = array(); + foreach ($files as $file) { + $info = Filesystem::getFileInfo($file); + $etags[$file] = $info['etag']; + } + return $etags; + } +} diff --git a/tests/lib/Files/FilesystemTest.php b/tests/lib/Files/FilesystemTest.php new file mode 100644 index 00000000000..a4a4345d2f9 --- /dev/null +++ b/tests/lib/Files/FilesystemTest.php @@ -0,0 +1,490 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Files; + +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\Temporary; +use OC\User\NoUserException; +use OCP\Files\Config\IMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + +class DummyMountProvider implements IMountProvider { + private $mounts = []; + + /** + * @param array $mounts + */ + public function __construct(array $mounts) { + $this->mounts = $mounts; + } + + /** + * Get the pre-registered mount points + * + * @param IUser $user + * @param IStorageFactory $loader + * @return \OCP\Files\Mount\IMountPoint[] + */ + public function getMountsForUser(IUser $user, IStorageFactory $loader) { + return isset($this->mounts[$user->getUID()]) ? $this->mounts[$user->getUID()] : []; + } +} + +/** + * Class FilesystemTest + * + * @group DB + * + * @package Test\Files + */ +class FilesystemTest extends \Test\TestCase { + + const TEST_FILESYSTEM_USER1 = "test-filesystem-user1"; + const TEST_FILESYSTEM_USER2 = "test-filesystem-user1"; + + /** + * @var array tmpDirs + */ + private $tmpDirs = array(); + + /** + * @return array + */ + private function getStorageData() { + $dir = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->tmpDirs[] = $dir; + return array('datadir' => $dir); + } + + protected function setUp() { + parent::setUp(); + $userBackend = new \Test\Util\User\Dummy(); + $userBackend->createUser(self::TEST_FILESYSTEM_USER1, self::TEST_FILESYSTEM_USER1); + $userBackend->createUser(self::TEST_FILESYSTEM_USER2, self::TEST_FILESYSTEM_USER2); + \OC::$server->getUserManager()->registerBackend($userBackend); + $this->loginAsUser(); + } + + protected function tearDown() { + foreach ($this->tmpDirs as $dir) { + \OC_Helper::rmdirr($dir); + } + + $this->logout(); + parent::tearDown(); + } + + public function testMount() { + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', self::getStorageData(), '/'); + $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/')); + $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/some/folder')); + list(, $internalPath) = \OC\Files\Filesystem::resolvePath('/'); + $this->assertEquals('', $internalPath); + list(, $internalPath) = \OC\Files\Filesystem::resolvePath('/some/folder'); + $this->assertEquals('some/folder', $internalPath); + + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', self::getStorageData(), '/some'); + $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/')); + $this->assertEquals('/some/', \OC\Files\Filesystem::getMountPoint('/some/folder')); + $this->assertEquals('/some/', \OC\Files\Filesystem::getMountPoint('/some/')); + $this->assertEquals('/some/', \OC\Files\Filesystem::getMountPoint('/some')); + list(, $internalPath) = \OC\Files\Filesystem::resolvePath('/some/folder'); + $this->assertEquals('folder', $internalPath); + } + + public function normalizePathData() { + return array( + array('/', ''), + array('/', '/'), + array('/', '//'), + array('/', '/', false), + array('/', '//', false), + + array('/path', '/path/'), + array('/path/', '/path/', false), + array('/path', 'path'), + + array('/foo/bar', '/foo//bar/'), + array('/foo/bar/', '/foo//bar/', false), + array('/foo/bar', '/foo////bar'), + array('/foo/bar', '/foo/////bar'), + array('/foo/bar', '/foo/bar/.'), + array('/foo/bar', '/foo/bar/./'), + array('/foo/bar/', '/foo/bar/./', false), + array('/foo/bar', '/foo/bar/./.'), + array('/foo/bar', '/foo/bar/././'), + array('/foo/bar/', '/foo/bar/././', false), + array('/foo/bar', '/foo/./bar/'), + array('/foo/bar/', '/foo/./bar/', false), + array('/foo/.bar', '/foo/.bar/'), + array('/foo/.bar/', '/foo/.bar/', false), + array('/foo/.bar/tee', '/foo/.bar/tee'), + + // Windows paths + array('/', ''), + array('/', '\\'), + array('/', '\\', false), + array('/', '\\\\'), + array('/', '\\\\', false), + + array('/path', '\\path'), + array('/path', '\\path', false), + array('/path', '\\path\\'), + array('/path/', '\\path\\', false), + + array('/foo/bar', '\\foo\\\\bar\\'), + array('/foo/bar/', '\\foo\\\\bar\\', false), + array('/foo/bar', '\\foo\\\\\\\\bar'), + array('/foo/bar', '\\foo\\\\\\\\\\bar'), + array('/foo/bar', '\\foo\\bar\\.'), + array('/foo/bar', '\\foo\\bar\\.\\'), + array('/foo/bar/', '\\foo\\bar\\.\\', false), + array('/foo/bar', '\\foo\\bar\\.\\.'), + array('/foo/bar', '\\foo\\bar\\.\\.\\'), + array('/foo/bar/', '\\foo\\bar\\.\\.\\', false), + array('/foo/bar', '\\foo\\.\\bar\\'), + array('/foo/bar/', '\\foo\\.\\bar\\', false), + array('/foo/.bar', '\\foo\\.bar\\'), + array('/foo/.bar/', '\\foo\\.bar\\', false), + array('/foo/.bar/tee', '\\foo\\.bar\\tee'), + + // Absolute windows paths NOT marked as absolute + array('/C:', 'C:\\'), + array('/C:/', 'C:\\', false), + array('/C:/tests', 'C:\\tests'), + array('/C:/tests', 'C:\\tests', false), + array('/C:/tests', 'C:\\tests\\'), + array('/C:/tests/', 'C:\\tests\\', false), + + // normalize does not resolve '..' (by design) + array('/foo/..', '/foo/../'), + array('/foo/..', '\\foo\\..\\'), + ); + } + + /** + * @dataProvider normalizePathData + */ + public function testNormalizePath($expected, $path, $stripTrailingSlash = true) { + $this->assertEquals($expected, \OC\Files\Filesystem::normalizePath($path, $stripTrailingSlash)); + } + + public function isValidPathData() { + return array( + array('/', true), + array('/path', true), + array('/foo/bar', true), + array('/foo//bar/', true), + array('/foo////bar', true), + array('/foo//\///bar', true), + array('/foo/bar/.', true), + array('/foo/bar/./', true), + array('/foo/bar/./.', true), + array('/foo/bar/././', true), + array('/foo/bar/././..bar', true), + array('/foo/bar/././..bar/a', true), + array('/foo/bar/././..', false), + array('/foo/bar/././../', false), + array('/foo/bar/.././', false), + array('/foo/bar/../../', false), + array('/foo/bar/../..\\', false), + array('..', false), + array('../', false), + array('../foo/bar', false), + array('..\foo/bar', false), + ); + } + + /** + * @dataProvider isValidPathData + */ + public function testIsValidPath($path, $expected) { + $this->assertSame($expected, \OC\Files\Filesystem::isValidPath($path)); + } + + public function isFileBlacklistedData() { + return array( + array('/etc/foo/bar/foo.txt', false), + array('\etc\foo/bar\foo.txt', false), + array('.htaccess', true), + array('.htaccess/', true), + array('.htaccess\\', true), + array('/etc/foo\bar/.htaccess\\', true), + array('/etc/foo\bar/.htaccess/', true), + array('/etc/foo\bar/.htaccess/foo', false), + array('//foo//bar/\.htaccess/', true), + array('\foo\bar\.HTAccess', true), + ); + } + + /** + * @dataProvider isFileBlacklistedData + */ + public function testIsFileBlacklisted($path, $expected) { + $this->assertSame($expected, \OC\Files\Filesystem::isFileBlacklisted($path)); + } + + public function normalizePathWindowsAbsolutePathData() { + return array( + array('C:/', 'C:\\'), + array('C:/', 'C:\\', false), + array('C:/tests', 'C:\\tests'), + array('C:/tests', 'C:\\tests', false), + array('C:/tests', 'C:\\tests\\'), + array('C:/tests/', 'C:\\tests\\', false), + ); + } + + /** + * @dataProvider normalizePathWindowsAbsolutePathData + */ + public function testNormalizePathWindowsAbsolutePath($expected, $path, $stripTrailingSlash = true) { + if (!\OC_Util::runningOnWindows()) { + $this->markTestSkipped('This test is Windows only'); + } + + $this->assertEquals($expected, \OC\Files\Filesystem::normalizePath($path, $stripTrailingSlash, true)); + } + + public function testNormalizePathUTF8() { + if (!class_exists('Patchwork\PHP\Shim\Normalizer')) { + $this->markTestSkipped('UTF8 normalizer Patchwork was not found'); + } + + $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("/foo/baru\xCC\x88")); + $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("\\foo\\baru\xCC\x88")); + } + + public function testHooks() { + if (\OC\Files\Filesystem::getView()) { + $user = \OC_User::getUser(); + } else { + $user = self::TEST_FILESYSTEM_USER1; + $backend = new \Test\Util\User\Dummy(); + \OC_User::useBackend($backend); + $backend->createUser($user, $user); + $userObj = \OC::$server->getUserManager()->get($user); + \OC::$server->getUserSession()->setUser($userObj); + \OC\Files\Filesystem::init($user, '/' . $user . '/files'); + + } + \OC_Hook::clear('OC_Filesystem'); + \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); + + \OC\Files\Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); + + $rootView = new \OC\Files\View(''); + $rootView->mkdir('/' . $user); + $rootView->mkdir('/' . $user . '/files'); + +// \OC\Files\Filesystem::file_put_contents('/foo', 'foo'); + \OC\Files\Filesystem::mkdir('/bar'); +// \OC\Files\Filesystem::file_put_contents('/bar//foo', 'foo'); + + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); + file_put_contents($tmpFile, 'foo'); + $fh = fopen($tmpFile, 'r'); +// \OC\Files\Filesystem::file_put_contents('/bar//foo', $fh); + } + + /** + * Tests that an exception is thrown when passed user does not exist. + * + * @expectedException \OC\User\NoUserException + */ + public function testLocalMountWhenUserDoesNotExist() { + $userId = $this->getUniqueID('user_'); + + \OC\Files\Filesystem::initMountPoints($userId); + } + + /** + * @expectedException \OC\User\NoUserException + */ + public function testNullUserThrows() { + \OC\Files\Filesystem::initMountPoints(null); + } + + public function testNullUserThrowsTwice() { + $thrown = 0; + try { + \OC\Files\Filesystem::initMountPoints(null); + } catch (NoUserException $e) { + $thrown++; + } + try { + \OC\Files\Filesystem::initMountPoints(null); + } catch (NoUserException $e) { + $thrown++; + } + $this->assertEquals(2, $thrown); + } + + /** + * Tests that an exception is thrown when passed user does not exist. + */ + public function testLocalMountWhenUserDoesNotExistTwice() { + $thrown = 0; + $userId = $this->getUniqueID('user_'); + + try { + \OC\Files\Filesystem::initMountPoints($userId); + } catch (NoUserException $e) { + $thrown++; + } + + try { + \OC\Files\Filesystem::initMountPoints($userId); + } catch (NoUserException $e) { + $thrown++; + } + + $this->assertEquals(2, $thrown); + } + + /** + * Tests that the home storage is used for the user's mount point + */ + public function testHomeMount() { + $userId = $this->getUniqueID('user_'); + + \OC::$server->getUserManager()->createUser($userId, $userId); + + \OC\Files\Filesystem::initMountPoints($userId); + + $homeMount = \OC\Files\Filesystem::getStorage('/' . $userId . '/'); + + $this->assertTrue($homeMount->instanceOfStorage('\OCP\Files\IHomeStorage')); + if (getenv('RUN_OBJECTSTORE_TESTS')) { + $this->assertTrue($homeMount->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')); + $this->assertEquals('object::user:' . $userId, $homeMount->getId()); + } else { + $this->assertTrue($homeMount->instanceOfStorage('\OC\Files\Storage\Home')); + $this->assertEquals('home::' . $userId, $homeMount->getId()); + } + + $user = \OC::$server->getUserManager()->get($userId); + if ($user !== null) { $user->delete(); } + } + + /** + * Tests that the home storage is used in legacy mode + * for the user's mount point + */ + public function testLegacyHomeMount() { + if (getenv('RUN_OBJECTSTORE_TESTS')) { + $this->markTestSkipped('legacy storage unrelated to objectstore environments'); + } + $datadir = \OC::$server->getConfig()->getSystemValue("datadirectory", \OC::$SERVERROOT . "/data"); + $userId = $this->getUniqueID('user_'); + + // insert storage into DB by constructing it + // to make initMountsPoint find its existence + $localStorage = new \OC\Files\Storage\Local(array('datadir' => $datadir . '/' . $userId . '/')); + // this will trigger the insert + $cache = $localStorage->getCache(); + + \OC::$server->getUserManager()->createUser($userId, $userId); + \OC\Files\Filesystem::initMountPoints($userId); + + $homeMount = \OC\Files\Filesystem::getStorage('/' . $userId . '/'); + + $this->assertTrue($homeMount->instanceOfStorage('\OC\Files\Storage\Home')); + $this->assertEquals('local::' . $datadir . '/' . $userId . '/', $homeMount->getId()); + + $user = \OC::$server->getUserManager()->get($userId); + if ($user !== null) { $user->delete(); } + // delete storage entry + $cache->clear(); + } + + public function dummyHook($arguments) { + $path = $arguments['path']; + $this->assertEquals($path, \OC\Files\Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized + } + + /** + * Test that the default cache dir is part of the user's home + */ + public function testMountDefaultCacheDir() { + $userId = $this->getUniqueID('user_'); + $config = \OC::$server->getConfig(); + $oldCachePath = $config->getSystemValue('cache_path', ''); + // no cache path configured + $config->setSystemValue('cache_path', ''); + + \OC::$server->getUserManager()->createUser($userId, $userId); + \OC\Files\Filesystem::initMountPoints($userId); + + $this->assertEquals( + '/' . $userId . '/', + \OC\Files\Filesystem::getMountPoint('/' . $userId . '/cache') + ); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath('/' . $userId . '/cache'); + $this->assertTrue($storage->instanceOfStorage('\OCP\Files\IHomeStorage')); + $this->assertEquals('cache', $internalPath); + $user = \OC::$server->getUserManager()->get($userId); + if ($user !== null) { $user->delete(); } + + $config->setSystemValue('cache_path', $oldCachePath); + } + + /** + * Test that an external cache is mounted into + * the user's home + */ + public function testMountExternalCacheDir() { + $userId = $this->getUniqueID('user_'); + + $config = \OC::$server->getConfig(); + $oldCachePath = $config->getSystemValue('cache_path', ''); + // set cache path to temp dir + $cachePath = \OC::$server->getTempManager()->getTemporaryFolder() . '/extcache'; + $config->setSystemValue('cache_path', $cachePath); + + \OC::$server->getUserManager()->createUser($userId, $userId); + \OC\Files\Filesystem::initMountPoints($userId); + + $this->assertEquals( + '/' . $userId . '/cache/', + \OC\Files\Filesystem::getMountPoint('/' . $userId . '/cache') + ); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath('/' . $userId . '/cache'); + $this->assertTrue($storage->instanceOfStorage('\OC\Files\Storage\Local')); + $this->assertEquals('', $internalPath); + $user = \OC::$server->getUserManager()->get($userId); + if ($user !== null) { $user->delete(); } + + $config->setSystemValue('cache_path', $oldCachePath); + } + + public function testRegisterMountProviderAfterSetup() { + \OC\Files\Filesystem::initMountPoints(self::TEST_FILESYSTEM_USER2); + $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/foo/bar')); + $mount = new MountPoint(new Temporary([]), '/foo/bar'); + $mountProvider = new DummyMountProvider([self::TEST_FILESYSTEM_USER2 => [$mount]]); + \OC::$server->getMountProviderCollection()->registerProvider($mountProvider); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::getMountPoint('/foo/bar')); + } +} diff --git a/tests/lib/Files/Mount/ManagerTest.php b/tests/lib/Files/Mount/ManagerTest.php new file mode 100644 index 00000000000..46ae597f56b --- /dev/null +++ b/tests/lib/Files/Mount/ManagerTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Mount; + +use \OC\Files\Storage\Temporary; + +class LongId extends Temporary { + public function getId() { + return 'long:' . str_repeat('foo', 50) . parent::getId(); + } +} + +class ManagerTest extends \Test\TestCase { + /** + * @var \OC\Files\Mount\Manager + */ + private $manager; + + protected function setup() { + parent::setUp(); + $this->manager = new \OC\Files\Mount\Manager(); + } + + public function testFind() { + $this->assertNull($this->manager->find('/')); + + $rootMount = new \OC\Files\Mount\MountPoint(new Temporary(array()), '/'); + $this->manager->addMount($rootMount); + $this->assertEquals($rootMount, $this->manager->find('/')); + $this->assertEquals($rootMount, $this->manager->find('/foo/bar')); + + $storage = new Temporary(array()); + $mount1 = new \OC\Files\Mount\MountPoint($storage, '/foo'); + $this->manager->addMount($mount1); + $this->assertEquals($rootMount, $this->manager->find('/')); + $this->assertEquals($mount1, $this->manager->find('/foo/bar')); + + $this->assertEquals(1, count($this->manager->findIn('/'))); + $mount2 = new \OC\Files\Mount\MountPoint(new Temporary(array()), '/bar'); + $this->manager->addMount($mount2); + $this->assertEquals(2, count($this->manager->findIn('/'))); + + $id = $mount1->getStorageId(); + $this->assertEquals(array($mount1), $this->manager->findByStorageId($id)); + + $mount3 = new \OC\Files\Mount\MountPoint($storage, '/foo/bar'); + $this->manager->addMount($mount3); + $this->assertEquals(array($mount1, $mount3), $this->manager->findByStorageId($id)); + } + + public function testLong() { + $storage = new LongId(array()); + $mount = new \OC\Files\Mount\MountPoint($storage, '/foo'); + $this->manager->addMount($mount); + + $id = $mount->getStorageId(); + $storageId = $storage->getId(); + $this->assertEquals(array($mount), $this->manager->findByStorageId($id)); + $this->assertEquals(array($mount), $this->manager->findByStorageId($storageId)); + $this->assertEquals(array($mount), $this->manager->findByStorageId(md5($storageId))); + } +} diff --git a/tests/lib/Files/Mount/MountPointTest.php b/tests/lib/Files/Mount/MountPointTest.php new file mode 100644 index 00000000000..392f10c7170 --- /dev/null +++ b/tests/lib/Files/Mount/MountPointTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Mount; + +class MountPointTest extends \Test\TestCase { + + public function testGetStorage() { + $storage = $this->getMock('\OCP\Files\Storage'); + $storage->expects($this->once()) + ->method('getId') + ->will($this->returnValue(123)); + + $loader = $this->getMock('\OCP\Files\Storage\IStorageFactory'); + $loader->expects($this->once()) + ->method('getInstance') + ->will($this->returnValue($storage)); + + $mountPoint = new \OC\Files\Mount\MountPoint( + // just use this because a real class is needed + '\Test\Files\Mount\MountPointTest', + '/mountpoint', + null, + $loader + ); + + $this->assertEquals($storage, $mountPoint->getStorage()); + $this->assertEquals(123, $mountPoint->getStorageId()); + $this->assertEquals('/mountpoint/', $mountPoint->getMountPoint()); + + $mountPoint->setMountPoint('another'); + $this->assertEquals('/another/', $mountPoint->getMountPoint()); + } + + public function testInvalidStorage() { + $loader = $this->getMock('\OCP\Files\Storage\IStorageFactory'); + $loader->expects($this->once()) + ->method('getInstance') + ->will($this->throwException(new \Exception('Test storage init exception'))); + + $called = false; + $wrapper = function($mountPoint, $storage) use ($called) { + $called = true; + }; + + $mountPoint = new \OC\Files\Mount\MountPoint( + // just use this because a real class is needed + '\Test\Files\Mount\MountPointTest', + '/mountpoint', + null, + $loader + ); + + $this->assertNull($mountPoint->getStorage()); + // call it again to make sure the init code only ran once + $this->assertNull($mountPoint->getStorage()); + + $this->assertNull($mountPoint->getStorageId()); + + // wrapping doesn't fail + $mountPoint->wrapStorage($wrapper); + + $this->assertNull($mountPoint->getStorage()); + + // storage wrapper never called + $this->assertFalse($called); + } +} diff --git a/tests/lib/Files/Mount/MountTest.php b/tests/lib/Files/Mount/MountTest.php new file mode 100644 index 00000000000..f21095f2fa5 --- /dev/null +++ b/tests/lib/Files/Mount/MountTest.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Mount; + + +use OC\Files\Storage\StorageFactory; +use OC\Files\Storage\Wrapper\Wrapper; + +class MountTest extends \Test\TestCase { + public function testFromStorageObject() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->disableOriginalConstructor() + ->getMock(); + $mount = new \OC\Files\Mount\MountPoint($storage, '/foo'); + $this->assertInstanceOf('\OC\Files\Storage\Temporary', $mount->getStorage()); + } + + public function testFromStorageClassname() { + $mount = new \OC\Files\Mount\MountPoint('\OC\Files\Storage\Temporary', '/foo'); + $this->assertInstanceOf('\OC\Files\Storage\Temporary', $mount->getStorage()); + } + + public function testWrapper() { + $test = $this; + $wrapper = function ($mountPoint, $storage) use (&$test) { + $test->assertEquals('/foo/', $mountPoint); + $test->assertInstanceOf('\OC\Files\Storage\Storage', $storage); + return new Wrapper(array('storage' => $storage)); + }; + + $loader = new StorageFactory(); + $loader->addStorageWrapper('test_wrapper', $wrapper); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->disableOriginalConstructor() + ->getMock(); + $mount = new \OC\Files\Mount\MountPoint($storage, '/foo', array(), $loader); + $this->assertInstanceOf('\OC\Files\Storage\Wrapper\Wrapper', $mount->getStorage()); + } +} diff --git a/tests/lib/Files/Node/FileTest.php b/tests/lib/Files/Node/FileTest.php new file mode 100644 index 00000000000..180c7b12ce4 --- /dev/null +++ b/tests/lib/Files/Node/FileTest.php @@ -0,0 +1,680 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Node; + +use OC\Files\FileInfo; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OC\Files\View; + +class FileTest extends \Test\TestCase { + private $user; + + protected function setUp() { + parent::setUp(); + $this->user = new \OC\User\User('', new \Test\Util\User\Dummy); + } + + protected function getMockStorage() { + $storage = $this->getMock('\OCP\Files\Storage'); + $storage->expects($this->any()) + ->method('getId') + ->will($this->returnValue('home::someuser')); + return $storage; + } + + protected function getFileInfo($data) { + return new FileInfo('', $this->getMockStorage(), '', $data, null); + } + + public function testDelete() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->exactly(2)) + ->method('emit') + ->will($this->returnValue(true)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $view->expects($this->once()) + ->method('unlink') + ->with('/bar/foo') + ->will($this->returnValue(true)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->delete(); + } + + public function testDeleteHooks() { + $test = $this; + $hooksRun = 0; + /** + * @param \OC\Files\Node\File $node + */ + $preListener = function ($node) use (&$test, &$hooksRun) { + $test->assertInstanceOf('\OC\Files\Node\File', $node); + $test->assertEquals('foo', $node->getInternalPath()); + $test->assertEquals('/bar/foo', $node->getPath()); + $test->assertEquals(1, $node->getId()); + $hooksRun++; + }; + + /** + * @param \OC\Files\Node\File $node + */ + $postListener = function ($node) use (&$test, &$hooksRun) { + $test->assertInstanceOf('\OC\Files\Node\NonExistingFile', $node); + $test->assertEquals('foo', $node->getInternalPath()); + $test->assertEquals('/bar/foo', $node->getPath()); + $test->assertEquals(1, $node->getId()); + $test->assertEquals('text/plain', $node->getMimeType()); + $hooksRun++; + }; + + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + $root->listen('\OC\Files', 'preDelete', $preListener); + $root->listen('\OC\Files', 'postDelete', $postListener); + + $view->expects($this->any()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL, 'fileid' => 1, 'mimetype' => 'text/plain')))); + + $view->expects($this->once()) + ->method('unlink') + ->with('/bar/foo') + ->will($this->returnValue(true)); + + $view->expects($this->any()) + ->method('resolvePath') + ->with('/bar/foo') + ->will($this->returnValue(array(null, 'foo'))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->delete(); + $this->assertEquals(2, $hooksRun); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testDeleteNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->delete(); + } + + public function testGetContent() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $hook = function ($file) { + throw new \Exception('Hooks are not supposed to be called'); + }; + + $root->listen('\OC\Files', 'preWrite', $hook); + $root->listen('\OC\Files', 'postWrite', $hook); + + $view->expects($this->once()) + ->method('file_get_contents') + ->with('/bar/foo') + ->will($this->returnValue('bar')); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals('bar', $node->getContent()); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testGetContentNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => 0)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->getContent(); + } + + public function testPutContent() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $view->expects($this->once()) + ->method('file_put_contents') + ->with('/bar/foo', 'bar') + ->will($this->returnValue(true)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->putContent('bar'); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testPutContentNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->putContent('bar'); + } + + public function testGetMimeType() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('mimetype' => 'text/plain')))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals('text/plain', $node->getMimeType()); + } + + public function testFOpenRead() { + $stream = fopen('php://memory', 'w+'); + fwrite($stream, 'bar'); + rewind($stream); + + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $hook = function ($file) { + throw new \Exception('Hooks are not supposed to be called'); + }; + + $root->listen('\OC\Files', 'preWrite', $hook); + $root->listen('\OC\Files', 'postWrite', $hook); + + $view->expects($this->once()) + ->method('fopen') + ->with('/bar/foo', 'r') + ->will($this->returnValue($stream)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $fh = $node->fopen('r'); + $this->assertEquals($stream, $fh); + $this->assertEquals('bar', fread($fh, 3)); + } + + public function testFOpenWrite() { + $stream = fopen('php://memory', 'w+'); + + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, new $view, $this->user); + + $hooksCalled = 0; + $hook = function ($file) use (&$hooksCalled) { + $hooksCalled++; + }; + + $root->listen('\OC\Files', 'preWrite', $hook); + $root->listen('\OC\Files', 'postWrite', $hook); + + $view->expects($this->once()) + ->method('fopen') + ->with('/bar/foo', 'w') + ->will($this->returnValue($stream)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $fh = $node->fopen('w'); + $this->assertEquals($stream, $fh); + fwrite($fh, 'bar'); + rewind($fh); + $this->assertEquals('bar', fread($stream, 3)); + $this->assertEquals(2, $hooksCalled); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testFOpenReadNotPermitted() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $hook = function ($file) { + throw new \Exception('Hooks are not supposed to be called'); + }; + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => 0)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->fopen('r'); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testFOpenReadWriteNoReadPermissions() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $hook = function () { + throw new \Exception('Hooks are not supposed to be called'); + }; + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_UPDATE)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->fopen('w'); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testFOpenReadWriteNoWritePermissions() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, new $view, $this->user); + + $hook = function () { + throw new \Exception('Hooks are not supposed to be called'); + }; + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $node->fopen('w'); + } + + public function testCopySameStorage() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->any()) + ->method('copy') + ->with('/bar/foo', '/bar/asd'); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL, 'fileid' => 3)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\Folder($root, $view, '/bar'); + $newNode = new \OC\Files\Node\File($root, $view, '/bar/asd'); + + $root->expects($this->exactly(2)) + ->method('get') + ->will($this->returnValueMap(array( + array('/bar/asd', $newNode), + array('/bar', $parentNode) + ))); + + $target = $node->copy('/bar/asd'); + $this->assertInstanceOf('\OC\Files\Node\File', $target); + $this->assertEquals(3, $target->getId()); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testCopyNotPermitted() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + /** + * @var \OC\Files\Storage\Storage | \PHPUnit_Framework_MockObject_MockObject $storage + */ + $storage = $this->getMock('\OC\Files\Storage\Storage'); + + $root->expects($this->never()) + ->method('getMount'); + + $storage->expects($this->never()) + ->method('copy'); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ, 'fileid' => 3)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\Folder($root, $view, '/bar'); + + $root->expects($this->once()) + ->method('get') + ->will($this->returnValueMap(array( + array('/bar', $parentNode) + ))); + + $node->copy('/bar/asd'); + } + + /** + * @expectedException \OCP\Files\NotFoundException + */ + public function testCopyNoParent() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->never()) + ->method('copy'); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + + $root->expects($this->once()) + ->method('get') + ->with('/bar/asd') + ->will($this->throwException(new NotFoundException())); + + $node->copy('/bar/asd/foo'); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testCopyParentIsFile() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->never()) + ->method('copy'); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\File($root, $view, '/bar'); + + $root->expects($this->once()) + ->method('get') + ->will($this->returnValueMap(array( + array('/bar', $parentNode) + ))); + + $node->copy('/bar/asd'); + } + + public function testMoveSameStorage() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->any()) + ->method('rename') + ->with('/bar/foo', '/bar/asd'); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL, 'fileid' => 1)))); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\Folder($root, $view, '/bar'); + + $root->expects($this->any()) + ->method('get') + ->will($this->returnValueMap(array(array('/bar', $parentNode), array('/bar/asd', $node)))); + + $target = $node->move('/bar/asd'); + $this->assertInstanceOf('\OC\Files\Node\File', $target); + $this->assertEquals(1, $target->getId()); + $this->assertEquals('/bar/asd', $node->getPath()); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testMoveNotPermitted() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $view->expects($this->never()) + ->method('rename'); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\Folder($root, $view, '/bar'); + + $root->expects($this->once()) + ->method('get') + ->with('/bar') + ->will($this->returnValue($parentNode)); + + $node->move('/bar/asd'); + } + + /** + * @expectedException \OCP\Files\NotFoundException + */ + public function testMoveNoParent() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + /** + * @var \OC\Files\Storage\Storage | \PHPUnit_Framework_MockObject_MockObject $storage + */ + $storage = $this->getMock('\OC\Files\Storage\Storage'); + + $storage->expects($this->never()) + ->method('rename'); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\Folder($root, $view, '/bar'); + + $root->expects($this->once()) + ->method('get') + ->with('/bar') + ->will($this->throwException(new NotFoundException())); + + $node->move('/bar/asd'); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testMoveParentIsFile() { + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $view->expects($this->never()) + ->method('rename'); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $parentNode = new \OC\Files\Node\File($root, $view, '/bar'); + + $root->expects($this->once()) + ->method('get') + ->with('/bar') + ->will($this->returnValue($parentNode)); + + $node->move('/bar/asd'); + } +} diff --git a/tests/lib/Files/Node/FolderTest.php b/tests/lib/Files/Node/FolderTest.php new file mode 100644 index 00000000000..7ce9fff1419 --- /dev/null +++ b/tests/lib/Files/Node/FolderTest.php @@ -0,0 +1,795 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Node; + +use OC\Files\Cache\Cache; +use OC\Files\FileInfo; +use OC\Files\Mount\MountPoint; +use OC\Files\Node\Node; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OC\Files\View; + +/** + * Class FolderTest + * + * @group DB + * + * @package Test\Files\Node + */ +class FolderTest extends \Test\TestCase { + private $user; + + protected function setUp() { + parent::setUp(); + $this->user = new \OC\User\User('', new \Test\Util\User\Dummy); + } + + protected function getMockStorage() { + $storage = $this->getMock('\OCP\Files\Storage'); + $storage->expects($this->any()) + ->method('getId') + ->will($this->returnValue('home::someuser')); + return $storage; + } + + protected function getFileInfo($data) { + return new FileInfo('', $this->getMockStorage(), '', $data, null); + } + + public function testDelete() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $root->expects($this->exactly(2)) + ->method('emit') + ->will($this->returnValue(true)); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $view->expects($this->once()) + ->method('rmdir') + ->with('/bar/foo') + ->will($this->returnValue(true)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $node->delete(); + } + + public function testDeleteHooks() { + $test = $this; + $hooksRun = 0; + /** + * @param \OC\Files\Node\File $node + */ + $preListener = function ($node) use (&$test, &$hooksRun) { + $test->assertInstanceOf('\OC\Files\Node\Folder', $node); + $test->assertEquals('foo', $node->getInternalPath()); + $test->assertEquals('/bar/foo', $node->getPath()); + $hooksRun++; + }; + + /** + * @param \OC\Files\Node\File $node + */ + $postListener = function ($node) use (&$test, &$hooksRun) { + $test->assertInstanceOf('\OC\Files\Node\NonExistingFolder', $node); + $test->assertEquals('foo', $node->getInternalPath()); + $test->assertEquals('/bar/foo', $node->getPath()); + $test->assertEquals(1, $node->getId()); + $hooksRun++; + }; + + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + $root->listen('\OC\Files', 'preDelete', $preListener); + $root->listen('\OC\Files', 'postDelete', $postListener); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL, 'fileid' => 1)))); + + $view->expects($this->once()) + ->method('rmdir') + ->with('/bar/foo') + ->will($this->returnValue(true)); + + $view->expects($this->any()) + ->method('resolvePath') + ->with('/bar/foo') + ->will($this->returnValue(array(null, 'foo'))); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $node->delete(); + $this->assertEquals(2, $hooksRun); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testDeleteNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $node->delete(); + } + + public function testGetDirectoryContent() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->any()) + ->method('getDirectoryContent') + ->with('/bar/foo') + ->will($this->returnValue(array( + new FileInfo('/bar/foo/asd', null, 'foo/asd', ['fileid' => 2, 'path' => '/bar/foo/asd', 'name' => 'asd', 'size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain'], null), + new FileInfo('/bar/foo/qwerty', null, 'foo/qwerty', ['fileid' => 3, 'path' => '/bar/foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'httpd/unix-directory'], null) + ))); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $children = $node->getDirectoryListing(); + $this->assertEquals(2, count($children)); + $this->assertInstanceOf('\OC\Files\Node\File', $children[0]); + $this->assertInstanceOf('\OC\Files\Node\Folder', $children[1]); + $this->assertEquals('asd', $children[0]->getName()); + $this->assertEquals('qwerty', $children[1]->getName()); + $this->assertEquals(2, $children[0]->getId()); + $this->assertEquals(3, $children[1]->getId()); + } + + public function testGet() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $root->expects($this->once()) + ->method('get') + ->with('/bar/foo/asd'); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $node->get('asd'); + } + + public function testNodeExists() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $child = new \OC\Files\Node\Folder($root, $view, '/bar/foo/asd'); + + $root->expects($this->once()) + ->method('get') + ->with('/bar/foo/asd') + ->will($this->returnValue($child)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $this->assertTrue($node->nodeExists('asd')); + } + + public function testNodeExistsNotExists() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $root->expects($this->once()) + ->method('get') + ->with('/bar/foo/asd') + ->will($this->throwException(new NotFoundException())); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $this->assertFalse($node->nodeExists('asd')); + } + + public function testNewFolder() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $view->expects($this->once()) + ->method('mkdir') + ->with('/bar/foo/asd') + ->will($this->returnValue(true)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $child = new \OC\Files\Node\Folder($root, $view, '/bar/foo/asd'); + $result = $node->newFolder('asd'); + $this->assertEquals($child, $result); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testNewFolderNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $node->newFolder('asd'); + } + + public function testNewFile() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $view->expects($this->once()) + ->method('touch') + ->with('/bar/foo/asd') + ->will($this->returnValue(true)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $child = new \OC\Files\Node\File($root, $view, '/bar/foo/asd'); + $result = $node->newFile('asd'); + $this->assertEquals($child, $result); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testNewFileNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $node->newFile('asd'); + } + + public function testGetFreeSpace() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('free_space') + ->with('/bar/foo') + ->will($this->returnValue(100)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $this->assertEquals(100, $node->getFreeSpace()); + } + + public function testSearch() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $mount->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + $mount->expects($this->once()) + ->method('getInternalPath') + ->will($this->returnValue('foo')); + + $cache->expects($this->once()) + ->method('search') + ->with('%qw%') + ->will($this->returnValue(array( + array('fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain') + ))); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('/bar/foo') + ->will($this->returnValue(array())); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar/foo') + ->will($this->returnValue($mount)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $result = $node->search('qw'); + $this->assertEquals(1, count($result)); + $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath()); + } + + public function testSearchInRoot() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $mount->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + $mount->expects($this->once()) + ->method('getInternalPath') + ->will($this->returnValue('files')); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $cache->expects($this->once()) + ->method('search') + ->with('%qw%') + ->will($this->returnValue(array( + array('fileid' => 3, 'path' => 'files/foo', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain'), + array('fileid' => 3, 'path' => 'files_trashbin/foo2.d12345', 'name' => 'foo2.d12345', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain'), + ))); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('') + ->will($this->returnValue(array())); + + $root->expects($this->once()) + ->method('getMount') + ->with('') + ->will($this->returnValue($mount)); + + $result = $root->search('qw'); + $this->assertEquals(1, count($result)); + $this->assertEquals('/foo', $result[0]->getPath()); + } + + public function testSearchInStorageRoot() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $mount->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + $mount->expects($this->once()) + ->method('getInternalPath') + ->will($this->returnValue('')); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $cache->expects($this->once()) + ->method('search') + ->with('%qw%') + ->will($this->returnValue(array( + array('fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain') + ))); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('/bar') + ->will($this->returnValue(array())); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar') + ->will($this->returnValue($mount)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar'); + $result = $node->search('qw'); + $this->assertEquals(1, count($result)); + $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath()); + } + + public function testSearchByTag() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $mount->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + $mount->expects($this->once()) + ->method('getInternalPath') + ->will($this->returnValue('foo')); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $cache->expects($this->once()) + ->method('searchByTag') + ->with('tag1', 'user1') + ->will($this->returnValue(array( + array('fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain') + ))); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('/bar/foo') + ->will($this->returnValue(array())); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar/foo') + ->will($this->returnValue($mount)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $result = $node->searchByTag('tag1', 'user1'); + $this->assertEquals(1, count($result)); + $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath()); + } + + public function testSearchSubStorages() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + $subCache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + $subStorage = $this->getMock('\OC\Files\Storage\Storage'); + $subMount = $this->getMock('\OC\Files\Mount\MountPoint', array(), array(null, '')); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $mount->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + $mount->expects($this->once()) + ->method('getInternalPath') + ->will($this->returnValue('foo')); + + $subMount->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($subStorage)); + + $subMount->expects($this->once()) + ->method('getMountPoint') + ->will($this->returnValue('/bar/foo/bar/')); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $subStorage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($subCache)); + + $cache->expects($this->once()) + ->method('search') + ->with('%qw%') + ->will($this->returnValue(array( + array('fileid' => 3, 'path' => 'foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain') + ))); + + $subCache->expects($this->once()) + ->method('search') + ->with('%qw%') + ->will($this->returnValue(array( + array('fileid' => 4, 'path' => 'asd/qweasd', 'name' => 'qweasd', 'size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain') + ))); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('/bar/foo') + ->will($this->returnValue(array($subMount))); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar/foo') + ->will($this->returnValue($mount)); + + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $result = $node->search('qw'); + $this->assertEquals(2, count($result)); + } + + public function testIsSubNode() { + $file = new Node(null, null, '/foo/bar'); + $folder = new \OC\Files\Node\Folder(null, null, '/foo'); + $this->assertTrue($folder->isSubNode($file)); + $this->assertFalse($folder->isSubNode($folder)); + + $file = new Node(null, null, '/foobar'); + $this->assertFalse($folder->isSubNode($file)); + } + + public function testGetById() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $mount = new MountPoint($storage, '/bar'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $view->expects($this->once()) + ->method('getFileInfo') + ->will($this->returnValue(new FileInfo('/bar/foo/qwerty', null, 'qwerty', ['mimetype' => 'text/plain'], null))); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $cache->expects($this->once()) + ->method('getPathById') + ->with('1') + ->will($this->returnValue('foo/qwerty')); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('/bar/foo') + ->will($this->returnValue(array())); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar/foo') + ->will($this->returnValue($mount)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $result = $node->getById(1); + $this->assertEquals(1, count($result)); + $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath()); + } + + public function testGetByIdOutsideFolder() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $mount = new MountPoint($storage, '/bar'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $cache->expects($this->once()) + ->method('getPathById') + ->with('1') + ->will($this->returnValue('foobar')); + + $root->expects($this->once()) + ->method('getMountsIn') + ->with('/bar/foo') + ->will($this->returnValue(array())); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar/foo') + ->will($this->returnValue($mount)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $result = $node->getById(1); + $this->assertCount(0, $result); + } + + public function testGetByIdMultipleStorages() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + $storage = $this->getMock('\OC\Files\Storage\Storage'); + $mount1 = new MountPoint($storage, '/bar'); + $mount2 = new MountPoint($storage, '/bar/foo/asd'); + $cache = $this->getMock('\OC\Files\Cache\Cache', array(), array('')); + + $view->expects($this->any()) + ->method('getFileInfo') + ->will($this->returnValue(new FileInfo('/bar/foo/qwerty', null, 'qwerty', ['mimetype' => 'plain'], null))); + + $storage->expects($this->any()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $cache->expects($this->any()) + ->method('getPathById') + ->with('1') + ->will($this->returnValue('foo/qwerty')); + + $root->expects($this->any()) + ->method('getMountsIn') + ->with('/bar/foo') + ->will($this->returnValue(array($mount2))); + + $root->expects($this->once()) + ->method('getMount') + ->with('/bar/foo') + ->will($this->returnValue($mount1)); + + $node = new \OC\Files\Node\Folder($root, $view, '/bar/foo'); + $result = $node->getById(1); + $this->assertEquals(2, count($result)); + $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath()); + $this->assertEquals('/bar/foo/asd/foo/qwerty', $result[1]->getPath()); + } + + public function uniqueNameProvider() { + return [ + // input, existing, expected + ['foo', [] , 'foo'], + ['foo', ['foo'] , 'foo (2)'], + ['foo', ['foo', 'foo (2)'] , 'foo (3)'] + ]; + } + + /** + * @dataProvider uniqueNameProvider + */ + public function testGetUniqueName($name, $existingFiles, $expected) { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + $folderPath = '/bar/foo'; + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + + $view->expects($this->any()) + ->method('file_exists') + ->will($this->returnCallback(function ($path) use ($existingFiles, $folderPath) { + foreach ($existingFiles as $existing) { + if ($folderPath . '/' . $existing === $path){ + return true; + } + } + return false; + })); + + $node = new \OC\Files\Node\Folder($root, $view, $folderPath); + $this->assertEquals($expected, $node->getNonExistingName($name)); + } +} diff --git a/tests/lib/Files/Node/HookConnectorTest.php b/tests/lib/Files/Node/HookConnectorTest.php new file mode 100644 index 00000000000..7245dd37593 --- /dev/null +++ b/tests/lib/Files/Node/HookConnectorTest.php @@ -0,0 +1,203 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Node; + +use OC\Files\Filesystem; +use OC\Files\Node\Root; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OCP\Files\Node; +use Test\TestCase; +use Test\Traits\MountProviderTrait; +use Test\Traits\UserTrait; + +/** + * Class HookConnectorTest + * + * @group DB + * + * @package Test\Files\Node + */ +class HookConnectorTest extends TestCase { + use UserTrait; + use MountProviderTrait; + + /** + * @var View + */ + private $view; + + /** + * @var Root + */ + private $root; + + /** + * @var string + */ + private $userId; + + public function setUp() { + parent::setUp(); + $this->userId = $this->getUniqueID(); + $this->createUser($this->userId, 'pass'); + $this->registerMount($this->userId, new Temporary(), '/' . $this->userId . '/files/'); + \OC_Util::setupFS($this->userId); + $this->view = new View(); + $this->root = new Root(Filesystem::getMountManager(), $this->view, \OC::$server->getUserManager()->get($this->userId)); + } + + public function tearDown() { + parent::tearDown(); + \OC_Hook::clear('OC_Filesystem'); + \OC_Util::tearDownFS(); + } + + public function viewToNodeProvider() { + return [ + [function () { + Filesystem::file_put_contents('test.txt', 'asd'); + }, 'preWrite'], + [function () { + Filesystem::file_put_contents('test.txt', 'asd'); + }, 'postWrite'], + [function () { + Filesystem::file_put_contents('test.txt', 'asd'); + }, 'preCreate'], + [function () { + Filesystem::file_put_contents('test.txt', 'asd'); + }, 'postCreate'], + [function () { + Filesystem::mkdir('test.txt'); + }, 'preCreate'], + [function () { + Filesystem::mkdir('test.txt'); + }, 'postCreate'], + [function () { + Filesystem::touch('test.txt'); + }, 'preTouch'], + [function () { + Filesystem::touch('test.txt'); + }, 'postTouch'], + [function () { + Filesystem::touch('test.txt'); + }, 'preCreate'], + [function () { + Filesystem::touch('test.txt'); + }, 'postCreate'], + [function () { + Filesystem::file_put_contents('test.txt', 'asd'); + Filesystem::unlink('test.txt'); + }, 'preDelete'], + [function () { + Filesystem::file_put_contents('test.txt', 'asd'); + Filesystem::unlink('test.txt'); + }, 'postDelete'], + [function () { + Filesystem::mkdir('test.txt'); + Filesystem::rmdir('test.txt'); + }, 'preDelete'], + [function () { + Filesystem::mkdir('test.txt'); + Filesystem::rmdir('test.txt'); + }, 'postDelete'], + ]; + } + + /** + * @param callable $operation + * @param string $expectedHook + * @dataProvider viewToNodeProvider + */ + public function testViewToNode(callable $operation, $expectedHook) { + $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $connector->viewToNode(); + $hookCalled = false; + /** @var Node $hookNode */ + $hookNode = null; + + $this->root->listen('\OC\Files', $expectedHook, function ($node) use (&$hookNode, &$hookCalled) { + $hookCalled = true; + $hookNode = $node; + }); + + $operation(); + + $this->assertTrue($hookCalled); + $this->assertEquals('/' . $this->userId . '/files/test.txt', $hookNode->getPath()); + } + + public function viewToNodeProviderCopyRename() { + return [ + [function () { + Filesystem::file_put_contents('source', 'asd'); + Filesystem::rename('source', 'target'); + }, 'preRename'], + [function () { + Filesystem::file_put_contents('source', 'asd'); + Filesystem::rename('source', 'target'); + }, 'postRename'], + [function () { + Filesystem::file_put_contents('source', 'asd'); + Filesystem::copy('source', 'target'); + }, 'preCopy'], + [function () { + Filesystem::file_put_contents('source', 'asd'); + Filesystem::copy('source', 'target'); + }, 'postCopy'], + ]; + } + + /** + * @param callable $operation + * @param string $expectedHook + * @dataProvider viewToNodeProviderCopyRename + */ + public function testViewToNodeCopyRename(callable $operation, $expectedHook) { + $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $connector->viewToNode(); + $hookCalled = false; + /** @var Node $hookSourceNode */ + $hookSourceNode = null; + /** @var Node $hookTargetNode */ + $hookTargetNode = null; + + $this->root->listen('\OC\Files', $expectedHook, function ($sourceNode, $targetNode) use (&$hookCalled, &$hookSourceNode, &$hookTargetNode) { + $hookCalled = true; + $hookSourceNode = $sourceNode; + $hookTargetNode = $targetNode; + }); + + $operation(); + + $this->assertTrue($hookCalled); + $this->assertEquals('/' . $this->userId . '/files/source', $hookSourceNode->getPath()); + $this->assertEquals('/' . $this->userId . '/files/target', $hookTargetNode->getPath()); + } + + public function testPostDeleteMeta() { + $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $connector->viewToNode(); + $hookCalled = false; + /** @var Node $hookNode */ + $hookNode = null; + + $this->root->listen('\OC\Files', 'postDelete', function ($node) use (&$hookNode, &$hookCalled) { + $hookCalled = true; + $hookNode = $node; + }); + + Filesystem::file_put_contents('test.txt', 'asd'); + $info = Filesystem::getFileInfo('test.txt'); + Filesystem::unlink('test.txt'); + + $this->assertTrue($hookCalled); + $this->assertEquals($hookNode->getId(), $info->getId()); + } +} diff --git a/tests/lib/Files/Node/IntegrationTest.php b/tests/lib/Files/Node/IntegrationTest.php new file mode 100644 index 00000000000..f52e0623e14 --- /dev/null +++ b/tests/lib/Files/Node/IntegrationTest.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Node; + +use OC\Files\Node\Root; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OC\User\User; + +/** + * Class IntegrationTest + * + * @group DB + * + * @package Test\Files\Node + */ +class IntegrationTest extends \Test\TestCase { + /** + * @var \OC\Files\Node\Root $root + */ + private $root; + + /** + * @var \OC\Files\Storage\Storage[] + */ + private $storages; + + /** + * @var \OC\Files\View $view + */ + private $view; + + protected function setUp() { + parent::setUp(); + + $manager = \OC\Files\Filesystem::getMountManager(); + + \OC_Hook::clear('OC_Filesystem'); + + $user = new User($this->getUniqueID('user'), new \Test\Util\User\Dummy); + $this->loginAsUser($user->getUID()); + + $this->view = new View(); + $this->root = new Root($manager, $this->view, $user); + $storage = new Temporary(array()); + $subStorage = new Temporary(array()); + $this->storages[] = $storage; + $this->storages[] = $subStorage; + $this->root->mount($storage, '/'); + $this->root->mount($subStorage, '/substorage/'); + } + + protected function tearDown() { + foreach ($this->storages as $storage) { + $storage->getCache()->clear(); + } + + $this->logout(); + parent::tearDown(); + } + + public function testBasicFile() { + $file = $this->root->newFile('/foo.txt'); + $this->assertCount(2, $this->root->getDirectoryListing()); + $this->assertTrue($this->root->nodeExists('/foo.txt')); + $id = $file->getId(); + $this->assertInstanceOf('\OC\Files\Node\File', $file); + $file->putContent('qwerty'); + $this->assertEquals('text/plain', $file->getMimeType()); + $this->assertEquals('qwerty', $file->getContent()); + $this->assertFalse($this->root->nodeExists('/bar.txt')); + $target = $file->move('/bar.txt'); + $this->assertEquals($id, $target->getId()); + $this->assertEquals($id, $file->getId()); + $this->assertFalse($this->root->nodeExists('/foo.txt')); + $this->assertTrue($this->root->nodeExists('/bar.txt')); + $this->assertEquals('bar.txt', $file->getName()); + $this->assertEquals('bar.txt', $file->getInternalPath()); + + $file->move('/substorage/bar.txt'); + $this->assertEquals($id, $file->getId()); + $this->assertEquals('qwerty', $file->getContent()); + } + + public function testBasicFolder() { + $folder = $this->root->newFolder('/foo'); + $this->assertTrue($this->root->nodeExists('/foo')); + $file = $folder->newFile('/bar'); + $this->assertTrue($this->root->nodeExists('/foo/bar')); + $file->putContent('qwerty'); + + $listing = $folder->getDirectoryListing(); + $this->assertEquals(1, count($listing)); + $this->assertEquals($file->getId(), $listing[0]->getId()); + $this->assertEquals($file->getStorage(), $listing[0]->getStorage()); + + + $rootListing = $this->root->getDirectoryListing(); + $this->assertEquals(2, count($rootListing)); + + $folder->move('/asd'); + /** + * @var \OC\Files\Node\File $file + */ + $file = $folder->get('/bar'); + $this->assertInstanceOf('\OC\Files\Node\File', $file); + $this->assertFalse($this->root->nodeExists('/foo/bar')); + $this->assertTrue($this->root->nodeExists('/asd/bar')); + $this->assertEquals('qwerty', $file->getContent()); + $folder->move('/substorage/foo'); + /** + * @var \OC\Files\Node\File $file + */ + $file = $folder->get('/bar'); + $this->assertInstanceOf('\OC\Files\Node\File', $file); + $this->assertTrue($this->root->nodeExists('/substorage/foo/bar')); + $this->assertEquals('qwerty', $file->getContent()); + } +} diff --git a/tests/lib/Files/Node/NodeTest.php b/tests/lib/Files/Node/NodeTest.php new file mode 100644 index 00000000000..a9575507b4f --- /dev/null +++ b/tests/lib/Files/Node/NodeTest.php @@ -0,0 +1,366 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Node; + +use OC\Files\FileInfo; + +class NodeTest extends \Test\TestCase { + private $user; + + protected function setUp() { + parent::setUp(); + $this->user = new \OC\User\User('', new \Test\Util\User\Dummy); + } + + protected function getMockStorage() { + $storage = $this->getMock('\OCP\Files\Storage'); + $storage->expects($this->any()) + ->method('getId') + ->will($this->returnValue('home::someuser')); + return $storage; + } + + protected function getFileInfo($data) { + return new FileInfo('', $this->getMockStorage(), '', $data, null); + } + + public function testStat() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $stat = array( + 'fileid' => 1, + 'size' => 100, + 'etag' => 'qwerty', + 'mtime' => 50, + 'permissions' => 0 + ); + + $view->expects($this->once()) + ->method('stat') + ->with('/bar/foo') + ->will($this->returnValue($stat)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals($stat, $node->stat()); + } + + public function testGetId() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $stat = $this->getFileInfo(array( + 'fileid' => 1, + 'size' => 100, + 'etag' => 'qwerty', + 'mtime' => 50 + )); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($stat)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals(1, $node->getId()); + } + + public function testGetSize() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + + $stat = $this->getFileInfo(array( + 'fileid' => 1, + 'size' => 100, + 'etag' => 'qwerty', + 'mtime' => 50 + )); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($stat)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals(100, $node->getSize()); + } + + public function testGetEtag() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $stat = $this->getFileInfo(array( + 'fileid' => 1, + 'size' => 100, + 'etag' => 'qwerty', + 'mtime' => 50 + )); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($stat)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals('qwerty', $node->getEtag()); + } + + public function testGetMTime() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $stat = $this->getFileInfo(array( + 'fileid' => 1, + 'size' => 100, + 'etag' => 'qwerty', + 'mtime' => 50 + )); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($stat)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals(50, $node->getMTime()); + } + + public function testGetStorage() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + /** + * @var \OC\Files\Storage\Storage | \PHPUnit_Framework_MockObject_MockObject $storage + */ + $storage = $this->getMock('\OC\Files\Storage\Storage'); + + $view->expects($this->once()) + ->method('resolvePath') + ->with('/bar/foo') + ->will($this->returnValue(array($storage, 'foo'))); + + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals($storage, $node->getStorage()); + } + + public function testGetPath() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals('/bar/foo', $node->getPath()); + } + + public function testGetInternalPath() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + /** + * @var \OC\Files\Storage\Storage | \PHPUnit_Framework_MockObject_MockObject $storage + */ + $storage = $this->getMock('\OC\Files\Storage\Storage'); + + $view->expects($this->once()) + ->method('resolvePath') + ->with('/bar/foo') + ->will($this->returnValue(array($storage, 'foo'))); + + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals('foo', $node->getInternalPath()); + } + + public function testGetName() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $node = new \OC\Files\Node\File($root, $view, '/bar/foo'); + $this->assertEquals('foo', $node->getName()); + } + + public function testTouchSetMTime() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->once()) + ->method('touch') + ->with('/bar/foo', 100) + ->will($this->returnValue(true)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $node = new \OC\Files\Node\Node($root, $view, '/bar/foo'); + $node->touch(100); + $this->assertEquals(100, $node->getMTime()); + } + + public function testTouchHooks() { + $test = $this; + $hooksRun = 0; + /** + * @param \OC\Files\Node\File $node + */ + $preListener = function ($node) use (&$test, &$hooksRun) { + $test->assertEquals('foo', $node->getInternalPath()); + $test->assertEquals('/bar/foo', $node->getPath()); + $hooksRun++; + }; + + /** + * @param \OC\Files\Node\File $node + */ + $postListener = function ($node) use (&$test, &$hooksRun) { + $test->assertEquals('foo', $node->getInternalPath()); + $test->assertEquals('/bar/foo', $node->getPath()); + $hooksRun++; + }; + + /** + * @var \OC\Files\Mount\Manager $manager + */ + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + $root->listen('\OC\Files', 'preTouch', $preListener); + $root->listen('\OC\Files', 'postTouch', $postListener); + + $view->expects($this->once()) + ->method('touch') + ->with('/bar/foo', 100) + ->will($this->returnValue(true)); + + $view->expects($this->any()) + ->method('resolvePath') + ->with('/bar/foo') + ->will($this->returnValue(array(null, 'foo'))); + + $view->expects($this->any()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_ALL)))); + + $node = new \OC\Files\Node\Node($root, $view, '/bar/foo'); + $node->touch(100); + $this->assertEquals(2, $hooksRun); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testTouchNotPermitted() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + $root->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $view->expects($this->any()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('permissions' => \OCP\Constants::PERMISSION_READ)))); + + $node = new \OC\Files\Node\Node($root, $view, '/bar/foo'); + $node->touch(100); + } + + /** + * @expectedException \OCP\Files\InvalidPathException + */ + public function testInvalidPath() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = $this->getMock('\OC\Files\Node\Root', array(), array($manager, $view, $this->user)); + + $node = new \OC\Files\Node\Node($root, $view, '/../foo'); + $node->getFileInfo(); + } +} diff --git a/tests/lib/Files/Node/RootTest.php b/tests/lib/Files/Node/RootTest.php new file mode 100644 index 00000000000..1b4824cba76 --- /dev/null +++ b/tests/lib/Files/Node/RootTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Node; + +use OC\Files\FileInfo; +use OCP\Files\NotPermittedException; +use OC\Files\Mount\Manager; + +/** + * @group DB + */ +class RootTest extends \Test\TestCase { + private $user; + + protected function setUp() { + parent::setUp(); + $this->user = new \OC\User\User('', new \Test\Util\User\Dummy); + } + + protected function getFileInfo($data) { + return new FileInfo('', null, '', $data, null); + } + + public function testGet() { + $manager = new Manager(); + /** + * @var \OC\Files\Storage\Storage $storage + */ + $storage = $this->getMock('\OC\Files\Storage\Storage'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue($this->getFileInfo(array('fileid' => 10, 'path' => 'bar/foo', 'name', 'mimetype' => 'text/plain')))); + + $root->mount($storage, ''); + $node = $root->get('/bar/foo'); + $this->assertEquals(10, $node->getId()); + $this->assertInstanceOf('\OC\Files\Node\File', $node); + } + + /** + * @expectedException \OCP\Files\NotFoundException + */ + public function testGetNotFound() { + $manager = new Manager(); + /** + * @var \OC\Files\Storage\Storage $storage + */ + $storage = $this->getMock('\OC\Files\Storage\Storage'); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with('/bar/foo') + ->will($this->returnValue(false)); + + $root->mount($storage, ''); + $root->get('/bar/foo'); + } + + /** + * @expectedException \OCP\Files\NotPermittedException + */ + public function testGetInvalidPath() { + $manager = new Manager(); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $root->get('/../foo'); + } + + /** + * @expectedException \OCP\Files\NotFoundException + */ + public function testGetNoStorages() { + $manager = new Manager(); + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + $root = new \OC\Files\Node\Root($manager, $view, $this->user); + + $root->get('/bar/foo'); + } +} diff --git a/tests/lib/Files/ObjectStore/NoopScannerTest.php b/tests/lib/Files/ObjectStore/NoopScannerTest.php new file mode 100644 index 00000000000..16bd325a8df --- /dev/null +++ b/tests/lib/Files/ObjectStore/NoopScannerTest.php @@ -0,0 +1,76 @@ +<?php +/** + * ownCloud + * + * @author Joas Schilling + * @copyright 2015 Joas Schilling nickvergessen@owncloud.com + * + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace Test\Files\ObjectStore; + +class NoopScannerTest extends \Test\TestCase { + /** @var \OC\Files\Storage\Storage $storage */ + private $storage; + + /** @var \OC\Files\ObjectStore\NoopScanner $scanner */ + private $scanner; + + protected function setUp() { + parent::setUp(); + + $this->storage = new \OC\Files\Storage\Temporary(array()); + $this->scanner = new \OC\Files\ObjectStore\NoopScanner($this->storage); + } + + function testFile() { + $data = "dummy file data\n"; + $this->storage->file_put_contents('foo.txt', $data); + + $this->assertEquals( + [], + $this->scanner->scanFile('foo.txt'), + 'Asserting that no error occurred while scanFile()' + ); + } + + private function fillTestFolders() { + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->mkdir('folder'); + $this->storage->file_put_contents('foo.txt', $textData); + $this->storage->file_put_contents('foo.png', $imgData); + $this->storage->file_put_contents('folder/bar.txt', $textData); + } + + function testFolder() { + $this->fillTestFolders(); + + $this->assertEquals( + [], + $this->scanner->scan(''), + 'Asserting that no error occurred while scan()' + ); + } + + function testBackgroundScan() { + $this->fillTestFolders(); + $this->storage->mkdir('folder2'); + $this->storage->file_put_contents('folder2/bar.txt', 'foobar'); + + $this->assertEquals( + [], + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW), + 'Asserting that no error occurred while scan(SCAN_SHALLOW)' + ); + + $this->scanner->backgroundScan(); + + $this->assertTrue( + true, + 'Asserting that no error occurred while backgroundScan()' + ); + } +} diff --git a/tests/lib/Files/ObjectStore/SwiftTest.php b/tests/lib/Files/ObjectStore/SwiftTest.php new file mode 100644 index 00000000000..9f69e040f87 --- /dev/null +++ b/tests/lib/Files/ObjectStore/SwiftTest.php @@ -0,0 +1,198 @@ +<?php +/** + * @author Jörn Friedrich Dreyer + * @copyright (c) 2014 Jörn Friedrich Dreyer <jfd@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Files\ObjectStore; + +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\ObjectStore\Swift; + +/** + * Class SwiftTest + * + * @group DB + * + * @package Test\Files\Cache\ObjectStore + */ +class SwiftTest extends \Test\Files\Storage\Storage { + + /** + * @var Swift + */ + private $objectStorage; + + protected function setUp() { + parent::setUp(); + + if (!getenv('RUN_OBJECTSTORE_TESTS')) { + $this->markTestSkipped('objectstore tests are unreliable in some environments'); + } + + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // create users + $users = array('test'); + foreach($users as $userName) { + $user = \OC::$server->getUserManager()->get($userName); + if ($user !== null) { $user->delete(); } + \OC::$server->getUserManager()->createUser($userName, $userName); + } + + // main test user + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::tearDown(); + \OC_User::setUserId('test'); + + $config = \OC::$server->getConfig()->getSystemValue('objectstore'); + $this->objectStorage = new Swift($config['arguments']); + $config['objectstore'] = $this->objectStorage; + $this->instance = new ObjectStoreStorage($config); + } + + protected function tearDown() { + if (is_null($this->instance)) { + return; + } + $this->objectStorage->deleteContainer(true); + $this->instance->getCache()->clear(); + + $users = array('test'); + foreach($users as $userName) { + $user = \OC::$server->getUserManager()->get($userName); + if ($user !== null) { $user->delete(); } + } + parent::tearDown(); + } + + public function testStat() { + + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $ctimeStart = time(); + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + $this->assertTrue($this->instance->isReadable('/lorem.txt')); + $ctimeEnd = time(); + $mTime = $this->instance->filemtime('/lorem.txt'); + + // check that ($ctimeStart - 5) <= $mTime <= ($ctimeEnd + 1) + $this->assertGreaterThanOrEqual(($ctimeStart - 5), $mTime); + $this->assertLessThanOrEqual(($ctimeEnd + 1), $mTime); + $this->assertEquals(filesize($textFile), $this->instance->filesize('/lorem.txt')); + + $stat = $this->instance->stat('/lorem.txt'); + //only size and mtime are required in the result + $this->assertEquals($stat['size'], $this->instance->filesize('/lorem.txt')); + $this->assertEquals($stat['mtime'], $mTime); + + if ($this->instance->touch('/lorem.txt', 100) !== false) { + $mTime = $this->instance->filemtime('/lorem.txt'); + $this->assertEquals($mTime, 100); + } + } + + public function testCheckUpdate() { + $this->markTestSkipped('Detecting external changes is not supported on object storages'); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testMove($source, $target) { + $this->initSourceAndTarget($source); + $sourceId = $this->instance->getCache()->getId(ltrim('/',$source)); + $this->assertNotEquals(-1, $sourceId); + + $this->instance->rename($source, $target); + + $this->assertTrue($this->instance->file_exists($target), $target.' was not created'); + $this->assertFalse($this->instance->file_exists($source), $source.' still exists'); + $this->assertSameAsLorem($target); + + $targetId = $this->instance->getCache()->getId(ltrim('/',$target)); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } + + public function testRenameDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $this->instance->file_put_contents('source/test2.txt', 'qwerty'); + $this->instance->mkdir('source/subfolder'); + $this->instance->file_put_contents('source/subfolder/test.txt', 'bar'); + $sourceId = $this->instance->getCache()->getId('source'); + $this->assertNotEquals(-1, $sourceId); + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertFalse($this->instance->file_exists('source/test2.txt')); + $this->assertFalse($this->instance->file_exists('source/subfolder')); + $this->assertFalse($this->instance->file_exists('source/subfolder/test.txt')); + + $this->assertTrue($this->instance->file_exists('target')); + $this->assertTrue($this->instance->file_exists('target/test1.txt')); + $this->assertTrue($this->instance->file_exists('target/test2.txt')); + $this->assertTrue($this->instance->file_exists('target/subfolder')); + $this->assertTrue($this->instance->file_exists('target/subfolder/test.txt')); + + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $this->assertEquals('qwerty', $this->instance->file_get_contents('target/test2.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents('target/subfolder/test.txt')); + $targetId = $this->instance->getCache()->getId('target'); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } + + public function testRenameOverWriteDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $sourceId = $this->instance->getCache()->getId('source'); + $this->assertNotEquals(-1, $sourceId); + + $this->instance->mkdir('target'); + $this->instance->file_put_contents('target/test1.txt', 'bar'); + $this->instance->file_put_contents('target/test2.txt', 'bar'); + + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertFalse($this->instance->file_exists('target/test2.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $targetId = $this->instance->getCache()->getId('target'); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } + + public function testRenameOverWriteDirectoryOverFile() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $sourceId = $this->instance->getCache()->getId('source'); + $this->assertNotEquals(-1, $sourceId); + + $this->instance->file_put_contents('target', 'bar'); + + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $targetId = $this->instance->getCache()->getId('target'); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } +} diff --git a/tests/lib/Files/PathVerificationTest.php b/tests/lib/Files/PathVerificationTest.php new file mode 100644 index 00000000000..9ce9416455f --- /dev/null +++ b/tests/lib/Files/PathVerificationTest.php @@ -0,0 +1,270 @@ +<?php +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ + +namespace Test\Files; + +use OC\Files\Storage\Local; +use OC\Files\View; + +/** + * Class PathVerificationTest + * + * @group DB + * + * @package Test\Files + */ +class PathVerificationTest extends \Test\TestCase { + + /** + * @var \OC\Files\View + */ + private $view; + + protected function setUp() { + parent::setUp(); + $this->view = new View(); + } + + /** + * @expectedException \OCP\Files\InvalidPathException + * @expectedExceptionMessage File name is too long + */ + public function testPathVerificationFileNameTooLong() { + $fileName = str_repeat('a', 500); + $this->view->verifyPath('', $fileName); + } + + + /** + * @dataProvider providesEmptyFiles + * @expectedException \OCP\Files\InvalidPathException + * @expectedExceptionMessage Empty filename is not allowed + */ + public function testPathVerificationEmptyFileName($fileName) { + $this->view->verifyPath('', $fileName); + } + + public function providesEmptyFiles() { + return [ + [''], + [' '], + ]; + } + + /** + * @dataProvider providesDotFiles + * @expectedException \OCP\Files\InvalidPathException + * @expectedExceptionMessage Dot files are not allowed + */ + public function testPathVerificationDotFiles($fileName) { + $this->view->verifyPath('', $fileName); + } + + public function providesDotFiles() { + return [ + ['.'], + ['..'], + [' .'], + [' ..'], + ['. '], + ['.. '], + [' . '], + [' .. '], + ]; + } + + /** + * @dataProvider providesAstralPlane + * @expectedException \OCP\Files\InvalidPathException + * @expectedExceptionMessage 4-byte characters are not supported in file names + */ + public function testPathVerificationAstralPlane($fileName) { + $this->view->verifyPath('', $fileName); + } + + public function providesAstralPlane() { + return [ + // this is the monkey emoji - http://en.wikipedia.org/w/index.php?title=%F0%9F%90%B5&redirect=no + ['🐵'], + ['🐵.txt'], + ['txt.💩'], + ['💩🐵.txt'], + ['💩🐵'], + ]; + } + + /** + * @dataProvider providesInvalidCharsWindows + * @expectedException \OCP\Files\InvalidCharacterInPathException + */ + public function testPathVerificationInvalidCharsWindows($fileName) { + $storage = new Local(['datadir' => '']); + + $fileName = " 123{$fileName}456 "; + self::invokePrivate($storage, 'verifyWindowsPath', [$fileName]); + } + + public function providesInvalidCharsWindows() { + return [ + [\chr(0)], + [\chr(1)], + [\chr(2)], + [\chr(3)], + [\chr(4)], + [\chr(5)], + [\chr(6)], + [\chr(7)], + [\chr(8)], + [\chr(9)], + [\chr(10)], + [\chr(11)], + [\chr(12)], + [\chr(13)], + [\chr(14)], + [\chr(15)], + [\chr(16)], + [\chr(17)], + [\chr(18)], + [\chr(19)], + [\chr(20)], + [\chr(21)], + [\chr(22)], + [\chr(23)], + [\chr(24)], + [\chr(25)], + [\chr(26)], + [\chr(27)], + [\chr(28)], + [\chr(29)], + [\chr(30)], + [\chr(31)], + ['<'], + ['>'], + [':'], + ['"'], + ['/'], + ['\\'], + ['|'], + ['?'], + ['*'], + ]; + } + + /** + * @dataProvider providesInvalidCharsPosix + * @expectedException \OCP\Files\InvalidCharacterInPathException + */ + public function testPathVerificationInvalidCharsPosix($fileName) { + $storage = new Local(['datadir' => '']); + + $fileName = " 123{$fileName}456 "; + self::invokePrivate($storage, 'verifyWindowsPath', [$fileName]); + } + + public function providesInvalidCharsPosix() { + return [ + [\chr(0)], + [\chr(1)], + [\chr(2)], + [\chr(3)], + [\chr(4)], + [\chr(5)], + [\chr(6)], + [\chr(7)], + [\chr(8)], + [\chr(9)], + [\chr(10)], + [\chr(11)], + [\chr(12)], + [\chr(13)], + [\chr(14)], + [\chr(15)], + [\chr(16)], + [\chr(17)], + [\chr(18)], + [\chr(19)], + [\chr(20)], + [\chr(21)], + [\chr(22)], + [\chr(23)], + [\chr(24)], + [\chr(25)], + [\chr(26)], + [\chr(27)], + [\chr(28)], + [\chr(29)], + [\chr(30)], + [\chr(31)], + ['/'], + ['\\'], + ]; + } + + /** + * @dataProvider providesReservedNamesWindows + * @expectedException \OCP\Files\ReservedWordException + */ + public function testPathVerificationReservedNamesWindows($fileName) { + $storage = new Local(['datadir' => '']); + + self::invokePrivate($storage, 'verifyWindowsPath', [$fileName]); + } + + public function providesReservedNamesWindows() { + return [ + [' CON '], + ['prn '], + ['AUX'], + ['NUL'], + ['COM1'], + ['COM2'], + ['COM3'], + ['COM4'], + ['COM5'], + ['COM6'], + ['COM7'], + ['COM8'], + ['COM9'], + ['LPT1'], + ['LPT2'], + ['LPT3'], + ['LPT4'], + ['LPT5'], + ['LPT6'], + ['LPT7'], + ['LPT8'], + ['LPT9'] + ]; + } + + /** + * @dataProvider providesValidPosixPaths + */ + public function testPathVerificationValidPaths($fileName) { + $storage = new Local(['datadir' => '']); + + self::invokePrivate($storage, 'verifyPosixPath', [$fileName]); + self::invokePrivate($storage, 'verifyWindowsPath', [$fileName]); + // nothing thrown + $this->assertTrue(true); + } + + public function providesValidPosixPaths() { + return [ + ['simple'], + ['simple.txt'], + ['\''], + ['`'], + ['%'], + ['()'], + ['[]'], + ['!'], + ['$'], + ['_'], + ]; + } +} diff --git a/tests/lib/Files/Storage/CommonTest.php b/tests/lib/Files/Storage/CommonTest.php new file mode 100644 index 00000000000..38faa9b0b21 --- /dev/null +++ b/tests/lib/Files/Storage/CommonTest.php @@ -0,0 +1,48 @@ +<?php +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2012 Robin Appelman icewind@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +namespace Test\Files\Storage; + +/** + * Class CommonTest + * + * @group DB + * + * @package Test\Files\Storage + */ +class CommonTest extends Storage { + /** + * @var string tmpDir + */ + private $tmpDir; + protected function setUp() { + parent::setUp(); + + $this->tmpDir = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->instance=new \OC\Files\Storage\CommonTest(array('datadir'=>$this->tmpDir)); + } + + protected function tearDown() { + \OC_Helper::rmdirr($this->tmpDir); + parent::tearDown(); + } +} diff --git a/tests/lib/Files/Storage/CopyDirectoryTest.php b/tests/lib/Files/Storage/CopyDirectoryTest.php new file mode 100644 index 00000000000..74b8148aa36 --- /dev/null +++ b/tests/lib/Files/Storage/CopyDirectoryTest.php @@ -0,0 +1,53 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Files\Storage; + +use OC\Files\Storage\Temporary; + +class StorageNoRecursiveCopy extends Temporary { + public function copy($path1, $path2) { + if ($this->is_dir($path1)) { + return false; + } + return copy($this->getSourcePath($path1), $this->getSourcePath($path2)); + } +} + +class CopyDirectoryStorage extends StorageNoRecursiveCopy { + use \OC\Files\Storage\PolyFill\CopyDirectory; +} + +/** + * Class CopyDirectoryTest + * + * @group DB + * + * @package Test\Files\Storage + */ +class CopyDirectoryTest extends Storage { + + protected function setUp() { + parent::setUp(); + $this->instance = new CopyDirectoryStorage([]); + } +} + diff --git a/tests/lib/Files/Storage/HomeStorageQuotaTest.php b/tests/lib/Files/Storage/HomeStorageQuotaTest.php new file mode 100644 index 00000000000..68fa4bb0bbb --- /dev/null +++ b/tests/lib/Files/Storage/HomeStorageQuotaTest.php @@ -0,0 +1,81 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Files\Storage; + +/** + * Class HomeStorageQuotaTest + * + * @group DB + */ +class HomeStorageQuotaTest extends \Test\TestCase { + /** + * Tests that the home storage is not wrapped when no quota exists. + */ + function testHomeStorageWrapperWithoutQuota() { + $user1 = $this->getUniqueID(); + \OC::$server->getUserManager()->createUser($user1, 'test'); + \OC::$server->getConfig()->setUserValue($user1, 'files', 'quota', 'none'); + \OC_User::setUserId($user1); + + \OC_Util::setupFS($user1); + + $userMount = \OC\Files\Filesystem::getMountManager()->find('/' . $user1 . '/'); + $this->assertNotNull($userMount); + $this->assertNotInstanceOf('\OC\Files\Storage\Wrapper\Quota', $userMount->getStorage()); + + // clean up + \OC_User::setUserId(''); + $user = \OC::$server->getUserManager()->get($user1); + if ($user !== null) { $user->delete(); } + \OC::$server->getConfig()->deleteAllUserValues($user1); + \OC_Util::tearDownFS(); + } + + /** + * Tests that the home storage is not wrapped when no quota exists. + */ + function testHomeStorageWrapperWithQuota() { + $user1 = $this->getUniqueID(); + \OC::$server->getUserManager()->createUser($user1, 'test'); + \OC::$server->getConfig()->setUserValue($user1, 'files', 'quota', '1024'); + \OC_User::setUserId($user1); + + \OC_Util::setupFS($user1); + + $userMount = \OC\Files\Filesystem::getMountManager()->find('/' . $user1 . '/'); + $this->assertNotNull($userMount); + $this->assertTrue($userMount->getStorage()->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')); + + // ensure that root wasn't wrapped + $rootMount = \OC\Files\Filesystem::getMountManager()->find('/'); + $this->assertNotNull($rootMount); + $this->assertNotInstanceOf('\OC\Files\Storage\Wrapper\Quota', $rootMount->getStorage()); + + // clean up + \OC_User::setUserId(''); + $user = \OC::$server->getUserManager()->get($user1); + if ($user !== null) { $user->delete(); } + \OC::$server->getConfig()->deleteAllUserValues($user1); + \OC_Util::tearDownFS(); + } + +} diff --git a/tests/lib/Files/Storage/HomeTest.php b/tests/lib/Files/Storage/HomeTest.php new file mode 100644 index 00000000000..d9a1b11849e --- /dev/null +++ b/tests/lib/Files/Storage/HomeTest.php @@ -0,0 +1,109 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Files\Storage; + +use OC\User\User; + +class DummyUser extends User { + private $home; + + private $uid; + + /** + * @param string $uid + * @param string $home + */ + public function __construct($uid, $home) { + $this->uid = $uid; + $this->home = $home; + } + + public function getHome() { + return $this->home; + } + + public function getUID() { + return $this->uid; + } +} + +/** + * Class Home + * + * @group DB + * + * @package Test\Files\Storage + */ +class HomeTest extends Storage { + /** + * @var string tmpDir + */ + private $tmpDir; + + private $userId; + + /** + * @var \OC\User\User $user + */ + private $user; + + protected function setUp() { + parent::setUp(); + + $this->tmpDir = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->userId = $this->getUniqueID('user_'); + $this->user = new DummyUser($this->userId, $this->tmpDir); + $this->instance = new \OC\Files\Storage\Home(array('user' => $this->user)); + } + + protected function tearDown() { + \OC_Helper::rmdirr($this->tmpDir); + parent::tearDown(); + } + + /** + * Tests that the home id is in the format home::user1 + */ + public function testId() { + $this->assertEquals('home::' . $this->userId, $this->instance->getId()); + } + + /** + * Tests that the legacy home id is in the format local::/path/to/datadir/user1/ + */ + public function testLegacyId() { + $this->instance = new \OC\Files\Storage\Home(array('user' => $this->user, 'legacy' => true)); + $this->assertEquals('local::' . $this->tmpDir . '/', $this->instance->getId()); + } + + /** + * Tests that getCache() returns an instance of HomeCache + */ + public function testGetCacheReturnsHomeCache() { + $this->assertInstanceOf('\OC\Files\Cache\HomeCache', $this->instance->getCache()); + } + + public function testGetOwner() { + $this->assertEquals($this->userId, $this->instance->getOwner('')); + } +} diff --git a/tests/lib/Files/Storage/LocalTest.php b/tests/lib/Files/Storage/LocalTest.php new file mode 100644 index 00000000000..7b8ae6a24b2 --- /dev/null +++ b/tests/lib/Files/Storage/LocalTest.php @@ -0,0 +1,88 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Files\Storage; + +/** + * Class LocalTest + * + * @group DB + * + * @package Test\Files\Storage + */ +class LocalTest extends Storage { + /** + * @var string tmpDir + */ + private $tmpDir; + + protected function setUp() { + parent::setUp(); + + $this->tmpDir = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->instance = new \OC\Files\Storage\Local(array('datadir' => $this->tmpDir)); + } + + protected function tearDown() { + \OC_Helper::rmdirr($this->tmpDir); + parent::tearDown(); + } + + public function testStableEtag() { + if (\OC_Util::runningOnWindows()) { + $this->markTestSkipped('[Windows] On Windows platform we have no stable etag generation - yet'); + } + + $this->instance->file_put_contents('test.txt', 'foobar'); + $etag1 = $this->instance->getETag('test.txt'); + $etag2 = $this->instance->getETag('test.txt'); + $this->assertEquals($etag1, $etag2); + } + + public function testEtagChange() { + if (\OC_Util::runningOnWindows()) { + $this->markTestSkipped('[Windows] On Windows platform we have no stable etag generation - yet'); + } + + $this->instance->file_put_contents('test.txt', 'foo'); + $this->instance->touch('test.txt', time() - 2); + $etag1 = $this->instance->getETag('test.txt'); + $this->instance->file_put_contents('test.txt', 'bar'); + $etag2 = $this->instance->getETag('test.txt'); + $this->assertNotEquals($etag1, $etag2); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidArgumentsEmptyArray() { + new \OC\Files\Storage\Local([]); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidArgumentsNoArray() { + new \OC\Files\Storage\Local(null); + } +} + diff --git a/tests/lib/Files/Storage/Storage.php b/tests/lib/Files/Storage/Storage.php new file mode 100644 index 00000000000..ed2ea87f9d9 --- /dev/null +++ b/tests/lib/Files/Storage/Storage.php @@ -0,0 +1,617 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Files\Storage; + +use OC\Files\Cache\Watcher; + +abstract class Storage extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Storage instance + */ + protected $instance; + protected $waitDelay = 0; + + /** + * Sleep for the number of seconds specified in the + * $waitDelay attribute + */ + protected function wait() { + if ($this->waitDelay > 0) { + sleep($this->waitDelay); + } + } + + /** + * the root folder of the storage should always exist, be readable and be recognized as a directory + */ + public function testRoot() { + $this->assertTrue($this->instance->file_exists('/'), 'Root folder does not exist'); + $this->assertTrue($this->instance->isReadable('/'), 'Root folder is not readable'); + $this->assertTrue($this->instance->is_dir('/'), 'Root folder is not a directory'); + $this->assertFalse($this->instance->is_file('/'), 'Root folder is a file'); + $this->assertEquals('dir', $this->instance->filetype('/')); + + //without this, any further testing would be useless, not an actual requirement for filestorage though + $this->assertTrue($this->instance->isUpdatable('/'), 'Root folder is not writable'); + } + + /** + * Check that the test() function works + */ + public function testTestFunction() { + $this->assertTrue($this->instance->test()); + } + + /** + * @dataProvider directoryProvider + */ + public function testDirectories($directory) { + $this->assertFalse($this->instance->file_exists('/' . $directory)); + + $this->assertTrue($this->instance->mkdir('/' . $directory)); + + $this->assertTrue($this->instance->file_exists('/' . $directory)); + $this->assertTrue($this->instance->is_dir('/' . $directory)); + $this->assertFalse($this->instance->is_file('/' . $directory)); + $this->assertEquals('dir', $this->instance->filetype('/' . $directory)); + $this->assertEquals(0, $this->instance->filesize('/' . $directory)); + $this->assertTrue($this->instance->isReadable('/' . $directory)); + $this->assertTrue($this->instance->isUpdatable('/' . $directory)); + + $dh = $this->instance->opendir('/'); + $content = array(); + while ($file = readdir($dh)) { + if ($file != '.' and $file != '..') { + $content[] = $file; + } + } + $this->assertEquals(array($directory), $content); + + $this->assertFalse($this->instance->mkdir('/' . $directory)); //can't create existing folders + $this->assertTrue($this->instance->rmdir('/' . $directory)); + + $this->wait(); + $this->assertFalse($this->instance->file_exists('/' . $directory)); + + $this->assertFalse($this->instance->rmdir('/' . $directory)); //can't remove non existing folders + + $dh = $this->instance->opendir('/'); + $content = array(); + while ($file = readdir($dh)) { + if ($file != '.' and $file != '..') { + $content[] = $file; + } + } + $this->assertEquals(array(), $content); + } + + public function directoryProvider() { + return [ + ['folder'], + [' folder'], + ['folder '], + ['folder with space'], + ['spéciäl földer'], + ['test single\'quote'], + ]; + } + + function loremFileProvider() { + $root = \OC::$SERVERROOT . '/tests/data/'; + return array( + // small file + array($root . 'lorem.txt'), + // bigger file (> 8 KB which is the standard PHP block size) + array($root . 'lorem-big.txt') + ); + } + + /** + * test the various uses of file_get_contents and file_put_contents + * + * @dataProvider loremFileProvider + */ + public function testGetPutContents($sourceFile) { + $sourceText = file_get_contents($sourceFile); + + //fill a file with string data + $this->instance->file_put_contents('/lorem.txt', $sourceText); + $this->assertFalse($this->instance->is_dir('/lorem.txt')); + $this->assertEquals($sourceText, $this->instance->file_get_contents('/lorem.txt'), 'data returned from file_get_contents is not equal to the source data'); + + //empty the file + $this->instance->file_put_contents('/lorem.txt', ''); + $this->assertEquals('', $this->instance->file_get_contents('/lorem.txt'), 'file not emptied'); + } + + /** + * test various known mimetypes + */ + public function testMimeType() { + $this->assertEquals('httpd/unix-directory', $this->instance->getMimeType('/')); + $this->assertEquals(false, $this->instance->getMimeType('/non/existing/file')); + + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile, 'r')); + $this->assertEquals('text/plain', $this->instance->getMimeType('/lorem.txt')); + + $pngFile = \OC::$SERVERROOT . '/tests/data/desktopapp.png'; + $this->instance->file_put_contents('/desktopapp.png', file_get_contents($pngFile, 'r')); + $this->assertEquals('image/png', $this->instance->getMimeType('/desktopapp.png')); + + $svgFile = \OC::$SERVERROOT . '/tests/data/desktopapp.svg'; + $this->instance->file_put_contents('/desktopapp.svg', file_get_contents($svgFile, 'r')); + $this->assertEquals('image/svg+xml', $this->instance->getMimeType('/desktopapp.svg')); + } + + + public function copyAndMoveProvider() { + return [ + ['/source.txt', '/target.txt'], + ['/source.txt', '/target with space.txt'], + ['/source with space.txt', '/target.txt'], + ['/source with space.txt', '/target with space.txt'], + ['/source.txt', '/tärgét.txt'], + ['/sòurcē.txt', '/target.txt'], + ['/sòurcē.txt', '/tärgét.txt'], + ['/single \' quote.txt', '/tar\'get.txt'], + ]; + } + + public function initSourceAndTarget($source, $target = null) { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->instance->file_put_contents($source, file_get_contents($textFile)); + if ($target) { + $testContents = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $this->instance->file_put_contents($target, $testContents); + } + } + + public function assertSameAsLorem($file) { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->assertEquals( + file_get_contents($textFile), + $this->instance->file_get_contents($file), + 'Expected ' . $file . ' to be a copy of ' . $textFile + ); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testCopy($source, $target) { + $this->initSourceAndTarget($source); + + $this->instance->copy($source, $target); + + $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); + $this->assertSameAsLorem($target); + $this->assertTrue($this->instance->file_exists($source), $source . ' was deleted'); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testMove($source, $target) { + $this->initSourceAndTarget($source); + + $this->instance->rename($source, $target); + + $this->wait(); + $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); + $this->assertFalse($this->instance->file_exists($source), $source . ' still exists'); + $this->assertSameAsLorem($target); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testCopyOverwrite($source, $target) { + $this->initSourceAndTarget($source, $target); + + $this->instance->copy($source, $target); + + $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); + $this->assertTrue($this->instance->file_exists($source), $source . ' was deleted'); + $this->assertSameAsLorem($target); + $this->assertSameAsLorem($source); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testMoveOverwrite($source, $target) { + $this->initSourceAndTarget($source, $target); + + $this->instance->rename($source, $target); + + $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); + $this->assertFalse($this->instance->file_exists($source), $source . ' still exists'); + $this->assertSameAsLorem($target); + } + + public function testLocal() { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + $localFile = $this->instance->getLocalFile('/lorem.txt'); + $this->assertTrue(file_exists($localFile)); + $this->assertEquals(file_get_contents($textFile), file_get_contents($localFile)); + + $this->instance->mkdir('/folder'); + $this->instance->file_put_contents('/folder/lorem.txt', file_get_contents($textFile)); + $this->instance->file_put_contents('/folder/bar.txt', 'asd'); + $this->instance->mkdir('/folder/recursive'); + $this->instance->file_put_contents('/folder/recursive/file.txt', 'foo'); + + // test below require to use instance->getLocalFile because the physical storage might be different + $localFile = $this->instance->getLocalFile('/folder/lorem.txt'); + $this->assertTrue(file_exists($localFile)); + $this->assertEquals(file_get_contents($localFile), file_get_contents($textFile)); + + $localFile = $this->instance->getLocalFile('/folder/bar.txt'); + $this->assertTrue(file_exists($localFile)); + $this->assertEquals(file_get_contents($localFile), 'asd'); + + $localFile = $this->instance->getLocalFile('/folder/recursive/file.txt'); + $this->assertTrue(file_exists($localFile)); + $this->assertEquals(file_get_contents($localFile), 'foo'); + } + + public function testStat() { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $ctimeStart = time(); + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + $this->assertTrue($this->instance->isReadable('/lorem.txt')); + $ctimeEnd = time(); + $mTime = $this->instance->filemtime('/lorem.txt'); + $this->assertTrue($this->instance->hasUpdated('/lorem.txt', $ctimeStart - 5)); + $this->assertTrue($this->instance->hasUpdated('/', $ctimeStart - 5)); + + // check that ($ctimeStart - 5) <= $mTime <= ($ctimeEnd + 1) + $this->assertGreaterThanOrEqual(($ctimeStart - 5), $mTime); + $this->assertLessThanOrEqual(($ctimeEnd + 1), $mTime); + $this->assertEquals(filesize($textFile), $this->instance->filesize('/lorem.txt')); + + $stat = $this->instance->stat('/lorem.txt'); + //only size and mtime are required in the result + $this->assertEquals($stat['size'], $this->instance->filesize('/lorem.txt')); + $this->assertEquals($stat['mtime'], $mTime); + + if ($this->instance->touch('/lorem.txt', 100) !== false) { + $mTime = $this->instance->filemtime('/lorem.txt'); + $this->assertEquals($mTime, 100); + } + + $mtimeStart = time(); + + $this->instance->unlink('/lorem.txt'); + $this->assertTrue($this->instance->hasUpdated('/', $mtimeStart - 5)); + } + + /** + * Test whether checkUpdate properly returns false when there was + * no change. + */ + public function testCheckUpdate() { + if ($this->instance instanceof \OC\Files\Storage\Wrapper\Wrapper) { + $this->markTestSkipped('Cannot test update check on wrappers'); + } + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $watcher = $this->instance->getWatcher(); + $watcher->setPolicy(Watcher::CHECK_ALWAYS); + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + $this->assertTrue($watcher->checkUpdate('/lorem.txt'), 'Update detected'); + $this->assertFalse($watcher->checkUpdate('/lorem.txt'), 'No update'); + } + + public function testUnlink() { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + + $this->assertTrue($this->instance->file_exists('/lorem.txt')); + + $this->assertTrue($this->instance->unlink('/lorem.txt')); + $this->wait(); + + $this->assertFalse($this->instance->file_exists('/lorem.txt')); + } + + public function testFOpen() { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + + $fh = @$this->instance->fopen('foo', 'r'); + if ($fh) { + fclose($fh); + } + $this->assertFalse($fh); + $this->assertFalse($this->instance->file_exists('foo')); + + $fh = $this->instance->fopen('foo', 'w'); + fwrite($fh, file_get_contents($textFile)); + fclose($fh); + $this->assertTrue($this->instance->file_exists('foo')); + + $fh = $this->instance->fopen('foo', 'r'); + $content = stream_get_contents($fh); + $this->assertEquals(file_get_contents($textFile), $content); + } + + public function testTouchCreateFile() { + $this->assertFalse($this->instance->file_exists('touch')); + // returns true on success + $this->assertTrue($this->instance->touch('touch')); + $this->assertTrue($this->instance->file_exists('touch')); + } + + public function testRecursiveRmdir() { + $this->instance->mkdir('folder'); + $this->instance->mkdir('folder/bar'); + $this->wait(); + $this->instance->file_put_contents('folder/asd.txt', 'foobar'); + $this->instance->file_put_contents('folder/bar/foo.txt', 'asd'); + $this->assertTrue($this->instance->rmdir('folder')); + $this->wait(); + $this->assertFalse($this->instance->file_exists('folder/asd.txt')); + $this->assertFalse($this->instance->file_exists('folder/bar/foo.txt')); + $this->assertFalse($this->instance->file_exists('folder/bar')); + $this->assertFalse($this->instance->file_exists('folder')); + } + + public function testRmdirEmptyFolder() { + $this->assertTrue($this->instance->mkdir('empty')); + $this->wait(); + $this->assertTrue($this->instance->rmdir('empty')); + $this->assertFalse($this->instance->file_exists('empty')); + } + + public function testRecursiveUnlink() { + $this->instance->mkdir('folder'); + $this->instance->mkdir('folder/bar'); + $this->instance->file_put_contents('folder/asd.txt', 'foobar'); + $this->instance->file_put_contents('folder/bar/foo.txt', 'asd'); + $this->assertTrue($this->instance->unlink('folder')); + $this->wait(); + $this->assertFalse($this->instance->file_exists('folder/asd.txt')); + $this->assertFalse($this->instance->file_exists('folder/bar/foo.txt')); + $this->assertFalse($this->instance->file_exists('folder/bar')); + $this->assertFalse($this->instance->file_exists('folder')); + } + + public function hashProvider() { + return array( + array('Foobar', 'md5'), + array('Foobar', 'sha1'), + array('Foobar', 'sha256'), + ); + } + + /** + * @dataProvider hashProvider + */ + public function testHash($data, $type) { + $this->instance->file_put_contents('hash.txt', $data); + $this->assertEquals(hash($type, $data), $this->instance->hash($type, 'hash.txt')); + $this->assertEquals(hash($type, $data, true), $this->instance->hash($type, 'hash.txt', true)); + } + + public function testHashInFileName() { + $this->instance->file_put_contents('#test.txt', 'data'); + $this->assertEquals('data', $this->instance->file_get_contents('#test.txt')); + + $this->instance->mkdir('#foo'); + $this->instance->file_put_contents('#foo/test.txt', 'data'); + $this->assertEquals('data', $this->instance->file_get_contents('#foo/test.txt')); + + $dh = $this->instance->opendir('#foo'); + $content = array(); + while ($file = readdir($dh)) { + if ($file != '.' and $file != '..') { + $content[] = $file; + } + } + + $this->assertEquals(array('test.txt'), $content); + } + + public function testCopyOverWriteFile() { + $this->instance->file_put_contents('target.txt', 'foo'); + $this->instance->file_put_contents('source.txt', 'bar'); + $this->instance->copy('source.txt', 'target.txt'); + $this->assertEquals('bar', $this->instance->file_get_contents('target.txt')); + } + + public function testRenameOverWriteFile() { + $this->instance->file_put_contents('target.txt', 'foo'); + $this->instance->file_put_contents('source.txt', 'bar'); + $this->instance->rename('source.txt', 'target.txt'); + $this->assertEquals('bar', $this->instance->file_get_contents('target.txt')); + $this->assertFalse($this->instance->file_exists('source.txt')); + } + + public function testRenameDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $this->instance->file_put_contents('source/test2.txt', 'qwerty'); + $this->instance->mkdir('source/subfolder'); + $this->instance->file_put_contents('source/subfolder/test.txt', 'bar'); + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertFalse($this->instance->file_exists('source/test2.txt')); + $this->assertFalse($this->instance->file_exists('source/subfolder')); + $this->assertFalse($this->instance->file_exists('source/subfolder/test.txt')); + + $this->assertTrue($this->instance->file_exists('target')); + $this->assertTrue($this->instance->file_exists('target/test1.txt')); + $this->assertTrue($this->instance->file_exists('target/test2.txt')); + $this->assertTrue($this->instance->file_exists('target/subfolder')); + $this->assertTrue($this->instance->file_exists('target/subfolder/test.txt')); + + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $this->assertEquals('qwerty', $this->instance->file_get_contents('target/test2.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents('target/subfolder/test.txt')); + } + + public function testRenameOverWriteDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + + $this->instance->mkdir('target'); + $this->instance->file_put_contents('target/test1.txt', 'bar'); + $this->instance->file_put_contents('target/test2.txt', 'bar'); + + $this->assertTrue($this->instance->rename('source', 'target'), 'rename must return true on success'); + + $this->assertFalse($this->instance->file_exists('source'), 'source has not been removed'); + $this->assertFalse($this->instance->file_exists('source/test1.txt'), 'source/test1.txt has not been removed'); + $this->assertFalse($this->instance->file_exists('target/test2.txt'), 'target/test2.txt has not been removed'); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt'), 'target/test1.txt has not been overwritten'); + } + + public function testRenameOverWriteDirectoryOverFile() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + + $this->instance->file_put_contents('target', 'bar'); + + $this->assertTrue($this->instance->rename('source', 'target'), 'rename must return true on success'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + } + + public function testCopyDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $this->instance->file_put_contents('source/test2.txt', 'qwerty'); + $this->instance->mkdir('source/subfolder'); + $this->instance->file_put_contents('source/subfolder/test.txt', 'bar'); + $this->instance->copy('source', 'target'); + + $this->assertTrue($this->instance->file_exists('source')); + $this->assertTrue($this->instance->file_exists('source/test1.txt')); + $this->assertTrue($this->instance->file_exists('source/test2.txt')); + $this->assertTrue($this->instance->file_exists('source/subfolder')); + $this->assertTrue($this->instance->file_exists('source/subfolder/test.txt')); + + $this->assertTrue($this->instance->file_exists('target')); + $this->assertTrue($this->instance->file_exists('target/test1.txt')); + $this->assertTrue($this->instance->file_exists('target/test2.txt')); + $this->assertTrue($this->instance->file_exists('target/subfolder')); + $this->assertTrue($this->instance->file_exists('target/subfolder/test.txt')); + + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $this->assertEquals('qwerty', $this->instance->file_get_contents('target/test2.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents('target/subfolder/test.txt')); + } + + public function testCopyOverWriteDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + + $this->instance->mkdir('target'); + $this->instance->file_put_contents('target/test1.txt', 'bar'); + $this->instance->file_put_contents('target/test2.txt', 'bar'); + + $this->instance->copy('source', 'target'); + + $this->assertFalse($this->instance->file_exists('target/test2.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + } + + public function testCopyOverWriteDirectoryOverFile() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + + $this->instance->file_put_contents('target', 'bar'); + + $this->instance->copy('source', 'target'); + + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + } + + public function testInstanceOfStorage() { + $this->assertTrue($this->instance->instanceOfStorage('\OCP\Files\Storage')); + $this->assertTrue($this->instance->instanceOfStorage(get_class($this->instance))); + $this->assertFalse($this->instance->instanceOfStorage('\OC')); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testCopyFromSameStorage($source, $target) { + $this->initSourceAndTarget($source); + + $this->instance->copyFromStorage($this->instance, $source, $target); + + $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); + $this->assertSameAsLorem($target); + $this->assertTrue($this->instance->file_exists($source), $source . ' was deleted'); + } + + public function testIsCreatable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isCreatable('source')); + } + + public function testIsReadable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isReadable('source')); + } + + public function testIsUpdatable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isUpdatable('source')); + } + + public function testIsDeletable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isDeletable('source')); + } + + public function testIsShareable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isSharable('source')); + } + + public function testStatAfterWrite() { + $this->instance->file_put_contents('foo.txt', 'bar'); + $stat = $this->instance->stat('foo.txt'); + $this->assertEquals(3, $stat['size']); + + $fh = $this->instance->fopen('foo.txt', 'w'); + fwrite($fh, 'qwerty'); + fclose($fh); + + $stat = $this->instance->stat('foo.txt'); + $this->assertEquals(6, $stat['size']); + } + + public function testPartFile() { + $this->instance->file_put_contents('bar.txt.part', 'bar'); + $this->instance->rename('bar.txt.part', 'bar.txt'); + $this->assertEquals('bar', $this->instance->file_get_contents('bar.txt')); + } +} diff --git a/tests/lib/Files/Storage/StorageFactoryTest.php b/tests/lib/Files/Storage/StorageFactoryTest.php new file mode 100644 index 00000000000..d3dc036ab42 --- /dev/null +++ b/tests/lib/Files/Storage/StorageFactoryTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage; + +use OC\Files\Mount\MountPoint; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage as IStorage; +use Test\TestCase; +use OC\Files\Storage\Wrapper\Wrapper; + +class DummyWrapper extends Wrapper { + public $data; + + public function __construct($arguments) { + parent::__construct($arguments); + if (isset($arguments['data'])) { + $this->data = $arguments['data']; + } + } +} + +class StorageFactoryTest extends TestCase { + public function testSimpleWrapper() { + $instance = new \OC\Files\Storage\StorageFactory(); + $mount = new MountPoint('\OC\Files\Storage\Temporary', '/foo', [[]], $instance); + $instance->addStorageWrapper('dummy', function ($mountPoint, IStorage $storage, IMountPoint $mount) { + $this->assertInstanceOf('\OC\Files\Storage\Temporary', $storage); + $this->assertEquals('/foo/', $mount->getMountPoint()); + $this->assertEquals('/foo/', $mountPoint); + return new DummyWrapper(['storage' => $storage]); + }); + $wrapped = $mount->getStorage(); + $this->assertInstanceOf('\Test\Files\Storage\DummyWrapper', $wrapped); + } + + public function testRemoveWrapper() { + $instance = new \OC\Files\Storage\StorageFactory(); + $mount = new MountPoint('\OC\Files\Storage\Temporary', '/foo', [[]], $instance); + $instance->addStorageWrapper('dummy', function ($mountPoint, IStorage $storage) { + return new DummyWrapper(['storage' => $storage]); + }); + $instance->removeStorageWrapper('dummy'); + $wrapped = $mount->getStorage(); + $this->assertInstanceOf('\OC\Files\Storage\Temporary', $wrapped); + } + + public function testWrapperPriority() { + $instance = new \OC\Files\Storage\StorageFactory(); + $mount = new MountPoint('\OC\Files\Storage\Temporary', '/foo', [[]], $instance); + $instance->addStorageWrapper('dummy1', function ($mountPoint, IStorage $storage) { + return new DummyWrapper(['storage' => $storage, 'data' => 1]); + }, 1); + $instance->addStorageWrapper('dummy2', function ($mountPoint, IStorage $storage) { + return new DummyWrapper(['storage' => $storage, 'data' => 100]); + }, 100); + $instance->addStorageWrapper('dummy3', function ($mountPoint, IStorage $storage) { + return new DummyWrapper(['storage' => $storage, 'data' => 50]); + }, 50); + /** @var \Test\Files\Storage\DummyWrapper $wrapped */ + $wrapped = $mount->getStorage(); + $this->assertInstanceOf('\Test\Files\Storage\DummyWrapper', $wrapped); + $this->assertEquals(1, $wrapped->data);// lowest priority is applied last, called first + $this->assertEquals(50, $wrapped->getWrapperStorage()->data); + $this->assertEquals(100, $wrapped->getWrapperStorage()->getWrapperStorage()->data); + } +} diff --git a/tests/lib/Files/Storage/Wrapper/AvailabilityTest.php b/tests/lib/Files/Storage/Wrapper/AvailabilityTest.php new file mode 100644 index 00000000000..5ea7f8403ce --- /dev/null +++ b/tests/lib/Files/Storage/Wrapper/AvailabilityTest.php @@ -0,0 +1,153 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Files\Storage\Wrapper; + +class AvailabilityTest extends \Test\TestCase { + protected function getWrapperInstance() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->disableOriginalConstructor() + ->getMock(); + $wrapper = new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); + return [$storage, $wrapper]; + } + + /** + * Storage is available + */ + public function testAvailable() { + list($storage, $wrapper) = $this->getWrapperInstance(); + $storage->expects($this->once()) + ->method('getAvailability') + ->willReturn(['available' => true, 'last_checked' => 0]); + $storage->expects($this->never()) + ->method('test'); + $storage->expects($this->once()) + ->method('mkdir'); + + $wrapper->mkdir('foobar'); + } + + /** + * Storage marked unavailable, TTL not expired + * + * @expectedException \OCP\Files\StorageNotAvailableException + */ + public function testUnavailable() { + list($storage, $wrapper) = $this->getWrapperInstance(); + $storage->expects($this->once()) + ->method('getAvailability') + ->willReturn(['available' => false, 'last_checked' => time()]); + $storage->expects($this->never()) + ->method('test'); + $storage->expects($this->never()) + ->method('mkdir'); + + $wrapper->mkdir('foobar'); + } + + /** + * Storage marked unavailable, TTL expired + */ + public function testUnavailableRecheck() { + list($storage, $wrapper) = $this->getWrapperInstance(); + $storage->expects($this->once()) + ->method('getAvailability') + ->willReturn(['available' => false, 'last_checked' => 0]); + $storage->expects($this->once()) + ->method('test') + ->willReturn(true); + $storage->expects($this->exactly(2)) + ->method('setAvailability') + ->withConsecutive( + [$this->equalTo(false)], // prevents concurrent rechecks + [$this->equalTo(true)] // sets correct availability + ); + $storage->expects($this->once()) + ->method('mkdir'); + + $wrapper->mkdir('foobar'); + } + + /** + * Storage marked available, but throws StorageNotAvailableException + * + * @expectedException \OCP\Files\StorageNotAvailableException + */ + public function testAvailableThrowStorageNotAvailable() { + list($storage, $wrapper) = $this->getWrapperInstance(); + $storage->expects($this->once()) + ->method('getAvailability') + ->willReturn(['available' => true, 'last_checked' => 0]); + $storage->expects($this->never()) + ->method('test'); + $storage->expects($this->once()) + ->method('mkdir') + ->will($this->throwException(new \OCP\Files\StorageNotAvailableException())); + $storage->expects($this->once()) + ->method('setAvailability') + ->with($this->equalTo(false)); + + $wrapper->mkdir('foobar'); + } + + /** + * Storage available, but call fails + * Method failure does not indicate storage unavailability + */ + public function testAvailableFailure() { + list($storage, $wrapper) = $this->getWrapperInstance(); + $storage->expects($this->once()) + ->method('getAvailability') + ->willReturn(['available' => true, 'last_checked' => 0]); + $storage->expects($this->never()) + ->method('test'); + $storage->expects($this->once()) + ->method('mkdir') + ->willReturn(false); + $storage->expects($this->never()) + ->method('setAvailability'); + + $wrapper->mkdir('foobar'); + } + + /** + * Storage available, but throws exception + * Standard exception does not indicate storage unavailability + * + * @expectedException \Exception + */ + public function testAvailableThrow() { + list($storage, $wrapper) = $this->getWrapperInstance(); + $storage->expects($this->once()) + ->method('getAvailability') + ->willReturn(['available' => true, 'last_checked' => 0]); + $storage->expects($this->never()) + ->method('test'); + $storage->expects($this->once()) + ->method('mkdir') + ->will($this->throwException(new \Exception())); + $storage->expects($this->never()) + ->method('setAvailability'); + + $wrapper->mkdir('foobar'); + } +} diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php new file mode 100644 index 00000000000..e9ebf2f3bdf --- /dev/null +++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php @@ -0,0 +1,926 @@ +<?php + +namespace Test\Files\Storage\Wrapper; + +use OC\Encryption\Util; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OC\User\Manager; +use Test\Files\Storage\Storage; + +class EncryptionTest extends Storage { + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $headerSize = 8192; + + /** + * @var Temporary + */ + private $sourceStorage; + + /** + * @var \OC\Files\Storage\Wrapper\Encryption | \PHPUnit_Framework_MockObject_MockObject + */ + protected $instance; + + /** + * @var \OC\Encryption\Keys\Storage | \PHPUnit_Framework_MockObject_MockObject + */ + private $keyStore; + + /** + * @var \OC\Encryption\Util | \PHPUnit_Framework_MockObject_MockObject + */ + private $util; + + /** + * @var \OC\Encryption\Manager | \PHPUnit_Framework_MockObject_MockObject + */ + private $encryptionManager; + + /** + * @var \OCP\Encryption\IEncryptionModule | \PHPUnit_Framework_MockObject_MockObject + */ + private $encryptionModule; + + /** + * @var \OC\Encryption\Update | \PHPUnit_Framework_MockObject_MockObject + */ + private $update; + + /** + * @var \OC\Files\Cache\Cache | \PHPUnit_Framework_MockObject_MockObject + */ + private $cache; + + /** + * @var \OC\Log | \PHPUnit_Framework_MockObject_MockObject + */ + private $logger; + + /** + * @var \OC\Encryption\File | \PHPUnit_Framework_MockObject_MockObject + */ + private $file; + + + /** + * @var \OC\Files\Mount\MountPoint | \PHPUnit_Framework_MockObject_MockObject + */ + private $mount; + + /** + * @var \OC\Files\Mount\Manager | \PHPUnit_Framework_MockObject_MockObject + */ + private $mountManager; + + /** + * @var \OC\Group\Manager | \PHPUnit_Framework_MockObject_MockObject + */ + private $groupManager; + + /** + * @var \OCP\IConfig | \PHPUnit_Framework_MockObject_MockObject + */ + private $config; + + /** @var \OC\Memcache\ArrayCache | \PHPUnit_Framework_MockObject_MockObject */ + private $arrayCache; + + + /** @var integer dummy unencrypted size */ + private $dummySize = -1; + + protected function setUp() { + + parent::setUp(); + + $mockModule = $this->buildMockModule(); + $this->encryptionManager = $this->getMockBuilder('\OC\Encryption\Manager') + ->disableOriginalConstructor() + ->setMethods(['getEncryptionModule', 'isEnabled']) + ->getMock(); + $this->encryptionManager->expects($this->any()) + ->method('getEncryptionModule') + ->willReturn($mockModule); + + $this->arrayCache = $this->getMock('OC\Memcache\ArrayCache'); + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->groupManager = $this->getMockBuilder('\OC\Group\Manager') + ->disableOriginalConstructor() + ->getMock(); + + $this->util = $this->getMock( + '\OC\Encryption\Util', + ['getUidAndFilename', 'isFile', 'isExcluded'], + [new View(), new Manager(), $this->groupManager, $this->config, $this->arrayCache]); + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturnCallback(function ($path) { + return ['user1', $path]; + }); + + $this->file = $this->getMockBuilder('\OC\Encryption\File') + ->disableOriginalConstructor() + ->setMethods(['getAccessList']) + ->getMock(); + $this->file->expects($this->any())->method('getAccessList')->willReturn([]); + + $this->logger = $this->getMock('\OC\Log'); + + $this->sourceStorage = new Temporary(array()); + + $this->keyStore = $this->getMockBuilder('\OC\Encryption\Keys\Storage') + ->disableOriginalConstructor()->getMock(); + + $this->update = $this->getMockBuilder('\OC\Encryption\Update') + ->disableOriginalConstructor()->getMock(); + + $this->mount = $this->getMockBuilder('\OC\Files\Mount\MountPoint') + ->disableOriginalConstructor() + ->setMethods(['getOption']) + ->getMock(); + $this->mount->expects($this->any())->method('getOption')->willReturnCallback(function ($option, $default) { + if ($option === 'encrypt' && $default === true) { + global $mockedMountPointEncryptionEnabled; + if ($mockedMountPointEncryptionEnabled !== null) { + return $mockedMountPointEncryptionEnabled; + } + } + return true; + }); + + $this->cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $this->cache->expects($this->any()) + ->method('get') + ->willReturnCallback(function($path) {return ['encrypted' => false, 'path' => $path];}); + + $this->mountManager = $this->getMockBuilder('\OC\Files\Mount\Manager') + ->disableOriginalConstructor()->getMock(); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $this->sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + ] + ) + ->setMethods(['getMetaData', 'getCache', 'getEncryptionModule']) + ->getMock(); + + $this->instance->expects($this->any()) + ->method('getMetaData') + ->willReturnCallback(function ($path) { + return ['encrypted' => true, 'size' => $this->dummySize, 'path' => $path]; + }); + + $this->instance->expects($this->any()) + ->method('getCache') + ->willReturn($this->cache); + + $this->instance->expects($this->any()) + ->method('getEncryptionModule') + ->willReturn($mockModule); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function buildMockModule() { + $this->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser']) + ->getMock(); + + $this->encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE'); + $this->encryptionModule->expects($this->any())->method('getDisplayName')->willReturn('Unit test module'); + $this->encryptionModule->expects($this->any())->method('begin')->willReturn([]); + $this->encryptionModule->expects($this->any())->method('end')->willReturn(''); + $this->encryptionModule->expects($this->any())->method('encrypt')->willReturnArgument(0); + $this->encryptionModule->expects($this->any())->method('decrypt')->willReturnArgument(0); + $this->encryptionModule->expects($this->any())->method('update')->willReturn(true); + $this->encryptionModule->expects($this->any())->method('shouldEncrypt')->willReturn(true); + $this->encryptionModule->expects($this->any())->method('getUnencryptedBlockSize')->willReturn(8192); + $this->encryptionModule->expects($this->any())->method('isReadable')->willReturn(true); + return $this->encryptionModule; + } + + /** + * @dataProvider dataTestGetMetaData + * + * @param string $path + * @param array $metaData + * @param bool $encrypted + * @param bool $unencryptedSizeSet + * @param int $storedUnencryptedSize + * @param array $expected + */ + public function testGetMetaData($path, $metaData, $encrypted, $unencryptedSizeSet, $storedUnencryptedSize, $expected) { + + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('get') + ->willReturnCallback( + function($path) use ($encrypted) { + return ['encrypted' => $encrypted, 'path' => $path, 'size' => 0, 'fileid' => 1]; + } + ); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + ] + ) + ->setMethods(['getCache', 'verifyUnencryptedSize']) + ->getMock(); + + if($unencryptedSizeSet) { + $this->invokePrivate($this->instance, 'unencryptedSize', [[$path => $storedUnencryptedSize]]); + } + + $fileEntry = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $sourceStorage->expects($this->once())->method('getMetaData')->with($path) + ->willReturn($metaData); + $sourceStorage->expects($this->any()) + ->method('getCache') + ->with($path) + ->willReturn($fileEntry); + $fileEntry->expects($this->any()) + ->method('get') + ->with($metaData['fileid']); + + $this->instance->expects($this->any())->method('getCache')->willReturn($cache); + $this->instance->expects($this->any())->method('verifyUnencryptedSize') + ->with($path, 0)->willReturn($expected['size']); + + $result = $this->instance->getMetaData($path); + if(isset($expected['encrypted'])) { + $this->assertSame($expected['encrypted'], (bool)$result['encrypted']); + + if(isset($expected['encryptedVersion'])) { + $this->assertSame($expected['encryptedVersion'], $result['encryptedVersion']); + } + } + $this->assertSame($expected['size'], $result['size']); + } + + public function dataTestGetMetaData() { + return [ + ['/test.txt', ['size' => 42, 'encrypted' => 2, 'encryptedVersion' => 2, 'fileid' => 1], true, true, 12, ['size' => 12, 'encrypted' => true, 'encryptedVersion' => 2]], + ['/test.txt', null, true, true, 12, null], + ['/test.txt', ['size' => 42, 'encrypted' => 0, 'fileid' => 1], false, false, 12, ['size' => 42, 'encrypted' => false]], + ['/test.txt', ['size' => 42, 'encrypted' => false, 'fileid' => 1], true, false, 12, ['size' => 12, 'encrypted' => true]] + ]; + } + + public function testFilesize() { + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('get') + ->willReturn(['encrypted' => true, 'path' => '/test.txt', 'size' => 0, 'fileid' => 1]); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $this->sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + ] + ) + ->setMethods(['getCache', 'verifyUnencryptedSize']) + ->getMock(); + + $this->instance->expects($this->any())->method('getCache')->willReturn($cache); + $this->instance->expects($this->any())->method('verifyUnencryptedSize') + ->willReturn(42); + + + $this->assertSame(42, + $this->instance->filesize('/test.txt') + ); + + } + + /** + * @dataProvider dataTestVerifyUnencryptedSize + * + * @param int $encryptedSize + * @param int $unencryptedSize + * @param bool $failure + * @param int $expected + */ + public function testVerifyUnencryptedSize($encryptedSize, $unencryptedSize, $failure, $expected) { + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + ] + ) + ->setMethods(['fixUnencryptedSize']) + ->getMock(); + + $sourceStorage->expects($this->once())->method('filesize')->willReturn($encryptedSize); + + $this->instance->expects($this->any())->method('fixUnencryptedSize') + ->with('/test.txt', $encryptedSize, $unencryptedSize) + ->willReturnCallback( + function() use ($failure, $expected) { + if ($failure) { + throw new \Exception(); + } else { + return $expected; + } + } + ); + + $this->assertSame( + $expected, + $this->invokePrivate($this->instance, 'verifyUnencryptedSize', ['/test.txt', $unencryptedSize]) + ); + } + + public function dataTestVerifyUnencryptedSize() { + return [ + [120, 80, false, 80], + [120, 120, false, 80], + [120, -1, false, 80], + [120, -1, true, -1] + ]; + } + + /** + * @dataProvider dataTestCopyAndRename + * + * @param string $source + * @param string $target + * @param $encryptionEnabled + * @param boolean $renameKeysReturn + */ + public function testRename($source, + $target, + $encryptionEnabled, + $renameKeysReturn) { + if ($encryptionEnabled) { + $this->keyStore + ->expects($this->once()) + ->method('renameKeys') + ->willReturn($renameKeysReturn); + } else { + $this->keyStore + ->expects($this->never())->method('renameKeys'); + } + $this->util->expects($this->any()) + ->method('isFile')->willReturn(true); + $this->encryptionManager->expects($this->once()) + ->method('isEnabled')->willReturn($encryptionEnabled); + + $this->instance->mkdir($source); + $this->instance->mkdir(dirname($target)); + $this->instance->rename($source, $target); + } + + public function testCopyEncryption() { + $this->instance->file_put_contents('source.txt', 'bar'); + $this->instance->copy('source.txt', 'target.txt'); + $this->assertSame('bar', $this->instance->file_get_contents('target.txt')); + $targetMeta = $this->instance->getMetaData('target.txt'); + $sourceMeta = $this->instance->getMetaData('source.txt'); + $this->assertSame($sourceMeta['encrypted'], $targetMeta['encrypted']); + $this->assertSame($sourceMeta['size'], $targetMeta['size']); + } + + /** + * data provider for testCopyTesting() and dataTestCopyAndRename() + * + * @return array + */ + public function dataTestCopyAndRename() { + return array( + array('source', 'target', true, false, false), + array('source', 'target', true, true, false), + array('source', '/subFolder/target', true, false, false), + array('source', '/subFolder/target', true, true, true), + array('source', '/subFolder/target', false, true, false), + ); + } + + public function testIsLocal() { + $this->encryptionManager->expects($this->once()) + ->method('isEnabled')->willReturn(true); + $this->assertFalse($this->instance->isLocal()); + } + + /** + * @dataProvider dataTestRmdir + * + * @param string $path + * @param boolean $rmdirResult + * @param boolean $isExcluded + * @param boolean $encryptionEnabled + */ + public function testRmdir($path, $rmdirResult, $isExcluded, $encryptionEnabled) { + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $util = $this->getMockBuilder('\OC\Encryption\Util')->disableOriginalConstructor()->getMock(); + + $sourceStorage->expects($this->once())->method('rmdir')->willReturn($rmdirResult); + $util->expects($this->any())->method('isExcluded')-> willReturn($isExcluded); + $this->encryptionManager->expects($this->any())->method('isEnabled')->willReturn($encryptionEnabled); + + $encryptionStorage = new \OC\Files\Storage\Wrapper\Encryption( + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/mountPoint', + 'mount' => $this->mount + ], + $this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update + ); + + + if ($rmdirResult === true && $isExcluded === false && $encryptionEnabled === true) { + $this->keyStore->expects($this->once())->method('deleteAllFileKeys')->with('/mountPoint' . $path); + } else { + $this->keyStore->expects($this->never())->method('deleteAllFileKeys'); + } + + $encryptionStorage->rmdir($path); + } + + public function dataTestRmdir() { + return array( + array('/file.txt', true, true, true), + array('/file.txt', false, true, true), + array('/file.txt', true, false, true), + array('/file.txt', false, false, true), + array('/file.txt', true, true, false), + array('/file.txt', false, true, false), + array('/file.txt', true, false, false), + array('/file.txt', false, false, false), + ); + } + + /** + * @dataProvider dataTestCopyKeys + * + * @param boolean $excluded + * @param boolean $expected + */ + public function testCopyKeys($excluded, $expected) { + $this->util->expects($this->once()) + ->method('isExcluded') + ->willReturn($excluded); + + if ($excluded) { + $this->keyStore->expects($this->never())->method('copyKeys'); + } else { + $this->keyStore->expects($this->once())->method('copyKeys')->willReturn(true); + } + + $this->assertSame($expected, + self::invokePrivate($this->instance, 'copyKeys', ['/source', '/target']) + ); + } + + public function dataTestCopyKeys() { + return array( + array(true, false), + array(false, true), + ); + } + + /** + * @dataProvider dataTestGetHeader + * + * @param string $path + * @param bool $strippedPathExists + * @param string $strippedPath + */ + public function testGetHeader($path, $strippedPathExists, $strippedPath) { + + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $util = $this->getMockBuilder('\OC\Encryption\Util') + ->setConstructorArgs( + [ + new View(), + new Manager(), + $this->groupManager, + $this->config, + $this->arrayCache + ] + )->getMock(); + + $instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + ] + ) + ->setMethods(['readFirstBlock', 'parseRawHeader']) + ->getMock(); + + $instance->expects($this->once())->method(('parseRawHeader')) + ->willReturn([Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']); + + if ($strippedPathExists) { + $instance->expects($this->once())->method('readFirstBlock') + ->with($strippedPath)->willReturn(''); + } else { + $instance->expects($this->once())->method('readFirstBlock') + ->with($path)->willReturn(''); + } + + $util->expects($this->once())->method('stripPartialFileExtension') + ->with($path)->willReturn($strippedPath); + $sourceStorage->expects($this->once()) + ->method('file_exists') + ->with($strippedPath) + ->willReturn($strippedPathExists); + + $this->invokePrivate($instance, 'getHeader', [$path]); + } + + public function dataTestGetHeader() { + return array( + array('/foo/bar.txt', false, '/foo/bar.txt'), + array('/foo/bar.txt.part', false, '/foo/bar.txt'), + array('/foo/bar.txt.ocTransferId7437493.part', false, '/foo/bar.txt'), + array('/foo/bar.txt.part', true, '/foo/bar.txt'), + array('/foo/bar.txt.ocTransferId7437493.part', true, '/foo/bar.txt'), + ); + } + + /** + * test if getHeader adds the default module correctly to the header for + * legacy files + * + * @dataProvider dataTestGetHeaderAddLegacyModule + */ + public function testGetHeaderAddLegacyModule($header, $isEncrypted, $expected) { + + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $util = $this->getMockBuilder('\OC\Encryption\Util') + ->setConstructorArgs([new View(), new Manager(), $this->groupManager, $this->config, $this->arrayCache]) + ->getMock(); + + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('get') + ->willReturnCallback(function($path) use ($isEncrypted) {return ['encrypted' => $isEncrypted, 'path' => $path];}); + + $instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + ] + ) + ->setMethods(['readFirstBlock', 'parseRawHeader', 'getCache']) + ->getMock(); + + $instance->expects($this->once())->method(('parseRawHeader'))->willReturn($header); + $instance->expects($this->any())->method('getCache')->willReturn($cache); + + $result = $this->invokePrivate($instance, 'getHeader', ['test.txt']); + $this->assertSameSize($expected, $result); + foreach ($result as $key => $value) { + $this->assertArrayHasKey($key, $expected); + $this->assertSame($expected[$key], $value); + } + } + + public function dataTestGetHeaderAddLegacyModule() { + return [ + [['cipher' => 'AES-128'], true, ['cipher' => 'AES-128', Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']], + [[], true, [Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']], + [[], false, []], + ]; + } + + /** + * @dataProvider dataTestParseRawHeader + */ + public function testParseRawHeader($rawHeader, $expected) { + $instance = new \OC\Files\Storage\Wrapper\Encryption( + [ + 'storage' => $this->sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + + ); + + $result = $this->invokePrivate($instance, 'parseRawHeader', [$rawHeader]); + $this->assertSameSize($expected, $result); + foreach ($result as $key => $value) { + $this->assertArrayHasKey($key, $expected); + $this->assertSame($expected[$key], $value); + } + } + + public function dataTestParseRawHeader() { + return [ + [str_pad('HBEGIN:oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT) + , [Util::HEADER_ENCRYPTION_MODULE_KEY => '0']], + [str_pad('HBEGIN:oc_encryption_module:0:custom_header:foo:HEND', $this->headerSize, '-', STR_PAD_RIGHT) + , ['custom_header' => 'foo', Util::HEADER_ENCRYPTION_MODULE_KEY => '0']], + [str_pad('HelloWorld', $this->headerSize, '-', STR_PAD_RIGHT), []], + ['', []], + [str_pad('HBEGIN:oc_encryption_module:0', $this->headerSize, '-', STR_PAD_RIGHT) + , []], + [str_pad('oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT) + , []], + ]; + } + + public function dataCopyBetweenStorage() { + return [ + [true, true, true], + [true, false, false], + [false, true, false], + [false, false, false], + ]; + } + + public function testCopyBetweenStorageMinimumEncryptedVersion() { + $storage2 = $this->getMockBuilder('OCP\Files\Storage') + ->disableOriginalConstructor() + ->getMock(); + + $sourceInternalPath = $targetInternalPath = 'file.txt'; + $preserveMtime = $isRename = false; + + $storage2->expects($this->any()) + ->method('fopen') + ->willReturnCallback(function($path, $mode) { + $temp = \OC::$server->getTempManager(); + return fopen($temp->getTemporaryFile(), $mode); + }); + $cache = $this->getMock('\OCP\Files\Cache\ICache'); + $cache->expects($this->once()) + ->method('get') + ->with($sourceInternalPath) + ->willReturn(['encryptedVersion' => 0]); + $storage2->expects($this->once()) + ->method('getCache') + ->willReturn($cache); + $this->encryptionManager->expects($this->any()) + ->method('isEnabled') + ->willReturn(true); + global $mockedMountPointEncryptionEnabled; + $mockedMountPointEncryptionEnabled = true; + + $expectedCachePut = [ + 'encrypted' => true, + ]; + $expectedCachePut['encryptedVersion'] = 1; + + $this->cache->expects($this->once()) + ->method('put') + ->with($sourceInternalPath, $expectedCachePut); + + $this->invokePrivate($this->instance, 'copyBetweenStorage', [$storage2, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename]); + + $this->assertFalse(false); + } + + /** + * @dataProvider dataCopyBetweenStorage + * + * @param bool $encryptionEnabled + * @param bool $mountPointEncryptionEnabled + * @param bool $expectedEncrypted + */ + public function testCopyBetweenStorage($encryptionEnabled, $mountPointEncryptionEnabled, $expectedEncrypted) { + $storage2 = $this->getMockBuilder('OCP\Files\Storage') + ->disableOriginalConstructor() + ->getMock(); + + $sourceInternalPath = $targetInternalPath = 'file.txt'; + $preserveMtime = $isRename = false; + + $storage2->expects($this->any()) + ->method('fopen') + ->willReturnCallback(function($path, $mode) { + $temp = \OC::$server->getTempManager(); + return fopen($temp->getTemporaryFile(), $mode); + }); + if($expectedEncrypted) { + $cache = $this->getMock('\OCP\Files\Cache\ICache'); + $cache->expects($this->once()) + ->method('get') + ->with($sourceInternalPath) + ->willReturn(['encryptedVersion' => 12345]); + $storage2->expects($this->once()) + ->method('getCache') + ->willReturn($cache); + } + $this->encryptionManager->expects($this->any()) + ->method('isEnabled') + ->willReturn($encryptionEnabled); + // FIXME can not overwrite the return after definition +// $this->mount->expects($this->at(0)) +// ->method('getOption') +// ->with('encrypt', true) +// ->willReturn($mountPointEncryptionEnabled); + global $mockedMountPointEncryptionEnabled; + $mockedMountPointEncryptionEnabled = $mountPointEncryptionEnabled; + + $expectedCachePut = [ + 'encrypted' => $expectedEncrypted, + ]; + if($expectedEncrypted === true) { + $expectedCachePut['encryptedVersion'] = 12345; + } + + $this->arrayCache->expects($this->never())->method('set'); + + $this->cache->expects($this->once()) + ->method('put') + ->with($sourceInternalPath, $expectedCachePut); + + $this->invokePrivate($this->instance, 'copyBetweenStorage', [$storage2, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename]); + + $this->assertFalse(false); + } + + /** + * @dataProvider dataTestCopyBetweenStorageVersions + * + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $copyResult + * @param bool $encrypted + */ + public function testCopyBetweenStorageVersions($sourceInternalPath, $targetInternalPath, $copyResult, $encrypted) { + + $sourceStorage = $this->getMockBuilder('OCP\Files\Storage') + ->disableOriginalConstructor() + ->getMock(); + + $targetStorage = $this->getMockBuilder('OCP\Files\Storage') + ->disableOriginalConstructor() + ->getMock(); + + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + + $mountPoint = '/mountPoint'; + + /** @var \OC\Files\Storage\Wrapper\Encryption |\PHPUnit_Framework_MockObject_MockObject $instance */ + $instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $targetStorage, + 'root' => 'foo', + 'mountPoint' => $mountPoint, + 'mount' => $this->mount + ], + $this->encryptionManager, + $this->util, + $this->logger, + $this->file, + null, + $this->keyStore, + $this->update, + $this->mountManager, + $this->arrayCache + ] + ) + ->setMethods(['updateUnencryptedSize', 'getCache']) + ->getMock(); + + $targetStorage->expects($this->once())->method('copyFromStorage') + ->with($sourceStorage, $sourceInternalPath, $targetInternalPath) + ->willReturn($copyResult); + + $instance->expects($this->any())->method('getCache') + ->willReturn($cache); + + $this->arrayCache->expects($this->once())->method('set') + ->with('encryption_copy_version_' . $sourceInternalPath, true); + + if ($copyResult) { + $cache->expects($this->once())->method('get') + ->with($sourceInternalPath) + ->willReturn(['encrypted' => $encrypted, 'size' => 42]); + if ($encrypted) { + $instance->expects($this->once())->method('updateUnencryptedSize') + ->with($mountPoint . $targetInternalPath, 42); + } else { + $instance->expects($this->never())->method('updateUnencryptedSize'); + } + } else { + $instance->expects($this->never())->method('updateUnencryptedSize'); + } + + $result = $this->invokePrivate( + $instance, + 'copyBetweenStorage', + [ + $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + false, + false + ] + ); + + $this->assertSame($copyResult, $result); + } + + public function dataTestCopyBetweenStorageVersions() { + return [ + ['/files/foo.txt', '/files_versions/foo.txt.768743', true, true], + ['/files/foo.txt', '/files_versions/foo.txt.768743', true, false], + ['/files/foo.txt', '/files_versions/foo.txt.768743', false, true], + ['/files/foo.txt', '/files_versions/foo.txt.768743', false, false], + ['/files_versions/foo.txt.6487634', '/files/foo.txt', true, true], + ['/files_versions/foo.txt.6487634', '/files/foo.txt', true, false], + ['/files_versions/foo.txt.6487634', '/files/foo.txt', false, true], + ['/files_versions/foo.txt.6487634', '/files/foo.txt', false, false], + + ]; + } + + /** + * @dataProvider dataTestIsVersion + * @param string $path + * @param bool $expected + */ + public function testIsVersion($path, $expected) { + $this->assertSame($expected, + $this->invokePrivate($this->instance, 'isVersion', [$path]) + ); + } + + public function dataTestIsVersion() { + return [ + ['files_versions/foo', true], + ['/files_versions/foo', true], + ['//files_versions/foo', true], + ['files/versions/foo', false], + ['files/files_versions/foo', false], + ['files_versions_test/foo', false], + ]; + } + +} diff --git a/tests/lib/Files/Storage/Wrapper/JailTest.php b/tests/lib/Files/Storage/Wrapper/JailTest.php new file mode 100644 index 00000000000..b03eb0bcc63 --- /dev/null +++ b/tests/lib/Files/Storage/Wrapper/JailTest.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage\Wrapper; + +class JailTest extends \Test\Files\Storage\Storage { + + /** + * @var \OC\Files\Storage\Temporary + */ + private $sourceStorage; + + public function setUp() { + parent::setUp(); + $this->sourceStorage = new \OC\Files\Storage\Temporary(array()); + $this->sourceStorage->mkdir('foo'); + $this->instance = new \OC\Files\Storage\Wrapper\Jail(array( + 'storage' => $this->sourceStorage, + 'root' => 'foo' + )); + } + + public function tearDown() { + // test that nothing outside our jail is touched + $contents = array(); + $dh = $this->sourceStorage->opendir(''); + while ($file = readdir($dh)) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + $contents[] = $file; + } + } + $this->assertEquals(array('foo'), $contents); + $this->sourceStorage->cleanUp(); + parent::tearDown(); + } + + public function testMkDirRooted() { + $this->instance->mkdir('bar'); + $this->assertTrue($this->sourceStorage->is_dir('foo/bar')); + } + + public function testFilePutContentsRooted() { + $this->instance->file_put_contents('bar', 'asd'); + $this->assertEquals('asd', $this->sourceStorage->file_get_contents('foo/bar')); + } +} diff --git a/tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php b/tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php new file mode 100644 index 00000000000..c3421553643 --- /dev/null +++ b/tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage\Wrapper; + +use OCP\Constants; + +class PermissionsMaskTest extends \Test\Files\Storage\Storage { + + /** + * @var \OC\Files\Storage\Temporary + */ + private $sourceStorage; + + public function setUp() { + parent::setUp(); + $this->sourceStorage = new \OC\Files\Storage\Temporary(array()); + $this->instance = $this->getMaskedStorage(Constants::PERMISSION_ALL); + } + + public function tearDown() { + $this->sourceStorage->cleanUp(); + parent::tearDown(); + } + + protected function getMaskedStorage($mask) { + return new \OC\Files\Storage\Wrapper\PermissionsMask(array( + 'storage' => $this->sourceStorage, + 'mask' => $mask + )); + } + + public function testMkdirNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->mkdir('foo')); + $this->assertFalse($storage->file_exists('foo')); + } + + public function testRmdirNoDelete() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE); + $this->assertTrue($storage->mkdir('foo')); + $this->assertTrue($storage->file_exists('foo')); + $this->assertFalse($storage->rmdir('foo')); + $this->assertTrue($storage->file_exists('foo')); + } + + public function testTouchNewFileNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->touch('foo')); + $this->assertFalse($storage->file_exists('foo')); + } + + public function testTouchNewFileNoUpdate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertTrue($storage->touch('foo')); + $this->assertTrue($storage->file_exists('foo')); + } + + public function testTouchExistingFileNoUpdate() { + $this->sourceStorage->touch('foo'); + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertFalse($storage->touch('foo')); + } + + public function testUnlinkNoDelete() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE); + $this->assertTrue($storage->touch('foo')); + $this->assertTrue($storage->file_exists('foo')); + $this->assertFalse($storage->unlink('foo')); + $this->assertTrue($storage->file_exists('foo')); + } + + public function testPutContentsNewFileNoUpdate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertTrue($storage->file_put_contents('foo', 'bar')); + $this->assertEquals('bar', $storage->file_get_contents('foo')); + } + + public function testPutContentsNewFileNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->file_put_contents('foo', 'bar')); + } + + public function testPutContentsExistingFileNoUpdate() { + $this->sourceStorage->touch('foo'); + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertFalse($storage->file_put_contents('foo', 'bar')); + } + + public function testFopenExistingFileNoUpdate() { + $this->sourceStorage->touch('foo'); + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertFalse($storage->fopen('foo', 'w')); + } + + public function testFopenNewFileNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->fopen('foo', 'w')); + } +} diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php new file mode 100644 index 00000000000..2e9f68b3c56 --- /dev/null +++ b/tests/lib/Files/Storage/Wrapper/QuotaTest.php @@ -0,0 +1,211 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage\Wrapper; + +//ensure the constants are loaded +use OC\Files\Cache\CacheEntry; + +\OC::$loader->load('\OC\Files\Filesystem'); + +/** + * Class QuotaTest + * + * @group DB + * + * @package Test\Files\Storage\Wrapper + */ +class QuotaTest extends \Test\Files\Storage\Storage { + /** + * @var string tmpDir + */ + private $tmpDir; + + protected function setUp() { + parent::setUp(); + + $this->tmpDir = \OC::$server->getTempManager()->getTemporaryFolder(); + $storage = new \OC\Files\Storage\Local(array('datadir' => $this->tmpDir)); + $this->instance = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => 10000000)); + } + + protected function tearDown() { + \OC_Helper::rmdirr($this->tmpDir); + parent::tearDown(); + } + + /** + * @param integer $limit + */ + protected function getLimitedStorage($limit) { + $storage = new \OC\Files\Storage\Local(array('datadir' => $this->tmpDir)); + $storage->mkdir('files'); + $storage->getScanner()->scan(''); + return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $limit)); + } + + public function testFilePutContentsNotEnoughSpace() { + $instance = $this->getLimitedStorage(3); + $this->assertFalse($instance->file_put_contents('files/foo', 'foobar')); + } + + public function testCopyNotEnoughSpace() { + $instance = $this->getLimitedStorage(9); + $this->assertEquals(6, $instance->file_put_contents('files/foo', 'foobar')); + $instance->getScanner()->scan(''); + $this->assertFalse($instance->copy('files/foo', 'files/bar')); + } + + public function testFreeSpace() { + $instance = $this->getLimitedStorage(9); + $this->assertEquals(9, $instance->free_space('')); + } + + public function testFreeSpaceWithUsedSpace() { + $instance = $this->getLimitedStorage(9); + $instance->getCache()->put( + '', array('size' => 3) + ); + $this->assertEquals(6, $instance->free_space('')); + } + + public function testFreeSpaceWithUnknownDiskSpace() { + $storage = $this->getMock( + '\OC\Files\Storage\Local', + array('free_space'), + array(array('datadir' => $this->tmpDir)) + ); + $storage->expects($this->any()) + ->method('free_space') + ->will($this->returnValue(-2)); + $storage->getScanner()->scan(''); + + $instance = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => 9)); + $instance->getCache()->put( + '', array('size' => 3) + ); + $this->assertEquals(6, $instance->free_space('')); + } + + public function testFreeSpaceWithUsedSpaceAndEncryption() { + $instance = $this->getLimitedStorage(9); + $instance->getCache()->put( + '', array('size' => 7) + ); + $this->assertEquals(2, $instance->free_space('')); + } + + public function testFWriteNotEnoughSpace() { + $instance = $this->getLimitedStorage(9); + $stream = $instance->fopen('files/foo', 'w+'); + $this->assertEquals(6, fwrite($stream, 'foobar')); + $this->assertEquals(3, fwrite($stream, 'qwerty')); + fclose($stream); + $this->assertEquals('foobarqwe', $instance->file_get_contents('files/foo')); + } + + public function testStreamCopyWithEnoughSpace() { + $instance = $this->getLimitedStorage(16); + $inputStream = fopen('data://text/plain,foobarqwerty', 'r'); + $outputStream = $instance->fopen('files/foo', 'w+'); + list($count, $result) = \OC_Helper::streamCopy($inputStream, $outputStream); + $this->assertEquals(12, $count); + $this->assertTrue($result); + fclose($inputStream); + fclose($outputStream); + } + + public function testStreamCopyNotEnoughSpace() { + $instance = $this->getLimitedStorage(9); + $inputStream = fopen('data://text/plain,foobarqwerty', 'r'); + $outputStream = $instance->fopen('files/foo', 'w+'); + list($count, $result) = \OC_Helper::streamCopy($inputStream, $outputStream); + $this->assertEquals(9, $count); + $this->assertFalse($result); + fclose($inputStream); + fclose($outputStream); + } + + public function testReturnFalseWhenFopenFailed() { + $failStorage = $this->getMock( + '\OC\Files\Storage\Local', + array('fopen'), + array(array('datadir' => $this->tmpDir))); + $failStorage->expects($this->any()) + ->method('fopen') + ->will($this->returnValue(false)); + + $instance = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $failStorage, 'quota' => 1000)); + + $this->assertFalse($instance->fopen('failedfopen', 'r')); + } + + public function testReturnRegularStreamOnRead() { + $instance = $this->getLimitedStorage(9); + + // create test file first + $stream = $instance->fopen('files/foo', 'w+'); + fwrite($stream, 'blablacontent'); + fclose($stream); + + $stream = $instance->fopen('files/foo', 'r'); + $meta = stream_get_meta_data($stream); + $this->assertEquals('plainfile', $meta['wrapper_type']); + fclose($stream); + + $stream = $instance->fopen('files/foo', 'rb'); + $meta = stream_get_meta_data($stream); + $this->assertEquals('plainfile', $meta['wrapper_type']); + fclose($stream); + } + + public function testReturnRegularStreamWhenOutsideFiles() { + $instance = $this->getLimitedStorage(9); + $instance->mkdir('files_other'); + + // create test file first + $stream = $instance->fopen('files_other/foo', 'w+'); + $meta = stream_get_meta_data($stream); + $this->assertEquals('plainfile', $meta['wrapper_type']); + fclose($stream); + } + + public function testReturnQuotaStreamOnWrite() { + $instance = $this->getLimitedStorage(9); + $stream = $instance->fopen('files/foo', 'w+'); + $meta = stream_get_meta_data($stream); + $expected_type = defined('HHVM_VERSION') ? 'File' : 'user-space'; + $this->assertEquals($expected_type, $meta['wrapper_type']); + fclose($stream); + } + + public function testSpaceRoot() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Local')->disableOriginalConstructor()->getMock(); + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')->disableOriginalConstructor()->getMock(); + $storage->expects($this->once()) + ->method('getCache') + ->will($this->returnValue($cache)); + $storage->expects($this->once()) + ->method('free_space') + ->will($this->returnValue(2048)); + $cache->expects($this->once()) + ->method('get') + ->with('files') + ->will($this->returnValue(new CacheEntry(['size' => 50]))); + + $instance = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => 1024, 'root' => 'files')); + + $this->assertEquals(1024 - 50, $instance->free_space('')); + } + + public function testInstanceOfStorageWrapper() { + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Local')); + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Wrapper')); + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')); + } +} diff --git a/tests/lib/Files/Storage/Wrapper/WrapperTest.php b/tests/lib/Files/Storage/Wrapper/WrapperTest.php new file mode 100644 index 00000000000..942ff27fc35 --- /dev/null +++ b/tests/lib/Files/Storage/Wrapper/WrapperTest.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage\Wrapper; + +class WrapperTest extends \Test\Files\Storage\Storage { + /** + * @var string tmpDir + */ + private $tmpDir; + + protected function setUp() { + parent::setUp(); + + $this->tmpDir = \OC::$server->getTempManager()->getTemporaryFolder(); + $storage = new \OC\Files\Storage\Local(array('datadir' => $this->tmpDir)); + $this->instance = new \OC\Files\Storage\Wrapper\Wrapper(array('storage' => $storage)); + } + + protected function tearDown() { + \OC_Helper::rmdirr($this->tmpDir); + parent::tearDown(); + } + + public function testInstanceOfStorageWrapper() { + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Local')); + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Wrapper')); + } +} diff --git a/tests/lib/Files/Stream/DummyEncryptionWrapper.php b/tests/lib/Files/Stream/DummyEncryptionWrapper.php new file mode 100644 index 00000000000..bb512d99c66 --- /dev/null +++ b/tests/lib/Files/Stream/DummyEncryptionWrapper.php @@ -0,0 +1,37 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + + +namespace Test\Files\Stream; + +class DummyEncryptionWrapper extends \OC\Files\Stream\Encryption { + + /** + * simulate a non-seekable stream wrapper by always return false + * + * @param int $position + * @return bool + */ + protected function parentStreamSeek($position) { + return false; + } + +} diff --git a/tests/lib/Files/Stream/EncryptionTest.php b/tests/lib/Files/Stream/EncryptionTest.php new file mode 100644 index 00000000000..45204e48d09 --- /dev/null +++ b/tests/lib/Files/Stream/EncryptionTest.php @@ -0,0 +1,342 @@ +<?php + +namespace Test\Files\Stream; + +use OC\Files\View; + +class EncryptionTest extends \Test\TestCase { + + /** @var \OCP\Encryption\IEncryptionModule | \PHPUnit_Framework_MockObject_MockObject */ + private $encryptionModule; + + /** + * @param string $fileName + * @param string $mode + * @param integer $unencryptedSize + * @return resource + */ + protected function getStream($fileName, $mode, $unencryptedSize, $wrapper = '\OC\Files\Stream\Encryption') { + clearstatcache(); + $size = filesize($fileName); + $source = fopen($fileName, $mode); + $internalPath = $fileName; + $fullPath = $fileName; + $header = []; + $uid = ''; + $this->encryptionModule = $this->buildMockModule(); + $storage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + $encStorage = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->disableOriginalConstructor()->getMock(); + $config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $arrayCache = $this->getMock('OC\Memcache\ArrayCache'); + $groupManager = $this->getMockBuilder('\OC\Group\Manager') + ->disableOriginalConstructor() + ->getMock(); + $file = $this->getMockBuilder('\OC\Encryption\File') + ->disableOriginalConstructor() + ->setMethods(['getAccessList']) + ->getMock(); + $file->expects($this->any())->method('getAccessList')->willReturn([]); + $util = $this->getMock( + '\OC\Encryption\Util', + ['getUidAndFilename'], + [new View(), new \OC\User\Manager(), $groupManager, $config, $arrayCache] + ); + $util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(['user1', $internalPath]); + + + return $wrapper::wrap($source, $internalPath, + $fullPath, $header, $uid, $this->encryptionModule, $storage, $encStorage, + $util, $file, $mode, $size, $unencryptedSize, 8192, $wrapper); + } + + /** + * @dataProvider dataProviderStreamOpen() + */ + public function testStreamOpen($mode, + $fullPath, + $fileExists, + $expectedSharePath, + $expectedSize, + $expectedUnencryptedSize, + $expectedReadOnly) { + + // build mocks + $encryptionModuleMock = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') + ->disableOriginalConstructor()->getMock(); + $encryptionModuleMock->expects($this->once()) + ->method('getUnencryptedBlockSize')->willReturn(99); + $encryptionModuleMock->expects($this->once()) + ->method('begin')->willReturn(true); + + $storageMock = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + $storageMock->expects($this->once())->method('file_exists')->willReturn($fileExists); + + $fileMock = $this->getMockBuilder('\OC\Encryption\File') + ->disableOriginalConstructor()->getMock(); + $fileMock->expects($this->once())->method('getAccessList') + ->will($this->returnCallback(function($sharePath) use ($expectedSharePath) { + $this->assertSame($expectedSharePath, $sharePath); + return array(); + })); + + $utilMock = $this->getMockBuilder('\OC\Encryption\Util') + ->disableOriginalConstructor()->getMock(); + $utilMock->expects($this->any()) + ->method('getHeaderSize') + ->willReturn(8192); + + // get a instance of the stream wrapper + $streamWrapper = $this->getMockBuilder('\OC\Files\Stream\Encryption') + ->setMethods(['loadContext', 'writeHeader', 'skipHeader'])->disableOriginalConstructor()->getMock(); + + // set internal properties of the stream wrapper + $stream = new \ReflectionClass('\OC\Files\Stream\Encryption'); + $encryptionModule = $stream->getProperty('encryptionModule'); + $encryptionModule->setAccessible(true); + $encryptionModule->setValue($streamWrapper, $encryptionModuleMock); + $encryptionModule->setAccessible(false); + $storage = $stream->getProperty('storage'); + $storage->setAccessible(true); + $storage->setValue($streamWrapper, $storageMock); + $storage->setAccessible(false); + $file = $stream->getProperty('file'); + $file->setAccessible(true); + $file->setValue($streamWrapper, $fileMock); + $file->setAccessible(false); + $util = $stream->getProperty('util'); + $util->setAccessible(true); + $util->setValue($streamWrapper, $utilMock); + $util->setAccessible(false); + $fullPathP = $stream->getProperty('fullPath'); + $fullPathP->setAccessible(true); + $fullPathP->setValue($streamWrapper, $fullPath); + $fullPathP->setAccessible(false); + $header = $stream->getProperty('header'); + $header->setAccessible(true); + $header->setValue($streamWrapper, array()); + $header->setAccessible(false); + $this->invokePrivate($streamWrapper, 'signed', [true]); + + // call stream_open, that's the method we want to test + $dummyVar = 'foo'; + $streamWrapper->stream_open('', $mode, '', $dummyVar); + + // check internal properties + $size = $stream->getProperty('size'); + $size->setAccessible(true); + $this->assertSame($expectedSize, + $size->getValue($streamWrapper) + ); + $size->setAccessible(false); + + $unencryptedSize = $stream->getProperty('unencryptedSize'); + $unencryptedSize->setAccessible(true); + $this->assertSame($expectedUnencryptedSize, + $unencryptedSize->getValue($streamWrapper) + ); + $unencryptedSize->setAccessible(false); + + $readOnly = $stream->getProperty('readOnly'); + $readOnly->setAccessible(true); + $this->assertSame($expectedReadOnly, + $readOnly->getValue($streamWrapper) + ); + $readOnly->setAccessible(false); + } + + public function dataProviderStreamOpen() { + return array( + array('r', '/foo/bar/test.txt', true, '/foo/bar/test.txt', null, null, true), + array('r', '/foo/bar/test.txt', false, '/foo/bar', null, null, true), + array('w', '/foo/bar/test.txt', true, '/foo/bar/test.txt', 8192, 0, false), + ); + } + + public function testWriteRead() { + $fileName = tempnam("/tmp", "FOO"); + $stream = $this->getStream($fileName, 'w+', 0); + $this->assertEquals(6, fwrite($stream, 'foobar')); + fclose($stream); + + $stream = $this->getStream($fileName, 'r', 6); + $this->assertEquals('foobar', fread($stream, 100)); + fclose($stream); + + $stream = $this->getStream($fileName, 'r+', 6); + $this->assertEquals(3, fwrite($stream, 'bar')); + fclose($stream); + + $stream = $this->getStream($fileName, 'r', 6); + $this->assertEquals('barbar', fread($stream, 100)); + fclose($stream); + + unlink($fileName); + } + + public function testRewind() { + $fileName = tempnam("/tmp", "FOO"); + $stream = $this->getStream($fileName, 'w+', 0); + $this->assertEquals(6, fwrite($stream, 'foobar')); + $this->assertEquals(TRUE, rewind($stream)); + $this->assertEquals('foobar', fread($stream, 100)); + $this->assertEquals(TRUE, rewind($stream)); + $this->assertEquals(3, fwrite($stream, 'bar')); + fclose($stream); + + $stream = $this->getStream($fileName, 'r', 6); + $this->assertEquals('barbar', fread($stream, 100)); + fclose($stream); + + unlink($fileName); +} + + public function testSeek() { + $fileName = tempnam("/tmp", "FOO"); + $stream = $this->getStream($fileName, 'w+', 0); + $this->assertEquals(6, fwrite($stream, 'foobar')); + $this->assertEquals(0, fseek($stream, 3)); + $this->assertEquals(6, fwrite($stream, 'foobar')); + fclose($stream); + + $stream = $this->getStream($fileName, 'r', 9); + $this->assertEquals('foofoobar', fread($stream, 100)); + $this->assertEquals(-1, fseek($stream, 10)); + $this->assertEquals(0, fseek($stream, 9)); + $this->assertEquals(-1, fseek($stream, -10, SEEK_CUR)); + $this->assertEquals(0, fseek($stream, -9, SEEK_CUR)); + $this->assertEquals(-1, fseek($stream, -10, SEEK_END)); + $this->assertEquals(0, fseek($stream, -9, SEEK_END)); + fclose($stream); + + unlink($fileName); + } + + function dataFilesProvider() { + return [ + ['lorem-big.txt'], + ['block-aligned.txt'], + ['block-aligned-plus-one.txt'], + ]; + } + + /** + * @dataProvider dataFilesProvider + */ + public function testWriteReadBigFile($testFile) { + + $expectedData = file_get_contents(\OC::$SERVERROOT . '/tests/data/' . $testFile); + // write it + $fileName = tempnam("/tmp", "FOO"); + $stream = $this->getStream($fileName, 'w+', 0); + // while writing the file from the beginning to the end we should never try + // to read parts of the file. This should only happen for write operations + // in the middle of a file + $this->encryptionModule->expects($this->never())->method('decrypt'); + fwrite($stream, $expectedData); + fclose($stream); + + // read it all + $stream = $this->getStream($fileName, 'r', strlen($expectedData)); + $data = stream_get_contents($stream); + fclose($stream); + + $this->assertEquals($expectedData, $data); + + // another read test with a loop like we do in several places: + $stream = $this->getStream($fileName, 'r', strlen($expectedData)); + $data = ''; + while (!feof($stream)) { + $data .= fread($stream, 8192); + } + fclose($stream); + + $this->assertEquals($expectedData, $data); + + unlink($fileName); + } + + /** + * simulate a non-seekable storage + * + * @dataProvider dataFilesProvider + */ + public function testWriteToNonSeekableStorage($testFile) { + + $wrapper = $this->getMockBuilder('\OC\Files\Stream\Encryption') + ->setMethods(['parentSeekStream'])->getMock(); + $wrapper->expects($this->any())->method('parentSeekStream')->willReturn(false); + + $expectedData = file_get_contents(\OC::$SERVERROOT . '/tests/data/' . $testFile); + // write it + $fileName = tempnam("/tmp", "FOO"); + $stream = $this->getStream($fileName, 'w+', 0, '\Test\Files\Stream\DummyEncryptionWrapper'); + // while writing the file from the beginning to the end we should never try + // to read parts of the file. This should only happen for write operations + // in the middle of a file + $this->encryptionModule->expects($this->never())->method('decrypt'); + fwrite($stream, $expectedData); + fclose($stream); + + // read it all + $stream = $this->getStream($fileName, 'r', strlen($expectedData), '\Test\Files\Stream\DummyEncryptionWrapper'); + $data = stream_get_contents($stream); + fclose($stream); + + $this->assertEquals($expectedData, $data); + + // another read test with a loop like we do in several places: + $stream = $this->getStream($fileName, 'r', strlen($expectedData)); + $data = ''; + while (!feof($stream)) { + $data .= fread($stream, 8192); + } + fclose($stream); + + $this->assertEquals($expectedData, $data); + + unlink($fileName); + + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function buildMockModule() { + $encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser']) + ->getMock(); + + $encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE'); + $encryptionModule->expects($this->any())->method('getDisplayName')->willReturn('Unit test module'); + $encryptionModule->expects($this->any())->method('begin')->willReturn([]); + $encryptionModule->expects($this->any())->method('end')->willReturn(''); + $encryptionModule->expects($this->any())->method('isReadable')->willReturn(true); + $encryptionModule->expects($this->any())->method('encrypt')->willReturnCallback(function($data) { + // simulate different block size by adding some padding to the data + if (isset($data[6125])) { + return str_pad($data, 8192, 'X'); + } + // last block + return $data; + }); + $encryptionModule->expects($this->any())->method('decrypt')->willReturnCallback(function($data) { + if (isset($data[8191])) { + return substr($data, 0, 6126); + } + // last block + return $data; + }); + $encryptionModule->expects($this->any())->method('update')->willReturn(true); + $encryptionModule->expects($this->any())->method('shouldEncrypt')->willReturn(true); + $encryptionModule->expects($this->any())->method('getUnencryptedBlockSize')->willReturn(6126); + return $encryptionModule; + } +} diff --git a/tests/lib/Files/Stream/QuotaTest.php b/tests/lib/Files/Stream/QuotaTest.php new file mode 100644 index 00000000000..d084f0c769c --- /dev/null +++ b/tests/lib/Files/Stream/QuotaTest.php @@ -0,0 +1,155 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Stream; + +class QuotaTest extends \Test\TestCase { + protected function tearDown() { + \OC\Files\Stream\Quota::clear(); + parent::tearDown(); + } + + /** + * @param string $mode + * @param integer $limit + */ + protected function getStream($mode, $limit) { + $source = fopen('php://temp', $mode); + return \OC\Files\Stream\Quota::wrap($source, $limit); + } + + public function testWriteEnoughSpace() { + $stream = $this->getStream('w+', 100); + $this->assertEquals(6, fwrite($stream, 'foobar')); + rewind($stream); + $this->assertEquals('foobar', fread($stream, 100)); + } + + public function testWriteNotEnoughSpace() { + $stream = $this->getStream('w+', 3); + $this->assertEquals(3, fwrite($stream, 'foobar')); + rewind($stream); + $this->assertEquals('foo', fread($stream, 100)); + } + + public function testWriteNotEnoughSpaceSecondTime() { + $stream = $this->getStream('w+', 9); + $this->assertEquals(6, fwrite($stream, 'foobar')); + $this->assertEquals(3, fwrite($stream, 'qwerty')); + rewind($stream); + $this->assertEquals('foobarqwe', fread($stream, 100)); + } + + public function testWriteEnoughSpaceRewind() { + $stream = $this->getStream('w+', 6); + $this->assertEquals(6, fwrite($stream, 'foobar')); + rewind($stream); + $this->assertEquals(3, fwrite($stream, 'qwe')); + rewind($stream); + $this->assertEquals('qwebar', fread($stream, 100)); + } + + public function testWriteNotEnoughSpaceRead() { + $stream = $this->getStream('w+', 6); + $this->assertEquals(6, fwrite($stream, 'foobar')); + rewind($stream); + $this->assertEquals('foobar', fread($stream, 6)); + $this->assertEquals(0, fwrite($stream, 'qwe')); + } + + public function testWriteNotEnoughSpaceExistingStream() { + $source = fopen('php://temp', 'w+'); + fwrite($source, 'foobar'); + $stream = \OC\Files\Stream\Quota::wrap($source, 3); + $this->assertEquals(3, fwrite($stream, 'foobar')); + rewind($stream); + $this->assertEquals('foobarfoo', fread($stream, 100)); + } + + public function testWriteNotEnoughSpaceExistingStreamRewind() { + $source = fopen('php://temp', 'w+'); + fwrite($source, 'foobar'); + $stream = \OC\Files\Stream\Quota::wrap($source, 3); + rewind($stream); + $this->assertEquals(6, fwrite($stream, 'qwerty')); + rewind($stream); + $this->assertEquals('qwerty', fread($stream, 100)); + } + + public function testFseekReturnsSuccess() { + $stream = $this->getStream('w+', 100); + fwrite($stream, '0123456789'); + $this->assertEquals(0, fseek($stream, 3, SEEK_SET)); + $this->assertEquals(0, fseek($stream, -1, SEEK_CUR)); + $this->assertEquals(0, fseek($stream, -4, SEEK_END)); + } + + public function testWriteAfterSeekEndWithEnoughSpace() { + $stream = $this->getStream('w+', 100); + fwrite($stream, '0123456789'); + fseek($stream, -3, SEEK_END); + $this->assertEquals(11, fwrite($stream, 'abcdefghijk')); + rewind($stream); + $this->assertEquals('0123456abcdefghijk', fread($stream, 100)); + } + + public function testWriteAfterSeekEndWithNotEnoughSpace() { + $stream = $this->getStream('w+', 13); + fwrite($stream, '0123456789'); + // seek forward first to potentially week out + // potential limit calculation errors + fseek($stream, 4, SEEK_SET); + // seek to the end + fseek($stream, -3, SEEK_END); + $this->assertEquals(6, fwrite($stream, 'abcdefghijk')); + rewind($stream); + $this->assertEquals('0123456abcdef', fread($stream, 100)); + } + + public function testWriteAfterSeekSetWithEnoughSpace() { + $stream = $this->getStream('w+', 100); + fwrite($stream, '0123456789'); + fseek($stream, 7, SEEK_SET); + $this->assertEquals(11, fwrite($stream, 'abcdefghijk')); + rewind($stream); + $this->assertEquals('0123456abcdefghijk', fread($stream, 100)); + } + + public function testWriteAfterSeekSetWithNotEnoughSpace() { + $stream = $this->getStream('w+', 13); + fwrite($stream, '0123456789'); + fseek($stream, 7, SEEK_SET); + $this->assertEquals(6, fwrite($stream, 'abcdefghijk')); + rewind($stream); + $this->assertEquals('0123456abcdef', fread($stream, 100)); + } + + public function testWriteAfterSeekCurWithEnoughSpace() { + $stream = $this->getStream('w+', 100); + fwrite($stream, '0123456789'); + rewind($stream); + fseek($stream, 3, SEEK_CUR); + fseek($stream, 5, SEEK_CUR); + fseek($stream, -1, SEEK_CUR); + $this->assertEquals(11, fwrite($stream, 'abcdefghijk')); + rewind($stream); + $this->assertEquals('0123456abcdefghijk', fread($stream, 100)); + } + + public function testWriteAfterSeekCurWithNotEnoughSpace() { + $stream = $this->getStream('w+', 13); + fwrite($stream, '0123456789'); + rewind($stream); + fseek($stream, 3, SEEK_CUR); + fseek($stream, 5, SEEK_CUR); + fseek($stream, -1, SEEK_CUR); + $this->assertEquals(6, fwrite($stream, 'abcdefghijk')); + rewind($stream); + $this->assertEquals('0123456abcdef', fread($stream, 100)); + } +} diff --git a/tests/lib/Files/Stream/StaticStreamTest.php b/tests/lib/Files/Stream/StaticStreamTest.php new file mode 100644 index 00000000000..309291a3a5b --- /dev/null +++ b/tests/lib/Files/Stream/StaticStreamTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Stream; + +class StaticStreamTest extends \Test\TestCase { + + private $sourceFile; + private $sourceText; + + protected function setUp() { + parent::setUp(); + $this->sourceFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->sourceText = file_get_contents($this->sourceFile); + } + + protected function tearDown() { + \OC\Files\Stream\StaticStream::clear(); + parent::tearDown(); + } + + public function testContent() { + file_put_contents('static://foo', $this->sourceText); + $this->assertEquals($this->sourceText, file_get_contents('static://foo')); + } + + public function testMultipleFiles() { + file_put_contents('static://foo', $this->sourceText); + file_put_contents('static://bar', strrev($this->sourceText)); + $this->assertEquals($this->sourceText, file_get_contents('static://foo')); + $this->assertEquals(strrev($this->sourceText), file_get_contents('static://bar')); + } + + public function testOverwrite() { + file_put_contents('static://foo', $this->sourceText); + file_put_contents('static://foo', 'qwerty'); + $this->assertEquals('qwerty', file_get_contents('static://foo')); + } + + public function testIsFile() { + $this->assertFalse(is_file('static://foo')); + file_put_contents('static://foo', $this->sourceText); + $this->assertTrue(is_file('static://foo')); + } + + public function testIsDir() { + $this->assertFalse(is_dir('static://foo')); + file_put_contents('static://foo', $this->sourceText); + $this->assertFalse(is_dir('static://foo')); + } + + public function testFileType() { + file_put_contents('static://foo', $this->sourceText); + $this->assertEquals('file', filetype('static://foo')); + } + + public function testUnlink() { + $this->assertFalse(file_exists('static://foo')); + file_put_contents('static://foo', $this->sourceText); + $this->assertTrue(file_exists('static://foo')); + unlink('static://foo'); + clearstatcache(); + $this->assertFalse(file_exists('static://foo')); + } +} diff --git a/tests/lib/Files/Type/DetectionTest.php b/tests/lib/Files/Type/DetectionTest.php new file mode 100644 index 00000000000..5800f4eb8e3 --- /dev/null +++ b/tests/lib/Files/Type/DetectionTest.php @@ -0,0 +1,287 @@ +<?php +/** + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Files\Type; + +use \OC\Files\Type\Detection; + +class DetectionTest extends \Test\TestCase { + /** @var Detection */ + private $detection; + + public function setUp() { + parent::setUp(); + $this->detection = new Detection( + \OC::$server->getURLGenerator(), + \OC::$SERVERROOT . '/config/', + \OC::$SERVERROOT . '/resources/config/' + ); + } + + public function testDetect() { + $dir = \OC::$SERVERROOT.'/tests/data'; + + $result = $this->detection->detect($dir."/"); + $expected = 'httpd/unix-directory'; + $this->assertEquals($expected, $result); + + $result = $this->detection->detect($dir."/data.tar.gz"); + $expected = 'application/x-gzip'; + $this->assertEquals($expected, $result); + + $result = $this->detection->detect($dir."/data.zip"); + $expected = 'application/zip'; + $this->assertEquals($expected, $result); + + $result = $this->detection->detect($dir."/testimagelarge.svg"); + $expected = 'image/svg+xml'; + $this->assertEquals($expected, $result); + + $result = $this->detection->detect($dir."/testimage.png"); + $expected = 'image/png'; + $this->assertEquals($expected, $result); + } + + public function testGetSecureMimeType() { + $result = $this->detection->getSecureMimeType('image/svg+xml'); + $expected = 'text/plain'; + $this->assertEquals($expected, $result); + + $result = $this->detection->getSecureMimeType('image/png'); + $expected = 'image/png'; + $this->assertEquals($expected, $result); + } + + public function testDetectPath() { + $this->assertEquals('text/plain', $this->detection->detectPath('foo.txt')); + $this->assertEquals('image/png', $this->detection->detectPath('foo.png')); + $this->assertEquals('image/png', $this->detection->detectPath('foo.bar.png')); + $this->assertEquals('application/octet-stream', $this->detection->detectPath('.png')); + $this->assertEquals('application/octet-stream', $this->detection->detectPath('foo')); + $this->assertEquals('application/octet-stream', $this->detection->detectPath('')); + } + + public function testDetectString() { + if (\OC_Util::runningOnWindows()) { + $this->markTestSkipped('[Windows] Strings have mimetype application/octet-stream on Windows'); + } + + $result = $this->detection->detectString("/data/data.tar.gz"); + $expected = 'text/plain; charset=us-ascii'; + $this->assertEquals($expected, $result); + } + + public function testMimeTypeIcon() { + if (!class_exists('org\\bovigo\\vfs\\vfsStream')) { + $this->markTestSkipped('Package vfsStream not installed'); + } + $confDir = \org\bovigo\vfs\vfsStream::setup(); + $mimetypealiases_dist = \org\bovigo\vfs\vfsStream::newFile('mimetypealiases.dist.json')->at($confDir); + + //Empty alias file + $mimetypealiases_dist->setContent(json_encode([], JSON_FORCE_OBJECT)); + + + /* + * Test dir mimetype + */ + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->once()) + ->method('imagePath') + ->with($this->equalTo('core'), $this->equalTo('filetypes/folder.png')) + ->willReturn('folder.svg'); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('dir'); + $this->assertEquals('folder.svg', $mimeType); + + + /* + * Test dir-shareed mimetype + */ + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->once()) + ->method('imagePath') + ->with($this->equalTo('core'), $this->equalTo('filetypes/folder-shared.png')) + ->willReturn('folder-shared.svg'); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('dir-shared'); + $this->assertEquals('folder-shared.svg', $mimeType); + + + /* + * Test dir external + */ + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->once()) + ->method('imagePath') + ->with($this->equalTo('core'), $this->equalTo('filetypes/folder-external.png')) + ->willReturn('folder-external.svg'); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('dir-external'); + $this->assertEquals('folder-external.svg', $mimeType); + + + /* + * Test complete mimetype + */ + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->once()) + ->method('imagePath') + ->with($this->equalTo('core'), $this->equalTo('filetypes/my-type.png')) + ->willReturn('my-type.svg'); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('my-type'); + $this->assertEquals('my-type.svg', $mimeType); + + + /* + * Test subtype + */ + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->exactly(2)) + ->method('imagePath') + ->withConsecutive( + [$this->equalTo('core'), $this->equalTo('filetypes/my-type.png')], + [$this->equalTo('core'), $this->equalTo('filetypes/my.png')] + ) + ->will($this->returnCallback( + function($appName, $file) { + if ($file === 'filetypes/my.png') { + return 'my.svg'; + } + throw new \RuntimeException(); + } + )); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('my-type'); + $this->assertEquals('my.svg', $mimeType); + + + /* + * Test default mimetype + */ + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->exactly(3)) + ->method('imagePath') + ->withConsecutive( + [$this->equalTo('core'), $this->equalTo('filetypes/foo-bar.png')], + [$this->equalTo('core'), $this->equalTo('filetypes/foo.png')], + [$this->equalTo('core'), $this->equalTo('filetypes/file.png')] + ) + ->will($this->returnCallback( + function($appName, $file) { + if ($file === 'filetypes/file.png') { + return 'file.svg'; + } + throw new \RuntimeException(); + } + )); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('foo-bar'); + $this->assertEquals('file.svg', $mimeType); + + /* + * Test chaching + */ + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->once()) + ->method('imagePath') + ->with($this->equalTo('core'), $this->equalTo('filetypes/foo-bar.png')) + ->willReturn('foo-bar.svg'); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('foo-bar'); + $this->assertEquals('foo-bar.svg', $mimeType); + $mimeType = $detection->mimeTypeIcon('foo-bar'); + $this->assertEquals('foo-bar.svg', $mimeType); + + + + /* + * Test aliases + */ + + //Put alias + $mimetypealiases_dist->setContent(json_encode(['foo' => 'foobar/baz'], JSON_FORCE_OBJECT)); + + //Mock UrlGenerator + $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor() + ->getMock(); + + //Only call the url generator once + $urlGenerator->expects($this->once()) + ->method('imagePath') + ->with($this->equalTo('core'), $this->equalTo('filetypes/foobar-baz.png')) + ->willReturn('foobar-baz.svg'); + + $detection = new Detection($urlGenerator, $confDir->url(), $confDir->url()); + $mimeType = $detection->mimeTypeIcon('foo'); + $this->assertEquals('foobar-baz.svg', $mimeType); + } +} diff --git a/tests/lib/Files/Type/LoaderTest.php b/tests/lib/Files/Type/LoaderTest.php new file mode 100644 index 00000000000..a77d672f667 --- /dev/null +++ b/tests/lib/Files/Type/LoaderTest.php @@ -0,0 +1,93 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Files\Type; + +use \OC\Files\Type\Loader; +use \OCP\IDBConnection; + +class LoaderTest extends \Test\TestCase { + /** @var IDBConnection */ + protected $db; + /** @var Loader */ + protected $loader; + + protected function setUp() { + $this->db = \OC::$server->getDatabaseConnection(); + $this->loader = new Loader($this->db); + } + + protected function tearDown() { + $deleteMimetypes = $this->db->getQueryBuilder(); + $deleteMimetypes->delete('mimetypes') + ->where($deleteMimetypes->expr()->like( + 'mimetype', $deleteMimetypes->createPositionalParameter('testing/%') + )); + $deleteMimetypes->execute(); + } + + + public function testGetMimetype() { + $qb = $this->db->getQueryBuilder(); + $qb->insert('mimetypes') + ->values([ + 'mimetype' => $qb->createPositionalParameter('testing/mymimetype') + ]); + $qb->execute(); + + $this->assertTrue($this->loader->exists('testing/mymimetype')); + $mimetypeId = $this->loader->getId('testing/mymimetype'); + $this->assertNotNull($mimetypeId); + + $mimetype = $this->loader->getMimetypeById($mimetypeId); + $this->assertEquals('testing/mymimetype', $mimetype); + } + + public function testGetNonexistentMimetype() { + $this->assertFalse($this->loader->exists('testing/nonexistent')); + // hopefully this ID doesn't exist + $this->assertNull($this->loader->getMimetypeById(12345)); + } + + public function testStore() { + $this->assertFalse($this->loader->exists('testing/mymimetype')); + $mimetypeId = $this->loader->getId('testing/mymimetype'); + + $qb = $this->db->getQueryBuilder(); + $qb->select('mimetype') + ->from('mimetypes') + ->where($qb->expr()->eq('id', $qb->createPositionalParameter($mimetypeId))); + + $mimetype = $qb->execute()->fetch(); + $this->assertEquals('testing/mymimetype', $mimetype['mimetype']); + + $this->assertEquals('testing/mymimetype', $this->loader->getMimetypeById($mimetypeId)); + $this->assertEquals($mimetypeId, $this->loader->getId('testing/mymimetype')); + } + + public function testStoreExists() { + $mimetypeId = $this->loader->getId('testing/mymimetype'); + $mimetypeId2 = $this->loader->getId('testing/mymimetype'); + + $this->assertEquals($mimetypeId, $mimetypeId2); + } + +} diff --git a/tests/lib/Files/Utils/ScannerTest.php b/tests/lib/Files/Utils/ScannerTest.php new file mode 100644 index 00000000000..0c8e41c24c7 --- /dev/null +++ b/tests/lib/Files/Utils/ScannerTest.php @@ -0,0 +1,190 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Utils; + +use OC\Files\Filesystem; +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\Temporary; +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + +class TestScanner extends \OC\Files\Utils\Scanner { + /** + * @var \OC\Files\Mount\MountPoint[] $mounts + */ + private $mounts = array(); + + /** + * @param \OC\Files\Mount\MountPoint $mount + */ + public function addMount($mount) { + $this->mounts[] = $mount; + } + + protected function getMounts($dir) { + return $this->mounts; + } +} + +/** + * Class ScannerTest + * + * @group DB + * + * @package Test\Files\Utils + */ +class ScannerTest extends \Test\TestCase { + /** + * @var \Test\Util\User\Dummy + */ + private $userBackend; + + protected function setUp() { + parent::setUp(); + + $this->userBackend = new \Test\Util\User\Dummy(); + \OC::$server->getUserManager()->registerBackend($this->userBackend); + $this->loginAsUser(); + } + + protected function tearDown() { + $this->logout(); + \OC::$server->getUserManager()->removeBackend($this->userBackend); + parent::tearDown(); + } + + public function testReuseExistingRoot() { + $storage = new Temporary(array()); + $mount = new MountPoint($storage, ''); + Filesystem::getMountManager()->addMount($mount); + $cache = $storage->getCache(); + + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', 'qwerty'); + $storage->file_put_contents('folder/bar.txt', 'qwerty'); + + $scanner = new TestScanner('', \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); + $scanner->addMount($mount); + + $scanner->scan(''); + $this->assertTrue($cache->inCache('folder/bar.txt')); + $oldRoot = $cache->get(''); + + $scanner->scan(''); + $newRoot = $cache->get(''); + $this->assertEquals($oldRoot, $newRoot); + } + + public function testReuseExistingFile() { + $storage = new Temporary(array()); + $mount = new MountPoint($storage, ''); + Filesystem::getMountManager()->addMount($mount); + $cache = $storage->getCache(); + + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', 'qwerty'); + $storage->file_put_contents('folder/bar.txt', 'qwerty'); + + $scanner = new TestScanner('', \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); + $scanner->addMount($mount); + + $scanner->scan(''); + $this->assertTrue($cache->inCache('folder/bar.txt')); + $old = $cache->get('folder/bar.txt'); + + $scanner->scan(''); + $new = $cache->get('folder/bar.txt'); + $this->assertEquals($old, $new); + } + + public function testScanSubMount() { + $uid = $this->getUniqueID(); + $this->userBackend->createUser($uid, 'test'); + + $mountProvider = $this->getMock('\OCP\Files\Config\IMountProvider'); + + $storage = new Temporary(array()); + $mount = new MountPoint($storage, '/' . $uid . '/files/foo'); + + $mountProvider->expects($this->any()) + ->method('getMountsForUser') + ->will($this->returnCallback(function (IUser $user, IStorageFactory $storageFactory) use ($mount, $uid) { + if ($user->getUID() === $uid) { + return [$mount]; + } else { + return []; + } + })); + + \OC::$server->getMountProviderCollection()->registerProvider($mountProvider); + $cache = $storage->getCache(); + + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', 'qwerty'); + $storage->file_put_contents('folder/bar.txt', 'qwerty'); + + $scanner = new \OC\Files\Utils\Scanner($uid, \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); + + $this->assertFalse($cache->inCache('folder/bar.txt')); + $scanner->scan('/' . $uid . '/files/foo'); + $this->assertTrue($cache->inCache('folder/bar.txt')); + } + + /** + * @return array + */ + public function invalidPathProvider() { + return [ + [ + '../', + ], + [ + '..\\', + ], + [ + '../..\\../', + ], + ]; + } + + /** + * @dataProvider invalidPathProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid path to scan + * @param string $invalidPath + */ + public function testInvalidPathScanning($invalidPath) { + $scanner = new TestScanner('', \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); + $scanner->scan($invalidPath); + } + + public function testPropagateEtag() { + $storage = new Temporary(array()); + $mount = new MountPoint($storage, ''); + Filesystem::getMountManager()->addMount($mount); + $cache = $storage->getCache(); + + $storage->mkdir('folder'); + $storage->file_put_contents('folder/bar.txt', 'qwerty'); + $storage->touch('folder/bar.txt', time() - 200); + + $scanner = new TestScanner('', \OC::$server->getDatabaseConnection(), \OC::$server->getLogger()); + $scanner->addMount($mount); + + $scanner->scan(''); + $this->assertTrue($cache->inCache('folder/bar.txt')); + $oldRoot = $cache->get(''); + + $storage->file_put_contents('folder/bar.txt', 'qwerty'); + $scanner->scan(''); + $newRoot = $cache->get(''); + + $this->assertNotEquals($oldRoot->getEtag(), $newRoot->getEtag()); + } +} diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php new file mode 100644 index 00000000000..2c27bb64a70 --- /dev/null +++ b/tests/lib/Files/ViewTest.php @@ -0,0 +1,2447 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ + +namespace Test\Files; + +use OC\Files\Cache\Watcher; +use OC\Files\Storage\Common; +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\Temporary; +use OCP\Files\FileInfo; +use OCP\Lock\ILockingProvider; + +class TemporaryNoTouch extends \OC\Files\Storage\Temporary { + public function touch($path, $mtime = null) { + return false; + } +} + +class TemporaryNoCross extends \OC\Files\Storage\Temporary { + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } +} + +class TemporaryNoLocal extends \OC\Files\Storage\Temporary { + public function instanceOfStorage($className) { + if ($className === '\OC\Files\Storage\Local') { + return false; + } else { + return parent::instanceOfStorage($className); + } + } +} + +/** + * Class ViewTest + * + * @group DB + * + * @package Test\Files + */ +class ViewTest extends \Test\TestCase { + /** + * @var \OC\Files\Storage\Storage[] $storages + */ + private $storages = array(); + + /** + * @var string + */ + private $user; + + /** + * @var \OCP\IUser + */ + private $userObject; + + /** + * @var \OCP\IGroup + */ + private $groupObject; + + /** @var \OC\Files\Storage\Storage */ + private $tempStorage; + + protected function setUp() { + parent::setUp(); + \OC_Hook::clear(); + + \OC_User::clearBackends(); + \OC_User::useBackend(new \Test\Util\User\Dummy()); + + //login + $userManager = \OC::$server->getUserManager(); + $groupManager = \OC::$server->getGroupManager(); + $this->user = 'test'; + $this->userObject = $userManager->createUser('test', 'test'); + + $this->groupObject = $groupManager->createGroup('group1'); + $this->groupObject->addUser($this->userObject); + + $this->loginAsUser($this->user); + // clear mounts but somehow keep the root storage + // that was initialized above... + \OC\Files\Filesystem::clearMounts(); + + $this->tempStorage = null; + } + + protected function tearDown() { + \OC_User::setUserId($this->user); + foreach ($this->storages as $storage) { + $cache = $storage->getCache(); + $ids = $cache->getAll(); + $cache->clear(); + } + + if ($this->tempStorage && !\OC_Util::runningOnWindows()) { + system('rm -rf ' . escapeshellarg($this->tempStorage->getDataDir())); + } + + $this->logout(); + + $this->userObject->delete(); + $this->groupObject->delete(); + + $mountProviderCollection = \OC::$server->getMountProviderCollection(); + \Test\TestCase::invokePrivate($mountProviderCollection, 'providers', [[]]); + + parent::tearDown(); + } + + /** + * @medium + */ + public function testCacheAPI() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $storage3 = $this->getTestStorage(); + $root = $this->getUniqueID('/'); + \OC\Files\Filesystem::mount($storage1, array(), $root . '/'); + \OC\Files\Filesystem::mount($storage2, array(), $root . '/substorage'); + \OC\Files\Filesystem::mount($storage3, array(), $root . '/folder/anotherstorage'); + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $storageSize = $textSize * 2 + $imageSize; + + $storageInfo = $storage3->getCache()->get(''); + $this->assertEquals($storageSize, $storageInfo['size']); + + $rootView = new \OC\Files\View($root); + + $cachedData = $rootView->getFileInfo('/foo.txt'); + $this->assertEquals($textSize, $cachedData['size']); + $this->assertEquals('text/plain', $cachedData['mimetype']); + $this->assertNotEquals(-1, $cachedData['permissions']); + + $cachedData = $rootView->getFileInfo('/'); + $this->assertEquals($storageSize * 3, $cachedData['size']); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + + // get cached data excluding mount points + $cachedData = $rootView->getFileInfo('/', false); + $this->assertEquals($storageSize, $cachedData['size']); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + + $cachedData = $rootView->getFileInfo('/folder'); + $this->assertEquals($storageSize + $textSize, $cachedData['size']); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + + $folderData = $rootView->getDirectoryContent('/'); + /** + * expected entries: + * folder + * foo.png + * foo.txt + * substorage + */ + $this->assertEquals(4, count($folderData)); + $this->assertEquals('folder', $folderData[0]['name']); + $this->assertEquals('foo.png', $folderData[1]['name']); + $this->assertEquals('foo.txt', $folderData[2]['name']); + $this->assertEquals('substorage', $folderData[3]['name']); + + $this->assertEquals($storageSize + $textSize, $folderData[0]['size']); + $this->assertEquals($imageSize, $folderData[1]['size']); + $this->assertEquals($textSize, $folderData[2]['size']); + $this->assertEquals($storageSize, $folderData[3]['size']); + + $folderData = $rootView->getDirectoryContent('/substorage'); + /** + * expected entries: + * folder + * foo.png + * foo.txt + */ + $this->assertEquals(3, count($folderData)); + $this->assertEquals('folder', $folderData[0]['name']); + $this->assertEquals('foo.png', $folderData[1]['name']); + $this->assertEquals('foo.txt', $folderData[2]['name']); + + $folderView = new \OC\Files\View($root . '/folder'); + $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/')); + + $cachedData = $rootView->getFileInfo('/foo.txt'); + $this->assertFalse($cachedData['encrypted']); + $id = $rootView->putFileInfo('/foo.txt', array('encrypted' => true)); + $cachedData = $rootView->getFileInfo('/foo.txt'); + $this->assertTrue($cachedData['encrypted']); + $this->assertEquals($cachedData['fileid'], $id); + + $this->assertFalse($rootView->getFileInfo('/non/existing')); + $this->assertEquals(array(), $rootView->getDirectoryContent('/non/existing')); + } + + /** + * @medium + */ + public function testGetPath() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $storage3 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + \OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage'); + + $rootView = new \OC\Files\View(''); + + $cachedData = $rootView->getFileInfo('/foo.txt'); + /** @var int $id1 */ + $id1 = $cachedData['fileid']; + $this->assertEquals('/foo.txt', $rootView->getPath($id1)); + + $cachedData = $rootView->getFileInfo('/substorage/foo.txt'); + /** @var int $id2 */ + $id2 = $cachedData['fileid']; + $this->assertEquals('/substorage/foo.txt', $rootView->getPath($id2)); + + $folderView = new \OC\Files\View('/substorage'); + $this->assertEquals('/foo.txt', $folderView->getPath($id2)); + } + + /** + * @expectedException \OCP\Files\NotFoundException + */ + function testGetPathNotExisting() { + $storage1 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, [], '/'); + + $rootView = new \OC\Files\View(''); + $cachedData = $rootView->getFileInfo('/foo.txt'); + /** @var int $id1 */ + $id1 = $cachedData['fileid']; + $folderView = new \OC\Files\View('/substorage'); + $this->assertNull($folderView->getPath($id1)); + } + + /** + * @medium + */ + public function testMountPointOverwrite() { + $storage1 = $this->getTestStorage(false); + $storage2 = $this->getTestStorage(); + $storage1->mkdir('substorage'); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + + $rootView = new \OC\Files\View(''); + $folderContent = $rootView->getDirectoryContent('/'); + $this->assertEquals(4, count($folderContent)); + } + + public function sharingDisabledPermissionProvider() { + return [ + ['no', '', true], + ['yes', 'group1', false], + ]; + } + + /** + * @dataProvider sharingDisabledPermissionProvider + */ + public function testRemoveSharePermissionWhenSharingDisabledForUser($excludeGroups, $excludeGroupsList, $expectedShareable) { + $appConfig = \OC::$server->getAppConfig(); + $oldExcludeGroupsFlag = $appConfig->getValue('core', 'shareapi_exclude_groups', 'no'); + $oldExcludeGroupsList = $appConfig->getValue('core', 'shareapi_exclude_groups_list', ''); + $appConfig->setValue('core', 'shareapi_exclude_groups', $excludeGroups); + $appConfig->setValue('core', 'shareapi_exclude_groups_list', $excludeGroupsList); + + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/mount'); + + $view = new \OC\Files\View('/'); + + $folderContent = $view->getDirectoryContent(''); + $this->assertEquals($expectedShareable, $folderContent[0]->isShareable()); + + $folderContent = $view->getDirectoryContent('mount'); + $this->assertEquals($expectedShareable, $folderContent[0]->isShareable()); + + $appConfig->setValue('core', 'shareapi_exclude_groups', $oldExcludeGroupsFlag); + $appConfig->setValue('core', 'shareapi_exclude_groups_list', $oldExcludeGroupsList); + } + + public function testCacheIncompleteFolder() { + $storage1 = $this->getTestStorage(false); + \OC\Files\Filesystem::clearMounts(); + \OC\Files\Filesystem::mount($storage1, array(), '/incomplete'); + $rootView = new \OC\Files\View('/incomplete'); + + $entries = $rootView->getDirectoryContent('/'); + $this->assertEquals(3, count($entries)); + + // /folder will already be in the cache but not scanned + $entries = $rootView->getDirectoryContent('/folder'); + $this->assertEquals(1, count($entries)); + } + + public function testAutoScan() { + $storage1 = $this->getTestStorage(false); + $storage2 = $this->getTestStorage(false); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + $textSize = strlen("dummy file data\n"); + + $rootView = new \OC\Files\View(''); + + $cachedData = $rootView->getFileInfo('/'); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + $this->assertEquals(-1, $cachedData['size']); + + $folderData = $rootView->getDirectoryContent('/substorage/folder'); + $this->assertEquals('text/plain', $folderData[0]['mimetype']); + $this->assertEquals($textSize, $folderData[0]['size']); + } + + /** + * @medium + */ + public function testSearch() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $storage3 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + \OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage'); + + $rootView = new \OC\Files\View(''); + + $results = $rootView->search('foo'); + $this->assertEquals(6, count($results)); + $paths = array(); + foreach ($results as $result) { + $this->assertEquals($result['path'], \OC\Files\Filesystem::normalizePath($result['path'])); + $paths[] = $result['path']; + } + $this->assertContains('/foo.txt', $paths); + $this->assertContains('/foo.png', $paths); + $this->assertContains('/substorage/foo.txt', $paths); + $this->assertContains('/substorage/foo.png', $paths); + $this->assertContains('/folder/anotherstorage/foo.txt', $paths); + $this->assertContains('/folder/anotherstorage/foo.png', $paths); + + $folderView = new \OC\Files\View('/folder'); + $results = $folderView->search('bar'); + $this->assertEquals(2, count($results)); + $paths = array(); + foreach ($results as $result) { + $paths[] = $result['path']; + } + $this->assertContains('/anotherstorage/folder/bar.txt', $paths); + $this->assertContains('/bar.txt', $paths); + + $results = $folderView->search('foo'); + $this->assertEquals(2, count($results)); + $paths = array(); + foreach ($results as $result) { + $paths[] = $result['path']; + } + $this->assertContains('/anotherstorage/foo.txt', $paths); + $this->assertContains('/anotherstorage/foo.png', $paths); + + $this->assertEquals(6, count($rootView->searchByMime('text'))); + $this->assertEquals(3, count($folderView->searchByMime('text'))); + } + + /** + * @medium + */ + public function testWatcher() { + $storage1 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + $storage1->getWatcher()->setPolicy(Watcher::CHECK_ALWAYS); + + $rootView = new \OC\Files\View(''); + + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertEquals(16, $cachedData['size']); + + $rootView->putFileInfo('foo.txt', array('storage_mtime' => 10)); + $storage1->file_put_contents('foo.txt', 'foo'); + clearstatcache(); + + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertEquals(3, $cachedData['size']); + } + + /** + * @medium + */ + public function testCopyBetweenStorageNoCross() { + $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); + $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); + $this->copyBetweenStorages($storage1, $storage2); + } + + /** + * @medium + */ + public function testCopyBetweenStorageCross() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $this->copyBetweenStorages($storage1, $storage2); + } + + /** + * @medium + */ + public function testCopyBetweenStorageCrossNonLocal() { + $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); + $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); + $this->copyBetweenStorages($storage1, $storage2); + } + + function copyBetweenStorages($storage1, $storage2) { + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + + $rootView = new \OC\Files\View(''); + $rootView->mkdir('substorage/emptyfolder'); + $rootView->copy('substorage', 'anotherfolder'); + $this->assertTrue($rootView->is_dir('/anotherfolder')); + $this->assertTrue($rootView->is_dir('/substorage')); + $this->assertTrue($rootView->is_dir('/anotherfolder/emptyfolder')); + $this->assertTrue($rootView->is_dir('/substorage/emptyfolder')); + $this->assertTrue($rootView->file_exists('/anotherfolder/foo.txt')); + $this->assertTrue($rootView->file_exists('/anotherfolder/foo.png')); + $this->assertTrue($rootView->file_exists('/anotherfolder/folder/bar.txt')); + $this->assertTrue($rootView->file_exists('/substorage/foo.txt')); + $this->assertTrue($rootView->file_exists('/substorage/foo.png')); + $this->assertTrue($rootView->file_exists('/substorage/folder/bar.txt')); + } + + /** + * @medium + */ + public function testMoveBetweenStorageNoCross() { + $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); + $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); + $this->moveBetweenStorages($storage1, $storage2); + } + + /** + * @medium + */ + public function testMoveBetweenStorageCross() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $this->moveBetweenStorages($storage1, $storage2); + } + + /** + * @medium + */ + public function testMoveBetweenStorageCrossNonLocal() { + $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); + $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); + $this->moveBetweenStorages($storage1, $storage2); + } + + function moveBetweenStorages($storage1, $storage2) { + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + + $rootView = new \OC\Files\View(''); + $rootView->rename('foo.txt', 'substorage/folder/foo.txt'); + $this->assertFalse($rootView->file_exists('foo.txt')); + $this->assertTrue($rootView->file_exists('substorage/folder/foo.txt')); + $rootView->rename('substorage/folder', 'anotherfolder'); + $this->assertFalse($rootView->is_dir('substorage/folder')); + $this->assertTrue($rootView->file_exists('anotherfolder/foo.txt')); + $this->assertTrue($rootView->file_exists('anotherfolder/bar.txt')); + } + + /** + * @medium + */ + public function testUnlink() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + + $rootView = new \OC\Files\View(''); + $rootView->file_put_contents('/foo.txt', 'asd'); + $rootView->file_put_contents('/substorage/bar.txt', 'asd'); + + $this->assertTrue($rootView->file_exists('foo.txt')); + $this->assertTrue($rootView->file_exists('substorage/bar.txt')); + + $this->assertTrue($rootView->unlink('foo.txt')); + $this->assertTrue($rootView->unlink('substorage/bar.txt')); + + $this->assertFalse($rootView->file_exists('foo.txt')); + $this->assertFalse($rootView->file_exists('substorage/bar.txt')); + } + + /** + * @medium + */ + public function testUnlinkRootMustFail() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + + $rootView = new \OC\Files\View(''); + $rootView->file_put_contents('/foo.txt', 'asd'); + $rootView->file_put_contents('/substorage/bar.txt', 'asd'); + + $this->assertFalse($rootView->unlink('')); + $this->assertFalse($rootView->unlink('/')); + $this->assertFalse($rootView->unlink('substorage')); + $this->assertFalse($rootView->unlink('/substorage')); + } + + /** + * @medium + */ + public function testTouch() { + $storage = $this->getTestStorage(true, '\Test\Files\TemporaryNoTouch'); + + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + $oldCachedData = $rootView->getFileInfo('foo.txt'); + + $rootView->touch('foo.txt', 500); + + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertEquals(500, $cachedData['mtime']); + $this->assertEquals($oldCachedData['storage_mtime'], $cachedData['storage_mtime']); + + $rootView->putFileInfo('foo.txt', array('storage_mtime' => 1000)); //make sure the watcher detects the change + $rootView->file_put_contents('foo.txt', 'asd'); + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertGreaterThanOrEqual($oldCachedData['mtime'], $cachedData['mtime']); + $this->assertEquals($cachedData['storage_mtime'], $cachedData['mtime']); + } + + /** + * @medium + */ + public function testViewHooks() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $defaultRoot = \OC\Files\Filesystem::getRoot(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '/substorage'); + \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); + + $rootView = new \OC\Files\View(''); + $subView = new \OC\Files\View($defaultRoot . '/substorage'); + $this->hookPath = null; + + $rootView->file_put_contents('/foo.txt', 'asd'); + $this->assertNull($this->hookPath); + + $subView->file_put_contents('/foo.txt', 'asd'); + $this->assertEquals('/substorage/foo.txt', $this->hookPath); + } + + private $hookPath; + + public function dummyHook($params) { + $this->hookPath = $params['path']; + } + + public function testSearchNotOutsideView() { + $storage1 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + $storage1->rename('folder', 'foo'); + $scanner = $storage1->getScanner(); + $scanner->scan(''); + + $view = new \OC\Files\View('/foo'); + + $result = $view->search('.txt'); + $this->assertCount(1, $result); + } + + /** + * @param bool $scan + * @param string $class + * @return \OC\Files\Storage\Storage + */ + private function getTestStorage($scan = true, $class = '\OC\Files\Storage\Temporary') { + /** + * @var \OC\Files\Storage\Storage $storage + */ + $storage = new $class(array()); + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', $textData); + $storage->file_put_contents('foo.png', $imgData); + $storage->file_put_contents('folder/bar.txt', $textData); + + if ($scan) { + $scanner = $storage->getScanner(); + $scanner->scan(''); + } + $this->storages[] = $storage; + return $storage; + } + + /** + * @medium + */ + public function testViewHooksIfRootStartsTheSame() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $defaultRoot = \OC\Files\Filesystem::getRoot(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '_substorage'); + \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); + + $subView = new \OC\Files\View($defaultRoot . '_substorage'); + $this->hookPath = null; + + $subView->file_put_contents('/foo.txt', 'asd'); + $this->assertNull($this->hookPath); + } + + private $hookWritePath; + private $hookCreatePath; + private $hookUpdatePath; + + public function dummyHookWrite($params) { + $this->hookWritePath = $params['path']; + } + + public function dummyHookUpdate($params) { + $this->hookUpdatePath = $params['path']; + } + + public function dummyHookCreate($params) { + $this->hookCreatePath = $params['path']; + } + + public function testEditNoCreateHook() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $defaultRoot = \OC\Files\Filesystem::getRoot(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), $defaultRoot); + \OC_Hook::connect('OC_Filesystem', 'post_create', $this, 'dummyHookCreate'); + \OC_Hook::connect('OC_Filesystem', 'post_update', $this, 'dummyHookUpdate'); + \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHookWrite'); + + $view = new \OC\Files\View($defaultRoot); + $this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null; + + $view->file_put_contents('/asd.txt', 'foo'); + $this->assertEquals('/asd.txt', $this->hookCreatePath); + $this->assertNull($this->hookUpdatePath); + $this->assertEquals('/asd.txt', $this->hookWritePath); + + $this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null; + + $view->file_put_contents('/asd.txt', 'foo'); + $this->assertNull($this->hookCreatePath); + $this->assertEquals('/asd.txt', $this->hookUpdatePath); + $this->assertEquals('/asd.txt', $this->hookWritePath); + + \OC_Hook::clear('OC_Filesystem', 'post_create'); + \OC_Hook::clear('OC_Filesystem', 'post_update'); + \OC_Hook::clear('OC_Filesystem', 'post_write'); + } + + /** + * @dataProvider resolvePathTestProvider + */ + public function testResolvePath($expected, $pathToTest) { + $storage1 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + + $view = new \OC\Files\View(''); + + $result = $view->resolvePath($pathToTest); + $this->assertEquals($expected, $result[1]); + + $exists = $view->file_exists($pathToTest); + $this->assertTrue($exists); + + $exists = $view->file_exists($result[1]); + $this->assertTrue($exists); + } + + function resolvePathTestProvider() { + return array( + array('foo.txt', 'foo.txt'), + array('foo.txt', '/foo.txt'), + array('folder', 'folder'), + array('folder', '/folder'), + array('folder', 'folder/'), + array('folder', '/folder/'), + array('folder/bar.txt', 'folder/bar.txt'), + array('folder/bar.txt', '/folder/bar.txt'), + array('', ''), + array('', '/'), + ); + } + + public function testUTF8Names() { + $names = array('虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا'); + + $storage = new \OC\Files\Storage\Temporary(array()); + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + foreach ($names as $name) { + $rootView->file_put_contents('/' . $name, 'dummy content'); + } + + $list = $rootView->getDirectoryContent('/'); + + $this->assertCount(count($names), $list); + foreach ($list as $item) { + $this->assertContains($item['name'], $names); + } + + $cache = $storage->getCache(); + $scanner = $storage->getScanner(); + $scanner->scan(''); + + $list = $cache->getFolderContents(''); + + $this->assertCount(count($names), $list); + foreach ($list as $item) { + $this->assertContains($item['name'], $names); + } + } + + public function xtestLongPath() { + + $storage = new \OC\Files\Storage\Temporary(array()); + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + + $longPath = ''; + $ds = DIRECTORY_SEPARATOR; + /* + * 4096 is the maximum path length in file_cache.path in *nix + * 1024 is the max path length in mac + * 228 is the max path length in windows + */ + $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789'; + $tmpdirLength = strlen(\OC::$server->getTempManager()->getTemporaryFolder()); + if (\OC_Util::runningOnWindows()) { + $this->markTestSkipped('[Windows] '); + $depth = ((260 - $tmpdirLength) / 57); + } elseif (\OC_Util::runningOnMac()) { + $depth = ((1024 - $tmpdirLength) / 57); + } else { + $depth = ((4000 - $tmpdirLength) / 57); + } + foreach (range(0, $depth - 1) as $i) { + $longPath .= $ds . $folderName; + $result = $rootView->mkdir($longPath); + $this->assertTrue($result, "mkdir failed on $i - path length: " . strlen($longPath)); + + $result = $rootView->file_put_contents($longPath . "{$ds}test.txt", 'lorem'); + $this->assertEquals(5, $result, "file_put_contents failed on $i"); + + $this->assertTrue($rootView->file_exists($longPath)); + $this->assertTrue($rootView->file_exists($longPath . "{$ds}test.txt")); + } + + $cache = $storage->getCache(); + $scanner = $storage->getScanner(); + $scanner->scan(''); + + $longPath = $folderName; + foreach (range(0, $depth - 1) as $i) { + $cachedFolder = $cache->get($longPath); + $this->assertTrue(is_array($cachedFolder), "No cache entry for folder at $i"); + $this->assertEquals($folderName, $cachedFolder['name'], "Wrong cache entry for folder at $i"); + + $cachedFile = $cache->get($longPath . '/test.txt'); + $this->assertTrue(is_array($cachedFile), "No cache entry for file at $i"); + $this->assertEquals('test.txt', $cachedFile['name'], "Wrong cache entry for file at $i"); + + $longPath .= $ds . $folderName; + } + } + + public function testTouchNotSupported() { + $storage = new TemporaryNoTouch(array()); + $scanner = $storage->getScanner(); + \OC\Files\Filesystem::mount($storage, array(), '/test/'); + $past = time() - 100; + $storage->file_put_contents('test', 'foobar'); + $scanner->scan(''); + $view = new \OC\Files\View(''); + $info = $view->getFileInfo('/test/test'); + + $view->touch('/test/test', $past); + $scanner->scanFile('test', \OC\Files\Cache\Scanner::REUSE_ETAG); + + $info2 = $view->getFileInfo('/test/test'); + $this->assertSame($info['etag'], $info2['etag']); + } + + public function testWatcherEtagCrossStorage() { + $storage1 = new Temporary(array()); + $storage2 = new Temporary(array()); + $scanner1 = $storage1->getScanner(); + $scanner2 = $storage2->getScanner(); + $storage1->mkdir('sub'); + \OC\Files\Filesystem::mount($storage1, array(), '/test/'); + \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage'); + + $past = time() - 100; + $storage2->file_put_contents('test.txt', 'foobar'); + $scanner1->scan(''); + $scanner2->scan(''); + $view = new \OC\Files\View(''); + + $storage2->getWatcher('')->setPolicy(Watcher::CHECK_ALWAYS); + + $oldFileInfo = $view->getFileInfo('/test/sub/storage/test.txt'); + $oldFolderInfo = $view->getFileInfo('/test'); + + $storage2->getCache()->update($oldFileInfo->getId(), array( + 'storage_mtime' => $past + )); + + $view->getFileInfo('/test/sub/storage/test.txt'); + $newFolderInfo = $view->getFileInfo('/test'); + + $this->assertNotEquals($newFolderInfo->getEtag(), $oldFolderInfo->getEtag()); + } + + /** + * @dataProvider absolutePathProvider + */ + public function testGetAbsolutePath($expectedPath, $relativePath) { + $view = new \OC\Files\View('/files'); + $this->assertEquals($expectedPath, $view->getAbsolutePath($relativePath)); + } + + public function testPartFileInfo() { + $storage = new Temporary(array()); + $scanner = $storage->getScanner(); + \OC\Files\Filesystem::mount($storage, array(), '/test/'); + $storage->file_put_contents('test.part', 'foobar'); + $scanner->scan(''); + $view = new \OC\Files\View('/test'); + $info = $view->getFileInfo('test.part'); + + $this->assertInstanceOf('\OCP\Files\FileInfo', $info); + $this->assertNull($info->getId()); + $this->assertEquals(6, $info->getSize()); + } + + function absolutePathProvider() { + return array( + array('/files/', ''), + array('/files/0', '0'), + array('/files/false', 'false'), + array('/files/true', 'true'), + array('/files/', '/'), + array('/files/test', 'test'), + array('/files/test', '/test'), + ); + } + + /** + * @dataProvider chrootRelativePathProvider + */ + function testChrootGetRelativePath($root, $absolutePath, $expectedPath) { + $view = new \OC\Files\View('/files'); + $view->chroot($root); + $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath)); + } + + public function chrootRelativePathProvider() { + return $this->relativePathProvider('/'); + } + + /** + * @dataProvider initRelativePathProvider + */ + public function testInitGetRelativePath($root, $absolutePath, $expectedPath) { + $view = new \OC\Files\View($root); + $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath)); + } + + public function initRelativePathProvider() { + return $this->relativePathProvider(null); + } + + public function relativePathProvider($missingRootExpectedPath) { + return array( + // No root - returns the path + array('', '/files', '/files'), + array('', '/files/', '/files/'), + + // Root equals path - / + array('/files/', '/files/', '/'), + array('/files/', '/files', '/'), + array('/files', '/files/', '/'), + array('/files', '/files', '/'), + + // False negatives: chroot fixes those by adding the leading slash. + // But setting them up with this root (instead of chroot($root)) + // will fail them, although they should be the same. + // TODO init should be fixed, so it also adds the leading slash + array('files/', '/files/', $missingRootExpectedPath), + array('files', '/files/', $missingRootExpectedPath), + array('files/', '/files', $missingRootExpectedPath), + array('files', '/files', $missingRootExpectedPath), + + // False negatives: Paths provided to the method should have a leading slash + // TODO input should be checked to have a leading slash + array('/files/', 'files/', null), + array('/files', 'files/', null), + array('/files/', 'files', null), + array('/files', 'files', null), + + // with trailing slashes + array('/files/', '/files/0', '0'), + array('/files/', '/files/false', 'false'), + array('/files/', '/files/true', 'true'), + array('/files/', '/files/test', 'test'), + array('/files/', '/files/test/foo', 'test/foo'), + + // without trailing slashes + // TODO false expectation: Should match "with trailing slashes" + array('/files', '/files/0', '/0'), + array('/files', '/files/false', '/false'), + array('/files', '/files/true', '/true'), + array('/files', '/files/test', '/test'), + array('/files', '/files/test/foo', '/test/foo'), + + // leading slashes + array('/files/', '/files_trashbin/', null), + array('/files', '/files_trashbin/', null), + array('/files/', '/files_trashbin', null), + array('/files', '/files_trashbin', null), + + // no leading slashes + array('files/', 'files_trashbin/', null), + array('files', 'files_trashbin/', null), + array('files/', 'files_trashbin', null), + array('files', 'files_trashbin', null), + + // mixed leading slashes + array('files/', '/files_trashbin/', null), + array('/files/', 'files_trashbin/', null), + array('files', '/files_trashbin/', null), + array('/files', 'files_trashbin/', null), + array('files/', '/files_trashbin', null), + array('/files/', 'files_trashbin', null), + array('files', '/files_trashbin', null), + array('/files', 'files_trashbin', null), + + array('files', 'files_trashbin/test', null), + array('/files', '/files_trashbin/test', null), + array('/files', 'files_trashbin/test', null), + ); + } + + public function testFileView() { + $storage = new Temporary(array()); + $scanner = $storage->getScanner(); + $storage->file_put_contents('foo.txt', 'bar'); + \OC\Files\Filesystem::mount($storage, array(), '/test/'); + $scanner->scan(''); + $view = new \OC\Files\View('/test/foo.txt'); + + $this->assertEquals('bar', $view->file_get_contents('')); + $fh = tmpfile(); + fwrite($fh, 'foo'); + rewind($fh); + $view->file_put_contents('', $fh); + $this->assertEquals('foo', $view->file_get_contents('')); + } + + /** + * @dataProvider tooLongPathDataProvider + * @expectedException \OCP\Files\InvalidPathException + */ + public function testTooLongPath($operation, $param0 = null) { + + $longPath = ''; + // 4000 is the maximum path length in file_cache.path + $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789'; + $depth = (4000 / 57); + foreach (range(0, $depth + 1) as $i) { + $longPath .= '/' . $folderName; + } + + $storage = new \OC\Files\Storage\Temporary(array()); + $this->tempStorage = $storage; // for later hard cleanup + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + + if ($param0 === '@0') { + $param0 = $longPath; + } + + if ($operation === 'hash') { + $param0 = $longPath; + $longPath = 'md5'; + } + + call_user_func(array($rootView, $operation), $longPath, $param0); + } + + public function tooLongPathDataProvider() { + return array( + array('getAbsolutePath'), + array('getRelativePath'), + array('getMountPoint'), + array('resolvePath'), + array('getLocalFile'), + array('getLocalFolder'), + array('mkdir'), + array('rmdir'), + array('opendir'), + array('is_dir'), + array('is_file'), + array('stat'), + array('filetype'), + array('filesize'), + array('readfile'), + array('isCreatable'), + array('isReadable'), + array('isUpdatable'), + array('isDeletable'), + array('isSharable'), + array('file_exists'), + array('filemtime'), + array('touch'), + array('file_get_contents'), + array('unlink'), + array('deleteAll'), + array('toTmpFile'), + array('getMimeType'), + array('free_space'), + array('getFileInfo'), + array('getDirectoryContent'), + array('getOwner'), + array('getETag'), + array('file_put_contents', 'ipsum'), + array('rename', '@0'), + array('copy', '@0'), + array('fopen', 'r'), + array('fromTmpFile', '@0'), + array('hash'), + array('hasUpdated', 0), + array('putFileInfo', array()), + ); + } + + public function testRenameCrossStoragePreserveMtime() { + $storage1 = new Temporary(array()); + $storage2 = new Temporary(array()); + $scanner1 = $storage1->getScanner(); + $scanner2 = $storage2->getScanner(); + $storage1->mkdir('sub'); + $storage1->mkdir('foo'); + $storage1->file_put_contents('foo.txt', 'asd'); + $storage1->file_put_contents('foo/bar.txt', 'asd'); + \OC\Files\Filesystem::mount($storage1, array(), '/test/'); + \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage'); + + $view = new \OC\Files\View(''); + $time = time() - 200; + $view->touch('/test/foo.txt', $time); + $view->touch('/test/foo', $time); + $view->touch('/test/foo/bar.txt', $time); + + $view->rename('/test/foo.txt', '/test/sub/storage/foo.txt'); + + $this->assertEquals($time, $view->filemtime('/test/sub/storage/foo.txt')); + + $view->rename('/test/foo', '/test/sub/storage/foo'); + + $this->assertEquals($time, $view->filemtime('/test/sub/storage/foo/bar.txt')); + } + + public function testRenameFailDeleteTargetKeepSource() { + $this->doTestCopyRenameFail('rename'); + } + + public function testCopyFailDeleteTargetKeepSource() { + $this->doTestCopyRenameFail('copy'); + } + + private function doTestCopyRenameFail($operation) { + $storage1 = new Temporary(array()); + /** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */ + $storage2 = $this->getMockBuilder('\Test\Files\TemporaryNoCross') + ->setConstructorArgs([[]]) + ->setMethods(['fopen']) + ->getMock(); + + $storage2->expects($this->any()) + ->method('fopen') + ->will($this->returnCallback(function ($path, $mode) use ($storage2) { + /** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */ + $source = fopen($storage2->getSourcePath($path), $mode); + return \OC\Files\Stream\Quota::wrap($source, 9); + })); + + $storage1->mkdir('sub'); + $storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH'); + $storage1->mkdir('dirtomove'); + $storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits + $storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit + $storage2->file_put_contents('existing.txt', '0123'); + $storage1->getScanner()->scan(''); + $storage2->getScanner()->scan(''); + \OC\Files\Filesystem::mount($storage1, array(), '/test/'); + \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage'); + + // move file + $view = new \OC\Files\View(''); + $this->assertTrue($storage1->file_exists('foo.txt')); + $this->assertFalse($storage2->file_exists('foo.txt')); + $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/foo.txt')); + $this->assertFalse($storage2->file_exists('foo.txt')); + $this->assertFalse($storage2->getCache()->get('foo.txt')); + $this->assertTrue($storage1->file_exists('foo.txt')); + + // if target exists, it will be deleted too + $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/existing.txt')); + $this->assertFalse($storage2->file_exists('existing.txt')); + $this->assertFalse($storage2->getCache()->get('existing.txt')); + $this->assertTrue($storage1->file_exists('foo.txt')); + + // move folder + $this->assertFalse($view->$operation('/test/dirtomove/', '/test/sub/storage/dirtomove/')); + // since the move failed, the full source tree is kept + $this->assertTrue($storage1->file_exists('dirtomove/indir1.txt')); + $this->assertTrue($storage1->file_exists('dirtomove/indir2.txt')); + // second file not moved/copied + $this->assertFalse($storage2->file_exists('dirtomove/indir2.txt')); + $this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt')); + + } + + public function testDeleteFailKeepCache() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage + */ + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setConstructorArgs(array(array())) + ->setMethods(array('unlink')) + ->getMock(); + $storage->expects($this->once()) + ->method('unlink') + ->will($this->returnValue(false)); + $scanner = $storage->getScanner(); + $cache = $storage->getCache(); + $storage->file_put_contents('foo.txt', 'asd'); + $scanner->scan(''); + \OC\Files\Filesystem::mount($storage, array(), '/test/'); + + $view = new \OC\Files\View('/test'); + + $this->assertFalse($view->unlink('foo.txt')); + $this->assertTrue($cache->inCache('foo.txt')); + } + + function directoryTraversalProvider() { + return [ + ['../test/'], + ['..\\test\\my/../folder'], + ['/test/my/../foo\\'], + ]; + } + + /** + * @dataProvider directoryTraversalProvider + * @expectedException \Exception + * @param string $root + */ + public function testConstructDirectoryTraversalException($root) { + new \OC\Files\View($root); + } + + public function testRenameOverWrite() { + $storage = new Temporary(array()); + $scanner = $storage->getScanner(); + $storage->mkdir('sub'); + $storage->mkdir('foo'); + $storage->file_put_contents('foo.txt', 'asd'); + $storage->file_put_contents('foo/bar.txt', 'asd'); + $scanner->scan(''); + \OC\Files\Filesystem::mount($storage, array(), '/test/'); + $view = new \OC\Files\View(''); + $this->assertTrue($view->rename('/test/foo.txt', '/test/foo/bar.txt')); + } + + public function testSetMountOptionsInStorage() { + $mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['foo' => 'bar']); + \OC\Files\Filesystem::getMountManager()->addMount($mount); + /** @var \OC\Files\Storage\Common $storage */ + $storage = $mount->getStorage(); + $this->assertEquals($storage->getMountOption('foo'), 'bar'); + } + + public function testSetMountOptionsWatcherPolicy() { + $mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['filesystem_check_changes' => Watcher::CHECK_NEVER]); + \OC\Files\Filesystem::getMountManager()->addMount($mount); + /** @var \OC\Files\Storage\Common $storage */ + $storage = $mount->getStorage(); + $watcher = $storage->getWatcher(); + $this->assertEquals(Watcher::CHECK_NEVER, $watcher->getPolicy()); + } + + public function testGetAbsolutePathOnNull() { + $view = new \OC\Files\View(); + $this->assertNull($view->getAbsolutePath(null)); + } + + public function testGetRelativePathOnNull() { + $view = new \OC\Files\View(); + $this->assertNull($view->getRelativePath(null)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testNullAsRoot() { + new \OC\Files\View(null); + } + + /** + * e.g. reading from a folder that's being renamed + * + * @expectedException \OCP\Lock\LockedException + * + * @dataProvider dataLockPaths + * + * @param string $rootPath + * @param string $pathPrefix + */ + public function testReadFromWriteLockedPath($rootPath, $pathPrefix) { + $rootPath = str_replace('{folder}', 'files', $rootPath); + $pathPrefix = str_replace('{folder}', 'files', $pathPrefix); + + $view = new \OC\Files\View($rootPath); + $storage = new Temporary(array()); + \OC\Files\Filesystem::mount($storage, [], '/'); + $this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE)); + $view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED); + } + + /** + * Reading from a files_encryption folder that's being renamed + * + * @dataProvider dataLockPaths + * + * @param string $rootPath + * @param string $pathPrefix + */ + public function testReadFromWriteUnlockablePath($rootPath, $pathPrefix) { + $rootPath = str_replace('{folder}', 'files_encryption', $rootPath); + $pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix); + + $view = new \OC\Files\View($rootPath); + $storage = new Temporary(array()); + \OC\Files\Filesystem::mount($storage, [], '/'); + $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE)); + $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED)); + } + + /** + * e.g. writing a file that's being downloaded + * + * @expectedException \OCP\Lock\LockedException + * + * @dataProvider dataLockPaths + * + * @param string $rootPath + * @param string $pathPrefix + */ + public function testWriteToReadLockedFile($rootPath, $pathPrefix) { + $rootPath = str_replace('{folder}', 'files', $rootPath); + $pathPrefix = str_replace('{folder}', 'files', $pathPrefix); + + $view = new \OC\Files\View($rootPath); + $storage = new Temporary(array()); + \OC\Files\Filesystem::mount($storage, [], '/'); + $this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED)); + $view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE); + } + + /** + * Writing a file that's being downloaded + * + * @dataProvider dataLockPaths + * + * @param string $rootPath + * @param string $pathPrefix + */ + public function testWriteToReadUnlockableFile($rootPath, $pathPrefix) { + $rootPath = str_replace('{folder}', 'files_encryption', $rootPath); + $pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix); + + $view = new \OC\Files\View($rootPath); + $storage = new Temporary(array()); + \OC\Files\Filesystem::mount($storage, [], '/'); + $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED)); + $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE)); + } + + /** + * Test that locks are on mount point paths instead of mount root + */ + public function testLockLocalMountPointPathInsteadOfStorageRoot() { + $lockingProvider = \OC::$server->getLockingProvider(); + $view = new \OC\Files\View('/testuser/files/'); + $storage = new Temporary([]); + \OC\Files\Filesystem::mount($storage, [], '/'); + $mountedStorage = new Temporary([]); + \OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint'); + + $this->assertTrue( + $view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, true), + 'Can lock mount point' + ); + + // no exception here because storage root was not locked + $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + + $thrown = false; + try { + $storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + } catch (\OCP\Lock\LockedException $e) { + $thrown = true; + } + $this->assertTrue($thrown, 'Mount point path was locked on root storage'); + + $lockingProvider->releaseAll(); + } + + /** + * Test that locks are on mount point paths and also mount root when requested + */ + public function testLockStorageRootButNotLocalMountPoint() { + $lockingProvider = \OC::$server->getLockingProvider(); + $view = new \OC\Files\View('/testuser/files/'); + $storage = new Temporary([]); + \OC\Files\Filesystem::mount($storage, [], '/'); + $mountedStorage = new Temporary([]); + \OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint'); + + $this->assertTrue( + $view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, false), + 'Can lock mount point' + ); + + $thrown = false; + try { + $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + } catch (\OCP\Lock\LockedException $e) { + $thrown = true; + } + $this->assertTrue($thrown, 'Mount point storage root was locked on original storage'); + + // local mount point was not locked + $storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + + $lockingProvider->releaseAll(); + } + + /** + * Test that locks are on mount point paths and also mount root when requested + */ + public function testLockMountPointPathFailReleasesBoth() { + $lockingProvider = \OC::$server->getLockingProvider(); + $view = new \OC\Files\View('/testuser/files/'); + $storage = new Temporary([]); + \OC\Files\Filesystem::mount($storage, [], '/'); + $mountedStorage = new Temporary([]); + \OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint.txt'); + + // this would happen if someone is writing on the mount point + $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + + $thrown = false; + try { + // this actually acquires two locks, one on the mount point and one on the storage root, + // but the one on the storage root will fail + $view->lockFile('/mountpoint.txt', ILockingProvider::LOCK_SHARED); + } catch (\OCP\Lock\LockedException $e) { + $thrown = true; + } + $this->assertTrue($thrown, 'Cannot acquire shared lock because storage root is already locked'); + + // from here we expect that the lock on the local mount point was released properly + // so acquiring an exclusive lock will succeed + $storage->acquireLock('/testuser/files/mountpoint.txt', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + + $lockingProvider->releaseAll(); + } + + public function dataLockPaths() { + return [ + ['/testuser/{folder}', ''], + ['/testuser', '/{folder}'], + ['', '/testuser/{folder}'], + ]; + } + + public function pathRelativeToFilesProvider() { + return [ + ['admin/files', ''], + ['admin/files/x', 'x'], + ['/admin/files', ''], + ['/admin/files/sub', 'sub'], + ['/admin/files/sub/', 'sub'], + ['/admin/files/sub/sub2', 'sub/sub2'], + ['//admin//files/sub//sub2', 'sub/sub2'], + ]; + } + + /** + * @dataProvider pathRelativeToFilesProvider + */ + public function testGetPathRelativeToFiles($path, $expectedPath) { + $view = new \OC\Files\View(); + $this->assertEquals($expectedPath, $view->getPathRelativeToFiles($path)); + } + + public function pathRelativeToFilesProviderExceptionCases() { + return [ + [''], + ['x'], + ['files'], + ['/files'], + ['/admin/files_versions/abc'], + ]; + } + + /** + * @dataProvider pathRelativeToFilesProviderExceptionCases + * @expectedException \InvalidArgumentException + */ + public function testGetPathRelativeToFilesWithInvalidArgument($path) { + $view = new \OC\Files\View(); + $view->getPathRelativeToFiles($path); + } + + public function testChangeLock() { + $view = new \OC\Files\View('/testuser/files/'); + $storage = new Temporary(array()); + \OC\Files\Filesystem::mount($storage, [], '/'); + + $view->lockFile('/test/sub', ILockingProvider::LOCK_SHARED); + $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED)); + $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE)); + + $view->changeLock('//test/sub', ILockingProvider::LOCK_EXCLUSIVE); + $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE)); + + $view->changeLock('test/sub', ILockingProvider::LOCK_SHARED); + $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED)); + + $view->unlockFile('/test/sub/', ILockingProvider::LOCK_SHARED); + + $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED)); + $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE)); + + } + + public function hookPathProvider() { + return [ + ['/foo/files', '/foo', true], + ['/foo/files/bar', '/foo', true], + ['/foo', '/foo', false], + ['/foo', '/files/foo', true], + ['/foo', 'filesfoo', false], + ['', '/foo/files', true], + ['', '/foo/files/bar.txt', true] + ]; + } + + /** + * @dataProvider hookPathProvider + * @param $root + * @param $path + * @param $shouldEmit + */ + public function testHookPaths($root, $path, $shouldEmit) { + $filesystemReflection = new \ReflectionClass('\OC\Files\Filesystem'); + $defaultRootValue = $filesystemReflection->getProperty('defaultInstance'); + $defaultRootValue->setAccessible(true); + $oldRoot = $defaultRootValue->getValue(); + $defaultView = new \OC\Files\View('/foo/files'); + $defaultRootValue->setValue($defaultView); + $view = new \OC\Files\View($root); + $result = $this->invokePrivate($view, 'shouldEmitHooks', [$path]); + $defaultRootValue->setValue($oldRoot); + $this->assertEquals($shouldEmit, $result); + } + + /** + * Create test movable mount points + * + * @param array $mountPoints array of mount point locations + * @return array array of MountPoint objects + */ + private function createTestMovableMountPoints($mountPoints) { + $mounts = []; + foreach ($mountPoints as $mountPoint) { + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([]) + ->getMock(); + + $mounts[] = $this->getMock( + '\Test\TestMoveableMountPoint', + ['moveMount'], + [$storage, $mountPoint] + ); + } + + $mountProvider = $this->getMock('\OCP\Files\Config\IMountProvider'); + $mountProvider->expects($this->any()) + ->method('getMountsForUser') + ->will($this->returnValue($mounts)); + + $mountProviderCollection = \OC::$server->getMountProviderCollection(); + $mountProviderCollection->registerProvider($mountProvider); + + return $mounts; + } + + /** + * Test mount point move + */ + public function testMountPointMove() { + $this->loginAsUser($this->user); + + list($mount1, $mount2) = $this->createTestMovableMountPoints([ + $this->user . '/files/mount1', + $this->user . '/files/mount2', + ]); + $mount1->expects($this->once()) + ->method('moveMount') + ->will($this->returnValue(true)); + + $mount2->expects($this->once()) + ->method('moveMount') + ->will($this->returnValue(true)); + + $view = new \OC\Files\View('/' . $this->user . '/files/'); + $view->mkdir('sub'); + + $this->assertTrue($view->rename('mount1', 'renamed_mount'), 'Can rename mount point'); + $this->assertTrue($view->rename('mount2', 'sub/moved_mount'), 'Can move a mount point into a subdirectory'); + } + + /** + * Test that moving a mount point into another is forbidden + */ + public function testMoveMountPointIntoAnother() { + $this->loginAsUser($this->user); + + list($mount1, $mount2) = $this->createTestMovableMountPoints([ + $this->user . '/files/mount1', + $this->user . '/files/mount2', + ]); + + $mount1->expects($this->never()) + ->method('moveMount'); + + $mount2->expects($this->never()) + ->method('moveMount'); + + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $this->assertFalse($view->rename('mount1', 'mount2'), 'Cannot overwrite another mount point'); + $this->assertFalse($view->rename('mount1', 'mount2/sub'), 'Cannot move a mount point into another'); + } + + /** + * Test that moving a mount point into a shared folder is forbidden + */ + public function testMoveMountPointIntoSharedFolder() { + $this->loginAsUser($this->user); + + list($mount1) = $this->createTestMovableMountPoints([ + $this->user . '/files/mount1', + ]); + + $mount1->expects($this->never()) + ->method('moveMount'); + + $view = new \OC\Files\View('/' . $this->user . '/files/'); + $view->mkdir('shareddir'); + $view->mkdir('shareddir/sub'); + $view->mkdir('shareddir/sub2'); + + $fileId = $view->getFileInfo('shareddir')->getId(); + $userObject = \OC::$server->getUserManager()->createUser('test2', 'IHateNonMockableStaticClasses'); + $this->assertTrue(\OCP\Share::shareItem('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2', \OCP\Constants::PERMISSION_READ)); + + $this->assertFalse($view->rename('mount1', 'shareddir'), 'Cannot overwrite shared folder'); + $this->assertFalse($view->rename('mount1', 'shareddir/sub'), 'Cannot move mount point into shared folder'); + $this->assertFalse($view->rename('mount1', 'shareddir/sub/sub2'), 'Cannot move mount point into shared subfolder'); + + $this->assertTrue(\OCP\Share::unshare('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2')); + $userObject->delete(); + } + + public function basicOperationProviderForLocks() { + return [ + // --- write hook ---- + [ + 'touch', + ['touch-create.txt'], + 'touch-create.txt', + 'create', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_EXCLUSIVE, + ILockingProvider::LOCK_SHARED, + ], + [ + 'fopen', + ['test-write.txt', 'w'], + 'test-write.txt', + 'write', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_EXCLUSIVE, + null, + // exclusive lock stays until fclose + ILockingProvider::LOCK_EXCLUSIVE, + ], + [ + 'mkdir', + ['newdir'], + 'newdir', + 'write', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_EXCLUSIVE, + ILockingProvider::LOCK_SHARED, + ], + [ + 'file_put_contents', + ['file_put_contents.txt', 'blah'], + 'file_put_contents.txt', + 'write', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_EXCLUSIVE, + ILockingProvider::LOCK_SHARED, + ], + + // ---- delete hook ---- + [ + 'rmdir', + ['dir'], + 'dir', + 'delete', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_EXCLUSIVE, + ILockingProvider::LOCK_SHARED, + ], + [ + 'unlink', + ['test.txt'], + 'test.txt', + 'delete', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_EXCLUSIVE, + ILockingProvider::LOCK_SHARED, + ], + + // ---- read hook (no post hooks) ---- + [ + 'file_get_contents', + ['test.txt'], + 'test.txt', + 'read', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_SHARED, + null, + ], + [ + 'fopen', + ['test.txt', 'r'], + 'test.txt', + 'read', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_SHARED, + null, + ], + [ + 'opendir', + ['dir'], + 'dir', + 'read', + ILockingProvider::LOCK_SHARED, + ILockingProvider::LOCK_SHARED, + null, + ], + + // ---- no lock, touch hook --- + ['touch', ['test.txt'], 'test.txt', 'touch', null, null, null], + + // ---- no hooks, no locks --- + ['is_dir', ['dir'], 'dir', null], + ['is_file', ['dir'], 'dir', null], + ['stat', ['dir'], 'dir', null], + ['filetype', ['dir'], 'dir', null], + ['filesize', ['dir'], 'dir', null], + ['isCreatable', ['dir'], 'dir', null], + ['isReadable', ['dir'], 'dir', null], + ['isUpdatable', ['dir'], 'dir', null], + ['isDeletable', ['dir'], 'dir', null], + ['isSharable', ['dir'], 'dir', null], + ['file_exists', ['dir'], 'dir', null], + ['filemtime', ['dir'], 'dir', null], + ]; + } + + /** + * Test whether locks are set before and after the operation + * + * @dataProvider basicOperationProviderForLocks + * + * @param string $operation operation name on the view + * @param array $operationArgs arguments for the operation + * @param string $lockedPath path of the locked item to check + * @param string $hookType hook type + * @param int $expectedLockBefore expected lock during pre hooks + * @param int $expectedLockduring expected lock during operation + * @param int $expectedLockAfter expected lock during post hooks + * @param int $expectedStrayLock expected lock after returning, should + * be null (unlock) for most operations + */ + public function testLockBasicOperation( + $operation, + $operationArgs, + $lockedPath, + $hookType, + $expectedLockBefore = ILockingProvider::LOCK_SHARED, + $expectedLockDuring = ILockingProvider::LOCK_SHARED, + $expectedLockAfter = ILockingProvider::LOCK_SHARED, + $expectedStrayLock = null + ) { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([$operation]) + ->getMock(); + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + + // work directly on disk because mkdir might be mocked + $realPath = $storage->getSourcePath(''); + mkdir($realPath . '/files'); + mkdir($realPath . '/files/dir'); + file_put_contents($realPath . '/files/test.txt', 'blah'); + $storage->getScanner()->scan('files'); + + $storage->expects($this->once()) + ->method($operation) + ->will($this->returnCallback( + function () use ($view, $lockedPath, &$lockTypeDuring) { + $lockTypeDuring = $this->getFileLockType($view, $lockedPath); + + return true; + } + )); + + $this->assertNull($this->getFileLockType($view, $lockedPath), 'File not locked before operation'); + + $this->connectMockHooks($hookType, $view, $lockedPath, $lockTypePre, $lockTypePost); + + // do operation + call_user_func_array(array($view, $operation), $operationArgs); + + if ($hookType !== null) { + $this->assertEquals($expectedLockBefore, $lockTypePre, 'File locked properly during pre-hook'); + $this->assertEquals($expectedLockAfter, $lockTypePost, 'File locked properly during post-hook'); + $this->assertEquals($expectedLockDuring, $lockTypeDuring, 'File locked properly during operation'); + } else { + $this->assertNull($lockTypeDuring, 'File not locked during operation'); + } + + $this->assertEquals($expectedStrayLock, $this->getFileLockType($view, $lockedPath)); + } + + /** + * Test locks for file_put_content with stream. + * This code path uses $storage->fopen instead + */ + public function testLockFilePutContentWithStream() { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $path = 'test_file_put_contents.txt'; + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods(['fopen']) + ->getMock(); + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + $storage->mkdir('files'); + + $storage->expects($this->once()) + ->method('fopen') + ->will($this->returnCallback( + function () use ($view, $path, &$lockTypeDuring) { + $lockTypeDuring = $this->getFileLockType($view, $path); + + return fopen('php://temp', 'r+'); + } + )); + + $this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost); + + $this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation'); + + // do operation + $view->file_put_contents($path, fopen('php://temp', 'r+')); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePost, 'File locked properly during post-hook'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation'); + + $this->assertNull($this->getFileLockType($view, $path)); + } + + /** + * Test locks for fopen with fclose at the end + */ + public function testLockFopen() { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $path = 'test_file_put_contents.txt'; + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods(['fopen']) + ->getMock(); + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + $storage->mkdir('files'); + + $storage->expects($this->once()) + ->method('fopen') + ->will($this->returnCallback( + function () use ($view, $path, &$lockTypeDuring) { + $lockTypeDuring = $this->getFileLockType($view, $path); + + return fopen('php://temp', 'r+'); + } + )); + + $this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost); + + $this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation'); + + // do operation + $res = $view->fopen($path, 'w'); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation'); + $this->assertNull($lockTypePost, 'No post hook, no lock check possible'); + + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File still locked after fopen'); + + fclose($res); + + $this->assertNull($this->getFileLockType($view, $path), 'File unlocked after fclose'); + } + + /** + * Test locks for fopen with fclose at the end + * + * @dataProvider basicOperationProviderForLocks + * + * @param string $operation operation name on the view + * @param array $operationArgs arguments for the operation + * @param string $path path of the locked item to check + */ + public function testLockBasicOperationUnlocksAfterException( + $operation, + $operationArgs, + $path + ) { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([$operation]) + ->getMock(); + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + + // work directly on disk because mkdir might be mocked + $realPath = $storage->getSourcePath(''); + mkdir($realPath . '/files'); + mkdir($realPath . '/files/dir'); + file_put_contents($realPath . '/files/test.txt', 'blah'); + $storage->getScanner()->scan('files'); + + $storage->expects($this->once()) + ->method($operation) + ->will($this->returnCallback( + function () { + throw new \Exception('Simulated exception'); + } + )); + + $thrown = false; + try { + call_user_func_array(array($view, $operation), $operationArgs); + } catch (\Exception $e) { + $thrown = true; + $this->assertEquals('Simulated exception', $e->getMessage()); + } + $this->assertTrue($thrown, 'Exception was rethrown'); + $this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception'); + } + + /** + * Test locks for fopen with fclose at the end + * + * @dataProvider basicOperationProviderForLocks + * + * @param string $operation operation name on the view + * @param array $operationArgs arguments for the operation + * @param string $path path of the locked item to check + * @param string $hookType hook type + */ + public function testLockBasicOperationUnlocksAfterCancelledHook( + $operation, + $operationArgs, + $path, + $hookType + ) { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([$operation]) + ->getMock(); + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + $storage->mkdir('files'); + + \OCP\Util::connectHook( + \OC\Files\Filesystem::CLASSNAME, + $hookType, + '\Test\HookHelper', + 'cancellingCallback' + ); + + call_user_func_array(array($view, $operation), $operationArgs); + + $this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception'); + } + + public function lockFileRenameOrCopyDataProvider() { + return [ + ['rename', ILockingProvider::LOCK_EXCLUSIVE], + ['copy', ILockingProvider::LOCK_SHARED], + ]; + } + + /** + * Test locks for rename or copy operation + * + * @dataProvider lockFileRenameOrCopyDataProvider + * + * @param string $operation operation to be done on the view + * @param int $expectedLockTypeSourceDuring expected lock type on source file during + * the operation + */ + public function testLockFileRename($operation, $expectedLockTypeSourceDuring) { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([$operation, 'filemtime']) + ->getMock(); + + $storage->expects($this->any()) + ->method('filemtime') + ->will($this->returnValue(123456789)); + + $sourcePath = 'original.txt'; + $targetPath = 'target.txt'; + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + $storage->mkdir('files'); + $view->file_put_contents($sourcePath, 'meh'); + + $storage->expects($this->once()) + ->method($operation) + ->will($this->returnCallback( + function () use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring) { + $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath); + $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath); + + return true; + } + )); + + $this->connectMockHooks($operation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost); + $this->connectMockHooks($operation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation'); + $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation'); + + $view->$operation($sourcePath, $targetPath); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook'); + $this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook'); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook'); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation'); + $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation'); + } + + /** + * simulate a failed copy operation. + * We expect that we catch the exception, free the lock and re-throw it. + * + * @expectedException \Exception + */ + public function testLockFileCopyException() { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods(['copy']) + ->getMock(); + + $sourcePath = 'original.txt'; + $targetPath = 'target.txt'; + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + $storage->mkdir('files'); + $view->file_put_contents($sourcePath, 'meh'); + + $storage->expects($this->once()) + ->method('copy') + ->will($this->returnCallback( + function () { + throw new \Exception(); + } + )); + + $this->connectMockHooks('copy', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost); + $this->connectMockHooks('copy', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation'); + $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation'); + + try { + $view->copy($sourcePath, $targetPath); + } catch (\Exception $e) { + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation'); + $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation'); + throw $e; + } + } + + /** + * Test rename operation: unlock first path when second path was locked + */ + public function testLockFileRenameUnlockOnException() { + $this->loginAsUser('test'); + + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $sourcePath = 'original.txt'; + $targetPath = 'target.txt'; + $view->file_put_contents($sourcePath, 'meh'); + + // simulate that the target path is already locked + $view->lockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file is locked before operation'); + + $thrown = false; + try { + $view->rename($sourcePath, $targetPath); + } catch (\OCP\Lock\LockedException $e) { + $thrown = true; + } + + $this->assertTrue($thrown, 'LockedException thrown'); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file still locked after operation'); + + $view->unlockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE); + } + + /** + * Test rename operation: unlock first path when second path was locked + */ + public function testGetOwner() { + $this->loginAsUser('test'); + + $view = new \OC\Files\View('/test/files/'); + + $path = 'foo.txt'; + $view->file_put_contents($path, 'meh'); + + $this->assertEquals('test', $view->getFileInfo($path)->getOwner()->getUID()); + + $folderInfo = $view->getDirectoryContent(''); + $folderInfo = array_values(array_filter($folderInfo, function (FileInfo $info) { + return $info->getName() === 'foo.txt'; + })); + + $this->assertEquals('test', $folderInfo[0]->getOwner()->getUID()); + + $subStorage = new Temporary(); + \OC\Files\Filesystem::mount($subStorage, [], '/test/files/asd'); + + $folderInfo = $view->getDirectoryContent(''); + $folderInfo = array_values(array_filter($folderInfo, function (FileInfo $info) { + return $info->getName() === 'asd'; + })); + + $this->assertEquals('test', $folderInfo[0]->getOwner()->getUID()); + } + + public function lockFileRenameOrCopyCrossStorageDataProvider() { + return [ + ['rename', 'moveFromStorage', ILockingProvider::LOCK_EXCLUSIVE], + ['copy', 'copyFromStorage', ILockingProvider::LOCK_SHARED], + ]; + } + + /** + * Test locks for rename or copy operation cross-storage + * + * @dataProvider lockFileRenameOrCopyCrossStorageDataProvider + * + * @param string $viewOperation operation to be done on the view + * @param string $storageOperation operation to be mocked on the storage + * @param int $expectedLockTypeSourceDuring expected lock type on source file during + * the operation + */ + public function testLockFileRenameCrossStorage($viewOperation, $storageOperation, $expectedLockTypeSourceDuring) { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([$storageOperation]) + ->getMock(); + $storage2 = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setMethods([$storageOperation, 'filemtime']) + ->getMock(); + + $storage2->expects($this->any()) + ->method('filemtime') + ->will($this->returnValue(123456789)); + + $sourcePath = 'original.txt'; + $targetPath = 'substorage/target.txt'; + + \OC\Files\Filesystem::mount($storage, array(), $this->user . '/'); + \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + $storage->mkdir('files'); + $view->file_put_contents($sourcePath, 'meh'); + + $storage->expects($this->never()) + ->method($storageOperation); + $storage2->expects($this->once()) + ->method($storageOperation) + ->will($this->returnCallback( + function () use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring) { + $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath); + $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath); + + return true; + } + )); + + $this->connectMockHooks($viewOperation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost); + $this->connectMockHooks($viewOperation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation'); + $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation'); + + $view->$viewOperation($sourcePath, $targetPath); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook'); + $this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook'); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook'); + + $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation'); + $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation'); + } + + /** + * Test locks when moving a mount point + */ + public function testLockMoveMountPoint() { + $this->loginAsUser('test'); + + list($mount) = $this->createTestMovableMountPoints([ + $this->user . '/files/substorage', + ]); + + $view = new \OC\Files\View('/' . $this->user . '/files/'); + $view->mkdir('subdir'); + + $sourcePath = 'substorage'; + $targetPath = 'subdir/substorage_moved'; + + $mount->expects($this->once()) + ->method('moveMount') + ->will($this->returnCallback( + function ($target) use ($mount, $view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring, &$lockTypeSharedRootDuring) { + $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath, true); + $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath, true); + + $lockTypeSharedRootDuring = $this->getFileLockType($view, $sourcePath, false); + + $mount->setMountPoint($target); + + return true; + } + )); + + $this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost, true); + $this->connectMockHooks('rename', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost, true); + // in pre-hook, mount point is still on $sourcePath + $this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSharedRootPre, $dummy, false); + // in post-hook, mount point is now on $targetPath + $this->connectMockHooks('rename', $view, $targetPath, $dummy, $lockTypeSharedRootPost, false); + + $this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked before operation'); + $this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked before operation'); + $this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked before operation'); + + $view->rename($sourcePath, $targetPath); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source path locked properly during pre-hook'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeSourceDuring, 'Source path locked properly during operation'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source path locked properly during post-hook'); + + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target path locked properly during pre-hook'); + $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target path locked properly during operation'); + $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target path locked properly during post-hook'); + + $this->assertNull($lockTypeSharedRootPre, 'Shared storage root not locked during pre-hook'); + $this->assertNull($lockTypeSharedRootDuring, 'Shared storage root not locked during move'); + $this->assertNull($lockTypeSharedRootPost, 'Shared storage root not locked during post-hook'); + + $this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked after operation'); + $this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked after operation'); + $this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked after operation'); + } + + /** + * Connect hook callbacks for hook type + * + * @param string $hookType hook type or null for none + * @param \OC\Files\View $view view to check the lock on + * @param string $path path for which to check the lock + * @param int $lockTypePre variable to receive lock type that was active in the pre-hook + * @param int $lockTypePost variable to receive lock type that was active in the post-hook + * @param bool $onMountPoint true to check the mount point instead of the + * mounted storage + */ + private function connectMockHooks($hookType, $view, $path, &$lockTypePre, &$lockTypePost, $onMountPoint = false) { + if ($hookType === null) { + return; + } + + $eventHandler = $this->getMockBuilder('\stdclass') + ->setMethods(['preCallback', 'postCallback']) + ->getMock(); + + $eventHandler->expects($this->any()) + ->method('preCallback') + ->will($this->returnCallback( + function () use ($view, $path, $onMountPoint, &$lockTypePre) { + $lockTypePre = $this->getFileLockType($view, $path, $onMountPoint); + } + )); + $eventHandler->expects($this->any()) + ->method('postCallback') + ->will($this->returnCallback( + function () use ($view, $path, $onMountPoint, &$lockTypePost) { + $lockTypePost = $this->getFileLockType($view, $path, $onMountPoint); + } + )); + + if ($hookType !== null) { + \OCP\Util::connectHook( + \OC\Files\Filesystem::CLASSNAME, + $hookType, + $eventHandler, + 'preCallback' + ); + \OCP\Util::connectHook( + \OC\Files\Filesystem::CLASSNAME, + 'post_' . $hookType, + $eventHandler, + 'postCallback' + ); + } + } + + /** + * Returns the file lock type + * + * @param \OC\Files\View $view view + * @param string $path path + * @param bool $onMountPoint true to check the mount point instead of the + * mounted storage + * + * @return int lock type or null if file was not locked + */ + private function getFileLockType(\OC\Files\View $view, $path, $onMountPoint = false) { + if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE, $onMountPoint)) { + return ILockingProvider::LOCK_EXCLUSIVE; + } else if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED, $onMountPoint)) { + return ILockingProvider::LOCK_SHARED; + } + return null; + } + + + public function testRemoveMoveableMountPoint() { + $mountPoint = '/' . $this->user . '/files/mount/'; + + // Mock the mount point + $mount = $this->getMockBuilder('\Test\TestMoveableMountPoint') + ->disableOriginalConstructor() + ->getMock(); + $mount->expects($this->once()) + ->method('getMountPoint') + ->willReturn($mountPoint); + $mount->expects($this->once()) + ->method('removeMount') + ->willReturn('foo'); + $mount->expects($this->any()) + ->method('getInternalPath') + ->willReturn(''); + + // Register mount + \OC\Files\Filesystem::getMountManager()->addMount($mount); + + // Listen for events + $eventHandler = $this->getMockBuilder('\stdclass') + ->setMethods(['umount', 'post_umount']) + ->getMock(); + $eventHandler->expects($this->once()) + ->method('umount') + ->with([\OC\Files\Filesystem::signal_param_path => '/mount']); + $eventHandler->expects($this->once()) + ->method('post_umount') + ->with([\OC\Files\Filesystem::signal_param_path => '/mount']); + \OCP\Util::connectHook( + \OC\Files\Filesystem::CLASSNAME, + 'umount', + $eventHandler, + 'umount' + ); + \OCP\Util::connectHook( + \OC\Files\Filesystem::CLASSNAME, + 'post_umount', + $eventHandler, + 'post_umount' + ); + + //Delete the mountpoint + $view = new \OC\Files\View('/' . $this->user . '/files'); + $this->assertEquals('foo', $view->rmdir('mount')); + } + + public function mimeFilterProvider() { + return [ + [null, ['test1.txt', 'test2.txt', 'test3.md', 'test4.png']], + ['text/plain', ['test1.txt', 'test2.txt']], + ['text/markdown', ['test3.md']], + ['text', ['test1.txt', 'test2.txt', 'test3.md']], + ]; + } + + /** + * @param string $filter + * @param string[] $expected + * @dataProvider mimeFilterProvider + */ + public function testGetDirectoryContentMimeFilter($filter, $expected) { + $storage1 = new Temporary(); + $root = $this->getUniqueID('/'); + \OC\Files\Filesystem::mount($storage1, array(), $root . '/'); + $view = new \OC\Files\View($root); + + $view->file_put_contents('test1.txt', 'asd'); + $view->file_put_contents('test2.txt', 'asd'); + $view->file_put_contents('test3.md', 'asd'); + $view->file_put_contents('test4.png', ''); + + $content = $view->getDirectoryContent('', $filter); + + $files = array_map(function(FileInfo $info) { + return $info->getName(); + }, $content); + sort($files); + + $this->assertEquals($expected, $files); + } + + public function testFilePutContentsClearsChecksum() { + $storage = new Temporary(array()); + $scanner = $storage->getScanner(); + $storage->file_put_contents('foo.txt', 'bar'); + \OC\Files\Filesystem::mount($storage, array(), '/test/'); + $scanner->scan(''); + + $view = new \OC\Files\View('/test/foo.txt'); + $view->putFileInfo('.', ['checksum' => '42']); + + $this->assertEquals('bar', $view->file_get_contents('')); + $fh = tmpfile(); + fwrite($fh, 'fooo'); + rewind($fh); + $view->file_put_contents('', $fh); + $this->assertEquals('fooo', $view->file_get_contents('')); + $data = $view->getFileInfo('.'); + $this->assertEquals('', $data->getChecksum()); + } +} |