From fd3f60805952ad2b37e4bcabc5a2532525dee1f0 Mon Sep 17 00:00:00 2001 From: julien Date: Mon, 16 Mar 2026 09:25:44 +0100 Subject: [PATCH] Working state --- .gitignore | 1 - src/Media/MediaController.php | 27 ++- tests/Auth/PasswordResetRepositoryTest.php | 4 +- tests/Auth/PasswordResetServiceTest.php | 186 +++++++----------- tests/Category/CategoryRepositoryTest.php | 2 +- tests/Media/MediaControllerTest.php | 30 +-- ...MediaControllerUploadCompatibilityTest.php | 117 ----------- tests/Media/MediaRepositoryTest.php | 2 +- tests/Media/MediaServiceTest.php | 4 +- tests/Post/PostExtensionTest.php | 15 -- tests/Post/PostRepositoryTest.php | 4 +- tests/Post/PostServiceTest.php | 48 +++++ tests/Shared/BootstrapTest.php | 61 +++--- tests/Shared/ClientIpResolverTest.php | 24 +-- tests/Shared/ConfigTest.php | 20 +- tests/Shared/ExtensionTest.php | 15 -- tests/Shared/FlashServiceTest.php | 21 +- tests/Shared/MailServiceTest.php | 27 +-- tests/Shared/NotFoundExceptionTest.php | 8 +- tests/Shared/ProvisionerTest.php | 55 +++--- tests/Shared/RoutesTest.php | 50 ++--- tests/Shared/SessionManagerEdgeCasesTest.php | 9 +- tests/User/UserRepositoryTest.php | 6 +- views/admin/posts/form.twig | 15 +- 24 files changed, 249 insertions(+), 502 deletions(-) delete mode 100644 tests/Media/MediaControllerUploadCompatibilityTest.php diff --git a/.gitignore b/.gitignore index f1b8e9a..f7ea1d3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ public/assets/ database/*.sqlite database/*.sqlite-shm database/*.sqlite-wal -database/.provision.lock # ============================================ # Cache & Logs diff --git a/src/Media/MediaController.php b/src/Media/MediaController.php index 4d615cf..dcca40e 100644 --- a/src/Media/MediaController.php +++ b/src/Media/MediaController.php @@ -87,7 +87,7 @@ final class MediaController public function upload(Request $req, Response $res): Response { $files = $req->getUploadedFiles(); - $uploadedFile = $files['file'] ?? $files['image'] ?? null; + $uploadedFile = $files['image'] ?? null; if ($uploadedFile === null || $uploadedFile->getError() !== UPLOAD_ERR_OK) { return $this->jsonError($res, "Aucun fichier reçu ou erreur d'upload", 400); @@ -103,10 +103,7 @@ final class MediaController return $this->jsonError($res, $e->getMessage(), 500); } - return $this->jsonOk($res, [ - 'url' => $url, - 'file' => $url, - ]); + return $this->jsonSuccess($res, $url); } /** @@ -148,21 +145,21 @@ final class MediaController } /** - * Retourne une réponse JSON de succès. + * Retourne une réponse JSON de succès avec l'URL du fichier uploadé. * - * @param Response $res La réponse HTTP - * @param array $data Données supplémentaires à fusionner + * @param Response $res La réponse HTTP + * @param string $fileUrl L'URL publique du fichier * - * @return Response La réponse JSON {"success": true, ...} + * @return Response La réponse JSON {"success": true, "file": "..."} */ - private function jsonOk(Response $res, array $data = []): Response + private function jsonSuccess(Response $res, string $fileUrl): Response { - $payload = json_encode(array_merge(['success' => true], $data), JSON_THROW_ON_ERROR); - $res->getBody()->write($payload); + $res->getBody()->write(json_encode([ + 'success' => true, + 'file' => $fileUrl, + ], JSON_THROW_ON_ERROR)); - return $res - ->withHeader('Content-Type', 'application/json') - ->withStatus(200); + return $res->withHeader('Content-Type', 'application/json')->withStatus(200); } /** diff --git a/tests/Auth/PasswordResetRepositoryTest.php b/tests/Auth/PasswordResetRepositoryTest.php index e534961..331aa2f 100644 --- a/tests/Auth/PasswordResetRepositoryTest.php +++ b/tests/Auth/PasswordResetRepositoryTest.php @@ -83,7 +83,7 @@ final class PasswordResetRepositoryTest extends TestCase public function testCreateSetsCreatedAt(): void { $stmt = $this->stmtOk(); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') @@ -104,7 +104,7 @@ final class PasswordResetRepositoryTest extends TestCase public function testFindActiveByHashReturnsNullWhenMissing(): void { $stmt = $this->stmtOk(false); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findActiveByHash('hashquinaexistepas')); } diff --git a/tests/Auth/PasswordResetServiceTest.php b/tests/Auth/PasswordResetServiceTest.php index f3b03b5..8f093b2 100644 --- a/tests/Auth/PasswordResetServiceTest.php +++ b/tests/Auth/PasswordResetServiceTest.php @@ -14,13 +14,8 @@ use PDO; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -/** - * Tests unitaires pour PasswordResetService. - * - * Vérifie la génération de token, la validation et la réinitialisation - * du mot de passe. Les dépendances sont mockées via leurs interfaces. - */ #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class PasswordResetServiceTest extends TestCase { /** @var PasswordResetRepositoryInterface&MockObject */ @@ -40,9 +35,9 @@ final class PasswordResetServiceTest extends TestCase protected function setUp(): void { $this->resetRepository = $this->createMock(PasswordResetRepositoryInterface::class); - $this->userRepository = $this->createMock(UserRepositoryInterface::class); - $this->mailService = $this->createMock(MailServiceInterface::class); - $this->db = $this->createMock(PDO::class); + $this->userRepository = $this->createMock(UserRepositoryInterface::class); + $this->mailService = $this->createMock(MailServiceInterface::class); + $this->db = $this->createMock(PDO::class); $this->service = new PasswordResetService( $this->resetRepository, @@ -52,13 +47,6 @@ final class PasswordResetServiceTest extends TestCase ); } - - // ── requestReset ─────────────────────────────────────────────── - - /** - * requestReset() avec un email inconnu ne doit ni envoyer d'email - * ni lever d'exception (protection contre l'énumération d'emails). - */ public function testRequestResetUnknownEmailReturnsSilently(): void { $this->userRepository->method('findByEmail')->willReturn(null); @@ -68,9 +56,6 @@ final class PasswordResetServiceTest extends TestCase $this->service->requestReset('inconnu@example.com', 'https://blog.exemple.com'); } - /** - * requestReset() doit invalider les tokens précédents avant d'en créer un nouveau. - */ public function testRequestResetInvalidatesPreviousTokens(): void { $user = $this->makeUser(); @@ -79,39 +64,39 @@ final class PasswordResetServiceTest extends TestCase $this->resetRepository->expects($this->once()) ->method('invalidateByUserId') ->with($user->getId()); - $this->resetRepository->method('create'); - $this->mailService->method('send'); + $this->resetRepository->expects($this->once()) + ->method('create'); + $this->mailService->expects($this->once()) + ->method('send'); $this->service->requestReset('alice@example.com', 'https://blog.exemple.com'); } - /** - * requestReset() doit persister un nouveau token en base. - */ public function testRequestResetCreatesTokenInDatabase(): void { $user = $this->makeUser(); $this->userRepository->method('findByEmail')->willReturn($user); - $this->resetRepository->method('invalidateByUserId'); + $this->resetRepository->expects($this->once()) + ->method('invalidateByUserId'); $this->resetRepository->expects($this->once()) ->method('create') ->with($user->getId(), $this->callback('is_string'), $this->callback('is_string')); - $this->mailService->method('send'); + $this->mailService->expects($this->once()) + ->method('send'); $this->service->requestReset('alice@example.com', 'https://blog.exemple.com'); } - /** - * requestReset() doit envoyer un email avec le bon destinataire et template. - */ public function testRequestResetSendsEmailWithCorrectAddress(): void { $user = $this->makeUser(); $this->userRepository->method('findByEmail')->willReturn($user); - $this->resetRepository->method('invalidateByUserId'); - $this->resetRepository->method('create'); + $this->resetRepository->expects($this->once()) + ->method('invalidateByUserId'); + $this->resetRepository->expects($this->once()) + ->method('create'); $this->mailService->expects($this->once()) ->method('send') @@ -125,16 +110,15 @@ final class PasswordResetServiceTest extends TestCase $this->service->requestReset('alice@example.com', 'https://blog.exemple.com'); } - /** - * L'URL de réinitialisation dans le contexte de l'email doit contenir le token brut. - */ public function testRequestResetUrlContainsToken(): void { $user = $this->makeUser(); $this->userRepository->method('findByEmail')->willReturn($user); - $this->resetRepository->method('invalidateByUserId'); - $this->resetRepository->method('create'); + $this->resetRepository->expects($this->once()) + ->method('invalidateByUserId'); + $this->resetRepository->expects($this->once()) + ->method('create'); $this->mailService->expects($this->once()) ->method('send') @@ -151,84 +135,81 @@ final class PasswordResetServiceTest extends TestCase $this->service->requestReset('alice@example.com', 'https://blog.exemple.com'); } - - // ── validateToken ────────────────────────────────────────────── - - /** - * validateToken() avec un token inexistant doit retourner null. - */ public function testValidateTokenMissingToken(): void { - $this->resetRepository->method('findActiveByHash')->willReturn(null); + $this->resetRepository->expects($this->once()) + ->method('findActiveByHash') + ->willReturn(null); $result = $this->service->validateToken('tokeninexistant'); $this->assertNull($result); } - /** - * validateToken() avec un token expiré doit retourner null. - */ public function testValidateTokenExpiredToken(): void { - $tokenRaw = 'montokenbrut'; + $tokenRaw = 'montokenbrut'; $tokenHash = hash('sha256', $tokenRaw); - $this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([ - 'user_id' => 1, - 'token_hash' => $tokenHash, - 'expires_at' => date('Y-m-d H:i:s', time() - 3600), - 'used_at' => null, - ]); + $this->resetRepository->expects($this->once()) + ->method('findActiveByHash') + ->with($tokenHash) + ->willReturn([ + 'user_id' => 1, + 'token_hash' => $tokenHash, + 'expires_at' => date('Y-m-d H:i:s', time() - 3600), + 'used_at' => null, + ]); $result = $this->service->validateToken($tokenRaw); $this->assertNull($result); } - /** - * validateToken() avec un token valide doit retourner l'utilisateur associé. - */ public function testValidateTokenValidToken(): void { - $user = $this->makeUser(); - $tokenRaw = 'montokenbrut'; + $user = $this->makeUser(); + $tokenRaw = 'montokenbrut'; $tokenHash = hash('sha256', $tokenRaw); - $this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([ - 'user_id' => $user->getId(), - 'token_hash' => $tokenHash, - 'expires_at' => date('Y-m-d H:i:s', time() + 3600), - 'used_at' => null, - ]); - $this->userRepository->expects($this->once())->method('findById')->with($user->getId())->willReturn($user); + $this->resetRepository->expects($this->once()) + ->method('findActiveByHash') + ->with($tokenHash) + ->willReturn([ + 'user_id' => $user->getId(), + 'token_hash' => $tokenHash, + 'expires_at' => date('Y-m-d H:i:s', time() + 3600), + 'used_at' => null, + ]); + $this->userRepository->expects($this->once()) + ->method('findById') + ->with($user->getId()) + ->willReturn($user); $result = $this->service->validateToken($tokenRaw); $this->assertSame($user, $result); } - - // ── resetPassword ────────────────────────────────────────────── - - /** - * resetPassword() avec un token invalide doit lever une InvalidArgumentException. - */ - /** - * validateToken() doit retourner null si le token est valide mais l'utilisateur a été supprimé. - */ public function testValidateTokenDeletedUserReturnsNull(): void { - $row = [ - 'user_id' => 999, - 'expires_at' => date('Y-m-d H:i:s', time() + 3600), - 'used_at' => null, - ]; + $tokenRaw = 'token-valide-mais-user-supprime'; + $tokenHash = hash('sha256', $tokenRaw); - $this->resetRepository->expects($this->once())->method('findActiveByHash')->willReturn($row); - $this->userRepository->expects($this->once())->method('findById')->with(999)->willReturn(null); + $this->resetRepository->expects($this->once()) + ->method('findActiveByHash') + ->with($tokenHash) + ->willReturn([ + 'user_id' => 999, + 'expires_at' => date('Y-m-d H:i:s', time() + 3600), + 'used_at' => null, + ]); + $this->userRepository->expects($this->once()) + ->method('findById') + ->with(999) + ->willReturn(null); - $result = $this->service->validateToken('token-valide-mais-user-supprime'); + $result = $this->service->validateToken($tokenRaw); $this->assertNull($result); } @@ -238,7 +219,7 @@ final class PasswordResetServiceTest extends TestCase $this->db->method('beginTransaction')->willReturn(true); $this->db->method('inTransaction')->willReturn(true); $this->db->expects($this->once())->method('rollBack'); - $this->resetRepository->method('consumeActiveToken')->willReturn(null); + $this->resetRepository->expects($this->once())->method('consumeActiveToken')->willReturn(null); $this->expectException(InvalidResetTokenException::class); $this->expectExceptionMessageMatches('/invalide ou a expiré/'); @@ -246,35 +227,17 @@ final class PasswordResetServiceTest extends TestCase $this->service->resetPassword('tokeninvalide', 'nouveaumdp1'); } - /** - * resetPassword() avec un mot de passe trop court doit lever WeakPasswordException. - */ public function testResetPasswordTooShortPasswordThrowsWeakPasswordException(): void { - $user = $this->makeUser(); - $tokenRaw = 'montokenbrut'; - $tokenHash = hash('sha256', $tokenRaw); - - $this->resetRepository->method('findActiveByHash')->willReturn([ - 'user_id' => $user->getId(), - 'token_hash' => $tokenHash, - 'expires_at' => date('Y-m-d H:i:s', time() + 3600), - 'used_at' => null, - ]); - $this->userRepository->method('findById')->willReturn($user); - $this->expectException(WeakPasswordException::class); - $this->service->resetPassword($tokenRaw, '1234567'); + $this->service->resetPassword('montokenbrut', '1234567'); } - /** - * resetPassword() doit mettre à jour le mot de passe et marquer le token comme consommé. - */ public function testResetPasswordUpdatesPasswordAndConsumesToken(): void { - $user = $this->makeUser(); - $tokenRaw = 'montokenbrut'; + $user = $this->makeUser(); + $tokenRaw = 'montokenbrut'; $tokenHash = hash('sha256', $tokenRaw); $this->db->method('beginTransaction')->willReturn(true); @@ -285,12 +248,15 @@ final class PasswordResetServiceTest extends TestCase ->method('consumeActiveToken') ->with($tokenHash, $this->callback('is_string')) ->willReturn([ - 'user_id' => $user->getId(), + 'user_id' => $user->getId(), 'token_hash' => $tokenHash, 'expires_at' => date('Y-m-d H:i:s', time() + 3600), - 'used_at' => null, + 'used_at' => null, ]); - $this->userRepository->method('findById')->willReturn($user); + $this->userRepository->expects($this->once()) + ->method('findById') + ->with($user->getId()) + ->willReturn($user); $this->userRepository->expects($this->once()) ->method('updatePassword') @@ -299,12 +265,6 @@ final class PasswordResetServiceTest extends TestCase $this->service->resetPassword($tokenRaw, 'nouveaumdp1'); } - - // ── Helpers ──────────────────────────────────────────────────── - - /** - * Crée un utilisateur de test standard. - */ private function makeUser(): User { return new User( diff --git a/tests/Category/CategoryRepositoryTest.php b/tests/Category/CategoryRepositoryTest.php index b70ba09..e4cb083 100644 --- a/tests/Category/CategoryRepositoryTest.php +++ b/tests/Category/CategoryRepositoryTest.php @@ -133,7 +133,7 @@ final class CategoryRepositoryTest extends TestCase public function testFindByIdReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findById(99)); } diff --git a/tests/Media/MediaControllerTest.php b/tests/Media/MediaControllerTest.php index 40e35a1..adf5e23 100644 --- a/tests/Media/MediaControllerTest.php +++ b/tests/Media/MediaControllerTest.php @@ -202,35 +202,7 @@ final class MediaControllerTest extends ControllerTestCase $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', - ]); + $this->assertJsonContains($res, ['success' => true, 'file' => '/media/abc123.webp']); } // ── delete ─────────────────────────────────────────────────────── diff --git a/tests/Media/MediaControllerUploadCompatibilityTest.php b/tests/Media/MediaControllerUploadCompatibilityTest.php deleted file mode 100644 index daa9e6d..0000000 --- a/tests/Media/MediaControllerUploadCompatibilityTest.php +++ /dev/null @@ -1,117 +0,0 @@ -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, - ); - } - - public function testUploadAcceptsFileFieldNameUsedByTrumbowyg(): void - { - $file = $this->makeValidUploadedFile(); - $this->sessionManager->method('getUserId')->willReturn(7); - $this->mediaService->expects($this->once()) - ->method('store') - ->with($file, 7) - ->willReturn('/media/from-file-field.webp'); - - $req = $this->makePost('/admin/media/upload')->withUploadedFiles(['file' => $file]); - $res = $this->controller->upload($req, $this->makeResponse()); - - $this->assertStatus($res, 200); - $this->assertJsonContains($res, [ - 'success' => true, - 'url' => '/media/from-file-field.webp', - 'file' => '/media/from-file-field.webp', - ]); - } - - - public function testUploadPrefersFileFieldWhenBothFileAndImageArePresent(): void - { - $fileField = $this->makeValidUploadedFile(); - $imageField = $this->makeValidUploadedFile(); - - $this->sessionManager->method('getUserId')->willReturn(11); - $this->mediaService->expects($this->once()) - ->method('store') - ->with($fileField, 11) - ->willReturn('/media/preferred-file-field.webp'); - - $req = $this->makePost('/admin/media/upload')->withUploadedFiles([ - 'file' => $fileField, - 'image' => $imageField, - ]); - $res = $this->controller->upload($req, $this->makeResponse()); - - $this->assertStatus($res, 200); - $this->assertJsonContains($res, [ - 'success' => true, - 'url' => '/media/preferred-file-field.webp', - 'file' => '/media/preferred-file-field.webp', - ]); - } - - public function testUploadSuccessResponseContainsBothUrlAndFileKeys(): void - { - $file = $this->makeValidUploadedFile(); - $this->sessionManager->method('getUserId')->willReturn(3); - $this->mediaService->method('store')->willReturn('/media/dual-key.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/dual-key.webp', - 'file' => '/media/dual-key.webp', - ]); - } - - /** @return UploadedFileInterface&MockObject */ - private function makeValidUploadedFile(): UploadedFileInterface - { - $file = $this->createMock(UploadedFileInterface::class); - $file->method('getError')->willReturn(UPLOAD_ERR_OK); - - return $file; - } -} diff --git a/tests/Media/MediaRepositoryTest.php b/tests/Media/MediaRepositoryTest.php index 6dacbf3..9619e6f 100644 --- a/tests/Media/MediaRepositoryTest.php +++ b/tests/Media/MediaRepositoryTest.php @@ -127,7 +127,7 @@ final class MediaRepositoryTest extends TestCase public function testFindByUserIdReturnsEmptyArrayWhenNone(): void { $stmt = $this->stmtForRead([]); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $this->assertSame([], $this->repository->findByUserId(99)); } diff --git a/tests/Media/MediaServiceTest.php b/tests/Media/MediaServiceTest.php index 2886746..e84b42e 100644 --- a/tests/Media/MediaServiceTest.php +++ b/tests/Media/MediaServiceTest.php @@ -211,7 +211,7 @@ final class MediaServiceTest extends TestCase private function makeUploadedFile(int $size): UploadedFileInterface { $stream = $this->createMock(StreamInterface::class); - $stream->method('getMetadata')->willReturnMap([['uri', '/nonexistent/path']]); + $stream->method('getMetadata')->willReturnCallback(static fn (?string $key = null): mixed => $key === 'uri' ? '/nonexistent/path' : null); $file = $this->createMock(UploadedFileInterface::class); $file->method('getSize')->willReturn($size); @@ -227,7 +227,7 @@ final class MediaServiceTest extends TestCase private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface { $stream = $this->createMock(StreamInterface::class); - $stream->method('getMetadata')->willReturnMap([['uri', $path]]); + $stream->expects($this->once())->method('getMetadata')->with('uri')->willReturn($path); $file = $this->createMock(UploadedFileInterface::class); $file->method('getSize')->willReturn($size); diff --git a/tests/Post/PostExtensionTest.php b/tests/Post/PostExtensionTest.php index 0f2a685..15b087e 100644 --- a/tests/Post/PostExtensionTest.php +++ b/tests/Post/PostExtensionTest.php @@ -59,21 +59,6 @@ final class PostExtensionTest extends TestCase self::assertNull($this->call('post_thumbnail', $post)); } - - public function testPostExcerptReturnsHtmlUnchangedWhenContentIsShortEnough(): void - { - $post = new Post(4, 'Titre', '

