diff --git a/CHANGELOG.md b/CHANGELOG.md index a176154..c583b75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,19 @@ All notable changes to this project will be documented in this file.
--> +## [UNRELEASED] + +### Summary +- _None_ + +### What's Changed +- feat(Get-AdoMembership): Add list support + +### Breaking Changes +- _None_ + +
+ ## [0.4.1] - 2026-02-17 ### Summary diff --git a/docs/Get-AdoMembership.md b/docs/Get-AdoMembership.md index e420dad..5f97637 100644 --- a/docs/Get-AdoMembership.md +++ b/docs/Get-AdoMembership.md @@ -13,17 +13,24 @@ title: Get-AdoMembership ## SYNOPSIS -Get the membership relationship between a subject and a container in Azure DevOps. +Get membership relationships ## SYNTAX -### __AllParameterSets +### GetMembership ```text Get-AdoMembership [[-CollectionUri] ] [-SubjectDescriptor] [-ContainerDescriptor] [[-Version] ] [] ``` +### ListMemberships + +```text +Get-AdoMembership [[-CollectionUri] ] [-SubjectDescriptor] + [[-Depth] ] [[-Direction] ] [[-Version] ] [] +``` + ## ALIASES This cmdlet has the following aliases, @@ -31,7 +38,8 @@ This cmdlet has the following aliases, ## DESCRIPTION -This cmdlet retrieves the membership relationship between a specified subject and container in Azure DevOps. +This cmdlet retrieves the membership relationships between a specified subject and container in Azure DevOps or +get all the memberships where this descriptor is a member in the relationship. ## EXAMPLES @@ -64,6 +72,38 @@ $params = @{ Retrieves the membership relationships for multiple subjects demonstrating pipeline input. +### EXAMPLE 3 + +#### PowerShell + +```powershell +$params = @{ + CollectionUri = 'https://vssps.dev.azure.com/my-org' + SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000' + Depth = 2 + Direction = 'up' +} +Get-AdoMembership @params +``` + +Retrieves all groups for a user with a depth of 2. + +### EXAMPLE 4 + +#### PowerShell + +```powershell +$params = @{ + CollectionUri = 'https://vssps.dev.azure.com/my-org' + SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000' + Depth = 2 + Direction = 'down' +} +Get-AdoMembership @params +``` + +Retrieves all memberships of a group with a depth of 2. + ## PARAMETERS ### -CollectionUri @@ -112,7 +152,7 @@ HelpMessage: '' ### -ContainerDescriptor -Mandatory. +Optional. A descriptor to the container in the relationship. ```yaml @@ -121,9 +161,32 @@ DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: -- Name: (All) +- Name: GetMembership Position: Named - IsRequired: true + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: true + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -Depth + +Optional. +The depth of memberships to retrieve when ContainerDescriptor is not specified. +Default is 1. + +```yaml +Type: System.Int32 +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: ListMemberships + Position: Named + IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: true ValueFromRemainingArguments: false @@ -132,15 +195,42 @@ AcceptedValues: [] HelpMessage: '' ``` +### -Direction + +Optional. +The direction of memberships to retrieve when ContainerDescriptor is not specified. + +The default value for direction is 'up' meaning return all memberships where the subject is a member (e.g. all groups the subject is a member of). +Alternatively, passing the direction as 'down' will return all memberships where the subject is a container (e.g. all members of the subject group). + +```yaml +Type: System.String +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: ListMemberships + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: true + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: +- up +- down +HelpMessage: '' +``` + ### -Version Optional. The API version to use for the request. -Default is '7.2-preview.1'. +Default is '7.1-preview.1'. ```yaml Type: System.String -DefaultValue: 7.2-preview.1 +DefaultValue: 7.1-preview.1 SupportsWildcards: false Aliases: - ApiVersion @@ -153,6 +243,7 @@ ParameterSets: ValueFromRemainingArguments: false DontShow: false AcceptedValues: +- 7.1-preview.1 - 7.2-preview.1 HelpMessage: '' ``` @@ -180,3 +271,4 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## RELATED LINKS - +- diff --git a/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1 b/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1 index 33af535..1d0c7b4 100644 --- a/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1 +++ b/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1 @@ -1,10 +1,11 @@ function Get-AdoMembership { <# .SYNOPSIS - Get the membership relationship between a subject and a container in Azure DevOps. + Get membership relationships .DESCRIPTION - This cmdlet retrieves the membership relationship between a specified subject and container in Azure DevOps. + This cmdlet retrieves the membership relationships between a specified subject and container in Azure DevOps or + get all the memberships where this descriptor is a member in the relationship. .PARAMETER CollectionUri Optional. The collection URI of the Azure DevOps collection/organization, e.g., https://vssps.dev.azure.com/my-org. @@ -13,16 +14,26 @@ Mandatory. A descriptor to the child subject in the relationship. .PARAMETER ContainerDescriptor - Mandatory. A descriptor to the container in the relationship. + Optional. A descriptor to the container in the relationship. + + .PARAMETER Depth + Optional. The depth of memberships to retrieve when ContainerDescriptor is not specified. Default is 1. + + .PARAMETER Direction + Optional. The direction of memberships to retrieve when ContainerDescriptor is not specified. + + The default value for direction is 'up' meaning return all memberships where the subject is a member (e.g. all groups the subject is a member of). + Alternatively, passing the direction as 'down' will return all memberships where the subject is a container (e.g. all members of the subject group). .PARAMETER Version - Optional. The API version to use for the request. Default is '7.1'. + Optional. The API version to use for the request. Default is '7.1-preview.1'. .OUTPUTS PSCustomObject .LINK - https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/get + - https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/get + - https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/list .EXAMPLE $params = @{ @@ -42,6 +53,28 @@ @('aadgp.00000000-0000-0000-0000-000000000002', 'aadgp.00000000-0000-0000-0000-000000000003') | Get-AdoMembership @params Retrieves the membership relationships for multiple subjects demonstrating pipeline input. + + .EXAMPLE + $params = @{ + CollectionUri = 'https://vssps.dev.azure.com/my-org' + SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000' + Depth = 2 + Direction = 'up' + } + Get-AdoMembership @params + + Retrieves all groups for a user with a depth of 2. + + .EXAMPLE + $params = @{ + CollectionUri = 'https://vssps.dev.azure.com/my-org' + SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000' + Depth = 2 + Direction = 'down' + } + Get-AdoMembership @params + + Retrieves all memberships of a group with a depth of 2. #> [CmdletBinding()] param ( @@ -52,9 +85,16 @@ [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)] [string[]]$SubjectDescriptor, - [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'GetMembership')] [string]$ContainerDescriptor, + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ListMemberships')] + [int32]$Depth, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ListMemberships')] + [ValidateSet('up', 'down')] + [string]$Direction, + [Parameter()] [Alias('ApiVersion')] [ValidateSet('7.1-preview.1', '7.2-preview.1')] @@ -66,6 +106,8 @@ Write-Debug ("CollectionUri: $CollectionUri") Write-Debug ("SubjectDescriptor: $($SubjectDescriptor -join ',')") Write-Debug ("ContainerDescriptor: $ContainerDescriptor") + Write-Debug ("Depth: $Depth") + Write-Debug ("Direction: $Direction") Write-Debug ("Version: $Version") Confirm-Default -Defaults ([ordered]@{ @@ -79,18 +121,38 @@ process { try { + $queryParameters = [List[string]]::new() + + if ($ContainerDescriptor) { + $uri = "$CollectionUri/_apis/graph/memberships/$SubjectDescriptor/$ContainerDescriptor" + } else { + $uri = "$CollectionUri/_apis/graph/memberships/$SubjectDescriptor" + + if ($Depth) { + $queryParameters.Add("depth=$Depth") + } + if ($Direction) { + $queryParameters.Add("direction=$Direction") + } + } + $params = @{ - Uri = "$CollectionUri/_apis/graph/memberships/$SubjectDescriptor/$ContainerDescriptor" - Version = $Version - Method = 'GET' + Uri = $uri + Version = $Version + QueryParameters = if ($queryParameters.Count -gt 0) { $queryParameters -join '&' } else { $null } + Method = 'GET' } - $result = Invoke-AdoRestMethod @params + $results = Invoke-AdoRestMethod @params + $memberships = if ($ContainerDescriptor) { @($results) } else { $results.value } - [PSCustomObject]@{ - memberDescriptor = $result.memberDescriptor - containerDescriptor = $result.containerDescriptor - collectionUri = $CollectionUri + foreach ($m_ in $memberships) { + $obj = [ordered]@{ + containerDescriptor = $m_.containerDescriptor + memberDescriptor = $m_.memberDescriptor + collectionUri = $CollectionUri + } + [PSCustomObject]$obj } } catch { diff --git a/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1 b/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1 index 469c795..fa642fb 100644 --- a/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1 +++ b/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1 @@ -21,6 +21,19 @@ Describe 'Get-AdoMembership' { memberDescriptor = 'aadgp.00000000-0000-0000-0000-000000000001' containerDescriptor = 'vssgp.00000000-0000-0000-0000-000000000002' } + + $mockMembershipsList = [PSCustomObject]@{ + value = @( + [PSCustomObject]@{ + memberDescriptor = 'aadgp.00000000-0000-0000-0000-000000000001' + containerDescriptor = 'vssgp.00000000-0000-0000-0000-000000000003' + } + [PSCustomObject]@{ + memberDescriptor = 'aadgp.00000000-0000-0000-0000-000000000001' + containerDescriptor = 'vssgp.00000000-0000-0000-0000-000000000004' + } + ) + } } Context 'Core Functionality Tests' { @@ -137,6 +150,83 @@ Describe 'Get-AdoMembership' { } } + Context 'ListMemberships Parameter Set Tests' { + BeforeEach { + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { return $mockMembershipsList } + Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { } + Mock -ModuleName Azure.DevOps.PSModule Start-Sleep { } + } + + It 'Should retrieve memberships without ContainerDescriptor' { + # Act + $result = Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up' + + # Assert + $result | Should -Not -BeNullOrEmpty + $result | Should -HaveCount 2 + } + + It 'Should construct API URI correctly without ContainerDescriptor' { + # Act + Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up' + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Uri -eq "$mockCollectionUri/_apis/graph/memberships/$mockSubjectDescriptor" + } + } + + It 'Should include Depth query parameter when specified' { + # Act + Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Depth 2 + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $QueryParameters -eq 'depth=2' + } + } + + It 'Should include Direction query parameter when specified' { + # Act + Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up' + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $QueryParameters -eq 'direction=up' + } + } + + It 'Should include both Depth and Direction query parameters when specified' { + # Act + Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Depth 2 -Direction 'down' + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $QueryParameters -eq 'depth=2&direction=down' + } + } + + It 'Should accept Direction value "up"' { + # Act & Assert + { Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up' } | Should -Not -Throw + } + + It 'Should accept Direction value "down"' { + # Act & Assert + { Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'down' } | Should -Not -Throw + } + + It 'Should return multiple memberships from value array' { + # Act + $result = Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Depth 2 + + # Assert + $result | Should -HaveCount 2 + $result[0].memberDescriptor | Should -Be $mockSubjectDescriptor + $result[1].memberDescriptor | Should -Be $mockSubjectDescriptor + } + } + Context 'Error Handling Tests' { BeforeEach { Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { } @@ -225,12 +315,73 @@ Describe 'Get-AdoMembership' { $param.Attributes.Mandatory | Should -Contain $true } - It 'Should have ContainerDescriptor as mandatory parameter' { + It 'Should have ContainerDescriptor as optional parameter' { # Arrange $param = (Get-Command Get-AdoMembership).Parameters['ContainerDescriptor'] # Assert - $param.Attributes.Mandatory | Should -Contain $true + $param.Attributes.Where({ $_.GetType().Name -eq 'ParameterAttribute' }).Mandatory | Should -Contain $false + } + + It 'Should have Depth as optional parameter' { + # Arrange + $param = (Get-Command Get-AdoMembership).Parameters['Depth'] + + # Assert + $param.Attributes.Where({ $_.GetType().Name -eq 'ParameterAttribute' }).Mandatory | Should -Contain $false + } + + It 'Should have Direction as optional parameter' { + # Arrange + $param = (Get-Command Get-AdoMembership).Parameters['Direction'] + + # Assert + $param.Attributes.Where({ $_.GetType().Name -eq 'ParameterAttribute' }).Mandatory | Should -Contain $false + } + } + + Context 'Parameter Set Tests' { + It 'Should have GetMembership parameter set' { + # Arrange + $paramSets = (Get-Command Get-AdoMembership).ParameterSets + + # Assert + $paramSets.Name | Should -Contain 'GetMembership' + } + + It 'Should have ListMemberships parameter set' { + # Arrange + $paramSets = (Get-Command Get-AdoMembership).ParameterSets + + # Assert + $paramSets.Name | Should -Contain 'ListMemberships' + } + + It 'Should have ContainerDescriptor in GetMembership parameter set' { + # Arrange + $paramSet = (Get-Command Get-AdoMembership).ParameterSets | Where-Object { $_.Name -eq 'GetMembership' } + $param = $paramSet.Parameters | Where-Object { $_.Name -eq 'ContainerDescriptor' } + + # Assert + $param | Should -Not -BeNullOrEmpty + } + + It 'Should have Depth in ListMemberships parameter set' { + # Arrange + $paramSet = (Get-Command Get-AdoMembership).ParameterSets | Where-Object { $_.Name -eq 'ListMemberships' } + $param = $paramSet.Parameters | Where-Object { $_.Name -eq 'Depth' } + + # Assert + $param | Should -Not -BeNullOrEmpty + } + + It 'Should have Direction in ListMemberships parameter set' { + # Arrange + $paramSet = (Get-Command Get-AdoMembership).ParameterSets | Where-Object { $_.Name -eq 'ListMemberships' } + $param = $paramSet.Parameters | Where-Object { $_.Name -eq 'Direction' } + + # Assert + $param | Should -Not -BeNullOrEmpty } } }