tests: tests for image tool

This commit is contained in:
2025-12-06 13:06:43 +03:00
parent 38668fb4a7
commit f34c12e043
6 changed files with 153 additions and 21 deletions

View File

@@ -6,5 +6,4 @@ use Exception;
class HttpNotFoundException extends Exception
{
}
}

View File

@@ -27,11 +27,6 @@ class ImageFactory
'no_image_path' => 'no_image.png',
];
private static array $modificators = [
'resize' => [self::class, 'resizeModification'],
'cover' => [self::class, 'coverModification'],
];
public function __construct(string $imageDir, string $siteUrl, string $driver, array $options = [])
{
$this->imageDir = rtrim($imageDir, '/');
@@ -53,7 +48,9 @@ class ImageFactory
$this->fullPath = $this->imageDir . '/' . $this->path;
}
$this->ensureFileExists($this->fullPath);
if (file_exists($this->fullPath)) {
$this->ensureFileExists($this->fullPath);
}
return $this;
}
@@ -86,7 +83,7 @@ class ImageFactory
$newImage = $this->resolveNewImagePath($format);
if (file_exists($newImage)) {
return $this->siteUrl . '/image/' . ltrim($newImage, $this->imageDir);
return $this->buildUrl($newImage);
}
$this->image = $this->manager->make($this->fullPath);
@@ -97,7 +94,13 @@ class ImageFactory
$this->image->encode($format, $quality)->save($newImage);
return $this->siteUrl . '/image/' . ltrim($newImage, $this->imageDir);
return $this->buildUrl($newImage);
}
private function buildUrl(string $path): string
{
$relativePath = substr($path, strlen($this->imageDir));
return $this->siteUrl . '/image/' . ltrim($relativePath, '/');
}
public function response(?string $format = null, ?int $quality = null): Response
@@ -148,13 +151,8 @@ class ImageFactory
$basename = $pathinfo['basename'];
$imagePath = rtrim($this->path, $basename);
foreach ($this->modifications as $name => $modification) {
$filename .= '_' . $name . '_' . $modification['width'] . 'x' . $modification['height'];
}
if ($this->modifications) {
$filename .= hash('SHA256', json_encode($this->modifications));
$filename .= '_' . substr(hash('SHA256', json_encode($this->modifications)), 0, 12);
}
return $this->imageDir . '/cache/' . $imagePath . $filename . '.' . $format;
@@ -189,7 +187,7 @@ class ImageFactory
{
$path = pathinfo($newImage, PATHINFO_DIRNAME);
if (! file_exists($path)) {
if (! mkdir($path, 0777, true) && ! is_dir($path)) {
if (! mkdir($path, 0755, true) && ! is_dir($path)) {
throw new RuntimeException(sprintf('Directory "%s" was not created', $path));
}
}

View File

@@ -13,4 +13,4 @@ class ImageNotFoundException extends Exception
parent::__construct($message, $code, $previous);
}
}
}

View File

@@ -19,4 +19,4 @@ class ImageToolServiceProvider extends ServiceProvider
);
});
}
}
}

View File

@@ -40,8 +40,10 @@ class SettingsHandler
if ($appIcon) {
$icons['icon192'] = $this->image->make($appIcon)->resize(192, 192)->url('png') . '?_v=' . $hash;
$icons['icon180'] = $this->image->make($appIcon)->resize(180, 180)->url('png') . '?_v=' . $hash;;
$icons['icon152'] = $this->image->make($appIcon)->resize(152, 152)->url('png') . '?_v=' . $hash;;
$icons['icon180'] = $this->image->make($appIcon)->resize(180, 180)->url('png') . '?_v=' . $hash;
;
$icons['icon152'] = $this->image->make($appIcon)->resize(152, 152)->url('png') . '?_v=' . $hash;
;
$icons['icon120'] = $this->image->make($appIcon)->resize(120, 120)->url('png') . '?_v=' . $hash;
$appIcon = $this->image->make($appIcon)->resize(null, 64)->url('png') . '?_v=' . $hash;
}

View File