Bonjour monde

', 'titre-4'); - - self::assertSame('Bonjour monde', $this->call('post_excerpt', $post, 50)); - } - - public function testPostInitialsFallsBackToFirstRawCharacterWhenOnlyStopWordsRemain(): void - { - $post = new Post(5, 'de la', '

Contenu

', 'slug-5'); - - self::assertSame('D', $this->call('post_initials', $post)); - } - public function testPostInitialsUseMeaningfulWordsAndFallback(): void { $post = new Post(1, 'Article de Blog', '

Contenu

', 'slug'); diff --git a/tests/Post/PostRepositoryTest.php b/tests/Post/PostRepositoryTest.php index e2b2568..c50abb3 100644 --- a/tests/Post/PostRepositoryTest.php +++ b/tests/Post/PostRepositoryTest.php @@ -159,7 +159,7 @@ final class PostRepositoryTest extends TestCase public function testFindRecentReturnsEmptyArray(): void { $stmt = $this->stmtForRead([]); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $this->assertSame([], $this->repository->findRecent(5)); } @@ -170,7 +170,7 @@ final class PostRepositoryTest extends TestCase public function testFindRecentPassesLimitCorrectly(): void { $stmt = $this->stmtForRead([]); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('bindValue') diff --git a/tests/Post/PostServiceTest.php b/tests/Post/PostServiceTest.php index 4489a24..3bd808e 100644 --- a/tests/Post/PostServiceTest.php +++ b/tests/Post/PostServiceTest.php @@ -110,6 +110,18 @@ final class PostServiceTest extends TestCase } + + /** + * getPostById() retourne l'article trouvé. + */ + public function testGetPostByIdReturnsPost(): void + { + $post = $this->makePost(7, 'Titre', 'slug-7'); + $this->repository->expects($this->once())->method('findById')->with(7)->willReturn($post); + + self::assertSame($post, $this->service->getPostById(7)); + } + // ── createPost ───────────────────────────────────────────────── /** @@ -217,6 +229,42 @@ final class PostServiceTest extends TestCase } + + /** + * updatePost() lève NotFoundException si la ligne disparaît entre lecture et écriture. + */ + public function testUpdatePostThrowsWhenRepositoryUpdateAffectsZeroRows(): void + { + $post = $this->makePost(3, 'Titre courant', 'titre-courant', '

