diff options
Diffstat (limited to 'tests/lib/Files/Cache/CacheTest.php')
-rw-r--r-- | tests/lib/Files/Cache/CacheTest.php | 830 |
1 files changed, 830 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..383962b7224 --- /dev/null +++ b/tests/lib/Files/Cache/CacheTest.php @@ -0,0 +1,830 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Files\Cache; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\CacheEntry; +use OC\Files\Search\SearchComparison; +use OC\Files\Search\SearchQuery; +use OC\Files\Storage\Temporary; +use OC\User\User; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Search\ISearchComparison; +use OCP\IDBConnection; +use OCP\ITagManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Server; + +class LongId extends Temporary { + public function getId(): string { + return 'long:' . str_repeat('foo', 50) . parent::getId(); + } +} + +/** + * Class CacheTest + * + * @group DB + * + * @package Test\Files\Cache + */ +class CacheTest extends \Test\TestCase { + /** + * @var Temporary $storage ; + */ + protected $storage; + /** + * @var Temporary $storage2 ; + */ + protected $storage2; + + /** + * @var Cache $cache + */ + protected $cache; + /** + * @var Cache $cache2 + */ + protected $cache2; + + protected function setUp(): void { + parent::setUp(); + + $this->storage = new Temporary([]); + $this->storage2 = new Temporary([]); + $this->cache = new Cache($this->storage); + $this->cache2 = new Cache($this->storage2); + $this->cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + $this->cache2->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + } + + protected function tearDown(): void { + if ($this->cache) { + $this->cache->clear(); + } + + parent::tearDown(); + } + + public function testGetNumericId(): void { + $this->assertNotNull($this->cache->getNumericStorageId()); + } + + public function testSimple(): void { + $file1 = 'foo'; + $file2 = 'foo/bar'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder']; + $data2 = ['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, ['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 testCacheEntryGetters(): void { + $file1 = 'foo'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/file']; + + $id1 = $this->cache->put($file1, $data1); + $entry = $this->cache->get($file1); + + $this->assertEquals($entry->getId(), $id1); + $this->assertEquals($entry->getStorageId(), $this->cache->getNumericStorageId()); + $this->assertEquals($entry->getPath(), 'foo'); + $this->assertEquals($entry->getName(), 'foo'); + $this->assertEquals($entry->getMimeType(), 'foo/file'); + $this->assertEquals($entry->getMimePart(), 'foo'); + $this->assertEquals($entry->getSize(), 100); + $this->assertEquals($entry->getMTime(), 50); + $this->assertEquals($entry->getStorageMTime(), 50); + $this->assertEquals($entry->getEtag(), null); + $this->assertEquals($entry->getPermissions(), 0); + $this->assertEquals($entry->isEncrypted(), false); + $this->assertEquals($entry->getMetadataEtag(), null); + $this->assertEquals($entry->getCreationTime(), null); + $this->assertEquals($entry->getUploadTime(), null); + $this->assertEquals($entry->getUnencryptedSize(), 100); + } + + public function testPartial(): void { + $file1 = 'foo'; + + $this->cache->put($file1, ['size' => 10]); + $this->assertEquals(new CacheEntry(['size' => 10]), $this->cache->get($file1)); + + $this->cache->put($file1, ['mtime' => 15]); + $this->assertEquals(new CacheEntry(['size' => 10, 'mtime' => 15]), $this->cache->get($file1)); + + $this->cache->put($file1, ['size' => 12]); + $this->assertEquals(new CacheEntry(['size' => 12, 'mtime' => 15]), $this->cache->get($file1)); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('folderDataProvider')] + public function testFolder($folder): void { + if (strpos($folder, 'F09F9890')) { + // 4 byte UTF doesn't work on mysql + $params = Server::get(\OC\DB\Connection::class)->getParams(); + if (Server::get(IDBConnection::class)->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL && $params['charset'] !== 'utf8mb4') { + $this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8'); + } + } + $file2 = $folder . '/bar'; + $file3 = $folder . '/foo'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $fileData = []; + $fileData['bar'] = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file']; + $fileData['foo'] = ['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'] = ['size' => -1, 'mtime' => 25, 'mimetype' => 'foo/file']; + $this->cache->put($file4, $fileData['unkownSize']); + + $this->assertEquals(-1, $this->cache->calculateFolderSize($folder)); + + $fileData['unkownSize'] = ['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(): void { + $folderData = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $fileData = ['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 static function folderDataProvider(): array { + return [ + ['folder'], + // that was too easy, try something harder + ['☺, WHITE SMILING FACE, UTF-8 hex E298BA'], + // what about 4 byte utf-8 + ['😐, NEUTRAL_FACE, UTF-8 hex F09F9890'], + // now the crazy stuff + [', UNASSIGNED PRIVATE USE, UTF-8 hex EF9890'], + // and my favorite + ['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(): void { + $file1 = 'folder'; + $file2 = 'folder/bar'; + $file3 = 'folder/foo'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $fileData = []; + $fileData['bar'] = ['size' => 1000, 'encrypted' => 1, 'mtime' => 20, 'mimetype' => 'foo/file']; + $fileData['foo'] = ['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'] = ['size' => -1, 'mtime' => 25, 'mimetype' => 'foo/file']; + $this->cache->put($file4, $fileData['unkownSize']); + + $this->assertEquals(-1, $this->cache->calculateFolderSize($file1)); + + $fileData['unkownSize'] = ['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(): void { + $dir1 = 'knownsize'; + $dir2 = 'unknownsize'; + $fileData = []; + $fileData[''] = ['size' => -1, 'mtime' => 20, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $fileData[$dir1] = ['size' => 1000, 'mtime' => 20, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $fileData[$dir2] = ['size' => -1, 'mtime' => 25, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + + $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)); + } + + public function testStatus(): void { + $this->assertEquals(Cache::NOT_FOUND, $this->cache->getStatus('foo')); + $this->cache->put('foo', ['size' => -1]); + $this->assertEquals(Cache::PARTIAL, $this->cache->getStatus('foo')); + $this->cache->put('foo', ['size' => -1, 'mtime' => 20, 'mimetype' => 'foo/file']); + $this->assertEquals(Cache::SHALLOW, $this->cache->getStatus('foo')); + $this->cache->put('foo', ['size' => 10]); + $this->assertEquals(Cache::COMPLETE, $this->cache->getStatus('foo')); + } + + public static function putWithAllKindOfQuotesData(): array { + return [ + ['`backtick`'], + ['´forward´'], + ['\'single\''], + ]; + } + + /** + * @param $fileName + */ + #[\PHPUnit\Framework\Attributes\DataProvider('putWithAllKindOfQuotesData')] + public function testPutWithAllKindOfQuotes($fileName): void { + $this->assertEquals(Cache::NOT_FOUND, $this->cache->get($fileName)); + $this->cache->put($fileName, ['size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file', 'etag' => $fileName]); + + $cacheEntry = $this->cache->get($fileName); + $this->assertEquals($fileName, $cacheEntry['etag']); + $this->assertEquals($fileName, $cacheEntry['path']); + } + + public function testSearch(): void { + $file1 = 'folder'; + $file2 = 'folder/foobar'; + $file3 = 'folder/foo'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder']; + $fileData = []; + $fileData['foobar'] = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file']; + $fileData['foo'] = ['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%'))); + + // 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'))); + } + + public function testSearchQueryByTag(): void { + $userId = static::getUniqueID('user'); + Server::get(IUserManager::class)->createUser($userId, $userId); + static::loginAsUser($userId); + $user = new User($userId, null, Server::get(IEventDispatcher::class)); + + $file1 = 'folder'; + $file2 = 'folder/foobar'; + $file3 = 'folder/foo'; + $file4 = 'folder/foo2'; + $file5 = 'folder/foo3'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder']; + $fileData = []; + $fileData['foobar'] = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file']; + $fileData['foo'] = ['size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file']; + $fileData['foo2'] = ['size' => 25, 'mtime' => 28, 'mimetype' => 'foo/file']; + $fileData['foo3'] = ['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 = Server::get(ITagManager::class)->load('files', [], false, $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')); + + $results = $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', 'tag2'), + 0, 0, [], $user + )); + $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'); + + static::logout(); + $user = Server::get(IUserManager::class)->get($userId); + if ($user !== null) { + try { + $user->delete(); + } catch (\Exception $e) { + } + } + } + + public function testSearchByQuery(): void { + $file1 = 'folder'; + $file2 = 'folder/foobar'; + $file3 = 'folder/foo'; + $data1 = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder']; + $fileData = []; + $fileData['foobar'] = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file']; + $fileData['foo'] = ['size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file']; + + $this->cache->put($file1, $data1); + $this->cache->put($file2, $fileData['foobar']); + $this->cache->put($file3, $fileData['foo']); + /** @var IUser $user */ + $user = $this->createMock(IUser::class); + + $this->assertCount(1, $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'name', 'foo'), 10, 0, [], $user))); + $this->assertCount(2, $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', 'foo%'), 10, 0, [], $user))); + $this->assertCount(2, $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', 'foo/file'), 10, 0, [], $user))); + $this->assertCount(3, $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'foo/%'), 10, 0, [], $user))); + $this->assertCount(1, $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'size', 100), 10, 0, [], $user))); + $this->assertCount(2, $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'size', 100), 10, 0, [], $user))); + } + + public static function movePathProvider(): array { + return [ + ['folder/foo', 'folder/foobar', ['1', '2']], + ['folder/foo', 'foo', ['1', '2']], + ['files/Индустрия_Инженерные системы ЦОД', 'files/Индустрия_Инженерные системы ЦОД1', ['1', '2']], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('movePathProvider')] + public function testMove($sourceFolder, $targetFolder, $children): void { + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar']; + $folderData = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + + // create folders + foreach ([$sourceFolder, $targetFolder] as $current) { + while (strpos($current, '/') > 0) { + $current = dirname($current); + $this->cache->put($current, $folderData); + $this->cache2->put($current, $folderData); + } + } + + $this->cache->put($sourceFolder, $folderData); + $this->cache2->put($sourceFolder, $folderData); + foreach ($children as $child) { + $this->cache->put($sourceFolder . '/' . $child, $data); + $this->cache2->put($sourceFolder . '/' . $child, $data); + } + + $this->cache->move($sourceFolder, $targetFolder); + + + $this->assertFalse($this->cache->inCache($sourceFolder)); + $this->assertTrue($this->cache2->inCache($sourceFolder)); + $this->assertTrue($this->cache->inCache($targetFolder)); + $this->assertFalse($this->cache2->inCache($targetFolder)); + foreach ($children as $child) { + $this->assertFalse($this->cache->inCache($sourceFolder . '/' . $child)); + $this->assertTrue($this->cache2->inCache($sourceFolder . '/' . $child)); + $this->assertTrue($this->cache->inCache($targetFolder . '/' . $child)); + $this->assertFalse($this->cache2->inCache($targetFolder . '/' . $child)); + } + } + + public function testMoveFromCache(): void { + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar']; + $folderData = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + + $this->cache2->put('folder', $folderData); + $this->cache2->put('folder/sub', $data); + + + $this->cache->moveFromCache($this->cache2, 'folder', 'targetfolder'); + + $this->assertFalse($this->cache2->inCache('folder')); + $this->assertFalse($this->cache2->inCache('folder/sub')); + + $this->assertTrue($this->cache->inCache('targetfolder')); + $this->assertTrue($this->cache->inCache('targetfolder/sub')); + } + + public function testGetIncomplete(): void { + $file1 = 'folder1'; + $file2 = 'folder2'; + $file3 = 'folder3'; + $file4 = 'folder4'; + $data = ['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()); + } + + public function testNonExisting(): void { + $this->assertFalse($this->cache->get('foo.txt')); + $this->assertFalse($this->cache->get(-1)); + $this->assertEquals([], $this->cache->getFolderContents('foo')); + } + + public function testGetById(): void { + $storageId = $this->storage->getId(); + $data = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file']; + $id = $this->cache->put('foo', $data); + + if (strlen($storageId) > 64) { + $storageId = md5($storageId); + } + $this->assertEquals([$storageId, 'foo'], Cache::getById($id)); + } + + public function testStorageMTime(): void { + $data = ['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', ['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', ['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']); + } + + public function testLongId(): void { + $storage = new LongId([]); + $cache = $storage->getCache(); + $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + $storageId = $storage->getId(); + $data = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file']; + $id = $cache->put('foo', $data); + $this->assertEquals([md5($storageId), 'foo'], Cache::getById($id)); + } + + /** + * this test show the bug resulting if we have no normalizer installed + */ + public function testWithoutNormalizer(): void { + // 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 Cache|\PHPUnit\Framework\MockObject\MockObject $cacheMock + */ + $cacheMock = $this->getMockBuilder(Cache::class) + ->onlyMethods(['normalize']) + ->setConstructorArgs([$this->storage]) + ->getMock(); + + $cacheMock->expects($this->any()) + ->method('normalize') + ->willReturnArgument(0); + + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + + // 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(): void { + 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 = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + + // 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'))); + } + + public static function bogusPathNamesProvider(): array { + return [ + ['/bogus.txt', 'bogus.txt'], + ['//bogus.txt', 'bogus.txt'], + ['bogus/', 'bogus'], + ['bogus//', 'bogus'], + ]; + } + + /** + * Test bogus paths with leading or doubled slashes + */ + #[\PHPUnit\Framework\Attributes\DataProvider('bogusPathNamesProvider')] + public function testBogusPaths($bogusPath, $fixedBogusPath): void { + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $parentId = $this->cache->getId(''); + + $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(): void { + $data1 = ['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 = ['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 static function escapingProvider(): array { + return [ + ['foo'], + ['o%'], + ['oth_r'], + ]; + } + + /** + * @param string $name + */ + #[\PHPUnit\Framework\Attributes\DataProvider('escapingProvider')] + public function testEscaping($name): void { + $data = ['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 = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + $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)); + } + } + + public function testExtended(): void { + $folderData = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; + + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'creation_time' => 20]; + $id1 = $this->cache->put('foo1', $data); + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'upload_time' => 30]; + $this->cache->put('foo2', $data); + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'metadata_etag' => 'foo']; + $this->cache->put('foo3', $data); + $data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain']; + $id4 = $this->cache->put('foo4', $data); + + $entry = $this->cache->get($id1); + $this->assertEquals(20, $entry->getCreationTime()); + $this->assertEquals(0, $entry->getUploadTime()); + $this->assertEquals(null, $entry->getMetadataEtag()); + + $entries = $this->cache->getFolderContents(''); + $this->assertCount(4, $entries); + + $this->assertEquals('foo1', $entries[0]->getName()); + $this->assertEquals('foo2', $entries[1]->getName()); + $this->assertEquals('foo3', $entries[2]->getName()); + $this->assertEquals('foo4', $entries[3]->getName()); + + $this->assertEquals(20, $entries[0]->getCreationTime()); + $this->assertEquals(0, $entries[0]->getUploadTime()); + $this->assertEquals(null, $entries[0]->getMetadataEtag()); + + $this->assertEquals(0, $entries[1]->getCreationTime()); + $this->assertEquals(30, $entries[1]->getUploadTime()); + $this->assertEquals(null, $entries[1]->getMetadataEtag()); + + $this->assertEquals(0, $entries[2]->getCreationTime()); + $this->assertEquals(0, $entries[2]->getUploadTime()); + $this->assertEquals('foo', $entries[2]->getMetadataEtag()); + + $this->assertEquals(0, $entries[3]->getCreationTime()); + $this->assertEquals(0, $entries[3]->getUploadTime()); + $this->assertEquals(null, $entries[3]->getMetadataEtag()); + + $this->cache->update($id1, ['upload_time' => 25]); + + $entry = $this->cache->get($id1); + $this->assertEquals(20, $entry->getCreationTime()); + $this->assertEquals(25, $entry->getUploadTime()); + $this->assertEquals(null, $entry->getMetadataEtag()); + + $this->cache->put('sub', $folderData); + + $this->cache->move('foo1', 'sub/foo1'); + + $entries = $this->cache->getFolderContents('sub'); + $this->assertCount(1, $entries); + + $this->assertEquals(20, $entries[0]->getCreationTime()); + $this->assertEquals(25, $entries[0]->getUploadTime()); + $this->assertEquals(null, $entries[0]->getMetadataEtag()); + + $this->cache->update($id4, ['upload_time' => 25]); + + $entry = $this->cache->get($id4); + $this->assertEquals(0, $entry->getCreationTime()); + $this->assertEquals(25, $entry->getUploadTime()); + $this->assertEquals(null, $entry->getMetadataEtag()); + + $this->cache->remove('sub'); + } +} |