Files
coruna-lab/tests/Unit/PhotoPreviewTest.php
T
2026-09-14 02:18:56 +08:00

119 lines
4.2 KiB
PHP

<?php
namespace Tests\Unit;
use App\Services\PhotoPreview;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class PhotoPreviewTest extends TestCase
{
#[Test]
public function basedir_probe_does_not_realpath_system_bins(): void
{
$preview = new PhotoPreview;
$ref = new \ReflectionMethod(PhotoPreview::class, 'isPathInsideOpenBasedir');
$this->assertFalse($ref->invoke($preview, '/usr/bin/heif-convert')
&& (string) ini_get('open_basedir') !== ''
&& ! str_contains((string) ini_get('open_basedir'), '/usr/bin'));
$resolved = (new \ReflectionMethod(PhotoPreview::class, 'resolveBinary'))->invoke($preview, 'heif-convert');
$this->assertNotNull($resolved);
}
#[Test]
public function detects_heic_ftyp_header(): void
{
$preview = new PhotoPreview;
$heicHead = "\x00\x00\x00\x28ftypheic\x00\x00\x00\x00mif1";
$this->assertTrue($preview->headLooksHeic($heicHead));
$this->assertFalse($preview->headLooksHeic("\xFF\xD8\xFF\xD9"));
$this->assertFalse($preview->headLooksHeic(''));
}
#[Test]
public function heic_convert_process_throw_does_not_bubble(): void
{
Storage::fake('local');
Process::fake(function () {
throw new \RuntimeException('proc_open is disabled');
});
$abs = sys_get_temp_dir().'/preview_'.uniqid().'.heic';
file_put_contents($abs, "\x00\x00\x00\x18ftypheic\x00\x00\x00\x00mif1");
$out = (new PhotoPreview)->payload($abs, 'dev-prev', hash('sha256', 'throw'));
$this->assertFalse($out['converted']);
$this->assertNotSame('', $out['bytes']);
@unlink($abs);
}
#[Test]
public function heic_without_throwing_when_system_bins_are_outside_basedir(): void
{
Storage::fake('local');
$abs = sys_get_temp_dir().'/preview_'.uniqid().'.heic';
file_put_contents($abs, "\x00\x00\x00\x18ftypheic\x00\x00\x00\x00mif1");
$out = (new PhotoPreview)->payload($abs, 'dev-prev', hash('sha256', 'heic-head'));
$this->assertFalse($out['converted']);
$this->assertNotSame('', $out['bytes']);
@unlink($abs);
}
#[Test]
public function jpeg_passthrough_does_not_convert(): void
{
Storage::fake('local');
$abs = $this->writeJpeg();
$out = (new PhotoPreview)->payload($abs, 'dev-prev', hash('sha256', 'jpeg'));
$this->assertFalse($out['converted']);
$this->assertSame('image/jpeg', $out['mime']);
$this->assertSame(file_get_contents($abs), $out['bytes']);
@unlink($abs);
}
#[Test]
public function heic_converts_to_jpeg_and_leaves_original(): void
{
if (! is_executable('/usr/bin/sips')) {
$this->markTestSkipped('sips is required to generate and convert HEIC');
}
Storage::fake('local');
$heic = $this->writeHeic();
$original = file_get_contents($heic);
$this->assertNotFalse($original);
$sha = hash('sha256', $original);
$out = (new PhotoPreview)->payload($heic, 'dev-prev', $sha);
$this->assertTrue($out['converted']);
$this->assertSame('image/jpeg', $out['mime']);
$this->assertSame("\xFF\xD8", substr($out['bytes'], 0, 2));
$this->assertSame($original, file_get_contents($heic));
Storage::disk('local')->assertExists('c2/photo-previews/dev-prev/'.$sha.'.jpg');
@unlink($heic);
}
private function writeJpeg(): string
{
$path = sys_get_temp_dir().'/preview_'.uniqid().'.jpg';
$im = imagecreatetruecolor(8, 8);
imagefilledrectangle($im, 0, 0, 7, 7, imagecolorallocate($im, 255, 255, 255));
imagejpeg($im, $path, 90);
return $path;
}
private function writeHeic(): string
{
$jpg = $this->writeJpeg();
$heic = sys_get_temp_dir().'/preview_'.uniqid().'.heic';
$result = Process::timeout(20)->run(['/usr/bin/sips', '-s', 'format', 'heic', '--out', $heic, $jpg]);
@unlink($jpg);
if (! $result->successful() || ! is_file($heic)) {
$this->markTestSkipped('sips could not write a HEIC fixture');
}
return $heic;
}
}