createConfiguredMock(TenantService::class, [ 'list' => [ ['id' => 2, 'description' => 'Zeta Corp'], ['id' => 1, 'description' => 'Alpha Inc'], ['id' => 3, 'description' => 'Mu Ltd'], ], ]); $roleService = $this->createConfiguredMock(RoleService::class, [ 'listActive' => [ ['id' => 2, 'description' => 'Viewer'], ['id' => 1, 'description' => 'Admin'], ], ]); $departmentService = $this->createConfiguredMock(DepartmentService::class, [ 'list' => [ ['id' => 3, 'description' => 'Sales'], ['id' => 1, 'description' => 'Engineering'], ['id' => 2, 'description' => 'Marketing'], ], ]); $service = $this->newService( tenantService: $tenantService, roleService: $roleService, departmentService: $departmentService ); $data = $service->buildPageData(); $tenantDescs = array_column($data['tenants'], 'description'); $this->assertSame(['Alpha Inc', 'Mu Ltd', 'Zeta Corp'], $tenantDescs); $roleDescs = array_column($data['roles'], 'description'); $this->assertSame(['Admin', 'Viewer'], $roleDescs); $deptDescs = array_column($data['departments'], 'description'); $this->assertSame(['Engineering', 'Marketing', 'Sales'], $deptDescs); } // --------------------------------------------------------------- // buildPageData: active token counts // --------------------------------------------------------------- public function testBuildPageDataIncludesActiveTokenCounts(): void { $rememberTokenRepository = $this->createConfiguredMock(RememberTokenRepository::class, [ 'countActive' => 42, ]); $apiTokenRepository = $this->createConfiguredMock(ApiTokenRepository::class, [ 'countActive' => 7, ]); $service = $this->newService( rememberTokenRepository: $rememberTokenRepository, apiTokenRepository: $apiTokenRepository ); $data = $service->buildPageData(); $this->assertSame(42, $data['active_login_tokens']); $this->assertSame(7, $data['active_api_tokens']); } // --------------------------------------------------------------- // buildPageData: values array contains expected keys // --------------------------------------------------------------- public function testBuildPageDataValuesContainExpectedKeys(): void { $service = $this->newService(); $data = $service->buildPageData(); $values = is_array($data['values'] ?? null) ? $data['values'] : []; $expectedKeys = [ 'default_tenant_id', 'default_role_id', 'default_department_id', 'app_title', 'app_locale', 'app_registration', 'api_token_default_ttl_days', 'api_token_max_ttl_days', 'user_inactivity_deactivate_days', 'user_inactivity_delete_days', 'session_idle_timeout_minutes', 'session_absolute_timeout_hours', 'microsoft_auto_remember_default', 'remember_token_lifetime_days', 'api_cors_allowed_origins', 'system_audit_enabled', 'system_audit_retention_days', 'frontend_telemetry_enabled', 'frontend_telemetry_sample_rate', 'frontend_telemetry_allowed_events', 'microsoft_shared_client_id', 'microsoft_authority', 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_secure', 'smtp_from', 'smtp_from_name', ]; foreach ($expectedKeys as $key) { $this->assertArrayHasKey($key, $values, "Expected key '$key' missing from values array"); } } // --------------------------------------------------------------- // SMTP settings: pass-through (gateway setters called) // --------------------------------------------------------------- public function testUpdateFromAdminCallsSmtpGatewaySetters(): void { $smtpGateway = $this->createMock(SettingsSmtpGateway::class); $smtpGateway->method('getSmtpSecure')->willReturn(''); $smtpGateway->expects($this->once())->method('setSmtpHost')->with('mail.example.com'); $smtpGateway->expects($this->once())->method('setSmtpPort')->with(587); $smtpGateway->expects($this->once())->method('setSmtpUser')->with('user@example.com'); $smtpGateway->expects($this->once())->method('setSmtpPassword')->with('secret'); $smtpGateway->expects($this->once())->method('setSmtpSecure')->with('tls'); $smtpGateway->expects($this->once())->method('setSmtpFrom')->with('noreply@example.com'); $smtpGateway->expects($this->once())->method('setSmtpFromName')->with('CoreCore'); $service = $this->newService(settingsSmtpGateway: $smtpGateway); $service->updateFromAdmin($this->validPostData([ 'smtp_host' => 'mail.example.com', 'smtp_port' => '587', 'smtp_user' => 'user@example.com', 'smtp_password' => 'secret', 'smtp_secure' => 'tls', 'smtp_from' => 'noreply@example.com', 'smtp_from_name' => 'CoreCore', ])); } public function testUpdateFromAdminSkipsSmtpPasswordWhenEmpty(): void { $smtpGateway = $this->createMock(SettingsSmtpGateway::class); $smtpGateway->method('getSmtpSecure')->willReturn(''); $smtpGateway->method('setSmtpHost'); $smtpGateway->method('setSmtpPort'); $smtpGateway->method('setSmtpUser'); $smtpGateway->expects($this->never())->method('setSmtpPassword'); $smtpGateway->method('setSmtpSecure'); $smtpGateway->method('setSmtpFrom'); $smtpGateway->method('setSmtpFromName'); $service = $this->newService(settingsSmtpGateway: $smtpGateway); $service->updateFromAdmin($this->validPostData([ 'smtp_password' => '', ])); } // --------------------------------------------------------------- // Helpers // --------------------------------------------------------------- /** * @param array $overrides * @return array */ private function validPostData(array $overrides = []): array { $base = [ 'default_tenant_id' => '0', 'default_role_id' => '0', 'default_department_id' => '0', 'app_title' => 'Demo', 'app_locale' => 'de', 'app_registration' => '1', 'api_token_default_ttl_days' => '90', 'api_token_max_ttl_days' => '365', 'user_inactivity_deactivate_days' => '180', 'user_inactivity_delete_days' => '365', 'session_idle_timeout_minutes' => '30', 'session_absolute_timeout_hours' => '8', 'api_cors_allowed_origins' => '', 'system_audit_enabled' => '1', 'system_audit_retention_days' => '365', 'frontend_telemetry_enabled' => '1', 'frontend_telemetry_sample_rate' => '0.2', 'frontend_telemetry_allowed_events' => ['warn_once'], 'smtp_host' => '', 'smtp_port' => '', 'smtp_user' => '', 'smtp_password' => '', 'smtp_secure' => '', 'smtp_from' => '', 'smtp_from_name' => '', 'microsoft_shared_client_id' => '', 'microsoft_shared_client_secret' => '', 'microsoft_authority' => 'https://login.microsoftonline.com/common/v2.0', ]; return array_replace($base, $overrides); } private function newService( ?SettingsAppGateway $settingsAppGateway = null, ?SettingsSmtpGateway $settingsSmtpGateway = null, ?TenantService $tenantService = null, ?RoleService $roleService = null, ?DepartmentService $departmentService = null, ?RememberTokenRepository $rememberTokenRepository = null, ?ApiTokenRepository $apiTokenRepository = null ): AdminSettingsService { $settingsDefaultsGateway = $this->createConfiguredMock(SettingsDefaultsGateway::class, [ 'getDefaultTenantId' => null, 'getDefaultRoleId' => null, 'getDefaultDepartmentId' => null, 'setDefaultTenantId' => true, 'setDefaultRoleId' => true, 'setDefaultDepartmentId' => true, ]); $settingsAppGateway = $settingsAppGateway ?? $this->createConfiguredMock(SettingsAppGateway::class, [ 'getAppLocale' => 'de', 'isRegistrationEnabled' => true, 'setAppTitle' => true, 'setAppLocale' => true, 'setRegistrationEnabled' => true, ]); $settingsApiPolicyGateway = $this->createConfiguredMock(SettingsApiPolicyGateway::class, [ 'getApiTokenDefaultTtlDays' => 90, 'getApiTokenMaxTtlDays' => 365, 'getApiCorsAllowedOriginsText' => '', 'setApiTokenDefaultTtlDays' => true, 'setApiTokenMaxTtlDays' => true, 'setApiCorsAllowedOrigins' => true, ]); $settingsUserLifecycleGateway = $this->createConfiguredMock(SettingsUserLifecycleGateway::class, [ 'getUserInactivityDeactivateDays' => 180, 'getUserInactivityDeleteDays' => 365, 'setUserInactivityDeactivateDays' => true, 'setUserInactivityDeleteDays' => true, ]); $settingsSessionGateway = $this->createConfiguredMock(SettingsSessionGateway::class, [ 'getSessionIdleTimeoutMinutes' => 30, 'getSessionAbsoluteTimeoutHours' => 8, 'setSessionIdleTimeoutMinutes' => true, 'setSessionAbsoluteTimeoutHours' => true, ]); $settingsSystemAuditGateway = $this->createConfiguredMock(SettingsSystemAuditGateway::class, [ 'isSystemAuditEnabled' => true, 'getSystemAuditRetentionDays' => 365, 'setSystemAuditEnabled' => true, 'setSystemAuditRetentionDays' => true, ]); $settingsFrontendTelemetryGateway = $this->createConfiguredMock(SettingsFrontendTelemetryGateway::class, [ 'isFrontendTelemetryEnabled' => true, 'getFrontendTelemetrySampleRate' => 0.2, 'getFrontendTelemetryAllowedEvents' => ['warn_once'], 'setFrontendTelemetryEnabled' => true, 'setFrontendTelemetrySampleRate' => true, 'setFrontendTelemetryAllowedEvents' => true, ]); $settingsMicrosoftGateway = $this->createConfiguredMock(SettingsMicrosoftGateway::class, [ 'getMicrosoftAuthority' => 'https://login.microsoftonline.com/common/v2.0', 'setMicrosoftSharedClientId' => true, 'setMicrosoftSharedClientSecret' => true, 'setMicrosoftAuthority' => true, ]); $settingsSmtpGateway = $settingsSmtpGateway ?? $this->createConfiguredMock(SettingsSmtpGateway::class, [ 'getSmtpSecure' => '', 'setSmtpHost' => true, 'setSmtpPort' => true, 'setSmtpUser' => true, 'setSmtpPassword' => true, 'setSmtpSecure' => true, 'setSmtpFrom' => true, 'setSmtpFromName' => true, ]); $loginPersistenceGateway = $this->createConfiguredMock(SettingsLoginPersistenceGateway::class, [ 'isMicrosoftAutoRememberDefault' => false, 'getRememberTokenLifetimeDays' => 30, 'setMicrosoftAutoRememberDefault' => true, 'setRememberTokenLifetimeDays' => true, ]); $settingsMetadataGateway = $this->createMock(SettingsMetadataGateway::class); $settingsMetadataGateway->method('getValue')->willReturn(null); $settingsMetadataGateway->method('get')->willReturnCallback( static fn (string $key): array => ['key' => $key, 'description' => 'setting.default'] ); $settingCacheService = $this->createConfiguredMock(SettingCacheService::class, [ 'update' => true, ]); $tenantService = $tenantService ?? $this->createConfiguredMock(TenantService::class, [ 'list' => [], ]); $roleService = $roleService ?? $this->createConfiguredMock(RoleService::class, [ 'listActive' => [], ]); $departmentService = $departmentService ?? $this->createConfiguredMock(DepartmentService::class, [ 'list' => [], ]); $rememberTokenRepository = $rememberTokenRepository ?? $this->createConfiguredMock(RememberTokenRepository::class, [ 'countActive' => 0, ]); $apiTokenRepository = $apiTokenRepository ?? $this->createConfiguredMock(ApiTokenRepository::class, [ 'countActive' => 0, ]); $systemAuditService = $this->createConfiguredMock(AuditRecorderInterface::class, [ 'record' => null, ]); return new AdminSettingsService( $settingsDefaultsGateway, $settingsAppGateway, $settingsApiPolicyGateway, $settingsUserLifecycleGateway, $settingsSessionGateway, $settingsSystemAuditGateway, $settingsFrontendTelemetryGateway, $settingsMicrosoftGateway, $settingsSmtpGateway, $loginPersistenceGateway, $settingsMetadataGateway, $settingCacheService, $tenantService, $roleService, $departmentService, $rememberTokenRepository, $apiTokenRepository, $systemAuditService ); } }