$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('not an image', ['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 ''; } }