From ebc477877ef46ca8685d673b7d720e26b06918f4 Mon Sep 17 00:00:00 2001 From: julien Date: Mon, 16 Mar 2026 03:22:37 +0100 Subject: [PATCH] Working state --- tests/Media/MediaControllerTest.php | 30 +++++- ...MediaControllerUploadCompatibilityTest.php | 91 +++++++++++++++++++ tests/Shared/ExtensionTest.php | 15 +++ tests/Shared/ProvisionerTest.php | 75 +++++++++++++++ tests/Shared/RoutesTest.php | 61 +++++++++++++ 5 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 tests/Media/MediaControllerUploadCompatibilityTest.php create mode 100644 tests/Shared/ProvisionerTest.php create mode 100644 tests/Shared/RoutesTest.php diff --git a/tests/Media/MediaControllerTest.php b/tests/Media/MediaControllerTest.php index adf5e23..40e35a1 100644 --- a/tests/Media/MediaControllerTest.php +++ b/tests/Media/MediaControllerTest.php @@ -202,7 +202,35 @@ final class MediaControllerTest extends ControllerTestCase $this->assertStatus($res, 200); $this->assertJsonContentType($res); - $this->assertJsonContains($res, ['success' => true, 'file' => '/media/abc123.webp']); + $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 ─────────────────────────────────────────────────────── diff --git a/tests/Media/MediaControllerUploadCompatibilityTest.php b/tests/Media/MediaControllerUploadCompatibilityTest.php new file mode 100644 index 0000000..43d0349 --- /dev/null +++ b/tests/Media/MediaControllerUploadCompatibilityTest.php @@ -0,0 +1,91 @@ +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 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/Shared/ExtensionTest.php b/tests/Shared/ExtensionTest.php index 8ae3040..f61e657 100644 --- a/tests/Shared/ExtensionTest.php +++ b/tests/Shared/ExtensionTest.php @@ -46,6 +46,21 @@ 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/ProvisionerTest.php b/tests/Shared/ProvisionerTest.php new file mode 100644 index 0000000..6ee6734 --- /dev/null +++ b/tests/Shared/ProvisionerTest.php @@ -0,0 +1,75 @@ + */ + private array $envBackup; + + protected function setUp(): void + { + $this->db = new PDO('sqlite::memory:', options: [ + 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); + + $this->envBackup = [ + 'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? '', + 'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? '', + 'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? '', + ]; + + $_ENV['ADMIN_USERNAME'] = 'shared-admin'; + $_ENV['ADMIN_EMAIL'] = 'shared-admin@example.com'; + $_ENV['ADMIN_PASSWORD'] = 'strong-secret'; + } + + protected function tearDown(): void + { + foreach ($this->envBackup as $key => $value) { + $_ENV[$key] = $value; + } + + if (!$this->lockExistedBefore && file_exists($this->lockPath)) { + @unlink($this->lockPath); + } + } + + public function testRunAppliesMigrationsSeedsAdminAndCreatesLockFile(): 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'); + + $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']); + } + + public function testRunIsIdempotentForAdminSeed(): 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); + } +} diff --git a/tests/Shared/RoutesTest.php b/tests/Shared/RoutesTest.php new file mode 100644 index 0000000..4266509 --- /dev/null +++ b/tests/Shared/RoutesTest.php @@ -0,0 +1,61 @@ +getRouteCollector()->getRoutes() as $route) { + $pattern = $route->getPattern(); + $methods = $route->getMethods(); + + if (!isset($actual[$pattern])) { + $actual[$pattern] = []; + } + + $actual[$pattern] = array_values(array_unique(array_merge($actual[$pattern], $methods))); + sort($actual[$pattern]); + } + + $expected = [ + '/' => ['GET'], + '/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'], + ]; + + ksort($expected); + ksort($actual); + + self::assertSame($expected, $actual); + } +}