Ancien contenu

'); + $this->repository->expects($this->once())->method('findById')->with(3)->willReturn($post); + $this->sanitizer->method('sanitize')->willReturn('

Contenu sûr

'); + $this->repository->expects($this->once())->method('update')->with(3, $this->isInstanceOf(Post::class), 'titre-courant', null)->willReturn(0); + + $this->expectException(NotFoundException::class); + + $this->service->updatePost(3, 'Titre courant', '

Contenu

'); + } + + /** + * updatePost() normalise et rend unique un slug personnalisé. + */ + public function testUpdatePostUsesNormalizedUniqueCustomSlug(): void + { + $current = $this->makePost(4, 'Titre courant', 'ancien-slug', '

Ancien contenu

'); + $this->repository->expects($this->once())->method('findById')->with(4)->willReturn($current); + $this->sanitizer->method('sanitize')->willReturn('

Contenu sûr

'); + $this->repository->expects($this->exactly(2)) + ->method('slugExists') + ->withAnyParameters() + ->willReturnOnConsecutiveCalls(true, false); + $this->repository->expects($this->once()) + ->method('update') + ->with(4, $this->isInstanceOf(Post::class), 'nouveau-slug-1', 2) + ->willReturn(1); + + $this->service->updatePost(4, 'Titre courant', '

