*/ public array $requestCalls = []; /** * @param array $requestResponses */ public function __construct( private readonly GraphResponse $applyPolicyResponse, private array $requestResponses = [], ) {} 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 { 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, 'payload' => $options['json'] ?? null, ]; return array_shift($this->requestResponses) ?? new GraphResponse(true, []); } } test('restore applies assignments with mapped groups', function () { $applyResponse = new GraphResponse(true, []); $requestResponses = [ new GraphResponse(true, []), // assign action ]; $client = new RestoreAssignmentGraphClient($applyResponse, $requestResponses); app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::create([ 'tenant_id' => 'tenant-1', 'name' => 'Tenant One', 'metadata' => [], ]); $policy = Policy::create([ 'tenant_id' => $tenant->id, 'external_id' => 'scp-1', 'policy_type' => 'settingsCatalogPolicy', 'display_name' => 'Settings Catalog Alpha', 'platform' => 'windows', ]); $backupSet = BackupSet::create([ 'tenant_id' => $tenant->id, 'name' => 'Backup', 'status' => 'completed', 'item_count' => 1, ]); $backupItem = BackupItem::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'policy_id' => $policy->id, 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'payload' => ['id' => $policy->external_id, '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy'], 'assignments' => [ [ 'id' => 'assignment-1', 'intent' => 'apply', 'target' => [ '@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'source-group-1', 'group_display_name' => 'Source One', ], ], [ 'id' => 'assignment-2', 'intent' => 'apply', 'target' => [ '@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'source-group-2', ], ], ], ]); $user = User::factory()->create(['email' => 'tester@example.com']); $this->actingAs($user); $service = app(RestoreService::class); $run = $service->execute( tenant: $tenant, backupSet: $backupSet, selectedItemIds: [$backupItem->id], dryRun: false, actorEmail: $user->email, actorName: $user->name, groupMapping: [ 'source-group-1' => 'target-group-1', 'source-group-2' => 'target-group-2', ], ); $summary = $run->results[0]['assignment_summary'] ?? null; expect($summary)->not->toBeNull(); expect($summary['success'])->toBe(2); expect($summary['failed'])->toBe(0); $postCalls = collect($client->requestCalls) ->filter(fn (array $call) => $call['method'] === 'POST') ->values(); expect($postCalls)->toHaveCount(1); expect($postCalls[0]['path'])->toBe('/deviceManagement/configurationPolicies/scp-1/assign'); $payloadAssignments = $postCalls[0]['payload']['assignments'] ?? []; $groupIds = collect($payloadAssignments)->pluck('target.groupId')->all(); expect($groupIds)->toBe(['target-group-1', 'target-group-2']); expect($payloadAssignments[0])->not->toHaveKey('id'); }); test('restore handles assignment failures gracefully', function () { $applyResponse = new GraphResponse(true, []); $requestResponses = [ new GraphResponse(false, ['error' => ['message' => 'Bad request']], 400, [ ['code' => 'BadRequest', 'message' => 'Bad request'], ], [], [ 'error_code' => 'BadRequest', 'error_message' => 'Bad request', ]), // assign action fails ]; $client = new RestoreAssignmentGraphClient($applyResponse, $requestResponses); app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::create([ 'tenant_id' => 'tenant-1', 'name' => 'Tenant One', 'metadata' => [], ]); $policy = Policy::create([ 'tenant_id' => $tenant->id, 'external_id' => 'scp-1', 'policy_type' => 'settingsCatalogPolicy', 'display_name' => 'Settings Catalog Alpha', 'platform' => 'windows', ]); $backupSet = BackupSet::create([ 'tenant_id' => $tenant->id, 'name' => 'Backup', 'status' => 'completed', 'item_count' => 1, ]); $backupItem = BackupItem::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'policy_id' => $policy->id, 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'payload' => ['id' => $policy->external_id, '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy'], 'assignments' => [ [ 'id' => 'assignment-1', 'intent' => 'apply', 'target' => [ '@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'source-group-1', ], ], [ 'id' => 'assignment-2', 'intent' => 'apply', 'target' => [ '@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'source-group-2', ], ], ], ]); $user = User::factory()->create(['email' => 'tester@example.com']); $this->actingAs($user); $service = app(RestoreService::class); $run = $service->execute( tenant: $tenant, backupSet: $backupSet, selectedItemIds: [$backupItem->id], dryRun: false, actorEmail: $user->email, actorName: $user->name, groupMapping: [ 'source-group-1' => 'target-group-1', 'source-group-2' => 'target-group-2', ], ); $summary = $run->results[0]['assignment_summary'] ?? null; expect($summary)->not->toBeNull(); expect($summary['success'])->toBe(0); expect($summary['failed'])->toBe(2); expect($run->results[0]['status'])->toBe('partial'); });