diff options
Diffstat (limited to 'tests/lib/Preview')
-rw-r--r-- | tests/lib/Preview/BackgroundCleanupJobTest.php | 228 | ||||
-rw-r--r-- | tests/lib/Preview/BitmapTest.php | 30 | ||||
-rw-r--r-- | tests/lib/Preview/GeneratorTest.php | 464 | ||||
-rw-r--r-- | tests/lib/Preview/HEICTest.php | 33 | ||||
-rw-r--r-- | tests/lib/Preview/ImageTest.php | 30 | ||||
-rw-r--r-- | tests/lib/Preview/MP3Test.php | 30 | ||||
-rw-r--r-- | tests/lib/Preview/MovieBrokenStuckFfmpegTest.php | 20 | ||||
-rw-r--r-- | tests/lib/Preview/MovieTest.php | 43 | ||||
-rw-r--r-- | tests/lib/Preview/OfficeTest.php | 40 | ||||
-rw-r--r-- | tests/lib/Preview/Provider.php | 181 | ||||
-rw-r--r-- | tests/lib/Preview/SVGTest.php | 65 | ||||
-rw-r--r-- | tests/lib/Preview/TXTTest.php | 31 |
12 files changed, 1195 insertions, 0 deletions
diff --git a/tests/lib/Preview/BackgroundCleanupJobTest.php b/tests/lib/Preview/BackgroundCleanupJobTest.php new file mode 100644 index 00000000000..ab904f2b499 --- /dev/null +++ b/tests/lib/Preview/BackgroundCleanupJobTest.php @@ -0,0 +1,228 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Files\Storage\Temporary; +use OC\Preview\BackgroundCleanupJob; +use OC\Preview\Storage\Root; +use OC\PreviewManager; +use OC\SystemConfig; +use OCP\App\IAppManager; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\File; +use OCP\Files\IMimeTypeLoader; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IDBConnection; +use OCP\IPreview; +use OCP\Server; +use Test\Traits\MountProviderTrait; +use Test\Traits\UserTrait; + +/** + * Class BackgroundCleanupJobTest + * + * @group DB + * + * @package Test\Preview + */ +class BackgroundCleanupJobTest extends \Test\TestCase { + use MountProviderTrait; + use UserTrait; + private string $userId; + private bool $trashEnabled; + private IDBConnection $connection; + private PreviewManager $previewManager; + private IRootFolder $rootFolder; + private IMimeTypeLoader $mimeTypeLoader; + private ITimeFactory $timeFactory; + + protected function setUp(): void { + parent::setUp(); + + $this->userId = $this->getUniqueID(); + $user = $this->createUser($this->userId, $this->userId); + + $storage = new Temporary([]); + $this->registerMount($this->userId, $storage, ''); + + $this->loginAsUser($this->userId); + $this->logout(); + $this->loginAsUser($this->userId); + + $appManager = Server::get(IAppManager::class); + $this->trashEnabled = $appManager->isEnabledForUser('files_trashbin', $user); + $appManager->disableApp('files_trashbin'); + + $this->connection = Server::get(IDBConnection::class); + $this->previewManager = Server::get(IPreview::class); + $this->rootFolder = Server::get(IRootFolder::class); + $this->mimeTypeLoader = Server::get(IMimeTypeLoader::class); + $this->timeFactory = Server::get(ITimeFactory::class); + } + + protected function tearDown(): void { + if ($this->trashEnabled) { + $appManager = Server::get(IAppManager::class); + $appManager->enableApp('files_trashbin'); + } + + $this->logout(); + + parent::tearDown(); + } + + private function getRoot(): Root { + return new Root( + Server::get(IRootFolder::class), + Server::get(SystemConfig::class) + ); + } + + private function setup11Previews(): array { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + + $files = []; + for ($i = 0; $i < 11; $i++) { + $file = $userFolder->newFile($i . '.txt'); + $file->putContent('hello world!'); + $this->previewManager->getPreview($file); + $files[] = $file; + } + + return $files; + } + + private function countPreviews(Root $previewRoot, array $fileIds): int { + $i = 0; + + foreach ($fileIds as $fileId) { + try { + $previewRoot->getFolder((string)$fileId); + } catch (NotFoundException $e) { + continue; + } + + $i++; + } + + return $i; + } + + public function testCleanupSystemCron(): void { + $files = $this->setup11Previews(); + $fileIds = array_map(function (File $f) { + return $f->getId(); + }, $files); + + $root = $this->getRoot(); + + $this->assertSame(11, $this->countPreviews($root, $fileIds)); + $job = new BackgroundCleanupJob($this->timeFactory, $this->connection, $root, $this->mimeTypeLoader, true); + $job->run([]); + + foreach ($files as $file) { + $file->delete(); + } + + $root = $this->getRoot(); + $this->assertSame(11, $this->countPreviews($root, $fileIds)); + $job->run([]); + + $root = $this->getRoot(); + $this->assertSame(0, $this->countPreviews($root, $fileIds)); + } + + public function testCleanupAjax(): void { + if ($this->connection->getShardDefinition('filecache')) { + $this->markTestSkipped('ajax cron is not supported for sharded setups'); + return; + } + $files = $this->setup11Previews(); + $fileIds = array_map(function (File $f) { + return $f->getId(); + }, $files); + + $root = $this->getRoot(); + + $this->assertSame(11, $this->countPreviews($root, $fileIds)); + $job = new BackgroundCleanupJob($this->timeFactory, $this->connection, $root, $this->mimeTypeLoader, false); + $job->run([]); + + foreach ($files as $file) { + $file->delete(); + } + + $root = $this->getRoot(); + $this->assertSame(11, $this->countPreviews($root, $fileIds)); + $job->run([]); + + $root = $this->getRoot(); + $this->assertSame(1, $this->countPreviews($root, $fileIds)); + $job->run([]); + + $root = $this->getRoot(); + $this->assertSame(0, $this->countPreviews($root, $fileIds)); + } + + public function testOldPreviews(): void { + if ($this->connection->getShardDefinition('filecache')) { + $this->markTestSkipped('old previews are not supported for sharded setups'); + return; + } + $appdata = Server::get(IAppDataFactory::class)->get('preview'); + + $f1 = $appdata->newFolder('123456781'); + $f1->newFile('foo.jpg', 'foo'); + $f2 = $appdata->newFolder('123456782'); + $f2->newFile('foo.jpg', 'foo'); + $f2 = $appdata->newFolder((string)PHP_INT_MAX - 1); + $f2->newFile('foo.jpg', 'foo'); + + /* + * Cleanup of OldPreviewLocations should only remove numeric folders on AppData level, + * therefore these files should stay untouched. + */ + $appdata->getFolder('/')->newFile('not-a-directory', 'foo'); + $appdata->getFolder('/')->newFile('133742', 'bar'); + + $appdata = Server::get(IAppDataFactory::class)->get('preview'); + // AppData::getDirectoryListing filters all non-folders + $this->assertSame(3, count($appdata->getDirectoryListing())); + try { + $appdata->getFolder('/')->getFile('not-a-directory'); + } catch (NotFoundException) { + $this->fail('Could not find file \'not-a-directory\''); + } + try { + $appdata->getFolder('/')->getFile('133742'); + } catch (NotFoundException) { + $this->fail('Could not find file \'133742\''); + } + + $job = new BackgroundCleanupJob($this->timeFactory, $this->connection, $this->getRoot(), $this->mimeTypeLoader, true); + $job->run([]); + + $appdata = Server::get(IAppDataFactory::class)->get('preview'); + + // Check if the files created above are still present + // Remember: AppData::getDirectoryListing filters all non-folders + $this->assertSame(0, count($appdata->getDirectoryListing())); + try { + $appdata->getFolder('/')->getFile('not-a-directory'); + } catch (NotFoundException) { + $this->fail('Could not find file \'not-a-directory\''); + } + try { + $appdata->getFolder('/')->getFile('133742'); + } catch (NotFoundException) { + $this->fail('Could not find file \'133742\''); + } + } +} diff --git a/tests/lib/Preview/BitmapTest.php b/tests/lib/Preview/BitmapTest.php new file mode 100644 index 00000000000..36e768010a9 --- /dev/null +++ b/tests/lib/Preview/BitmapTest.php @@ -0,0 +1,30 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\Postscript; + +/** + * Class BitmapTest + * + * @group DB + * + * @package Test\Preview + */ +class BitmapTest extends Provider { + protected function setUp(): void { + parent::setUp(); + + $fileName = 'testimage.eps'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + $this->width = 2400; + $this->height = 1707; + $this->provider = new Postscript; + } +} diff --git a/tests/lib/Preview/GeneratorTest.php b/tests/lib/Preview/GeneratorTest.php new file mode 100644 index 00000000000..edf5418da6e --- /dev/null +++ b/tests/lib/Preview/GeneratorTest.php @@ -0,0 +1,464 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Preview; + +use OC\Preview\Generator; +use OC\Preview\GeneratorHelper; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\File; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IConfig; +use OCP\IImage; +use OCP\IPreview; +use OCP\Preview\BeforePreviewFetchedEvent; +use OCP\Preview\IProviderV2; +use Psr\Log\LoggerInterface; + +class GeneratorTest extends \Test\TestCase { + /** @var IConfig&\PHPUnit\Framework\MockObject\MockObject */ + private $config; + + /** @var IPreview&\PHPUnit\Framework\MockObject\MockObject */ + private $previewManager; + + /** @var IAppData&\PHPUnit\Framework\MockObject\MockObject */ + private $appData; + + /** @var GeneratorHelper&\PHPUnit\Framework\MockObject\MockObject */ + private $helper; + + /** @var IEventDispatcher&\PHPUnit\Framework\MockObject\MockObject */ + private $eventDispatcher; + + /** @var Generator */ + private $generator; + + private LoggerInterface&\PHPUnit\Framework\MockObject\MockObject $logger; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->previewManager = $this->createMock(IPreview::class); + $this->appData = $this->createMock(IAppData::class); + $this->helper = $this->createMock(GeneratorHelper::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->generator = new Generator( + $this->config, + $this->previewManager, + $this->appData, + $this->helper, + $this->eventDispatcher, + $this->logger, + ); + } + + public function testGetCachedPreview(): void { + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(true); + $file->method('getMimeType') + ->willReturn('myMimeType'); + $file->method('getId') + ->willReturn(42); + + $this->previewManager->method('isMimeSupported') + ->with($this->equalTo('myMimeType')) + ->willReturn(true); + + $previewFolder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with($this->equalTo(42)) + ->willReturn($previewFolder); + + $maxPreview = $this->createMock(ISimpleFile::class); + $maxPreview->method('getName') + ->willReturn('1000-1000-max.png'); + $maxPreview->method('getSize')->willReturn(1000); + $maxPreview->method('getMimeType') + ->willReturn('image/png'); + + $previewFile = $this->createMock(ISimpleFile::class); + $previewFile->method('getSize')->willReturn(1000); + $previewFile->method('getName')->willReturn('256-256.png'); + + $previewFolder->method('getDirectoryListing') + ->willReturn([$maxPreview, $previewFile]); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null)); + + $result = $this->generator->getPreview($file, 100, 100); + $this->assertSame($previewFile, $result); + } + + public function testGetNewPreview(): void { + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(true); + $file->method('getMimeType') + ->willReturn('myMimeType'); + $file->method('getId') + ->willReturn(42); + + $this->previewManager->method('isMimeSupported') + ->with($this->equalTo('myMimeType')) + ->willReturn(true); + + $previewFolder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with($this->equalTo(42)) + ->willThrowException(new NotFoundException()); + + $this->appData->method('newFolder') + ->with($this->equalTo(42)) + ->willReturn($previewFolder); + + $this->config->method('getSystemValue') + ->willReturnCallback(function ($key, $default) { + return $default; + }); + + $this->config->method('getSystemValueInt') + ->willReturnCallback(function ($key, $default) { + return $default; + }); + + $invalidProvider = $this->createMock(IProviderV2::class); + $invalidProvider->method('isAvailable') + ->willReturn(true); + $unavailableProvider = $this->createMock(IProviderV2::class); + $unavailableProvider->method('isAvailable') + ->willReturn(false); + $validProvider = $this->createMock(IProviderV2::class); + $validProvider->method('isAvailable') + ->with($file) + ->willReturn(true); + + $this->previewManager->method('getProviders') + ->willReturn([ + '/image\/png/' => ['wrongProvider'], + '/myMimeType/' => ['brokenProvider', 'invalidProvider', 'unavailableProvider', 'validProvider'], + ]); + + $this->helper->method('getProvider') + ->willReturnCallback(function ($provider) use ($invalidProvider, $validProvider, $unavailableProvider) { + if ($provider === 'wrongProvider') { + $this->fail('Wrongprovider should not be constructed!'); + } elseif ($provider === 'brokenProvider') { + return false; + } elseif ($provider === 'invalidProvider') { + return $invalidProvider; + } elseif ($provider === 'validProvider') { + return $validProvider; + } elseif ($provider === 'unavailableProvider') { + return $unavailableProvider; + } + $this->fail('Unexpected provider requested'); + }); + + $image = $this->createMock(IImage::class); + $image->method('width')->willReturn(2048); + $image->method('height')->willReturn(2048); + $image->method('valid')->willReturn(true); + $image->method('dataMimeType')->willReturn('image/png'); + + $this->helper->method('getThumbnail') + ->willReturnCallback(function ($provider, $file, $x, $y) use ($invalidProvider, $validProvider, $image) { + if ($provider === $validProvider) { + return $image; + } else { + return false; + } + }); + + $image->method('data') + ->willReturn('my data'); + + $maxPreview = $this->createMock(ISimpleFile::class); + $maxPreview->method('getName')->willReturn('2048-2048-max.png'); + $maxPreview->method('getMimeType')->willReturn('image/png'); + $maxPreview->method('getSize')->willReturn(1000); + + $previewFile = $this->createMock(ISimpleFile::class); + $previewFile->method('getSize')->willReturn(1000); + + $previewFolder->method('getDirectoryListing') + ->willReturn([]); + $previewFolder->method('newFile') + ->willReturnMap([ + ['2048-2048-max.png', 'my data', $maxPreview], + ['256-256.png', 'my resized data', $previewFile], + ]); + + $previewFolder->method('getFile') + ->with($this->equalTo('256-256.png')) + ->willThrowException(new NotFoundException()); + + $image = $this->getMockImage(2048, 2048, 'my resized data'); + $this->helper->method('getImage') + ->with($this->equalTo($maxPreview)) + ->willReturn($image); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null)); + + $result = $this->generator->getPreview($file, 100, 100); + $this->assertSame($previewFile, $result); + } + + public function testInvalidMimeType(): void { + $this->expectException(NotFoundException::class); + + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(true); + $file->method('getId') + ->willReturn(42); + + $this->previewManager->method('isMimeSupported') + ->with('invalidType') + ->willReturn(false); + + $previewFolder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with($this->equalTo(42)) + ->willReturn($previewFolder); + + $maxPreview = $this->createMock(ISimpleFile::class); + $maxPreview->method('getName') + ->willReturn('2048-2048-max.png'); + $maxPreview->method('getMimeType') + ->willReturn('image/png'); + + $previewFolder->method('getDirectoryListing') + ->willReturn([$maxPreview]); + + $previewFolder->method('getFile') + ->with($this->equalTo('1024-512-crop.png')) + ->willThrowException(new NotFoundException()); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType')); + + $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'); + } + + public function testReturnCachedPreviewsWithoutCheckingSupportedMimetype(): void { + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(true); + $file->method('getId') + ->willReturn(42); + + + $previewFolder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with($this->equalTo(42)) + ->willReturn($previewFolder); + + $maxPreview = $this->createMock(ISimpleFile::class); + $maxPreview->method('getName') + ->willReturn('2048-2048-max.png'); + $maxPreview->method('getSize')->willReturn(1000); + $maxPreview->method('getMimeType') + ->willReturn('image/png'); + + $preview = $this->createMock(ISimpleFile::class); + $preview->method('getSize')->willReturn(1000); + $preview->method('getName')->willReturn('1024-512-crop.png'); + + $previewFolder->method('getDirectoryListing') + ->willReturn([$maxPreview, $preview]); + + $this->previewManager->expects($this->never()) + ->method('isMimeSupported'); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType')); + + $result = $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'); + $this->assertSame($preview, $result); + } + + public function testNoProvider(): void { + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(true); + $file->method('getMimeType') + ->willReturn('myMimeType'); + $file->method('getId') + ->willReturn(42); + + $previewFolder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with($this->equalTo(42)) + ->willReturn($previewFolder); + + $previewFolder->method('getDirectoryListing') + ->willReturn([]); + + $this->previewManager->method('getProviders') + ->willReturn([]); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file, 100, 100, false, IPreview::MODE_FILL, null)); + + $this->expectException(NotFoundException::class); + $this->generator->getPreview($file, 100, 100); + } + + private function getMockImage($width, $height, $data = null) { + $image = $this->createMock(IImage::class); + $image->method('height')->willReturn($width); + $image->method('width')->willReturn($height); + $image->method('valid')->willReturn(true); + $image->method('dataMimeType')->willReturn('image/png'); + $image->method('data')->willReturn($data); + + $image->method('resizeCopy')->willReturnCallback(function ($size) use ($data) { + return $this->getMockImage($size, $size, $data); + }); + $image->method('preciseResizeCopy')->willReturnCallback(function ($width, $height) use ($data) { + return $this->getMockImage($width, $height, $data); + }); + $image->method('cropCopy')->willReturnCallback(function ($x, $y, $width, $height) use ($data) { + return $this->getMockImage($width, $height, $data); + }); + + return $image; + } + + public static function dataSize(): array { + return [ + [1024, 2048, 512, 512, false, IPreview::MODE_FILL, 256, 512], + [1024, 2048, 512, 512, false, IPreview::MODE_COVER, 512, 1024], + [1024, 2048, 512, 512, true, IPreview::MODE_FILL, 1024, 1024], + [1024, 2048, 512, 512, true, IPreview::MODE_COVER, 1024, 1024], + + [1024, 2048, -1, 512, false, IPreview::MODE_COVER, 256, 512], + [1024, 2048, 512, -1, false, IPreview::MODE_FILL, 512, 1024], + + [1024, 2048, 250, 1100, true, IPreview::MODE_COVER, 256, 1126], + [1024, 1100, 250, 1100, true, IPreview::MODE_COVER, 250, 1100], + + [1024, 2048, 4096, 2048, false, IPreview::MODE_FILL, 1024, 2048], + [1024, 2048, 4096, 2048, false, IPreview::MODE_COVER, 1024, 2048], + + + [2048, 1024, 512, 512, false, IPreview::MODE_FILL, 512, 256], + [2048, 1024, 512, 512, false, IPreview::MODE_COVER, 1024, 512], + [2048, 1024, 512, 512, true, IPreview::MODE_FILL, 1024, 1024], + [2048, 1024, 512, 512, true, IPreview::MODE_COVER, 1024, 1024], + + [2048, 1024, -1, 512, false, IPreview::MODE_FILL, 1024, 512], + [2048, 1024, 512, -1, false, IPreview::MODE_COVER, 512, 256], + + [2048, 1024, 4096, 1024, true, IPreview::MODE_FILL, 2048, 512], + [2048, 1024, 4096, 1024, true, IPreview::MODE_COVER, 2048, 512], + + //Test minimum size + [2048, 1024, 32, 32, false, IPreview::MODE_FILL, 64, 32], + [2048, 1024, 32, 32, false, IPreview::MODE_COVER, 64, 32], + [2048, 1024, 32, 32, true, IPreview::MODE_FILL, 64, 64], + [2048, 1024, 32, 32, true, IPreview::MODE_COVER, 64, 64], + ]; + } + + /** + * + * @param int $maxX + * @param int $maxY + * @param int $reqX + * @param int $reqY + * @param bool $crop + * @param string $mode + * @param int $expectedX + * @param int $expectedY + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSize')] + public function testCorrectSize($maxX, $maxY, $reqX, $reqY, $crop, $mode, $expectedX, $expectedY): void { + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(true); + $file->method('getMimeType') + ->willReturn('myMimeType'); + $file->method('getId') + ->willReturn(42); + + $this->previewManager->method('isMimeSupported') + ->with($this->equalTo('myMimeType')) + ->willReturn(true); + + $previewFolder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder') + ->with($this->equalTo(42)) + ->willReturn($previewFolder); + + $maxPreview = $this->createMock(ISimpleFile::class); + $maxPreview->method('getName') + ->willReturn($maxX . '-' . $maxY . '-max.png'); + $maxPreview->method('getMimeType') + ->willReturn('image/png'); + $maxPreview->method('getSize')->willReturn(1000); + + $previewFolder->method('getDirectoryListing') + ->willReturn([$maxPreview]); + + $filename = $expectedX . '-' . $expectedY; + if ($crop) { + $filename .= '-crop'; + } + $filename .= '.png'; + $previewFolder->method('getFile') + ->with($this->equalTo($filename)) + ->willThrowException(new NotFoundException()); + + $image = $this->getMockImage($maxX, $maxY); + $this->helper->method('getImage') + ->with($this->equalTo($maxPreview)) + ->willReturn($image); + + $preview = $this->createMock(ISimpleFile::class); + $preview->method('getSize')->willReturn(1000); + $previewFolder->method('newFile') + ->with($this->equalTo($filename)) + ->willReturn($preview); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file, $reqX, $reqY, $crop, $mode, null)); + + $result = $this->generator->getPreview($file, $reqX, $reqY, $crop, $mode); + if ($expectedX === $maxX && $expectedY === $maxY) { + $this->assertSame($maxPreview, $result); + } else { + $this->assertSame($preview, $result); + } + } + + public function testUnreadbleFile(): void { + $file = $this->createMock(File::class); + $file->method('isReadable') + ->willReturn(false); + + $this->expectException(NotFoundException::class); + + $this->generator->getPreview($file, 100, 100, false); + } +} diff --git a/tests/lib/Preview/HEICTest.php b/tests/lib/Preview/HEICTest.php new file mode 100644 index 00000000000..def113fbf08 --- /dev/null +++ b/tests/lib/Preview/HEICTest.php @@ -0,0 +1,33 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Preview; + +use OC\Preview\HEIC; + +/** + * Class BitmapTest + * + * @group DB + * + * @package Test\Preview + */ +class HEICTest extends Provider { + protected function setUp(): void { + if (!in_array('HEIC', \Imagick::queryFormats('HEI*'))) { + $this->markTestSkipped('ImageMagick is not HEIC aware. Skipping tests'); + } else { + parent::setUp(); + + $fileName = 'testimage.heic'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + $this->width = 1680; + $this->height = 1050; + $this->provider = new HEIC; + } + } +} diff --git a/tests/lib/Preview/ImageTest.php b/tests/lib/Preview/ImageTest.php new file mode 100644 index 00000000000..88c3090557f --- /dev/null +++ b/tests/lib/Preview/ImageTest.php @@ -0,0 +1,30 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\JPEG; + +/** + * Class ImageTest + * + * @group DB + * + * @package Test\Preview + */ +class ImageTest extends Provider { + protected function setUp(): void { + parent::setUp(); + + $fileName = 'testimage.jpg'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + $this->width = 1680; + $this->height = 1050; + $this->provider = new JPEG(); + } +} diff --git a/tests/lib/Preview/MP3Test.php b/tests/lib/Preview/MP3Test.php new file mode 100644 index 00000000000..faa06fe42a1 --- /dev/null +++ b/tests/lib/Preview/MP3Test.php @@ -0,0 +1,30 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\MP3; + +/** + * Class MP3Test + * + * @group DB + * + * @package Test\Preview + */ +class MP3Test extends Provider { + protected function setUp(): void { + parent::setUp(); + + $fileName = 'testimage.mp3'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + $this->width = 200; + $this->height = 200; + $this->provider = new MP3; + } +} diff --git a/tests/lib/Preview/MovieBrokenStuckFfmpegTest.php b/tests/lib/Preview/MovieBrokenStuckFfmpegTest.php new file mode 100644 index 00000000000..e66d5e64649 --- /dev/null +++ b/tests/lib/Preview/MovieBrokenStuckFfmpegTest.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Preview; + +/** + * Class MovieTest + * + * @group DB + * + * @package Test\Preview + */ +class MovieBrokenStuckFfmpegTest extends MovieTest { + protected string $fileName = 'broken-video.webm'; +} diff --git a/tests/lib/Preview/MovieTest.php b/tests/lib/Preview/MovieTest.php new file mode 100644 index 00000000000..c85a9f99748 --- /dev/null +++ b/tests/lib/Preview/MovieTest.php @@ -0,0 +1,43 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\Movie; +use OCP\IBinaryFinder; +use OCP\Server; + +/** + * Class MovieTest + * + * @group DB + * + * @package Test\Preview + */ +class MovieTest extends Provider { + protected string $fileName = 'testimage.mp4'; + protected int $width = 560; + protected int $height = 320; + + protected function setUp(): void { + $binaryFinder = Server::get(IBinaryFinder::class); + $movieBinary = $binaryFinder->findBinaryPath('avconv'); + if (!is_string($movieBinary)) { + $movieBinary = $binaryFinder->findBinaryPath('ffmpeg'); + } + + if (is_string($movieBinary)) { + parent::setUp(); + + $this->imgPath = $this->prepareTestFile($this->fileName, \OC::$SERVERROOT . '/tests/data/' . $this->fileName); + $this->provider = new Movie(['movieBinary' => $movieBinary]); + } else { + $this->markTestSkipped('No Movie provider present'); + } + } +} diff --git a/tests/lib/Preview/OfficeTest.php b/tests/lib/Preview/OfficeTest.php new file mode 100644 index 00000000000..62a4767fd2e --- /dev/null +++ b/tests/lib/Preview/OfficeTest.php @@ -0,0 +1,40 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\OpenDocument; +use OCP\IBinaryFinder; +use OCP\Server; + +/** + * Class OfficeTest + * + * @group DB + * + * @package Test\Preview + */ +class OfficeTest extends Provider { + protected function setUp(): void { + $binaryFinder = Server::get(IBinaryFinder::class); + $libreofficeBinary = $binaryFinder->findBinaryPath('libreoffice'); + $openofficeBinary = $libreofficeBinary === false ? $binaryFinder->findBinaryPath('openoffice') : false; + + if ($libreofficeBinary !== false || $openofficeBinary !== false) { + parent::setUp(); + + $fileName = 'testimage.odt'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + $this->width = 595; + $this->height = 842; + $this->provider = new OpenDocument; + } else { + $this->markTestSkipped('No Office provider present'); + } + } +} diff --git a/tests/lib/Preview/Provider.php b/tests/lib/Preview/Provider.php new file mode 100644 index 00000000000..d8f10c430e4 --- /dev/null +++ b/tests/lib/Preview/Provider.php @@ -0,0 +1,181 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Files\Filesystem; +use OC\Files\Node\File; +use OC\Files\Storage\Storage; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OC\Preview\TXT; +use OCP\Files\IRootFolder; +use OCP\IImage; +use OCP\IUserManager; +use OCP\Server; + +abstract class Provider extends \Test\TestCase { + protected string $imgPath; + protected int $width; + protected int $height; + /** @var \OC\Preview\Provider|mixed $provider */ + protected $provider; + protected int $maxWidth = 1024; + protected int $maxHeight = 1024; + protected bool $scalingUp = false; + protected string $userId; + protected View $rootView; + protected Storage $storage; + + protected function setUp(): void { + parent::setUp(); + + $userManager = Server::get(IUserManager::class); + $userManager->clearBackends(); + $backend = new \Test\Util\User\Dummy(); + $userManager->registerBackend($backend); + + $userId = $this->getUniqueID(); + $backend->createUser($userId, $userId); + $this->loginAsUser($userId); + + $this->storage = new Temporary([]); + Filesystem::mount($this->storage, [], '/' . $userId . '/'); + + $this->rootView = new View(''); + $this->rootView->mkdir('/' . $userId); + $this->rootView->mkdir('/' . $userId . '/files'); + + $this->userId = $userId; + } + + protected function tearDown(): void { + $this->logout(); + + parent::tearDown(); + } + + public static function dimensionsDataProvider() { + return [ + [-rand(5, 100), -rand(5, 100)], + [rand(5, 100), rand(5, 100)], + [-rand(5, 100), rand(5, 100)], + [rand(5, 100), -rand(5, 100)], + ]; + } + + /** + * Launches all the tests we have + * + * @requires extension imagick + * + * @param int $widthAdjustment + * @param int $heightAdjustment + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dimensionsDataProvider')] + public function testGetThumbnail($widthAdjustment, $heightAdjustment): void { + $ratio = round($this->width / $this->height, 2); + $this->maxWidth = $this->width - $widthAdjustment; + $this->maxHeight = $this->height - $heightAdjustment; + + // Testing code + /*print_r("w $this->width "); + print_r("h $this->height "); + print_r("r $ratio ");*/ + + $preview = $this->getPreview($this->provider); + // The TXT provider uses the max dimensions to create its canvas, + // so the ratio will always be the one of the max dimension canvas + if (!$this->provider instanceof TXT) { + $this->doesRatioMatch($preview, $ratio); + } + $this->doesPreviewFit($preview); + } + + /** + * Adds the test file to the filesystem + * + * @param string $fileName name of the file to create + * @param string $fileContent path to file to use for test + * + * @return string + */ + protected function prepareTestFile($fileName, $fileContent) { + $imgData = file_get_contents($fileContent); + $imgPath = '/' . $this->userId . '/files/' . $fileName; + $this->rootView->file_put_contents($imgPath, $imgData); + + $scanner = $this->storage->getScanner(); + $scanner->scan(''); + + return $imgPath; + } + + /** + * Retrieves a max size thumbnail can be created + * + * @param \OC\Preview\Provider $provider + * + * @return bool|IImage + */ + private function getPreview($provider) { + $file = new File(Server::get(IRootFolder::class), $this->rootView, $this->imgPath); + $preview = $provider->getThumbnail($file, $this->maxWidth, $this->maxHeight, $this->scalingUp); + + if (get_class($this) === BitmapTest::class && $preview === null) { + $this->markTestSkipped('An error occured while operating with Imagick.'); + } + + $this->assertNotEquals(false, $preview); + $this->assertEquals(true, $preview->valid()); + + return $preview; + } + + /** + * Checks if the preview ratio matches the original ratio + * + * @param IImage $preview + * @param int $ratio + */ + private function doesRatioMatch($preview, $ratio) { + $previewRatio = round($preview->width() / $preview->height(), 2); + $this->assertEquals($ratio, $previewRatio); + } + + /** + * Tests if a max size preview of smaller dimensions can be created + * + * @param IImage $preview + */ + private function doesPreviewFit($preview) { + $maxDimRatio = round($this->maxWidth / $this->maxHeight, 2); + $previewRatio = round($preview->width() / $preview->height(), 2); + + // Testing code + /*print_r("mw $this->maxWidth "); + print_r("mh $this->maxHeight "); + print_r("mr $maxDimRatio "); + $pw = $preview->width(); + $ph = $preview->height(); + print_r("pw $pw "); + print_r("ph $ph "); + print_r("pr $previewRatio ");*/ + + if ($maxDimRatio < $previewRatio) { + $this->assertLessThanOrEqual($this->maxWidth, $preview->width()); + $this->assertLessThan($this->maxHeight, $preview->height()); + } elseif ($maxDimRatio > $previewRatio) { + $this->assertLessThan($this->maxWidth, $preview->width()); + $this->assertLessThanOrEqual($this->maxHeight, $preview->height()); + } else { // Original had to be resized + $this->assertLessThanOrEqual($this->maxWidth, $preview->width()); + $this->assertLessThanOrEqual($this->maxHeight, $preview->height()); + } + } +} diff --git a/tests/lib/Preview/SVGTest.php b/tests/lib/Preview/SVGTest.php new file mode 100644 index 00000000000..8c6d9bb6691 --- /dev/null +++ b/tests/lib/Preview/SVGTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\SVG; +use OCP\Files\File; + +/** + * Class SVGTest + * + * @group DB + * + * @package Test\Preview + */ +class SVGTest extends Provider { + protected function setUp(): void { + $checkImagick = new \Imagick(); + if (count($checkImagick->queryFormats('SVG')) === 1) { + parent::setUp(); + + $fileName = 'testimagelarge.svg'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + $this->width = 3000; + $this->height = 2000; + $this->provider = new SVG; + } else { + $this->markTestSkipped('No SVG provider present'); + } + } + + public static function dataGetThumbnailSVGHref(): array { + return [ + ['href'], + [' href'], + ["\nhref"], + ['xlink:href'], + [' xlink:href'], + ["\nxlink:href"], + ]; + } + + /** + * @requires extension imagick + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetThumbnailSVGHref')] + public function testGetThumbnailSVGHref(string $content): void { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> + <image x="0" y="0"' . $content . '="fxlogo.png" height="100" width="100" /> +</svg>'); + rewind($handle); + + $file = $this->createMock(File::class); + $file->method('fopen') + ->willReturn($handle); + + self::assertNull($this->provider->getThumbnail($file, 512, 512)); + } +} diff --git a/tests/lib/Preview/TXTTest.php b/tests/lib/Preview/TXTTest.php new file mode 100644 index 00000000000..d722495136c --- /dev/null +++ b/tests/lib/Preview/TXTTest.php @@ -0,0 +1,31 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace Test\Preview; + +use OC\Preview\TXT; + +/** + * Class TXTTest + * + * @group DB + * + * @package Test\Preview + */ +class TXTTest extends Provider { + protected function setUp(): void { + parent::setUp(); + + $fileName = 'lorem-big.txt'; + $this->imgPath = $this->prepareTestFile($fileName, \OC::$SERVERROOT . '/tests/data/' . $fileName); + // Arbitrary width and length which won't be used to calculate the ratio + $this->width = 500; + $this->height = 200; + $this->provider = new TXT; + } +} |