first commit
This commit is contained in:
311
tests/Media/MediaControllerTest.php
Normal file
311
tests/Media/MediaControllerTest.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Exception\FileTooLargeException;
|
||||
use App\Media\Exception\InvalidMimeTypeException;
|
||||
use App\Media\Exception\StorageException;
|
||||
use App\Media\Media;
|
||||
use App\Media\MediaController;
|
||||
use App\Media\MediaServiceInterface;
|
||||
use App\Shared\Http\FlashServiceInterface;
|
||||
use App\Shared\Http\SessionManagerInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Tests\ControllerTestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour MediaController.
|
||||
*
|
||||
* Couvre index(), upload() et delete() :
|
||||
* - index : filtrage admin vs utilisateur ordinaire
|
||||
* - upload : absence de fichier, erreur PSR-7, exceptions métier (taille, MIME, stockage), succès
|
||||
* - delete : introuvable, non-propriétaire, succès propriétaire, succès admin
|
||||
*/
|
||||
final class MediaControllerTest extends ControllerTestCase
|
||||
{
|
||||
/** @var \Slim\Views\Twig&MockObject */
|
||||
private \Slim\Views\Twig $view;
|
||||
|
||||
/** @var MediaServiceInterface&MockObject */
|
||||
private MediaServiceInterface $mediaService;
|
||||
|
||||
/** @var FlashServiceInterface&MockObject */
|
||||
private FlashServiceInterface $flash;
|
||||
|
||||
/** @var SessionManagerInterface&MockObject */
|
||||
private SessionManagerInterface $sessionManager;
|
||||
|
||||
private MediaController $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->view = $this->makeTwigMock();
|
||||
$this->mediaService = $this->createMock(MediaServiceInterface::class);
|
||||
$this->flash = $this->createMock(FlashServiceInterface::class);
|
||||
$this->sessionManager = $this->createMock(SessionManagerInterface::class);
|
||||
|
||||
$this->controller = new MediaController(
|
||||
$this->view,
|
||||
$this->mediaService,
|
||||
$this->flash,
|
||||
$this->sessionManager,
|
||||
);
|
||||
}
|
||||
|
||||
// ── index ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* index() doit appeler findAll() pour un admin et rendre la vue.
|
||||
*/
|
||||
public function testIndexShowsAllMediaForAdmin(): void
|
||||
{
|
||||
$this->sessionManager->method('isAdmin')->willReturn(true);
|
||||
$this->sessionManager->method('isEditor')->willReturn(false);
|
||||
|
||||
$this->mediaService->expects($this->once())->method('findAll')->willReturn([]);
|
||||
$this->mediaService->expects($this->never())->method('findByUserId');
|
||||
|
||||
$this->view->expects($this->once())
|
||||
->method('render')
|
||||
->with($this->anything(), 'admin/media/index.twig', $this->anything())
|
||||
->willReturnArgument(0);
|
||||
|
||||
$res = $this->controller->index($this->makeGet('/admin/media'), $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* index() doit appeler findAll() pour un éditeur.
|
||||
*/
|
||||
public function testIndexShowsAllMediaForEditor(): void
|
||||
{
|
||||
$this->sessionManager->method('isAdmin')->willReturn(false);
|
||||
$this->sessionManager->method('isEditor')->willReturn(true);
|
||||
|
||||
$this->mediaService->expects($this->once())->method('findAll')->willReturn([]);
|
||||
$this->mediaService->expects($this->never())->method('findByUserId');
|
||||
|
||||
$res = $this->controller->index($this->makeGet('/admin/media'), $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* index() doit appeler findByUserId() pour un utilisateur ordinaire.
|
||||
*/
|
||||
public function testIndexShowsOwnMediaForRegularUser(): void
|
||||
{
|
||||
$this->sessionManager->method('isAdmin')->willReturn(false);
|
||||
$this->sessionManager->method('isEditor')->willReturn(false);
|
||||
$this->sessionManager->method('getUserId')->willReturn(42);
|
||||
|
||||
$this->mediaService->expects($this->once())->method('findByUserId')->with(42)->willReturn([]);
|
||||
$this->mediaService->expects($this->never())->method('findAll');
|
||||
|
||||
$this->controller->index($this->makeGet('/admin/media'), $this->makeResponse());
|
||||
}
|
||||
|
||||
// ── upload ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* upload() doit retourner 400 JSON si aucun fichier n'est dans la requête.
|
||||
*/
|
||||
public function testUploadReturns400WhenNoFilePresent(): void
|
||||
{
|
||||
$req = $this->makePost('/admin/media/upload');
|
||||
$res = $this->controller->upload($req, $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 400);
|
||||
$this->assertJsonContentType($res);
|
||||
$this->assertJsonContains($res, ['error' => "Aucun fichier reçu ou erreur d'upload"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* upload() doit retourner 400 JSON si le fichier PSR-7 signale une erreur d'upload.
|
||||
*/
|
||||
public function testUploadReturns400WhenFileHasUploadError(): void
|
||||
{
|
||||
/** @var UploadedFileInterface&MockObject $file */
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getError')->willReturn(UPLOAD_ERR_INI_SIZE);
|
||||
|
||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
||||
$res = $this->controller->upload($req, $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 400);
|
||||
$this->assertJsonContains($res, ['error' => "Aucun fichier reçu ou erreur d'upload"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* upload() doit retourner 413 JSON si le fichier dépasse la taille autorisée.
|
||||
*/
|
||||
public function testUploadReturns413OnFileTooLarge(): void
|
||||
{
|
||||
$file = $this->makeValidUploadedFile();
|
||||
$this->mediaService->method('store')
|
||||
->willThrowException(new FileTooLargeException(2 * 1024 * 1024));
|
||||
|
||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
||||
$res = $this->controller->upload($req, $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 413);
|
||||
$this->assertJsonContentType($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* upload() doit retourner 415 JSON si le type MIME n'est pas autorisé.
|
||||
*/
|
||||
public function testUploadReturns415OnInvalidMimeType(): void
|
||||
{
|
||||
$file = $this->makeValidUploadedFile();
|
||||
$this->mediaService->method('store')
|
||||
->willThrowException(new InvalidMimeTypeException('application/pdf'));
|
||||
|
||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
||||
$res = $this->controller->upload($req, $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 415);
|
||||
$this->assertJsonContentType($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* upload() doit retourner 500 JSON si une erreur de stockage survient.
|
||||
*/
|
||||
public function testUploadReturns500OnStorageException(): void
|
||||
{
|
||||
$file = $this->makeValidUploadedFile();
|
||||
$this->mediaService->method('store')
|
||||
->willThrowException(new StorageException('Disk full'));
|
||||
|
||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
||||
$res = $this->controller->upload($req, $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 500);
|
||||
$this->assertJsonContentType($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* upload() doit retourner 200 JSON avec l'URL du fichier en cas de succès.
|
||||
*/
|
||||
public function testUploadReturns200JsonWithUrlOnSuccess(): void
|
||||
{
|
||||
$file = $this->makeValidUploadedFile();
|
||||
$this->sessionManager->method('getUserId')->willReturn(1);
|
||||
$this->mediaService->method('store')->willReturn('/media/abc123.webp');
|
||||
|
||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
||||
$res = $this->controller->upload($req, $this->makeResponse());
|
||||
|
||||
$this->assertStatus($res, 200);
|
||||
$this->assertJsonContentType($res);
|
||||
$this->assertJsonContains($res, ['success' => true, 'file' => '/media/abc123.webp']);
|
||||
}
|
||||
|
||||
// ── delete ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* delete() doit flasher une erreur et rediriger si le média est introuvable.
|
||||
*/
|
||||
public function testDeleteRedirectsWithErrorWhenMediaNotFound(): void
|
||||
{
|
||||
$this->mediaService->method('findById')->willReturn(null);
|
||||
|
||||
$this->flash->expects($this->once())->method('set')
|
||||
->with('media_error', 'Fichier introuvable');
|
||||
|
||||
$res = $this->controller->delete(
|
||||
$this->makePost('/admin/media/delete/99'),
|
||||
$this->makeResponse(),
|
||||
['id' => '99'],
|
||||
);
|
||||
|
||||
$this->assertRedirectTo($res, '/admin/media');
|
||||
}
|
||||
|
||||
/**
|
||||
* delete() doit flasher une erreur si l'utilisateur n'est pas propriétaire du média.
|
||||
*/
|
||||
public function testDeleteRedirectsWithErrorWhenNotOwner(): void
|
||||
{
|
||||
$media = new Media(5, 'file.webp', '/media/file.webp', 'abc', 10);
|
||||
$this->mediaService->method('findById')->willReturn($media);
|
||||
$this->sessionManager->method('isAdmin')->willReturn(false);
|
||||
$this->sessionManager->method('isEditor')->willReturn(false);
|
||||
$this->sessionManager->method('getUserId')->willReturn(99); // autre utilisateur
|
||||
|
||||
$this->flash->expects($this->once())->method('set')
|
||||
->with('media_error', $this->stringContains('autorisé'));
|
||||
|
||||
$res = $this->controller->delete(
|
||||
$this->makePost('/admin/media/delete/5'),
|
||||
$this->makeResponse(),
|
||||
['id' => '5'],
|
||||
);
|
||||
|
||||
$this->assertRedirectTo($res, '/admin/media');
|
||||
}
|
||||
|
||||
/**
|
||||
* delete() doit supprimer le média et rediriger avec succès si l'utilisateur est propriétaire.
|
||||
*/
|
||||
public function testDeleteSucceedsForOwner(): void
|
||||
{
|
||||
$media = new Media(5, 'file.webp', '/media/file.webp', 'abc', 42);
|
||||
$this->mediaService->method('findById')->willReturn($media);
|
||||
$this->sessionManager->method('isAdmin')->willReturn(false);
|
||||
$this->sessionManager->method('isEditor')->willReturn(false);
|
||||
$this->sessionManager->method('getUserId')->willReturn(42);
|
||||
|
||||
$this->mediaService->expects($this->once())->method('delete')->with($media);
|
||||
$this->flash->expects($this->once())->method('set')
|
||||
->with('media_success', 'Fichier supprimé');
|
||||
|
||||
$res = $this->controller->delete(
|
||||
$this->makePost('/admin/media/delete/5'),
|
||||
$this->makeResponse(),
|
||||
['id' => '5'],
|
||||
);
|
||||
|
||||
$this->assertRedirectTo($res, '/admin/media');
|
||||
}
|
||||
|
||||
/**
|
||||
* delete() doit permettre la suppression à un admin même s'il n'est pas propriétaire.
|
||||
*/
|
||||
public function testDeleteSucceedsForAdmin(): void
|
||||
{
|
||||
$media = new Media(5, 'file.webp', '/media/file.webp', 'abc', 10);
|
||||
$this->mediaService->method('findById')->willReturn($media);
|
||||
$this->sessionManager->method('isAdmin')->willReturn(true);
|
||||
$this->sessionManager->method('isEditor')->willReturn(false);
|
||||
$this->sessionManager->method('getUserId')->willReturn(1); // admin, pas propriétaire
|
||||
|
||||
$this->mediaService->expects($this->once())->method('delete');
|
||||
|
||||
$res = $this->controller->delete(
|
||||
$this->makePost('/admin/media/delete/5'),
|
||||
$this->makeResponse(),
|
||||
['id' => '5'],
|
||||
);
|
||||
|
||||
$this->assertRedirectTo($res, '/admin/media');
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Crée un mock d'UploadedFileInterface sans erreur d'upload.
|
||||
*
|
||||
* @return UploadedFileInterface&MockObject
|
||||
*/
|
||||
private function makeValidUploadedFile(): UploadedFileInterface
|
||||
{
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getError')->willReturn(UPLOAD_ERR_OK);
|
||||
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
53
tests/Media/MediaModelTest.php
Normal file
53
tests/Media/MediaModelTest.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Media;
|
||||
use DateTime;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class MediaModelTest extends TestCase
|
||||
{
|
||||
public function testConstructAndGettersExposeMediaData(): void
|
||||
{
|
||||
$createdAt = new DateTime('2026-03-01 10:00:00');
|
||||
$media = new Media(8, 'file.webp', '/media/file.webp', 'abc123', 12, $createdAt);
|
||||
|
||||
self::assertSame(8, $media->getId());
|
||||
self::assertSame('file.webp', $media->getFilename());
|
||||
self::assertSame('/media/file.webp', $media->getUrl());
|
||||
self::assertSame('abc123', $media->getHash());
|
||||
self::assertSame(12, $media->getUserId());
|
||||
self::assertSame($createdAt, $media->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testFromArrayHydratesMedia(): void
|
||||
{
|
||||
$media = Media::fromArray([
|
||||
'id' => '9',
|
||||
'filename' => 'stored.webp',
|
||||
'url' => '/media/stored.webp',
|
||||
'hash' => 'hash-9',
|
||||
'user_id' => '3',
|
||||
'created_at' => '2026-03-02 12:30:00',
|
||||
]);
|
||||
|
||||
self::assertSame(9, $media->getId());
|
||||
self::assertSame('stored.webp', $media->getFilename());
|
||||
self::assertSame('/media/stored.webp', $media->getUrl());
|
||||
self::assertSame('hash-9', $media->getHash());
|
||||
self::assertSame(3, $media->getUserId());
|
||||
self::assertSame('2026-03-02 12:30:00', $media->getCreatedAt()->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function testCreatedAtDefaultsToNowWhenMissing(): void
|
||||
{
|
||||
$before = new DateTime('-2 seconds');
|
||||
$media = new Media(1, 'f.webp', '/media/f.webp', 'h', null);
|
||||
$after = new DateTime('+2 seconds');
|
||||
|
||||
self::assertGreaterThanOrEqual($before->getTimestamp(), $media->getCreatedAt()->getTimestamp());
|
||||
self::assertLessThanOrEqual($after->getTimestamp(), $media->getCreatedAt()->getTimestamp());
|
||||
}
|
||||
}
|
||||
336
tests/Media/MediaRepositoryTest.php
Normal file
336
tests/Media/MediaRepositoryTest.php
Normal file
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Media;
|
||||
use App\Media\MediaRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour MediaRepository.
|
||||
*
|
||||
* Vérifie que chaque méthode du dépôt construit le bon SQL,
|
||||
* lie les bons paramètres et retourne les bonnes valeurs.
|
||||
*
|
||||
* PDO et PDOStatement sont mockés pour isoler complètement
|
||||
* le dépôt de la base de données.
|
||||
*/
|
||||
final class MediaRepositoryTest extends TestCase
|
||||
{
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private MediaRepository $repository;
|
||||
|
||||
/**
|
||||
* Données représentant une ligne média en base de données.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $rowImage;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new MediaRepository($this->db);
|
||||
|
||||
$this->rowImage = [
|
||||
'id' => 1,
|
||||
'filename' => 'photo.webp',
|
||||
'url' => '/media/photo.webp',
|
||||
'hash' => str_repeat('a', 64),
|
||||
'user_id' => 2,
|
||||
'created_at' => '2024-06-01 10:00:00',
|
||||
];
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────
|
||||
|
||||
private function stmtForRead(array $rows = [], array|false $row = false): PDOStatement&MockObject
|
||||
{
|
||||
$stmt = $this->createMock(PDOStatement::class);
|
||||
$stmt->method('execute')->willReturn(true);
|
||||
$stmt->method('fetchAll')->willReturn($rows);
|
||||
$stmt->method('fetch')->willReturn($row);
|
||||
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
private function stmtForWrite(int $rowCount = 1): PDOStatement&MockObject
|
||||
{
|
||||
$stmt = $this->createMock(PDOStatement::class);
|
||||
$stmt->method('execute')->willReturn(true);
|
||||
$stmt->method('rowCount')->willReturn($rowCount);
|
||||
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
|
||||
// ── findAll ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* findAll() retourne un tableau vide si aucun média n'existe.
|
||||
*/
|
||||
public function testFindAllReturnsEmptyArrayWhenNone(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
$this->db->method('query')->willReturn($stmt);
|
||||
|
||||
$this->assertSame([], $this->repository->findAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* findAll() retourne des instances Media hydratées.
|
||||
*/
|
||||
public function testFindAllReturnsMediaInstances(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([$this->rowImage]);
|
||||
$this->db->method('query')->willReturn($stmt);
|
||||
|
||||
$result = $this->repository->findAll();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertInstanceOf(Media::class, $result[0]);
|
||||
$this->assertSame('photo.webp', $result[0]->getFilename());
|
||||
$this->assertSame('/media/photo.webp', $result[0]->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* findAll() interroge la table 'media' triée par id DESC.
|
||||
*/
|
||||
public function testFindAllQueriesWithDescendingOrder(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('media'),
|
||||
$this->stringContains('id DESC'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$this->repository->findAll();
|
||||
}
|
||||
|
||||
|
||||
// ── findByUserId ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* findByUserId() retourne un tableau vide si l'utilisateur n'a aucun média.
|
||||
*/
|
||||
public function testFindByUserIdReturnsEmptyArrayWhenNone(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$this->assertSame([], $this->repository->findByUserId(99));
|
||||
}
|
||||
|
||||
/**
|
||||
* findByUserId() retourne uniquement les médias de l'utilisateur donné.
|
||||
*/
|
||||
public function testFindByUserIdReturnsUserMedia(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([$this->rowImage]);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$result = $this->repository->findByUserId(2);
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertSame(2, $result[0]->getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* findByUserId() exécute avec le bon user_id.
|
||||
*/
|
||||
public function testFindByUserIdQueriesWithCorrectUserId(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':user_id' => 5]);
|
||||
|
||||
$this->repository->findByUserId(5);
|
||||
}
|
||||
|
||||
|
||||
// ── findById ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* findById() retourne null si le média est absent.
|
||||
*/
|
||||
public function testFindByIdReturnsNullWhenMissing(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead(row: false);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$this->assertNull($this->repository->findById(99));
|
||||
}
|
||||
|
||||
/**
|
||||
* findById() retourne une instance Media si le média existe.
|
||||
*/
|
||||
public function testFindByIdReturnsMediaWhenFound(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead(row: $this->rowImage);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$result = $this->repository->findById(1);
|
||||
|
||||
$this->assertInstanceOf(Media::class, $result);
|
||||
$this->assertSame(1, $result->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* findById() exécute avec le bon identifiant.
|
||||
*/
|
||||
public function testFindByIdQueriesWithCorrectId(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead(row: false);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 8]);
|
||||
|
||||
$this->repository->findById(8);
|
||||
}
|
||||
|
||||
|
||||
// ── findByHash ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* findByHash() retourne null si aucun média ne correspond au hash.
|
||||
*/
|
||||
public function testFindByHashReturnsNullWhenMissing(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead(row: false);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$this->assertNull($this->repository->findByHash(str_repeat('b', 64)));
|
||||
}
|
||||
|
||||
/**
|
||||
* findByHash() retourne une instance Media si le hash existe (doublon détecté).
|
||||
*/
|
||||
public function testFindByHashReturnsDuplicateMedia(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead(row: $this->rowImage);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$result = $this->repository->findByHash(str_repeat('a', 64));
|
||||
|
||||
$this->assertInstanceOf(Media::class, $result);
|
||||
$this->assertSame(str_repeat('a', 64), $result->getHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* findByHash() exécute avec le bon hash.
|
||||
*/
|
||||
public function testFindByHashQueriesWithCorrectHash(): void
|
||||
{
|
||||
$hash = str_repeat('c', 64);
|
||||
$stmt = $this->stmtForRead(row: false);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':hash' => $hash]);
|
||||
|
||||
$this->repository->findByHash($hash);
|
||||
}
|
||||
|
||||
|
||||
// ── create ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* create() prépare un INSERT avec les bonnes colonnes.
|
||||
*/
|
||||
public function testCreateCallsInsertWithCorrectData(): void
|
||||
{
|
||||
$media = Media::fromArray($this->rowImage);
|
||||
$stmt = $this->stmtForWrite();
|
||||
|
||||
$this->db->method('prepare')
|
||||
->with($this->stringContains('INSERT INTO media'))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with($this->callback(function (array $data) use ($media): bool {
|
||||
return $data[':filename'] === $media->getFilename()
|
||||
&& $data[':url'] === $media->getUrl()
|
||||
&& $data[':hash'] === $media->getHash()
|
||||
&& $data[':user_id'] === $media->getUserId()
|
||||
&& isset($data[':created_at']);
|
||||
}));
|
||||
|
||||
$this->db->method('lastInsertId')->willReturn('1');
|
||||
|
||||
$this->repository->create($media);
|
||||
}
|
||||
|
||||
/**
|
||||
* create() retourne l'identifiant généré par la base de données.
|
||||
*/
|
||||
public function testCreateReturnsGeneratedId(): void
|
||||
{
|
||||
$media = Media::fromArray($this->rowImage);
|
||||
$stmt = $this->stmtForWrite();
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
$this->db->method('lastInsertId')->willReturn('15');
|
||||
|
||||
$this->assertSame(15, $this->repository->create($media));
|
||||
}
|
||||
|
||||
|
||||
// ── delete ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* delete() prépare un DELETE avec le bon identifiant.
|
||||
*/
|
||||
public function testDeleteCallsDeleteWithCorrectId(): void
|
||||
{
|
||||
$stmt = $this->stmtForWrite(1);
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('DELETE FROM media'))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 4]);
|
||||
|
||||
$this->repository->delete(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete() retourne le nombre de lignes supprimées.
|
||||
*/
|
||||
public function testDeleteReturnsDeletedRowCount(): void
|
||||
{
|
||||
$stmt = $this->stmtForWrite(1);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$this->assertSame(1, $this->repository->delete(4));
|
||||
}
|
||||
|
||||
/**
|
||||
* delete() retourne 0 si le média n'existait plus.
|
||||
*/
|
||||
public function testDeleteReturnsZeroWhenNotFound(): void
|
||||
{
|
||||
$stmt = $this->stmtForWrite(0);
|
||||
$this->db->method('prepare')->willReturn($stmt);
|
||||
|
||||
$this->assertSame(0, $this->repository->delete(99));
|
||||
}
|
||||
}
|
||||
90
tests/Media/MediaServiceDuplicateAfterInsertRaceTest.php
Normal file
90
tests/Media/MediaServiceDuplicateAfterInsertRaceTest.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Media;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\MediaService;
|
||||
use PDOException;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
final class MediaServiceDuplicateAfterInsertRaceTest extends TestCase
|
||||
{
|
||||
/** @var MediaRepositoryInterface&MockObject */
|
||||
private MediaRepositoryInterface $repository;
|
||||
|
||||
private string $uploadDir;
|
||||
|
||||
private MediaService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(MediaRepositoryInterface::class);
|
||||
$this->uploadDir = sys_get_temp_dir() . '/slim_media_race_' . uniqid('', true);
|
||||
@mkdir($this->uploadDir, 0755, true);
|
||||
|
||||
$this->service = new MediaService($this->repository, $this->uploadDir, '/media', 5 * 1024 * 1024);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
foreach (glob($this->uploadDir . '/*') ?: [] as $file) {
|
||||
@unlink($file);
|
||||
}
|
||||
@rmdir($this->uploadDir);
|
||||
}
|
||||
|
||||
public function testReturnsDuplicateUrlWhenInsertRaceOccurs(): void
|
||||
{
|
||||
$tmpFile = $this->createMinimalGif();
|
||||
$hash = hash_file('sha256', $tmpFile);
|
||||
self::assertNotFalse($hash);
|
||||
|
||||
$duplicate = new Media(77, 'existing.gif', '/media/existing.gif', $hash, 1);
|
||||
|
||||
$this->repository->expects($this->exactly(2))
|
||||
->method('findByHash')
|
||||
->with($hash)
|
||||
->willReturnOnConsecutiveCalls(null, $duplicate);
|
||||
|
||||
$this->repository->expects($this->once())
|
||||
->method('create')
|
||||
->willThrowException(new PDOException('duplicate key'));
|
||||
|
||||
$file = $this->makeUploadedFileFromPath($tmpFile, filesize($tmpFile));
|
||||
$url = $this->service->store($file, 1);
|
||||
|
||||
self::assertSame('/media/existing.gif', $url);
|
||||
self::assertCount(0, glob($this->uploadDir . '/*') ?: []);
|
||||
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface
|
||||
{
|
||||
$stream = $this->createMock(StreamInterface::class);
|
||||
$stream->method('getMetadata')->with('uri')->willReturn($path);
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn($size);
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
$file->method('moveTo')->willReturnCallback(static function (string $dest) use ($path): void {
|
||||
copy($path, $dest);
|
||||
});
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function createMinimalGif(): string
|
||||
{
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'slim_gif_');
|
||||
self::assertNotFalse($tmpFile);
|
||||
file_put_contents($tmpFile, base64_decode('R0lGODdhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='));
|
||||
|
||||
return $tmpFile;
|
||||
}
|
||||
}
|
||||
45
tests/Media/MediaServiceEdgeCasesTest.php
Normal file
45
tests/Media/MediaServiceEdgeCasesTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\MediaService;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\Exception\FileTooLargeException;
|
||||
use App\Media\Exception\StorageException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
final class MediaServiceEdgeCasesTest extends TestCase
|
||||
{
|
||||
public function testRejectsWhenSizeUnknown(): void
|
||||
{
|
||||
$repo = $this->createMock(MediaRepositoryInterface::class);
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn(null);
|
||||
|
||||
$service = new MediaService($repo, '/tmp', '/media', 1000);
|
||||
|
||||
$this->expectException(StorageException::class);
|
||||
$service->store($file, 1);
|
||||
}
|
||||
|
||||
public function testRejectsWhenFileTooLarge(): void
|
||||
{
|
||||
$repo = $this->createMock(MediaRepositoryInterface::class);
|
||||
|
||||
$stream = $this->createMock(StreamInterface::class);
|
||||
$stream->method('getMetadata')->willReturn('/tmp/file');
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn(999999);
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
|
||||
$service = new MediaService($repo, '/tmp', '/media', 100);
|
||||
|
||||
$this->expectException(FileTooLargeException::class);
|
||||
$service->store($file, 1);
|
||||
}
|
||||
}
|
||||
40
tests/Media/MediaServiceInvalidMimeTest.php
Normal file
40
tests/Media/MediaServiceInvalidMimeTest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Exception\InvalidMimeTypeException;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\MediaService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
final class MediaServiceInvalidMimeTest extends TestCase
|
||||
{
|
||||
public function testRejectsNonImageContentEvenWithImageLikeFilename(): void
|
||||
{
|
||||
$repo = $this->createMock(MediaRepositoryInterface::class);
|
||||
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'upload_');
|
||||
self::assertNotFalse($tmpFile);
|
||||
file_put_contents($tmpFile, 'not an image');
|
||||
|
||||
$stream = $this->createMock(StreamInterface::class);
|
||||
$stream->method('getMetadata')->with('uri')->willReturn($tmpFile);
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn(filesize($tmpFile));
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
$file->method('getClientFilename')->willReturn('photo.png');
|
||||
|
||||
$service = new MediaService($repo, sys_get_temp_dir(), '/media', 500000);
|
||||
|
||||
try {
|
||||
$this->expectException(InvalidMimeTypeException::class);
|
||||
$service->store($file, 1);
|
||||
} finally {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
tests/Media/MediaServiceInvalidTempPathTest.php
Normal file
33
tests/Media/MediaServiceInvalidTempPathTest.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Exception\StorageException;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\MediaService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
final class MediaServiceInvalidTempPathTest extends TestCase
|
||||
{
|
||||
public function testRejectsWhenTemporaryPathIsMissing(): void
|
||||
{
|
||||
$repository = $this->createMock(MediaRepositoryInterface::class);
|
||||
|
||||
$stream = $this->createMock(StreamInterface::class);
|
||||
$stream->method('getMetadata')->with('uri')->willReturn(null);
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn(128);
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
|
||||
$service = new MediaService($repository, sys_get_temp_dir(), '/media', 500000);
|
||||
|
||||
$this->expectException(StorageException::class);
|
||||
$this->expectExceptionMessage('Impossible de localiser le fichier temporaire uploadé');
|
||||
|
||||
$service->store($file, 1);
|
||||
}
|
||||
}
|
||||
256
tests/Media/MediaServiceTest.php
Normal file
256
tests/Media/MediaServiceTest.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Exception\FileTooLargeException;
|
||||
use App\Media\Exception\InvalidMimeTypeException;
|
||||
use App\Media\Media;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\MediaService;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour MediaService.
|
||||
*
|
||||
* Stratégie : les opérations sur le système de fichiers réel (finfo, GD,
|
||||
* copy, moveTo) sont exercées via de vrais fichiers JPEG temporaires ;
|
||||
* le repository reste un mock pour isoler la logique de persistance.
|
||||
*
|
||||
* Les cas couverts :
|
||||
* - rejet si taille > maxSize
|
||||
* - rejet si type MIME non autorisé
|
||||
* - déduplication : retour de l'URL existante si hash déjà connu
|
||||
* - stockage : fichier écrit sur disque, media créé en base
|
||||
* - suppression : fichier supprimé du disque et entrée retirée de la base
|
||||
*/
|
||||
final class MediaServiceTest extends TestCase
|
||||
{
|
||||
/** @var MediaRepositoryInterface&MockObject */
|
||||
private MediaRepositoryInterface $repository;
|
||||
|
||||
private string $uploadDir;
|
||||
|
||||
private MediaService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(MediaRepositoryInterface::class);
|
||||
$this->uploadDir = sys_get_temp_dir() . '/slim_media_test_' . uniqid();
|
||||
@mkdir($this->uploadDir, 0755, true);
|
||||
|
||||
$this->service = new MediaService(
|
||||
mediaRepository: $this->repository,
|
||||
uploadDir: $this->uploadDir,
|
||||
uploadUrl: '/media',
|
||||
maxSize: 5 * 1024 * 1024,
|
||||
);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Nettoyage du répertoire temporaire
|
||||
foreach (glob($this->uploadDir . '/*') ?: [] as $file) {
|
||||
@unlink($file);
|
||||
}
|
||||
@rmdir($this->uploadDir);
|
||||
}
|
||||
|
||||
|
||||
// ── store — rejets ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* store() doit lever FileTooLargeException si la taille dépasse le maximum.
|
||||
*/
|
||||
public function testStoreThrowsFileTooLargeWhenOversized(): void
|
||||
{
|
||||
$file = $this->makeUploadedFile(size: 6 * 1024 * 1024);
|
||||
|
||||
$this->expectException(FileTooLargeException::class);
|
||||
|
||||
$this->service->store($file, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* store() doit lever InvalidMimeTypeException pour un type MIME non autorisé.
|
||||
*/
|
||||
public function testStoreThrowsInvalidMimeType(): void
|
||||
{
|
||||
// Créer un vrai fichier texte — finfo retournera text/plain
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'slim_test_');
|
||||
file_put_contents($tmpFile, 'ceci est un fichier texte');
|
||||
|
||||
$file = $this->makeUploadedFileFromPath($tmpFile, filesize($tmpFile));
|
||||
|
||||
$this->expectException(InvalidMimeTypeException::class);
|
||||
|
||||
try {
|
||||
$this->service->store($file, 1);
|
||||
} finally {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ── store — déduplication ──────────────────────────────────────
|
||||
|
||||
/**
|
||||
* store() doit retourner l'URL existante sans créer de doublon si le hash est connu.
|
||||
*/
|
||||
public function testStoreReturnsDuplicateUrl(): void
|
||||
{
|
||||
$tmpFile = $this->createMinimalJpeg();
|
||||
$hash = hash_file('sha256', $tmpFile);
|
||||
|
||||
$existing = new Media(7, 'existing.jpg', '/media/existing.jpg', $hash, 1);
|
||||
$this->repository->method('findByHash')->willReturn($existing);
|
||||
$this->repository->expects($this->never())->method('create');
|
||||
|
||||
$file = $this->makeUploadedFileFromPath($tmpFile, filesize($tmpFile));
|
||||
$url = $this->service->store($file, 1);
|
||||
|
||||
$this->assertSame('/media/existing.jpg', $url);
|
||||
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
|
||||
// ── store — stockage nominal ───────────────────────────────────
|
||||
|
||||
/**
|
||||
* store() doit créer le fichier sur disque et appeler repository::create().
|
||||
*/
|
||||
public function testStoreWritesFileAndCreatesRecord(): void
|
||||
{
|
||||
$tmpFile = $this->createMinimalJpeg();
|
||||
|
||||
$this->repository->method('findByHash')->willReturn(null);
|
||||
$this->repository->expects($this->once())->method('create');
|
||||
|
||||
$file = $this->makeUploadedFileFromPath($tmpFile, filesize($tmpFile));
|
||||
$url = $this->service->store($file, 1);
|
||||
|
||||
$this->assertStringStartsWith('/media/', $url);
|
||||
|
||||
// Le fichier doit exister sur le disque
|
||||
$filename = basename($url);
|
||||
$this->assertFileExists($this->uploadDir . DIRECTORY_SEPARATOR . $filename);
|
||||
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
|
||||
// ── delete ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* delete() doit supprimer le fichier physique et appeler repository::delete().
|
||||
*/
|
||||
public function testDeleteRemovesFileAndRecord(): void
|
||||
{
|
||||
$filename = 'test_media.jpg';
|
||||
$filePath = $this->uploadDir . DIRECTORY_SEPARATOR . $filename;
|
||||
file_put_contents($filePath, 'fake image data');
|
||||
|
||||
$media = new Media(42, $filename, '/media/' . $filename, 'fakehash', 1);
|
||||
|
||||
$this->repository->expects($this->once())->method('delete')->with(42);
|
||||
|
||||
$this->service->delete($media);
|
||||
|
||||
$this->assertFileDoesNotExist($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete() ne doit pas lever d'exception si le fichier physique n'existe plus.
|
||||
*/
|
||||
public function testDeleteIsSilentWhenFileMissing(): void
|
||||
{
|
||||
$media = new Media(99, 'inexistant.jpg', '/media/inexistant.jpg', 'hash', 1);
|
||||
|
||||
$this->repository->expects($this->once())->method('delete')->with(99);
|
||||
|
||||
// Ne doit pas lever d'exception
|
||||
$this->service->delete($media);
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
|
||||
// ── Lectures déléguées ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* findById() doit déléguer au repository et retourner null si absent.
|
||||
*/
|
||||
public function testFindByIdReturnsNullWhenMissing(): void
|
||||
{
|
||||
$this->repository->method('findById')->willReturn(null);
|
||||
|
||||
$this->assertNull($this->service->findById(999));
|
||||
}
|
||||
|
||||
/**
|
||||
* findById() doit retourner le média trouvé.
|
||||
*/
|
||||
public function testFindByIdReturnsMedia(): void
|
||||
{
|
||||
$media = new Media(3, 'photo.jpg', '/media/photo.jpg', 'abc123', 1);
|
||||
$this->repository->method('findById')->with(3)->willReturn($media);
|
||||
|
||||
$this->assertSame($media, $this->service->findById(3));
|
||||
}
|
||||
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Crée un mock d'UploadedFile avec une taille donnée et un tmpPath bidon.
|
||||
*/
|
||||
private function makeUploadedFile(int $size): UploadedFileInterface
|
||||
{
|
||||
$stream = $this->createMock(StreamInterface::class);
|
||||
$stream->method('getMetadata')->with('uri')->willReturn('/nonexistent/path');
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn($size);
|
||||
$file->method('getError')->willReturn(UPLOAD_ERR_OK);
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un mock d'UploadedFile pointant vers un fichier réel.
|
||||
*/
|
||||
private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface
|
||||
{
|
||||
$stream = $this->createMock(StreamInterface::class);
|
||||
$stream->method('getMetadata')->with('uri')->willReturn($path);
|
||||
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn($size);
|
||||
$file->method('getError')->willReturn(UPLOAD_ERR_OK);
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
$file->method('moveTo')->willReturnCallback(function (string $dest) use ($path) {
|
||||
copy($path, $dest);
|
||||
});
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un fichier JPEG valide via GD (1×1 pixel).
|
||||
* Retourne le chemin du fichier temporaire.
|
||||
*/
|
||||
private function createMinimalJpeg(): string
|
||||
{
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'slim_jpeg_') . '.jpg';
|
||||
|
||||
$img = imagecreatetruecolor(1, 1);
|
||||
imagejpeg($img, $tmpFile);
|
||||
imagedestroy($img);
|
||||
|
||||
return $tmpFile;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user