set('tenantpilot.hardening.intune_write_gate.enabled', true); config()->set('tenantpilot.hardening.intune_write_gate.freshness_threshold_hours', 24); }); test('blocked restore start creates audit log entry', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $tenant->update([ 'rbac_status' => null, 'rbac_last_checked_at' => null, ]); $backupSet = BackupSet::factory()->create(['tenant_id' => $tenant->id]); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); try { RestoreRunResource::createRestoreRun([ 'backup_set_id' => $backupSet->id, 'is_dry_run' => false, 'selected_items' => null, ]); $this->fail('Expected ValidationException to be thrown'); } catch (ValidationException) { // Expected } $auditLog = AuditLog::query() ->where('tenant_id', $tenant->id) ->where('action', 'intune_rbac.write_blocked') ->first(); expect($auditLog)->not->toBeNull() ->and($auditLog->status)->toBe('blocked') ->and($auditLog->actor_email)->toBe($user->email) ->and($auditLog->resource_type)->toBe('restore_run') ->and($auditLog->metadata)->toBeArray() ->and($auditLog->metadata['operation_type'])->toBe('restore.execute') ->and($auditLog->metadata['reason_code'])->toBe('intune_rbac.not_configured') ->and($auditLog->metadata['backup_set_id'])->toBe($backupSet->id); }); test('audit log does not contain token or credential fields', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $tenant->update([ 'rbac_status' => 'degraded', 'rbac_last_checked_at' => now(), ]); $backupSet = BackupSet::factory()->create(['tenant_id' => $tenant->id]); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); try { RestoreRunResource::createRestoreRun([ 'backup_set_id' => $backupSet->id, 'is_dry_run' => false, 'selected_items' => null, ]); $this->fail('Expected ValidationException to be thrown'); } catch (ValidationException) { // Expected } $auditLog = AuditLog::query() ->where('tenant_id', $tenant->id) ->where('action', 'intune_rbac.write_blocked') ->first(); expect($auditLog)->not->toBeNull(); $metadataJson = json_encode($auditLog->metadata); $sensitivePatterns = ['token', 'secret', 'password', 'credential', 'bearer', 'client_secret']; foreach ($sensitivePatterns as $pattern) { expect(str_contains(strtolower($metadataJson), $pattern))->toBeFalse( "Audit log metadata should not contain '{$pattern}'" ); } }); test('audit log records correct reason code for unhealthy status', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $tenant->update([ 'rbac_status' => 'failed', 'rbac_last_checked_at' => now(), ]); $backupSet = BackupSet::factory()->create(['tenant_id' => $tenant->id]); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); try { RestoreRunResource::createRestoreRun([ 'backup_set_id' => $backupSet->id, 'is_dry_run' => false, 'selected_items' => null, ]); $this->fail('Expected ValidationException to be thrown'); } catch (ValidationException) { // Expected } $auditLog = AuditLog::query() ->where('tenant_id', $tenant->id) ->where('action', 'intune_rbac.write_blocked') ->first(); expect($auditLog)->not->toBeNull() ->and($auditLog->metadata['reason_code'])->toBe('intune_rbac.unhealthy'); });