Contenu

', ' Nouveau slug !! ', 2); + } + // ── Helpers ──────────────────────────────────────────────────── /** diff --git a/tests/Shared/BootstrapTest.php b/tests/Shared/BootstrapTest.php index d3e15a7..db7db21 100644 --- a/tests/Shared/BootstrapTest.php +++ b/tests/Shared/BootstrapTest.php @@ -6,43 +6,43 @@ namespace Tests\Shared; use App\Shared\Bootstrap; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use ReflectionProperty; use Slim\Factory\AppFactory; +use Slim\App; #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class BootstrapTest extends TestCase { - /** @var array */ private array $envBackup = []; protected function setUp(): void { - $this->envBackup = $_ENV; + $this->envBackup = [ + 'APP_AUTO_PROVISION' => $_ENV['APP_AUTO_PROVISION'] ?? null, + ]; } protected function tearDown(): void { - $_ENV = $this->envBackup; + foreach ($this->envBackup as $key => $value) { + if ($value === null) { + unset($_ENV[$key]); + } else { + $_ENV[$key] = $value; + } + } } - public function testGetContainerReturnsPreloadedContainer(): void + public function testInitializeInfrastructureReturnsPreloadedContainer(): void { $bootstrap = Bootstrap::create(); - $container = new class implements ContainerInterface { - public function get(string $id): mixed - { - throw new \RuntimeException('Not expected'); - } + $container = $this->createStub(ContainerInterface::class); - public function has(string $id): bool - { - return false; - } - }; + $this->setPrivate($bootstrap, 'container', $container); - $this->setPrivateProperty($bootstrap, 'container', $container); - - self::assertSame($container, $bootstrap->getContainer()); self::assertSame($container, $bootstrap->initializeInfrastructure()); + self::assertSame($container, $bootstrap->getContainer()); } public function testCreateHttpAppReturnsPreloadedApp(): void @@ -50,40 +50,29 @@ final class BootstrapTest extends TestCase $bootstrap = Bootstrap::create(); $app = AppFactory::create(); - $this->setPrivateProperty($bootstrap, 'app', $app); + $this->setPrivate($bootstrap, 'app', $app); self::assertSame($app, $bootstrap->createHttpApp()); } - public function testInitializeReturnsPreloadedAppWhenAutoProvisioningDisabled(): void + public function testInitializeReturnsPreloadedAppWhenAutoProvisionIsDisabled(): void { - $_ENV['APP_ENV'] = 'production'; $_ENV['APP_AUTO_PROVISION'] = '0'; $bootstrap = Bootstrap::create(); - $container = new class implements ContainerInterface { - public function get(string $id): mixed - { - throw new \RuntimeException('Not expected'); - } - - public function has(string $id): bool - { - return false; - } - }; + $container = $this->createStub(ContainerInterface::class); $app = AppFactory::create(); - $this->setPrivateProperty($bootstrap, 'container', $container); - $this->setPrivateProperty($bootstrap, 'app', $app); + $this->setPrivate($bootstrap, 'container', $container); + $this->setPrivate($bootstrap, 'app', $app); self::assertSame($app, $bootstrap->initialize()); } - private function setPrivateProperty(object $object, string $property, mixed $value): void + private function setPrivate(Bootstrap $bootstrap, string $property, mixed $value): void { - $reflection = new \ReflectionProperty($object, $property); + $reflection = new ReflectionProperty($bootstrap, $property); $reflection->setAccessible(true); - $reflection->setValue($object, $value); + $reflection->setValue($bootstrap, $value); } } diff --git a/tests/Shared/ClientIpResolverTest.php b/tests/Shared/ClientIpResolverTest.php index 54b49a7..4706ec0 100644 --- a/tests/Shared/ClientIpResolverTest.php +++ b/tests/Shared/ClientIpResolverTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Slim\Psr7\Factory\ServerRequestFactory; #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class ClientIpResolverTest extends TestCase { public function testResolveReturnsDefaultWhenRemoteAddrMissing(): void @@ -53,27 +54,4 @@ final class ClientIpResolverTest extends TestCase self::assertSame('127.0.0.1', $resolver->resolve($request)); } - - public function testResolveReturnsRemoteAddrWhenTrustedProxyHasNoForwardedHeader(): void - { - $request = (new ServerRequestFactory())->createServerRequest('GET', '/', [ - 'REMOTE_ADDR' => '127.0.0.1', - ]); - - $resolver = new ClientIpResolver(['127.0.0.1']); - - self::assertSame('127.0.0.1', $resolver->resolve($request)); - } - - public function testResolveTrimsWhitespaceAroundRemoteAndForwardedAddresses(): void - { - $request = (new ServerRequestFactory())->createServerRequest('GET', '/', [ - 'REMOTE_ADDR' => ' 127.0.0.1 ', - 'HTTP_X_FORWARDED_FOR' => ' 203.0.113.10 , 198.51.100.12', - ]); - - $resolver = new ClientIpResolver(['*']); - - self::assertSame('203.0.113.10', $resolver->resolve($request)); - } } diff --git a/tests/Shared/ConfigTest.php b/tests/Shared/ConfigTest.php index c56f140..189bc69 100644 --- a/tests/Shared/ConfigTest.php +++ b/tests/Shared/ConfigTest.php @@ -7,6 +7,7 @@ use App\Shared\Config; use PHPUnit\Framework\TestCase; #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class ConfigTest extends TestCase { public function testGetTwigCacheReturnsFalseInDev(): void @@ -22,25 +23,6 @@ final class ConfigTest extends TestCase self::assertStringEndsWith('/var/cache/twig', $cachePath); } - public function testGetDatabasePathReturnsExistingFilePathUnchanged(): void - { - $dbFile = dirname(__DIR__, 2).'/database/app.sqlite'; - $dbDir = dirname($dbFile); - - if (!is_dir($dbDir)) { - mkdir($dbDir, 0755, true); - } - - if (!file_exists($dbFile)) { - touch($dbFile); - } - - $path = Config::getDatabasePath(); - - self::assertSame($dbFile, $path); - self::assertFileExists($dbFile); - } - public function testGetDatabasePathCreatesDatabaseFileWhenMissing(): void { $dbFile = dirname(__DIR__, 2).'/database/app.sqlite'; diff --git a/tests/Shared/ExtensionTest.php b/tests/Shared/ExtensionTest.php index f61e657..8ae3040 100644 --- a/tests/Shared/ExtensionTest.php +++ b/tests/Shared/ExtensionTest.php @@ -46,21 +46,6 @@ final class ExtensionTest extends TestCase ], $extension->getGlobals()); } - public function testSessionExtensionExposesNullDefaultsWhenSessionIsEmpty(): void - { - $_SESSION = []; - - $extension = new SessionExtension(); - - self::assertSame([ - 'session' => [ - 'user_id' => null, - 'username' => null, - 'role' => null, - ], - ], $extension->getGlobals()); - } - public function testCsrfExtensionExposesTokens(): void { $storage = []; diff --git a/tests/Shared/FlashServiceTest.php b/tests/Shared/FlashServiceTest.php index 0770eea..921c5af 100644 --- a/tests/Shared/FlashServiceTest.php +++ b/tests/Shared/FlashServiceTest.php @@ -7,6 +7,7 @@ use App\Shared\Http\FlashService; use PHPUnit\Framework\TestCase; #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class FlashServiceTest extends TestCase { protected function setUp(): void @@ -34,26 +35,6 @@ final class FlashServiceTest extends TestCase self::assertArrayNotHasKey('count', $_SESSION['flash']); } - public function testGetCastsBooleanFalseToEmptyStringAndRemovesIt(): void - { - $_SESSION['flash']['flag'] = false; - - $flash = new FlashService(); - - self::assertSame('', $flash->get('flag')); - self::assertArrayNotHasKey('flag', $_SESSION['flash']); - } - - public function testSetOverridesPreviousMessageForSameKey(): void - { - $flash = new FlashService(); - - $flash->set('notice', 'Premier'); - $flash->set('notice', 'Second'); - - self::assertSame('Second', $flash->get('notice')); - } - public function testGetReturnsNullWhenMissing(): void { $flash = new FlashService(); diff --git a/tests/Shared/MailServiceTest.php b/tests/Shared/MailServiceTest.php index 1114b7f..b11c7d3 100644 --- a/tests/Shared/MailServiceTest.php +++ b/tests/Shared/MailServiceTest.php @@ -6,17 +6,17 @@ namespace Tests\Shared; use App\Shared\Mail\MailService; use PHPMailer\PHPMailer\PHPMailer; use PHPUnit\Framework\TestCase; +use ReflectionMethod; use Slim\Views\Twig; use Twig\Loader\ArrayLoader; #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class MailServiceTest extends TestCase { public function testCreateMailerUsesSslConfiguration(): void { - $service = $this->createService('ssl', 465); - - /** @var PHPMailer $mailer */ + $service = $this->makeService('ssl', 465); $mailer = $this->invokeCreateMailer($service); self::assertSame('smtp', $mailer->Mailer); @@ -27,25 +27,23 @@ final class MailServiceTest extends TestCase self::assertSame(PHPMailer::ENCRYPTION_SMTPS, $mailer->SMTPSecure); self::assertSame(465, $mailer->Port); self::assertSame(PHPMailer::CHARSET_UTF8, $mailer->CharSet); - self::assertSame('noreply@example.test', $mailer->From); + self::assertSame('no-reply@example.test', $mailer->From); self::assertSame('Slim Blog', $mailer->FromName); } public function testCreateMailerUsesStartTlsWhenEncryptionIsNotSsl(): void { - $service = $this->createService('tls', 587); - - /** @var PHPMailer $mailer */ + $service = $this->makeService('tls', 587); $mailer = $this->invokeCreateMailer($service); self::assertSame(PHPMailer::ENCRYPTION_STARTTLS, $mailer->SMTPSecure); self::assertSame(587, $mailer->Port); } - private function createService(string $encryption, int $port): MailService + private function makeService(string $encryption, int $port): MailService { $twig = new Twig(new ArrayLoader([ - 'emails/test.twig' => '

