fedi-feed-router/tests/Unit/Modules/Lemmy/Services/ThumbnailUploaderTest.php

169 lines
5.6 KiB
PHP
Raw Normal View History

<?php
namespace Tests\Unit\Modules\Lemmy\Services;
use App\Modules\Lemmy\Services\ThumbnailUploader;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class ThumbnailUploaderTest extends TestCase
{
/**
* @param int<1, max> $width
* @param int<1, max> $height
*/
private function jpeg(int $width, int $height): string
{
$image = imagecreatetruecolor($width, $height);
$colour = imagecolorallocate($image, 120, 90, 60);
imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $colour === false ? 0 : $colour);
ob_start();
imagejpeg($image, null, 90);
$bytes = (string) ob_get_clean();
imagedestroy($image);
return $bytes;
}
private function uploader(): ThumbnailUploader
{
return new ThumbnailUploader('https://lemmy.world');
}
private function fake(string $sourceBody, mixed $uploadResponse, int $uploadStatus = 200): void
{
Http::fake([
'https://cdn.example.com/*' => Http::response($sourceBody, 200, ['Content-Type' => 'image/jpeg']),
'https://lemmy.world/pictrs/image' => Http::response($uploadResponse, $uploadStatus),
]);
}
public function test_it_returns_an_instance_hosted_url(): void
{
$this->fake($this->jpeg(1200, 800), ['files' => [['file' => 'abc123.jpg', 'delete_token' => 'tok']]]);
$url = $this->uploader()->upload('https://cdn.example.com/big.jpg', 'token');
$this->assertSame('https://lemmy.world/pictrs/image/abc123.jpg', $url);
}
public function test_it_downscales_a_wide_image_before_upload(): void
{
$source = $this->jpeg(2000, 1000);
$this->fake($source, ['files' => [['file' => 'abc123.jpg']]]);
$this->uploader()->upload('https://cdn.example.com/big.jpg', 'token');
Http::assertSent(function (Request $request) use ($source): bool {
if ($request->url() !== 'https://lemmy.world/pictrs/image') {
return false;
}
$uploaded = $this->multipartFileContents($request);
$size = getimagesizefromstring($uploaded);
return $size !== false
&& $size[0] === 600
&& $size[1] === 300
&& $uploaded !== $source
&& strlen($uploaded) < strlen($source);
});
}
public function test_it_uploads_a_small_image_untouched(): void
{
$source = $this->jpeg(320, 240);
$this->fake($source, ['files' => [['file' => 'abc123.jpg']]]);
$this->uploader()->upload('https://cdn.example.com/small.jpg', 'token');
Http::assertSent(function (Request $request) use ($source): bool {
return $request->url() !== 'https://lemmy.world/pictrs/image'
|| $this->multipartFileContents($request) === $source;
});
}
public function test_it_sends_the_token_as_a_bearer_header(): void
{
$this->fake($this->jpeg(800, 600), ['files' => [['file' => 'abc123.jpg']]]);
$this->uploader()->upload('https://cdn.example.com/big.jpg', 'secret-token');
Http::assertSent(fn (Request $request): bool => $request->url() !== 'https://lemmy.world/pictrs/image'
|| $request->header('Authorization')[0] === 'Bearer secret-token');
}
public function test_it_returns_null_for_a_null_source(): void
{
Http::fake();
$this->assertNull($this->uploader()->upload(null, 'token'));
Http::assertNothingSent();
}
public function test_it_returns_null_for_an_empty_source(): void
{
Http::fake();
$this->assertNull($this->uploader()->upload('', 'token'));
Http::assertNothingSent();
}
public function test_it_returns_null_when_the_download_fails(): void
{
Http::fake(['https://cdn.example.com/*' => Http::response('', 404)]);
$this->assertNull($this->uploader()->upload('https://cdn.example.com/gone.jpg', 'token'));
}
public function test_it_returns_null_when_the_source_is_not_an_image(): void
{
$this->fake('<html>not an image</html>', ['files' => [['file' => 'abc123.jpg']]]);
$this->assertNull($this->uploader()->upload('https://cdn.example.com/page.html', 'token'));
}
public function test_it_returns_null_when_the_upload_fails(): void
{
$this->fake($this->jpeg(1200, 800), ['error' => 'nope'], 500);
$this->assertNull($this->uploader()->upload('https://cdn.example.com/big.jpg', 'token'));
}
public function test_it_returns_null_when_the_upload_response_has_no_file(): void
{
$this->fake($this->jpeg(1200, 800), ['files' => []]);
$this->assertNull($this->uploader()->upload('https://cdn.example.com/big.jpg', 'token'));
}
public function test_it_returns_null_when_the_source_exceeds_the_size_cap(): void
{
$this->fake(str_repeat('x', 10_485_761), ['files' => [['file' => 'abc123.jpg']]]);
$this->assertNull($this->uploader()->upload('https://cdn.example.com/huge.jpg', 'token'));
}
public function test_it_returns_null_when_the_download_throws(): void
{
Http::fake(fn () => throw new \RuntimeException('connection refused'));
$this->assertNull($this->uploader()->upload('https://cdn.example.com/big.jpg', 'token'));
}
private function multipartFileContents(Request $request): string
{
foreach ($request->data() as $part) {
if (($part['name'] ?? null) === 'images[]') {
return (string) $part['contents'];
}
}
return '';
}
}