view = $this->makeTwigMock(); $this->settings = $this->createMock(SettingsServiceInterface::class); $this->auditLogger = $this->createMock(AuditLoggerInterface::class); $this->auditLogReader = $this->createMock(AuditLogReaderInterface::class); $this->notifications = $this->createMock(NotificationServiceInterface::class); $this->authorization = $this->createMock(AuthorizationServiceInterface::class); $this->sessionManager = $this->createMock(SessionManagerInterface::class); $this->flash = $this->createMock(FlashServiceInterface::class); $this->authorization->method('canRole')->willReturnMap([ ['admin', Permission::SETTINGS_MANAGE, true], ['admin', Permission::AUDIT_LOG_VIEW, true], ['admin', Permission::NOTIFICATIONS_SEND, true], ['admin', Permission::CONTENT_MANAGE, true], ['user', Permission::SETTINGS_MANAGE, false], ['user', Permission::AUDIT_LOG_VIEW, false], ['user', Permission::NOTIFICATIONS_SEND, false], ['user', Permission::CONTENT_MANAGE, false], ]); $this->sessionManager->method('isAdmin')->willReturn(true); $this->sessionManager->method('isEditor')->willReturn(false); $this->sessionManager->method('getUserId')->willReturn(1); $this->settings->method('getString')->willReturnMap([ ['site.title', 'Netslim Blog', 'Netslim Blog'], ['site.tagline', '', 'Baseline'], ['site.meta_description', '', 'Description'], ['blog.home_intro', '', 'Accueil'], ['notifications.demo_recipient', '', 'demo@example.test'], ]); $this->settings->method('getInt')->willReturnMap([ ['blog.public_posts_per_page', 6, 6], ['blog.admin_posts_per_page', 12, 12], ]); $this->auditLogReader->method('listRecent')->willReturn([ new AuditEntryView(1, 'settings.updated', 'settings', 'blog', 1, [], '2026-03-20T12:00:00+00:00'), ]); $this->notifications->method('recent')->willReturn([ new NotificationDispatchView(1, 'demo@example.test', 'Sujet', '@Site/emails/demo-notification.twig', 'sent', 'site.demo', null, '2026-03-20T12:00:00+00:00', '2026-03-20T12:00:01+00:00'), ]); $this->controller = new SiteController( $this->view, $this->settings, $this->auditLogger, $this->auditLogReader, $this->notifications, $this->authorization, $this->sessionManager, $this->flash, ); } public function testSettingsPageRedirectsWhenPermissionIsMissing(): void { $sessionManager = $this->createMock(SessionManagerInterface::class); $sessionManager->method('isAdmin')->willReturn(false); $sessionManager->method('isEditor')->willReturn(false); $sessionManager->method('getUserId')->willReturn(1); $authorization = $this->createMock(AuthorizationServiceInterface::class); $authorization->expects($this->once()) ->method('canRole') ->with('user', Permission::SETTINGS_MANAGE) ->willReturn(false); $controller = new SiteController( $this->view, $this->settings, $this->auditLogger, $this->auditLogReader, $this->notifications, $authorization, $sessionManager, $this->flash, ); $this->flash->expects($this->once())->method('set')->with('site_error', $this->stringContains('réglages')); $response = $controller->settings($this->makeGet('/admin/settings'), $this->makeResponse()); $this->assertRedirectTo($response, '/admin'); } public function testSaveSettingsPersistsValuesAndAuditsChange(): void { $this->settings->expects($this->exactly(7))->method('set'); $this->auditLogger->expects($this->once())->method('record')->with( 'settings.updated', 'settings', 'blog', 1, $this->callback(fn (array $context): bool => isset($context['keys']) && count($context['keys']) === 7), ); $this->flash->expects($this->once())->method('set')->with('site_success', $this->stringContains('réglages')); $response = $this->controller->saveSettings($this->makePost('/admin/settings', [ 'site_title' => 'Mon blog', 'site_tagline' => 'Baseline', 'site_meta_description' => 'Description', 'home_intro' => 'Accueil', 'public_posts_per_page' => '8', 'admin_posts_per_page' => '16', 'demo_recipient' => 'demo@example.test', ]), $this->makeResponse()); $this->assertRedirectTo($response, '/admin/settings'); } public function testSendNotificationUsesNotificationServiceAndWritesAuditEntry(): void { $this->notifications->expects($this->once())->method('sendTemplate')->with( 'demo@example.test', 'Sujet', '@Site/emails/demo-notification.twig', $this->callback(static fn (mixed $context): bool => is_array($context)), 'site.demo-notification', ); $this->auditLogger->expects($this->once())->method('record')->with( 'notification.sent', 'notification', 'demo@example.test', 1, $this->callback(fn (array $context): bool => ($context['subject'] ?? null) === 'Sujet'), ); $response = $this->controller->sendNotification($this->makePost('/admin/notifications/send', [ 'recipient' => 'demo@example.test', 'subject' => 'Sujet', ]), $this->makeResponse()); $this->assertRedirectTo($response, '/admin/notifications'); } }