'#microsoft.graph.deviceAndAppManagementRoleDefinition', 'displayName' => 'Policy and Profile Manager', 'description' => 'Built-in RBAC role', 'isBuiltIn' => true, 'rolePermissions' => [ [ 'resourceActions' => [ [ 'allowedResourceActions' => [ 'Microsoft.Intune/deviceConfigurations/read', 'Microsoft.Intune/deviceConfigurations/create', ], 'notAllowedResourceActions' => [ 'Microsoft.Intune/deviceConfigurations/delete', ], ], ], ], ], 'roleScopeTagIds' => ['scope-1', '0'], ]; $result = $normalizer->normalize($snapshot, 'intuneRoleDefinition', 'all'); $summary = collect($result['settings'])->firstWhere('title', 'Role definition'); $permissionBlock = collect($result['settings'])->firstWhere('title', 'Permission block 1'); $summaryEntries = collect($summary['entries'] ?? [])->keyBy('key'); $permissionEntries = collect($permissionBlock['entries'] ?? [])->keyBy('key'); expect($result['status'])->toBe('ok'); expect($summaryEntries['Role source']['value'] ?? null)->toBe('Built-in'); expect($summaryEntries['Permission blocks']['value'] ?? null)->toBe(1); expect($summaryEntries['Scope tag IDs']['value'] ?? null)->toBe(['scope-1', '0']); expect($permissionEntries['Allowed actions']['value'] ?? null)->toBe([ 'Microsoft.Intune/deviceConfigurations/create', 'Microsoft.Intune/deviceConfigurations/read', ]); expect($permissionEntries['Denied actions']['value'] ?? null)->toBe([ 'Microsoft.Intune/deviceConfigurations/delete', ]); }); it('flattens custom intune role definitions deterministically regardless of permission block order', function (): void { $normalizer = app(IntuneRoleDefinitionNormalizer::class); $firstSnapshot = [ 'displayName' => 'Custom RBAC Role', 'isBuiltIn' => false, 'rolePermissions' => [ [ 'resourceActions' => [ [ 'allowedResourceActions' => [ 'Microsoft.Intune/deviceCompliancePolicies/read', ], ], ], ], [ 'resourceActions' => [ [ 'allowedResourceActions' => [ 'Microsoft.Intune/deviceConfigurations/read', ], 'condition' => '@Resource[Microsoft.Intune/deviceConfigurations] Exists', ], ], ], ], ]; $secondSnapshot = [ 'displayName' => 'Custom RBAC Role', 'isBuiltIn' => false, 'rolePermissions' => [ $firstSnapshot['rolePermissions'][1], $firstSnapshot['rolePermissions'][0], ], ]; expect($normalizer->flattenForDiff($firstSnapshot, 'intuneRoleDefinition', 'all')) ->toBe($normalizer->flattenForDiff($secondSnapshot, 'intuneRoleDefinition', 'all')); });