Skip to content

Commit

Permalink
Added loop functionality to Get-JiraGroupMember.
Browse files Browse the repository at this point in the history
This function will now loop through and return all members of the given JIRA group by default, rather than stopping at 50 members.  Fixes #14.
  • Loading branch information
replicaJunction committed Dec 9, 2015
1 parent 152d7a9 commit 7cac9ed
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 114 deletions.
246 changes: 149 additions & 97 deletions PSJira/Functions/Get-JiraGroupMember.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,49 +1,127 @@
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"

InModuleScope PSJira {

$showMockData = $false

$jiraServer = 'http://jiraserver.example.com'

$testUsername = 'powershell-test'
$testEmail = "$testUsername@example.com"

$testGroupName = 'Test Group'
$testGroupNameEscaped = [System.Web.HttpUtility]::UrlPathEncode($testGroupName)
$testGroupSize = 1

# The REST result returned by the interan call within Get-JiraGroup
$restResultNoUsers = @"
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"

InModuleScope PSJira {

$ShowMockData = $false
$ShowDebugText = $false

Describe "Get-JiraGroupMember" {
if ($ShowDebugText)
{
Mock "Write-Debug" {
Write-Host " [DEBUG] $Message" -ForegroundColor Yellow
}
}

Mock Get-JiraConfigServer {
'https://jira.example.com'
}

# If we don't override this in a context or test, we don't want it to
# actually try to query a JIRA instance
Mock Invoke-JiraMethod {}

Mock Invoke-JiraMethod -ModuleName PSJira -ParameterFilter { $Method -eq 'Get' -and $URI -like '*/rest/api/*/group?groupname=testgroup*' } {
ConvertFrom-Json @'
{
"name": "$testGroupName",
"self": "$jiraServer/rest/api/2/group?groupname=$testGroupName",
"users": {
"size": $testGroupSize,
"items": [],
"max-results": 50,
"start-index": 0,
"end-index": 0
},
"expand": "users"
"Name": "testgroup",
"RestUrl": "https://jira.example.com/rest/api/2/group?groupname=testgroup",
"Size": 2
}
"@

$restResultWithUsers = @"
'@
}

Mock Get-JiraGroup -ModuleName PSJira {
$obj = [PSCustomObject] @{
'Name' = 'testgroup'
'RestUrl' = 'https://jira.example.com/rest/api/2/group?groupname=testgroup'
'Size' = 2
}
$obj.PSObject.TypeNames.Insert(0, 'PSJira.Group')
Write-Output $obj
}

Context "Sanity checking" {
$command = Get-Command -Name Get-JiraGroupMember

function defParam($name)
{
It "Has a -$name parameter" {
$command.Parameters.Item($name) | Should Not BeNullOrEmpty
}
}

defParam 'Group'
defParam 'StartIndex'
defParam 'MaxResults'
defParam 'Credential'
}

Context "Behavior testing" {
Mock Invoke-JiraMethod -ModuleName PSJira {
if ($ShowMockData)
{
Write-Host " Mocked Invoke-JiraMethod" -ForegroundColor Cyan
Write-Host " [Uri] $Uri" -ForegroundColor Cyan
Write-Host " [Method] $Method" -ForegroundColor Cyan
# Write-Host " [Body] $Body" -ForegroundColor Cyan
}
}

Mock Get-JiraUser -ModuleName PSJira {
[PSCustomObject] @{
'Name' = 'username'
}
}

It "Obtains members about a provided group in JIRA" {
{ Get-JiraGroupMember -Group testgroup } | Should Not Throw
Assert-MockCalled -CommandName Invoke-JiraMethod -ModuleName PSJira -Exactly -Times 1 -Scope It -ParameterFilter { $Method -eq 'Get' -and $URI -like '*/rest/api/*/group?groupname=testgroup&expand=users*' }
}

It "Supports the -StartIndex and -MaxResults parameters to page through search results" {
{ Get-JiraGroupMember -Group testgroup -StartIndex 10 -MaxResults 50 } | Should Not Throw
# Expected: expand=users[10:60] (start index of 10, last index of 10+50)
# https://docs.atlassian.com/jira/REST/6.4.12/#d2e2307
# Also, -like doesn't seem to "like" square brackets
Assert-MockCalled -CommandName Invoke-JiraMethod -ModuleName PSJira -Exactly -Times 1 -Scope It -ParameterFilter { $Method -eq 'Get' -and $URI -like '*/rest/api/*/group?groupname=testgroup&expand=users*10:60*' }
}

It "Returns all issues via looping if -MaxResults is not specified" {

# In order to test this, we'll need a slightly more elaborate
# mock that actually returns some data.

Mock Invoke-JiraMethod -ModuleName PSJira {
if ($ShowMockData)
{
Write-Host " Mocked Invoke-JiraMethod" -ForegroundColor Cyan
Write-Host " [Uri] $Uri" -ForegroundColor Cyan
Write-Host " [Method] $Method" -ForegroundColor Cyan
}
ConvertFrom-Json -InputObject @'
{
"name": "$testGroupName",
"self": "$jiraServer/rest/api/2/group?groupname=$testGroupName",
"name": "testgroup",
"self": "https://jira.example.com/rest/api/2/group?groupname=testgroup",
"users": {
"size": $testGroupSize,
"size": 2,
"items": [
{
"self": "$jiraServer/rest/api/2/user?username=$testUsername",
"key": "$testUsername",
"name": "$testUsername",
"emailAddress": "$testEmail",
"displayName": "Powershell Test User",
"self": "https://jira.example.com/rest/api/2/user?username=testuser1",
"key": "testuser1",
"name": "testuser1",
"emailAddress": "testuser1@example.com",
"displayName": "Test User 1",
"active": true
},
{
"self": "https://jira.example.com/rest/api/2/user?username=testuser2",
"key": "testuser2",
"name": "testuser2",
"emailAddress": "testuser2@example.com",
"displayName": "Test User 2",
"active": true
}
],
Expand All @@ -52,60 +130,34 @@ InModuleScope PSJira {
"end-index": 0
},
"expand": "users"
}
"@

Describe "Get-JiraGroupMember" {

Mock Get-JiraConfigServer -ModuleName PSJira {
Write-Output $jiraServer
}

Mock Get-JiraGroup -ModuleName PSJira {
ConvertTo-JiraGroup ( ConvertFrom-Json -InputObject $restResultNoUsers )
}

# This is called by Get-JiraGroupMember - user information included.
# Note that the URI is changed from "latest" to "2" since this is operating on the output from Get-JiraGroup,
# and JIRA never returns the "latest" symlink.
Mock Invoke-JiraMethod -ModuleName PSJira -ParameterFilter {$Method -eq 'Get' -and $URI -eq "$jiraServer/rest/api/2/group?groupname=$testGroupName&expand=users"} {
if ($ShowMockData)
{
Write-Host " Mocked Invoke-JiraMethod with GET method" -ForegroundColor Cyan
Write-Host " [Method] $Method" -ForegroundColor Cyan
Write-Host " [URI] $URI" -ForegroundColor Cyan
}
ConvertFrom-Json -InputObject $restResultWithUsers
}

# Generic catch-all. This will throw an exception if we forgot to mock something.
Mock Invoke-JiraMethod -ModuleName PSJira {
Write-Host " Mocked Invoke-JiraMethod with no parameter filter." -ForegroundColor DarkRed
Write-Host " [Method] $Method" -ForegroundColor DarkRed
Write-Host " [URI] $URI" -ForegroundColor DarkRed
throw "Unidentified call to Invoke-JiraMethod"
}

# Mock Write-Debug {
# Write-Host "DEBUG: $Message" -ForegroundColor Yellow
# }

#############
# Tests
#############

It "Returns the members of a given JIRA group" {
$members = Get-JiraGroupMember -Group $testGroupName
$members | Should Not BeNullOrEmpty
@($members).Count | Should Be $testGroupSize
}

It "Returns results as PSJira.User objects" {
$members = Get-JiraGroupMember -Group $testGroupName
# Shenanigans to account for members being either an array or a single object
@(Get-Member -InputObject @($members)[0])[0].TypeName | Should Be 'PSJira.User'
}
}
}


}
'@
}

{ Get-JiraGroupMember -Group testgroup } | Should Not Throw

Assert-MockCalled -CommandName Get-JiraGroup -Exactly -Times 1 -Scope It -ParameterFilter { $GroupName -eq 'testgroup' }
Assert-MockCalled -CommandName Invoke-JiraMethod -Exactly -Times 1 -Scope It -ParameterFilter { $Method -eq 'Get' -and $URI -like '*/rest/api/*/group?groupname=testgroup&expand=users*0:2*' }

}
}

Context "Input testing" {
It "Accepts a group name for the -Group parameter" {
{ Get-JiraGroupMember -Group testgroup } | Should Not Throw
Assert-MockCalled -CommandName Invoke-JiraMethod -ModuleName PSJira -Exactly -Times 1 -Scope It -ParameterFilter { $Method -eq 'Get' -and $URI -like '*/rest/api/*/group?groupname=testgroup&expand=users*' }
}

It "Accepts a group object for the -InputObject parameter" {
$group = Get-JiraGroup -GroupName testgroup

{ Get-JiraGroupMember -Group $group } | Should Not Throw
Assert-MockCalled -CommandName Invoke-JiraMethod -ModuleName PSJira -Exactly -Times 1 -Scope It -ParameterFilter { $Method -eq 'Get' -and $URI -like '*/rest/api/*/group?groupname=testgroup&expand=users*' }

# We called Get-JiraGroup once manually, and it should be
# called twice by Get-JiraGroupMember.
Assert-MockCalled -CommandName Get-JiraGroup -Exactly -Times 3 -Scope It
}
}
}
}
111 changes: 94 additions & 17 deletions PSJira/Functions/Get-JiraGroupMember.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ function Get-JiraGroupMember
[PSJira.Group] The group to query for members
.OUTPUTS
[PSJira.User[]] Members of the provided group
.NOTES
By default, this will return all active users who are members of the
given group. For large groups, this can take quite some time.
To limit the number of group members returned, use
the MaxResults parameter. You can also combine this with the
StartIndex parameter to "page" through results.
This function does not return inactive users. This appears to be a
limitation of JIRA's REST API.
#>
[CmdletBinding()]
param(
Expand All @@ -25,11 +35,42 @@ function Get-JiraGroupMember
ValueFromPipelineByPropertyName = $true)]
[Object] $Group,

