'#microsoft.graph.deviceAndAppManagementRoleAssignment', 'displayName' => 'Helpdesk Assignment', 'description' => 'Delegated access for helpdesk operators', 'scopeType' => 'allDevicesAssignment', 'members' => [ ['displayName' => 'Helpdesk Group', 'id' => 'group-1'], 'group-2', ], 'scopeMembers' => [ ['displayName' => 'Berlin Devices', 'id' => 'scope-group-1'], ], 'resourceScopes' => [ '/deviceManagement/managedDevices', '/', ], 'roleDefinition' => [ 'id' => 'role-1', 'displayName' => 'Policy and Profile Manager', ], ]; $result = $normalizer->normalize($snapshot, 'intuneRoleAssignment', 'all'); $summary = collect($result['settings'])->firstWhere('title', 'Role assignment'); $members = collect($result['settings'])->firstWhere('title', 'Members'); $scopeMembers = collect($result['settings'])->firstWhere('title', 'Scope members'); $resourceScopes = collect($result['settings'])->firstWhere('title', 'Resource scopes'); $summaryEntries = collect($summary['entries'] ?? [])->keyBy('key'); expect($result['status'])->toBe('ok'); expect($summaryEntries['Role definition']['value'] ?? null)->toBe('Policy and Profile Manager (role-1)'); expect($summaryEntries['Members count']['value'] ?? null)->toBe(2); expect($summaryEntries['Scope members count']['value'] ?? null)->toBe(1); expect($summaryEntries['Resource scopes count']['value'] ?? null)->toBe(2); expect($members['entries'][0]['value'] ?? null)->toBe([ 'Helpdesk Group (group-1)', 'group-2', ]); expect($scopeMembers['entries'][0]['value'] ?? null)->toBe([ 'Berlin Devices (scope-group-1)', ]); expect($resourceScopes['entries'][0]['value'] ?? null)->toBe([ '/', '/deviceManagement/managedDevices', ]); }); it('uses identifier fallbacks and stable ordering when expanded role assignment data is incomplete', function (): void { $normalizer = app(IntuneRoleAssignmentNormalizer::class); $firstSnapshot = [ 'displayName' => 'Fallback Assignment', 'members' => ['group-2', 'group-1'], 'scopeMembers' => [ ['id' => 'scope-2'], ['id' => 'scope-1'], ], 'resourceScopes' => ['/b', '/a'], 'roleDefinition' => [ 'id' => 'role-fallback', ], ]; $secondSnapshot = [ 'displayName' => 'Fallback Assignment', 'members' => ['group-1', 'group-2'], 'scopeMembers' => [ ['id' => 'scope-1'], ['id' => 'scope-2'], ], 'resourceScopes' => ['/a', '/b'], 'roleDefinition' => [ 'id' => 'role-fallback', ], ]; $normalized = $normalizer->normalize($firstSnapshot, 'intuneRoleAssignment', 'all'); $summary = collect($normalized['settings'])->firstWhere('title', 'Role assignment'); $summaryEntries = collect($summary['entries'] ?? [])->keyBy('key'); expect($normalized['status'])->toBe('warning'); expect($normalized['warnings'])->toContain('Role definition display name unavailable; using identifier fallback.'); expect($summaryEntries['Role definition']['value'] ?? null)->toBe('role-fallback'); expect($normalizer->flattenForDiff($firstSnapshot, 'intuneRoleAssignment', 'all')) ->toBe($normalizer->flattenForDiff($secondSnapshot, 'intuneRoleAssignment', 'all')); });