$responses */ public function __construct(private array $responses = []) {} public function listPolicies(string $policyType, array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function getOrganization(array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function getServicePrincipalPermissions(array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function request(string $method, string $path, array $options = []): GraphResponse { $this->requests[] = [ 'method' => $method, 'path' => $path, 'options' => $options, ]; return array_shift($this->responses) ?? new GraphResponse(success: true, data: []); } } it('maps existing foundations by display name', function () { $tenant = Tenant::factory()->create(); $backupSet = BackupSet::factory()->for($tenant)->create(); $item = BackupItem::factory() ->for($tenant) ->for($backupSet) ->state([ 'policy_id' => null, 'policy_identifier' => 'filter-1', 'policy_type' => 'assignmentFilter', 'platform' => 'all', 'payload' => [ 'id' => 'filter-1', 'displayName' => 'Filter One', ], 'metadata' => [ 'displayName' => 'Filter One', ], ]) ->create(); $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) { $mock->shouldReceive('fetchAll') ->once() ->andReturn([ 'items' => [ [ 'source_id' => 'filter-2', 'display_name' => 'Filter One', 'payload' => [], 'metadata' => [], ], ], 'failures' => [], ]); }); $client = new FoundationMappingGraphClient; app()->instance(GraphClientInterface::class, $client); $service = app(FoundationMappingService::class); $result = $service->map($tenant, collect([$item]), false); expect($result['failed'])->toBe(0); expect($result['skipped'])->toBe(0); expect($result['mapping'])->toBe(['filter-1' => 'filter-2']); expect($result['entries'])->toHaveCount(1); expect($result['entries'][0]['decision'])->toBe('mapped_existing'); expect($result['entries'][0]['targetId'])->toBe('filter-2'); expect($result['entries'][0]['sourceName'])->toBe('Filter One'); }); it('creates missing foundations when executing', function () { config()->set('graph_contracts.types.assignmentFilter', [ 'resource' => 'deviceManagement/assignmentFilters', 'create_method' => 'POST', 'update_strip_keys' => ['isBuiltIn'], ]); $tenant = Tenant::factory()->create([ 'tenant_id' => 'tenant-1', 'app_client_id' => 'client-1', 'app_client_secret' => 'secret-1', ]); $backupSet = BackupSet::factory()->for($tenant)->create(); $item = BackupItem::factory() ->for($tenant) ->for($backupSet) ->state([ 'policy_id' => null, 'policy_identifier' => 'filter-1', 'policy_type' => 'assignmentFilter', 'platform' => 'all', 'payload' => [ 'id' => 'filter-1', '@odata.type' => '#microsoft.graph.deviceAndAppManagementAssignmentFilter', 'displayName' => 'Filter One', 'isBuiltIn' => false, ], 'metadata' => [ 'displayName' => 'Filter One', ], ]) ->create(); $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) { $mock->shouldReceive('fetchAll') ->once() ->andReturn([ 'items' => [ [ 'source_id' => 'filter-2', 'display_name' => 'Filter One', 'payload' => [], 'metadata' => [], ], [ 'source_id' => 'filter-3', 'display_name' => 'Filter One', 'payload' => [], 'metadata' => [], ], ], 'failures' => [], ]); }); $client = new FoundationMappingGraphClient([ new GraphResponse(true, [ 'id' => 'filter-99', 'displayName' => 'Filter One (Copy)', ]), ]); app()->instance(GraphClientInterface::class, $client); $service = app(FoundationMappingService::class); $result = $service->map($tenant, collect([$item]), true); expect($result['mapping'])->toBe(['filter-1' => 'filter-99']); expect($result['entries'][0]['decision'])->toBe('created_copy'); expect($result['entries'][0]['targetName'])->toBe('Filter One (Copy)'); expect($client->requests)->toHaveCount(1); expect($client->requests[0]['method'])->toBe('POST'); expect($client->requests[0]['path'])->toBe('deviceManagement/assignmentFilters'); $payload = $client->requests[0]['options']['json']; expect($payload['displayName'])->toBe('Filter One (Copy)'); expect($payload)->not->toHaveKey('id'); expect($payload)->not->toHaveKey('@odata.type'); expect($payload)->not->toHaveKey('isBuiltIn'); }); it('skips built-in scope tags', function () { $tenant = Tenant::factory()->create(); $backupSet = BackupSet::factory()->for($tenant)->create(); $item = BackupItem::factory() ->for($tenant) ->for($backupSet) ->state([ 'policy_id' => null, 'policy_identifier' => '0', 'policy_type' => 'roleScopeTag', 'platform' => 'all', 'payload' => [ 'id' => '0', 'displayName' => 'Default', 'isBuiltIn' => true, ], 'metadata' => [ 'displayName' => 'Default', ], ]) ->create(); $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) { $mock->shouldReceive('fetchAll') ->once() ->andReturn([ 'items' => [], 'failures' => [], ]); }); $client = new FoundationMappingGraphClient; app()->instance(GraphClientInterface::class, $client); $service = app(FoundationMappingService::class); $result = $service->map($tenant, collect([$item]), false); expect($result['skipped'])->toBe(1); expect($result['entries'][0]['decision'])->toBe('skipped'); expect($result['entries'][0]['reason'])->toBe('Built-in scope tag cannot be created.'); }); it('marks failures when foundation listing fails', function () { $tenant = Tenant::factory()->create(); $backupSet = BackupSet::factory()->for($tenant)->create(); $item = BackupItem::factory() ->for($tenant) ->for($backupSet) ->state([ 'policy_id' => null, 'policy_identifier' => 'filter-1', 'policy_type' => 'assignmentFilter', 'platform' => 'all', 'payload' => [ 'id' => 'filter-1', 'displayName' => 'Filter One', ], 'metadata' => [ 'displayName' => 'Filter One', ], ]) ->create(); $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) { $mock->shouldReceive('fetchAll') ->once() ->andReturn([ 'items' => [], 'failures' => [ [ 'foundation_type' => 'assignmentFilter', 'reason' => 'Graph failure', 'status' => 500, ], ], ]); }); $client = new FoundationMappingGraphClient; app()->instance(GraphClientInterface::class, $client); $service = app(FoundationMappingService::class); $result = $service->map($tenant, collect([$item]), false); expect($result['failed'])->toBe(1); expect($result['entries'][0]['decision'])->toBe('failed'); expect($result['entries'][0]['reason'])->toBe('Graph failure'); });