createMock(SettingRepositoryInterface::class); $settings->expects($this->once()) ->method('set') ->with('app_locale', null, 'setting.app_locale') ->willReturn(true); $gateway = $this->newAppGateway($settings); self::assertTrue($gateway->setAppLocale(' ')); } public function testAppThemeStoresNullForUnknownTheme(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->once()) ->method('set') ->with('app_theme', null, 'setting.app_theme') ->willReturn(true); $gateway = $this->newAppGateway($settings); self::assertTrue($gateway->setAppTheme('unknown')); } public function testAppBooleanFlagsUseExpectedDefaults(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->method('getValue')->willReturnMap([ ['app_theme_user', null], ['app_registration', null], ]); $gateway = $this->newAppGateway($settings); self::assertTrue($gateway->isUserThemeAllowed()); self::assertTrue($gateway->isRegistrationEnabled()); } public function testAppPrimaryColorNormalizesHexWithoutHash(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->once()) ->method('set') ->with('app_primary_color', '#abc', 'setting.app_primary_color') ->willReturn(true); $gateway = $this->newAppGateway($settings); self::assertTrue($gateway->setAppPrimaryColor('abc')); } public function testAppPrimaryColorRejectsInvalidValue(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->never())->method('set'); $gateway = $this->newAppGateway($settings); self::assertFalse($gateway->setAppPrimaryColor('not-a-color')); } public function testSystemAuditUsesFallbacksAndRejectsOutOfRangeRetention(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->method('getValue')->willReturnCallback(static function (string $key): ?string { return $key === 'system_audit_retention_days' ? '5' : null; }); $settings->expects($this->never())->method('set'); $gateway = new SettingsSystemAuditGateway(new SettingsMetadataGateway($settings)); self::assertTrue($gateway->isSystemAuditEnabled()); self::assertSame(365, $gateway->getSystemAuditRetentionDays()); self::assertFalse($gateway->setSystemAuditRetentionDays(10)); } public function testFrontendTelemetryUsesDefaultsWhenSettingsMissing(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->method('getValue')->willReturn(null); $gateway = new SettingsFrontendTelemetryGateway(new SettingsMetadataGateway($settings)); self::assertFalse($gateway->isFrontendTelemetryEnabled()); self::assertSame(0.2, $gateway->getFrontendTelemetrySampleRate()); self::assertSame(['warn_once', 'ajax_error'], $gateway->getFrontendTelemetryAllowedEvents()); } public function testFrontendTelemetryRejectsOutOfRangeSampleRate(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->never())->method('set'); $gateway = new SettingsFrontendTelemetryGateway(new SettingsMetadataGateway($settings)); self::assertFalse($gateway->setFrontendTelemetrySampleRate(1.5)); } public function testFrontendTelemetryNormalizesAllowedEvents(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->once()) ->method('set') ->with( 'frontend_telemetry_allowed_events', 'ajax_error,warn_once', 'setting.frontend_telemetry_allowed_events' ) ->willReturn(true); $gateway = new SettingsFrontendTelemetryGateway(new SettingsMetadataGateway($settings)); self::assertTrue($gateway->setFrontendTelemetryAllowedEvents(['frontend.warn_once', 'ajax_error', 'invalid'])); } public function testSmtpSecureReturnsNullForNone(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->method('getValue')->willReturnCallback(static function (string $key): ?string { if ($key === 'smtp_secure') { return 'none'; } return null; }); $gateway = new SettingsSmtpGateway(new SettingsMetadataGateway($settings)); self::assertNull($gateway->getSmtpSecure()); } public function testSmtpSecureRejectsInvalidValue(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->never())->method('set'); $gateway = new SettingsSmtpGateway(new SettingsMetadataGateway($settings)); self::assertFalse($gateway->setSmtpSecure('starttls')); } public function testSmtpHostPersistsNullForEmptyString(): void { $settings = $this->createMock(SettingRepositoryInterface::class); $settings->expects($this->once()) ->method('set') ->with('smtp_host', null, 'setting.smtp_host') ->willReturn(true); $gateway = new SettingsSmtpGateway(new SettingsMetadataGateway($settings)); self::assertTrue($gateway->setSmtpHost(' ')); } private function newAppGateway(SettingRepositoryInterface $settingRepository): SettingsAppGateway { $themeConfigService = $this->createMock(ThemeConfigGateway::class); $themeConfigService->method('all')->willReturn(['light' => 'Light', 'dark' => 'Dark']); return new SettingsAppGateway(new SettingsMetadataGateway($settingRepository), $themeConfigService); } }