diff --git a/.docs/Get-VSTeamUserEntitlement.md b/.docs/Get-VSTeamUserEntitlement.md index e148c0e4..ca9305a1 100644 --- a/.docs/Get-VSTeamUserEntitlement.md +++ b/.docs/Get-VSTeamUserEntitlement.md @@ -6,6 +6,11 @@ +Please note that Filter, Name, UserType and License parameters only works when MemberEntitlementManagement module version is 6.0 or upper +In the same way Top and Skip paramerers only works up to version 5.1 + +You can setup the specific version for the MemberEntitlementManagement calling Set-VSTeamAPIVersion -Service MemberEntitlementManagement -Version VersionNumberYouNeed. + ## SYNTAX ## DESCRIPTION @@ -14,6 +19,44 @@ ## EXAMPLES +### Example 1: Get user by Id + +```powershell +Get-VSTeamUserEntitlement -Id f1ef22eb-5dd6-4e26-907c-986a0311b106 +``` + +This command gets the user entitlement of the user identified by id. + +### Example 2: Get users by name + +```powershell +Get-VSTeamUserEntitlement -Name username +``` + +This command gets a list of users which mail or user name contains 'username'. +Filtering by Name, License, or UserType is available only when MemberEntitlementManagement service version is 6.0 or upper. See Get-VSTeamAPIVersion and Set-VSTeamAPIVersion commands + + +### Example 3: Filter with some conditions + +```powershell +Get-VSTeamUserEntitlement -Filter "licenseId eq 'Account-Express' and licenseStatus eq 'Disabled'" +``` + +This command gets a list of users that match the license status and license type conditions. +The -Filter parameter is available only when MemberEntitlementManagement service version is 6.0 or upper. See Get-VSTeamAPIVersion and Set-VSTeamAPIVersion commands + + +### Example 4: List paged users + +```powershell +Get-VSTeamUserEntitlement -Skip 100 -Top 100 +``` + +This command list the from the user in the 101 position, the next 100 users +Filtering using the -Top -Skip parameters only works when MemberEntitlementManagement service version is below 6.0. See Get-VSTeamAPIVersion and Set-VSTeamAPIVersion commands + + ## PARAMETERS ### Skip @@ -53,11 +96,91 @@ Comma (",") separated list of properties to select in the result entitlements. ```yaml Type: String -Parameter Sets: List +Parameter Sets: List,PagedFilter,PagedParams Required: True Default value: None ``` +### MaxPages + +User entlitement API returs a paged result. This parameter allows to limit the number of pages to be retrieved. Default is 0 = all pages. + +```yaml +Type: int +Parameter Sets: PagedFilter,PagedParams +Required: False +Default value: $null +``` + +### Filter + +Equality operators relating to searching user entitlements seperated by and clauses. Valid filters include: licenseId, licenseStatus, userType, and name. +- licenseId: filters based on license assignment using license names. i.e. licenseId eq 'Account-Stakeholder' or licenseId eq 'Account-Express'. +- licenseStatus: filters based on license status. currently only supports disabled. i.e. licenseStatus eq 'Disabled'. To get disabled basic licenses, you would pass (licenseId eq 'Account-Express' and licenseStatus eq 'Disabled') +- userType: filters off identity type. Suppored types are member or guest i.e. userType eq 'member'. +- name: filters on if the user's display name or email contians given input. i.e. get all users with "test" in email or displayname is "name eq 'test'". + +A valid query could be: (licenseId eq 'Account-Stakeholder' or (licenseId eq 'Account-Express' and licenseStatus eq 'Disabled')) and name eq 'test' and userType eq 'guest'. + +Currently, filter names and values must match exactly the case. i.e.: +* LicenseID will throw Invalid filter message. +* licenseId eq 'account-stakeholder' will return an empty list + +```yaml +Type: string +Parameter Sets: PagedFilter +Required: False +Default value: None +``` + +### License + +Filters based on license assignment using license names + +The acceptable values for this parameter are: +- Account-Stakeholder: Stakeholder +- Account-Express: Basic +- Account-Advanced: Basic + Test Plans + +Other licenses which source (licenseSource) is MSDN cannot be filtered here +Parameter values are case sensitive + +```yaml +Type: string +Parameter Sets: PagedParams +Required: False +Default value: None +``` + +### UserType + +Filters based on user type + +The acceptable values for this parameter are: +- member +- guest + +Parameter values are case sensitive + +```yaml +Type: string +Parameter Sets: PagedParams +Required: False +Default value: None +``` + +### Name + +Filters on if the user's display name or email contains given input + +```yaml +Type: string +Parameter Sets: PagedParams +Required: False +Default value: None +``` + + ## INPUTS ## OUTPUTS diff --git a/.docs/synopsis/Get-VSTeamUserEntitlement.md b/.docs/synopsis/Get-VSTeamUserEntitlement.md index 65b573ce..325fefd6 100644 --- a/.docs/synopsis/Get-VSTeamUserEntitlement.md +++ b/.docs/synopsis/Get-VSTeamUserEntitlement.md @@ -1 +1 @@ -Get User Entitlement for a user. \ No newline at end of file +Get User Entitlement for a user, or a paged list of users matching the specified filter \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca34971..552e5d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ Merged [Pull Request](https://github.com/MethodsAndPractices/vsteam/pull/468) from [Michael19842](https://github.com/Michael19842) the following: - Added parameter `Templateparameter` for queue new build with custom template parameters +Merged [Pull Request](https://github.com/MethodsAndPractices/vsteam/pull/459) from [Miguel Nieto](https://github.com/mnieto) the following: + +- Enable API version 6.0 calls in Get-VSTeamUserEntitlement. This add new parameters only available starting from version 6.0. + - Top and Skip parameters are still valid on versions up to 5.1. But will throw if called from version 6.0. This preservers backwards compability, but can be a breaking change if you do not pay attention to the API version in your scripts + - New parameters will throw if called from version 5.1 + - Function behaviour can be changed from Set-VSTeamAPIVersion -Service MemberEntitlementManagement -Version $yourVersion + ## 7.8.0 Merged [Pull Request](https://github.com/MethodsAndPractices/vsteam/pull/475) from [rbleattler](https://github.com/rbleattler) the following: - Added Update-VSTeamGitRepositoryDefaultBranch to allow for changing the default branch of a repository diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index db3294bf..59194165 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -121,6 +121,64 @@ function _callAPI { } } + +# General function to manage API Calls that involve a paged response, +# either with a ContinuationToken property in the body payload or +# with a X-MS-ContinuationToken header +# TODO: Add functionality to manage paged responses based on X-MS-ContinuationToken header +# TODO: This would need to be integrated probably into the _callAPI function? +function _callAPIContinuationToken { + [CmdletBinding()] + param( + [string]$Url, + # If present, or $true, the function will manage the pages using the header + # specified in $ContinuationTokenName. + # If not present, or $false, the function will manage the pages using the + # continuationToken property specified in $ContinuationTokenName. + [switch]$UseHeader, + # Allows to specify a header or continuation token property different of the default values. + # If this parameter is not specified, the default value is X-MS-ContinuationToken or continuationToken + # depending if $UseHeader is present or not, respectively + [string]$ContinuationTokenName, + # Property in the response body payload that contains the collecion of objects to return to the calling function + [string]$PropertyName, + # Number of pages to be retrieved. If 0, or not specified, it will return all the available pages + [int]$MaxPages + ) + + if ($MaxPages -le 0){ + $MaxPages = [int32]::MaxValue + } + if ([string]::IsNullOrEmpty($ContinuationTokenName)) { + if ($UseHeader.IsPresent) { + $ContinuationTokenName = "X-MS-ContinuationToken" + } else { + $ContinuationTokenName = "continuationToken" + } + } + $i = 0 + $obj = @() + $apiParameters = $url + do { + if ($UseHeader.IsPresent) { + throw "Continuation token from response headers not supported in this version" + } else { + $resp = _callAPI -url $apiParameters + $continuationToken = $resp."$ContinuationTokenName" + $i++ + Write-Verbose "page $i" + $obj += $resp."$PropertyName" + if (-not [String]::IsNullOrEmpty($continuationToken)) { + $continuationToken = [uri]::EscapeDataString($continuationToken) + $apiParameters = "${url}&continuationToken=$continuationToken" + } + } + } while (-not [String]::IsNullOrEmpty($continuationToken) -and $i -lt $MaxPages) + + return $obj +} + + # Not all versions support the name features. function _supportsGraph { @@ -175,9 +233,22 @@ function _supportsSecurityNamespace { } function _supportsMemberEntitlementManagement { + [CmdletBinding(DefaultParameterSetName="upto")] + param( + [parameter(ParameterSetName="upto")] + [string]$UpTo = $null, + [parameter(ParameterSetName="onwards")] + [string]$Onwards = $null + + ) _hasAccount - if (-not $(_getApiVersion MemberEntitlementManagement)) { + $apiVer = _getApiVersion MemberEntitlementManagement + if (-not $apiVer) { throw 'This account does not support Member Entitlement.' + } elseif (-not [string]::IsNullOrEmpty($UpTo) -and $apiVer -gt $UpTo) { + throw "EntitlementManagemen version must be equal or lower than $UpTo for this call, current value $apiVer" + } elseif (-not [string]::IsNullOrEmpty($Onwards) -and $apiVer -lt $Onwards) { + throw "EntitlementManagemen version must be equal or greater than $Onwards for this call, current value $apiVer" } } @@ -1180,4 +1251,19 @@ function _checkForModuleUpdates { } } +} + +function _countParameters() { + param( + $BoundParameters + ) + $counter = 0 + $advancedPameters = @('Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable', 'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable') + foreach($p in $BoundParameters.GetEnumerator()) { + if ($p.Key -notin $advancedPameters) { + $counter++ + } + } + Write-Verbose "Found $counter parameters" + $counter } \ No newline at end of file diff --git a/Source/Public/Get-VSTeamUserEntitlement.ps1 b/Source/Public/Get-VSTeamUserEntitlement.ps1 index 735a0f79..d88212e8 100644 --- a/Source/Public/Get-VSTeamUserEntitlement.ps1 +++ b/Source/Public/Get-VSTeamUserEntitlement.ps1 @@ -9,22 +9,58 @@ function Get-VSTeamUserEntitlement { [int] $Skip = 0, [Parameter(ParameterSetName = 'List')] + [Parameter(ParameterSetName = 'PagedFilter')] + [Parameter(ParameterSetName = 'PagedParams')] [ValidateSet('Projects', 'Extensions', 'Grouprules')] [string[]] $Select, [Parameter(ParameterSetName = 'ByID')] [Alias('UserId')] - [string[]] $Id + [string[]] $Id, + + [Parameter(ParameterSetName = 'PagedFilter')] + [Parameter(ParameterSetName = 'PagedParams')] + [int] $MaxPages = 0, + + [Parameter(ParameterSetName = 'PagedFilter')] + [string] $Filter, + + [Parameter(ParameterSetName = 'PagedParams')] + [ValidateSet('Account-Stakeholder', 'Account-Express', 'Account-Advanced', IgnoreCase = $false)] + [Alias('License')] + [string] $LicenseId, + + [Parameter(ParameterSetName = 'PagedParams')] + [ValidateSet('guest', 'member', IgnoreCase = $false)] + [string] $UserType, + + [Parameter(ParameterSetName = 'PagedParams')] + [Alias('UserName')] + [Alias('Mail')] + [string] $Name ) + process { # This will throw if this account does not support MemberEntitlementManagement - _supportsMemberEntitlementManagement + # or supported version is not correct with the type of API call + $paramCounter = _countParameters -BoundParameters $PSBoundParameters + + $paramset = 'PagedParams', 'PagedFilter' + if ($paramCounter -eq 0) { + _supportsMemberEntitlementManagement + } elseif ($paramset -contains $PSCmdlet.ParameterSetName) { + _supportsMemberEntitlementManagement -Onwards '6.0' + } else { + _supportsMemberEntitlementManagement -UpTo '5.1' + } + + $apiVersion = _getApiVersion MemberEntitlementManagement $commonArgs = @{ subDomain = 'vsaex' resource = 'userentitlements' - version = $(_getApiVersion MemberEntitlementManagement) + version = $apiVersion } if ($Id) { @@ -37,23 +73,51 @@ function Get-VSTeamUserEntitlement { } } else { - # Build the url to list the teams + # use the appropiate syntax depending on the API version + $useContinuationToken = ($paramCounter -eq 0 -and $apiVersion -gt '6.0') -or ($paramset -contains $PSCmdlet.ParameterSetName) + $listurl = _buildRequestURI @commonArgs + $objs = @() + Write-Verbose "Use continuation token: $useContinuationToken" + if ($useContinuationToken) { + if ($psCmdLet.ParameterSetName -eq 'PagedParams') { + #parameter names must be lowercase, parameter values depends on the parameter + if ($name) { + $filter += "name eq '$name' and " + } + if ($LicenseId) { - $listurl += _appendQueryString -name "top" -value $top -retainZero - $listurl += _appendQueryString -name "skip" -value $skip -retainZero - $listurl += _appendQueryString -name "select" -value ($select -join ",") + $filter += "licenseId eq '$LicenseId' and " + } + if ($UserType) { + $filter += "userType eq '$UserType' and " + } + $filter = $filter.SubString(0, $filter.Length - 5) + } + $listurl += _appendQueryString -name "`$filter" -value $filter + $listurl += _appendQueryString -name "select" -value ($select -join ",") - # Call the REST API - $resp = _callAPI -url $listurl + # Call the REST API + Write-Verbose "API params: $listurl" + $items = _callAPIContinuationToken -Url $listurl -PropertyName "members" + foreach ($item in $items) { + $objs += [vsteam_lib.UserEntitlement]::new($item) + } + } else { + $listurl += _appendQueryString -name "top" -value $top -retainZero + $listurl += _appendQueryString -name "skip" -value $skip -retainZero + $listurl += _appendQueryString -name "select" -value ($select -join ",") - $objs = @() + # Call the REST API + Write-Verbose "API params: $listurl" + $resp = _callAPI -url $listurl - foreach ($item in $resp.members) { - $objs += [vsteam_lib.UserEntitlement]::new($item) + foreach ($item in $resp.members) { + $objs += [vsteam_lib.UserEntitlement]::new($item) + } } - Write-Output $objs + } } -} \ No newline at end of file +} diff --git a/Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json b/Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json new file mode 100644 index 00000000..256a4185 --- /dev/null +++ b/Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json @@ -0,0 +1,288 @@ +{ + "members": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "mlastName@test.com", + "mailAddress": "mlastName@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Math lastName", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T06:43:29.0769722Z", + "dateCreated": "2020-02-25T05:27:12.1277176Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "guest", + "directoryAlias": "dlbm3_test.com#EXT#", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "dlbm3@test.com", + "mailAddress": "dlbm3@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "pending", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "0001-01-01T00:00:00Z", + "dateCreated": "2020-09-07T18:29:19.290717Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "directoryAlias": "test", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "test@test.com", + "mailAddress": "test@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T15:38:08.1632123Z", + "dateCreated": "2017-12-24T16:41:16.743Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + } + ], + "continuationToken": "+RID:~I7MHAPrCvZ7TpEEAAAAAAA==#RT:1#TRC:100#ISV:2#IEO:65551#QCF:4#FPC:AggGAQAAAAAAAAIBAAAAJAAABgEAAAAAAAACANOkCQEAAAAAAAACAN6KFQEAAAAAAAACAKew/QAAAAAkAAACACCS/wAAAAAkAAACADmfAQEAAAAkAAACACK1AgEAAAAkAAAGAGO4kQAKAA==", + "totalCount": 0, + "items": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "mlastName@test.com", + "mailAddress": "mlastName@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Math lastName", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T06:43:29.0769722Z", + "dateCreated": "2020-02-25T05:27:12.1277176Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "guest", + "directoryAlias": "dlbm3_test.com#EXT#", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "dlbm3@test.com", + "mailAddress": "dlbm3@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "pending", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "0001-01-01T00:00:00Z", + "dateCreated": "2020-09-07T18:29:19.290717Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "directoryAlias": "test", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "test@test.com", + "mailAddress": "test@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T15:38:08.1632123Z", + "dateCreated": "2017-12-24T16:41:16.743Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + } + ] +} diff --git a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 index 66bdae0a..17ded083 100644 --- a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 +++ b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 @@ -32,7 +32,7 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { BeforeAll { Mock _getApiVersion { return 'VSTS' } Mock _getInstance { return 'https://dev.azure.com/test' } - Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } + Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement-Id.json' } -ParameterFilter { @@ -40,35 +40,108 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { } } - It 'no parameters should return users' { - $users = Get-VSTeamUserEntitlement + Context 'Get-VSTeamUserEntitlement up to version 6.0' { + BeforeAll { + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } + } + + It 'no parameters should return users' { + $users = Get-VSTeamUserEntitlement + + $users.count | Should -Be 3 + $users[0].UserName | Should -Be 'Math lastName' + + # Make sure it was called with the correct URI + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0" + } + } + + It 'by Id should return users with projects' { + $user = Get-VSTeamUserEntitlement -Id '00000000-0000-0000-0000-000000000000' + + $user.Email | Should -Be 'test@test.com' -Because 'email is from type' + $user.userName | Should -Be 'Donovan Brown' -Because 'userName is from type' + + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements/00000000-0000-0000-0000-000000000000?api-version=$(_getApiVersion MemberEntitlementManagement)" + } + } - $users.count | Should -Be 3 - $users[0].UserName | Should -Be 'Math lastName' + It 'with select for projects should return users with projects' { + Get-VSTeamUserEntitlement -Select Projects + + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0&select=Projects" + } + } - # Make sure it was called with the correct URI - Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0" + It 'should throw with paged parameterset and version below 6.0 ' { + { Get-VSTeamUserEntitlement -Name 'Math' } | Should -Throw } + } - It 'by Id should return users with projects' { - $user = Get-VSTeamUserEntitlement -Id '00000000-0000-0000-0000-000000000000' + Context 'Get-VSTeamUserEntitlement version 6.0 onwards' { + BeforeAll { + Mock _getApiVersion { return '6.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } + Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement-ContinuationToken.json' } -ParameterFilter { + $Uri -match "filter=userType eq 'guest'$" + } + Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } -ParameterFilter { + $Uri -like "*filter=userType eq 'guest'&continuationToken=*" + } + } + + It 'should throw by ID and version 6.0 onwards' { + { Get-VSTeamUserEntitlement -Id '00000000-0000-0000-0000-000000000000' } | Should -Throw + } + + It "with incorrect case in license parameter should throw" { + { Get-VSTeamUserEntitlement -License account-Advanced} | Should -Throw + } - $user.Email | Should -Be 'test@test.com' -Because 'email is from type' - $user.userName | Should -Be 'Donovan Brown' -Because 'userName is from type' + It "with incorrect case in usertype parameter should throw" { + { Get-VSTeamUserEntitlement -UserType Member} | Should -Throw + } - Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements/00000000-0000-0000-0000-000000000000?api-version=$(_getApiVersion MemberEntitlementManagement)" + It 'no parameters should return users' { + $users = Get-VSTeamUserEntitlement + + $users.count | Should -Be 3 + $users[0].UserName | Should -Be 'Math lastName' + + # Make sure it was called with the correct URI + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)" + } + } + + It 'by filter should return users that match that filter' { + Get-VSTeamUserEntitlement -Filter "name eq 'Math'" + + # Make sure it was called with the correct URI parameters + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&`$filter=name eq 'Math'" + } + } + + It 'by name should translate to filter' { + Get-VSTeamUserEntitlement -Name "Math" -License Account-Advanced -UserType member + + # Make sure it was called with the correct URI parameters. Filter parameter names are case sensitive + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&`$filter=name eq 'Math' and licenseId eq 'Account-Advanced' and userType eq 'member'" + } } - } - It 'with select for projects should return users with projects' { - Get-VSTeamUserEntitlement -Select Projects + It 'with many matches continuationToken is used' { + $users = Get-VSTeamUserEntitlement -UserType guest + $users.Count | Should -Be 6 - Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0&select=Projects" + Should -Invoke Invoke-RestMethod -Exactly -Times 2 } + } } } diff --git a/Tests/function/tests/common.Tests.ps1 b/Tests/function/tests/common.Tests.ps1 index 1a778f55..1ff8db6b 100644 --- a/Tests/function/tests/common.Tests.ps1 +++ b/Tests/function/tests/common.Tests.ps1 @@ -37,6 +37,33 @@ Describe 'Common' { } } + Context '_callAPIContinuationToken' { + BeforeAll { + Mock _callAPI { Open-SampleFile 'Get-VSTeamUserEntitlement-ContinuationToken.json' } -ParameterFilter { + $Url -match "filter=userType eq 'guest'$" + } + Mock _callAPI { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } -ParameterFilter { + $Url -like "*filter=userType eq 'guest'&continuationToken=*" + } + } + + # TODO: To be removed when support to manage X-MS-ContinuationToken header is added and replace it with specific tests + It 'not supported should throw with UseHeader parameter' { + { _callAPIContinuationToken -UseHeader } | Should -Throw + } + + It 'When MaxPages has default value, all pages are returned' { + _callAPIContinuationToken -Url "https://vsaex.dev.azure.com/test/_apis/userentitlements?`$filter=userType eq 'guest'" -PropertyName 'members' + Should -Invoke _callAPI -Exactly -Times 2 + } + + It 'When number of pages are greater than MaxMages, only MaxPages are returned' { + _callAPIContinuationToken -Url "https://vsaex.dev.azure.com/test/_apis/userentitlements?`$filter=userType eq 'guest'" -PropertyName 'members' -MaxPages 1 + Should -Invoke _callAPI -Exactly -Times 1 + } + + } + Context '_getPermissionInheritanceInfo' { BeforeAll { Mock Get-VSTeamBuildDefinition { Open-SampleFile 'Get-BuildDefinition_AzD.json' -ReturnValue }