2026-03-09 20:07:55 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MintyPHP\Tests\Service\Settings;
|
|
|
|
|
|
|
|
|
|
use MintyPHP\Repository\Auth\ApiTokenRepository;
|
|
|
|
|
use MintyPHP\Repository\Auth\RememberTokenRepository;
|
|
|
|
|
use MintyPHP\Service\Access\RoleService;
|
refactor(audit): extract audit domain into self-contained module
Move the entire audit subsystem (system audit, API audit, import audit,
user lifecycle audit, frontend telemetry) from core into modules/audit/.
Core decoupling via interface-based injection:
- AuditRecorderInterface replaces SystemAuditService in 10+ core services
- UserLifecycleAuditInterface / ImportAuditInterface for specialized flows
- NullAuditRecorder fallback when audit module is disabled
- ApiBootstrap/ApiResponse use null-safe callable resolvers
Module structure (modules/audit/):
- Manifest with routes, permissions, scheduler jobs, authorization policy
- 9 services, 8 repositories, 6 domain enums, 4 job handlers
- 33 page files, 4 JS files, 8 test files, migration scripts, i18n
Core cleanup:
- OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService
surgically cleaned of audit-specific constants
- Sidebar template cleared of hardcoded audit navigation
- AuditModuleIsolationContractTest ensures no future core→module coupling
All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5
clean, architecture contracts verified.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:12:49 +01:00
|
|
|
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
2026-03-09 20:07:55 +01:00
|
|
|
use MintyPHP\Service\Org\DepartmentService;
|
|
|
|
|
use MintyPHP\Service\Settings\AdminSettingsService;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingCacheService;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsApiPolicyGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsAppGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsDefaultsGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsFrontendTelemetryGateway;
|
2026-03-10 22:48:10 +01:00
|
|
|
use MintyPHP\Service\Settings\SettingsLoginPersistenceGateway;
|
2026-03-09 20:07:55 +01:00
|
|
|
use MintyPHP\Service\Settings\SettingsMetadataGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsMicrosoftGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsSessionGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsSmtpGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsSystemAuditGateway;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsUserLifecycleGateway;
|
|
|
|
|
use MintyPHP\Service\Tenant\TenantService;
|
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
|
|
|
|
|
|
class AdminSettingsServiceSessionPolicyTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
public function testBuildPageDataIncludesSessionPolicyValuesAndMetadata(): void
|
|
|
|
|
{
|
|
|
|
|
$settingsSessionGateway = $this->createConfiguredMock(SettingsSessionGateway::class, [
|
|
|
|
|
'getSessionIdleTimeoutMinutes' => 45,
|
|
|
|
|
'getSessionAbsoluteTimeoutHours' => 12,
|
|
|
|
|
'setSessionIdleTimeoutMinutes' => true,
|
|
|
|
|
'setSessionAbsoluteTimeoutHours' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$settingsMetadataGateway = $this->createMock(SettingsMetadataGateway::class);
|
|
|
|
|
$settingsMetadataGateway->method('getValue')->willReturn(null);
|
|
|
|
|
$settingsMetadataGateway->method('get')->willReturnCallback(
|
|
|
|
|
static fn (string $key): array => ['key' => $key, 'description' => 'desc-' . $key]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$service = $this->newService($settingsSessionGateway, $settingsMetadataGateway);
|
|
|
|
|
$data = $service->buildPageData();
|
|
|
|
|
|
|
|
|
|
$values = is_array($data['values'] ?? null) ? $data['values'] : [];
|
|
|
|
|
$settings = is_array($data['settings'] ?? null) ? $data['settings'] : [];
|
|
|
|
|
|
|
|
|
|
$this->assertSame(45, (int) ($values['session_idle_timeout_minutes'] ?? 0));
|
|
|
|
|
$this->assertSame(12, (int) ($values['session_absolute_timeout_hours'] ?? 0));
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'desc-session_idle_timeout_minutes',
|
|
|
|
|
(string) (($settings['session_idle_timeout_minutes'] ?? [])['description'] ?? '')
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'desc-session_absolute_timeout_hours',
|
|
|
|
|
(string) (($settings['session_absolute_timeout_hours'] ?? [])['description'] ?? '')
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateFromAdminPersistsValidSessionPolicyValues(): void
|
|
|
|
|
{
|
|
|
|
|
$settingsSessionGateway = $this->createMock(SettingsSessionGateway::class);
|
|
|
|
|
$settingsSessionGateway->method('getSessionIdleTimeoutMinutes')->willReturn(30);
|
|
|
|
|
$settingsSessionGateway->method('getSessionAbsoluteTimeoutHours')->willReturn(8);
|
|
|
|
|
$settingsSessionGateway->expects($this->once())
|
|
|
|
|
->method('setSessionIdleTimeoutMinutes')
|
|
|
|
|
->with(15)
|
|
|
|
|
->willReturn(true);
|
|
|
|
|
$settingsSessionGateway->expects($this->once())
|
|
|
|
|
->method('setSessionAbsoluteTimeoutHours')
|
|
|
|
|
->with(12)
|
|
|
|
|
->willReturn(true);
|
|
|
|
|
|
|
|
|
|
$service = $this->newService($settingsSessionGateway);
|
|
|
|
|
$result = $service->updateFromAdmin($this->validPostData([
|
|
|
|
|
'session_idle_timeout_minutes' => '15',
|
|
|
|
|
'session_absolute_timeout_hours' => '12',
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
$this->assertSame([], $result['errors'] ?? []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateFromAdminReturnsValidationErrorsForInvalidSessionPolicy(): void
|
|
|
|
|
{
|
|
|
|
|
$settingsSessionGateway = $this->createMock(SettingsSessionGateway::class);
|
|
|
|
|
$settingsSessionGateway->method('getSessionIdleTimeoutMinutes')->willReturn(30);
|
|
|
|
|
$settingsSessionGateway->method('getSessionAbsoluteTimeoutHours')->willReturn(8);
|
|
|
|
|
$settingsSessionGateway->expects($this->once())
|
|
|
|
|
->method('setSessionIdleTimeoutMinutes')
|
|
|
|
|
->with(2)
|
|
|
|
|
->willReturn(false);
|
|
|
|
|
$settingsSessionGateway->expects($this->once())
|
|
|
|
|
->method('setSessionAbsoluteTimeoutHours')
|
|
|
|
|
->with(100)
|
|
|
|
|
->willReturn(false);
|
|
|
|
|
|
|
|
|
|
$service = $this->newService($settingsSessionGateway);
|
|
|
|
|
$result = $service->updateFromAdmin($this->validPostData([
|
|
|
|
|
'session_idle_timeout_minutes' => '2',
|
|
|
|
|
'session_absolute_timeout_hours' => '100',
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
$errors = is_array($result['errors'] ?? null) ? $result['errors'] : [];
|
|
|
|
|
$errorKeys = [];
|
|
|
|
|
foreach ($errors as $error) {
|
|
|
|
|
if (is_array($error) && isset($error['key'])) {
|
|
|
|
|
$errorKeys[] = (string) $error['key'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertContains('session_idle_timeout_invalid', $errorKeys);
|
|
|
|
|
$this->assertContains('session_absolute_timeout_invalid', $errorKeys);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 22:48:10 +01:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Login persistence settings
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
public function testBuildPageDataIncludesLoginPersistenceValues(): void
|
|
|
|
|
{
|
|
|
|
|
$loginPersistenceGateway = $this->createConfiguredMock(SettingsLoginPersistenceGateway::class, [
|
|
|
|
|
'isMicrosoftAutoRememberDefault' => true,
|
|
|
|
|
'getRememberTokenLifetimeDays' => 14,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$service = $this->newService(loginPersistenceGateway: $loginPersistenceGateway);
|
|
|
|
|
$data = $service->buildPageData();
|
|
|
|
|
$values = is_array($data['values'] ?? null) ? $data['values'] : [];
|
|
|
|
|
|
|
|
|
|
$this->assertTrue((bool) ($values['microsoft_auto_remember_default'] ?? false));
|
|
|
|
|
$this->assertSame(14, (int) ($values['remember_token_lifetime_days'] ?? 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateFromAdminPersistsLoginPersistenceValues(): void
|
|
|
|
|
{
|
|
|
|
|
$loginPersistenceGateway = $this->createMock(SettingsLoginPersistenceGateway::class);
|
|
|
|
|
$loginPersistenceGateway->method('isMicrosoftAutoRememberDefault')->willReturn(false);
|
|
|
|
|
$loginPersistenceGateway->method('getRememberTokenLifetimeDays')->willReturn(30);
|
|
|
|
|
$loginPersistenceGateway->expects($this->once())
|
|
|
|
|
->method('setMicrosoftAutoRememberDefault')
|
|
|
|
|
->with(true);
|
|
|
|
|
$loginPersistenceGateway->expects($this->once())
|
|
|
|
|
->method('setRememberTokenLifetimeDays')
|
|
|
|
|
->with(14)
|
|
|
|
|
->willReturn(true);
|
|
|
|
|
|
|
|
|
|
$service = $this->newService(loginPersistenceGateway: $loginPersistenceGateway);
|
|
|
|
|
$result = $service->updateFromAdmin($this->validPostData([
|
|
|
|
|
'microsoft_auto_remember_default' => '1',
|
|
|
|
|
'remember_token_lifetime_days' => '14',
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
$this->assertSame([], $result['errors'] ?? []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateFromAdminReturnsErrorForInvalidRememberTtl(): void
|
|
|
|
|
{
|
|
|
|
|
$loginPersistenceGateway = $this->createMock(SettingsLoginPersistenceGateway::class);
|
|
|
|
|
$loginPersistenceGateway->method('isMicrosoftAutoRememberDefault')->willReturn(false);
|
|
|
|
|
$loginPersistenceGateway->method('getRememberTokenLifetimeDays')->willReturn(30);
|
|
|
|
|
$loginPersistenceGateway->method('setMicrosoftAutoRememberDefault');
|
|
|
|
|
$loginPersistenceGateway->expects($this->once())
|
|
|
|
|
->method('setRememberTokenLifetimeDays')
|
|
|
|
|
->with(500)
|
|
|
|
|
->willReturn(false);
|
|
|
|
|
|
|
|
|
|
$service = $this->newService(loginPersistenceGateway: $loginPersistenceGateway);
|
|
|
|
|
$result = $service->updateFromAdmin($this->validPostData([
|
|
|
|
|
'remember_token_lifetime_days' => '500',
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
$errors = is_array($result['errors'] ?? null) ? $result['errors'] : [];
|
|
|
|
|
$errorKeys = [];
|
|
|
|
|
foreach ($errors as $error) {
|
|
|
|
|
if (is_array($error) && isset($error['key'])) {
|
|
|
|
|
$errorKeys[] = (string) $error['key'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertContains('remember_token_lifetime_days_invalid', $errorKeys);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 20:07:55 +01:00
|
|
|
/**
|
|
|
|
|
* @param array<string, mixed> $overrides
|
|
|
|
|
* @return array<string, mixed>
|
|
|
|
|
*/
|
|
|
|
|
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_theme' => 'light',
|
|
|
|
|
'app_theme_user' => '1',
|
|
|
|
|
'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'],
|
|
|
|
|
'app_primary_color' => '#2FA4A4',
|
|
|
|
|
'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(
|
|
|
|
|
?SettingsSessionGateway $settingsSessionGateway = null,
|
2026-03-10 22:48:10 +01:00
|
|
|
?SettingsMetadataGateway $settingsMetadataGateway = null,
|
|
|
|
|
?SettingsLoginPersistenceGateway $loginPersistenceGateway = null
|
2026-03-09 20:07:55 +01:00
|
|
|
): AdminSettingsService {
|
|
|
|
|
$settingsDefaultsGateway = $this->createConfiguredMock(SettingsDefaultsGateway::class, [
|
|
|
|
|
'getDefaultTenantId' => null,
|
|
|
|
|
'getDefaultRoleId' => null,
|
|
|
|
|
'getDefaultDepartmentId' => null,
|
|
|
|
|
'setDefaultTenantId' => true,
|
|
|
|
|
'setDefaultRoleId' => true,
|
|
|
|
|
'setDefaultDepartmentId' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$settingsAppGateway = $this->createConfiguredMock(SettingsAppGateway::class, [
|
|
|
|
|
'getAppLocale' => 'de',
|
|
|
|
|
'getAppTheme' => 'light',
|
|
|
|
|
'isUserThemeAllowed' => true,
|
|
|
|
|
'isRegistrationEnabled' => true,
|
|
|
|
|
'getAppPrimaryColor' => '#2FA4A4',
|
|
|
|
|
'setAppTitle' => true,
|
|
|
|
|
'setAppLocale' => true,
|
|
|
|
|
'setAppTheme' => true,
|
|
|
|
|
'setUserThemeAllowed' => true,
|
|
|
|
|
'setRegistrationEnabled' => true,
|
|
|
|
|
'setAppPrimaryColor' => 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 = $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 = $this->createConfiguredMock(SettingsSmtpGateway::class, [
|
|
|
|
|
'getSmtpSecure' => '',
|
|
|
|
|
'setSmtpHost' => true,
|
|
|
|
|
'setSmtpPort' => true,
|
|
|
|
|
'setSmtpUser' => true,
|
|
|
|
|
'setSmtpPassword' => true,
|
|
|
|
|
'setSmtpSecure' => true,
|
|
|
|
|
'setSmtpFrom' => true,
|
|
|
|
|
'setSmtpFromName' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-10 22:48:10 +01:00
|
|
|
$loginPersistenceGateway = $loginPersistenceGateway ?? $this->createConfiguredMock(SettingsLoginPersistenceGateway::class, [
|
|
|
|
|
'isMicrosoftAutoRememberDefault' => false,
|
|
|
|
|
'getRememberTokenLifetimeDays' => 30,
|
|
|
|
|
'setMicrosoftAutoRememberDefault' => true,
|
|
|
|
|
'setRememberTokenLifetimeDays' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-09 20:07:55 +01:00
|
|
|
if ($settingsMetadataGateway === null) {
|
|
|
|
|
$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 = $this->createConfiguredMock(TenantService::class, [
|
|
|
|
|
'list' => [],
|
|
|
|
|
]);
|
|
|
|
|
$roleService = $this->createConfiguredMock(RoleService::class, [
|
|
|
|
|
'listActive' => [],
|
|
|
|
|
]);
|
|
|
|
|
$departmentService = $this->createConfiguredMock(DepartmentService::class, [
|
|
|
|
|
'list' => [],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$rememberTokenRepository = $this->createConfiguredMock(RememberTokenRepository::class, [
|
|
|
|
|
'countActive' => 0,
|
|
|
|
|
]);
|
|
|
|
|
$apiTokenRepository = $this->createConfiguredMock(ApiTokenRepository::class, [
|
|
|
|
|
'countActive' => 0,
|
|
|
|
|
]);
|
refactor(audit): extract audit domain into self-contained module
Move the entire audit subsystem (system audit, API audit, import audit,
user lifecycle audit, frontend telemetry) from core into modules/audit/.
Core decoupling via interface-based injection:
- AuditRecorderInterface replaces SystemAuditService in 10+ core services
- UserLifecycleAuditInterface / ImportAuditInterface for specialized flows
- NullAuditRecorder fallback when audit module is disabled
- ApiBootstrap/ApiResponse use null-safe callable resolvers
Module structure (modules/audit/):
- Manifest with routes, permissions, scheduler jobs, authorization policy
- 9 services, 8 repositories, 6 domain enums, 4 job handlers
- 33 page files, 4 JS files, 8 test files, migration scripts, i18n
Core cleanup:
- OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService
surgically cleaned of audit-specific constants
- Sidebar template cleared of hardcoded audit navigation
- AuditModuleIsolationContractTest ensures no future core→module coupling
All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5
clean, architecture contracts verified.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:12:49 +01:00
|
|
|
$systemAuditService = $this->createConfiguredMock(AuditRecorderInterface::class, [
|
2026-03-09 20:07:55 +01:00
|
|
|
'record' => null,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return new AdminSettingsService(
|
|
|
|
|
$settingsDefaultsGateway,
|
|
|
|
|
$settingsAppGateway,
|
|
|
|
|
$settingsApiPolicyGateway,
|
|
|
|
|
$settingsUserLifecycleGateway,
|
|
|
|
|
$settingsSessionGateway,
|
|
|
|
|
$settingsSystemAuditGateway,
|
|
|
|
|
$settingsFrontendTelemetryGateway,
|
|
|
|
|
$settingsMicrosoftGateway,
|
|
|
|
|
$settingsSmtpGateway,
|
2026-03-10 22:48:10 +01:00
|
|
|
$loginPersistenceGateway,
|
2026-03-09 20:07:55 +01:00
|
|
|
$settingsMetadataGateway,
|
|
|
|
|
$settingCacheService,
|
|
|
|
|
$tenantService,
|
|
|
|
|
$roleService,
|
|
|
|
|
$departmentService,
|
|
|
|
|
$rememberTokenRepository,
|
|
|
|
|
$apiTokenRepository,
|
|
|
|
|
$systemAuditService
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|