aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Files/Cache/CacheTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Files/Cache/CacheTest.php')
-rw-r--r--tests/lib/Files/Cache/CacheTest.php830
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');
+ }
+}