# Credentials to use to connect to Jira. If not specified, this function will use anonymous access.
# Index of the first user to return. This can be used to "page" through
# users in a large group or a slow connection.
[Parameter(Mandatory = $false)]
[ValidateRange(0, [Int]::MaxValue)]
[Int] $StartIndex = 0,

# Maximum number of results to return. By default, all users will be
# returned.
[Parameter(Mandatory = $false)]
[ValidateRange(0, [Int]::MaxValue)]
[Int] $MaxResults = 0,

# Credentials to use to connect to JIRA. If not specified, this function will use anonymous access.
[Parameter(Mandatory = $false)]
[System.Management.Automation.PSCredential] $Credential
)

begin
{
# This is a parameter in Get-JiraIssue, but in testing, JIRA doesn't
# reliably return more than 50 results at a time.
$pageSize = 50

if ($MaxResults -eq 0)
{
Write-Debug "[Get-JiraGroupMember] MaxResults was not specified. Using loop mode to obtain all members."
$loopMode = $true
} else {
$loopMode = $false
if ($MaxResults -gt 50)
{
Write-Warning "JIRA's API may not properly support MaxResults values higher than 50 for this method. If you receive inconsistent results, do not pass the MaxResults parameter to this function to return all results."
}
}
}

process
{
Write-Debug "[Get-JiraGroupMember] Obtaining a reference to Jira group [$Group]"
Expand All @@ -39,26 +80,62 @@ function Get-JiraGroupMember
{
foreach ($g in $groupObj)
{
Write-Debug "[Get-JiraGroupMember] Asking JIRA for members of group [$g]"
$url = "$($g.RestUrl)&expand=users"

Write-Debug "[Get-JiraGroupMember] Preparing for blastoff!"
$groupResult = Invoke-JiraMethod -Method Get -URI $url -Credential $Credential

if ($groupResult)
if ($loopMode)
{
# ConvertTo-JiraGroup contains logic to convert and add group members to
# group objects if the members are returned from JIRA.
# Using the Size property of the group object, iterate
# through all users in a given group.

Write-Debug "[Get-JiraGroupMember] Converting results to PSJira.Group and PSJira.User objects"
$groupObjResult = ConvertTo-JiraGroup -InputObject $groupResult
$totalResults = $g.Size
$allUsers = New-Object -TypeName System.Collections.ArrayList
Write-Debug "[Get-JiraGroupMember] Paging through all results (loop mode)"

for ($i = 0; $i -lt $totalResults; $i = $i + $PageSize)
{
if ($PageSize -gt ($i + $totalResults))
{
$thisPageSize = $totalResults - $i
} else {
$thisPageSize = $PageSize
}
$percentComplete = ($i / $totalResults) * 100
Write-Progress -Activity 'Get-JiraGroupMember' -Status "Obtaining members ($i - $($i + $thisPageSize) of $totalResults)..." -PercentComplete $percentComplete
Write-Debug "[Get-JiraGroupMember] Obtaining members $i - $($i + $thisPageSize)..."
$thisSection = Get-JiraGroupMember -Group $g -StartIndex $i -MaxResults $thisPageSize -Credential $Credential
foreach ($t in $thisSection)
{
[void] $allUsers.Add($t)
}
}

Write-Progress -Activity 'Get-JiraGroupMember' -Completed
Write-Output ($allUsers.ToArray())

Write-Debug "[Get-JiraGroupMember] Outputting group members"
Write-Output $groupObjResult.Member
} else {
# Something is wrong here...we didn't get back a result from JIRA when we *did* get a
# valid group from Get-JiraGroup earlier.
Write-Warning "Something strange happened when invoking JIRA method Get to URL [$url]"
# Since user is an expandable property of the returned
# group from JIRA, JIRA doesn't use the MaxResults argument
# found in other REST endpoints. Instead, we need to pass
# expand=users[0:15] for users 0-15 (inclusive).
$url = '{0}&expand=users[{1}:{2}]' -f $g.RestUrl, $StartIndex, ($StartIndex + $MaxResults)

Write-Debug "[Get-JiraGroupMember] Preparing for blastoff!"
$groupResult = Invoke-JiraMethod -Method Get -URI $url -Credential $Credential

if ($groupResult)
{
# ConvertTo-JiraGroup contains logic to convert and add
# users (group members) to user objects if the members
# are returned from JIRA.

Write-Debug "[Get-JiraGroupMember] Converting results to PSJira.Group and PSJira.User objects"
$groupObjResult = ConvertTo-JiraGroup -InputObject $groupResult

Write-Debug "[Get-JiraGroupMember] Outputting group members"
Write-Output $groupObjResult.Member
} else {
# Something is wrong here...we didn't get back a result from JIRA when we *did* get a
# valid group from Get-JiraGroup earlier.
Write-Warning "A JIRA group could not be found at URL [$url], even though this seems to be a valid group."
}
}
}
} else {
Expand Down

0 comments on commit 7cac9ed

Please sign in to comment.