mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
When requesting previews, which we don't find in oc_previews, search in IAppData first before creating them. Move the logic from MovepreviewJob to PreviewMigrationService and reuse that in the Preview Generator. At the same time rename MovePreviewJob to PreviewMigrationJob as it is a better name. Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
527 lines
17 KiB
PHP
527 lines
17 KiB
PHP
<?php
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace Test\Preview;
|
|
|
|
use OC\Core\AppInfo\ConfigLexicon;
|
|
use OC\Preview\Db\Preview;
|
|
use OC\Preview\Db\PreviewMapper;
|
|
use OC\Preview\Generator;
|
|
use OC\Preview\GeneratorHelper;
|
|
use OC\Preview\PreviewMigrationService;
|
|
use OC\Preview\Storage\StorageFactory;
|
|
use OCP\EventDispatcher\IEventDispatcher;
|
|
use OCP\Files\File;
|
|
use OCP\Files\Mount\IMountPoint;
|
|
use OCP\Files\NotFoundException;
|
|
use OCP\IAppConfig;
|
|
use OCP\IConfig;
|
|
use OCP\IImage;
|
|
use OCP\IPreview;
|
|
use OCP\Preview\BeforePreviewFetchedEvent;
|
|
use OCP\Preview\IProviderV2;
|
|
use OCP\Preview\IVersionedPreviewFile;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\Attributes\TestWith;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use Psr\Log\LoggerInterface;
|
|
use Test\TestCase;
|
|
|
|
abstract class VersionedPreviewFile implements IVersionedPreviewFile, File {
|
|
|
|
}
|
|
|
|
class GeneratorTest extends TestCase {
|
|
private IConfig&MockObject $config;
|
|
private IAppConfig&MockObject $appConfig;
|
|
private IPreview&MockObject $previewManager;
|
|
private GeneratorHelper&MockObject $helper;
|
|
private IEventDispatcher&MockObject $eventDispatcher;
|
|
private Generator $generator;
|
|
private LoggerInterface&MockObject $logger;
|
|
private StorageFactory&MockObject $storageFactory;
|
|
private PreviewMapper&MockObject $previewMapper;
|
|
private PreviewMigrationService&MockObject $migrationService;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$this->config = $this->createMock(IConfig::class);
|
|
$this->appConfig = $this->createMock(IAppConfig::class);
|
|
$this->previewManager = $this->createMock(IPreview::class);
|
|
$this->helper = $this->createMock(GeneratorHelper::class);
|
|
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
|
$this->logger = $this->createMock(LoggerInterface::class);
|
|
$this->previewMapper = $this->createMock(PreviewMapper::class);
|
|
$this->storageFactory = $this->createMock(StorageFactory::class);
|
|
$this->migrationService = $this->createMock(PreviewMigrationService::class);
|
|
|
|
$this->generator = new Generator(
|
|
$this->config,
|
|
$this->appConfig,
|
|
$this->previewManager,
|
|
$this->helper,
|
|
$this->eventDispatcher,
|
|
$this->logger,
|
|
$this->previewMapper,
|
|
$this->storageFactory,
|
|
$this->migrationService,
|
|
);
|
|
}
|
|
|
|
private function getFile(int $fileId, string $mimeType, bool $hasVersion = false): File {
|
|
$mountPoint = $this->createMock(IMountPoint::class);
|
|
$mountPoint->method('getNumericStorageId')->willReturn(42);
|
|
if ($hasVersion) {
|
|
$file = $this->createMock(VersionedPreviewFile::class);
|
|
$file->method('getPreviewVersion')->willReturn('abc');
|
|
} else {
|
|
$file = $this->createMock(File::class);
|
|
}
|
|
$file->method('isReadable')
|
|
->willReturn(true);
|
|
$file->method('getMimeType')
|
|
->willReturn($mimeType);
|
|
$file->method('getId')
|
|
->willReturn($fileId);
|
|
$file->method('getMountPoint')
|
|
->willReturn($mountPoint);
|
|
return $file;
|
|
}
|
|
|
|
#[TestWith([true])]
|
|
#[TestWith([false])]
|
|
public function testGetCachedPreview(bool $hasPreview): void {
|
|
$file = $this->getFile(42, 'myMimeType', $hasPreview);
|
|
|
|
$this->previewManager->method('isMimeSupported')
|
|
->with($this->equalTo('myMimeType'))
|
|
->willReturn(true);
|
|
|
|
$maxPreview = new Preview();
|
|
$maxPreview->setWidth(1000);
|
|
$maxPreview->setHeight(1000);
|
|
$maxPreview->setMax(true);
|
|
$maxPreview->setSize(1000);
|
|
$maxPreview->setCropped(false);
|
|
$maxPreview->setStorageId(1);
|
|
$maxPreview->setVersion($hasPreview ? 'abc' : null);
|
|
$maxPreview->setMimeType('image/png');
|
|
|
|
$previewFile = new Preview();
|
|
$previewFile->setWidth(256);
|
|
$previewFile->setHeight(256);
|
|
$previewFile->setMax(false);
|
|
$previewFile->setSize(1000);
|
|
$previewFile->setVersion($hasPreview ? 'abc' : null);
|
|
$previewFile->setCropped(false);
|
|
$previewFile->setStorageId(1);
|
|
$previewFile->setMimeType('image/png');
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => [
|
|
$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($hasPreview ? 'abc-256-256.png' : '256-256.png', $result->getName());
|
|
$this->assertSame(1000, $result->getSize());
|
|
}
|
|
|
|
#[TestWith([true])]
|
|
#[TestWith([false])]
|
|
public function testGetNewPreview(bool $hasVersion): void {
|
|
$file = $this->getFile(42, 'myMimeType', $hasVersion);
|
|
|
|
$this->previewManager->method('isMimeSupported')
|
|
->with($this->equalTo('myMimeType'))
|
|
->willReturn(true);
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => []]);
|
|
|
|
$this->config->method('getSystemValueString')
|
|
->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): false|IImage {
|
|
if ($provider === $validProvider) {
|
|
return $image;
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
$image->method('data')
|
|
->willReturn('my data');
|
|
|
|
$this->previewMapper->method('insert')
|
|
->willReturnCallback(fn (Preview $preview): Preview => $preview);
|
|
|
|
$this->previewMapper->method('update')
|
|
->willReturnCallback(fn (Preview $preview): Preview => $preview);
|
|
|
|
$this->storageFactory->method('writePreview')
|
|
->willReturnCallback(function (Preview $preview, mixed $data) use ($hasVersion): int {
|
|
$data = stream_get_contents($data);
|
|
if ($hasVersion) {
|
|
switch ($preview->getName()) {
|
|
case 'abc-2048-2048-max.png':
|
|
$this->assertSame('my data', $data);
|
|
return 1000;
|
|
case 'abc-256-256.png':
|
|
$this->assertSame('my resized data', $data);
|
|
return 1000;
|
|
}
|
|
} else {
|
|
switch ($preview->getName()) {
|
|
case '2048-2048-max.png':
|
|
$this->assertSame('my data', $data);
|
|
return 1000;
|
|
case '256-256.png':
|
|
$this->assertSame('my resized data', $data);
|
|
return 1000;
|
|
}
|
|
}
|
|
$this->fail('file name is wrong:' . $preview->getName());
|
|
});
|
|
|
|
$image = $this->getMockImage(2048, 2048, 'my resized data');
|
|
$this->helper->method('getImage')
|
|
->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($hasVersion ? 'abc-256-256.png' : '256-256.png', $result->getName());
|
|
$this->assertSame(1000, $result->getSize());
|
|
}
|
|
|
|
|
|
public function testMigrateOldPreview(): void {
|
|
$file = $this->getFile(42, 'myMimeType', false);
|
|
|
|
$maxPreview = new Preview();
|
|
$maxPreview->setWidth(1000);
|
|
$maxPreview->setHeight(1000);
|
|
$maxPreview->setMax(true);
|
|
$maxPreview->setSize(1000);
|
|
$maxPreview->setCropped(false);
|
|
$maxPreview->setStorageId(1);
|
|
$maxPreview->setVersion(null);
|
|
$maxPreview->setMimeType('image/png');
|
|
|
|
$previewFile = new Preview();
|
|
$previewFile->setWidth(256);
|
|
$previewFile->setHeight(256);
|
|
$previewFile->setMax(false);
|
|
$previewFile->setSize(1000);
|
|
$previewFile->setVersion(null);
|
|
$previewFile->setCropped(false);
|
|
$previewFile->setStorageId(1);
|
|
$previewFile->setMimeType('image/png');
|
|
|
|
$this->previewManager->method('isMimeSupported')
|
|
->with($this->equalTo('myMimeType'))
|
|
->willReturn(true);
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => []]);
|
|
|
|
$this->config->method('getSystemValueString')
|
|
->willReturnCallback(function ($key, $default) {
|
|
return $default;
|
|
});
|
|
|
|
$this->config->method('getSystemValueInt')
|
|
->willReturnCallback(function ($key, $default) {
|
|
return $default;
|
|
});
|
|
|
|
$this->appConfig->method('getValueBool')
|
|
->willReturnCallback(fn ($app, $key, $default) => match ($key) {
|
|
ConfigLexicon::ON_DEMAND_PREVIEW_MIGRATION => true,
|
|
'previewMovedDone' => false,
|
|
});
|
|
|
|
$this->migrationService->expects($this->exactly(1))
|
|
->method('migrateFileId')
|
|
->willReturn([$maxPreview, $previewFile]);
|
|
|
|
$result = $this->generator->getPreview($file, 100, 100);
|
|
$this->assertSame('256-256.png', $result->getName());
|
|
$this->assertSame(1000, $result->getSize());
|
|
}
|
|
|
|
public function testInvalidMimeType(): void {
|
|
$this->expectException(NotFoundException::class);
|
|
|
|
$file = $this->getFile(42, 'invalidType');
|
|
|
|
$this->previewManager->method('isMimeSupported')
|
|
->with('invalidType')
|
|
->willReturn(false);
|
|
|
|
$maxPreview = new Preview();
|
|
$maxPreview->setWidth(2048);
|
|
$maxPreview->setHeight(2048);
|
|
$maxPreview->setMax(true);
|
|
$maxPreview->setSize(1000);
|
|
$maxPreview->setVersion(null);
|
|
$maxPreview->setMimetype('image/png');
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => [
|
|
$maxPreview,
|
|
]]);
|
|
|
|
$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->getFile(42, 'myMimeType');
|
|
|
|
$maxPreview = new Preview();
|
|
$maxPreview->setWidth(2048);
|
|
$maxPreview->setHeight(2048);
|
|
$maxPreview->setMax(true);
|
|
$maxPreview->setSize(1000);
|
|
$maxPreview->setVersion(null);
|
|
$maxPreview->setMimeType('image/png');
|
|
|
|
$previewFile = new Preview();
|
|
$previewFile->setWidth(1024);
|
|
$previewFile->setHeight(512);
|
|
$previewFile->setMax(false);
|
|
$previewFile->setSize(1000);
|
|
$previewFile->setCropped(true);
|
|
$previewFile->setVersion(null);
|
|
$previewFile->setMimeType('image/png');
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => [
|
|
$maxPreview,
|
|
$previewFile,
|
|
]]);
|
|
|
|
$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('1024-512-crop.png', $result->getName());
|
|
}
|
|
|
|
public function testNoProvider(): void {
|
|
$file = $this->getFile(42, 'myMimeType');
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => []]);
|
|
|
|
$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(int $width, int $height, string $data = '') {
|
|
$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],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('dataSize')]
|
|
public function testCorrectSize(int $maxX, int $maxY, int $reqX, int $reqY, bool $crop, string $mode, int $expectedX, int $expectedY): void {
|
|
$file = $this->getFile(42, 'myMimeType');
|
|
|
|
$this->previewManager->method('isMimeSupported')
|
|
->with($this->equalTo('myMimeType'))
|
|
->willReturn(true);
|
|
|
|
$maxPreview = new Preview();
|
|
$maxPreview->setWidth($maxX);
|
|
$maxPreview->setHeight($maxY);
|
|
$maxPreview->setMax(true);
|
|
$maxPreview->setSize(1000);
|
|
$maxPreview->setVersion(null);
|
|
$maxPreview->setMimeType('image/png');
|
|
|
|
$this->assertSame($maxPreview->getName(), $maxX . '-' . $maxY . '-max.png');
|
|
$this->assertSame($maxPreview->getMimeType(), 'image/png');
|
|
|
|
$this->previewMapper->method('getAvailablePreviews')
|
|
->with($this->equalTo([42]))
|
|
->willReturn([42 => [
|
|
$maxPreview,
|
|
]]);
|
|
|
|
$filename = $expectedX . '-' . $expectedY;
|
|
if ($crop) {
|
|
$filename .= '-crop';
|
|
}
|
|
$filename .= '.png';
|
|
|
|
$image = $this->getMockImage($maxX, $maxY);
|
|
$this->helper->method('getImage')
|
|
->willReturn($image);
|
|
|
|
$this->previewMapper->method('insert')
|
|
->willReturnCallback(function (Preview $preview) use ($filename): Preview {
|
|
$this->assertSame($preview->getName(), $filename);
|
|
return $preview;
|
|
});
|
|
|
|
$this->previewMapper->method('update')
|
|
->willReturnCallback(fn (Preview $preview): Preview => $preview);
|
|
|
|
$this->storageFactory->method('writePreview')
|
|
->willReturn(1000);
|
|
|
|
$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->getName(), $result->getName());
|
|
} else {
|
|
$this->assertSame($filename, $result->getName());
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|