@@ -0,0 +1,133 @@
<?php
namespace Tests\Unit\Framework\ImageTool;
use Openguru\OpenCartFramework\ImageTool\ImageFactory;
use Openguru\OpenCartFramework\ImageTool\ImageNotFoundException;
use Tests\TestCase;
class ImageFactoryTest extends TestCase
{
private string $tempDir;
private string $imageDir;
private string $siteUrl = 'http://localhost';
protected function setUp(): void
{
parent::setUp();
// Используем путь внутри контейнера или монтируемую папку, если это необходимо.
// Но sys_get_temp_dir() тоже должен работать внутри контейнера.
$this->tempDir = sys_get_temp_dir() . '/oc_test_' . uniqid();
$this->imageDir = $this->tempDir . '/images';
if (!mkdir($this->imageDir, 0755, true) && !is_dir($this->imageDir)) {
$this->markTestSkipped('Cannot create temp dir');
}
// 1x1 прозрачный пиксель
$imageContent = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=');
file_put_contents($this->imageDir . '/test.png', $imageContent);
file_put_contents($this->imageDir . '/no_image.png', $imageContent);
}
protected function tearDown(): void
{
$this->recursiveRemove($this->tempDir);
parent::tearDown();
}
private function recursiveRemove($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir . "/" . $object))
$this->recursiveRemove($dir . "/" . $object);
else
unlink($dir . "/" . $object);
}
}
rmdir($dir);
}
}
public function testUrlGenerationLtrimBug()
{
// Создаем подпапку catalog
$subDir = $this->imageDir . '/catalog';
mkdir($subDir, 0755, true);
copy($this->imageDir . '/test.png', $subDir . '/test.png');
$factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd');
// Ожидание: http://localhost/image/cache/catalog/test_...
$url = $factory->make('catalog/test.png')->url();
// Проверяем, что 'cache' не пострадал от ltrim
$this->assertStringContainsString('/image/cache/catalog/test', $url, 'URL corrupted: ' . $url);
}
public function testFilenameLengthAndDuplication()
{
$factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd');
$factory->make('test.png')->resize(100, 100);
$url = $factory->url();
$pathInfo = pathinfo($url);
$filename = $pathInfo['filename'];
// Теперь имя файла НЕ должно содержать явного resize_100x100, только хеш
// $this->assertStringNotContainsString('resize_100x100', $filename);
// Проверяем хеш (первые 12 символов SHA256)
$expectedHash = substr(hash('SHA256', json_encode(['resize' => ['width' => 100, 'height' => 100]])), 0, 12);
$this->assertStringContainsString($expectedHash, $filename);
$this->assertFileExists($this->imageDir . '/cache/test_' . $expectedHash . '.webp');
}
public function testMissingImageFallback()
{
$factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd');
// Запрашиваем несуществующую картинку
$url = $factory->make('non_existent.png')->url();
// Должен вернуть no_image
// Так как путь к no_image = no_image.png, а модификаций нет,
// кеш файл будет no_image.webp (по умолчанию формат webp)
$this->assertStringContainsString('no_image', $url);
// Проверяем, что сам файл создался (конвертация в webp)
$this->assertFileExists($this->imageDir . '/cache/no_image.webp');
}
public function testMissingFallbackImageSafeFail()
{
// Удаляем no_image.png
unlink($this->imageDir . '/no_image.png');
// Теперь ImageFactory не должен падать с Exception в конструкторе/make,
// но ensureFileExists вызовется.
// Подождите, в моей реализации я добавил:
// if (file_exists($this->fullPath)) { $this->ensureFileExists($this->fullPath); }
// То есть если файла нет И фоллбека нет, то $path останется 'no_image.png', fullPath = .../no_image.png
// И file_exists вернет false.
// И ensureFileExists НЕ вызовется.
// И вернется объект.
$factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd');
$obj = $factory->make('non_existent.png');
$this->assertNotNull($obj);
// Но при попытке сделать url() -> make() -> ImageManager попытается открыть файл и упадет?
// $this->image = $this->manager->make($this->fullPath);
// Intervention Image кинет исключение, если файла нет.
$this->expectException(\Intervention\Image\Exception\NotReadableException::class);
$obj->url();
}
}