From bb42e3cdd520678ee2c901b18e65ce23e8facfa2 Mon Sep 17 00:00:00 2001 From: Kees Verhaar Date: Mon, 4 Sep 2017 15:30:39 +0200 Subject: [PATCH] Add support for teams --- Team.psd1 | 8 +- src/teams.psm1 | 309 +++++++++++++++++++++++++++++++++++++++++++ test/teams.Tests.ps1 | 126 ++++++++++++++++++ 3 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 src/teams.psm1 create mode 100644 test/teams.Tests.ps1 diff --git a/Team.psd1 b/Team.psd1 index f951b8899..8f903b4cd 100644 --- a/Team.psd1 +++ b/Team.psd1 @@ -78,7 +78,8 @@ 'src\queues.psm1', 'src\releaseDefinitions.psm1', 'src\releases.psm1', - 'src\serviceendpoints.psm1') + 'src\serviceendpoints.psm1', + '.\src\teams.psm1') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @('Add-AzureRMServiceEndpoint', @@ -90,6 +91,7 @@ 'Add-ReleaseEnvironment', 'Add-ReleaseDefinition', 'Add-TeamAccount', + 'Add-Team', 'Clear-DefaultProject', 'Get-Approval', 'Get-Build', @@ -102,6 +104,8 @@ 'Get-ReleaseDefinition', 'Get-ServiceEndpoint', 'Get-TeamInfo', + 'Get-Team', + 'Get-TeamMembers', 'Remove-Build', 'Remove-BuildDefinition', 'Remove-Project', @@ -109,10 +113,12 @@ 'Remove-ReleaseDefinition', 'Remove-ServiceEndpoint', 'Remove-TeamAccount', + 'Remove-Team', 'Set-Approval', 'Set-DefaultProject', 'Set-ReleaseStatus', 'Update-Project', + 'Update-Team', 'Get-GitRepository', 'Add-GitRepository', 'Remove-GitRepository', diff --git a/src/teams.psm1 b/src/teams.psm1 new file mode 100644 index 000000000..bdd55bc0e --- /dev/null +++ b/src/teams.psm1 @@ -0,0 +1,309 @@ +Set-StrictMode -Version Latest + +# Load common code +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +. "$here\common.ps1" + +function _buildURL { + param( + [parameter(Mandatory = $true)] + [string] $ProjectName, + [string] $TeamId, + [switch] $GetMembers + ) + + if(-not $env:TEAM_ACCT) { + throw 'You must call Add-TeamAccount before calling any other functions in this module.' + } + + $version = '1.0' + $resource = "/projects/$ProjectName/teams" + $instance = $env:TEAM_ACCT + + if ($TeamId) { + $resource += "/$TeamId" + } + + if ($GetMembers.IsPresent) { + if(-not $TeamID) { + throw 'You must provide $TeamId when getting team members.' + } + $resource += "/members" + } + + # Build the url to list the projects + return $instance + '/_apis' + $resource + '?api-version=' + $version +} + +# Apply types to the returned objects so format and type files can +# identify the object and act on it. +function _applyTypes { + param($item) + + $item.PSObject.TypeNames.Insert(0, 'Team.Project') + + # Only returned for a single item + if ($item.PSObject.Properties.Match('defaultTeam').count -gt 0 -and $null -ne $item.defaultTeam) { + $item.defaultTeam.PSObject.TypeNames.Insert(0, 'Team.Team') + } + + if ($item.PSObject.Properties.Match('_links').count -gt 0 -and $null -ne $item._links) { + $item._links.PSObject.TypeNames.Insert(0, 'Team.Links') + } +} + +function Get-Team { + [CmdletBinding(DefaultParameterSetName = 'List')] + param ( + [Parameter(ParameterSetName = 'List')] + [int] $Top, + + [Parameter(ParameterSetName = 'List')] + [int] $Skip, + + [Parameter(ParameterSetName = 'ByID', ValueFromPipeline = $true)] + [string[]] $TeamId + ) + + DynamicParam { + _buildProjectNameDynamicParam + } + + process { + # Bind the parameter to a friendly variable + $ProjectName = $PSBoundParameters["ProjectName"] + + if($TeamId) { + foreach ($item in $TeamId) { + # Build the url to return the single build + $listurl = _buildURL -projectName $ProjectName -teamId $item + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # HOW DOES THIS WORK?!? + # _applyTypes -item $resp + + Write-Output $resp + } + } else { + # Build the url to list the builds + $listurl = _buildURL -projectName $ProjectName + + $listurl += _appendQueryString -name "`$top" -value $top + $listurl += _appendQueryString -name "`$skip" -value $skip + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # Apply a Type Name so we can use custom format view and custom type extensions + foreach ($item in $resp.value) { + _applyTypes -item $item + } + + Write-Output $resp.value + } + } +} + +function Get-TeamMembers { + [CmdletBinding(DefaultParameterSetName = 'List')] + param ( + [Parameter(ParameterSetName = 'List')] + [int] $Top, + + [Parameter(ParameterSetName = 'List')] + [int] $Skip, + + [Parameter(Mandatory = $true, ParameterSetName = 'List', ValueFromPipeline = $true)] + [string] $TeamId + ) + + DynamicParam { + _buildProjectNameDynamicParam + } + + process { + # Bind the parameter to a friendly variable + $ProjectName = $PSBoundParameters["ProjectName"] + + + # Build the url to list the builds + $listurl = _buildURL -projectName $ProjectName -teamId $TeamId -GetMembers + + $listurl += _appendQueryString -name "`$top" -value $top + $listurl += _appendQueryString -name "`$skip" -value $skip + + Write-Output $listurl + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # Apply a Type Name so we can use custom format view and custom type extensions + foreach ($item in $resp.value) { + # HOW DOES THIS WORK?!?! + #_applyTypes -item $item + } + + Write-Output $resp.value + } +} + +function Add-Team { + param( + [Parameter(Mandatory = $true)] + [string]$TeamName, + [string]$Description = "" + ) + DynamicParam { + _buildProjectNameDynamicParam + } + + process { + # Bind the parameter to a friendly variable + $ProjectName = $PSBoundParameters["ProjectName"] + + $listurl = _buildURL -ProjectName $ProjectName + $body = '{ "name": "' + $TeamName + '", "description": "' + $Description + '" }' + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Post -ContentType "application/json" -Body $body -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Post -ContentType "application/json" -Body $body -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # HOW DOES THIS WORK?!? + #_applyTypes -item $resp + + return $resp + } +} + +function Add-Team { + param( + [Parameter(Mandatory = $true)] + [string]$TeamName, + [string]$Description = "" + ) + DynamicParam { + _buildProjectNameDynamicParam + } + + process { + # Bind the parameter to a friendly variable + $ProjectName = $PSBoundParameters["ProjectName"] + + $listurl = _buildURL -ProjectName $ProjectName + $body = '{ "name": "' + $TeamName + '", "description": "' + $Description + '" }' + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Post -ContentType "application/json" -Body $body -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Post -ContentType "application/json" -Body $body -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # HOW DOES THIS WORK?!? + #_applyTypes -item $resp + + return $resp + } +} + +function Update-Team { + param( + [Parameter(Mandatory = $True)] + [string]$TeamToUpdate, + [string]$NewTeamName, + [string]$Description + ) + DynamicParam { + _buildProjectNameDynamicParam + } + + process { + # Bind the parameter to a friendly variable + $ProjectName = $PSBoundParameters["ProjectName"] + + $listurl = _buildURL -ProjectName $ProjectName -TeamId $TeamToUpdate + if(-not $NewTeamName -and -not $Description) { + throw 'You must provide a new team name or description, or both.' + } + + if(-not $NewTeamName) + { + $body = '{"description": "' + $Description + '" }' + } + if(-not $Description) + { + $body = '{ "name": "' + $NewTeamName + '" }' + } + if($NewTeamName -and $Description) + { + $body = '{ "name": "' + $NewTeamName + '", "description": "' + $Description + '" }' + } + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Patch -ContentType "application/json" -Body $body -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Patch -ContentType "application/json" -Body $body -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # HOW DOES THIS WORK?!? + #_applyTypes -item $resp + + return $resp + } +} + +function Remove-Team { + param( + [Parameter(Mandatory = $True)] + [string]$TeamId + ) + DynamicParam { + _buildProjectNameDynamicParam + } + + process { + # Bind the parameter to a friendly variable + $ProjectName = $PSBoundParameters["ProjectName"] + + $listurl = _buildURL -ProjectName $ProjectName -TeamId $TeamId + + # Call the REST API + if (_useWindowsAuthenticationOnPremise) { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method DELETE -Uri $listurl -UseDefaultCredentials + } + else { + $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method DELETE -Uri $listurl -Headers @{Authorization = "Basic $env:TEAM_PAT"} + } + + # HOW DOES THIS WORK?!? + #_applyTypes -item $resp + + return $resp + } +} + +Export-ModuleMember -Alias * -Function Get-Team, Get-TeamMembers, Add-Team, Update-Team, Remove-Team \ No newline at end of file diff --git a/test/teams.Tests.ps1 b/test/teams.Tests.ps1 new file mode 100644 index 000000000..b29aeccc0 --- /dev/null +++ b/test/teams.Tests.ps1 @@ -0,0 +1,126 @@ +Set-StrictMode -Version Latest + +Get-Module team | Remove-Module -Force +Import-Module $PSScriptRoot\..\src\teams.psm1 -Force + +InModuleScope teams { + $env:TEAM_ACCT = 'https://test.visualstudio.com' + + Describe "Teams" { + . "$PSScriptRoot\mockProjectNameDynamicParam.ps1" + + Context 'Get-Team with project name' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teams' { + Get-Team -ProjectName Test + + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/Test/teams?api-version=1.0' + } + } + } + + Context 'Get-Team with project name, with top' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teams' { + Get-Team -ProjectName Test -Top 10 + + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/Test/teams?api-version=1.0&$top=10' + } + } + } + + Context 'Get-Team with project name, with skip' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teams' { + Get-Team -ProjectName Test -Skip 10 + + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/Test/teams?api-version=1.0&$skip=10' + } + } + } + + Context 'Get-Team with project name, with top and skip' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teams' { + Get-Team -ProjectName Test -Top 10 -Skip 5 + + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/Test/teams?api-version=1.0&$top=10&$skip=5' + } + } + } + + Context 'Get-Team with specific project and specific team' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teams' { + Get-Team -ProjectName Test -TeamId TestTeam + + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/Test/teams/TestTeam?api-version=1.0' + } + } + } + + Context 'Get-Teammembers for specific project and team' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teammembers' { + Get-TeamMembers -ProjectName TestProject -TeamId TestTeam + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/TestProject/teams/TestTeam/members?api-version=1.0' + } + } + } + + Context 'Get-Teammembers for specific project and team, with top' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teammembers' { + Get-TeamMembers -ProjectName TestProject -TeamId TestTeam -Top 10 + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/TestProject/teams/TestTeam/members?api-version=1.0&$top=10' + } + } + } + + Context 'Get-Teammembers for specific project and team, with skip' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teammembers' { + Get-TeamMembers -ProjectName TestProject -TeamId TestTeam -Skip 5 + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/TestProject/teams/TestTeam/members?api-version=1.0&$skip=5' + } + } + } + + Context 'Get-Teammembers for specific project and team, with top and skip' { + Mock Invoke-RestMethod { return @{value='teams'}} + + It 'Should return teammembers' { + Get-TeamMembers -ProjectName TestProject -TeamId TestTeam -Top 10 -Skip 5 + # Make sure it was called with the correct URI + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Uri -eq 'https://test.visualstudio.com/_apis/projects/TestProject/teams/TestTeam/members?api-version=1.0&$top=10&$skip=5' + } + } + } + } + +} \ No newline at end of file