Hello {{ name }}

', + 'emails/test.twig' => '

Bonjour {{ name }}

', ])); return new MailService( @@ -55,16 +53,19 @@ final class MailServiceTest extends TestCase 'mailer-user', 'mailer-pass', $encryption, - 'noreply@example.test', + 'no-reply@example.test', 'Slim Blog', ); } - private function invokeCreateMailer(MailService $service): mixed + private function invokeCreateMailer(MailService $service): PHPMailer { - $method = new \ReflectionMethod($service, 'createMailer'); + $method = new ReflectionMethod($service, 'createMailer'); $method->setAccessible(true); - return $method->invoke($service); + /** @var PHPMailer $mailer */ + $mailer = $method->invoke($service); + + return $mailer; } } diff --git a/tests/Shared/NotFoundExceptionTest.php b/tests/Shared/NotFoundExceptionTest.php index 75d7bb2..0ad0dbe 100644 --- a/tests/Shared/NotFoundExceptionTest.php +++ b/tests/Shared/NotFoundExceptionTest.php @@ -6,12 +6,14 @@ namespace Tests\Shared; use App\Shared\Exception\NotFoundException; use PHPUnit\Framework\TestCase; +#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class NotFoundExceptionTest extends TestCase { - public function testConstructorFormatsEntityAndIdentifierInMessage(): void + public function testMessageContainsEntityAndIdentifier(): void { - $exception = new NotFoundException('Article', 15); + $exception = new NotFoundException('Article', 'mon-slug'); - self::assertSame('Article introuvable : 15', $exception->getMessage()); + self::assertSame('Article introuvable : mon-slug', $exception->getMessage()); } } diff --git a/tests/Shared/ProvisionerTest.php b/tests/Shared/ProvisionerTest.php index 6ee6734..4d2e7e0 100644 --- a/tests/Shared/ProvisionerTest.php +++ b/tests/Shared/ProvisionerTest.php @@ -7,69 +7,70 @@ use App\Shared\Database\Provisioner; use PDO; use PHPUnit\Framework\TestCase; +#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class ProvisionerTest extends TestCase { private PDO $db; private string $lockPath; - private bool $lockExistedBefore; - - /** @var array */ - private array $envBackup; + private array $envBackup = []; protected function setUp(): void { $this->db = new PDO('sqlite::memory:', options: [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); $this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1); $this->lockPath = dirname(__DIR__, 2) . '/database/.provision.lock'; - $this->lockExistedBefore = file_exists($this->lockPath); + @unlink($this->lockPath); $this->envBackup = [ - 'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? '', - 'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? '', - 'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? '', + 'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? null, + 'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? null, + 'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? null, ]; - $_ENV['ADMIN_USERNAME'] = 'shared-admin'; - $_ENV['ADMIN_EMAIL'] = 'shared-admin@example.com'; - $_ENV['ADMIN_PASSWORD'] = 'strong-secret'; + $_ENV['ADMIN_USERNAME'] = 'Admin'; + $_ENV['ADMIN_EMAIL'] = 'Admin@example.com'; + $_ENV['ADMIN_PASSWORD'] = 'secret1234'; } protected function tearDown(): void { - foreach ($this->envBackup as $key => $value) { - $_ENV[$key] = $value; - } + @unlink($this->lockPath); - if (!$this->lockExistedBefore && file_exists($this->lockPath)) { - @unlink($this->lockPath); + foreach ($this->envBackup as $key => $value) { + if ($value === null) { + unset($_ENV[$key]); + } else { + $_ENV[$key] = $value; + } } } - public function testRunAppliesMigrationsSeedsAdminAndCreatesLockFile(): void + public function testRunCreatesProvisionLockAndSeedsAdminUser(): void { Provisioner::run($this->db); self::assertFileExists($this->lockPath); - $migrationCount = (int) $this->db->query('SELECT COUNT(*) FROM migrations')->fetchColumn(); - self::assertGreaterThan(0, $migrationCount, 'Les migrations doivent être enregistrées'); + $row = $this->db->query('SELECT username, email, role FROM users')->fetch(); - $admin = $this->db->query("SELECT username, email, role FROM users WHERE username = 'shared-admin'")->fetch(); - self::assertIsArray($admin); - self::assertSame('shared-admin@example.com', $admin['email']); - self::assertSame('admin', $admin['role']); + self::assertIsArray($row); + self::assertSame('admin', $row['username']); + self::assertSame('admin@example.com', $row['email']); + self::assertSame('admin', $row['role']); } - public function testRunIsIdempotentForAdminSeed(): void + public function testRunIsIdempotent(): void { Provisioner::run($this->db); Provisioner::run($this->db); - $adminCount = (int) $this->db->query("SELECT COUNT(*) FROM users WHERE username = 'shared-admin'")->fetchColumn(); - self::assertSame(1, $adminCount); + $count = (int) $this->db->query('SELECT COUNT(*) FROM users WHERE username = "admin"')->fetchColumn(); + + self::assertSame(1, $count); } } diff --git a/tests/Shared/RoutesTest.php b/tests/Shared/RoutesTest.php index 4266509..d520ff9 100644 --- a/tests/Shared/RoutesTest.php +++ b/tests/Shared/RoutesTest.php @@ -7,6 +7,8 @@ use App\Shared\Routes; use PHPUnit\Framework\TestCase; use Slim\Factory\AppFactory; +#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class RoutesTest extends TestCase { public function testRegisterDeclaresExpectedPublicAndProtectedRoutes(): void @@ -15,46 +17,48 @@ final class RoutesTest extends TestCase Routes::register($app); $actual = []; + foreach ($app->getRouteCollector()->getRoutes() as $route) { $pattern = $route->getPattern(); - $methods = $route->getMethods(); - - if (!isset($actual[$pattern])) { - $actual[$pattern] = []; - } + $methods = array_values(array_diff($route->getMethods(), ['HEAD', 'OPTIONS'])); + $actual[$pattern] ??= []; $actual[$pattern] = array_values(array_unique(array_merge($actual[$pattern], $methods))); sort($actual[$pattern]); } + ksort($actual); + $expected = [ '/' => ['GET'], + '/account/password' => ['GET', 'POST'], + '/admin' => ['GET'], + '/admin/categories' => ['GET'], + '/admin/categories/create' => ['POST'], + '/admin/categories/delete/{id}' => ['POST'], + '/admin/media' => ['GET'], + '/admin/media/delete/{id}' => ['POST'], + '/admin/media/upload' => ['POST'], + '/admin/posts' => ['GET'], + '/admin/posts/create' => ['POST'], + '/admin/posts/delete/{id}' => ['POST'], + '/admin/posts/edit/{id}' => ['GET', 'POST'], + '/admin/users' => ['GET'], + '/admin/users/create' => ['GET', 'POST'], + '/admin/users/delete/{id}' => ['POST'], + '/admin/users/role/{id}' => ['POST'], '/article/{slug}' => ['GET'], - '/rss.xml' => ['GET'], '/auth/login' => ['GET', 'POST'], '/auth/logout' => ['POST'], '/password/forgot' => ['GET', 'POST'], '/password/reset' => ['GET', 'POST'], - '/account/password' => ['GET', 'POST'], - '/admin' => ['GET'], - '/admin/posts' => ['GET'], - '/admin/posts/edit/{id}' => ['GET', 'POST'], - '/admin/posts/create' => ['POST'], - '/admin/posts/delete/{id}' => ['POST'], - '/admin/media/upload' => ['POST'], - '/admin/media' => ['GET'], - '/admin/media/delete/{id}' => ['POST'], - '/admin/categories' => ['GET'], - '/admin/categories/create' => ['POST'], - '/admin/categories/delete/{id}' => ['POST'], - '/admin/users' => ['GET'], - '/admin/users/create' => ['GET', 'POST'], - '/admin/users/role/{id}' => ['POST'], - '/admin/users/delete/{id}' => ['POST'], + '/rss.xml' => ['GET'], ]; + foreach ($expected as $pattern => $methods) { + sort($methods); + } ksort($expected); - ksort($actual); self::assertSame($expected, $actual); } diff --git a/tests/Shared/SessionManagerEdgeCasesTest.php b/tests/Shared/SessionManagerEdgeCasesTest.php index d5c0a44..7e7c65c 100644 --- a/tests/Shared/SessionManagerEdgeCasesTest.php +++ b/tests/Shared/SessionManagerEdgeCasesTest.php @@ -7,6 +7,7 @@ use App\Shared\Http\SessionManager; use PHPUnit\Framework\TestCase; #[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] + final class SessionManagerEdgeCasesTest extends TestCase { private SessionManager $manager; @@ -30,14 +31,6 @@ final class SessionManagerEdgeCasesTest extends TestCase self::assertFalse($this->manager->isAuthenticated()); } - public function testGetUserIdCastsNumericStringToInteger(): void - { - $_SESSION['user_id'] = '42'; - - self::assertSame(42, $this->manager->getUserId()); - self::assertTrue($this->manager->isAuthenticated()); - } - public function testSetUserUsesDefaultRoleUser(): void { $this->manager->setUser(12, 'julien'); diff --git a/tests/User/UserRepositoryTest.php b/tests/User/UserRepositoryTest.php index 50c344f..f6a3e64 100644 --- a/tests/User/UserRepositoryTest.php +++ b/tests/User/UserRepositoryTest.php @@ -128,7 +128,7 @@ final class UserRepositoryTest extends TestCase public function testFindByIdReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findById(99)); } @@ -139,7 +139,7 @@ final class UserRepositoryTest extends TestCase public function testFindByIdReturnsUserWhenFound(): void { $stmt = $this->stmtForRead(row: $this->rowAlice); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $result = $this->repository->findById(1); @@ -153,7 +153,7 @@ final class UserRepositoryTest extends TestCase public function testFindByIdQueriesWithCorrectId(): void { $stmt = $this->stmtForRead(row: false); - $this->db->method('prepare')->willReturn($stmt); + $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') diff --git a/views/admin/posts/form.twig b/views/admin/posts/form.twig index ff67edb..c902645 100644 --- a/views/admin/posts/form.twig +++ b/views/admin/posts/form.twig @@ -130,20 +130,7 @@ serverPath: '/admin/media/upload', fileFieldName: 'file', urlPropertyName: 'url', - statusPropertyName: 'success', - headers: { - 'X-Requested-With': 'XMLHttpRequest' - }, - data: [ - { - name: '{{ csrf.keys.name }}', - value: '{{ csrf.name }}' - }, - { - name: '{{ csrf.keys.value }}', - value: '{{ csrf.value }}' - } - ] + statusPropertyName: 'success' } } });