Files
service_wopi/tests/php/Unit/PreviewTest.php
T
2026-09-14 18:50:26 -04:00

129 lines
6.0 KiB
PHP

<?php
declare(strict_types=1);
use KTXC\Context\TenantContextInterface;
use KTXC\Resource\ProviderManager;
use KTXF\Preview\PreviewGenerationException;
use KTXF\Preview\PreviewRequest;
use KTXF\Preview\PreviewSource;
use KTXF\Resource\BinaryResource;
use KTXF\Resource\Provider\ProviderInterface;
use KTXM\ProviderPreviewImageMagick\Provider as ImageProvider;
use KTXM\ServiceWopi\Host\Configuration;
use KTXM\ServiceWopi\Host\WopiService;
use KTXM\ServiceWopi\Module;
use KTXM\ServiceWopi\Preview\ConversionClient;
use KTXM\ServiceWopi\Preview\Provider;
use PHPUnit\Framework\TestCase;
final class PreviewTest extends TestCase
{
public function testRegistersPreviewProvider(): void
{
$registry = $this->createMock(ProviderManager::class);
$registry->expects(self::once())->method('register')->with(ProviderInterface::TYPE_PREVIEW, 'wopi', Provider::class);
(new Module($registry))->boot();
}
public function testSupportedFormatsAndOutputs(): void
{
$provider = $this->provider($this->createStub(ConversionClient::class));
foreach (WopiService::FORMATS as $mime) {
self::assertTrue($provider->supports($mime, $this->request()));
}
self::assertFalse($provider->supports('application/pdf', $this->request()));
self::assertFalse($provider->supports('invalid', $this->request()));
self::assertFalse($provider->supports(WopiService::FORMATS['docx'], new PreviewRequest(100, 100, 'application/pdf', 80)));
}
public function testDisabledWopiDoesNotOfferPreviews(): void
{
$tenant = $this->createStub(TenantContextInterface::class);
$tenant->method('requireIdentifier')->willReturn('tenant');
$wopi = $this->createStub(WopiService::class);
$wopi->method('configuration')->willThrowException(new \KTXM\ServiceWopi\Host\HostException('Disabled', 503));
$provider = new Provider($tenant, $wopi, $this->createStub(ConversionClient::class), new ImageProvider());
self::assertFalse($provider->supports(WopiService::FORMATS['docx'], $this->request()));
}
public function testRejectsStreamExceedingTenantLimitBeforeUpload(): void
{
$client = $this->createMock(ConversionClient::class);
$client->expects(self::never())->method('pdf');
$this->expectException(PreviewGenerationException::class);
$this->provider($client, 3)->generate($this->source('oversized'), $this->request());
}
public function testConversionFailurePropagatesForManagerFallback(): void
{
$client = $this->createMock(ConversionClient::class);
$client->expects(self::once())->method('pdf')->willThrowException(new PreviewGenerationException('Unavailable'));
$this->expectException(PreviewGenerationException::class);
$this->provider($client)->generate($this->source('document'), $this->request());
}
public function testRendersOnlyFirstPageAndResizesToRequestedOutput(): void
{
// Two differently shaped, colored pages make selecting the wrong page observable.
$pdf = new Imagick();
foreach ([[400, 200, 'red'], [200, 400, 'blue']] as [$width, $height, $color]) {
$page = new Imagick();
$page->newImage($width, $height, 'transparent', 'pdf');
$drawing = new ImagickDraw();
$drawing->setFillColor($color);
$drawing->rectangle($width * 0.15, $height * 0.2, $width * 0.75, $height * 0.8);
$page->drawImage($drawing);
$drawing->clear();
$pdf->addImage($page);
$page->clear();
}
$bytes = $pdf->getImagesBlob();
$pdf->clear();
$client = $this->createMock(ConversionClient::class);
$client->expects(self::once())->method('pdf')->with(
self::callback(fn ($server) => $server->url === 'https://office.test'),
'document', 'docx', WopiService::FORMATS['docx'],
)->willReturn($bytes);
$result = $this->provider($client)->generate($this->source('document'), $this->request());
self::assertSame(100, $result->width);
self::assertSame(50, $result->height);
self::assertSame('image/webp', $result->resource->mimeType());
$output = implode('', iterator_to_array($result->resource->stream()));
$image = new Imagick();
$image->readImageBlob($output);
$color = $image->getImagePixelColor(20, 20)->getColor();
self::assertGreaterThan(200, $color['r']);
self::assertLessThan(30, $color['b']);
$paper = $image->getImagePixelColor(5, 5)->getColor();
self::assertGreaterThan(245, $paper['r']);
self::assertGreaterThan(245, $paper['g']);
self::assertGreaterThan(245, $paper['b']);
self::assertSame(1.0, $image->getImagePixelColor(5, 5)->getColorValue(Imagick::COLOR_ALPHA));
$edge = $image->getImagePixelColor(0, 25)->getColor();
self::assertLessThan(230, $edge['r']);
self::assertGreaterThan(130, $edge['r']);
$image->clear();
}
private function provider(ConversionClient $client, int $limit = 1048576): Provider
{
$tenant = $this->createStub(TenantContextInterface::class);
$tenant->method('requireIdentifier')->willReturn('tenant');
$wopi = $this->createStub(WopiService::class);
$wopi->method('configuration')->willReturn(new Configuration([
'enabled' => true, 'office_server' => 'https://office.test',
'origin_server' => 'https://app.test', 'max_bytes' => $limit,
]));
return new Provider($tenant, $wopi, $client, new ImageProvider());
}
private function request(): PreviewRequest { return new PreviewRequest(100, 100, 'image/webp', 80); }
private function source(string $content): PreviewSource
{
return new PreviewSource('document', 'file', 'version', WopiService::FORMATS['docx'], null,
static fn () => new BinaryResource('file.docx', WopiService::FORMATS['docx'],
(static function () use ($content): Generator { yield $content; })()));
}
}