}> */ public array $applyPolicyCalls = []; /** @var array}> */ public array $requestCalls = []; /** * @param array $requestMap */ public function __construct( private readonly GraphResponse $applyPolicyResponse, private readonly array $requestMap = [], ) {} public function listPolicies(string $policyType, array $options = []): GraphResponse { return new GraphResponse(true, []); } public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse { return new GraphResponse(true, ['payload' => []]); } public function getOrganization(array $options = []): GraphResponse { return new GraphResponse(true, []); } public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse { $this->applyPolicyCalls[] = [ 'policyType' => $policyType, 'policyId' => $policyId, 'payload' => $payload, 'options' => $options, ]; return $this->applyPolicyResponse; } public function getServicePrincipalPermissions(array $options = []): GraphResponse { return new GraphResponse(true, []); } public function request(string $method, string $path, array $options = []): GraphResponse { $this->requestCalls[] = [ 'method' => strtoupper($method), 'path' => $path, 'options' => $options, ]; foreach ($this->requestMap as $needle => $response) { if (is_string($needle) && $needle !== '' && str_contains($path, $needle)) { return $response; } } return new GraphResponse(true, []); } } test('restore executes endpoint security policy settings via settings endpoint', function () { $client = new EndpointSecurityRestoreGraphClient(new GraphResponse(true, [])); app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::factory()->create(); $backupSet = BackupSet::factory()->for($tenant)->create([ 'status' => 'completed', 'item_count' => 1, ]); $backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([ 'policy_id' => null, 'policy_identifier' => 'esp-1', 'policy_type' => 'endpointSecurityPolicy', 'platform' => 'windows', 'payload' => [ 'id' => 'esp-1', '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy', 'name' => 'Endpoint Security Policy', 'platforms' => ['windows10'], 'technologies' => ['endpointSecurity'], 'templateReference' => [ 'templateId' => 'template-1', 'templateFamily' => 'endpointSecurityFirewall', 'templateDisplayName' => 'Windows Firewall Rules', 'templateDisplayVersion' => 'Version 1', ], 'settings' => [ [ 'id' => 's1', 'settingInstance' => [ '@odata.type' => '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance', 'settingDefinitionId' => 'device_vendor_msft_policy_config_defender_allowrealtimemonitoring', 'simpleSettingValue' => [ 'value' => 1, ], ], ], ], ], 'assignments' => null, ]); $service = app(RestoreService::class); $run = $service->execute( tenant: $tenant, backupSet: $backupSet, selectedItemIds: [$backupItem->id], dryRun: false, ); expect($run->status)->toBe('completed'); expect($client->applyPolicyCalls)->toHaveCount(1); expect($client->applyPolicyCalls[0]['policyType'])->toBe('endpointSecurityPolicy'); expect($client->applyPolicyCalls[0]['payload'])->not->toHaveKey('settings'); $settingsCalls = collect($client->requestCalls) ->filter(fn (array $call) => $call['method'] === 'POST' && str_contains($call['path'], '/settings')) ->values(); expect($settingsCalls)->toHaveCount(1); expect($settingsCalls[0]['path'])->toContain('deviceManagement/configurationPolicies/esp-1/settings'); $body = $settingsCalls[0]['options']['json'] ?? null; expect($body)->toBeArray()->not->toBeEmpty(); expect($body[0]['settingInstance']['settingDefinitionId'] ?? null) ->toBe('device_vendor_msft_policy_config_defender_allowrealtimemonitoring'); }); test('restore fails when endpoint security template is missing', function () { $applyNotFound = new GraphResponse(false, ['error' => ['message' => 'Not found']], 404, [], [], [ 'error_code' => 'NotFound', 'error_message' => 'Not found', ]); $templateNotFound = new GraphResponse(false, ['error' => ['message' => 'Template missing']], 404, [], [], [ 'error_code' => 'NotFound', 'error_message' => 'Template missing', ]); $client = new EndpointSecurityRestoreGraphClient($applyNotFound, [ 'configurationPolicyTemplates' => $templateNotFound, ]); app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::factory()->create(); $backupSet = BackupSet::factory()->for($tenant)->create([ 'status' => 'completed', 'item_count' => 1, ]); $backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([ 'policy_id' => null, 'policy_identifier' => 'esp-missing', 'policy_type' => 'endpointSecurityPolicy', 'platform' => 'windows', 'payload' => [ 'id' => 'esp-missing', '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy', 'name' => 'Endpoint Security Policy', 'platforms' => ['windows10'], 'technologies' => ['endpointSecurity'], 'templateReference' => [ 'templateId' => 'missing-template', ], 'settings' => [ [ 'id' => 's1', 'settingInstance' => [ '@odata.type' => '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance', 'settingDefinitionId' => 'device_vendor_msft_policy_config_defender_allowrealtimemonitoring', 'simpleSettingValue' => [ 'value' => 1, ], ], ], ], ], 'assignments' => null, ]); $service = app(RestoreService::class); $run = $service->execute( tenant: $tenant, backupSet: $backupSet, selectedItemIds: [$backupItem->id], dryRun: false, ); expect($run->status)->toBe('failed'); $createCalls = collect($client->requestCalls) ->filter(fn (array $call) => $call['method'] === 'POST' && $call['path'] === 'deviceManagement/configurationPolicies') ->values(); expect($createCalls)->toHaveCount(0); }); test('restore risk checks flag missing endpoint security templates as blocking', function () { $templateNotFound = new GraphResponse(false, ['error' => ['message' => 'Template missing']], 404, [], [], [ 'error_code' => 'NotFound', 'error_message' => 'Template missing', ]); $client = new EndpointSecurityRestoreGraphClient(new GraphResponse(true, []), [ 'configurationPolicyTemplates' => $templateNotFound, ]); app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::factory()->create(); $backupSet = BackupSet::factory()->for($tenant)->create([ 'status' => 'completed', 'item_count' => 1, ]); $backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([ 'policy_id' => null, 'policy_identifier' => 'esp-missing', 'policy_type' => 'endpointSecurityPolicy', 'platform' => 'windows', 'payload' => [ 'id' => 'esp-missing', '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy', 'templateReference' => [ 'templateId' => 'missing-template', 'templateFamily' => 'endpointSecurityFirewall', ], 'settings' => [], ], 'assignments' => null, ]); $checker = app(RestoreRiskChecker::class); $result = $checker->check( tenant: $tenant, backupSet: $backupSet, selectedItemIds: [$backupItem->id], groupMapping: [], ); $results = collect($result['results'] ?? []); $templateCheck = $results->firstWhere('code', 'endpoint_security_templates'); expect($templateCheck)->not->toBeNull(); expect($templateCheck['severity'] ?? null)->toBe('blocking'); });