|
9 | 9 | namespace Test\Files\Cache; |
10 | 10 |
|
11 | 11 | use OC\Files\Cache\Cache; |
| 12 | +use OC\Files\Cache\CacheDependencies; |
12 | 13 | use OC\Files\Cache\CacheEntry; |
| 14 | +use OC\Files\Cache\QuerySearchHelper; |
13 | 15 | use OC\Files\Cache\Wrapper\CacheJail; |
14 | 16 | use OC\Files\Search\SearchComparison; |
15 | 17 | use OC\Files\Search\SearchQuery; |
16 | 18 | use OC\Files\Storage\Temporary; |
| 19 | +use OC\SystemConfig; |
| 20 | +use OC\User\DisplayNameCache; |
17 | 21 | use OC\User\User; |
| 22 | +use OCP\DB\QueryBuilder\IQueryBuilder; |
18 | 23 | use OCP\EventDispatcher\IEventDispatcher; |
| 24 | +use OCP\Files\Cache\CacheEntriesRemovedEvent; |
19 | 25 | use OCP\Files\Cache\ICacheEntry; |
| 26 | +use OCP\Files\IMimeTypeLoader; |
20 | 27 | use OCP\Files\Search\ISearchComparison; |
| 28 | +use OCP\Files\Storage\IStorage; |
| 29 | +use OCP\FilesMetadata\IFilesMetadataManager; |
21 | 30 | use OCP\IDBConnection; |
22 | 31 | use OCP\ITagManager; |
23 | 32 | use OCP\IUser; |
24 | 33 | use OCP\IUserManager; |
25 | 34 | use OCP\Server; |
| 35 | +use Psr\Log\LoggerInterface; |
26 | 36 |
|
27 | 37 | class LongId extends Temporary { |
28 | 38 | #[\Override] |
@@ -245,6 +255,149 @@ public function testRemoveRecursive(): void { |
245 | 255 | } |
246 | 256 | } |
247 | 257 |
|
| 258 | + /** |
| 259 | + * @return array{0: Cache, 1: \Closure(): list<CacheEntriesRemovedEvent>} |
| 260 | + */ |
| 261 | + private function cacheWithRecordedEvents(IStorage $storage): array { |
| 262 | + $events = []; |
| 263 | + $dispatcher = $this->createMock(IEventDispatcher::class); |
| 264 | + $dispatcher->method('dispatchTyped') |
| 265 | + ->willReturnCallback(function (object $event) use (&$events): void { |
| 266 | + if ($event instanceof CacheEntriesRemovedEvent) { |
| 267 | + $events[] = $event; |
| 268 | + } |
| 269 | + }); |
| 270 | + |
| 271 | + $dependencies = new CacheDependencies( |
| 272 | + Server::get(IMimeTypeLoader::class), |
| 273 | + Server::get(IDBConnection::class), |
| 274 | + $dispatcher, |
| 275 | + Server::get(QuerySearchHelper::class), |
| 276 | + Server::get(SystemConfig::class), |
| 277 | + Server::get(LoggerInterface::class), |
| 278 | + Server::get(IFilesMetadataManager::class), |
| 279 | + Server::get(DisplayNameCache::class), |
| 280 | + ); |
| 281 | + |
| 282 | + return [new Cache($storage, $dependencies), function () use (&$events): array { |
| 283 | + return $events; |
| 284 | + }]; |
| 285 | + } |
| 286 | + |
| 287 | + /** |
| 288 | + * @param list<CacheEntriesRemovedEvent> $events |
| 289 | + * @return list<int> |
| 290 | + */ |
| 291 | + private function announcedFileIds(array $events): array { |
| 292 | + $fileIds = []; |
| 293 | + foreach ($events as $event) { |
| 294 | + foreach ($event->getCacheEntryRemovedEvents() as $removed) { |
| 295 | + $fileIds[] = $removed->getFileId(); |
| 296 | + } |
| 297 | + } |
| 298 | + return $fileIds; |
| 299 | + } |
| 300 | + |
| 301 | + public function testRemoveSingleFileAnnouncesBatchEvent(): void { |
| 302 | + $storage = new Temporary([]); |
| 303 | + [$cache, $recorded] = $this->cacheWithRecordedEvents($storage); |
| 304 | + $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); |
| 305 | + $fileId = $cache->put('foo.txt', ['size' => 1, 'mtime' => 20, 'mimetype' => 'text/plain']); |
| 306 | + |
| 307 | + $cache->remove('foo.txt'); |
| 308 | + |
| 309 | + $this->assertEquals([$fileId], $this->announcedFileIds($recorded())); |
| 310 | + } |
| 311 | + |
| 312 | + public function testRemoveRecursiveAnnouncesRealFileIds(): void { |
| 313 | + $storage = new Temporary([]); |
| 314 | + [$cache, $recorded] = $this->cacheWithRecordedEvents($storage); |
| 315 | + $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); |
| 316 | + $folderData = ['size' => 100, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]; |
| 317 | + $fileData = ['size' => 1000, 'mtime' => 20, 'mimetype' => 'text/plain']; |
| 318 | + |
| 319 | + $expected = [$cache->put('folder', $folderData)]; |
| 320 | + $expected[] = $cache->put('folder/sub', $folderData); |
| 321 | + $expected[] = $cache->put('folder/foo.txt', $fileData); |
| 322 | + $expected[] = $cache->put('folder/sub/bar.txt', $fileData); |
| 323 | + |
| 324 | + $cache->remove('folder'); |
| 325 | + |
| 326 | + $announced = $this->announcedFileIds($recorded()); |
| 327 | + sort($expected); |
| 328 | + sort($announced); |
| 329 | + $this->assertEquals($expected, $announced); |
| 330 | + } |
| 331 | + |
| 332 | + public function testRemoveRecursiveAnnouncesEveryChildExactlyOnce(): void { |
| 333 | + $storage = new Temporary([]); |
| 334 | + [$cache, $recorded] = $this->cacheWithRecordedEvents($storage); |
| 335 | + $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); |
| 336 | + $fileData = ['size' => 1, 'mtime' => 20, 'mimetype' => 'text/plain']; |
| 337 | + |
| 338 | + $expected = [$cache->put('folder', ['size' => 0, 'mtime' => 50, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE])]; |
| 339 | + for ($i = 0; $i < IQueryBuilder::MAX_IN_PARAMETERS + 1; $i++) { |
| 340 | + $expected[] = $cache->insert("folder/child$i.txt", $fileData); |
| 341 | + } |
| 342 | + |
| 343 | + $cache->remove('folder'); |
| 344 | + |
| 345 | + $announced = $this->announcedFileIds($recorded()); |
| 346 | + $this->assertCount(count($expected), $announced, 'every removed entry is announced exactly once'); |
| 347 | + sort($expected); |
| 348 | + sort($announced); |
| 349 | + $this->assertEquals($expected, $announced); |
| 350 | + } |
| 351 | + |
| 352 | + public function testClearEmptiesTheStorageEvenIfAListenerThrows(): void { |
| 353 | + $storage = new Temporary([]); |
| 354 | + $dispatcher = $this->createMock(IEventDispatcher::class); |
| 355 | + $dispatcher->method('dispatchTyped') |
| 356 | + ->willReturnCallback(function (object $event): void { |
| 357 | + if ($event instanceof CacheEntriesRemovedEvent) { |
| 358 | + throw new \RuntimeException('listener blew up'); |
| 359 | + } |
| 360 | + }); |
| 361 | + $dependencies = new CacheDependencies( |
| 362 | + Server::get(IMimeTypeLoader::class), |
| 363 | + Server::get(IDBConnection::class), |
| 364 | + $dispatcher, |
| 365 | + Server::get(QuerySearchHelper::class), |
| 366 | + Server::get(SystemConfig::class), |
| 367 | + Server::get(LoggerInterface::class), |
| 368 | + Server::get(IFilesMetadataManager::class), |
| 369 | + Server::get(DisplayNameCache::class), |
| 370 | + ); |
| 371 | + $cache = new Cache($storage, $dependencies); |
| 372 | + $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); |
| 373 | + $cache->put('foo.txt', ['size' => 1, 'mtime' => 20, 'mimetype' => 'text/plain']); |
| 374 | + |
| 375 | + try { |
| 376 | + $cache->clear(); |
| 377 | + $this->fail('the listener exception should surface'); |
| 378 | + } catch (\RuntimeException $e) { |
| 379 | + $this->assertEquals('listener blew up', $e->getMessage()); |
| 380 | + } |
| 381 | + |
| 382 | + $this->assertFalse($cache->inCache('foo.txt')); |
| 383 | + $this->assertFalse($cache->inCache('')); |
| 384 | + } |
| 385 | + |
| 386 | + public function testClearAnnouncesRemovedEntries(): void { |
| 387 | + $storage = new Temporary([]); |
| 388 | + [$cache, $recorded] = $this->cacheWithRecordedEvents($storage); |
| 389 | + $expected = [$cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE])]; |
| 390 | + $expected[] = $cache->put('foo.txt', ['size' => 1, 'mtime' => 20, 'mimetype' => 'text/plain']); |
| 391 | + $expected[] = $cache->put('bar.txt', ['size' => 1, 'mtime' => 20, 'mimetype' => 'text/plain']); |
| 392 | + |
| 393 | + $cache->clear(); |
| 394 | + |
| 395 | + $announced = $this->announcedFileIds($recorded()); |
| 396 | + sort($expected); |
| 397 | + sort($announced); |
| 398 | + $this->assertEquals($expected, $announced); |
| 399 | + } |
| 400 | + |
248 | 401 | public static function folderDataProvider(): array { |
249 | 402 | return [ |
250 | 403 | ['folder'], |
|
0 commit comments