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, 'url' => '/media/abc123.webp', 'file' => '/media/abc123.webp', ]); } /** * upload() doit utiliser 0 comme identifiant utilisateur de secours si la session ne contient pas d'utilisateur. */ public function testUploadUsesZeroAsFallbackUserId(): void { $file = $this->makeValidUploadedFile(); $this->sessionManager->method('getUserId')->willReturn(null); $this->mediaService->expects($this->once()) ->method('store') ->with($file, 0) ->willReturn('/media/fallback-user.webp'); $req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]); $res = $this->controller->upload($req, $this->makeResponse()); $this->assertStatus($res, 200); $this->assertJsonContains($res, [ 'success' => true, 'url' => '/media/fallback-user.webp', 'file' => '/media/fallback-user.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; } }