aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Preview
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Preview')
-rw-r--r--tests/lib/Preview/BackgroundCleanupJobTest.php228
-rw-r--r--tests/lib/Preview/BitmapTest.php30
-rw-r--r--tests/lib/Preview/GeneratorTest.php464
-rw-r--r--tests/lib/Preview/HEICTest.php33
-rw-r--r--tests/lib/Preview/ImageTest.php30
-rw-r--r--tests/lib/Preview/MP3Test.php30
-rw-r--r--tests/lib/Preview/MovieBrokenStuckFfmpegTest.php20
-rw-r--r--tests/lib/Preview/MovieTest.php43
-rw-r--r--tests/lib/Preview/OfficeTest.php40
-rw-r--r--tests/lib/Preview/Provider.php181
-rw-r--r--tests/lib/Preview/SVGTest.php65
-rw-r--r--tests/lib/Preview/TXTTest.php31
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;
+ }
+}