diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b5ecc397..d4ac695e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,10 +32,10 @@ --> - [ ] You actually ran the code that you just wrote, especially if you did just "one last quick change". - [ ] Comment-based help added/updated, including examples. -- [ ] [Static analysis](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#static-analysis) -is reporting back clean. +- [ ] [Static analysis](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#static-analysis) is reporting back clean. - [ ] New/changed code adheres to our [coding guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#coding-guidelines). +- [ ] New/changed code continues to [support the pipeline](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#pipeline-support). - [ ] Changes to the manifest file follow the [manifest guidance](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#module-manifest). -- [ ] Unit tests were added/updated and are all passing. See [testing guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#testing). +- [ ] Unit tests were added/updated and are all passing. See [testing guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#testing). This includes making sure that all pipeline input variations have been covered. - [ ] Relevant usage examples have been added/updated in [USAGE.md](https://github.com/microsoft/PowerShellForGitHub/blob/master/USAGE.md). - [ ] If desired, ensure your name is added to our [Contributors list](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#contributors) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bb7e8d8..1f690f71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,7 @@ Looking for information on how to use this module? Head on over to [README.md]( * [Adding New Configuration Properties](#adding-new-configuration-properties) * [Code Comments](#code-comments) * [Debugging Tips](#debugging-tips) +* [Pipeline Support](#pipeline-support) * [Testing](#testing) * [Installing Pester](#installing-pester) * [Configuring Your Environment](#configuring-your-environment) @@ -286,8 +287,71 @@ Set-GitHubConfiguration -LogRequestBody ---------- +### Pipeline Support + +This module has comprehensive support for the PowerShell pipeline. It is imperative that all +new functionality added to the module embraces this design. + + * Most functions are declared as a `filter`. This is the equivalent of a `function` where the + body of the function is the `process` block, and the `begin/end` blocks are empty. + + * In limited cases where one of the inputs is an array of something, and you specifically want that + to be processed as a single command (like adding a bunch of labels to a single issue at once), + you can implement it as a `function` where you use `begin/process` to gather all of the values + into a single internal array, and then do the actual command execution in the `end` block. A + good example of that which you can follow can be seen with `Add-GitHubIssueLabel`. + + * Any function that requires the repo's `Uri` to be provided should be additionally aliased with + `[Alias('RepositoryUrl')]` and its `[Parameter()]` definition should include `ValueFromPipelineByPropertyName`. + + * Do not use any generic term like `Name` in your parameters. That will end up causing unintended + pipeline issues down the line. For instance, if it's a label, call it `Label`, even though `Name` + would make sense, other objects in the pipeline (like a `GitHub.Respository` object) also have + a `name` property that would conflict. + + * You should plan on adding additional properties to all objects being returned from an API call. + Any object that is specific to a repository should have a `RepositoryUrl` `NoteProperty` added + to it, enabling it to be piped-in to any other command that requires knowing which repository + you're talking about. Additionally, any other property that might be necessary to uniquely + identify that object in a different command should get added properties. For example, with Issues, + we add both an `IssueNumber` property and an `IssueId` property to it, as the Issue commands + need to interact with the `IssueNumber` while the Event commands interact with the `IssueId`. + We prefer to _only_ add additional properties that are believed to be needed as input to other + commands (as opposed to creating alias properties for all of the object's properties). + + * For every major file, you will find an `Add-GitHub*AdditionalProperties` filter method at the end. + If you're writing a new file, you'll need to create this yourself (and model it after an existing + one). The goal of this is that you can simply pipe the output of your `Invoke-GHRestMethod` + directly into this method to update the result with the additional properties, and then return + that modified version to the user. The benefit of this approach is that you can then apply that + filter on child objects within the primary object. For instance, a `GitHub.Issue` has multiple + `GitHub.User` objects, `GitHub.Label` objects, a `GitHub.Milestone` object and more. Within + `Add-GitHubIssueAdditionalProperties`, it just needs to know to call the appropriate + `Add-GitHub*AdditionalProperties` method on the qualifying child properties, without needing to + know anything more about them. + + * That method will also "type" information to each object. This is forward-looking work to ease + support for providing formatting of various object types in the future. That type should be + defined at the top of the current file at the script level (see other files for an example), + and you should be sure to both specify it in the `.OUTPUTS` section of the Comment Based Help (CBH) + for the command, as well as with `[OutputType({$script:GitHubUserTypeName})]` (for example). + + * Going along with the `.OUTPUTS` is the `.INPUTS` section. Please maintain this section as well. + If you add any new type that will gain a `RepositoryUrl` property, then you'll need to update + virtually _all_ of the `.INPUTS` entries across all of the files where the function has a `Uri` + parameter. Please keep these type names alphabetical. + + * To enable debugging issues involving pipeline support, there is an additional configuration + property that you might use: `Set-GitHubConfiguration -DisablePipelineSupport`. That will + prevent the module from adding _any_ additional properties to the objects. + +---------- + ### Testing [![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) + #### Installing Pester This module supports testing using the [Pester UT framework](https://github.com/pester/Pester). @@ -350,6 +414,8 @@ There are many more nuances to code-coverage, see #### Automated Tests [![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) These test are configured to automatically execute upon any update to the `master` branch of `microsoft/PowerShellForGitHub`. @@ -362,9 +428,9 @@ as well...it is stored, encrypted, within Azure DevOps. It is not accessible fo the CI pipeline. To run the tests locally with your own account, see [configuring-your-environment](#configuring-your-environment). -> NOTE: We're currently encountering issues with the tests successfully running within the pipeline. -> They do complete successfully locally, so please test your changes locally before submitting a -> pull request. +> Your change must successfully pass all tests before they will be merged. While we will run a CI +> build on your behalf for any submitted pull request, it's to your benefit to verify your changes +> locally first. #### New Test Guidelines Your tests should have NO dependencies on an account being set up in a specific way. They should diff --git a/GitHubAnalytics.ps1 b/GitHubAnalytics.ps1 index e4bba220..0ee2310b 100644 --- a/GitHubAnalytics.ps1 +++ b/GitHubAnalytics.ps1 @@ -24,8 +24,12 @@ function Group-GitHubIssue The date property that should be inspected when determining which week grouping the issue if part of. + .INPUTS + GitHub.Issue + .OUTPUTS - [PSCustomObject[]] Collection of issues and counts, by week, along with the total count of issues. + [PSCustomObject[]] + Collection of issues and counts, by week, along with the total count of issues. .EXAMPLE $issues = @() @@ -90,8 +94,12 @@ function Group-GitHubIssue foreach ($week in $weekDates) { $filteredIssues = @($Issue | Where-Object { - (($DateType -eq 'Created') -and ($_.created_at -ge $week) -and ($_.created_at -le $endOfWeek)) -or - (($DateType -eq 'Closed') -and ($_.closed_at -ge $week) -and ($_.closed_at -le $endOfWeek)) + (($DateType -eq 'Created') -and + ($_.created_at -ge $week) -and + ($_.created_at -le $endOfWeek)) -or + (($DateType -eq 'Closed') -and + ($_.closed_at -ge $week) -and + ($_.closed_at -le $endOfWeek)) }) $endOfWeek = $week @@ -144,6 +152,9 @@ function Group-GitHubPullRequest The date property that should be inspected when determining which week grouping the pull request if part of. + .INPUTS + GitHub.PullRequest + .OUTPUTS [PSCustomObject[]] Collection of pull requests and counts, by week, along with the total count of pull requests. @@ -211,8 +222,12 @@ function Group-GitHubPullRequest foreach ($week in $weekDates) { $filteredPullRequests = @($PullRequest | Where-Object { - (($DateType -eq 'Created') -and ($_.created_at -ge $week) -and ($_.created_at -le $endOfWeek)) -or - (($DateType -eq 'Merged') -and ($_.merged_at -ge $week) -and ($_.merged_at -le $endOfWeek)) + (($DateType -eq 'Created') -and + ($_.created_at -ge $week) -and + ($_.created_at -le $endOfWeek)) -or + (($DateType -eq 'Merged') -and + ($_.merged_at -ge $week) -and + ($_.merged_at -le $endOfWeek)) }) $endOfWeek = $week @@ -265,6 +280,7 @@ function Get-WeekDate Get-WeekDate -Weeks 10 #> [CmdletBinding()] + [OutputType([DateTime[]])] param( [ValidateRange(0, 10000)] [int] $Weeks = 12 diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index 6a2c1309..eccc9810 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubAssignee +filter Get-GitHubAssignee { <# .DESCRIPTION @@ -32,14 +32,40 @@ function Get-GitHubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.User + .EXAMPLE - Get-GitHubAssigneeList -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubAssigneeList -OwnerName microsoft -RepositoryName PowerShellForGitHub + + Lists the available assignees for issues from the microsoft\PowerShellForGitHub project. - Lists the available assignees for issues from the Microsoft\PowerShellForGitHub project. + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubAssigneeList + + Lists the available assignees for issues from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -51,7 +77,9 @@ function Get-GitHubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -79,14 +107,14 @@ function Get-GitHubAssignee 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) } -function Test-GitHubAssignee +filter Test-GitHubAssignee { <# .DESCRIPTION - Checks if a user has permission to be assigned to an issue in this repository. Returns a boolean. + Checks if a user has permission to be assigned to an issue in this repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -116,18 +144,50 @@ function Test-GitHubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + .OUTPUTS - [bool] If the assignee can be assigned to issues in the repository. + [bool] + If the assignee can be assigned to issues in the repository. .EXAMPLE - Test-GitHubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" + Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" - Checks if a user has permission to be assigned to an issue from the Microsoft\PowerShellForGitHub project. + Checks if a user has permission to be assigned to an issue + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Test-GitHubAssignee -Assignee 'octocat' + + Checks if a user has permission to be assigned to an issue + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $octocat = Get-GitHubUser -UserName 'octocat' + $repo = $octocat | Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub + + Checks if a user has permission to be assigned to an issue + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [OutputType([bool])] + [OutputType([bool])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -139,9 +199,13 @@ function Test-GitHubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('UserName')] [string] $Assignee, [string] $AccessToken, @@ -183,11 +247,11 @@ function Test-GitHubAssignee } } -function New-GithubAssignee +function New-GitHubAssignee { <# .DESCRIPTION - Adds a list of assignees to a Github Issue for the given repository. + Adds a list of assignees to a GitHub Issue for the given repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -208,7 +272,9 @@ function New-GithubAssignee Issue number to add the assignees to. .PARAMETER Assignee - Usernames of users to assign this issue to. NOTE: Only users with push access can add assignees to an issue. + Usernames of users to assign this issue to. + + NOTE: Only users with push access can add assignees to an issue. Assignees are silently ignored otherwise. .PARAMETER AccessToken @@ -221,14 +287,60 @@ function New-GithubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.Issue + .EXAMPLE - New-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignee + $assignees = @('octocat') + New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Assignee $assignee + + Additionally assigns the usernames in $assignee to Issue #1 + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $assignees = @('octocat') + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | New-GitHubAssignee -Issue 1 -Assignee $assignee + + Additionally assigns the usernames in $assignee to Issue #1 + from the microsoft\PowerShellForGitHub project. - Lists the available assignees for issues from the Microsoft\PowerShellForGitHub project. + .EXAMPLE + $assignees = @('octocat') + Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | + Get-GitHubIssue -Issue 1 | + New-GitHubAssignee -Assignee $assignee + + Additionally assigns the usernames in $assignee to Issue #1 + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $octocat = Get-GitHubUser -UserName 'octocat' + $octocat | New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + + Additionally assigns the user 'octocat' to Issue #1 + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -240,14 +352,22 @@ function New-GithubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateCount(1, 10)] + [Alias('UserName')] [string[]] $Assignee, [string] $AccessToken, @@ -255,43 +375,59 @@ function New-GithubAssignee [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'AssigneeCount' = $Assignee.Count - 'Issue' = (Get-PiiSafeString -PlainText $Issue) + begin + { + $userNames = @() } - $hashBody = @{ - 'assignees' = $Assignee + process + { + foreach ($name in $Assignee) + { + $userNames += $name + } } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Post' - 'Description' = "Add assignees to issue $Issue for $RepositoryName" - 'AccessToken' = $AccessToken - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + end + { + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName - return Invoke-GHRestMethod @params + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'AssigneeCount' = $userNames.Count + 'Issue' = (Get-PiiSafeString -PlainText $Issue) + } + + $hashBody = @{ + 'assignees' = $userNames + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Add assignees to issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = $script:symmetraAcceptHeader + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) + } } -function Remove-GithubAssignee +function Remove-GitHubAssignee { <# .DESCRIPTION - Removes an assignee from a Github issue. + Removes an assignee from a GitHub issue. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -312,7 +448,7 @@ function Remove-GithubAssignee Issue number to remove the assignees from. .PARAMETER Assignee - Usernames of assignees to remove from an issue. NOTE: Only users with push access can remove assignees from an issue. Assignees are silently ignored otherwise. + Usernames of assignees to remove from an issue. .PARAMETER Force If this switch is specified, you will not be prompted for confirmation of command execution. @@ -327,25 +463,68 @@ function Remove-GithubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Issue + .EXAMPLE - Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees + $assignees = @('octocat') + Remove-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Assignee $assignee - Removes the available assignees for issues from the Microsoft\PowerShellForGitHub project. + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. .EXAMPLE - Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees -Confirm:$false + $assignees = @('octocat') + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Remove-GitHubAssignee -Issue 1 -Assignee $assignee + + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. - Removes the available assignees for issues from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + Will not prompt for confirmation because -Confirm:$false was specified .EXAMPLE - Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees -Force + $assignees = @('octocat') + Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | + Get-GitHubIssue -Issue 1 | + Remove-GitHubAssignee -Assignee $assignee + + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. + + Will not prompt for confirmation because -Force was specified + + .EXAMPLE + $octocat = Get-GitHubUser -UserName 'octocat' + $octocat | Remove-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. - Removes the available assignees for issues from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + .NOTES + Only users with push access can remove assignees from an issue. + Assignees are silently ignored otherwise. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements', ConfirmImpact="High")] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] @@ -356,13 +535,22 @@ function Remove-GithubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] + [Alias('UserName')] [string[]] $Assignee, [switch] $Force, @@ -372,42 +560,58 @@ function Remove-GithubAssignee [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'AssigneeCount' = $Assignee.Count - 'Issue' = (Get-PiiSafeString -PlainText $Issue) - } - - $hashBody = @{ - 'assignees' = $Assignee + begin + { + $userNames = @() } - if ($Force -and (-not $Confirm)) + process { - $ConfirmPreference = 'None' + foreach ($name in $Assignee) + { + $userNames += $name + } } - if ($PSCmdlet.ShouldProcess($Assignee -join ', ', "Remove assignee(s)")) + end { - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Delete' - 'Description' = "Removing assignees from issue $Issue for $RepositoryName" - 'AccessToken' = $AccessToken - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'AssigneeCount' = $Assignee.Count + 'Issue' = (Get-PiiSafeString -PlainText $Issue) + } + + $hashBody = @{ + 'assignees' = $userNames } - return Invoke-GHRestMethod @params + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ShouldProcess($userNames -join ', ', "Remove assignee(s)")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Delete' + 'Description' = "Removing assignees from issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = $script:symmetraAcceptHeader + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) + } } } diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index 3bbb297a..357d5792 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubRepositoryBranch +@{ + GitHubBranchTypeName = 'GitHub.Branch' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubRepositoryBranch { <# .SYNOPSIS @@ -38,22 +44,60 @@ function Get-GitHubRepositoryBranch the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of branches within the given repository. + GitHub.Branch + List of branches within the given repository. .EXAMPLE - Get-GitHubRepositoryBranch -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub Gets all branches for the specified repository. .EXAMPLE - Get-GitHubRepositoryBranch -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -Name master + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubRepositoryBranch + + Gets all branches for the specified repository. + + .EXAMPLE + Get-GitHubRepositoryBranch -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -BranchName master Gets information only on the master branch for the specified repository. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubRepositoryBranch -BranchName master + + Gets information only on the master branch for the specified repository. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $branch = $repo | Get-GitHubRepositoryBranch -BranchName master + $branch | Get-GitHubRepositoryBranch + + Gets information only on the master branch for the specified repository, and then does it + again. This tries to show some of the different types of objects you can pipe into this + function. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubBranchTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [Alias('Get-GitHubBranch')] @@ -66,10 +110,13 @@ function Get-GitHubRepositoryBranch [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [string] $Name, + [Parameter(ValueFromPipelineByPropertyName)] + [string] $BranchName, [switch] $ProtectedOnly, @@ -90,7 +137,7 @@ function Get-GitHubRepositoryBranch } $uriFragment = "repos/$OwnerName/$RepositoryName/branches" - if (-not [String]::IsNullOrEmpty($Name)) { $uriFragment = $uriFragment + "/$Name" } + if (-not [String]::IsNullOrEmpty($BranchName)) { $uriFragment = $uriFragment + "/$BranchName" } $getParams = @() if ($ProtectedOnly) { $getParams += 'protected=true' } @@ -104,6 +151,54 @@ function Get-GitHubRepositoryBranch 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubBranchAdditionalProperties) } +filter Add-GitHubBranchAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Branch objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Branch +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubBranchTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.commit.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + Add-Member -InputObject $item -Name 'BranchName' -Value $item.name -MemberType NoteProperty -Force + } + + Write-Output $item + } +} diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index c66c4b77..0320f399 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -95,6 +95,13 @@ function Set-GitHubConfiguration Specify this switch to disable the hashing of potential PII data prior to submitting the data to telemetry (if telemetry hasn't been disabled via DisableTelemetry). + .PARAMETER DisablePipelineSupport + By default, this module will modify all objects returned by the API calls by adding + additional, consistent properties to those objects which ease pipelining those objects + into other functions. This is highly convenient functionality. You would only want to + disable this functionality if you are experiencing some edge case problems and are awaiting + a proper fix. + .PARAMETER DisableSmarterObjects By default, this module will modify all objects returned by the API calls to update any properties that can be converted to objects (like strings for Date/Time's being @@ -189,6 +196,8 @@ function Set-GitHubConfiguration [switch] $DisablePiiProtection, + [switch] $DisablePipelineSupport, + [switch] $DisableSmarterObjects, [switch] $DisableTelemetry, @@ -279,6 +288,7 @@ function Get-GitHubConfiguration 'DefaultRepositoryName', 'DisableLogging', 'DisablePiiProtection', + 'DisablePipelineSupport', 'DisableSmarterObjects', 'DisableTelemetry', 'DisableUpdateCheck', @@ -338,7 +348,8 @@ function Save-GitHubConfiguration if (($null -ne $ev) -and ($ev.Count -gt 0)) { - Write-Log -Message "Failed to persist these updated settings to disk. They will remain for this PowerShell session only." -Level Warning -Exception $ev[0] + $message = "Failed to persist these updated settings to disk. They will remain for this PowerShell session only." + Write-Log -Message $message -Level Warning -Exception $ev[0] } } @@ -449,7 +460,8 @@ function Resolve-PropertyValue } else { - Write-Log "The locally cached $Name configuration was not of type $Type. Reverting to default value." -Level Warning + $message = "The locally cached $Name configuration was not of type $Type. Reverting to default value." + Write-Log -Message $message -Level Warning return $DefaultValue } } @@ -485,7 +497,8 @@ function Reset-GitHubConfiguration Deletes the local configuration file and loads in all default configuration values. .NOTES - This command will not clear your authentication token. Please use Clear-GitHubAuthentication to accomplish that. + This command will not clear your authentication token. + Please use Clear-GitHubAuthentication to accomplish that. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -503,13 +516,15 @@ function Reset-GitHubConfiguration if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) { - Write-Log -Message "Reset was unsuccessful. Experienced a problem trying to remove the file [$script:configurationFilePath]." -Level Warning -Exception $ev[0] + $message = "Reset was unsuccessful. Experienced a problem trying to remove the file [$script:configurationFilePath]." + Write-Log -Message $message -Level Warning -Exception $ev[0] } } Initialize-GitHubConfiguration - Write-Log -Message "This has not cleared your authentication token. Call Clear-GitHubAuthentication to accomplish that." -Level Verbose + $message = "This has not cleared your authentication token. Call Clear-GitHubAuthentication to accomplish that." + Write-Log -Message $message -Level Verbose } function Read-GitHubConfiguration @@ -555,7 +570,8 @@ function Read-GitHubConfiguration } catch { - Write-Log -Message 'The configuration file for this module is in an invalid state. Use Reset-GitHubConfiguration to recover.' -Level Warning + $message = 'The configuration file for this module is in an invalid state. Use Reset-GitHubConfiguration to recover.' + Write-Log -Message $message -Level Warning } } @@ -613,6 +629,7 @@ function Import-GitHubConfiguration 'applicationInsightsKey' = '66d83c52-3070-489b-886b-09860e05e78a' 'disableLogging' = ([String]::IsNullOrEmpty($logPath)) 'disablePiiProtection' = $false + 'disablePipelineSupport' = $false 'disableSmarterObjects' = $false 'disableTelemetry' = $false 'disableUpdateCheck' = $false @@ -628,11 +645,13 @@ function Import-GitHubConfiguration 'suppressTelemetryReminder' = $false 'webRequestTimeoutSec' = 0 - # This hash is generated by using Helper.ps1's Get-Sha512Hash in Tests/Config/Settings.ps1 like so: + # This hash is generated by using Helper.ps1's Get-Sha512Hash in Tests/Config/Settings.ps1 + # like so: # . ./Helpers.ps1; Get-Sha512Hash -PlainText (Get-Content -Path ./Tests/Config/Settings.ps1 -Raw -Encoding Utf8) - # The hash is used to identify if the user has made changes to the config file prior to running the UT's locally. - # It intentionally cannot be modified via Set-GitHubConfiguration and must be updated directly in the - # source code here should the default Settings.ps1 file ever be changed. + # The hash is used to identify if the user has made changes to the config file prior to + # running the UT's locally. It intentionally cannot be modified via Set-GitHubConfiguration + # and must be updated directly in the source code here should the default Settings.ps1 file + # ever be changed. 'testConfigSettingsHash' = '272EE14CED396100A7AFD23EA21CA262470B7F4D80E47B7ABD90508B86210775F020EEF79D322F4C22A53835F700E1DFD13D0509C1D08DD6F9771B3F0133EDAB' } @@ -664,7 +683,7 @@ function Backup-GitHubConfiguration The path to store the user's current configuration file. .PARAMETER Force - If specified, will overwrite the contents of any file with the same name at th + If specified, will overwrite the contents of any file with the same name at the location specified by Path. .EXAMPLE @@ -717,7 +736,9 @@ function Restore-GitHubConfiguration [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [ValidateScript({if (Test-Path -Path $_ -PathType Leaf) { $true } else { throw "$_ does not exist." }})] + [ValidateScript({ + if (Test-Path -Path $_ -PathType Leaf) { $true } + else { throw "$_ does not exist." }})] [string] $Path ) @@ -924,7 +945,8 @@ function Clear-GitHubAuthentication Clears out any GitHub API token from memory, as well as from local file storage. .NOTES - This command will not clear your configuration settings. Please use Reset-GitHubConfiguration to accomplish that. + This command will not clear your configuration settings. + Please use Reset-GitHubConfiguration to accomplish that. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -946,14 +968,18 @@ function Clear-GitHubAuthentication { Remove-Item -Path $script:accessTokenFilePath -Force -ErrorAction SilentlyContinue -ErrorVariable ev - if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) + if (($null -ne $ev) -and + ($ev.Count -gt 0) -and + ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) { - Write-Log -Message "Experienced a problem trying to remove the file that persists the Access Token [$script:accessTokenFilePath]." -Level Warning -Exception $ev[0] + $message = "Experienced a problem trying to remove the file that persists the Access Token [$script:accessTokenFilePath]." + Write-Log -Message $message -Level Warning -Exception $ev[0] } } } - Write-Log -Message "This has not cleared your configuration settings. Call Reset-GitHubConfiguration to accomplish that." -Level Verbose + $message = "This has not cleared your configuration settings. Call Reset-GitHubConfiguration to accomplish that." + Write-Log -Message $message -Level Verbose } function Get-AccessToken @@ -1010,19 +1036,22 @@ function Get-AccessToken { $secureString = $content | ConvertTo-SecureString - Write-Log -Message "Restoring Access Token from file. This value can be cleared in the future by calling Clear-GitHubAuthentication." -Level Verbose + $message = "Restoring Access Token from file. This value can be cleared in the future by calling Clear-GitHubAuthentication." + Write-Log -Message $messsage -Level Verbose $script:accessTokenCredential = New-Object System.Management.Automation.PSCredential "", $secureString return $script:accessTokenCredential.GetNetworkCredential().Password } catch { - Write-Log -Message 'The Access Token file for this module contains an invalid SecureString (files can''t be shared by users or computers). Use Set-GitHubAuthentication to update it.' -Level Warning + $message = 'The Access Token file for this module contains an invalid SecureString (files can''t be shared by users or computers). Use Set-GitHubAuthentication to update it.' + Write-Log -Message $message -Level Warning } } if (-not [String]::IsNullOrEmpty($global:gitHubApiToken)) { - Write-Log -Message 'Storing the Access Token in `$global:gitHubApiToken` is insecure and is no longer recommended. To cache your Access Token for use across future PowerShell sessions, please use Set-GitHubAuthentication instead.' -Level Warning + $message = 'Storing the Access Token in `$global:gitHubApiToken` is insecure and is no longer recommended. To cache your Access Token for use across future PowerShell sessions, please use Set-GitHubAuthentication instead.' + Write-Log -Message $message -Level Warning return $global:gitHubApiToken } @@ -1030,7 +1059,8 @@ function Get-AccessToken (-not $script:seenTokenWarningThisSession)) { $script:seenTokenWarningThisSession = $true - Write-Log -Message 'This module has not yet been configured with a personal GitHub Access token. The module can still be used, but GitHub will limit your usage to 60 queries per hour. You can get a GitHub API token from https://github.com/settings/tokens/new (provide a description and check any appropriate scopes).' -Level Warning + $message = 'This module has not yet been configured with a personal GitHub Access token. The module can still be used, but GitHub will limit your usage to 60 queries per hour. You can get a GitHub API token from https://github.com/settings/tokens/new (provide a description and check any appropriate scopes).' + Write-Log -Message $message -Level Warning } return $null diff --git a/GitHubContents.ps1 b/GitHubContents.ps1 index fce4b6c4..1514382e 100644 --- a/GitHubContents.ps1 +++ b/GitHubContents.ps1 @@ -1,4 +1,10 @@ -function Get-GitHubContent +@{ + GitHubContentTypeName = 'GitHub.Content' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + + filter Get-GitHubContent { <# .SYNOPSIS @@ -26,14 +32,19 @@ function Get-GitHubContent .PARAMETER MediaType The format in which the API will return the body of the issue. - Object - Return a json object representation a file or folder. This is the default if you do not pass any specific media type. - Raw - Return the raw contents of a file. - Html - For markup files such as Markdown or AsciiDoc, you can retrieve the rendered HTML using the Html media type. + + Object - Return a json object representation a file or folder. + This is the default if you do not pass any specific media type. + Raw - Return the raw contents of a file. + Html - For markup files such as Markdown or AsciiDoc, + you can retrieve the rendered HTML using the Html media type. .PARAMETER ResultAsString - If this switch is specified and the MediaType is either Raw or Html then the resulting bytes will be decoded the result will be - returned as a string instead of bytes. If the MediaType is Object, then an additional property on the object is returned 'contentAsString' - which will be the decoded base64 result as a string. + If this switch is specified and the MediaType is either Raw or Html then the + resulting bytes will be decoded the result will be returned as a string instead of bytes. + If the MediaType is Object, then an additional property on the object named + 'contentAsString' will be included and its value will be the decoded base64 result + as a string. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -45,6 +56,25 @@ function Get-GitHubContent the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + [String] + GitHub.Content + .EXAMPLE Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -MediaType Html @@ -59,21 +89,39 @@ function Get-GitHubContent Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path Tests List the files within the "Tests" path of the repository + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubContent -Path Tests + + List the files within the "Tests" path of the repository + + .NOTES + Unable to specify Path as ValueFromPipeline because a Repository object may be incorrectly + coerced into a string used for Path, thus confusing things. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Elements')] + [OutputType([String])] + [OutputType({$script:GitHubContentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $RepositoryName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $Path, @@ -141,5 +189,57 @@ function Get-GitHubContent } } + if ($MediaType -eq 'Object') + { + $null = $result | Add-GitHubContentAdditionalProperties + } + return $result } + +filter Add-GitHubContentAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Content objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Content +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubContentTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Write-Output $item + } +} \ No newline at end of file diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 4a095b65..542ae5a8 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -4,15 +4,21 @@ @{ defaultAcceptHeader = 'application/vnd.github.v3+json' mediaTypeVersion = 'v3' - squirrelAcceptHeader = 'application/vnd.github.squirrel-girl-preview' - symmetraAcceptHeader = 'application/vnd.github.symmetra-preview+json' - mercyAcceptHeader = 'application/vnd.github.mercy-preview+json' - nebulaAcceptHeader = 'application/vnd.github.nebula-preview+json' baptisteAcceptHeader = 'application/vnd.github.baptiste-preview+json' - scarletWitchAcceptHeader = 'application/vnd.github.scarlet-witch-preview+json' dorianAcceptHeader = 'application/vnd.github.dorian-preview+json' + hagarAcceptHeader = 'application/vnd.github.hagar-preview+json' + hellcatAcceptHeader = 'application/vnd.github.hellcat-preview+json' + inertiaAcceptHeader = 'application/vnd.github.inertia-preview+json' londonAcceptHeader = 'application/vnd.github.london-preview+json' - + machineManAcceptHeader = 'application/vnd.github.machine-man-preview' + mercyAcceptHeader = 'application/vnd.github.mercy-preview+json' + mockingbirdAcceptHeader = 'application/vnd.github.mockingbird-preview' + nebulaAcceptHeader = 'application/vnd.github.nebula-preview+json' + sailorVAcceptHeader = 'application/vnd.github.sailor-v-preview+json' + scarletWitchAcceptHeader = 'application/vnd.github.scarlet-witch-preview+json' + squirrelGirlAcceptHeader = 'application/vnd.github.squirrel-girl-preview' + starfoxAcceptHeader = 'application/vnd.github.starfox-preview+json' + symmetraAcceptHeader = 'application/vnd.github.symmetra-preview+json' }.GetEnumerator() | ForEach-Object { Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value } @@ -97,8 +103,8 @@ function Invoke-GHRestMethod no additional status shown to the user until a response is returned from the REST request. .NOTES - This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access to the headers - that are returned in the response, and Invoke-RestMethod drops those headers. + This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access + to the headers that are returned in the response, and Invoke-RestMethod drops those headers. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -239,7 +245,15 @@ function Invoke-GHRestMethod $jobName = "Invoke-GHRestMethod-" + (Get-Date).ToFileTime().ToString() [scriptblock]$scriptBlock = { - param($Url, $Method, $Headers, $Body, $ValidBodyContainingRequestMethods, $TimeoutSec, $LogRequestBody, $ScriptRootPath) + param( + $Url, + $Method, + $Headers, + $Body, + $ValidBodyContainingRequestMethods, + $TimeoutSec, + $LogRequestBody, + $ScriptRootPath) # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within # the context of this script block since we're running in a different @@ -341,7 +355,8 @@ function Invoke-GHRestMethod } catch [ArgumentException] { - # The content must not be JSON (which is a legitimate situation). We'll return the raw content result instead. + # The content must not be JSON (which is a legitimate situation). + # We'll return the raw content result instead. # We do this unnecessary assignment to avoid PSScriptAnalyzer's PSAvoidUsingEmptyCatchBlock. $finalResult = $finalResult } @@ -353,7 +368,9 @@ function Invoke-GHRestMethod # a lot of time. Let's optimize here by not bothering to send in something that we # know is definitely not convertible ([int32] on PS5, [long] on PS7). if (($finalResult -isnot [Object[]]) -or - (($finalResult.Count -gt 0) -and ($finalResult[0] -isnot [int]) -and ($finalResult[0] -isnot [long]))) + (($finalResult.Count -gt 0) -and + ($finalResult[0] -isnot [int]) -and + ($finalResult[0] -isnot [long]))) { $finalResult = ConvertTo-SmarterObject -InputObject $finalResult } @@ -418,8 +435,9 @@ function Invoke-GHRestMethod } catch { - # We only know how to handle WebExceptions, which will either come in "pure" when running with -NoStatus, - # or will come in as a RemoteException when running normally (since it's coming from the asynchronous Job). + # We only know how to handle WebExceptions, which will either come in "pure" + # when running with -NoStatus, or will come in as a RemoteException when running + # normally (since it's coming from the asynchronous Job). $ex = $null $message = $null $statusCode = $null @@ -524,7 +542,9 @@ function Invoke-GHRestMethod if ($statusCode -eq 404) { - $output += "This typically happens when the current user isn't properly authenticated. You may need an Access Token with additional scopes checked." + $explanation = @('This typically happens when the current user isn''t properly authenticated.', + 'You may need an Access Token with additional scopes checked.') + $output += ($explanation -join ' ') } if (-not [String]::IsNullOrEmpty($requestId)) @@ -701,7 +721,7 @@ function Invoke-GHRestMethodMultipleResult } } -function Split-GitHubUri +filter Split-GitHubUri { <# .SYNOPSIS @@ -723,28 +743,39 @@ function Split-GitHubUri .PARAMETER RepositoryName Returns the Repository Name from the Uri if it can be identified. + .INPUTS + [String] + .OUTPUTS [PSCustomObject] - The OwnerName and RepositoryName elements from the provided URL .EXAMPLE - Split-GitHubUri -Uri 'https://github.com/PowerShell/PowerShellForGitHub' + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' PowerShellForGitHub .EXAMPLE - Split-GitHubUri -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -RepositoryName + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' -RepositoryName PowerShellForGitHub .EXAMPLE - Split-GitHubUri -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -OwnerName + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' -OwnerName + + microsoft + + .EXAMPLE + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' - PowerShell + @{'ownerName' = 'microsoft'; 'repositoryName' = 'PowerShellForGitHub'} #> [CmdletBinding(DefaultParameterSetName='RepositoryName')] + [OutputType([Hashtable])] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string] $Uri, @@ -776,10 +807,50 @@ function Split-GitHubUri { return $components.ownerName } - elseif ($RepositoryName -or ($PSCmdlet.ParameterSetName -eq 'RepositoryName')) + elseif ($RepositoryName) { return $components.repositoryName } + else + { + return $components + } +} + +function Join-GitHubUri +{ +<# + .SYNOPSIS + Combines the provided repository elements into a repository URL. + + .DESCRIPTION + Combines the provided repository elements into a repository URL. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + + .PARAMETER RepositoryName + Name of the repository. + + .OUTPUTS + [String] - The repository URL. +#> + [CmdletBinding()] + [OutputType([String])] + param + ( + [Parameter(Mandatory)] + [string] $OwnerName, + + [Parameter(Mandatory)] + [string] $RepositoryName + ) + + + $hostName = (Get-GitHubConfiguration -Name 'ApiHostName') + return "https://$hostName/$OwnerName/$RepositoryName" } function Resolve-RepositoryElements @@ -892,6 +963,12 @@ filter ConvertTo-SmarterObject .PARAMETER InputObject The object to update + + .INPUTS + [object] + + .OUTPUTS + [object] #> [CmdletBinding()] param( @@ -937,7 +1014,8 @@ filter ConvertTo-SmarterObject } catch { - Write-Log -Message "Unable to convert $($property.Name) value of $($property.Value) to a [DateTime] object. Leaving as-is." -Level Verbose + $message = "Unable to convert $($property.Name) value of $($property.Value) to a [DateTime] object. Leaving as-is." + Write-Log -Message $message -Level Verbose } } @@ -970,10 +1048,15 @@ function Get-MediaAcceptHeader .PARAMETER MediaType The format in which the API will return the body of the comment or issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. Object - Return a json object representation a file or folder. .PARAMETER AsJson @@ -982,12 +1065,16 @@ function Get-MediaAcceptHeader .PARAMETER AcceptHeader The accept header that should be included with the MediaType accept header. + .OUTPUTS + [String] + .EXAMPLE Get-MediaAcceptHeader -MediaType Raw Returns a formatted AcceptHeader for v3 of the response object #> [CmdletBinding()] + [OutputType([String])] param( [ValidateSet('Raw', 'Text', 'Html', 'Full', 'Object')] [string] $MediaType = 'Raw', diff --git a/GitHubEvents.ps1 b/GitHubEvents.ps1 index 7dae9bc9..c904bc6a 100644 --- a/GitHubEvents.ps1 +++ b/GitHubEvents.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubEvent +@{ + GitHubEventTypeName = 'GitHub.Event' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubEvent { <# .DESCRIPTION @@ -22,11 +28,13 @@ function Get-GitHubEvent The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER EventID - The ID of a specific event to get. If not supplied, will return back all events for this repository. + .PARAMETER EventId + The ID of a specific event to get. + If not supplied, will return back all events for this repository. .PARAMETER Issue - Issue number to get events for. If not supplied, will return back all events for this repository. + Issue number to get events for. + If not supplied, will return back all events for this repository. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -38,37 +46,91 @@ function Get-GitHubEvent the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Event + .EXAMPLE - Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the events for the Microsoft\PowerShellForGitHub project. + Get the events for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='RepositoryElements')] + [OutputType({$script:GitHubEventTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='EventElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='EventElements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='EventElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='EventElements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='RepositoryUri')] - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='EventUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='RepositoryUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='EventUri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='EventUri')] - [Parameter(Mandatory, ParameterSetName='EventElements')] - [int64] $EventID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='EventUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='EventElements')] + [int64] $EventId, - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueElements')] + [Alias('IssueNumber')] [int64] $Issue, [string] $AccessToken, @@ -86,16 +148,16 @@ function Get-GitHubEvent 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) 'ProvidedIssue' = $PSBoundParameters.ContainsKey('Issue') - 'ProvidedEvent' = $PSBoundParameters.ContainsKey('EventID') + 'ProvidedEvent' = $PSBoundParameters.ContainsKey('EventId') } $uriFragment = "repos/$OwnerName/$RepositoryName/issues/events" $description = "Getting events for $RepositoryName" - if ($PSBoundParameters.ContainsKey('EventID')) + if ($PSBoundParameters.ContainsKey('EventId')) { - $uriFragment = "repos/$OwnerName/$RepositoryName/issues/events/$EventID" - $description = "Getting event $EventID for $RepositoryName" + $uriFragment = "repos/$OwnerName/$RepositoryName/issues/events/$EventId" + $description = "Getting event $EventId for $RepositoryName" } elseif ($PSBoundParameters.ContainsKey('Issue')) { @@ -104,10 +166,10 @@ function Get-GitHubEvent } $acceptHeaders = @( - 'application/vnd.github.starfox-preview+json', - 'application/vnd.github.sailer-v-preview+json', - 'application/vnd.github.symmetra-preview+json', - 'application/vnd.github.machine-man-preview') + $script:starfoxAcceptHeader, + $script:sailorVAcceptHeader, + $script:symmetraAcceptHeader, + $script:machineManAcceptHeader) $params = @{ 'UriFragment' = $uriFragment @@ -119,5 +181,134 @@ function Get-GitHubEvent 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubEventAdditionalProperties) +} + +filter Add-GitHubEventAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Event objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Event +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubEventTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'EventId' -Value $item.id -MemberType NoteProperty -Force + + @('actor', 'assignee', 'assigner', 'assignees', 'committer', 'requested_reviewer', 'review_requester', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.issue) + { + $null = Add-GitHubIssueAdditionalProperties -InputObject $item.issue + Add-Member -InputObject $item -Name 'IssueId' -Value $item.issue.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueNumber' -Value $item.issue.number -MemberType NoteProperty -Force + } + + if ($null -ne $item.label) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.label + } + + if ($null -ne $item.labels) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.labels + } + + if ($null -ne $item.milestone) + { + $null = Add-GitHubMilestoneAdditionalProperties -InputObject $item.milestone + } + + if ($null -ne $item.project_id) + { + Add-Member -InputObject $item -Name 'ProjectId' -Value $item.project_id -MemberType NoteProperty -Force + } + + if ($null -ne $item.project_card) + { + $null = Add-GitHubProjectCardAdditionalProperties -InputObject $item.project_card + Add-Member -InputObject $item -Name 'CardId' -Value $item.project_card.id -MemberType NoteProperty -Force + } + + if ($null -ne $item.column_name) + { + Add-Member -InputObject $item -Name 'ColumnName' -Value $item.column_name -MemberType NoteProperty -Force + } + + if ($null -ne $item.source) + { + $null = Add-GitHubIssueAdditionalProperties -InputObject $item.source + if ($item.source.PSObject.TypeNames[0] -eq 'GitHub.PullRequest') + { + Add-Member -InputObject $item -Name 'PullRequestId' -Value $item.source.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $item.source.number -MemberType NoteProperty -Force + } + else + { + Add-Member -InputObject $item -Name 'IssueId' -Value $item.source.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueNumber' -Value $item.source.number -MemberType NoteProperty -Force + } + } + + if ($item.issue_url -match '^.*/issues/(\d+)$') + { + $issueNumber = $Matches[1] + Add-Member -InputObject $item -Name 'IssueNumber' -Value $issueNumber -MemberType NoteProperty -Force + } + + if ($item.pull_request_url -match '^.*/pull/(\d+)$') + { + $pullRequestNumber = $Matches[1] + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $pullRequestNumber -MemberType NoteProperty -Force + } + + if ($null -ne $item.dismissed_review) + { + # TODO: Add dismissed_review (object) and dismissed_review[review_id] once Reviews are supported + + # $null = Add-GitHubPullRequestReviewAdditionalProperties -InputObject $item.dismissed_review + # Add-Member -InputObject $item -Name 'ReviewId' -Value $item.dismissed_review.review_id -MemberType NoteProperty -Force + } + } + + Write-Output $item + } } diff --git a/GitHubComments.ps1 b/GitHubIssueComments.ps1 similarity index 58% rename from GitHubComments.ps1 rename to GitHubIssueComments.ps1 index ce8ac615..a388db93 100644 --- a/GitHubComments.ps1 +++ b/GitHubIssueComments.ps1 @@ -1,11 +1,18 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubComment +@{ + GitHubCommentTypeName = 'GitHub.Comment' + GitHubIssueCommentTypeName = 'GitHub.IssueComment' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubIssueComment { <# .DESCRIPTION - Get the comments for a given Github repository. + Get the Issue comments for a given GitHub repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -22,7 +29,7 @@ function Get-GitHubComment The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER CommentID + .PARAMETER Comment The ID of a specific comment to get. If not supplied, will return back all comments for this repository. .PARAMETER Issue @@ -40,10 +47,15 @@ function Get-GitHubComment .PARAMETER MediaType The format in which the API will return the body of the comment. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -55,52 +67,136 @@ function Get-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.IssueComment + + .EXAMPLE + Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub + + Get all of the Issue comments for the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubIssueComment -Since ([DateTime]::Now).AddDays(-1) + + Get all of the Issue comments for the microsoft\PowerShellForGitHub project since yesterday. + .EXAMPLE - Get-GitHubComment-OwnerName Microsoft -RepositoryName PowerShellForGitHub + $issue = $repo | Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 - Get the comments for the Microsoft\PowerShellForGitHub project. + Get the comments Issue #1 in the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $issue = $repo | Get-GitHubIssue -Issue 1 + $issue | Get-GitHubIssueComment + + Get the comments Issue #1 in the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $issue = $repo | Get-GitHubIssue -Issue 1 + $comments = $issue | Get-GitHubIssueComment + $comment[0] | Get-GitHubIssueComment + + Get the most recent comment on Issue #1 in the microsoft\PowerShellForGitHub project by + passing it in via the pipeline. This shows some of the different types of objects you + can pipe into this function. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='RepositoryElements')] + [Alias('Get-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 + [OutputType({$script:GitHubIssueCommentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='CommentElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='CommentElements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='CommentElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='CommentElements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='RepositoryUri')] - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='CommentUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='RepositoryUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='CommentUri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='CommentUri')] - [Parameter(Mandatory, ParameterSetName='CommentElements')] - [string] $CommentID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='CommentElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='CommentUri')] + [Alias('CommentId')] + [string] $Comment, - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='RepositoryElements')] + [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='IssueElements')] [Parameter(ParameterSetName='IssueUri')] [DateTime] $Since, - [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='RepositoryElements')] + [Parameter(ParameterSetName='RepositoryUri')] [ValidateSet('Created', 'Updated')] [string] $Sort, - [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='RepositoryElements')] + [Parameter(ParameterSetName='RepositoryUri')] [ValidateSet('Ascending', 'Descending')] [string] $Direction, @@ -130,13 +226,13 @@ function Get-GitHubComment 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) 'ProvidedIssue' = $PSBoundParameters.ContainsKey('Issue') - 'ProvidedComment' = $PSBoundParameters.ContainsKey('CommentID') + 'ProvidedComment' = $PSBoundParameters.ContainsKey('Comment') } - if ($PSBoundParameters.ContainsKey('CommentID')) + if ($PSBoundParameters.ContainsKey('Comment')) { - $uriFragment = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentId" - $description = "Getting comment $CommentID for $RepositoryName" + $uriFragment = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" + $description = "Getting comment $Comment for $RepositoryName" } elseif ($PSBoundParameters.ContainsKey('Issue')) { @@ -181,20 +277,20 @@ function Get-GitHubComment 'UriFragment' = $uriFragment 'Description' = $description 'AccessToken' = $AccessToken - 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelAcceptHeader) + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubIssueCommentAdditionalProperties) } -function New-GitHubComment +filter New-GitHubIssueComment { <# .DESCRIPTION - Creates a new Github comment in an issue for the given repository + Creates a new GitHub comment for an issue for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -235,14 +331,35 @@ function New-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.IssueComment + .EXAMPLE - New-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" + New-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" - Creates a new Github comment in an issue for the Microsoft\PowerShellForGitHub project. + Creates a new GitHub comment for an issue for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [Alias('New-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 + [OutputType({$script:GitHubIssueCommentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -254,10 +371,15 @@ function New-GitHubComment [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [Parameter(Mandatory)] @@ -293,20 +415,20 @@ function New-GitHubComment 'Method' = 'Post' 'Description' = "Creating comment under issue $Issue for $RepositoryName" 'AccessToken' = $AccessToken - 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelAcceptHeader) + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueCommentAdditionalProperties) } -function Set-GitHubComment +filter Set-GitHubIssueComment { <# .DESCRIPTION - Set an existing comment in an issue for the given repository + Modifies an existing comment in an issue for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -323,8 +445,8 @@ function Set-GitHubComment The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER CommentID - The comment ID of the comment to edit. + .PARAMETER Comment + The ID of the comment to edit. .PARAMETER Body The new contents of the comment. @@ -332,10 +454,15 @@ function Set-GitHubComment .PARAMETER MediaType The format in which the API will return the body of the comment. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -347,14 +474,35 @@ function Set-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.IssueComment + .EXAMPLE - Set-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Body "Testing this API" + Set-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 -Body "Testing this API" - Update an existing comment in an issue for the Microsoft\PowerShellForGitHub project. + Updates an existing comment in an issue for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [Alias('Set-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 + [OutputType({$script:GitHubIssueCommentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -366,11 +514,16 @@ function Set-GitHubComment [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] - [string] $CommentID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CommentId')] + [int64] $Comment, [Parameter(Mandatory)] [string] $Body, @@ -392,7 +545,7 @@ function Set-GitHubComment $telemetryProperties = @{ 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'CommentID' = (Get-PiiSafeString -PlainText $CommentID) + 'Comment' = (Get-PiiSafeString -PlainText $Comment) } $hashBody = @{ @@ -400,25 +553,25 @@ function Set-GitHubComment } $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentID" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Update comment $CommentID for $RepositoryName" + 'Description' = "Update comment $Comment for $RepositoryName" 'AccessToken' = $AccessToken - 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelAcceptHeader) + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueCommentAdditionalProperties) } -function Remove-GitHubComment +filter Remove-GitHubIssueComment { <# .DESCRIPTION - Deletes a Github comment for the given repository + Deletes a GitHub comment from an Issue in the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -435,8 +588,8 @@ function Remove-GitHubComment The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER CommentID - The id of the comment to delete. + .PARAMETER Comment + The ID of the comment to delete. .PARAMETER Force If this switch is specified, you will not be prompted for confirmation of command execution. @@ -451,26 +604,45 @@ function Remove-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 + Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 - Deletes a Github comment from the Microsoft\PowerShellForGitHub project. + Deletes a GitHub comment from an Issue in the microsoft\PowerShellForGitHub project. .EXAMPLE - Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Confirm:$false + Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 -Confirm:$false - Deletes a Github comment from the Microsoft\PowerShellForGitHub project without prompting confirmation. + Deletes a Github comment from an Issue in the microsoft\PowerShellForGitHub project + without prompting confirmation. .EXAMPLE - Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Force + Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 -Force - Deletes a Github comment from the Microsoft\PowerShellForGitHub project without prompting confirmation. + Deletes a GitHub comment from an Issue in the microsoft\PowerShellForGitHub project + without prompting confirmation. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements', ConfirmImpact="High")] [Alias('Delete-GitHubComment')] + [Alias('Delete-GitHubIssueComment')] + [Alias('Remove-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] @@ -481,11 +653,16 @@ function Remove-GitHubComment [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] - [string] $CommentID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CommentId')] + [int64] $Comment, [switch] $Force, @@ -503,7 +680,7 @@ function Remove-GitHubComment $telemetryProperties = @{ 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'CommentID' = (Get-PiiSafeString -PlainText $CommentID) + 'Comment' = (Get-PiiSafeString -PlainText $Comment) } if ($Force -and (-not $Confirm)) @@ -511,12 +688,12 @@ function Remove-GitHubComment $ConfirmPreference = 'None' } - if ($PSCmdlet.ShouldProcess($CommentID, "Remove comment")) + if ($PSCmdlet.ShouldProcess($Comment, "Remove comment")) { $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentID" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" 'Method' = 'Delete' - 'Description' = "Removing comment $CommentID for $RepositoryName" + 'Description' = "Removing comment $Comment for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -527,3 +704,60 @@ function Remove-GitHubComment } } +filter Add-GitHubIssueCommentAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Issue Comment objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.IssueComment +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubIssueCommentTypeName + ) + + foreach ($item in $InputObject) + { + # Provide a generic comment type too + $item.PSObject.TypeNames.Insert(0, $script:GitHubCommentTypeName) + + # But we want the specific type on top + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + Add-Member -InputObject $item -Name 'CommentId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.user) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.user + } + } + + Write-Output $item + } +} \ No newline at end of file diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index d27bf93e..8e903c00 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubIssue +@{ + GitHubIssueTypeName = 'GitHub.Issue' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubIssue { <# .SYNOPSIS @@ -29,7 +35,7 @@ function Get-GitHubIssue The organization whose issues should be retrieved. .PARAMETER RepositoryType - all: Retrieve issues across owned, member and org repositories + all: Retrieve issues across owned, member and org repositories ownedAndMember: Retrieve issues across owned and member repositories .PARAMETER Issue @@ -69,7 +75,7 @@ function Get-GitHubIssue all: All milestones will be returned. none: Only issues without milestones will be returned. - .PARAMETER Milestone + .PARAMETER MilestoneNumber Only issues with this milestone will be returned. .PARAMETER AssigneeType @@ -85,15 +91,20 @@ function Get-GitHubIssue Only issues created by this specified user will be returned. .PARAMETER Mentioned - Only issues that mention this specified user will be returned. + Only issues that mention this specified user will be returned. .PARAMETER MediaType The format in which the API will return the body of the issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -105,19 +116,39 @@ function Get-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.Issue + .EXAMPLE - Get-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State Open + Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -State Open - Gets all the currently open issues in the Microsoft\PowerShellForGitHub repository. + Gets all the currently open issues in the microsoft\PowerShellForGitHub repository. .EXAMPLE - Get-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State All -Assignee Octocat + Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -State All -Assignee Octocat - Gets every issue in the Microsoft\PowerShellForGitHub repository that is assigned to Octocat. + Gets every issue in the microsoft\PowerShellForGitHub repository that is assigned to Octocat. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -128,14 +159,19 @@ function Get-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [Parameter(ValueFromPipelineByPropertyName)] [string] $OrganizationName, [ValidateSet('All', 'OwnedAndMember')] [string] $RepositoryType = 'All', + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [switch] $IgnorePullRequests, @@ -159,13 +195,16 @@ function Get-GitHubIssue [ValidateSet('Specific', 'All', 'None')] [string] $MilestoneType, - [string] $Milestone, + [Parameter(ValueFromPipelineByPropertyName)] + [int64] $MilestoneNumber, [ValidateSet('Specific', 'All', 'None')] [string] $AssigneeType, [string] $Assignee, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('UserName')] [string] $Creator, [string] $Mentioned, @@ -266,17 +305,17 @@ function Get-GitHubIssue { $getParams += 'mentioned=none' } - elseif ([String]::IsNullOrEmpty($Milestone)) + elseif ($PSBoundParameters.ContainsKey('$MilestoneNumber')) { - $message = "MilestoneType was set to [$MilestoneType], but no value for Milestone was provided." + $message = "MilestoneType was set to [$MilestoneType], but no value for MilestoneNumber was provided." Write-Log -Message $message -Level Error throw $message } } - if ($PSBoundParameters.ContainsKey('Milestone')) + if ($PSBoundParameters.ContainsKey('MilestoneNumber')) { - $getParams += "milestone=$Milestone" + $getParams += "milestone=$MilestoneNumber" } if ($PSBoundParameters.ContainsKey('AssigneeType')) @@ -324,7 +363,7 @@ function Get-GitHubIssue try { - $result = Invoke-GHRestMethodMultipleResult @params + $result = (Invoke-GHRestMethodMultipleResult @params | Add-GitHubIssueAdditionalProperties) if ($IgnorePullRequests) { @@ -339,7 +378,7 @@ function Get-GitHubIssue finally {} } -function Get-GitHubIssueTimeline +filter Get-GitHubIssueTimeline { <# .SYNOPSIS @@ -376,12 +415,31 @@ function Get-GitHubIssueTimeline the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Event + .EXAMPLE - Get-GitHubIssueTimeline -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 24 + Get-GitHubIssueTimeline -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 24 #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubEventTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -393,10 +451,15 @@ function Get-GitHubIssueTimeline [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [string] $AccessToken, @@ -418,17 +481,17 @@ function Get-GitHubIssueTimeline $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/timeline" 'Description' = "Getting timeline for Issue #$Issue in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.mockingbird-preview' + 'AcceptHeader' = $script:mockingbirdAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubEventAdditionalProperties) } -function New-GitHubIssue +filter New-GitHubIssue { <# .SYNOPSIS @@ -470,10 +533,15 @@ function New-GitHubIssue .PARAMETER MediaType The format in which the API will return the body of the issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -485,12 +553,31 @@ function New-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Issue + .EXAMPLE - New-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Title 'Test Issue' + New-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title 'Test Issue' #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -501,7 +588,9 @@ function New-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter(Mandatory)] @@ -512,6 +601,8 @@ function New-GitHubIssue [string[]] $Assignee, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('MilestoneNumber')] [int64] $Milestone, [string[]] $Label, @@ -556,10 +647,10 @@ function New-GitHubIssue 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) } -function Update-GitHubIssue +filter Update-GitHubIssue { <# .SYNOPSIS @@ -596,7 +687,7 @@ function Update-GitHubIssue Login(s) for Users to assign to the issue. Provide an empty array to clear all existing assignees. - .PARAMETER Milestone + .PARAMETER MilestoneNumber The number of the milestone to associate this issue with. Set to 0/$null to remove current. @@ -610,10 +701,15 @@ function Update-GitHubIssue .PARAMETER MediaType The format in which the API will return the body of the issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -625,12 +721,31 @@ function Update-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Issue + .EXAMPLE - Update-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -State Closed + Update-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -State Closed #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -641,10 +756,15 @@ function Update-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [string] $Title, @@ -653,7 +773,7 @@ function Update-GitHubIssue [string[]] $Assignee, - [int64] $Milestone, + [int64] $MilestoneNumber, [string[]] $Label, @@ -686,10 +806,10 @@ function Update-GitHubIssue if ($PSBoundParameters.ContainsKey('Assignee')) { $hashBody['assignees'] = @($Assignee) } if ($PSBoundParameters.ContainsKey('Label')) { $hashBody['labels'] = @($Label) } if ($PSBoundParameters.ContainsKey('State')) { $hashBody['state'] = $State.ToLower() } - if ($PSBoundParameters.ContainsKey('Milestone')) + if ($PSBoundParameters.ContainsKey('MilestoneNumber')) { - $hashBody['milestone'] = $Milestone - if ($Milestone -in (0, $null)) + $hashBody['milestone'] = $MilestoneNumber + if ($MilestoneNumber -in (0, $null)) { $hashBody['milestone'] = $null } @@ -707,10 +827,10 @@ function Update-GitHubIssue 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) } -function Lock-GitHubIssue +filter Lock-GitHubIssue { <# .SYNOPSIS @@ -750,8 +870,23 @@ function Lock-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Lock-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -Reason Spam + Lock-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -Reason Spam #> [CmdletBinding( SupportsShouldProcess, @@ -766,10 +901,15 @@ function Lock-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [ValidateSet('OffTopic', 'TooHeated', 'Resolved', 'Spam')] @@ -805,7 +945,7 @@ function Lock-GitHubIssue } $telemetryProperties['Reason'] = $Reason - $hashBody['active_lock_reason'] = $reasonConverter[$Reason] + $hashBody['lock_reason'] = $reasonConverter[$Reason] } $params = @{ @@ -813,7 +953,7 @@ function Lock-GitHubIssue 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Put' 'Description' = "Locking Issue #$Issue on $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.sailor-v-preview+json' + 'AcceptHeader' = $script:sailorVAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -823,7 +963,7 @@ function Lock-GitHubIssue return Invoke-GHRestMethod @params } -function Unlock-GitHubIssue +filter Unlock-GitHubIssue { <# .SYNOPSIS @@ -860,55 +1000,162 @@ function Unlock-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Unlock-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 4 + Unlock-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 #> -[CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName='Elements')] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] -param( - [Parameter(ParameterSetName='Elements')] - [string] $OwnerName, + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, - [Parameter(ParameterSetName='Elements')] - [string] $RepositoryName, + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, - [Parameter( - Mandatory, - ParameterSetName='Uri')] - [string] $Uri, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, - [Parameter(Mandatory)] - [int64] $Issue, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] + [int64] $Issue, - [string] $AccessToken, + [string] $AccessToken, - [switch] $NoStatus -) + [switch] $NoStatus + ) -Write-InvocationLog + Write-InvocationLog -$elements = Resolve-RepositoryElements -$OwnerName = $elements.ownerName -$RepositoryName = $elements.repositoryName + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName -$telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) -} + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } -$params = @{ - 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock" - 'Method' = 'Delete' - 'Description' = "Unlocking Issue #$Issue on $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.sailor-v-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock" + 'Method' = 'Delete' + 'Description' = "Unlocking Issue #$Issue on $RepositoryName" + 'AcceptHeader' = $script:sailorVAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params } -return Invoke-GHRestMethod @params +filter Add-GitHubIssueAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Issue objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Issue +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubIssueTypeName + ) + + foreach ($item in $InputObject) + { + # Pull requests are _also_ issues. A pull request that is retrieved through the + # Issue endpoint will also have a 'pull_request' property. Let's make sure that + # we mark it up appropriately. + if ($null -ne $item.pull_request) + { + $null = Add-GitHubPullRequestAdditionalProperties -InputObject $item + Write-Output $item + continue + } + + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueNumber' -Value $item.number -MemberType NoteProperty -Force + + @('assignee', 'assignees', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.labels) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.labels + } + + if ($null -ne $item.milestone) + { + $null = Add-GitHubMilestoneAdditionalProperties -InputObject $item.milestone + } + + if ($null -ne $item.closed_by) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.closed_by + } + + if ($null -ne $item.repository) + { + $null = Add-GitHubRepositoryAdditionalProperties -InputObject $item.repository + } + } + + Write-Output $item + } } diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index a134e08e..b020e54c 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubLabel +@{ + GitHubLabelTypeName = 'GitHub.Label' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubLabel { <# .SYNOPSIS @@ -25,15 +31,15 @@ function Get-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Name of the specific label to be retrieved. If not supplied, all labels will be retrieved. Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Issue If provided, will return all of the labels for this particular issue. - .PARAMETER Milestone - If provided, will return all of the labels for this particular milestone. + .PARAMETER MilestoneNumber + If provided, will return all of the labels assigned to issues for this particular milestone. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -45,52 +51,74 @@ function Get-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Get-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets the information for every label from the Microsoft\PowerShellForGitHub project. + Gets the information for every label from the microsoft\PowerShellForGitHub project. .EXAMPLE - Get-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -LabelName TestLabel + Get-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel - Gets the information for the label named "TestLabel" from the Microsoft\PowerShellForGitHub + Gets the information for the label named "TestLabel" from the microsoft\PowerShellForGitHub project. + + .NOTES + There were a lot of complications with the ParameterSets with this function due to pipeline + input. For the time being, the ParameterSets have been simplified and the validation of + parameter combinations is happening within the function itself. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='NameUri')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] - [Parameter(Mandatory, ParameterSetName='NameElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] - [Parameter(Mandatory, ParameterSetName='NameElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='NameUri')] - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='NameUri')] - [Parameter(Mandatory, ParameterSetName='NameElements')] + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] [Alias('LabelName')] - [string] $Name, + [string] $Label, - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] - [int64] $Milestone, + [Parameter(ValueFromPipelineByPropertyName)] + [int64] $MilestoneNumber, [string] $AccessToken, @@ -108,6 +136,23 @@ function Get-GitHubLabel 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + # There were a lot of complications trying to get pipelining working right when using all of + # the necessary ParameterSets, so we'll do internal parameter validation instead until someone + # can figure out the right way to do the parameter sets here _with_ pipeline support. + if ($PSBoundParameters.ContainsKey('Label') -or + $PSBoundParameters.ContainsKey('Issue') -or + $PSBoundParameters.ContainsKey('MilestoneNumber')) + { + if (-not ($PSBoundParameters.ContainsKey('Label') -xor + $PSBoundParameters.ContainsKey('Issue') -xor + $PSBoundParameters.ContainsKey('MilestoneNumber'))) + { + $message = 'Label, Issue and Milestone are mutually exclusive. Only one can be specified in a single command.' + Write-Log -Message $message -Level Error + throw $message + } + } + $uriFragment = [String]::Empty $description = [String]::Empty @@ -116,18 +161,18 @@ function Get-GitHubLabel $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels" $description = "Getting labels for Issue $Issue in $RepositoryName" } - elseif ($PSBoundParameters.ContainsKey('Milestone')) + elseif ($PSBoundParameters.ContainsKey('MilestoneNumber')) { - $uriFragment = "/repos/$OwnerName/$RepositoryName/milestones/$Milestone/labels" - $description = "Getting labels for Milestone $Milestone in $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/milestones/$MilestoneNumber/labels" + $description = "Getting labels for issues in Milestone $MilestoneNumber in $RepositoryName" } else { - $uriFragment = "repos/$OwnerName/$RepositoryName/labels/$Name" + $uriFragment = "repos/$OwnerName/$RepositoryName/labels/$Label" if ($PSBoundParameters.ContainsKey('Name')) { - $description = "Getting label $Name for $RepositoryName" + $description = "Getting label $Label for $RepositoryName" } else { @@ -138,22 +183,17 @@ function Get-GitHubLabel $params = @{ 'UriFragment' = $uriFragment 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - if (-not [String]::IsNullOrWhiteSpace($Name)) - { - $params["Description"] = "Getting label $Name for $RepositoryName" - } - - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubLabelAdditionalProperties) } -function New-GitHubLabel +filter New-GitHubLabel { <# .SYNOPSIS @@ -177,12 +217,13 @@ function New-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Name of the label to be created. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Color - Color (in HEX) for the new label, without the leading # sign. + Color (in HEX) for the new label. .PARAMETER Description A short description of the label. @@ -197,14 +238,33 @@ function New-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - New-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Color BBBBBB + New-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Color BBBBBB Creates a new, grey-colored label called "TestLabel" in the PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -216,16 +276,20 @@ function New-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline)] [Alias('LabelName')] - [string] $Name, + [string] $Label, [Parameter(Mandatory)] - [Alias('LabelColor')] [ValidateScript({if ($_ -match '^#?[ABCDEF0-9]{6}$') { $true } else { throw "Color must be provided in hex." }})] + [Alias('LabelColor')] [string] $Color = "EEEEEE", [string] $Description, @@ -254,7 +318,7 @@ function New-GitHubLabel } $hashBody = @{ - 'name' = $Name + 'name' = $Label 'color' = $Color 'description' = $Description } @@ -263,18 +327,18 @@ function New-GitHubLabel 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Creating label $Name in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'Description' = "Creating label $Label in $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) } -function Remove-GitHubLabel +filter Remove-GitHubLabel { <# .SYNOPSIS @@ -298,7 +362,7 @@ function Remove-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Name of the label to be deleted. Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ @@ -315,20 +379,44 @@ function Remove-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel + Remove-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel Removes the label called "TestLabel" from the PowerShellForGitHub project. .EXAMPLE - Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Confirm:$false + $label = $repo | Get-GitHubLabel -Label 'Test Label' -Color '#AAAAAA' + $label | Remove-GitHubLabel + + Removes the label we just created using the pipeline, but will prompt for confirmation + because neither -Confirm:$false nor -Force was specified. - Removes the label called "TestLabel" from the PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + .EXAMPLE + Remove-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Confirm:$false + + Removes the label called "TestLabel" from the PowerShellForGitHub project. + Will not prompt for confirmation, as -Confirm:$false was specified. .EXAMPLE - Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Force + Remove-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Force - Removes the label called "TestLabel" from the PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + Removes the label called "TestLabel" from the PowerShellForGitHub project. + Will not prompt for confirmation, as -Force was specified. #> [CmdletBinding( SupportsShouldProcess, @@ -345,13 +433,18 @@ function Remove-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('LabelName')] - [string] $Name, + [string] $Label, [switch] $Force, @@ -376,13 +469,13 @@ function Remove-GitHubLabel $ConfirmPreference = 'None' } - if ($PSCmdlet.ShouldProcess($Name, "Remove label")) + if ($PSCmdlet.ShouldProcess($Label, "Remove label")) { $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Label" 'Method' = 'Delete' - 'Description' = "Deleting label $Name from $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'Description' = "Deleting label $Label from $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -393,7 +486,7 @@ function Remove-GitHubLabel } } -function Update-GitHubLabel +filter Update-GitHubLabel { <# .SYNOPSIS @@ -417,16 +510,18 @@ function Update-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Current name of the label to be updated. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER NewName New name for the label being updated. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Color - Color (in HEX) for the new label, without the leading # sign. + Color (in HEX) for the new label. .PARAMETER Description A short description of the label. @@ -441,8 +536,26 @@ function Update-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Update-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -NewName NewTestLabel -LabelColor BBBB00 + Update-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -NewName NewTestLabel -Color BBBB00 Updates the existing label called TestLabel in the PowerShellForGitHub project to be called 'NewTestLabel' and be colored yellow. @@ -450,6 +563,7 @@ function Update-GitHubLabel [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -460,18 +574,20 @@ function Update-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [Alias('LabelName')] - [string] $Name, + [string] $Label, - [Parameter(Mandatory)] [Alias('NewLabelName')] [string] $NewName, - [Parameter(Mandatory)] [Alias('LabelColor')] [ValidateScript({if ($_ -match '^#?[ABCDEF0-9]{6}$') { $true } else { throw "Color must be provided in hex." }})] [string] $Color = "EEEEEE", @@ -494,27 +610,34 @@ function Update-GitHubLabel 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + # Be robust to users who choose to provide a color in hex by specifying the leading # sign + # (by just stripping it out). + if ($Color.StartsWith('#')) + { + $Color = $Color.Substring(1) + } + $hashBody = @{} if ($PSBoundParameters.ContainsKey('NewName')) { $hashBody['name'] = $NewName } - if ($PSBoundParameters.ContainsKey('Color')) { $hashBody['color'] = $Color } if ($PSBoundParameters.ContainsKey('Description')) { $hashBody['description'] = $Description } + if ($PSBoundParameters.ContainsKey('Color')) { $hashBody['color'] = $Color } $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Label" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Updating label $Name" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'Description' = "Updating label $Label" + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) } -function Set-GitHubLabel +filter Set-GitHubLabel { <# .SYNOPSIS @@ -558,19 +681,34 @@ function Set-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. - .NOTES - This method does not rename any existing labels, as it doesn't have any context regarding - which Issue the new name is for. Therefore, it is possible that by running this function - on a repository with Issues that have already been assigned Labels, you may experience data - loss as a minor correction to you (maybe fixing a typo) will result in the old Label being - removed (and thus unassigned from existing Issues) and then the new one created. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository .EXAMPLE - Set-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Label @(@{'name' = 'TestLabel'; 'color' = 'EEEEEE'}, @{'name' = 'critical'; 'color' = 'FF000000'; 'description' = 'Needs immediate attention'}) + Set-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label @(@{'name' = 'TestLabel'; 'color' = 'EEEEEE'}, @{'name' = 'critical'; 'color' = 'FF000000'; 'description' = 'Needs immediate attention'}) Removes any labels not in this Label array, ensure the current assigned color and descriptions match what's in the array for the labels that do already exist, and then creates new labels for any remaining ones in the Label list. + + .NOTES + This method does not rename any existing labels, as it doesn't have any context regarding + which Label the new name is for. Therefore, it is possible that by running this function + on a repository with Issues that have already been assigned Labels, you may experience data + loss as a minor correction to you (maybe fixing a typo) will result in the old Label being + removed (and thus unassigned from existing Issues) and then the new one created. #> [CmdletBinding( SupportsShouldProcess, @@ -586,9 +724,12 @@ function Set-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [Parameter(ValueFromPipelineByPropertyName)] [object[]] $Label, [string] $AccessToken, @@ -623,12 +764,12 @@ function Set-GitHubLabel if ($labelToConfigure.name -notin $existingLabelNames) { # Create label if it doesn't exist - $null = New-GitHubLabel -Name $labelToConfigure.name -Color $labelToConfigure.color @commonParams + $null = New-GitHubLabel -Label $labelToConfigure.name -Color $labelToConfigure.color @commonParams } else { # Update label's color if it already exists - $null = Update-GitHubLabel -Name $labelToConfigure.name -NewName $labelToConfigure.name -Color $labelToConfigure.color @commonParams + $null = Update-GitHubLabel -Label $labelToConfigure.name -NewName $labelToConfigure.name -Color $labelToConfigure.color @commonParams } } @@ -637,7 +778,7 @@ function Set-GitHubLabel if ($labelName -notin $labelNames) { # Remove label if it exists but is not in desired label list - $null = Remove-GitHubLabel -Name $labelName @commonParams -Confirm:$false + $null = Remove-GitHubLabel -Label $labelName @commonParams -Confirm:$false } } } @@ -666,7 +807,7 @@ function Add-GitHubIssueLabel .PARAMETER Issue Issue number to add the label to. - .PARAMETER Name + .PARAMETER Label Array of label names to add to the issue .PARAMETER AccessToken @@ -679,69 +820,124 @@ function Add-GitHubIssueLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Add-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Name $labels + Add-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Label $labels Adds labels to an issue in the PowerShellForGitHub project. + + .NOTES + This is implemented as a function rather than a filter because the ValueFromPipeline + parameter (Name) is itself an array which we want to ensure is processed only a single time. + This API endpoint doesn't add labels to a repository, it replaces the existing labels with + the new set provided, so we need to make sure that we have all the requested labels available + to us at the time that the API endpoint is called. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] [Alias('LabelName')] - [string[]] $Name, + [ValidateNotNullOrEmpty()] + [string[]] $Label, [string] $AccessToken, [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'LabelCount' = $Name.Count + begin + { + $labelNames = @() } - $hashBody = @{ - 'labels' = $Name + process + { + foreach ($name in $Label) + { + $labelNames += $name + } } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Post' - 'Description' = "Adding labels to issue $Issue in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + end + { + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'LabelCount' = $Label.Count + } + + $hashBody = @{ + 'labels' = $labelNames + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Adding labels to issue $Issue in $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) + } } function Set-GitHubIssueLabel @@ -768,9 +964,12 @@ function Set-GitHubIssueLabel .PARAMETER Issue Issue number to replace the labels. - .PARAMETER LabelName + .PARAMETER Label Array of label names that will be set on the issue. + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. @@ -781,14 +980,65 @@ function Set-GitHubIssueLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Set-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -LabelName $labels + Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Label $labels Replaces labels on an issue in the PowerShellForGitHub project. + + .EXAMPLE + ('help wanted', 'good first issue') | Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + + Replaces labels on an issue in the PowerShellForGitHub project + with 'help wanted' and 'good first issue'. + + .EXAMPLE + Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Confirm:$false + + Removes all labels from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Confirm:$false was specified. + + This is the same result as having called + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Confirm:$false + + .EXAMPLE + Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Force + + Removes all labels from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Force was specified. + + This is the same result as having called + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Force + + .NOTES + This is implemented as a function rather than a filter because the ValueFromPipeline + parameter (Name) is itself an array which we want to ensure is processed only a single time. + This API endpoint doesn't add labels to a repository, it replaces the existing labels with + the new set provided, so we need to make sure that we have all the requested labels available + to us at the time that the API endpoint is called. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact='High')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -800,53 +1050,86 @@ function Set-GitHubIssueLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter(ValueFromPipelineByPropertyName)] [Alias('LabelName')] - [string[]] $Name, + [string[]] $Label, + + [switch] $Force, [string] $AccessToken, [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'LabelCount' = $Name.Count + begin + { + $labelNames = @() } - $hashBody = @{ - 'labels' = $Name + process + { + foreach ($name in $Label) + { + $labelNames += $name + } } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Put' - 'Description' = "Replacing labels to issue $Issue in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + end + { + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'LabelCount' = $Label.Count + } - return Invoke-GHRestMethod @params + $hashBody = @{ + 'labels' = $labelNames + } + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (($labelNames.Count -eq 0) -and (-not $PSCmdlet.ShouldProcess($Issue, "Remove all labels from issue"))) + { + return + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Put' + 'Description' = "Replacing labels to issue $Issue in $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) + } } -function Remove-GitHubIssueLabel +filter Remove-GitHubIssueLabel { <# .DESCRIPTION @@ -870,9 +1153,10 @@ function Remove-GitHubIssueLabel .PARAMETER Issue Issue number to remove the label from. - .PARAMETER Name + .PARAMETER Label Name of the label to be deleted. If not provided, will delete all labels on the issue. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Force If this switch is specified, you will not be prompted for confirmation of command execution. @@ -887,20 +1171,37 @@ function Remove-GitHubIssueLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Issue 1 Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. .EXAMPLE - Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 -Confirm:$false + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Issue 1 -Confirm:$false - Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Confirm:$false was specified. .EXAMPLE - Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 -Force + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Issue 1 -Force - Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Force was specified. #> [CmdletBinding( SupportsShouldProcess, @@ -908,21 +1209,33 @@ function Remove-GitHubIssueLabel ConfirmImpact="High")] [Alias('Delete-GitHubLabel')] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('LabelName')] - [string] $Name, + [string] $Label, [switch] $Force, @@ -943,10 +1256,9 @@ function Remove-GitHubIssueLabel } $description = [String]::Empty - - if ($PSBoundParameters.ContainsKey('Name')) + if ($PSBoundParameters.ContainsKey('Label')) { - $description = "Deleting label $Name from issue $Issue in $RepositoryName" + $description = "Deleting label $Label from issue $Issue in $RepositoryName" } else { @@ -958,13 +1270,13 @@ function Remove-GitHubIssueLabel $ConfirmPreference = 'None' } - if ($PSCmdlet.ShouldProcess($Name, "Remove label")) + if ($PSCmdlet.ShouldProcess($Label, "Remove label")) { $params = @{ - 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Name" + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Label" 'Method' = 'Delete' 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -975,6 +1287,60 @@ function Remove-GitHubIssueLabel } } +filter Add-GitHubLabelAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Label objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Label +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubLabelTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + if ($null -ne $item.id) + { + Add-Member -InputObject $item -Name 'LabelId' -Value $item.id -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'LabelName' -Value $item.name -MemberType NoteProperty -Force + } + + Write-Output $item + } +} + # A set of labels that a project might want to initially populate their repository with # Used by Set-GitHubLabel when no Label list is provided by the user. # This list exists to support v0.1.0 users. diff --git a/GitHubMilestones.ps1 b/GitHubMilestones.ps1 index 96f5946e..70b5b003 100644 --- a/GitHubMilestones.ps1 +++ b/GitHubMilestones.ps1 @@ -1,15 +1,21 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -# For more information refer to" +@{ + GitHubMilestoneTypeName = 'GitHub.Milestone' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +# For more information refer to: # https://github.community/t5/How-to-use-Git-and-GitHub/Milestone-quot-Due-On-quot-field-defaults-to-7-00-when-set-by-v3/m-p/6901 $script:minimumHoursToEnsureDesiredDateInPacificTime = 9 -function Get-GitHubMilestone +filter Get-GitHubMilestone { <# .DESCRIPTION - Get the milestones for a given Github repository. + Get the milestones for a given GitHub repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -27,7 +33,8 @@ function Get-GitHubMilestone them individually. .PARAMETER Milestone - The number of a specific milestone to get. If not supplied, will return back all milestones for this repository. + The number of a specific milestone to get. If not supplied, will return back all milestones + for this repository. .PARAMETER Sort How to sort the results. @@ -48,33 +55,75 @@ function Get-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Milestone + .EXAMPLE - Get-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub - Get the milestones for the Microsoft\PowerShellForGitHub project. + Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub + Get the milestones for the microsoft\PowerShellForGitHub project. .EXAMPLE Get-GitHubMilestone -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -Milestone 1 - Get milestone number 1 for the Microsoft\PowerShellForGitHub project. + Get milestone number 1 for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='RepositoryElements')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] - [Parameter(Mandatory, ParameterSetName='RepositoryUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='MilestoneUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='RepositoryUri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='MilestoneUri')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='MilestoneElements')] + [Alias('MilestoneNumber')] [int64] $Milestone, [Parameter(ParameterSetName='RepositoryUri')] @@ -128,16 +177,17 @@ function Get-GitHubMilestone } $getParams += "sort=$($sortConverter[$Sort])" - } - if ($PSBoundParameters.ContainsKey('Direction')) - { - $directionConverter = @{ - 'Ascending' = 'asc' - 'Descending' = 'desc' - } + # We only look at this parameter if the user provided Sort as well. + if ($PSBoundParameters.ContainsKey('Direction')) + { + $directionConverter = @{ + 'Ascending' = 'asc' + 'Descending' = 'desc' + } - $getParams += "direction=$($directionConverter[$Direction])" + $getParams += "direction=$($directionConverter[$Direction])" + } } if ($PSBoundParameters.ContainsKey('State')) @@ -159,14 +209,14 @@ function Get-GitHubMilestone 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubMilestoneAdditionalProperties) } -function New-GitHubMilestone +filter New-GitHubMilestone { <# .DESCRIPTION - Creates a new Github milestone for the given repository + Creates a new GitHub milestone for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -207,10 +257,28 @@ function New-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Milestone + .EXAMPLE - New-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" + New-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" - Creates a new Github milestone for the Microsoft\PowerShellForGitHub project. + Creates a new GitHub milestone for the microsoft\PowerShellForGitHub project. .NOTES For more information on how GitHub handles the dates specified in DueOn, please refer to @@ -229,17 +297,31 @@ function New-GitHubMilestone DefaultParameterSetName='Elements')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='Elements')] [string] $Title, [ValidateSet('Open', 'Closed')] @@ -305,10 +387,10 @@ function New-GitHubMilestone 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubMilestoneAdditionalProperties) } -function Set-GitHubMilestone +filter Set-GitHubMilestone { <# .DESCRIPTION @@ -356,10 +438,28 @@ function Set-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Milestone + .EXAMPLE - Set-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API" + Set-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API" - Update an existing milestone for the Microsoft\PowerShellForGitHub project. + Update an existing milestone for the microsoft\PowerShellForGitHub project. .NOTES For more information on how GitHub handles the dates specified in DueOn, please refer to @@ -378,21 +478,40 @@ function Set-GitHubMilestone DefaultParameterSetName='Elements')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements')] + [Alias('MilestoneNumber')] [int64] $Milestone, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $Title, [ValidateSet('Open', 'Closed')] @@ -459,14 +578,14 @@ function Set-GitHubMilestone 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubMilestoneAdditionalProperties) } -function Remove-GitHubMilestone +filter Remove-GitHubMilestone { <# .DESCRIPTION - Deletes a Github milestone for the given repository + Deletes a GitHub milestone for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -499,20 +618,37 @@ function Remove-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 + Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 - Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. + Deletes a GitHub milestone from the microsoft\PowerShellForGitHub project. .EXAMPLE - Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Confirm:$false + Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Confirm:$false - Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + Deletes a Github milestone from the microsoft\PowerShellForGitHub project. Will not prompt + for confirmation, as -Confirm:$false was specified. .EXAMPLE - Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Force + Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Force - Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + Deletes a Github milestone from the microsoft\PowerShellForGitHub project. Will not prompt + for confirmation, as -Force was specified. #> [CmdletBinding( SupportsShouldProcess, @@ -521,18 +657,33 @@ function Remove-GitHubMilestone [Alias('Delete-GitHubMilestone')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] - [string] $Milestone, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements')] + [Alias('MilestoneNumber')] + [int64] $Milestone, [switch] $Force, @@ -573,3 +724,57 @@ function Remove-GitHubMilestone return Invoke-GHRestMethod @params } } + +filter Add-GitHubMilestoneAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Milestone objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Milestone +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubMilestoneTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'MilestoneId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'MilestoneNumber' -Value $item.number -MemberType NoteProperty -Force + + if ($null -ne $item.creator) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.creator + } + } + + Write-Output $item + } +} diff --git a/GitHubMiscellaneous.ps1 b/GitHubMiscellaneous.ps1 index 28e9d08d..b137d6ea 100644 --- a/GitHubMiscellaneous.ps1 +++ b/GitHubMiscellaneous.ps1 @@ -1,6 +1,16 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +@{ + GitHubRateLimitTypeName = 'GitHub.RateLimit' + GitHubLicenseTypeName = 'GitHub.License' + GitHubEmojiTypeName = 'GitHub.Emoji' + GitHubCodeOfConductTypeName = 'GitHub.CodeOfConduct' + GitHubGitignoreTypeName = 'GitHub.Gitignore' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + function Get-GitHubRateLimit { <# @@ -27,7 +37,7 @@ function Get-GitHubRateLimit If not supplied here, the DefaultNoStatus configuration property value will be used. .OUTPUTS - [PSCustomObject] + GitHub.RateLimit Limits returned are _per hour_. The Search API has a custom rate limit, separate from the rate limit @@ -38,7 +48,8 @@ function Get-GitHubRateLimit For these reasons, the Rate Limit API response categorizes your rate limit. Under resources, you'll see three objects: - The core object provides your rate limit status for all non-search-related resources in the REST API. + The core object provides your rate limit status for all non-search-related resources + in the REST API. The search object provides your rate limit status for the Search API. The graphql object provides your rate limit status for the GraphQL API. @@ -52,6 +63,7 @@ function Get-GitHubRateLimit Get-GitHubRateLimit #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubRateLimitTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -71,7 +83,9 @@ function Get-GitHubRateLimit 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + $result.PSObject.TypeNames.Insert(0, $script:GitHubRateLimitTypeName) + return $result } function ConvertFrom-GitHubMarkdown @@ -110,6 +124,9 @@ function ConvertFrom-GitHubMarkdown the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + .OUTPUTS [String] The HTML version of the Markdown content. @@ -119,6 +136,7 @@ function ConvertFrom-GitHubMarkdown Returns back '

Bolded Text

' #> [CmdletBinding(SupportsShouldProcess)] + [OutputType([String])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -126,7 +144,9 @@ function ConvertFrom-GitHubMarkdown Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] - [ValidateScript({if ([System.Text.Encoding]::UTF8.GetBytes($_).Count -lt 400000) { $true } else { throw "Content must be less than 400 KB." }})] + [ValidateScript({ + if ([System.Text.Encoding]::UTF8.GetBytes($_).Count -lt 400000) { $true } + else { throw "Content must be less than 400 KB." }})] [string] $Content, [ValidateSet('Markdown', 'GitHubFlavoredMarkdown')] @@ -177,7 +197,7 @@ function ConvertFrom-GitHubMarkdown } } -function Get-GitHubLicense +filter Get-GitHubLicense { <# .SYNOPSIS @@ -201,8 +221,8 @@ function Get-GitHubLicense The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name - The name of the license to retrieve the content for. If not specified, all licenses + .PARAMETER Key + The key of the license to retrieve the content for. If not specified, all licenses will be returned. .PARAMETER AccessToken @@ -215,27 +235,52 @@ function Get-GitHubLicense the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + [String] + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + + .OUTPUTS + GitHub.License + .EXAMPLE Get-GitHubLicense Returns metadata about popular open source licenses .EXAMPLE - Get-GitHubLicense -Name mit + Get-GitHubLicense -Key mit Gets the content of the mit license file .EXAMPLE - Get-GitHubLicense -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubLicense -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets the content of the license file for the Microsoft\PowerShellForGitHub repository. + Gets the content of the license file for the microsoft\PowerShellForGitHub repository. It may be necessary to convert the content of the file. Check the 'encoding' property of the result to know how 'content' is encoded. As an example, to convert from Base64, do the following: [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($result.content)) #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='All')] + [OutputType({$script:GitHubLicenseTypeName})] + [OutputType({$script:GitHubContentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -246,13 +291,17 @@ function Get-GitHubLicense [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Individual')] - [string] $Name, + [Alias('LicenseKey')] + [string] $Key, [string] $AccessToken, @@ -269,11 +318,11 @@ function Get-GitHubLicense $uriFragment = 'licenses' $description = 'Getting all licenses' - if ($PSBoundParameters.ContainsKey('Name')) + if ($PSBoundParameters.ContainsKey('Key')) { - $telemetryProperties['Name'] = $Name - $uriFragment = "licenses/$Name" - $description = "Getting the $Name license" + $telemetryProperties['Key'] = $Name + $uriFragment = "licenses/$Key" + $description = "Getting the $Key license" } elseif ((-not [String]::IsNullOrEmpty($OwnerName)) -and (-not [String]::IsNullOrEmpty($RepositoryName))) { @@ -293,7 +342,35 @@ function Get-GitHubLicense 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + foreach ($item in $result) + { + if ($PSCmdlet.ParameterSetName -in ('Elements', 'Uri')) + { + $null = $item | Add-GitHubContentAdditionalProperties + + # Add the decoded Base64 content directly to the object as an additional String property + $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($item.content)) + Add-Member -InputObject $item -NotePropertyName "contentAsString" -NotePropertyValue $decoded + + $item.license.PSObject.TypeNames.Insert(0, $script:GitHubLicenseTypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'LicenseKey' -Value $item.license.key -MemberType NoteProperty -Force + } + } + else + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubLicenseTypeName) + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'LicenseKey' -Value $item.key -MemberType NoteProperty -Force + } + } + } + + return $result } function Get-GitHubEmoji @@ -318,13 +395,13 @@ function Get-GitHubEmoji If not supplied here, the DefaultNoStatus configuration property value will be used. .OUTPUTS - [PSCustomObject] - The emoji name and a link to its image. + GitHub.Emoji .EXAMPLE Get-GitHubEmoji #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubEmojiTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -344,17 +421,19 @@ function Get-GitHubEmoji 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + $result.PSObject.TypeNames.Insert(0, $script:GitHubEmojiTypeName) + return $result } -function Get-GitHubCodeOfConduct +filter Get-GitHubCodeOfConduct { <# .SYNOPSIS - Gets license or license content from GitHub. + Gets Codes of Conduct or a specific Code of Conduct from GitHub. .DESCRIPTION - Gets license or license content from GitHub. + Gets Codes of Conduct or a specific Code of Conduct from GitHub. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -371,9 +450,9 @@ function Get-GitHubCodeOfConduct The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name - The name of the license to retrieve the content for. If not specified, all licenses - will be returned. + .PARAMETER Key + The unique key of the Code of Conduct to retrieve the content for. If not specified, all + Codes of Conduct will be returned. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -385,20 +464,40 @@ function Get-GitHubCodeOfConduct the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + + .OUTPUTS + GitHub.CodeOfConduct + .EXAMPLE Get-GitHubCodeOfConduct Returns metadata about popular Codes of Conduct .EXAMPLE - Get-GitHubCodeOfConduct -Name citizen_code_of_conduct + Get-GitHubCodeOfConduct -Key citizen_code_of_conduct Gets the content of the 'Citizen Code of Conduct' .EXAMPLE - Get-GitHubCodeOfConduct -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubCodeOfConduct -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets the content of the Code of Conduct file for the Microsoft\PowerShellForGitHub repository + Gets the content of the Code of Conduct file for the microsoft\PowerShellForGitHub repository if one is detected. It may be necessary to convert the content of the file. Check the 'encoding' property of @@ -408,6 +507,7 @@ function Get-GitHubCodeOfConduct [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($result.content)) #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubCodeOfConductTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -418,13 +518,17 @@ function Get-GitHubCodeOfConduct [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Individual')] - [string] $Name, + [Alias('CodeOfConductKey')] + [string] $Key, [string] $AccessToken, @@ -441,11 +545,11 @@ function Get-GitHubCodeOfConduct $uriFragment = 'codes_of_conduct' $description = 'Getting all Codes of Conduct' - if ($PSBoundParameters.ContainsKey('Name')) + if ($PSBoundParameters.ContainsKey('Key')) { - $telemetryProperties['Name'] = $Name - $uriFragment = "codes_of_conduct/$Name" - $description = "Getting the $Name Code of Conduct" + $telemetryProperties['Key'] = $Name + $uriFragment = "codes_of_conduct/$Key" + $description = "Getting the $Key Code of Conduct" } elseif ((-not [String]::IsNullOrEmpty($OwnerName)) -and (-not [String]::IsNullOrEmpty($RepositoryName))) { @@ -458,7 +562,7 @@ function Get-GitHubCodeOfConduct $params = @{ 'UriFragment' = $uriFragment 'Method' = 'Get' - 'AcceptHeader' = 'application/vnd.github.scarlet-witch-preview+json' + 'AcceptHeader' = $script:scarletWitchAcceptHeader 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -466,10 +570,20 @@ function Get-GitHubCodeOfConduct 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + foreach ($item in $result) + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubCodeOfConductTypeName) + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'CodeOfConductKey' -Value $item.key -MemberType NoteProperty -Force + } + } + + return $result } -function Get-GitHubGitIgnore +filter Get-GitHubGitIgnore { <# .SYNOPSIS @@ -484,6 +598,9 @@ function Get-GitHubGitIgnore The name of the .gitignore template whose content should be fetched. Not providing this will cause a list of all available templates to be returned. + .PARAMETER RawContent + If specified, the raw content of the specified .gitignore file will be returned. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. @@ -494,6 +611,12 @@ function Get-GitHubGitIgnore the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + + .OUTPUTS + GitHub.Gitignore + .EXAMPLE Get-GitHubGitIgnore @@ -505,10 +628,17 @@ function Get-GitHubGitIgnore Returns the content of the VisualStudio.gitignore template. #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubGitignoreTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( + [Parameter( + ValueFromPipeline, + ParameterSetName='Individual')] [string] $Name, + [Parameter(ParameterSetName='Individual')] + [switch] $RawContent, + [string] $AccessToken, [switch] $NoStatus @@ -537,5 +667,21 @@ function Get-GitHubGitIgnore 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + if ($RawContent) + { + $params['AcceptHeader'] = (Get-MediaAcceptHeader -MediaType 'Raw') + } + + $result = Invoke-GHRestMethod @params + if ($PSBoundParameters.ContainsKey('Name') -and (-not $RawContent)) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubGitignoreTypeName) + } + + if ($RawContent) + { + $result = [System.Text.Encoding]::UTF8.GetString($result) + } + + return $result } diff --git a/GitHubOrganizations.ps1 b/GitHubOrganizations.ps1 index db2073ba..021ba709 100644 --- a/GitHubOrganizations.ps1 +++ b/GitHubOrganizations.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubOrganizationMember +@{ + GitHubOrganizationTypeName = 'GitHub.Organization' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubOrganizationMember { <# .SYNOPSIS @@ -25,18 +31,26 @@ function Get-GitHubOrganizationMember the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + .OUTPUTS - [PSCustomObject[]] List of members within the organization. + GitHub.User + List of members within the organization. .EXAMPLE Get-GitHubOrganizationMember -OrganizationName PowerShell #> + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] - [CmdletBinding(SupportsShouldProcess)] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $OrganizationName, @@ -60,10 +74,10 @@ function Get-GitHubOrganizationMember 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) } -function Test-GitHubOrganizationMember +filter Test-GitHubOrganizationMember { <# .SYNOPSIS @@ -90,23 +104,30 @@ function Test-GitHubOrganizationMember the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + .OUTPUTS [Bool] .EXAMPLE Test-GitHubOrganizationMember -OrganizationName PowerShell -UserName Octocat #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [CmdletBinding(SupportsShouldProcess)] [OutputType([bool])] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $OrganizationName, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $UserName, @@ -142,3 +163,49 @@ function Test-GitHubOrganizationMember return $false } } + +filter Add-GitHubOrganizationAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Organization objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Organization +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubOrganizationTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'OrganizationName' -Value $item.login -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'OrganizationId' -Value $item.id -MemberType NoteProperty -Force + } + + Write-Output $item + } +} diff --git a/GitHubProjectCards.ps1 b/GitHubProjectCards.ps1 index 10a4d46d..ed911fa5 100644 --- a/GitHubProjectCards.ps1 +++ b/GitHubProjectCards.ps1 @@ -1,19 +1,25 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubProjectCard +@{ + GitHubProjectCardTypeName = 'GitHub.ProjectCard' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubProjectCard { <# .DESCRIPTION - Get the cards for a given Github Project Column. + Get the cards for a given GitHub Project Column. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub .PARAMETER Column ID of the column to retrieve cards for. - .PARAMETER ArchivedState - Only cards with this ArchivedState are returned. + .PARAMETER State + Only cards with this State are returned. Options are all, archived, or NotArchived (default). .PARAMETER AccessToken @@ -26,18 +32,25 @@ function Get-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectCard + .EXAMPLE Get-GitHubProjectCard -Column 999999 Get the the not_archived cards for column 999999. .EXAMPLE - Get-GitHubProjectCard -Column 999999 -ArchivedState All + Get-GitHubProjectCard -Column 999999 -State All - Gets all the cards for column 999999, no matter the ArchivedState. + Gets all the cards for column 999999, no matter the State. .EXAMPLE - Get-GitHubProjectCard -Column 999999 -ArchivedState Archived + Get-GitHubProjectCard -Column 999999 -State Archived Gets the archived cards for column 999999. @@ -48,17 +61,27 @@ function Get-GitHubProjectCard #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName = 'Column')] + DefaultParameterSetName = 'Card')] + [OutputType({$script:GitHubProjectCardTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Column')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Column')] + [Alias('ColumnId')] [int64] $Column, - [Parameter(Mandatory, ParameterSetName = 'Card')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Card')] + [Alias('CardId')] [int64] $Card, [ValidateSet('All', 'Archived', 'NotArchived')] - [string] $ArchivedState = 'NotArchived', + [Alias('ArchivedState')] + [string] $State = 'NotArchived', [string] $AccessToken, @@ -87,14 +110,14 @@ function Get-GitHubProjectCard $description = "Getting project card $Card" } - if ($PSBoundParameters.ContainsKey('ArchivedState')) + if ($PSBoundParameters.ContainsKey('State')) { $getParams = @() - $Archived = $ArchivedState.ToLower().Replace('notarchived','not_archived') + $Archived = $State.ToLower().Replace('notarchived','not_archived') $getParams += "archived_state=$Archived" $uriFragment = "$uriFragment`?" + ($getParams -join '&') - $description += " with ArchivedState '$Archived'" + $description += " with State '$Archived'" } $params = @{ @@ -104,17 +127,17 @@ function Get-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubProjectCardAdditionalProperties) } -function New-GitHubProjectCard +filter New-GitHubProjectCard { <# .DESCRIPTION - Creates a new card for a Github project. + Creates a new card for a GitHub project. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -124,13 +147,13 @@ function New-GitHubProjectCard .PARAMETER Note The name of the column to create. - .PARAMETER ContentId - The issue or pull request ID you want to associate with this card. + .PARAMETER IssueId + The ID of the issue you want to associate with this card (not to be confused with + the Issue _number_ which you see in the URL and can refer to with a hashtag). - .PARAMETER ContentType - The type of content you want to associate with this card. - Required if you provide ContentId. - Use Issue when ContentId is an issue ID and use PullRequest when ContentId is a pull request id. + .PARAMETER PullRequestId + The ID of the pull request you want to associate with this card (not to be confused with + the Pull Request _number_ which you see in the URL and can refer to with a hashtag). .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -142,44 +165,61 @@ function New-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. - .EXAMPLE - New-GitHubProjectCard -Column 999999 -Note 'Note on card' + .INPUTS + GitHub.IssueComment + GitHub.Issue + GitHub.PullRequest + GitHub.ProjectCard + GitHub.ProjectColumn - Creates a card on column 999999 with the note 'Note on card'. + .OUTPUTS + GitHub.ProjectCard .EXAMPLE - New-GitHubProjectCard -Column 999999 -ContentId 888888 -ContentType Issue + New-GitHubProjectCard -Column 999999 -Note 'Note on card' - Creates a card on column 999999 for the issue with ID 888888. + Creates a card on column 999999 with the note 'Note on card'. .EXAMPLE - New-GitHubProjectCard -Column 999999 -ContentId 888888 -ContentType Issue + New-GitHubProjectCard -Column 999999 -IssueId 888888 Creates a card on column 999999 for the issue with ID 888888. .EXAMPLE - New-GitHubProjectCard -Column 999999 -ContentId 777777 -ContentType PullRequest + New-GitHubProjectCard -Column 999999 -PullRequestId 888888 - Creates a card on column 999999 for the pull request with ID 777777. + Creates a card on column 999999 for the pull request with ID 888888. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Note')] + [OutputType({$script:GitHubProjectCardTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, - [Parameter(Mandatory, ParameterSetName = 'Note')] + [Parameter( + Mandatory, + ParameterSetName = 'Note')] + [Alias('Content')] [string] $Note, - [Parameter(Mandatory, ParameterSetName = 'Content')] - [int64] $ContentId, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Issue')] + [int64] $IssueId, - [Parameter(Mandatory, ParameterSetName = 'Content')] - [ValidateSet('Issue', 'PullRequest')] - [string] $ContentType, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'PullRequest')] + [int64] $PullRequestId, [string] $AccessToken, @@ -201,13 +241,22 @@ function New-GitHubProjectCard 'note' = $Note } } - elseif ($PSCmdlet.ParameterSetName -eq 'Content') + elseif ($PSCmdlet.ParameterSetName -in ('Issue', 'PullRequest')) { - $telemetryProperties['Content'] = $true + $contentType = $PSCmdlet.ParameterSetName + $telemetryProperties['ContentType'] = $contentType $hashBody = @{ - 'content_id' = $ContentId - 'content_type' = $ContentType + 'content_type' = $contentType + } + + if ($PSCmdlet.ParameterSetName -eq 'Issue') + { + $hashBody['content_id'] = $IssueId + } + else + { + $hashBody['content_id'] = $PullRequestId } } @@ -220,13 +269,13 @@ function New-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectCardAdditionalProperties) } -function Set-GitHubProjectCard +filter Set-GitHubProjectCard { <# .DESCRIPTION @@ -257,6 +306,12 @@ function Set-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + + .OUTPUTS + GitHub.ProjectCard + .EXAMPLE Set-GitHubProjectCard -Card 999999 -Note UpdatedNote @@ -273,13 +328,18 @@ function Set-GitHubProjectCard Restores the card with ID 999999. #> [CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName = 'Note')] + SupportsShouldProcess, + DefaultParameterSetName = 'Note')] + [OutputType({$script:GitHubProjectCardTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CardId')] [int64] $Card, + [Alias('Content')] [string] $Note, [Parameter(ParameterSetName = 'Archive')] @@ -329,13 +389,13 @@ function Set-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectCardAdditionalProperties) } -function Remove-GitHubProjectCard +filter Remove-GitHubProjectCard { <# .DESCRIPTION @@ -359,6 +419,9 @@ function Remove-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + .EXAMPLE Remove-GitHubProjectCard -Card 999999 @@ -380,7 +443,10 @@ function Remove-GitHubProjectCard [Alias('Delete-GitHubProjectCard')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CardId')] [int64] $Card, [switch] $Force, @@ -412,14 +478,14 @@ function Remove-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params } } -function Move-GitHubProjectCard +filter Move-GitHubProjectCard { <# .DESCRIPTION @@ -439,7 +505,7 @@ function Move-GitHubProjectCard .PARAMETER After Moves the card to the position after the card ID specified. - .PARAMETER ColumnId + .PARAMETER Column The ID of a column in the same project to move the card to. .PARAMETER AccessToken @@ -452,6 +518,10 @@ function Move-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + .EXAMPLE Move-GitHubProjectCard -Card 999999 -Top @@ -469,16 +539,18 @@ function Move-GitHubProjectCard Within the same column. .EXAMPLE - Move-GitHubProjectCard -Card 999999 -After 888888 -ColumnId 123456 + Move-GitHubProjectCard -Card 999999 -After 888888 -Column 123456 Moves the project card with ID 999999 to the position after the card ID 888888, in the column with ID 123456. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CardId')] [int64] $Card, [switch] $Top, @@ -487,7 +559,9 @@ function Move-GitHubProjectCard [int64] $After, - [int64] $ColumnId, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] + [int64] $Column, [string] $AccessToken, @@ -524,10 +598,10 @@ function Move-GitHubProjectCard 'position' = $Position } - if ($PSBoundParameters.ContainsKey('ColumnId')) + if ($PSBoundParameters.ContainsKey('Column')) { - $telemetryProperties['ColumnId'] = $true - $hashBody.add('column_id', $ColumnId) + $telemetryProperties['Column'] = $true + $hashBody.add('column_id', $Column) } $params = @{ @@ -539,8 +613,89 @@ function Move-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params -} \ No newline at end of file +} + + +filter Add-GitHubProjectCardAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Project Card objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.ProjectCard +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubProjectCardTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'CardId' -Value $item.id -MemberType NoteProperty -Force + + if ($item.project_url -match '^.*/projects/(\d+)$') + { + $projectId = $Matches[1] + Add-Member -InputObject $item -Name 'ProjectId' -Value $projectId -MemberType NoteProperty -Force + } + + if ($item.column_url -match '^.*/columns/(\d+)$') + { + $columnId = $Matches[1] + Add-Member -InputObject $item -Name 'ColumnId' -Value $columnId -MemberType NoteProperty -Force + } + + if ($null -ne $item.content_url) + { + $elements = Split-GitHubUri -Uri $item.content_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + if ($item.content_url -match '^.*/issues/(\d+)$') + { + $issueNumber = $Matches[1] + Add-Member -InputObject $item -Name 'IssueNumber' -Value $issueNumber -MemberType NoteProperty -Force + } + elseif ($item.content_url -match '^.*/pull/(\d+)$') + { + $pullRequestNumber = $Matches[1] + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $pullRequestNumber -MemberType NoteProperty -Force + } + } + + if ($null -ne $item.creator) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.creator + } + } + + Write-Output $item + } +} diff --git a/GitHubProjectColumns.ps1 b/GitHubProjectColumns.ps1 index 4e4d0e82..f65cd309 100644 --- a/GitHubProjectColumns.ps1 +++ b/GitHubProjectColumns.ps1 @@ -1,11 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubProjectColumn +@{ + GitHubProjectColumnTypeName = 'GitHub.ProjectColumn' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubProjectColumn { <# .DESCRIPTION - Get the columns for a given Github Project. + Get the columns for a given GitHub Project. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -25,6 +31,14 @@ function Get-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectColumn + .EXAMPLE Get-GitHubProjectColumn -Project 999999 @@ -37,14 +51,24 @@ function Get-GitHubProjectColumn #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName = 'Project')] + DefaultParameterSetName = 'Column')] + [OutputType({$script:GitHubProjectColumnTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory, ParameterSetName = 'Project')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Project')] + [Alias('ProjectId')] [int64] $Project, - [Parameter(Mandatory, ParameterSetName = 'Column')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Column')] + [Alias('ColumnId')] [int64] $Column, [string] $AccessToken, @@ -81,17 +105,17 @@ function Get-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubProjectColumnAdditionalProperties) } -function New-GitHubProjectColumn +filter New-GitHubProjectColumn { <# .DESCRIPTION - Creates a new column for a Github project. + Creates a new column for a GitHub project. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -111,22 +135,37 @@ function New-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectColumn + .EXAMPLE - New-GitHubProjectColumn -Project 999999 -Name 'Done' + New-GitHubProjectColumn -Project 999999 -ColumnName 'Done' Creates a column called 'Done' for the project with ID 999999. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubProjectColumnTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ProjectId')] [int64] $Project, - [Parameter(Mandatory)] - [string] $Name, + [Parameter( + Mandatory, + ValueFromPipeline)] + [Alias('Name')] + [string] $ColumnName, [string] $AccessToken, @@ -139,10 +178,10 @@ function New-GitHubProjectColumn $telemetryProperties['Project'] = Get-PiiSafeString -PlainText $Project $uriFragment = "/projects/$Project/columns" - $apiDescription = "Creating project column $Name" + $apiDescription = "Creating project column $ColumnName" $hashBody = @{ - 'name' = $Name + 'name' = $ColumnName } $params = @{ @@ -154,13 +193,13 @@ function New-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectColumnAdditionalProperties) } -function Set-GitHubProjectColumn +filter Set-GitHubProjectColumn { <# .DESCRIPTION @@ -184,21 +223,32 @@ function Set-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectColumn + .EXAMPLE - Set-GitHubProjectColumn -Column 999999 -Name NewColumnName + Set-GitHubProjectColumn -Column 999999 -ColumnName NewColumnName Set the project column name to 'NewColumnName' with column with ID 999999. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubProjectColumnTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, [Parameter(Mandatory)] - [string] $Name, + [Alias('Name')] + [string] $ColumnName, [string] $AccessToken, @@ -213,7 +263,7 @@ function Set-GitHubProjectColumn $apiDescription = "Updating column $Column" $hashBody = @{ - 'name' = $Name + 'name' = $ColumnName } $params = @{ @@ -225,13 +275,13 @@ function Set-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectColumnAdditionalProperties) } -function Remove-GitHubProjectColumn +filter Remove-GitHubProjectColumn { <# .DESCRIPTION @@ -255,6 +305,10 @@ function Remove-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + .EXAMPLE Remove-GitHubProjectColumn -Column 999999 @@ -276,7 +330,10 @@ function Remove-GitHubProjectColumn [Alias('Delete-GitHubProjectColumn')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, [switch] $Force, @@ -308,14 +365,14 @@ function Remove-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params } } -function Move-GitHubProjectColumn +filter Move-GitHubProjectColumn { <# .DESCRIPTION @@ -346,6 +403,10 @@ function Move-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + .EXAMPLE Move-GitHubProjectColumn -Column 999999 -First @@ -361,12 +422,14 @@ function Move-GitHubProjectColumn Moves the project column with ID 999999 to the position after column with ID 888888. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, [switch] $First, @@ -419,8 +482,60 @@ function Move-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params -} \ No newline at end of file +} + +filter Add-GitHubProjectColumnAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Project Column objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.ProjectColumn +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubProjectColumnTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'ColumnId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'ColumnName' -Value $item.name -MemberType NoteProperty -Force + + if ($item.project_url -match '^.*/projects/(\d+)$') + { + $projectId = $Matches[1] + Add-Member -InputObject $item -Name 'ProjectId' -Value $projectId -MemberType NoteProperty -Force + } + } + + Write-Output $item + } +} diff --git a/GitHubProjects.ps1 b/GitHubProjects.ps1 index 6d026730..3476f263 100644 --- a/GitHubProjects.ps1 +++ b/GitHubProjects.ps1 @@ -1,11 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubProject +@{ + GitHubProjectTypeName = 'GitHub.Project' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubProject { <# .DESCRIPTION - Get the projects for a given Github user, repository or organization. + Get the projects for a given GitHub user, repository or organization. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -44,10 +50,28 @@ function Get-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Project + .EXAMPLE - Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the projects for the Microsoft\PowerShellForGitHub repository. + Get the projects for the microsoft\PowerShellForGitHub repository. .EXAMPLE Get-GitHubProject -OrganizationName Microsoft @@ -57,7 +81,7 @@ function Get-GitHubProject .EXAMPLE Get-GitHubProject -Uri https://github.com/Microsoft/PowerShellForGitHub - Get the projects for the Microsoft\PowerShellForGitHub repository using the Uri. + Get the projects for the microsoft\PowerShellForGitHub repository using the Uri. .EXAMPLE Get-GitHubProject -UserName GitHubUser @@ -65,9 +89,9 @@ function Get-GitHubProject Get the projects for the user GitHubUser. .EXAMPLE - Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State Closed + Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub -State Closed - Get closed projects from the Microsoft\PowerShellForGitHub repo. + Get closed projects from the microsoft\PowerShellForGitHub repo. .EXAMPLE Get-GitHubProject -Project 4378613 @@ -77,24 +101,50 @@ function Get-GitHubProject [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Elements')] + [OutputType({$script:GitHubPullRequestTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName = 'Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ProjectObject')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName = 'Organization')] + [Parameter( + Mandatory, + ParameterSetName = 'Organization')] [string] $OrganizationName, - [Parameter(Mandatory, ParameterSetName = 'User')] + [Parameter( + Mandatory, + ParameterSetName = 'User')] [string] $UserName, - [Parameter(Mandatory, ParameterSetName = 'Project')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Project')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ProjectObject')] + [Alias('ProjectId')] [int64] $Project, [ValidateSet('Open', 'Closed', 'All')] @@ -111,7 +161,7 @@ function Get-GitHubProject $uriFragment = [String]::Empty $description = [String]::Empty - if ($PSCmdlet.ParameterSetName -eq 'Project') + if ($PSCmdlet.ParameterSetName -in @('Project', 'ProjectObject')) { $telemetryProperties['Project'] = Get-PiiSafeString -PlainText $Project @@ -162,18 +212,18 @@ function Get-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubProjectAdditionalProperties) } -function New-GitHubProject +filter New-GitHubProject { <# .DESCRIPTION - Creates a new Github project for the given repository + Creates a new GitHub project for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -212,48 +262,83 @@ function New-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Project + .EXAMPLE - New-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestProject + New-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub -ProjectName TestProject - Creates a project called 'TestProject' for the Microsoft\PowerShellForGitHub repository. + Creates a project called 'TestProject' for the microsoft\PowerShellForGitHub repository. .EXAMPLE - New-GitHubProject -OrganizationName Microsoft -Name TestProject -Description 'This is just a test project' + New-GitHubProject -OrganizationName Microsoft -ProjectName TestProject -Description 'This is just a test project' Create a project for the Microsoft organization called 'TestProject' with a description. .EXAMPLE - New-GitHubProject -Uri https://github.com/Microsoft/PowerShellForGitHub -Name TestProject + New-GitHubProject -Uri https://github.com/Microsoft/PowerShellForGitHub -ProjectName TestProject - Create a project for the Microsoft\PowerShellForGitHub repository using the Uri called 'TestProject'. + Create a project for the microsoft\PowerShellForGitHub repository + using the Uri called 'TestProject'. .EXAMPLE - New-GitHubProject -UserProject -Name 'TestProject' + New-GitHubProject -UserProject -ProjectName 'TestProject' Creates a project for the signed in user called 'TestProject'. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Elements')] + [OutputType({$script:GitHubPullRequestTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName = 'Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName = 'Organization')] + [Parameter( + Mandatory, + ParameterSetName = 'Organization')] [string] $OrganizationName, - [Parameter(Mandatory, ParameterSetName = 'User')] + [Parameter( + Mandatory, + ParameterSetName = 'User')] [switch] $UserProject, - [Parameter(Mandatory)] - [string] $Name, + [Parameter( + Mandatory, + ValueFromPipeline)] + [Alias('Name')] + [string] $ProjectName, [string] $Description, @@ -265,7 +350,7 @@ function New-GitHubProject Write-InvocationLog $telemetryProperties = @{} - $telemetryProperties['Name'] = Get-PiiSafeString -PlainText $Name + $telemetryProperties['ProjectName'] = Get-PiiSafeString -PlainText $ProjectName $uriFragment = [String]::Empty $apiDescription = [String]::Empty @@ -297,7 +382,7 @@ function New-GitHubProject } $hashBody = @{ - 'name' = $Name + 'name' = $ProjectName } if ($PSBoundParameters.ContainsKey('Description')) @@ -314,13 +399,13 @@ function New-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectAdditionalProperties) } -function Set-GitHubProject +filter Set-GitHubProject { <# .DESCRIPTION @@ -357,23 +442,45 @@ function Set-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Project + .EXAMPLE Set-GitHubProject -Project 999999 -State Closed Set the project with ID '999999' to closed. .EXAMPLE - $project = Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' + $project = Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' Set-GitHubProject -Project $project.id -State Closed - Get the ID for the 'TestProject' project for the Microsoft\PowerShellForGitHub + Get the ID for the 'TestProject' project for the microsoft\PowerShellForGitHub repository and set state to closed. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubPullRequestTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] + [Alias('ProjectId')] [int64] $Project, [string] $Description, @@ -433,17 +540,17 @@ function Set-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectAdditionalProperties) } -function Remove-GitHubProject +filter Remove-GitHubProject { <# .DESCRIPTION - Removes the projects for a given Github repository. + Removes the projects for a given GitHub repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -463,6 +570,21 @@ function Remove-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE Remove-GitHubProject -Project 4387531 @@ -479,10 +601,10 @@ function Remove-GitHubProject Remove project with ID '4387531' without prompting for confirmation. .EXAMPLE - $project = Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' + $project = Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' Remove-GitHubProject -Project $project.id - Get the ID for the 'TestProject' project for the Microsoft\PowerShellForGitHub + Get the ID for the 'TestProject' project for the microsoft\PowerShellForGitHub repository and then remove the project. #> [CmdletBinding( @@ -491,7 +613,10 @@ function Remove-GitHubProject [Alias('Delete-GitHubProject')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ProjectId')] [int64] $Project, [switch] $Force, @@ -523,10 +648,70 @@ function Remove-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params } } + +filter Add-GitHubProjectAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Project objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Project +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubProjectTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + + # A "user" project has no associated repository, and adding this in that scenario + # would cause API-level errors with piping further on, + if ($elements.OwnerName -ne 'users') + { + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'ProjectId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.creator) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.creator + } + } + + Write-Output $item + } +} diff --git a/GitHubPullRequests.ps1 b/GitHubPullRequests.ps1 index d2d99337..c08c887a 100644 --- a/GitHubPullRequests.ps1 +++ b/GitHubPullRequests.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubPullRequest +@{ + GitHubPullRequestTypeName = 'GitHub.PullRequest' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubPullRequest { <# .SYNOPSIS @@ -58,14 +64,29 @@ function Get-GitHubPullRequest the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of Pull Requests that match the specified criteria. + GitHub.PulLRequest .EXAMPLE $pullRequests = Get-GitHubPullRequest -Uri 'https://github.com/PowerShell/PowerShellForGitHub' .EXAMPLE - $pullRequests = Get-GitHubPullRequest -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State Closed + $pullRequests = Get-GitHubPullRequest -OwnerName microsoft -RepositoryName PowerShellForGitHub -State Closed #> [CmdletBinding( SupportsShouldProcess, @@ -80,10 +101,16 @@ function Get-GitHubPullRequest [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [string] $PullRequest, + [Parameter( + ValueFromPipeline, + ValueFromPipelineByPropertyName)] + [Alias('PullRequestNumber')] + [int64] $PullRequest, [ValidateSet('Open', 'Closed', 'All')] [string] $State = 'Open', @@ -117,7 +144,7 @@ function Get-GitHubPullRequest $uriFragment = "/repos/$OwnerName/$RepositoryName/pulls" $description = "Getting pull requests for $RepositoryName" - if (-not [String]::IsNullOrEmpty($PullRequest)) + if ($PSBoundParameters.ContainsKey('PullRequest')) { $uriFragment = $uriFragment + "/$PullRequest" $description = "Getting pull request $PullRequest for $RepositoryName" @@ -154,24 +181,25 @@ function Get-GitHubPullRequest $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubPullRequestAdditionalProperties) } -function New-GitHubPullRequest +filter New-GitHubPullRequest { <# .SYNOPSIS Create a new pull request in the specified repository. .DESCRIPTION - Opens a new pull request from the given branch into the given branch in the specified repository. + Opens a new pull request from the given branch into the given branch + in the specified repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -231,8 +259,23 @@ function New-GitHubPullRequest the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject] An object describing the created pull request. + GitHub.PullRequest .EXAMPLE $prParams = @{ @@ -254,7 +297,9 @@ function New-GitHubPullRequest #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='Elements_Title')] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements_Title')] param( [Parameter(ParameterSetName='Elements_Title')] [Parameter(ParameterSetName='Elements_Issue')] @@ -266,10 +311,13 @@ function New-GitHubPullRequest [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri_Title')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri_Issue')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( @@ -287,10 +335,13 @@ function New-GitHubPullRequest [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Elements_Issue')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri_Issue')] + [Alias('IssueNumber')] [int] $Issue, [Parameter(Mandatory)] @@ -387,5 +438,73 @@ function New-GitHubPullRequest $restParams['AcceptHeader'] = $acceptHeader } - return Invoke-GHRestMethod @restParams + return (Invoke-GHRestMethod @restParams | Add-GitHubPullRequestAdditionalProperties) +} + +filter Add-GitHubPullRequestAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Repository objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubPullRequestTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'PullRequestId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $item.number -MemberType NoteProperty -Force + + @('assignee', 'assignees', 'requested_reviewers', 'merged_by', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.labels) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.labels + } + + if ($null -ne $item.milestone) + { + $null = Add-GitHubMilestoneAdditionalProperties -InputObject $item.milestone + } + + if ($null -ne $item.requested_teams) + { + $null = Add-GitHubTeamAdditionalProperties -InputObject $item.requested_teams + } + + # TODO: What type are item.head and item.base? + } + + Write-Output $item + } } diff --git a/GitHubReleases.ps1 b/GitHubReleases.ps1 index e7eb49a2..6083f669 100644 --- a/GitHubReleases.ps1 +++ b/GitHubReleases.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubRelease +@{ + GitHubReleaseTypeName = 'GitHub.Release' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubRelease { <# .SYNOPSIS @@ -25,8 +31,8 @@ function Get-GitHubRelease The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER ReleaseId - Specific releaseId of a release. + .PARAMETER Release + The ID of a specific release. This is an optional parameter which can limit the results to a single release. .PARAMETER Latest @@ -47,13 +53,31 @@ function Get-GitHubRelease the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Release + .EXAMPLE Get-GitHubRelease Gets all releases for the default configured owner/repository. .EXAMPLE - Get-GitHubRelease -ReleaseId 12345 + Get-GitHubRelease -Release 12345 Get a specific release for the default configured owner/repository @@ -84,49 +108,50 @@ function Get-GitHubRelease [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubReleaseTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter( - ParameterSetName='Elements')] - [Parameter( - ParameterSetName="Elements-ReleaseId")] - [Parameter( - ParameterSetName="Elements-Latest")] - [Parameter( - ParameterSetName="Elements-Tag")] + [Parameter(ParameterSetName='Elements')] + [Parameter(ParameterSetName="Elements-ReleaseId")] + [Parameter(ParameterSetName="Elements-Latest")] + [Parameter(ParameterSetName="Elements-Tag")] [string] $OwnerName, - [Parameter( - ParameterSetName='Elements')] - [Parameter( - ParameterSetName="Elements-ReleaseId")] - [Parameter( - ParameterSetName="Elements-Latest")] - [Parameter( - ParameterSetName="Elements-Tag")] + [Parameter(ParameterSetName='Elements')] + [Parameter(ParameterSetName="Elements-ReleaseId")] + [Parameter(ParameterSetName="Elements-Latest")] + [Parameter(ParameterSetName="Elements-Tag")] [string] $RepositoryName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-ReleaseId")] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-Latest")] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-Tag")] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Elements-ReleaseId")] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-ReleaseId")] - [string] $ReleaseId, + [Alias('ReleaseId')] + [int64] $Release, [Parameter( Mandatory, @@ -163,12 +188,12 @@ function Get-GitHubRelease $uriFragment = "repos/$OwnerName/$RepositoryName/releases" $description = "Getting releases for $OwnerName/$RepositoryName" - if(-not [String]::IsNullOrEmpty($ReleaseId)) + if ($PSBoundParameters.ContainsKey('Release')) { - $telemetryProperties['ProvidedReleaseId'] = $true + $telemetryProperties['ProvidedRelease'] = $true - $uriFragment += "/$ReleaseId" - $description = "Getting release information for $ReleaseId from $OwnerName/$RepositoryName" + $uriFragment += "/$Release" + $description = "Getting release information for $Release from $OwnerName/$RepositoryName" } if($Latest) @@ -193,8 +218,66 @@ function Get-GitHubRelease 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubReleaseAdditionalProperties) +} + + +filter Add-GitHubReleaseAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Release objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Release +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubReleaseTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + if (-not [String]::IsNullOrEmpty($item.html_url)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'ReleaseId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.author) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.author + } + } + + Write-Output $item + } } diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index f962d1d4..c59e33e5 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -1,7 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function New-GitHubRepository +@{ + GitHubRepositoryTypeName = 'GitHub.Repository' + GitHubRepositoryTopicTypeName = 'GitHub.RepositoryTopic' + GitHubRepositoryContributorStatisticsTypeName = 'GitHub.RepositoryContributorStatistics' + GitHubRepositoryLanguageTypeName = 'GitHub.RepositoryLanguage' + GitHubRepositoryTagTypeName = 'GitHub.RepositoryTag' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter New-GitHubRepository { <# .SYNOPSIS @@ -85,19 +95,45 @@ function New-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE New-GitHubRepository -RepositoryName MyNewRepo -AutoInit + .EXAMPLE + 'MyNewRepo' | New-GitHubRepository -AutoInit + .EXAMPLE New-GitHubRepository -RepositoryName MyNewRepo -Organization MyOrg -DisallowRebaseMerge #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [Alias('Name')] [string] $RepositoryName, + [Parameter(ValueFromPipelineByPropertyName)] [string] $OrganizationName, [string] $Description, @@ -185,13 +221,13 @@ function New-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) } -function Remove-GitHubRepository +filter Remove-GitHubRepository { <# .SYNOPSIS @@ -228,6 +264,21 @@ function Remove-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE Remove-GitHubRepository -OwnerName You -RepositoryName YourRepoToDelete @@ -243,6 +294,12 @@ function Remove-GitHubRepository Remove-GitHubRepository -Uri https://github.com/You/YourRepoToDelete -Force Remove repository with the given URI, without prompting for confirmation. + + .EXAMPLE + $repo = Get-GitHubRepository -Uri https://github.com/You/YourRepoToDelete + $repo | Remove-GitHubRepository -Force + + You can also pipe in a repo that was returned from a previous command. #> [CmdletBinding( SupportsShouldProcess, @@ -258,7 +315,9 @@ function Remove-GitHubRepository [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [switch] $Force, @@ -293,14 +352,14 @@ function Remove-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } return Invoke-GHRestMethod @params } } -function Get-GitHubRepository +filter Get-GitHubRepository { <# .SYNOPSIS @@ -370,6 +429,24 @@ function Get-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE Get-GitHubRepository @@ -385,11 +462,21 @@ function Get-GitHubRepository Gets all of the repositories for the user octocat + .EXAMPLE + Get-GitHubUser -UserName octocat | Get-GitHubRepository + + Gets all of the repositories for the user octocat + .EXAMPLE Get-GitHubRepository -Uri https://github.com/microsoft/PowerShellForGitHub Gets information about the microsoft/PowerShellForGitHub repository. + .EXAMPLE + $repo | Get-GitHubRepository + + You can pipe in a previous repository to get its refreshed information. + .EXAMPLE Get-GitHubRepository -OrganizationName PowerShell @@ -398,10 +485,14 @@ function Get-GitHubRepository [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='AuthenticatedUser')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(ParameterSetName='ElementsOrUser')] + [Parameter( + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsOrUser')] + [Alias('UserName')] [string] $OwnerName, [Parameter(ParameterSetName='ElementsOrUser')] @@ -409,14 +500,18 @@ function Get-GitHubRepository [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(ParameterSetName='Organization')] + [Parameter( + ValueFromPipelineByPropertyName, + ParameterSetName='Organization')] [string] $OrganizationName, - [ValidateSet('All', 'Public', 'Private')] [Parameter(ParameterSetName='AuthenticatedUser')] + [ValidateSet('All', 'Public', 'Private')] [string] $Visibility, [Parameter(ParameterSetName='AuthenticatedUser')] @@ -521,7 +616,6 @@ function Get-GitHubRepository } 'Organization' { - $telemetryProperties['OrganizationName'] = Get-PiiSafeString -PlainText $OrganizationName $uriFragment = "orgs/$OrganizationName/repos" @@ -598,13 +692,13 @@ function Get-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubRepositoryAdditionalProperties) } -function Rename-GitHubRepository +filter Rename-GitHubRepository { <# .SYNOPSIS @@ -625,7 +719,8 @@ function Rename-GitHubRepository .PARAMETER Uri Uri for the repository to rename. You can supply this directly, or more easily by - using Get-GitHubRepository to get the repository as you please, and then piping the result to this cmdlet + using Get-GitHubRepository to get the repository as you please, + and then piping the result to this cmdlet. .PARAMETER NewName The new name to set for the given GitHub repository @@ -643,56 +738,87 @@ function Rename-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE Get-GitHubRepository -Owner octocat -RepositoryName hello-world | Rename-GitHubRepository -NewName hello-again-world - Get the given 'hello-world' repo from the user 'octocat' and rename it to be https://github.com/octocat/hello-again-world. + Get the given 'hello-world' repo from the user 'octocat' and then + rename it to be https://github.com/octocat/hello-again-world. .EXAMPLE Get-GitHubRepository -Uri https://github.com/octocat/hello-world | Rename-GitHubRepository -NewName hello-again-world -Confirm:$false - Get the repository at https://github.com/octocat/hello-world and then rename it https://github.com/octocat/hello-again-world. Will not prompt for confirmation, as -Confirm:$false was specified. + Get the repository at https://github.com/octocat/hello-world and then + rename it https://github.com/octocat/hello-again-world. + Will not prompt for confirmation, as -Confirm:$false was specified. .EXAMPLE Rename-GitHubRepository -Uri https://github.com/octocat/hello-world -NewName hello-again-world - Rename the repository at https://github.com/octocat/hello-world to https://github.com/octocat/hello-again-world. + Rename the repository at https://github.com/octocat/hello-world to + https://github.com/octocat/hello-again-world. .EXAMPLE New-GitHubRepositoryFork -Uri https://github.com/octocat/hello-world | Foreach-Object {$_ | Rename-GitHubRepository -NewName "$($_.name)_fork"} - Fork the `hello-world` repository from the user 'octocat', and then rename the newly forked repository by appending '_fork'. + Fork the `hello-world` repository from the user 'octocat', and then + rename the newly forked repository by appending '_fork'. .EXAMPLE Rename-GitHubRepository -Uri https://github.com/octocat/hello-world -NewName hello-again-world -Confirm:$false - Rename the repository at https://github.com/octocat/hello-world to https://github.com/octocat/hello-again-world without prompting for confirmation. + Rename the repository at https://github.com/octocat/hello-world to + https://github.com/octocat/hello-again-world without prompting for confirmation. .EXAMPLE Rename-GitHubRepository -Uri https://github.com/octocat/hello-world -NewName hello-again-world -Force - Rename the repository at https://github.com/octocat/hello-world to https://github.com/octocat/hello-again-world without prompting for confirmation. + Rename the repository at https://github.com/octocat/hello-world to + https://github.com/octocat/hello-again-world without prompting for confirmation. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Uri', ConfirmImpact="High")] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory=$true, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory=$true, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='Uri')] - [Alias("html_url")] + [Alias("RepositoryUrl")] [string] $Uri, - [parameter(Mandatory)][String]$NewName, + [parameter(Mandatory)] + [String] $NewName, [switch] $Force, @@ -701,44 +827,12 @@ function Rename-GitHubRepository [switch] $NoStatus ) - process - { - $repositoryInfoForDisplayMessage = if ($PSCmdlet.ParameterSetName -eq "Uri") { $Uri } else { $OwnerName, $RepositoryName -join "/" } - - if ($Force -and (-not $Confirm)) - { - $ConfirmPreference = 'None' - } - - if ($PSCmdlet.ShouldProcess($repositoryInfoForDisplayMessage, "Rename repository to '$NewName'")) - { - Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - } - - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName" - 'Method' = 'Patch' - Body = ConvertTo-Json -InputObject @{name = $NewName} - 'Description' = "Renaming repository at '$repositoryInfoForDisplayMessage' to '$NewName'" - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) - } - - return Invoke-GHRestMethod @params - } - } + # This method was created by mistake and is now retained to avoid a breaking change. + # Update-GitHubRepository is able to handle this scenario just fine. + return Update-GitHubRepository @PSBoundParameters } -function Update-GitHubRepository +filter Update-GitHubRepository { <# .SYNOPSIS @@ -762,6 +856,9 @@ function Update-GitHubRepository The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. + .PARAMETER NewName + Rename the repository to this new name. + .PARAMETER Description A short description of the repository. @@ -808,6 +905,10 @@ function Update-GitHubRepository Specify this to archive this repository. NOTE: You cannot unarchive repositories through the API / this module. + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution + when renaming the repository. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. @@ -818,8 +919,26 @@ function Update-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - Update-GitHubRepository -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Description 'The best way to automate your GitHub interactions' + Update-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub -Description 'The best way to automate your GitHub interactions' Changes the description of the specified repository. @@ -827,10 +946,19 @@ function Update-GitHubRepository Update-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -Private:$false Changes the visibility of the specified repository to be public. + + .EXAMPLE + Get-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub | + Update-GitHubRepository -NewName 'PoShForGitHub' -Force + + Renames the repository without any user confirmation prompting. This is identical to using + Rename-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -NewName 'PoShForGitHub' -Confirm:$false #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact='High')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -841,9 +969,14 @@ function Update-GitHubRepository [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [ValidateNotNullOrEmpty()] + [string] $NewName, + [string] $Description, [string] $Homepage, @@ -870,6 +1003,8 @@ function Update-GitHubRepository [switch] $Archived, + [switch] $Force, + [string] $AccessToken, [switch] $NoStatus @@ -886,8 +1021,22 @@ function Update-GitHubRepository 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } - $hashBody = @{ - 'name' = $RepositoryName + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + $hashBody = @{} + + if ($PSBoundParameters.ContainsKey('NewName')) + { + $existingName = if ($PSCmdlet.ParameterSetName -eq 'Uri') { $Uri } else { $OwnerName, $RepositoryName -join '/' } + if (-not $PSCmdlet.ShouldProcess($existingName, "Rename repository to '$NewName'")) + { + return + } + + $hashBody['name'] = $NewName } if ($PSBoundParameters.ContainsKey('Description')) { $hashBody['description'] = $Description } @@ -913,13 +1062,13 @@ function Update-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) } -function Get-GitHubRepositoryTopic +filter Get-GitHubRepositoryTopic { <# .SYNOPSIS @@ -953,8 +1102,26 @@ function Get-GitHubRepositoryTopic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.RepositoryTopic + .EXAMPLE - Get-GitHubRepositoryTopic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryTopic -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub @@ -962,6 +1129,7 @@ function Get-GitHubRepositoryTopic [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTopicTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -972,7 +1140,9 @@ function Get-GitHubRepositoryTopic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -999,10 +1169,11 @@ function Get-GitHubRepositoryTopic 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryTopicTypeName -OwnerName $OwnerName -RepositoryName $RepositoryName) } function Set-GitHubRepositoryTopic @@ -1029,7 +1200,7 @@ function Set-GitHubRepositoryTopic The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Topic Array of topics to add to the repository. .PARAMETER Clear @@ -1045,15 +1216,44 @@ function Set-GitHubRepositoryTopic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.RepositoryTopic + .EXAMPLE - Set-GitHubRepositoryTopic -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Clear + Set-GitHubRepositoryTopic -OwnerName microsoft -RepositoryName PowerShellForGitHub -Clear .EXAMPLE - Set-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub -Name ('octocat', 'powershell', 'github') + Set-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub -Topic ('octocat', 'powershell', 'github') + + .EXAMPLE + ('octocat', 'powershell', 'github') | Set-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub + + .NOTES + This is implemented as a function rather than a filter because the ValueFromPipeline + parameter (Topic) is itself an array which we want to ensure is processed only a single time. + This API endpoint doesn't add topics to a repository, it replaces the existing topics with + the new set provided, so we need to make sure that we have all the requested topics available + to us at the time that the API endpoint is called. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='ElementsName')] + [OutputType({$script:GitHubRepositoryTopicTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='ElementsName')] @@ -1066,19 +1266,25 @@ function Set-GitHubRepositoryTopic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='UriName')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='UriClear')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipeline, ParameterSetName='ElementsName')] [Parameter( Mandatory, + ValueFromPipeline, ParameterSetName='UriName')] - [string[]] $Name, + [Alias('Name')] + [string[]] $Topic, [Parameter( Mandatory, @@ -1093,48 +1299,64 @@ function Set-GitHubRepositoryTopic [switch] $NoStatus ) - Write-InvocationLog -Invocation $MyInvocation - - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'Clear' = $PSBoundParameters.ContainsKey('Clear') + begin + { + $topics = @() } - if ($Clear) + process { - $description = "Clearing topics in $RepositoryName" - $Name = @() + foreach ($value in $Topic) + { + $topics += $value + } } - else + + end { - $description = "Replacing topics in $RepositoryName" - } + Write-InvocationLog -Invocation $MyInvocation - $hashBody = @{ - 'names' = $Name - } + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/topics" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Put' - 'Description' = $description - 'AcceptHeader' = $script:mercyAcceptHeader - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) - } + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'Clear' = $PSBoundParameters.ContainsKey('Clear') + } - return Invoke-GHRestMethod @params + if ($Clear) + { + $description = "Clearing topics in $RepositoryName" + } + else + { + $description = "Replacing topics in $RepositoryName" + } + + $hashBody = @{ + 'names' = $topics + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/topics" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Put' + 'Description' = $description + 'AcceptHeader' = $script:mercyAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryTopicTypeName -OwnerName $OwnerName -RepositoryName $RepositoryName) + } } -function Get-GitHubRepositoryContributor +filter Get-GitHubRepositoryContributor { <# .SYNOPSIS @@ -1181,11 +1403,27 @@ function Get-GitHubRepositoryContributor the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of contributors for the repository. + GitHub.User + GitHub.RepositoryContributorStatistics .EXAMPLE - Get-GitHubRepositoryContributor -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryContributor -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryContributor -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -IncludeStatistics @@ -1193,6 +1431,8 @@ function Get-GitHubRepositoryContributor [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubUserTypeName})] + [OutputType({$script:GitHubRepositoryContributorStatisticsTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -1203,7 +1443,9 @@ function Get-GitHubRepositoryContributor [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [switch] $IncludeAnonymousContributors, @@ -1240,13 +1482,34 @@ function Get-GitHubRepositoryContributor 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $results = Invoke-GHRestMethodMultipleResult @params + + if ($IncludeStatistics) + { + foreach ($item in $results) + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubRepositoryContributorStatisticsTypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $repositoryUrl = (Join-GitHubUri -OwnerName $OwnerName -RepositoryName $RepositoryName) + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + $null = Add-GitHubUserAdditionalProperties -InputObject $item.author + } + } + } + else + { + $results = $results | Add-GitHubUserAdditionalProperties } - return Invoke-GHRestMethodMultipleResult @params + return $results } -function Get-GitHubRepositoryCollaborator +filter Get-GitHubRepositoryCollaborator { <# .SYNOPSIS @@ -1274,17 +1537,39 @@ function Get-GitHubRepositoryCollaborator If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + .PARAMETER Affiliation + Filter collaborators returned by their affiliation. Can be one of: + All: All collaborators the authenticated user can see. + Direct: All collaborators with permissions to an organization-owned repository, + regardless of organization membership status. + Outside: All outside collaborators of an organization-owned repository. + .PARAMETER NoStatus If this switch is specified, long-running commands will run on the main thread with no commandline status update. When not specified, those commands run in the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of collaborators for the repository. + GitHub.User .EXAMPLE - Get-GitHubRepositoryCollaborator -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryCollaborator -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryCollaborator -Uri 'https://github.com/PowerShell/PowerShellForGitHub' @@ -1292,7 +1577,8 @@ function Get-GitHubRepositoryCollaborator [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [OutputType({$script:GitHubUserTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1302,9 +1588,14 @@ function Get-GitHubRepositoryCollaborator [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [ValidateSet('All', 'Direct', 'Outside')] + [string] $Affiliation = 'All', + [string] $AccessToken, [switch] $NoStatus @@ -1321,19 +1612,23 @@ function Get-GitHubRepositoryCollaborator 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + $getParams = @( + "affiliation=$($Affiliation.ToLower())" + ) + $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/collaborators" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/collaborators?" + ($getParams -join '&') 'Description' = "Getting collaborators for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) } -function Get-GitHubRepositoryLanguage +filter Get-GitHubRepositoryLanguage { <# .SYNOPSIS @@ -1367,12 +1662,27 @@ function Get-GitHubRepositoryLanguage the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of languages for the specified repository. The value shown - for each language is the number of bytes of code written in that language. + GitHub.RepositoryLanguage - The value shown for each language is the number + of bytes of code written in that language. .EXAMPLE - Get-GitHubRepositoryLanguage -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryLanguage -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryLanguage -Uri https://github.com/PowerShell/PowerShellForGitHub @@ -1380,6 +1690,7 @@ function Get-GitHubRepositoryLanguage [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryLanguageTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -1390,7 +1701,9 @@ function Get-GitHubRepositoryLanguage [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -1415,13 +1728,14 @@ function Get-GitHubRepositoryLanguage 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryLanguageTypeName) } -function Get-GitHubRepositoryTag +filter Get-GitHubRepositoryTag { <# .SYNOPSIS @@ -1455,8 +1769,26 @@ function Get-GitHubRepositoryTag the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.RepositoryTag + .EXAMPLE - Get-GitHubRepositoryTag -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryTag -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryTag -Uri https://github.com/PowerShell/PowerShellForGitHub @@ -1464,6 +1796,7 @@ function Get-GitHubRepositoryTag [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTagTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -1474,7 +1807,9 @@ function Get-GitHubRepositoryTag [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -1499,13 +1834,14 @@ function Get-GitHubRepositoryTag 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryTagTypeName -OwnerName $OwnerName -RepositoryName $RepositoryName) } -function Move-GitHubRepositoryOwnership +filter Move-GitHubRepositoryOwnership { <# .SYNOPSIS @@ -1546,12 +1882,31 @@ function Move-GitHubRepositoryOwnership the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - Move-GitHubRepositoryOwnership -OwnerName Microsoft -RepositoryName PowerShellForGitHub -NewOwnerName OctoCat + Move-GitHubRepositoryOwnership -OwnerName microsoft -RepositoryName PowerShellForGitHub -NewOwnerName OctoCat #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTypeName})] [Alias('Transfer-GitHubRepositoryOwnership')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( @@ -1563,7 +1918,9 @@ function Move-GitHubRepositoryOwnership [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter(Mandatory)] @@ -1602,8 +1959,98 @@ function Move-GitHubRepositoryOwnership 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) +} + +filter Add-GitHubRepositoryAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Repository objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER OwnerName + Owner of the repository. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .PARAMETER RepositoryName + Name of the repository. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Repository +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubRepositoryTypeName, + + [string] $OwnerName, + + [string] $RepositoryName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $repositoryUrl = [String]::Empty + if ([String]::IsNullOrEmpty($item.html_url)) + { + if ($PSBoundParameters.ContainsKey('OwnerName') -and + $PSBoundParameters.ContainsKey('RepositoryName')) + { + $repositoryUrl = (Join-GitHubUri -OwnerName $OwnerName -RepositoryName $RepositoryName) + } + } + else + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + } + + if (-not [String]::IsNullOrEmpty($repositoryUrl)) + { + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + if ($item.id -gt 0) + { + Add-Member -InputObject $item -Name 'RepositoryId' -Value $item.id -MemberType NoteProperty -Force + } + + if ($null -ne $item.owner) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.owner + } + + if ($null -ne $item.organization) + { + $null = Add-GitHubOrganizationAdditionalProperties -InputObject $item.organization + } + } + + Write-Output $item + } } diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 index f50b2af9..3322c726 100644 --- a/GitHubRepositoryForks.ps1 +++ b/GitHubRepositoryForks.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubRepositoryFork +filter Get-GitHubRepositoryFork { <# .SYNOPSIS @@ -38,14 +38,33 @@ function Get-GitHubRepositoryFork the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets all of the forks for the Microsoft\PowerShellForGitHub repository. + Gets all of the forks for the microsoft\PowerShellForGitHub repository. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -57,7 +76,9 @@ function Get-GitHubRepositoryFork [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [ValidateSet('Newest', 'Oldest', 'Stargazers')] @@ -93,10 +114,10 @@ function Get-GitHubRepositoryFork 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubRepositoryAdditionalProperties) } -function New-GitHubRepositoryFork +filter New-GitHubRepositoryFork { <# .SYNOPSIS @@ -134,19 +155,38 @@ function New-GitHubRepositoryFork the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - New-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub + New-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub Creates a fork of this repository under the current authenticated user's account. .EXAMPLE - New-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -OrganizationName OctoLabs + New-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub -OrganizationName OctoLabs Creates a fork of this repository under the OctoLabs organization. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -157,7 +197,9 @@ function New-GitHubRepositoryFork [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $OrganizationName, @@ -196,7 +238,7 @@ function New-GitHubRepositoryFork 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - $result = Invoke-GHRestMethod @params + $result = (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) Write-Log -Message 'Forking a repository happens asynchronously. You may have to wait a short period of time (up to 5 minutes) before you can access the git objects.' -Level Warning return $result diff --git a/GitHubRepositoryTraffic.ps1 b/GitHubRepositoryTraffic.ps1 index c27ab1be..bcdcda13 100644 --- a/GitHubRepositoryTraffic.ps1 +++ b/GitHubRepositoryTraffic.ps1 @@ -1,7 +1,16 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubReferrerTraffic +@{ + GitHubReferrerTrafficTypeName = 'GitHub.ReferrerTraffic' + GitHubPathTrafficTypeName = 'GitHub.PathTraffic' + GitHubViewTrafficTypeName = 'GitHub.ViewTraffic' + GitHubCloneTrafficTypeName = 'GitHub.CloneTraffic' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubReferrerTraffic { <# .SYNOPSIS @@ -35,14 +44,33 @@ function Get-GitHubReferrerTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.ReferrerTraffic + .EXAMPLE - Get-GitHubReferrerTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubReferrerTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the top 10 referrers over the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the top 10 referrers over the last 14 days from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubReferrerTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -54,7 +82,9 @@ function Get-GitHubReferrerTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -83,14 +113,21 @@ function Get-GitHubReferrerTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubReferrerTrafficTypeName) + } + + return $result } -function Get-GitHubPathTraffic +filter Get-GitHubPathTraffic { <# .SYNOPSIS - Get the top 10 popular contents over the last 14 days for a given Github repository. + Get the top 10 popular contents over the last 14 days for a given GitHub repository. .DESCRIPTION Get the top 10 popular contents over the last 14 days for a given GitHub repository. @@ -120,14 +157,34 @@ function Get-GitHubPathTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.PathTraffic + .EXAMPLE - Get-GitHubPathTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubPathTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the top 10 popular contents over the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the top 10 popular contents over the last 14 days + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubPathTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -139,7 +196,9 @@ function Get-GitHubPathTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -168,18 +227,27 @@ function Get-GitHubPathTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubPathTrafficTypeName) + } + + return $result } -function Get-GitHubViewTraffic +filter Get-GitHubViewTraffic { <# .SYNOPSIS - Get the total number of views and breakdown per day or week for the last 14 days for the given Github Repository. + Get the total number of views and breakdown per day or week for the last 14 days for the + given GitHub Repository. .DESCRIPTION Get the total number of views and breakdown per day or week for the last 14 days. - Timestamps are aligned to UTC midnight of the beginning of the day or week. Week begins on Monday. + Timestamps are aligned to UTC midnight of the beginning of the day or week. + Week begins on Monday. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -209,14 +277,34 @@ function Get-GitHubViewTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.ViewTraffic + .EXAMPLE - Get-GitHubViewTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubViewTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the total number of views and breakdown per day or week for the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the total number of views and breakdown per day or week for the last 14 days from + the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubViewTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -228,7 +316,9 @@ function Get-GitHubViewTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [ValidateSet('Day', 'Week')] @@ -261,18 +351,27 @@ function Get-GitHubViewTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubViewTrafficTypeName) + } + + return $result } -function Get-GitHubCloneTraffic +filter Get-GitHubCloneTraffic { <# .SYNOPSIS - Get the total number of clones and breakdown per day or week for the last 14 days for the given Github Repository. + Get the total number of clones and breakdown per day or week for the last 14 days for the + given GitHub Repository. .DESCRIPTION Get the total number of clones and breakdown per day or week for the last 14 days. - Timestamps are aligned to UTC midnight of the beginning of the day or week. Week begins on Monday. + Timestamps are aligned to UTC midnight of the beginning of the day or week. + Week begins on Monday. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -302,14 +401,34 @@ function Get-GitHubCloneTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.CloneTraffic + .EXAMPLE - Get-GitHubCloneTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubCloneTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the total number of clones and breakdown per day or week for the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the total number of clones and breakdown per day or week for the last 14 days + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubCloneTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -321,7 +440,9 @@ function Get-GitHubCloneTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [ValidateSet('Day', 'Week')] @@ -354,5 +475,12 @@ function Get-GitHubCloneTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubCloneTrafficTypeName) + } + + return $result } diff --git a/GitHubTeams.ps1 b/GitHubTeams.ps1 index 6c27c294..fa6da2cc 100644 --- a/GitHubTeams.ps1 +++ b/GitHubTeams.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubTeam +@{ + GitHubTeamTypeName = 'GitHub.Team' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubTeam { <# .SYNOPSIS @@ -41,17 +47,35 @@ function Get-GitHubTeam the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.Organization + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.Team + .OUTPUTS - [PSCustomObject[]] The team(s) that match the user's request. + GitHub.Team .EXAMPLE Get-GitHubTeam -OrganizationName PowerShell #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubTeamTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param ( [Parameter(ParameterSetName='Elements')] @@ -62,17 +86,21 @@ function Get-GitHubTeam [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Organization')] [ValidateNotNullOrEmpty()] [string] $OrganizationName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Single')] [ValidateNotNullOrEmpty()] [string] $TeamId, @@ -117,7 +145,7 @@ function Get-GitHubTeam $params = @{ 'UriFragment' = $uriFragment - 'AcceptHeader' = 'application/vnd.github.hellcat-preview+json' + 'AcceptHeader' = $script:hellcatAcceptHeader 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -125,10 +153,11 @@ function Get-GitHubTeam 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubTeamAdditionalProperties) } -function Get-GitHubTeamMember +filter Get-GitHubTeamMember { <# .SYNOPSIS @@ -158,30 +187,51 @@ function Get-GitHubTeamMember the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.Team + .OUTPUTS - [PSCustomObject[]] List of members on the team within the organization. + GitHub.User .EXAMPLE $members = Get-GitHubTeamMember -Organization PowerShell -TeamName Everybody #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='ID')] + [OutputType({$script:GitHubUserTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $OrganizationName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Name')] [ValidateNotNullOrEmpty()] [String] $TeamName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='ID')] [int64] $TeamId, @@ -223,5 +273,57 @@ function Get-GitHubTeamMember 'NoStatus' = $NoStatus } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) +} + +filter Add-GitHubTeamAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Team objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Team +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubTeamTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'TeamName' -Value $item.name -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'TeamId' -Value $item.id -MemberType NoteProperty -Force + + # Apply these properties to any embedded parent teams as well. + if ($null -ne $item.parent) + { + $null = Add-GitHubTeamAdditionalProperties -InputObject $item.parent + } + } + + Write-Output $item + } } diff --git a/GitHubUsers.ps1 b/GitHubUsers.ps1 index 3a77bca4..ed2b2cb9 100644 --- a/GitHubUsers.ps1 +++ b/GitHubUsers.ps1 @@ -1,7 +1,14 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubUser +@{ + GitHubUserTypeName = 'GitHub.User' + GitHubUserContextualInformationTypeName = 'GitHub.UserContextualInformation' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubUser { <# .SYNOPSIS @@ -14,7 +21,8 @@ function Get-GitHubUser .PARAMETER User The GitHub user to retrieve information for. - If not specified, will retrieve information on all GitHub users (and may take a while to complete). + If not specified, will retrieve information on all GitHub users + (and may take a while to complete). .PARAMETER Current If specified, gets information on the current user. @@ -38,11 +46,22 @@ function Get-GitHubUser which provides an email entry for this endpoint. If the user does not set a public email address for email, then it will have a value of null. + .INPUTS + GitHub.User + + .OUTPUTS + GitHub.User + .EXAMPLE - Get-GitHubUser -User octocat + Get-GitHubUser -UserName octocat Gets information on just the user named 'octocat' + .EXAMPLE + 'octocat', 'PowerShellForGitHubTeam' | Get-GitHubUser + + Gets information on the users named 'octocat' and 'PowerShellForGitHubTeam' + .EXAMPLE Get-GitHubUser @@ -56,11 +75,17 @@ function Get-GitHubUser [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='ListAndSearch')] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(ParameterSetName='ListAndSearch')] - [string] $User, + [Parameter( + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='ListAndSearch')] + [Alias('Name')] + [Alias('User')] + [string] $UserName, [Parameter(ParameterSetName='Current')] [switch] $Current, @@ -80,19 +105,22 @@ function Get-GitHubUser if ($Current) { - return Invoke-GHRestMethod -UriFragment "user" -Description "Getting current authenticated user" -Method 'Get' @params + return (Invoke-GHRestMethod -UriFragment "user" -Description "Getting current authenticated user" -Method 'Get' @params | + Add-GitHubUserAdditionalProperties) } - elseif ([String]::IsNullOrEmpty($User)) + elseif ([String]::IsNullOrEmpty($UserName)) { - return Invoke-GHRestMethodMultipleResult -UriFragment 'users' -Description 'Getting all users' @params + return (Invoke-GHRestMethodMultipleResult -UriFragment 'users' -Description 'Getting all users' @params | + Add-GitHubUserAdditionalProperties) } else { - return Invoke-GHRestMethod -UriFragment "users/$User" -Description "Getting user $User" -Method 'Get' @params + return (Invoke-GHRestMethod -UriFragment "users/$UserName" -Description "Getting user $UserName" -Method 'Get' @params | + Add-GitHubUserAdditionalProperties) } } -function Get-GitHubUserContextualInformation +filter Get-GitHubUserContextualInformation { <# .SYNOPSIS @@ -106,11 +134,23 @@ function Get-GitHubUserContextualInformation .PARAMETER User The GitHub user to retrieve information for. - .PARAMETER Subject - Identifies which additional information to receive about the user's hovercard. + .PARAMETER OrganizationId + The ID of an Organization. When provided, this returns back the context for the user + in relation to this Organization. + + .PARAMETER RepositoryId + The ID for a Repository. When provided, this returns back the context for the user + in relation to this Repository. - .PARAMETER SubjectId - The ID for the Subject. Required when Subject has been specified. + .PARAMETER IssueId + The ID for a Issue. When provided, this returns back the context for the user + in relation to this Issue. + NOTE: This is the *id* of the issue and not the issue *number*. + + .PARAMETER PullRequestId + The ID for a PullRequest. When provided, this returns back the context for the user + in relation to this Pull Request. + NOTE: This is the *id* of the pull request and not the pull request *number*. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -122,23 +162,68 @@ function Get-GitHubUserContextualInformation the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Issue + GitHub.Organization + GitHub.PullRequest + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.UserContextualInformation + .EXAMPLE Get-GitHubUserContextualInformation -User octocat .EXAMPLE - Get-GitHubUserContextualInformation -User octocat -Subject Repository -SubjectId 1300192 + Get-GitHubUserContextualInformation -User octocat -RepositoryId 1300192 + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName 'PowerShellForGitHub' + $repo | Get-GitHubUserContextualInformation -User octocat + + .EXAMPLE + Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 70 | + Get-GitHubUserContextualInformation -User octocat #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='NoContext')] + [OutputType({$script:GitHubUserContextualInformationTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] - [string] $User, - - [ValidateSet('Organization', 'Repository', 'Issue', 'PullRequest')] - [string] $Subject, - - [string] $SubjectId, + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] + [Alias('Name')] + [Alias('User')] + [string] $UserName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Organization')] + [int64] $OrganizationId, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Repository')] + [int64] $RepositoryId, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Issue')] + [int64] $IssueId, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='PullRequest')] + [int64] $PullRequestId, [string] $AccessToken, @@ -149,38 +234,70 @@ function Get-GitHubUserContextualInformation $getParams = @() - # Intentionally not using -xor here because we need to know if we're setting the GET parameters as well. - if ((-not [String]::IsNullOrEmpty($Subject)) -or (-not [String]::IsNullOrEmpty($SubjectId))) + $contextType = [String]::Empty + $contextId = 0 + if ($PSCmdlet.ParameterSetName -ne 'NoContext') { - if ([String]::IsNullOrEmpty($Subject) -or [String]::IsNullOrEmpty($SubjectId)) + if ($PSCmdlet.ParameterSetName -eq 'Organization') { - $message = 'If either Subject or SubjectId has been provided, then BOTH must be provided.' - Write-Log -Message $message -Level Error - throw $message + $getParams += 'subject_type=organization' + $getParams += "subject_id=$OrganizationId" + + $contextType = 'OrganizationId' + $contextId = $OrganizationId + } + elseif ($PSCmdlet.ParameterSetName -eq 'Repository') + { + $getParams += 'subject_type=repository' + $getParams += "subject_id=$RepositoryId" + + $contextType = 'RepositoryId' + $contextId = $RepositoryId } + elseif ($PSCmdlet.ParameterSetName -eq 'Issue') + { + $getParams += 'subject_type=issue' + $getParams += "subject_id=$IssueId" - $subjectConverter = @{ - 'Organization' = 'organization' - 'Repository' = 'repository' - 'Issue' = 'issue' - 'PullRequest' = 'pull_request' + $contextType = 'IssueId' + $contextId = $IssueId } + elseif ($PSCmdlet.ParameterSetName -eq 'PullRequest') + { + $getParams += 'subject_type=pull_request' + $getParams += "subject_id=$PullRequestId" - $getParams += "subject_type=$($subjectConverter[$Subject])" - $getParams += "subject_id=$SubjectId" + $contextType = 'PullRequestId' + $contextId = $PullRequestId + } } $params = @{ - 'UriFragment' = "users/$User/hovercard`?" + ($getParams -join '&') + 'UriFragment' = "users/$UserName/hovercard`?" + ($getParams -join '&') 'Method' = 'Get' - 'Description' = "Getting hovercard information for $User" - 'AcceptHeader' = 'application/vnd.github.hagar-preview+json' + 'Description' = "Getting hovercard information for $UserName" + 'AcceptHeader' = $script:hagarAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + foreach ($item in $result.contexts) + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubUserContextualInformationTypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'UserName' -Value $UserName -MemberType NoteProperty -Force + if ($PSCmdlet.ParameterSetName -ne 'NoContext') + { + Add-Member -InputObject $item -Name $contextType -Value $contextId -MemberType NoteProperty -Force + } + } + } + + return $result } function Update-GitHubCurrentUser @@ -226,6 +343,9 @@ function Update-GitHubCurrentUser the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .OUTPUTS + GitHub.User + .EXAMPLE Update-GitHubCurrentUser -Location 'Seattle, WA' -Hireable:$false @@ -233,6 +353,7 @@ function Update-GitHubCurrentUser are not currently hireable. #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [string] $Name, @@ -275,5 +396,82 @@ function Update-GitHubCurrentUser 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubUserAdditionalProperties) +} + +filter Add-GitHubUserAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub User objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER Name + The name of the user. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .PARAMETER Id + The ID of the user. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.User +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubUserTypeName, + + [string] $Name, + + [int64] $Id + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $userName = $item.login + if ([String]::IsNullOrEmpty($userName) -and $PSBoundParameters.ContainsKey('Name')) + { + $userName = $Name + } + + if (-not [String]::IsNullOrEmpty($userName)) + { + Add-Member -InputObject $item -Name 'UserName' -Value $userName -MemberType NoteProperty -Force + } + + $userId = $item.id + if (($userId -eq 0) -and $PSBoundParameters.ContainsKey('Id')) + { + $userId = $Id + } + + if ($userId -ne 0) + { + Add-Member -InputObject $item -Name 'UserId' -Value $userId -MemberType NoteProperty -Force + } + } + + Write-Output $item + } } diff --git a/Helpers.ps1 b/Helpers.ps1 index 1813d168..17fa80e2 100644 --- a/Helpers.ps1 +++ b/Helpers.ps1 @@ -281,13 +281,13 @@ function Write-Log [System.Management.Automation.ErrorRecord] $Exception ) - Begin + begin { # Accumulate the list of Messages, whether by pipeline or parameter. $messages = @() } - Process + process { foreach ($m in $Message) { @@ -295,7 +295,7 @@ function Write-Log } } - End + end { if ($null -ne $Exception) { @@ -499,6 +499,7 @@ function Write-InvocationLog } function DeepCopy-Object +{ <# .SYNOPSIS Creates a deep copy of a serializable object. @@ -524,7 +525,6 @@ function DeepCopy-Object .RETURNS An exact copy of the PSObject that was just deep copied. #> -{ [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Intentional. This isn't exported, and needed to be explicit relative to Copy-Object.")] param( @@ -682,9 +682,7 @@ function Resolve-UnverifiedPath #> [CmdletBinding()] param( - [Parameter( - Position=0, - ValueFromPipeline)] + [Parameter(ValueFromPipeline)] [string] $Path ) diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 5ef27773..c7f51c82 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -24,9 +24,9 @@ 'GitHubAssignees.ps1', 'GitHubBranches.ps1', 'GitHubCore.ps1', - 'GitHubComments.ps1', 'GitHubContents.ps1', 'GitHubEvents.ps1', + 'GitHubIssueComments.ps1', 'GitHubIssues.ps1', 'GitHubLabels.ps1', 'GitHubMilestones.ps1', @@ -57,13 +57,13 @@ 'Get-GitHubAssignee', 'Get-GitHubCloneTraffic', 'Get-GitHubCodeOfConduct', - 'Get-GitHubComment', 'Get-GitHubConfiguration', 'Get-GitHubContent', 'Get-GitHubEmoji', 'Get-GitHubEvent', 'Get-GitHubGitIgnore', 'Get-GitHubIssue', + 'Get-GitHubIssueComment', 'Get-GitHubIssueTimeline', 'Get-GitHubLabel', 'Get-GitHubLicense', @@ -95,13 +95,14 @@ 'Group-GitHubPullRequest', 'Invoke-GHRestMethod', 'Invoke-GHRestMethodMultipleResult', + 'Join-GitHubUri', 'Lock-GitHubIssue', 'Move-GitHubProjectCard', 'Move-GitHubProjectColumn', 'Move-GitHubRepositoryOwnership', - 'New-GithubAssignee', - 'New-GitHubComment', + 'New-GitHubAssignee', 'New-GitHubIssue', + 'New-GitHubIssueComment', 'New-GitHubLabel', 'New-GitHubMilestone', 'New-GitHubProject', @@ -110,8 +111,8 @@ 'New-GitHubPullRequest', 'New-GitHubRepository', 'New-GitHubRepositoryFork', - 'Remove-GithubAssignee', - 'Remove-GitHubComment', + 'Remove-GitHubAssignee', + 'Remove-GitHubIssueComment', 'Remove-GitHubIssueLabel', 'Remove-GitHubLabel', 'Remove-GitHubMilestone', @@ -123,8 +124,8 @@ 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', 'Set-GitHubAuthentication', - 'Set-GitHubComment', 'Set-GitHubConfiguration', + 'Set-GitHubIssueComment', 'Set-GitHubIssueLabel', 'Set-GitHubLabel', 'Set-GitHubMilestone', @@ -145,6 +146,7 @@ AliasesToExport = @( 'Delete-GitHubComment', + 'Delete-GitHubIssueComment', 'Delete-GitHubLabel', 'Delete-GitHubMilestone', 'Delete-GitHubProject', @@ -152,6 +154,10 @@ 'Delete-GitHubProjectColumn' 'Delete-GitHubRepository', 'Get-GitHubBranch', + 'Get-GitHubComment', + 'New-GitHubComment', + 'Remove-GitHubComment', + 'Set-GitHubComment', 'Transfer-GitHubRepositoryOwnership' ) diff --git a/README.md b/README.md index 0332ee91..0488dc54 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3990/badge)](https://bestpractices.coreinfrastructure.org/projects/3990) [![tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2FQuackFu)](https://twitter.com/intent/tweet?text=%23PowerShellForGitHub%20%40QuackFu%20&original_referer=https://github.com/microsoft/PowerShellForGitHub)
-[![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build?definitionId=109&_a=summary&repositoryFilter=63&branchFilter=2197) -[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build?definitionId=109&_a=summary&repositoryFilter=63&branchFilter=2197) -[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build?definitionId=109&_a=summary&repositoryFilter=63&branchFilter=2197) +[![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master)
[![Help Wanted Issues](https://img.shields.io/github/issues/microsoft/PowerShellForGitHub/help%20wanted)](https://github.com/microsoft/PowerShellForGitHub/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) [![GitHub last commit](https://img.shields.io/github/last-commit/microsoft/PowerShellForGitHub)](https://github.com/HowardWolosky/PowerShellForGitHub/commits/master) @@ -35,28 +35,58 @@ ## Overview This is a [PowerShell](https://microsoft.com/powershell) [module](https://technet.microsoft.com/en-us/library/dd901839.aspx) -that provides command-line interaction and automation for the [GitHub v3 API](https://developer.github.com/v3/). +that provides stateless command-line interaction and automation for the +[GitHub v3 API](https://developer.github.com/v3/). + +**Embracing the benefits of PowerShell, it has +[full support for pipelining](./USAGE.md#embracing-the-pipeline), allowing you pipe the output of +virtually any command into any other command within the module.** ---------- ## Current API Support At present, this module can: - * Query issues - * Query [pull requests](https://developer.github.com/v3/pulls/) + * Query, create, update and remove [Repositories](https://developer.github.com/v3/repos/) including + * Query [Branches](https://developer.github.com/v3/repos/branches/) + * Query and create new [Forks](https://developer.github.com/v3/repos/forks/) + * Query/retrieve [Content](https://developer.github.com/v3/repos/contents/) from a repo. + * Query the languages and tags in a repository, and and query/update its topics. + * Change repository ownership. + * Query various [traffic reports](https://developer.github.com/v3/repos/traffic/) including + referral sources and paths, page views and clones. + * Query, create, edit, lock/unlock [Issues](https://developer.github.com/v3/issues/) and + all of their related properties: + * Query, check, add and remove [Assignees](https://developer.github.com/v3/issues/assignees/) + * Query, create, edit and remove [Issue Comments](https://developer.github.com/v3/issues/comments/) + * Query, create, edit and remove [Labels](https://developer.github.com/v3/issues/labels/) + * Query [Events](https://developer.github.com/v3/issues/events/) and the + [timeline](https://developer.github.com/v3/issues/timeline/) + * Query, create, edit and remove [Milestones](https://developer.github.com/v3/issues/milestones/) + * Query and create [Pull Requests](https://developer.github.com/v3/pulls/) * Query [collaborators](https://developer.github.com/v3/repos/collaborators/) * Query [contributors](https://developer.github.com/v3/repos/statistics/) - * Query [organizations](https://developer.github.com/v3/orgs/) - * Query, create, update and remove [Issues](https://developer.github.com/v3/issues/) and - all of their related properties (assignees, comments, events, labels, milestones, timeline) - * Query, create, update and remove [Labels](https://developer.github.com/v3/issues/labels/) - * Query, check, add and remove [Assignees](https://developer.github.com/v3/issues/assignees/) - * Query, create, update and remove [Repositories](https://developer.github.com/v3/repos/) + * Query [organizations](https://developer.github.com/v3/orgs/) and their members. * Query and update [Users](https://developer.github.com/v3/users/) + * Query [Teams](https://developer.github.com/v3/teams/) and their members. + * Query, create, edit and remove [Projects](https://developer.github.com/v3/projects/), along with + [Project Columns](https://developer.github.com/v3/projects/columns/) and + [Project Cards](https://developer.github.com/v3/projects/cards/) + * Query [Releases](https://developer.github.com/v3/repos/releases/) + * Miscellaneous functionality: + * Get all [Codes of Conduct](https://developer.github.com/v3/codes_of_conduct/) as well as that + of a specific repo. + * Get all [GitHub emojis](https://developer.github.com/v3/emojis/) + * Get [gitignore templates](https://developer.github.com/v3/gitignore/) + * Get [commonly used licenses](https://developer.github.com/v3/licenses/) as well as that for + a specific repository. + * [Convert markdown](https://developer.github.com/v3/markdown/) to the equivalent HTML + * Get your current [rate limit](https://developer.github.com/v3/rate_limit/) for API usage. Development is ongoing, with the goal to add broad support for the entire API set. -For a comprehensive look at what work is remaining to be API Complete, refer to [Issue #70](https://github.com/PowerShell/PowerShellForGitHub/issues/70). +For a comprehensive look at what work is remaining to be API Complete, refer to +[Issue #70](https://github.com/microsoft/PowerShellForGitHub/issues/70). Review [examples](USAGE.md#examples) to see how the module can be used to accomplish some of these tasks. @@ -135,7 +165,7 @@ Set-GitHubConfiguration -ApiHostName "github.contoso.com" Example command: ```powershell -$issues = Get-GitHubIssue -Uri 'https://github.com/PowerShell/PowerShellForGitHub' +$issues = Get-GitHubIssue -Uri 'https://github.com/microsoft/PowerShellForGitHub' ``` For more example commands, please refer to [USAGE](USAGE.md#examples). @@ -147,7 +177,7 @@ For more example commands, please refer to [USAGE](USAGE.md#examples). Please see the [Contribution Guide](CONTRIBUTING.md) for information on how to develop and contribute. -If you have any problems, please consult [GitHub Issues](https://github.com/PowerShell/PowerShellForGitHub/issues) +If you have any problems, please consult [GitHub Issues](https://github.com/microsoft/PowerShellForGitHub/issues) to see if has already been discussed. If you do not see your problem captured, please file [feedback](CONTRIBUTING.md#feedback). diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index f28dc534..2e5d5512 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -1,6 +1,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# PSScriptAnalyzer incorrectly flags a number of variables as PSUseDeclaredVarsMoreThanAssignments +# since it doesn't work well with variables defined in BeforeAll{} but only referenced in a later Context. +# We are suppressing that rule in Test files, which means that we are then losing out on catching +# scenarios where we might be assigning to a variable and then referencing it with a typo. +# By setting StrictMode, the test file will immediately fail if there are any variables that are +# being referenced before they were assigned. It won't catch variables that are assigned to but +# never referenced, but that's not as big of a deal for tests. +Set-StrictMode -Version 1.0 + # Caches if the tests are actively configured with an access token. $script:accessTokenConfigured = $false diff --git a/Tests/GitHubAnalytics.tests.ps1 b/Tests/GitHubAnalytics.tests.ps1 index df62e4f5..1be29162 100644 --- a/Tests/GitHubAnalytics.tests.ps1 +++ b/Tests/GitHubAnalytics.tests.ps1 @@ -6,307 +6,18 @@ Tests for GitHubAnalytics.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - Describe 'Obtaining issues for repository' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - - Context 'When initially created, there are no issues' { - $issues = @(Get-GitHubIssue -Uri $repo.svn_url) - - It 'Should return expected number of issues' { - $issues.Count | Should be 0 - } - } - - Context 'When there are issues present' { - $newIssues = @() - for ($i = 0; $i -lt 4; $i++) - { - $newIssues += New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([guid]::NewGuid().Guid) - } - - $newIssues[0] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed - $newIssues[-1] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed - - $issues = @(Get-GitHubIssue -Uri $repo.svn_url) - It 'Should return only open issues' { - $issues.Count | Should be 2 - } - - $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All) - It 'Should return all issues' { - $issues.Count | Should be 4 - } - - $createdOnOrAfterDate = Get-Date -Date $newIssues[0].created_at - $createdOnOrBeforeDate = Get-Date -Date $newIssues[2].created_at - $issues = @((Get-GitHubIssue -Uri $repo.svn_url) | Where-Object { ($_.created_at -ge $createdOnOrAfterDate) -and ($_.created_at -le $createdOnOrBeforeDate) }) - - It 'Smart object date conversion works for comparing dates' { - $issues.Count | Should be 2 - } - - $createdDate = Get-Date -Date $newIssues[1].created_at - $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All | Where-Object { ($_.created_at -ge $createdDate) -and ($_.state -eq 'closed') }) - - It 'Able to filter based on date and state' { - $issues.Count | Should be 1 - } - } - - Context 'When issues are retrieved with a specific MediaTypes' { - $newIssue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([guid]::NewGuid()) -Body ([guid]::NewGuid()) - - $issues = @(Get-GitHubIssue -Uri $repo.svn_url -Issue $newIssue.number -MediaType 'Html') - It 'Should return an issue with body_html' { - $issues[0].body_html | Should not be $null - } - } - - $null = Remove-GitHubRepository -Uri ($repo.svn_url) -Confirm:$false - } - - Describe 'Obtaining repository with biggest number of issues' { - $repo1 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $repo2 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - - Context 'When no additional conditions specified' { - for ($i = 0; $i -lt 3; $i++) - { - $null = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo1.name -Title ([guid]::NewGuid().Guid) - } - - $repos = @(($repo1.svn_url), ($repo2.svn_url)) - $issueCounts = @() - $repos | ForEach-Object { $issueCounts = $issueCounts + ([PSCustomObject]@{ 'Uri' = $_; 'Count' = (Get-GitHubIssue -Uri $_).Count }) } - $issueCounts = $issueCounts | Sort-Object -Property Count -Descending - - It 'Should return expected number of issues for each repository' { - $issueCounts[0].Count | Should be 3 - $issueCounts[1].Count | Should be 0 - } - - It 'Should return expected repository names' { - $issueCounts[0].Uri | Should be $repo1.svn_url - $issueCounts[1].Uri | Should be $repo2.svn_url - } - } - - $null = Remove-GitHubRepository -Uri ($repo1.svn_url) -Confirm:$false - $null = Remove-GitHubRepository -Uri ($repo2.svn_url) -Confirm:$false - } - - - # TODO: Re-enable these tests once the module has sufficient support getting the repository into the - # required state for testing, and to recover back to the original state at the conclusion of the test. - - # Describe 'Obtaining pull requests for repository' { - # Context 'When no additional conditions specified' { - # $pullRequests = Get-GitHubPullRequest -Uri $script:repositoryUrl - - # It 'Should return expected number of PRs' { - # @($pullRequests).Count | Should be 2 - # } - # } - - # Context 'When state and time range specified' { - # $mergedStartDate = Get-Date -Date '2016-04-10' - # $mergedEndDate = Get-Date -Date '2016-05-07' - # $pullRequests = Get-GitHubPullRequest -Uri $script:repositoryUrl -State Closed | - # Where-Object { ($_.merged_at -ge $mergedStartDate) -and ($_.merged_at -le $mergedEndDate) } - - # It 'Should return expected number of PRs' { - # @($pullRequests).Count | Should be 3 - # } - # } - # } - - # Describe 'Obtaining repository with biggest number of pull requests' { - # Context 'When no additional conditions specified' { - # @($script:repositoryUrl, $script:repositoryUrl2) | - # ForEach-Object { - # $pullRequestCounts += ([PSCustomObject]@{ - # 'Uri' = $_; - # 'Count' = (Get-GitHubPullRequest -Uri $_).Count }) } - # $pullRequestCounts = $pullRequestCounts | Sort-Object -Property Count -Descending - - # It 'Should return expected number of pull requests for each repository' { - # @($pullRequestCounts[0].Count) | Should be 2 - # @($pullRequestCounts[1].Count) | Should be 0 - # } - - # It 'Should return expected repository names' { - # @($pullRequestCounts[0].Uri) | Should be $script:repositoryUrl - # @($pullRequestCounts[1].Uri) | Should be $script:repositoryUrl2 - # } - # } - - # Context 'When state and time range specified' { - # $mergedDate = Get-Date -Date '2015-04-20' - # $repos = @($script:repositoryUrl, $script:repositoryUrl2) - # $pullRequestCounts = @() - # $pullRequestSearchParams = @{ - # 'State' = 'closed' - # } - # $repos | - # ForEach-Object { - # $pullRequestCounts += ([PSCustomObject]@{ - # 'Uri' = $_; - # 'Count' = ( - # (Get-GitHubPullRequest -Uri $_ @pullRequestSearchParams) | - # Where-Object { $_.merged_at -ge $mergedDate } - # ).Count - # }) } - - # $pullRequestCounts = $pullRequestCounts | Sort-Object -Property Count -Descending - # $pullRequests = Get-GitHubTopPullRequestRepository -Uri @($script:repositoryUrl, $script:repositoryUrl2) -State Closed -MergedOnOrAfter - - # It 'Should return expected number of pull requests for each repository' { - # @($pullRequests[0].Count) | Should be 3 - # @($pullRequests[1].Count) | Should be 0 - # } - - # It 'Should return expected repository names' { - # @($pullRequests[0].Uri) | Should be $script:repositoryUrl - # @($pullRequests[1].Uri) | Should be $script:repositoryUrl2 - # } - # } - # } - - if ($script:accessTokenConfigured) - { - Describe 'Obtaining collaborators for repository' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - $repositoryUrl = "https://github.com/$script:ownerName/$repositoryName" - - $collaborators = @(Get-GitHubRepositoryCollaborator -Uri $repositoryUrl) - - It 'Should return expected number of collaborators' { - $collaborators.Count | Should be 1 - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } - } - - Describe 'Obtaining contributors for repository' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - $repositoryUrl = "https://github.com/$script:ownerName/$repositoryName" - - $contributors = @(Get-GitHubRepositoryContributor -Uri $repositoryUrl -IncludeStatistics) - - It 'Should return expected number of contributors' { - $contributors.Count | Should be 1 - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } - - if ($script:accessTokenConfigured) - { - # TODO: Re-enable these tests once the module has sufficient support getting the Organization - # and repository into the required state for testing, and to recover back to the original state - # at the conclusion of the test. - - # Describe 'Obtaining organization members' { - # $members = Get-GitHubOrganizationMember -OrganizationName $script:organizationName - - # It 'Should return expected number of organization members' { - # @($members).Count | Should be 1 - # } - # } - - # Describe 'Obtaining organization teams' { - # $teams = Get-GitHubTeam -OrganizationName $script:organizationName - - # It 'Should return expected number of organization teams' { - # @($teams).Count | Should be 2 - # } - # } - - # Describe 'Obtaining organization team members' { - # $members = Get-GitHubTeamMember -OrganizationName $script:organizationName -TeamName $script:organizationTeamName - - # It 'Should return expected number of organization team members' { - # @($members).Count | Should be 1 - # } - # } - } - - Describe 'Getting repositories from organization' { - $original = @(Get-GitHubRepository -OrganizationName $script:organizationName) - - $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -OrganizationName $script:organizationName - $current = @(Get-GitHubRepository -OrganizationName $script:organizationName) - - It 'Should return expected number of organization repositories' { - ($current.Count - $original.Count) | Should be 1 - } - - $null = Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false - } - - Describe 'Getting unique contributors from contributors array' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - - $contributors = @(Get-GitHubRepositoryContributor -OwnerName $script:ownerName -RepositoryName $repositoryName -IncludeStatistics) - - $uniqueContributors = $contributors | - Select-Object -ExpandProperty author | - Select-Object -ExpandProperty login -Unique - Sort-Object - - It 'Should return expected number of unique contributors' { - $uniqueContributors.Count | Should be 1 - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } - - Describe 'Getting repository name from url' { - $repositoryName = [guid]::NewGuid().Guid - $url = "https://github.com/$script:ownerName/$repositoryName" - $name = Split-GitHubUri -Uri $url -RepositoryName - - It 'Should return expected repository name' { - $name | Should be $repositoryName - } - } - - Describe 'Getting repository owner from url' { - $repositoryName = [guid]::NewGuid().Guid - $url = "https://github.com/$script:ownerName/$repositoryName" - $owner = Split-GitHubUri -Uri $url -OwnerName - - It 'Should return expected repository owner' { - $owner | Should be $script:ownerName - } - } - - Describe 'Getting branches for repository' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - - $branches = @(Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName) - - It 'Should return expected number of repository branches' { - $branches.Count | Should be 1 - } - - It 'Should return the name of the branches' { - $branches[0].name | Should be 'master' - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } + # TODO } finally { diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index 5e4738d5..d9eeb61c 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -6,62 +6,253 @@ Tests for GitHubAssignees.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $issue = New-GitHubIssue -Uri $repo.svn_url -Title "Test issue" + Describe 'Getting an Assignee' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Describe 'Getting a valid assignee' { + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - Context 'For getting a valid assignee' { - $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) + Context 'For getting assignees in a repository via parameters' { + $assigneeList = @(Get-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name) It 'Should have returned the one assignee' { - $assigneeList.Count | Should be 1 + $assigneeList.Count | Should -Be 1 } - $assigneeUserName = $assigneeList[0].login - - It 'Should have returned an assignee with a login'{ - $assigneeUserName | Should not be $null + It 'Should have the expected type' { + $assigneeList[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' } + } - $hasPermission = Test-GitHubAssignee -Uri $repo.svn_url -Assignee $assigneeUserName + Context 'For getting assignees in a repository with the repo on the pipeline' { + $assigneeList = @($repo | Get-GitHubAssignee) - It 'Should have returned an assignee with permission to be assigned to an issue'{ - $hasPermission | Should be $true + It 'Should have returned the one assignee' { + $assigneeList.Count | Should -Be 1 } + It 'Should have the expected type' { + $assigneeList[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } } - Describe 'Adding and removing an assignee to an issue'{ + Describe 'Testing for a valid Assignee' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $octocat = Get-GitHubUser -UserName 'octocat' + $owner = Get-GitHubUser -UserName $script:ownerName + } - Context 'For adding an assignee to an issue'{ - $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) - $assigneeUserName = $assigneeList[0].login - $assignees = $assigneeUserName - New-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees - $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - It 'Should have assigned the user to the issue' { - $issue.assignee.login | Should be $assigneeUserName + Context 'For testing valid owner with parameters' { + $hasPermission = Test-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Assignee $script:ownerName + + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeTrue } + } - Remove-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees -Confirm:$false - $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + Context 'For testing valid owner with the repo on the pipeline' { + $hasPermission = $repo | Test-GitHubAssignee -Assignee $script:ownerName - It 'Should have removed the user from issue' { - $issue.assignees.Count | Should be 0 + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeTrue + } + } + + Context 'For testing valid owner with a user object on the pipeline' { + $hasPermission = $owner | Test-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name + + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeTrue + } + } + + Context 'For testing invalid owner with a user object on the pipeline' { + $hasPermission = $octocat | Test-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name + + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeFalse } } } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Describe 'Adding and Removing Assignees from an Issue' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $owner = Get-GitHubUser -UserName $script:ownerName + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } + + Context 'Adding and removing an assignee via parameters' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = New-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = Remove-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login -Confirm:$false + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + + Context 'Adding an assignee with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = $repo | New-GitHubAssignee -Issue $issue.number -Assignee $owner.login + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = $repo | Remove-GitHubAssignee -Issue $issue.number -Assignee $owner.login -Force -Confirm:$false + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + + Context 'Adding an assignee with the issue on the pipeline' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = $issue | New-GitHubAssignee -Assignee $owner.login + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = $issue | Remove-GitHubAssignee -Assignee $owner.login -Force + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + + Context 'Adding an assignee with the assignee user object on the pipeline' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = $owner | New-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = $owner | Remove-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Force + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + } } finally { diff --git a/Tests/GitHubBranches.tests.ps1 b/Tests/GitHubBranches.tests.ps1 new file mode 100644 index 00000000..f0df1ca9 --- /dev/null +++ b/Tests/GitHubBranches.tests.ps1 @@ -0,0 +1,119 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubBranches.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting branches for repository' { + BeforeAll { + $repositoryName = [guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName -AutoInit + $branchName = 'master' + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } + + Context 'Getting all branches for a repository with parameters' { + $branches = @(Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName) + + It 'Should return expected number of repository branches' { + $branches.Count | Should -Be 1 + } + + It 'Should return the name of the expected branch' { + $branches.name | Should -Contain $branchName + } + + It 'Should have the exected type and addititional properties' { + $branches[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branches[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $branches[0].BranchName | Should -Be $branches[0].name + } + } + + Context 'Getting all branches for a repository with the repo on the pipeline' { + $branches = @($repo | Get-GitHubRepositoryBranch) + + It 'Should return expected number of repository branches' { + $branches.Count | Should -Be 1 + } + + It 'Should return the name of the expected branch' { + $branches.name | Should -Contain $branchName + } + + It 'Should have the exected type and addititional properties' { + $branches[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branches[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $branches[0].BranchName | Should -Be $branches[0].name + } + } + + Context 'Getting a specific branch for a repository with parameters' { + $branch = Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName -BranchName $branchName + + It 'Should return the expected branch name' { + $branch.name | Should -Be $branchName + } + + It 'Should have the exected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $branch.name + } + } + + Context 'Getting a specific branch for a repository with the repo on the pipeline' { + $branch = $repo | Get-GitHubRepositoryBranch -BranchName $branchName + + It 'Should return the expected branch name' { + $branch.name | Should -Be $branchName + } + + It 'Should have the exected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $branch.name + } + } + + Context 'Getting a specific branch for a repository with the branch object on the pipeline' { + $branch = Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName -BranchName $branchName + $branchAgain = $branch | Get-GitHubRepositoryBranch + + It 'Should return the expected branch name' { + $branchAgain.name | Should -Be $branchName + } + + It 'Should have the exected type and addititional properties' { + $branchAgain.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branchAgain.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branchAgain.BranchName | Should -Be $branchAgain.name + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubComments.tests.ps1 b/Tests/GitHubComments.tests.ps1 deleted file mode 100644 index 66c9242b..00000000 --- a/Tests/GitHubComments.tests.ps1 +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# -.Synopsis - Tests for GitHubComments.ps1 module -#> - -# This is common test code setup logic for all Pester test files -$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent -. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') - -try -{ - # Define Script-scoped, readonly, hidden variables. - @{ - defaultIssueTitle = "Test Title" - defaultCommentBody = "This is a test body." - defaultEditedCommentBody = "This is an edited test body." - }.GetEnumerator() | ForEach-Object { - Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value - } - - Describe 'Creating, modifying and deleting comments' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - - $issue = New-GitHubIssue -Uri $repo.svn_url -Title $defaultIssueTitle - - Context 'For creating a new comment' { - $newComment = New-GitHubComment -Uri $repo.svn_url -Issue $issue.number -Body $defaultCommentBody - $existingComment = Get-GitHubComment -Uri $repo.svn_url -CommentID $newComment.id - - It "Should have the expected body text" { - $existingComment.body | Should be $defaultCommentBody - } - } - - Context 'For getting comments from an issue' { - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url -Issue $issue.number) - - It 'Should have the expected number of comments' { - $existingComments.Count | Should be 1 - } - - It 'Should have the expected body text on the first comment' { - $existingComments[0].body | Should be $defaultCommentBody - } - } - - Context 'For getting comments from an issue with a specific MediaType' { - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url -Issue $issue.number -MediaType 'Html') - - It 'Should have the expected body_html on the first comment' { - $existingComments[0].body_html | Should not be $null - } - } - - Context 'For editing a comment' { - $newComment = New-GitHubComment -Uri $repo.svn_url -Issue $issue.number -Body $defaultCommentBody - $editedComment = Set-GitHubComment -Uri $repo.svn_url -CommentID $newComment.id -Body $defaultEditedCommentBody - - It 'Should have a body that is not equal to the original body' { - $editedComment.body | Should not be $newComment.Body - } - - It 'Should have the edited content' { - $editedComment.body | Should be $defaultEditedCommentBody - } - } - - Context 'For getting comments from a repository and deleting them' { - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url) - - It 'Should have the expected number of comments' { - $existingComments.Count | Should be 2 - } - - foreach($comment in $existingComments) { - Remove-GitHubComment -Uri $repo.svn_url -CommentID $comment.id -Confirm:$false - } - - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url) - - It 'Should have no comments' { - $existingComments.Count | Should be 0 - } - } - - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false - } -} -finally -{ - if (Test-Path -Path $script:originalConfigFile -PathType Leaf) - { - # Restore the user's configuration to its pre-test state - Restore-GitHubConfiguration -Path $script:originalConfigFile - $script:originalConfigFile = $null - } -} diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index 7dbf568e..806d373c 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubContents.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -29,74 +34,133 @@ try } Describe 'Getting file and folder content' { - # AutoInit will create a readme with the GUID of the repo name - $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit + BeforeAll { + # AutoInit will create a readme with the GUID of the repo name + $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit + } - Context 'For getting folder contents' { + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + Context 'For getting folder contents with parameters' { $folderOutput = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name It "Should have the expected name" { - $folderOutput.name | Should be "" + $folderOutput.name | Should -BeNullOrEmpty } + It "Should have the expected path" { - $folderOutput.path | Should be "" + $folderOutput.path | Should -BeNullOrEmpty } + It "Should have the expected type" { - $folderOutput.type | Should be "dir" + $folderOutput.type | Should -Be "dir" } + It "Should have the expected entries" { - $folderOutput.entries.length | Should be 1 + $folderOutput.entries.length | Should -Be 1 } + It "Should have the expected entry data" { - $folderOutput.entries[0].name | Should be $readmeFileName - $folderOutput.entries[0].path | Should be $readmeFileName + $folderOutput.entries[0].name | Should -Be $readmeFileName + $folderOutput.entries[0].path | Should -Be $readmeFileName + } + + It "Should have the expected type and additional properties" { + $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl } } Context 'For getting folder contents via URL' { - $folderOutput = Get-GitHubContent -Uri "https://github.com/$($script:ownerName)/$($repo.name)" It "Should have the expected name" { - $folderOutput.name | Should be "" + $folderOutput.name | Should -BeNullOrEmpty } + It "Should have the expected path" { - $folderOutput.path | Should be "" + $folderOutput.path | Should -BeNullOrEmpty } + It "Should have the expected type" { - $folderOutput.type | Should be "dir" + $folderOutput.type | Should -Be "dir" } + It "Should have the expected entries" { - $folderOutput.entries.length | Should be 1 + $folderOutput.entries.length | Should -Be 1 } + It "Should have the expected entry data" { - $folderOutput.entries[0].name | Should be $readmeFileName - $folderOutput.entries[0].path | Should be $readmeFileName + $folderOutput.entries[0].name | Should -Be $readmeFileName + $folderOutput.entries[0].path | Should -Be $readmeFileName + } + + It "Should have the expected type" { + $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl } } - Context 'For getting raw (byte) file contents' { + Context 'For getting folder contents with the repo on the pipeline' { + $folderOutput = $repo | Get-GitHubContent + + It "Should have the expected name" { + $folderOutput.name | Should -BeNullOrEmpty + } + + It "Should have the expected path" { + $folderOutput.path | Should -BeNullOrEmpty + } + + It "Should have the expected type" { + $folderOutput.type | Should -Be "dir" + } + + It "Should have the expected entries" { + $folderOutput.entries.length | Should -Be 1 + } + + It "Should have the expected entry data" { + $folderOutput.entries[0].name | Should -Be $readmeFileName + $folderOutput.entries[0].path | Should -Be $readmeFileName + } + It "Should have the expected type" { + $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + } + + Context 'For getting raw (byte) file contents' { $readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw $readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes) It "Should have the expected content" { - $readmeFileString | Should be $rawOutput + $readmeFileString | Should -Be $rawOutput + } + + It "Should have the expected type" { + $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty } } Context 'For getting raw (string) file contents' { - $readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw -ResultAsString It "Should have the expected content" { - $readmeFileString | Should be $rawOutput + $readmeFileString | Should -Be $rawOutput + } + + It "Should have the expected type" { + $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty } } Context 'For getting html (byte) file contents' { - $readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html $readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes) @@ -108,10 +172,14 @@ try $readmeNoBreaks.StartsWith($htmlOutputStart) | Should -BeTrue $readmeNoBreaks.IndexOf($repoGuid) | Should -BeGreaterOrEqual 0 } + + It "Should have the expected type" { + $readmeNoBreaks.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeNoBreaks.RepositoryUrl | Should -BeNullOrEmpty + } } Context 'For getting html (string) file contents' { - $readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html -ResultAsString # Replace newlines with empty for comparison purposes @@ -122,55 +190,69 @@ try $readmeNoBreaks.StartsWith($htmlOutputStart) | Should -BeTrue $readmeNoBreaks.IndexOf($repoGuid) | Should -BeGreaterOrEqual 0 } + + It "Should have the expected type" { + $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty + } } Context 'For getting object (default) file result' { - $readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName It "Should have the expected name" { - $readmeFileObject.name | Should be $readmeFileName + $readmeFileObject.name | Should -Be $readmeFileName } + It "Should have the expected path" { - $readmeFileObject.path | Should be $readmeFileName + $readmeFileObject.path | Should -Be $readmeFileName } + It "Should have the expected type" { - $readmeFileObject.type | Should be "file" + $readmeFileObject.type | Should -Be "file" } + It "Should have the expected encoding" { - $readmeFileObject.encoding | Should be "base64" + $readmeFileObject.encoding | Should -Be "base64" } It "Should have the expected content" { # Convert from base64 $readmeFileString = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($readmeFileObject.content)) - $readmeFileString | Should be $rawOutput + $readmeFileString | Should -Be $rawOutput + } + + It "Should have the expected type" { + $readmeFileObject.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $readmeFileObject.RepositoryUrl | Should -Be $repo.RepositoryUrl } } Context 'For getting object file result as string' { - $readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Object -ResultAsString It "Should have the expected name" { - $readmeFileObject.name | Should be $readmeFileName + $readmeFileObject.name | Should -Be $readmeFileName } It "Should have the expected path" { - $readmeFileObject.path | Should be $readmeFileName + $readmeFileObject.path | Should -Be $readmeFileName } It "Should have the expected type" { - $readmeFileObject.type | Should be "file" + $readmeFileObject.type | Should -Be "file" } It "Should have the expected encoding" { - $readmeFileObject.encoding | Should be "base64" + $readmeFileObject.encoding | Should -Be "base64" } It "Should have the expected content" { - $readmeFileObject.contentAsString | Should be $rawOutput + $readmeFileObject.contentAsString | Should -Be $rawOutput } - } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It "Should have the expected type" { + $readmeFileObject.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $readmeFileObject.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + } } } finally diff --git a/Tests/GitHubCore.Tests.ps1 b/Tests/GitHubCore.Tests.ps1 index 0fab5a3a..cb6c3533 100644 --- a/Tests/GitHubCore.Tests.ps1 +++ b/Tests/GitHubCore.Tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubCore.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -28,7 +33,7 @@ try It 'Should return the same values' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -48,7 +53,7 @@ try It 'Should return the correct values' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -78,26 +83,26 @@ try $converted = ConvertTo-SmarterObject -InputObject $original It 'Should convert the value to a [DateTime]' { - $converted.closed_at -is [DateTime] | Should be $true - $converted.committed_at -is [DateTime] | Should be $true - $converted.completed_at -is [DateTime] | Should be $true - $converted.created_at -is [DateTime] | Should be $true - $converted.date -is [DateTime] | Should be $true - $converted.due_on -is [DateTime] | Should be $true - $converted.last_edited_at -is [DateTime] | Should be $true - $converted.last_read_at -is [DateTime] | Should be $true - $converted.merged_at -is [DateTime] | Should be $true - $converted.published_at -is [DateTime] | Should be $true - $converted.pushed_at -is [DateTime] | Should be $true - $converted.starred_at -is [DateTime] | Should be $true - $converted.started_at -is [DateTime] | Should be $true - $converted.submitted_at -is [DateTime] | Should be $true - $converted.timestamp -is [DateTime] | Should be $true - $converted.updated_at -is [DateTime] | Should be $true + $converted.closed_at -is [DateTime] | Should -Be $true + $converted.committed_at -is [DateTime] | Should -Be $true + $converted.completed_at -is [DateTime] | Should -Be $true + $converted.created_at -is [DateTime] | Should -Be $true + $converted.date -is [DateTime] | Should -Be $true + $converted.due_on -is [DateTime] | Should -Be $true + $converted.last_edited_at -is [DateTime] | Should -Be $true + $converted.last_read_at -is [DateTime] | Should -Be $true + $converted.merged_at -is [DateTime] | Should -Be $true + $converted.published_at -is [DateTime] | Should -Be $true + $converted.pushed_at -is [DateTime] | Should -Be $true + $converted.starred_at -is [DateTime] | Should -Be $true + $converted.started_at -is [DateTime] | Should -Be $true + $converted.submitted_at -is [DateTime] | Should -Be $true + $converted.timestamp -is [DateTime] | Should -Be $true + $converted.updated_at -is [DateTime] | Should -Be $true } It 'Should NOT convert the value to a [DateTime] if it''s not a known property' { - $converted.prop1 -is [DateTime] | Should be $false + $converted.prop1 -is [DateTime] | Should -Be $false } } @@ -124,22 +129,22 @@ try $converted = ConvertTo-SmarterObject -InputObject $original It 'Should keep the existing value' { - $original.closed_at -eq $converted.closed_at | Should be $true - $original.committed_at -eq $converted.committed_at | Should be $true - $original.completed_at -eq $converted.completed_at | Should be $true - $original.created_at -eq $converted.created_at | Should be $true - $original.date -eq $converted.date | Should be $true - $original.due_on -eq $converted.due_on | Should be $true - $original.last_edited_at -eq $converted.last_edited_at | Should be $true - $original.last_read_at -eq $converted.last_read_at | Should be $true - $original.merged_at -eq $converted.merged_at | Should be $true - $original.published_at -eq $converted.published_at | Should be $true - $original.pushed_at -eq $converted.pushed_at | Should be $true - $original.starred_at -eq $converted.starred_at | Should be $true - $original.started_at -eq $converted.started_at | Should be $true - $original.submitted_at -eq $converted.submitted_at | Should be $true - $original.timestamp -eq $converted.timestamp | Should be $true - $original.updated_at -eq $converted.updated_at | Should be $true + $original.closed_at -eq $converted.closed_at | Should -Be $true + $original.committed_at -eq $converted.committed_at | Should -Be $true + $original.completed_at -eq $converted.completed_at | Should -Be $true + $original.created_at -eq $converted.created_at | Should -Be $true + $original.date -eq $converted.date | Should -Be $true + $original.due_on -eq $converted.due_on | Should -Be $true + $original.last_edited_at -eq $converted.last_edited_at | Should -Be $true + $original.last_read_at -eq $converted.last_read_at | Should -Be $true + $original.merged_at -eq $converted.merged_at | Should -Be $true + $original.published_at -eq $converted.published_at | Should -Be $true + $original.pushed_at -eq $converted.pushed_at | Should -Be $true + $original.starred_at -eq $converted.starred_at | Should -Be $true + $original.started_at -eq $converted.started_at | Should -Be $true + $original.submitted_at -eq $converted.submitted_at | Should -Be $true + $original.timestamp -eq $converted.timestamp | Should -Be $true + $original.updated_at -eq $converted.updated_at | Should -Be $true } } @@ -156,7 +161,7 @@ try It 'Should still be an empty array after conversion' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -173,7 +178,7 @@ try It 'Should still be a single item array after conversion' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -190,11 +195,56 @@ try It 'Should still be a multi item array after conversion' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } } } + + Describe 'Testing Split-GitHubUri' { + BeforeAll { + $repositoryName = [guid]::NewGuid().Guid + $url = "https://github.com/$script:ownerName/$repositoryName" + } + + Context 'For getting the OwnerName' { + It 'Should return expected repository name' { + $name = Split-GitHubUri -Uri $url -RepositoryName + $name | Should -Be $repositoryName + } + + It 'Should return expected repository name with the pipeline' { + $name = $url | Split-GitHubUri -RepositoryName + $name | Should -Be $repositoryName + } + } + + Context 'For getting the RepositoryName' { + It 'Should return expected owner name' { + $name = Split-GitHubUri -Uri $url -OwnerName + $name | Should -Be $script:ownerName + } + + It 'Should return expected owner name with the pipeline' { + $owner = $url | Split-GitHubUri -OwnerName + $owner | Should -Be $script:ownerName + } + } + + Context 'For getting both the OwnerName and the RepositoryName' { + It 'Should return both OwnerName and RepositoryName' { + $elements = Split-GitHubUri -Uri $url + $elements.ownerName | Should -Be $script:ownerName + $elements.repositoryName | Should -Be $repositoryName + } + + It 'Should return both OwnerName and RepositoryName with the pipeline' { + $elements = $url | Split-GitHubUri + $elements.ownerName | Should -Be $script:ownerName + $elements.repositoryName | Should -Be $repositoryName + } + } + } } finally { diff --git a/Tests/GitHubEvents.tests.ps1 b/Tests/GitHubEvents.tests.ps1 index 4411d640..43390e7a 100644 --- a/Tests/GitHubEvents.tests.ps1 +++ b/Tests/GitHubEvents.tests.ps1 @@ -6,83 +6,132 @@ Tests for GitHubEvents.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - if ($accessTokenConfigured) - { - Describe 'Getting events from repository' { + Describe 'Getting events from repository' { + BeforeAll { $repositoryName = [Guid]::NewGuid() - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $null = $repo | Remove-GitHubRepository -Force + } - Context 'For getting events from a new repository' { - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'For getting events from a new repository (via parameter)' { + $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) - It 'Should have no events' { - $events.Count | Should be 0 - } + It 'Should have no events (via parameter)' { + $events.Count | Should -Be 0 } + } + + Context 'For getting events from a new repository (via pipeline)' { + $events = @($repo | Get-GitHubEvent) - $issue = New-GithubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Closed + It 'Should have no events (via parameter)' { + $events.Count | Should -Be 0 + } + } - Context 'For getting events from a repository' { - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'For getting Issue events from a repository' { + $issue = $repo | New-GitHubIssue -Title 'New Issue' + $issue = $issue | Update-GitHubIssue -State Closed + $events = @($repo | Get-GitHubEvent) - It 'Should have an event from closing an issue' { - $events.Count | Should be 1 - } + It 'Should have an event from closing an issue' { + $events.Count | Should -Be 1 } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + $events[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $events[0].issue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $events[0].IssueId | Should -Be $events[0].issue.id + $events[0].IssueNumber | Should -Be $events[0].issue.number + $events[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } + } - Describe 'Getting events from an issue' { + Describe 'Getting events from an issue' { + BeforeAll { $repositoryName = [Guid]::NewGuid() - $null = New-GitHubRepository -RepositoryName $repositoryName - $issue = New-GithubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" + $repo = New-GitHubRepository -RepositoryName $repositoryName + $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - Context 'For getting events from a new issue' { - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) + Context 'For getting events from a new issue' { + $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) - It 'Should have no events' { - $events.Count | Should be 0 - } + It 'Should have no events' { + $events.Count | Should -Be 0 } + } - Context 'For getting events from an issue' { - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Closed - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Open - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'For getting events from an issue' { + $issue = $issue | Update-GitHubIssue -State Closed + $issue = $issue | Update-GitHubIssue -State Open + $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) - It 'Should have two events from closing and opening the issue' { - $events.Count | Should be 2 - } + It 'Should have two events from closing and opening the issue' { + $events.Count | Should -Be 2 } - - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } - Describe 'Getting an event directly' { + } + + Describe 'Getting an event directly' { + BeforeAll { $repositoryName = [Guid]::NewGuid() - $null = New-GitHubRepository -RepositoryName $repositoryName - $issue = New-GithubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Closed - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Open - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + $repo = New-GitHubRepository -RepositoryName $repositoryName + $issue = $repo | New-GitHubIssue -Title 'New Issue' + $issue = $issue | Update-GitHubIssue -State Closed + $issue = $issue | Update-GitHubIssue -State Open + $events = @($repo | Get-GitHubEvent) + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - Context 'For getting an event directly'{ - $singleEvent = Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -EventID $events[0].id + Context 'For getting a single event directly by parameter'{ + $singleEvent = Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -EventID $events[0].id - It 'Should have the correct event type'{ - $singleEvent.event | Should be 'reopened' - } + It 'Should have the correct event type' { + $singleEvent.event | Should -Be 'reopened' + } + } + + Context 'For getting a single event directly by pipeline'{ + $singleEvent = $events[0] | Get-GitHubEvent + + It 'Should have the expected event type' { + $singleEvent.event | Should -Be $events[0].event } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the same id' { + $singleEvent.id | Should -Be $events[0].id + } + + It 'Should have the expected type and additional properties' { + $singleEvent.PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $singleEvent.RepositoryUrl | Should -Be $repo.RepositoryUrl + $singleEvent.EventId | Should -Be $singleEvent.id + $singleEvent.actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } } } diff --git a/Tests/GitHubIssueComments.tests.ps1 b/Tests/GitHubIssueComments.tests.ps1 new file mode 100644 index 00000000..d5a82873 --- /dev/null +++ b/Tests/GitHubIssueComments.tests.ps1 @@ -0,0 +1,251 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubComments.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # Define Script-scoped, readonly, hidden variables. + @{ + defaultIssueTitle = "Test Title" + defaultCommentBody = "This is a test body." + defaultEditedCommentBody = "This is an edited test body." + }.GetEnumerator() | ForEach-Object { + Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value + } + + Describe 'Creating, modifying and deleting comments' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $issue = $repo | New-GitHubIssue -Title $defaultIssueTitle + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } + + Context 'With parameters' { + $comment = New-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $comment.id + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $commentId = $result.id + $updated = Set-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $commentId + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Remove-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Force + It 'Should have been removed' { + { Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId } | Should -Throw + } + } + + Context 'With the repo on the pipeline' { + $comment = $repo | New-GitHubIssueComment -Issue $issue.number -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = $repo | Get-GitHubIssueComment -Comment $comment.id + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $commentId = $result.id + $updated = $repo | Set-GitHubIssueComment -Comment $commentId -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $commentId + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $repo | Remove-GitHubIssueComment -Comment $commentId -Force + It 'Should have been removed' { + { $repo | Get-GitHubIssueComment -Comment $commentId } | Should -Throw + } + } + + Context 'With the issue on the pipeline' { + $comment = $issue | New-GitHubIssueComment -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $comment.id + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $commentId = $result.id + $updated = Set-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $commentId + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Remove-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Force + It 'Should have been removed' { + { Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId } | Should -Throw + } + } + + Context 'With the comment object on the pipeline' { + $comment = New-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = $comment | Get-GitHubIssueComment + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $updated = $comment | Set-GitHubIssueComment -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $comment.id + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $comment | Remove-GitHubIssueComment -Force + It 'Should have been removed' { + { $comment | Get-GitHubIssueComment } | Should -Throw + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubIssues.tests.ps1 b/Tests/GitHubIssues.tests.ps1 new file mode 100644 index 00000000..21b4dd30 --- /dev/null +++ b/Tests/GitHubIssues.tests.ps1 @@ -0,0 +1,617 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubIssues.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting issues for a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Getting all issues for a repository with parameters' { + $currentIssues = @(Get-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name) + + $numIssues = 2 + 1..$numIssues | + ForEach-Object { New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) } | + Out-Null + + $issues = @(Get-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name) + It 'Should return expected number of issues' { + $issues.Count | Should -Be ($numIssues + $currentIssues.Count) + } + } + + Context 'Getting all issues for a repository with the repo on the pipeline' { + $currentIssues = @($repo | Get-GitHubIssue) + + $numIssues = 2 + 1..$numIssues | + ForEach-Object { $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) } | + Out-Null + + $issues = @($repo | Get-GitHubIssue) + It 'Should return expected number of issues' { + $issues.Count | Should -Be ($numIssues + $currentIssues.Count) + } + } + + Context 'Getting a specific issue with parameters' { + $issue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + + $result = Get-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number + It 'Should be the expected Issue' { + $result.id | Should -Be $issue.id + } + + It 'Should have the expected property values' { + $result.user.login | Should -Be $script:ownerName + $result.labels | Should -BeNullOrEmpty + $result.milestone | Should -BeNullOrEmpty + $result.assignee | Should -BeNullOrEmpty + $result.assignees | Should -BeNullOrEmpty + $result.closed_by | Should -BeNullOrEmpty + $result.repository | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.IssueId | Should -Be $result.id + $result.IssueNumber | Should -Be $result.number + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting a specific issue with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + + $result = $repo | Get-GitHubIssue -Issue $issue.number + It 'Should be the expected Issue' { + $result.id | Should -Be $issue.id + } + + It 'Should have the expected property values' { + $result.user.login | Should -Be $script:ownerName + $result.labels | Should -BeNullOrEmpty + $result.milestone | Should -BeNullOrEmpty + $result.assignee | Should -BeNullOrEmpty + $result.assignees | Should -BeNullOrEmpty + $result.closed_by | Should -BeNullOrEmpty + $result.repository | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.IssueId | Should -Be $result.id + $result.IssueNumber | Should -Be $result.number + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting a specific issue with the issue on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + + $result = $issue | Get-GitHubIssue -Issue $issue.number + It 'Should be the expected Issue' { + $result.id | Should -Be $issue.id + } + + It 'Should have the expected property values' { + $result.user.login | Should -Be $script:ownerName + $result.labels | Should -BeNullOrEmpty + $result.milestone | Should -BeNullOrEmpty + $result.assignee | Should -BeNullOrEmpty + $result.assignees | Should -BeNullOrEmpty + $result.closed_by | Should -BeNullOrEmpty + $result.repository | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.IssueId | Should -Be $result.id + $result.IssueNumber | Should -Be $result.number + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'When issues are retrieved with a specific MediaTypes' { + $newIssue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([guid]::NewGuid()) -Body ([Guid]::NewGuid()) + + $issues = @(Get-GitHubIssue -Uri $repo.svn_url -Issue $newIssue.number -MediaType 'Html') + It 'Should return an issue with body_html' { + $issues[0].body_html | Should -Not -Be $null + } + } + } + + Describe 'Date-specific Issue tests' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Date specific scenarios' { + $existingIssues = @($repo | Get-GitHubIssue -State All) + + $newIssues = @() + for ($i = 0; $i -lt 4; $i++) + { + $newIssues += New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + Start-Sleep -Seconds 1 # Needed to ensure that there is a unique creation timestamp between issues + } + + $newIssues[0] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed + $newIssues[-1] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed + + $existingOpenIssues = @($existingIssues | Where-Object { $_.state -eq 'open' }) + $newOpenIssues = @($newIssues | Where-Object { $_.state -eq 'open' }) + $issues = @($repo | Get-GitHubIssue) + It 'Should return only open issues' { + $issues.Count | Should -Be ($newOpenIssues.Count + $existingOpenIssues.Count) + } + + $issues = @($repo | Get-GitHubIssue -State All) + It 'Should return all issues' { + $issues.Count | Should -Be ($newIssues.Count + $existingIssues.Count) + } + + $createdOnOrAfterDate = Get-Date -Date $newIssues[0].created_at + $createdOnOrBeforeDate = Get-Date -Date $newIssues[2].created_at + $issues = @(($repo | Get-GitHubIssue) | + Where-Object { ($_.created_at -ge $createdOnOrAfterDate) -and ($_.created_at -le $createdOnOrBeforeDate) }) + + It 'Smart object date conversion works for comparing dates' { + $issues.Count | Should -Be 2 + } + + $createdDate = Get-Date -Date $newIssues[1].created_at + $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All | + Where-Object { ($_.created_at -ge $createdDate) -and ($_.state -eq 'closed') }) + + It 'Able to filter based on date and state' { + $issues.Count | Should -Be 1 + } + } + } + Describe 'Creating issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $milestone = $repo | New-GitHubMilestone -Title ([Guid]::NewGuid().Guid) + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Creating an Issue with parameters' { + $params = @{ + 'OwnerName' = $script:ownerName + 'RepositoryName' = $repo.name + 'Title' = '-issue title-' + 'Body' = '-issue body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $issue = New-GitHubIssue @params + + It 'Should have the expected property values' { + $issue.title | Should -Be $params.Title + $issue.body | Should -Be $params.Body + $issue.assignee.login | Should -Be $params.Assignee + $issue.assignees.Count | Should -Be 1 + $issue.assignees[0].login | Should -Be $params.Assignee + $issue.milestone.number | Should -Be $params.Milestone + $issue.labels.Count | Should -Be 1 + $issue.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $issue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $issue.RepositoryUrl | Should -Be $repo.RepositoryUrl + $issue.IssueId | Should -Be $issue.id + $issue.IssueNumber | Should -Be $issue.number + $issue.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $issue.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + + Context 'Creating an Issue with the repo on the pipeline' { + $params = @{ + 'Title' = '-issue title-' + 'Body' = '-issue body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $issue = $repo | New-GitHubIssue @params + + It 'Should have the expected property values' { + $issue.title | Should -Be $params.Title + $issue.body | Should -Be $params.Body + $issue.assignee.login | Should -Be $params.Assignee + $issue.assignees.Count | Should -Be 1 + $issue.assignees[0].login | Should -Be $params.Assignee + $issue.milestone.number | Should -Be $params.Milestone + $issue.labels.Count | Should -Be 1 + $issue.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $issue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $issue.RepositoryUrl | Should -Be $repo.RepositoryUrl + $issue.IssueId | Should -Be $issue.id + $issue.IssueNumber | Should -Be $issue.number + $issue.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $issue.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + } + + Describe 'Updating issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $milestone = $repo | New-GitHubMilestone -Title ([Guid]::NewGuid().Guid) + $title = 'issue title' + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Updating an Issue with parameters' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title $title + It 'Should have the expected property values' { + $issue.title | Should -Be $title + $issue.body | Should -BeNullOrEmpty + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + $issue.milestone | Should -BeNullOrEmpty + $issue.labels | Should -BeNullOrEmpty + } + + $params = @{ + 'OwnerName' = $script:ownerName + 'RepositoryName' = $repo.name + 'Issue' = $issue.number + 'Title' = '-new title-' + 'Body' = '-new body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $updated = Update-GitHubIssue @params + It 'Should have the expected property values' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.title | Should -Be $params.Title + $updated.body | Should -Be $params.Body + $updated.assignee.login | Should -Be $params.Assignee + $updated.assignees.Count | Should -Be 1 + $updated.assignees[0].login | Should -Be $params.Assignee + $updated.milestone.number | Should -Be $params.Milestone + $updated.labels.Count | Should -Be 1 + $updated.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.IssueId | Should -Be $updated.id + $updated.IssueNumber | Should -Be $updated.number + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $updated.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + + Context 'Updating an Issue with the repo on the pipeline' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title $title + It 'Should have the expected property values' { + $issue.title | Should -Be $title + $issue.body | Should -BeNullOrEmpty + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + $issue.milestone | Should -BeNullOrEmpty + $issue.labels | Should -BeNullOrEmpty + } + + $params = @{ + 'Issue' = $issue.number + 'Title' = '-new title-' + 'Body' = '-new body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $updated = $repo | Update-GitHubIssue @params + It 'Should have the expected property values' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.title | Should -Be $params.Title + $updated.body | Should -Be $params.Body + $updated.assignee.login | Should -Be $params.Assignee + $updated.assignees.Count | Should -Be 1 + $updated.assignees[0].login | Should -Be $params.Assignee + $updated.milestone.number | Should -Be $params.Milestone + $updated.labels.Count | Should -Be 1 + $updated.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.IssueId | Should -Be $updated.id + $updated.IssueNumber | Should -Be $updated.number + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $updated.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + + Context 'Updating an Issue with the issue on the pipeline' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title $title + It 'Should have the expected property values' { + $issue.title | Should -Be $title + $issue.body | Should -BeNullOrEmpty + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + $issue.milestone | Should -BeNullOrEmpty + $issue.labels | Should -BeNullOrEmpty + } + + $params = @{ + 'Title' = '-new title-' + 'Body' = '-new body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $updated = $issue | Update-GitHubIssue @params + It 'Should have the expected property values' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.title | Should -Be $params.Title + $updated.body | Should -Be $params.Body + $updated.assignee.login | Should -Be $params.Assignee + $updated.assignees.Count | Should -Be 1 + $updated.assignees[0].login | Should -Be $params.Assignee + $updated.milestone.number | Should -Be $params.Milestone + $updated.labels.Count | Should -Be 1 + $updated.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.IssueId | Should -Be $updated.id + $updated.IssueNumber | Should -Be $updated.number + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $updated.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + } + + Describe 'Locking and unlocking issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Locking and unlocking an Issue with parameters' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + It 'Should be unlocked' { + $issue.locked | Should -BeFalse + $issue.active_lock_reason | Should -BeNullOrEmpty + } + + $reason = 'Resolved' + Lock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number -Reason $reason + $updated = Get-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + It 'Should be locked' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeTrue + $updated.active_lock_reason | Should -Be $reason + } + + Unlock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + $updated = Get-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + It 'Should be unlocked again' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeFalse + $updated.active_lock_reason | Should -BeNullOrEmpty + } + } + + Context 'Locking and unlocking an Issue with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + It 'Should be unlocked' { + $issue.locked | Should -BeFalse + $issue.active_lock_reason | Should -BeNullOrEmpty + } + + $reason = 'Resolved' + $repo | Lock-GitHubIssue -Issue $issue.number -Reason $reason + $updated = $repo | Get-GitHubIssue -Issue $issue.number + It 'Should be locked' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeTrue + $updated.active_lock_reason | Should -Be $reason + } + + $repo | Unlock-GitHubIssue -Issue $issue.number + $updated = $repo | Get-GitHubIssue -Issue $issue.number + It 'Should be unlocked again' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeFalse + $updated.active_lock_reason | Should -BeNullOrEmpty + } + } + + Context 'Locking and unlocking an Issue with the issue on the pipeline' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + It 'Should be unlocked' { + $issue.locked | Should -BeFalse + $issue.active_lock_reason | Should -BeNullOrEmpty + } + + $reason = 'Resolved' + $issue | Lock-GitHubIssue -Reason $reason + $updated = $issue | Get-GitHubIssue + It 'Should be locked' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeTrue + $updated.active_lock_reason | Should -Be $reason + } + + $issue | Unlock-GitHubIssue + $updated = $issue | Get-GitHubIssue + It 'Should be unlocked again' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeFalse + $updated.active_lock_reason | Should -BeNullOrEmpty + } + } + } + + Describe 'Issue Timeline' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Getting the Issue timeline with parameters' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + $timeline = @(Get-GitHubIssueTimeline -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number) + It 'Should have no events so far' { + $timeline.Count | Should -Be 0 + } + + Lock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + $timeline = @(Get-GitHubIssueTimeline -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number) + It 'Should have an event now' { + $timeline.Count | Should -Be 1 + $timeline[0].event | Should -Be 'locked' + } + + It 'Should have the expected type and additional properties' { + $timeline[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $timeline[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $timeline[0].EventId | Should -Be $timeline[0].id + $timeline[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting the Issue timeline with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + $timeline = @($repo | Get-GitHubIssueTimeline -Issue $issue.number) + It 'Should have no events so far' { + $timeline.Count | Should -Be 0 + } + + $repo | Lock-GitHubIssue -Issue $issue.number + $timeline = @($repo | Get-GitHubIssueTimeline -Issue $issue.number) + It 'Should have an event now' { + $timeline.Count | Should -Be 1 + $timeline[0].event | Should -Be 'locked' + } + + It 'Should have the expected type and additional properties' { + $timeline[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $timeline[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $timeline[0].EventId | Should -Be $timeline[0].id + $timeline[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting the Issue timeline with the issue on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + $timeline = @($issue | Get-GitHubIssueTimeline) + It 'Should have no events so far' { + $timeline.Count | Should -Be 0 + } + + $issue | Lock-GitHubIssue + $timeline = @($issue | Get-GitHubIssueTimeline) + It 'Should have an event now' { + $timeline.Count | Should -Be 1 + $timeline[0].event | Should -Be 'locked' + } + + It 'Should have the expected type and additional properties' { + $timeline[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $timeline[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $timeline[0].EventId | Should -Be $timeline[0].id + $timeline[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index 1de571dc..e2807442 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -6,13 +6,18 @@ Tests for GitHubLabels.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - $defaultLabels = @( + $defaultLabels = @( @{ 'name' = 'pri:lowest' 'color' = '4285F4' @@ -71,255 +76,1271 @@ try } ) - if ($accessTokenConfigured) - { - Describe 'Getting labels from repository' { + Describe 'Getting labels from a repository' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + $repo = New-GitHubRepository -RepositoryName $repositoryName - Context 'When querying for all labels' { - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels + } - It 'Should return expected number of labels' { - $labels.Count | Should be $:defaultLabels.Count + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'When querying for all labels' { + $labels = @(Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName) + + It 'Should return expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } + } - Context 'When querying for specific label' { - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name bug + Context 'When querying for all labels (via repo on pipeline)' { + $labels = @($repo | Get-GitHubLabel) + + It 'Should return expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } - It 'Should return expected label' { - $label.name | Should be "bug" + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } + } + + Context 'When pipeline properties are disabled' { + BeforeAll { + Set-GitHubConfiguration -DisablePipelineSupport + $labels = @($repo | Get-GitHubLabel) + } + + AfterAll { + Set-GitHubConfiguration -DisablePipelineSupport:$false + } + + It 'Should return expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -BeNullOrEmpty + $label.LabelId | Should -BeNullOrEmpty + $label.LabelName | Should -BeNullOrEmpty + } + } } - Describe 'Creating new label' { + Context 'When querying for a specific label' { + $labelName = 'bug' + $label = Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelName + + It 'Should return expected label' { + $label.name | Should -Be $labelName + } + + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + Context 'When querying for a specific label (via repo on pipeline)' { + $labelName = 'bug' + $label = $repo | Get-GitHubLabel -Label $labelName + + It 'Should return expected label' { + $label.name | Should -Be $labelName + } + + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + # TODO: This test has been disabled until we can figure out how to fix the parameter sets + # for Get-GitHubLabel pipelining to still support Label this way. + # + # Context 'When querying for a specific label (via Label on pipeline)' { + # $labelName = 'bug' + # $label = $labelName | Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName + + # It 'Should return expected label' { + # $label.name | Should -Be $labelName + # } + + # It 'Should have the expected type and additional properties' { + # $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + # $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + # $label.LabelId | Should -Be $label.id + # $label.LabelName | Should -Be $label.name + # } + # } + } + + Describe 'Creating a new label' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + Context 'On a repo with parameters' { $labelName = [Guid]::NewGuid().Guid - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + $color = 'AAAAAA' + $label = New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelName -Color $color It 'New label should be created' { - $label.name | Should be $labelName + $label.name | Should -Be $labelName + $label.color | Should -Be $color } - AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + Context 'On a repo with and color starts with a #' { + $labelName = [Guid]::NewGuid().Guid + $color = '#AAAAAA' + $label = New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelName -Color $color + + It 'New label should be created' { + $label.name | Should -Be $labelName + $label.color | Should -Be $color.Substring(1) + $label.description | Should -BeNullOrEmpty } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } } - Describe 'Removing label' { - $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + Context 'On a repo with the repo on the pipeline' { + $labelName = [Guid]::NewGuid().Guid + $color = 'BBBBBB' + $description = 'test description' + $label = $repo | New-GitHubLabel -Label $labelName -Color $color -Description $description + It 'New label should be created' { + $label.name | Should -Be $labelName + $label.color | Should -Be $color + $label.description | Should -Be $description + } + + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + Context 'On a repo with the name on the pipeline' { $labelName = [Guid]::NewGuid().Guid - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + $color = 'CCCCCC' + $label = $labelName | New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Color $color - It 'Should return increased number of labels' { - $labels.Count | Should be ($defaultLabels.Count + 1) + It 'New label should be created' { + $label.name | Should -Be $labelName + $label.color | Should -Be $color } - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } - It 'Should return expected number of labels' { - $labels.Count | Should be $defaultLabels.Count + Context 'On a repo with three names on the pipeline' { + $labelNames = @(([Guid]::NewGuid().Guid), ([Guid]::NewGuid().Guid), ([Guid]::NewGuid().Guid)) + $color = 'CCCCCC' + $labels = @($labelNames | New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Color $color) + + It 'Has the right count of labels' { + $labels.Count | Should -Be $labelNames.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Has the right label details' { + foreach ($label in $labels) + { + $labelNames | Should -Contain $label.name + $label.color | Should -Be $color + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } + } - Describe 'Updating label' { + Describe 'Removing a label' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + } - $labelName = [Guid]::NewGuid().Guid + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Removing a label with parameters' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Force + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + + Context 'Removing a label with the repo on the pipeline' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + $repo | Remove-GitHubLabel -Label $label.name -Confirm:$false + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + + Context 'Removing a label with the name on the pipeline' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + $label.name | Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Force + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + + Context 'Removing a label with the label object on the pipeline' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + $label | Remove-GitHubLabel -Force + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + } + + Describe 'Updating a label' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Updating label color with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newColor = 'AAAAAA' + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor + + It 'Label should have different color' { + $result.name | Should -Be $label.name + $result.color | Should -Be $newColor + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } - Context 'Updating label color' { - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -NewName $labelName -Color AAAAAA - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + Context 'Updating label color (with #) with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' - AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false + $newColor = '#AAAAAA' + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor + + It 'Label should have different color' { + $result.name | Should -Be $label.name + $result.color | Should -Be $newColor.Substring(1) + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newName = [Guid]::NewGuid().Guid + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName + + It 'Label should have different name' { + $result.name | Should -Be $newName + $result.color | Should -Be $label.color + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label description with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' + + $newDescription = [Guid]::NewGuid().Guid + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Description $newDescription + + It 'Label should have different name' { + $result.name | Should -Be $label.name + $result.color | Should -Be $label.color + $result.description | Should -Be $newDescription + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name, color and description with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' + + $newName = [Guid]::NewGuid().Guid + $newColor = 'AAAAAA' + $newDescription = [Guid]::NewGuid().Guid + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName -Color $newColor -Description $newDescription + + It 'Label should have different everything' { + $result.name | Should -Be $newName + $result.color | Should -Be $newColor + $result.description | Should -Be $newDescription + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + + + } + + Context 'Updating label color with repo on the pipeline' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newColor = 'AAAAAA' + $result = $repo | Update-GitHubLabel -Label $label.name -Color $newColor + + It 'Label should have different color' { + $result.name | Should -Be $label.name + $result.color | Should -Be $newColor + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name with the label on the pipeline' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newName = [Guid]::NewGuid().Guid + $result = $label | Update-GitHubLabel -NewName $newName + + It 'Label should have different name' { + $result.name | Should -Be $newName + $result.color | Should -Be $label.color + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name, color and description with the label on the pipeline' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' + + $newName = [Guid]::NewGuid().Guid + $newColor = 'AAAAAA' + $newDescription = [Guid]::NewGuid().Guid + $result = $label | Update-GitHubLabel -NewName $newName -Color $newColor -Description $newDescription + + It 'Label should have different everything' { + $result.name | Should -Be $newName + $result.color | Should -Be $newColor + $result.description | Should -Be $newDescription + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + } + + Describe 'Initializing the labels on a repository' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Applying a default set of labels' { + Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels + + $labels = @($repo | Get-GitHubLabel) + + It 'Should return the expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } + + It 'Should have the right set of labels' { + foreach ($item in $defaultLabels) + { + $label = $labels | Where-Object { $_.name -eq $item.name } + $item.name | Should -Be $label.name + $item.color | Should -Be $label.color } + } + } - It 'Label should have different color' { - $label.color | Should be AAAAAA + Context 'Applying an overlapping set of labels' { + $newLabels = @( + @{ 'name' = $defaultLabels[0].name; 'color' = 'aaaaaa' }, + @{ 'name' = $defaultLabels[1].name; 'color' = 'bbbbbb' } + @{ 'name' = $defaultLabels[2].name; 'color' = $defaultLabels[2].color } + @{ 'name' = ([Guid]::NewGuid().Guid); 'color' = 'cccccc' } + @{ 'name' = ([Guid]::NewGuid().Guid); 'color' = 'dddddd' } + ) + + $originalLabels = @($repo | Get-GitHubLabel) + $null = $repo | Set-GitHubLabel -Label $newLabels + $labels = @($repo | Get-GitHubLabel) + + It 'Should return the expected number of labels' { + $labels.Count | Should -Be $newLabels.Count + } + + It 'Should have the right set of labels' { + foreach ($item in $newLabels) + { + $label = $labels | Where-Object { $_.name -eq $item.name } + $item.name | Should -Be $label.name + $item.color | Should -Be $label.color } } - Context 'Updating label name' { - $newLabelName = $labelName + "2" - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -NewName $newLabelName -Color BBBBBB - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName + It 'Should have retained the ID''s of the pre-existing labels' { + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[0].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[0].name } + $label.id | Should -Be $originalLabel.id + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[1].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[1].name } + $label.id | Should -Be $originalLabel.id + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[2].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[2].name } + $label.id | Should -Be $originalLabel.id + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[3].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[3].name } + $originalLabel | Should -BeNullOrEmpty + $label | Should -Not -BeNullOrEmpty + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[4].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[4].name } + $originalLabel | Should -BeNullOrEmpty + $label | Should -Not -BeNullOrEmpty + } + } + + } - AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName -Confirm:$false + Describe 'Adding labels to an issue' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Adding labels to an issue' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @(Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $expectedLabels) + + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } + + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label } + } - It 'Label should have different color' { - $label | Should not be $null - $label.color | Should be BBBBBB + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + $issueLabels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } - Describe 'Applying set of labels on repository' { - $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + Context 'Adding labels to an issue with the repo on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @($repo | Add-GitHubIssueLabel -Issue $issue.number -LabelName $expectedLabels) - $labelName = [Guid]::NewGuid().Guid - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } - # Add new label - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } - # Change color of existing label - Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -NewName "bug" -Color BBBBBB + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } - # Remove one of approved labels" - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Confirm:$false + $issueLabels = $repo | Get-GitHubLabel -Issue $issue.number - It 'Should return increased number of labels' { - $($labels).Count | Should be ($defaultLabels.Count + 1) + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count } - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } - It 'Should return expected number of labels' { - $labels.Count | Should be $defaultLabels.Count - $bugLabel = $labels | Where-Object {$_.name -eq "bug"} - $bugLabel.color | Should be "fc2929" + Context 'Adding labels to an issue with the issue on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @($issue | Add-GitHubIssueLabel -LabelName $expectedLabels) + + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + $issueLabels = $issue | Get-GitHubLabel + + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } - Describe 'Adding labels to an issue'{ - $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + Context 'Adding labels to an issue with the label names on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @($expectedLabels | Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number) - $issueName = [Guid]::NewGuid().Guid - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } - Context 'Adding labels to an issue' { - $labelsToAdd = @('pri:lowest', 'pri:low', 'pri:medium', 'pri:high', 'pri:highest', 'bug', 'duplicate', - 'enhancement', 'up for grabs', 'question', 'discussion', 'wontfix', 'in progress', 'ready') - $addedLabels = @(Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd) + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } - It 'Should return the number of labels that were just added' { - $addedLabels.Count | Should be $defaultLabels.Count + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } + } + + $issueLabels = $issue | Get-GitHubLabel - $labelIssues = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } - It 'Should return the number of labels that were just added from querying the issue again' { - $labelIssues.Count | Should be $defaultLabels.Count + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } + } + + Context 'Adding labels to an issue with the label object on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $labels = @($expectedLabels | ForEach-Object { Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $_ } ) + $result = @($labels | Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number) - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } + + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + $issueLabels = $issue | Get-GitHubLabel + + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } + } - Describe 'Creating a new Issue with labels' { + Describe 'Creating a new Issue with labels' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'When creating a new issue using parameters' { + $issueName = [Guid]::NewGuid().Guid + $issueLabels = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repositoryName -Title $issueName -Label $issueLabels + + It 'Should return the number of labels that were just added' { + $issue.labels.Count | Should -Be $issueLabels.Count + } + It 'Should be the right set of labels' { + foreach ($label in $issueLabels) + { + $issue.labels.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issue.labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + + Context 'When creating a new issue using the repo on the pipeline' { $issueName = [Guid]::NewGuid().Guid $issueLabels = @($defaultLabels[0].name, $defaultLabels[1].name) - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName -Label $issueLabels + $issue = $repo | New-GitHubIssue -Title $issueName -Label $issueLabels It 'Should return the number of labels that were just added' { - $issue.labels.Count | Should be $issueLabels.Count + $issue.labels.Count | Should -Be $issueLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should be the right set of labels' { + foreach ($label in $issueLabels) + { + $issue.labels.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issue.labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } + } - Describe 'Removing labels on an issue'{ + Describe 'Removing labels on an issue' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'For removing an individual issue with parameters' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + # Doing this manually instead of in a loop to try out different combinations of -Confirm:$false and -Force + Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelsToAdd[0] -Issue $issue.number -Confirm:$false + Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelsToAdd[1] -Issue $issue.number -Force + Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelsToAdd[2] -Issue $issue.number -Confirm:$false -Force + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed all labels from the issue' { + $issueLabels.Count | Should -Be 0 + } + } + Context 'For removing an individual issue using the repo on the pipeline' { $issueName = [Guid]::NewGuid().Guid - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName + $issue = $repo | New-GitHubIssue -Title $issueName - $labelsToAdd = @('pri:lowest', 'pri:low', 'pri:medium', 'pri:high', 'pri:highest', 'bug', 'duplicate', - 'enhancement', 'up for grabs', 'question', 'discussion', 'wontfix', 'in progress', 'ready') - Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd - Context 'For removing individual issues'{ - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Issue $issue.number -Confirm:$false - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "question" -Issue $issue.number -Force - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -Issue $issue.number -Confirm:$false -Force - $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $labelToRemove = $labelsToAdd[0] + $repo | Remove-GitHubIssueLabel -Label $labelToRemove -Issue $issue.number -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed the expected label from the issue' { + $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + $issueLabels.name | Should -Not -Contain $labelToRemove + } + } + + Context 'For removing an individual issue using the issue on the pipeline' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $labelToRemove = $labelsToAdd[1] + $issue | Remove-GitHubIssueLabel -Label $labelToRemove -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed the expected label from the issue' { + $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + $issueLabels.name | Should -Not -Contain $labelToRemove + } + } + + # TODO: This has been disabled for now, as ValueFromPipeline has been disabled until we + # sort out some complication issues with the ParameterSets + # + # Context 'For removing an individual issue using the label name on the pipeline' { + # $issueName = [Guid]::NewGuid().Guid + # $issue = $repo | New-GitHubIssue -Title $issueName + + # $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + # $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + # $issueLabels = @($issue | Get-GitHubLabel) + # It 'Should have the expected number of labels' { + # $issueLabels.Count | Should -Be $labelsToAdd.Count + # } + + # $labelToRemove = $labelsToAdd[2] + # $labelToRemove | Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number -Confirm:$false + + # $issueLabels = @($issue | Get-GitHubLabel) + # It 'Should have removed the expected label from the issue' { + # $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + # $issueLabels.name | Should -Not -Contain $labelToRemove + # } + # } + + Context 'For removing an individual issue using the label object on the pipeline' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } - It 'Should have removed three labels from the issue' { - $labelIssues.Count | Should be ($defaultLabels.Count - 3) + $labelToRemove = $labelsToAdd[0] + $label = $repo | Get-GitHubLabel -Label $labelToRemove + $label | Remove-GitHubIssueLabel -Issue $issue.number -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed the expected label from the issue' { + $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + $issueLabels.name | Should -Not -Contain $labelToRemove + } + } + + Context 'For removing all issues' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $issue | Remove-GitHubIssueLabel -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed all labels from the issue' { + $issueLabels.Count | Should -Be 0 + } + } + + Context 'For removing all issues using Set-GitHubIssueLabel with the Issue on the pipeline' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $issue | Set-GitHubIssueLabel -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed all labels from the issue' { + $issueLabels.Count | Should -Be 0 + } + } + } + + Describe 'Replacing labels on an issue' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Change the set of labels with parameters' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label + } + } + + $newIssueLabels = @($defaultLabels[0].name, $defaultLabels[5].name) + $result = @(Set-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number -Label $newIssueLabels) + + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabels.Count + foreach ($label in $newIssueLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + + Context 'Change the set of labels with the repo on the pipeline' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label } } - Context 'For removing all issues'{ - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -Confirm:$false - $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) + $newIssueLabels = @($defaultLabels[0].name, $defaultLabels[5].name) + $result = @($repo | Set-GitHubIssueLabel -Issue $issue.number -Label $newIssueLabels) - It 'Should have removed all labels from the issue' { - $labelIssues.Count | Should be 0 + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabels.Count + foreach ($label in $newIssueLabels) + { + $result.name | Should -Contain $label } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } - Describe 'Replacing labels on an issue'{ + Context 'Change the set of labels with the issue on the pipeline' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label + } + } + + $newIssueLabels = @($defaultLabels[0].name, $defaultLabels[5].name) + $result = @($issue | Set-GitHubIssueLabel -Label $newIssueLabels) + + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabels.Count + foreach ($label in $newIssueLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + + Context 'Change the set of labels with parameters with the labels on the pipeline' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label + } + } + + $newIssueLabelNames = @($defaultLabels[0].name, $defaultLabels[5].name) + $issueLabels = @($newIssueLabelNames | ForEach-Object { $repo | Get-GitHubLabel -Label $_ }) + $result = @($issueLabels | Set-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number) + + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabelNames.Count + foreach ($label in $newIssueLabelNames) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + } + + Describe 'Labels and Milestones' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels - $issueName = [Guid]::NewGuid().Guid - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName + $milestone = $repo | New-GitHubMilestone -Title 'test milestone' + + $issueLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $milestone | New-GitHubIssue -Title 'test issue' -Label $issueLabels + + $issueLabels2 = @($defaultLabels[4].name, $defaultLabels[5].name) + $issue2 = $milestone | New-GitHubIssue -Title 'test issue' -Label $issueLabels2 + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Getting labels for issues in a milestone with parameters' { + It 'Should return the number of labels that were just added to the issue' { + $issue.labels.Count | Should -Be $issueLabels.Count + $issue2.labels.Count | Should -Be $issueLabels2.Count + } + + $milestoneLabels = Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Milestone $milestone.number + + It 'Should return the same number of labels in the issue that were assigned to the milestone' { + $milestoneLabels.Count | Should -Be ($issue.labels.Count + $issue2.labels.Count) + } + + It 'Should be the right set of labels' { + $allLabels = $issue.labels.name + $issue2.labels.name + foreach ($label in $allLabels) + { + $milestoneLabels.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $milestoneLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } - $labelsToAdd = @('pri:lowest', 'pri:low', 'pri:medium', 'pri:high', 'pri:highest', 'bug', 'duplicate', - 'enhancement', 'up for grabs', 'question', 'discussion', 'wontfix', 'in progress', 'ready') + Context 'Getting labels for issues in a milestone with the repo on the pipeline' { + It 'Should return the number of labels that were just added to the issue' { + $issue.labels.Count | Should -Be $issueLabels.Count + $issue2.labels.Count | Should -Be $issueLabels2.Count + } - Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName 'pri:medium' + $milestoneLabels = $repo | Get-GitHubLabel -Milestone $milestone.number - $addedLabels = @(Set-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd) + It 'Should return the same number of labels in the issues that were assigned to the milestone' { + $milestoneLabels.Count | Should -Be ($issue.labels.Count + $issue2.labels.Count) + } - It 'Should return the issue with 14 labels' { - $addedLabels.Count | Should be $labelsToAdd.Count + It 'Should be the right set of labels' { + $allLabels = $issue.labels.name + $issue2.labels.name + foreach ($label in $allLabels) + { + $milestoneLabels.name | Should -Contain $label + } } - $labelIssues = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + It 'Should have the expected type and additional properties' { + foreach ($label in $milestoneLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } - It 'Should have 14 labels after querying the issue' { - $labelIssues.Count | Should be $defaultLabels.Count + Context 'Getting labels for issues in a milestone on the pipeline' { + It 'Should return the number of labels that were just added to the issue' { + $issue.labels.Count | Should -Be $issueLabels.Count + $issue2.labels.Count | Should -Be $issueLabels2.Count } - $updatedIssueLabels = $labelsToAdd[0] - $updatedIssue = Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -Label $updatedIssueLabels + $milestoneLabels = $milestone | Get-GitHubLabel + + It 'Should return the same number of labels in the issue that is assigned to the milestone' { + $milestoneLabels.Count | Should -Be ($issue.labels.Count + $issue2.labels.Count) + } - It 'Should have 1 label after updating the issue' { - $updatedIssue.labels.Count | Should be $updatedIssueLabels.Count + It 'Should be the right set of labels' { + $allLabels = $issue.labels.name + $issue2.labels.name + foreach ($label in $allLabels) + { + $milestoneLabels.name | Should -Contain $label + } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + foreach ($label in $milestoneLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } } } diff --git a/Tests/GitHubMilestones.tests.ps1 b/Tests/GitHubMilestones.tests.ps1 index db9ab8cf..048e5dac 100644 --- a/Tests/GitHubMilestones.tests.ps1 +++ b/Tests/GitHubMilestones.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubMilestones.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -14,116 +19,400 @@ try { # Define Script-scoped, readonly, hidden variables. @{ - defaultIssueTitle = "This is a test issue." - defaultMilestoneTitle1 = "This is a test milestone title #1." - defaultMilestoneTitle2 = "This is a test milestone title #2." - defaultMilestoneTitle3 = "This is a test milestone title #3." - defaultMilestoneTitle4 = "This is a test milestone title #4." - defaultEditedMilestoneTitle = "This is an edited milestone title." - defaultMilestoneDescription = "This is a test milestone description." - defaultEditedMilestoneDescription = "This is an edited milestone description." defaultMilestoneDueOn = (Get-Date).AddYears(1).ToUniversalTime() }.GetEnumerator() | ForEach-Object { Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value } - Describe 'Creating, modifying and deleting milestones' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $issue = New-GitHubIssue -Uri $repo.svn_url -Title $defaultIssueTitle + Describe 'Creating a milestone' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - Context 'For creating a new milestone' { - $newMilestone = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle1 -State "Closed" -DueOn $defaultMilestoneDueOn - $existingMilestone = Get-GitHubMilestone -Uri $repo.svn_url -Milestone $newMilestone.number + $commonParams = @{ + 'State' = 'Closed' + 'DueOn' = $script:defaultMilestoneDueOn + 'Description' = 'Milestone description' + } - # We'll be testing to make sure that regardless of the time in the timestamp, we'll get the desired date. - $newMilestoneDueOnEarlyMorning = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle2 -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(1) - $newMilestoneDueOnLateEvening = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle3 -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(23) + $title = 'Milestone title' + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Using the parameter' { + BeforeAll { + $milestone = New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Title $title @commonParams + } + + AfterAll { + $milestone | Remove-GitHubMilestone -Force + } + + $returned = Get-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Milestone $milestone.MilestoneNumber + + It 'Should exist' { + $returned.id | Should -Be $milestone.id + } + + It 'Should have the correct creation properties' { + $milestone.title | Should -Be $title + $milestone.state | Should -Be $commonParams['State'] + $milestone.description | Should -Be $commonParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $milestone.due_on).Date | Should -Be $commonParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $milestone.RepositoryUrl | Should -Be $repo.RepositoryUrl + $milestone.MilestoneId | Should -Be $milestone.id + $milestone.MilestoneNumber | Should -Be $milestone.number + $milestone.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Using the pipeline for the repo' { + BeforeAll { + $milestone = $repo | New-GitHubMilestone -Title $title @commonParams + } + + AfterAll { + $milestone | Remove-GitHubMilestone -Force + } + + $returned = $milestone | Get-GitHubMilestone + + It 'Should exist' { + $returned.id | Should -Be $milestone.id + } + + It 'Should have the correct creation properties' { + $milestone.title | Should -Be $title + $milestone.state | Should -Be $commonParams['State'] + $milestone.description | Should -Be $commonParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $milestone.due_on).Date | Should -Be $commonParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $milestone.RepositoryUrl | Should -Be $repo.RepositoryUrl + $milestone.MilestoneId | Should -Be $milestone.id + $milestone.MilestoneNumber | Should -Be $milestone.number + $milestone.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } - It "Should have the expected title text" { - $existingMilestone.title | Should be $defaultMilestoneTitle1 + Context 'Using the pipeline for the title' { + BeforeAll { + $milestone = $title | New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name @commonParams } - It "Should have the expected state" { - $existingMilestone.state | Should be "closed" + AfterAll { + $milestone | Remove-GitHubMilestone -Force } - It "Should have the expected due_on date" { + $returned = $repo | Get-GitHubMilestone -Milestone $milestone.MilestoneNumber + + It 'Should exist' { + $returned.id | Should -Be $milestone.id + } + + It 'Should have the correct creation properties' { + $milestone.title | Should -Be $title + $milestone.state | Should -Be $commonParams['State'] + $milestone.description | Should -Be $commonParams['Description'] + # GitHub drops the time that is attached to 'due_on', so it's only relevant # to compare the dates against each other. - (Get-Date -Date $existingMilestone.due_on).Date | Should be $defaultMilestoneDueOn.Date + (Get-Date -Date $milestone.due_on).Date | Should -Be $commonParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $milestone.RepositoryUrl | Should -Be $repo.RepositoryUrl + $milestone.MilestoneId | Should -Be $milestone.id + $milestone.MilestoneNumber | Should -Be $milestone.number + $milestone.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } + } + + Context 'That is due at different times of the day' { + # We'll be testing to make sure that regardless of the time in the timestamp, we'll get the desired date. + $title = 'Milestone title' It "Should have the expected due_on date even if early morning" { + $milestone = $repo | New-GitHubMilestone -Title 'Due early in the morning' -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(1) + # GitHub drops the time that is attached to 'due_on', so it's only relevant # to compare the dates against each other. - (Get-Date -Date $newMilestoneDueOnEarlyMorning.due_on).Date | Should be $defaultMilestoneDueOn.Date + (Get-Date -Date $milestone.due_on).Date | Should -Be $defaultMilestoneDueOn.Date } It "Should have the expected due_on date even if late evening" { + $milestone = $repo | New-GitHubMilestone -Title 'Due late in the evening' -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(23) + # GitHub drops the time that is attached to 'due_on', so it's only relevant # to compare the dates against each other. - (Get-Date -Date $newMilestoneDueOnLateEvening.due_on).Date | Should be $defaultMilestoneDueOn.Date + (Get-Date -Date $milestone.due_on).Date | Should -Be $defaultMilestoneDueOn.Date } + } + } - It "Should allow the addition of an existing issue" { - Update-GitHubIssue -Uri $repo.svn_url -Issue $issue.number -Milestone $existingMilestone.number - } + Describe 'Associating milestones with issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $milestone = $repo | New-GitHubMilestone -Title 'Milestone Title' + $issue = $repo | New-GitHubIssue -Title 'Issue Title' + } + + AfterAll { + $repo | Remove-GitHubRepository -Force } - Context 'For getting milestones from a repo' { - $existingMilestones =@(Get-GitHubMilestone -Uri $repo.svn_url -State Closed) + Context 'Adding milestone to an issue' { + It 'Should not have any open issues associated with it' { + $issue.milestone | Should -BeNullOrEmpty + $milestone.open_issues | Should -Be 0 + } + + $issue = $issue | Update-GitHubIssue -Milestone $milestone.MilestoneNumber + $milestone = $milestone | Get-GitHubMilestone + It "Should be associated to the milestone now" { + $issue.milestone.number | Should -Be $milestone.MilestoneNumber + $milestone.open_issues | Should -Be 1 + } + + $issue = $issue | Update-GitHubIssue -Milestone 0 + $milestone = $milestone | Get-GitHubMilestone + It 'Should no longer be associated to the milestone' { + $issue.milestone | Should -BeNullOrEmpty + $milestone.open_issues | Should -Be 0 + } + + $issue = $issue | Update-GitHubIssue -Milestone $milestone.MilestoneNumber + $milestone = $milestone | Get-GitHubMilestone + It "Should be associated to the milestone again" { + $issue.milestone.number | Should -Be $milestone.MilestoneNumber + $milestone.open_issues | Should -Be 1 + } + + $milestone | Remove-GitHubMilestone -Force $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + It 'Should have removed the association when the milestone was deleted' { + $issue.milestone | Should -BeNullOrEmpty + } + } + } + + Describe 'Getting milestones' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $title = 'Milestone title' + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Getting a specific milestone' { + BeforeAll { + $closedMilestone = 'C' | New-GitHubMilestone -Uri $repo.RepositoryUrl -State 'Closed' + $openMilestone = 'O' | New-GitHubMilestone -Uri $repo.RepositoryUrl -State 'Open' + } + + AfterAll { + $closedMilestone | Remove-GitHubMilestone -Force + $openMilestone | Remove-GitHubMilestone -Force + } + + $milestone = $closedMilestone + $returned = Get-GitHubMilestone -Uri $repo.RepositoryUrl -Milestone $milestone.MilestoneNumber + It 'Should get the right milestone as a parameter' { + $returned.MilestoneId | Should -Be $milestone.MilestoneId + } + + It 'Should have the expected type and additional properties' { + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $returned.RepositoryUrl | Should -Be $repo.RepositoryUrl + $returned.MilestoneId | Should -Be $returned.id + $returned.MilestoneNumber | Should -Be $returned.number + $returned.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $milestone = $openMilestone + $returned = $openMilestone | Get-GitHubMilestone + It 'Should get the right milestone via the pipeline' { + $returned.MilestoneId | Should -Be $milestone.MilestoneId + } + + It 'Should have the expected type and additional properties' { + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $returned.RepositoryUrl | Should -Be $repo.RepositoryUrl + $returned.MilestoneId | Should -Be $returned.id + $returned.MilestoneNumber | Should -Be $returned.number + $returned.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting multiple milestones' { + BeforeAll { + $today = (Get-Date).ToUniversalTime() + $nextWeek = (Get-Date).AddDays(7).ToUniversalTime() + $numClosedMilestones = 3 + $numOpenMilestones = 4 + $closed = 1..$numClosedMilestones | ForEach-Object { $repo | New-GitHubMilestone -Title "Closed $_" -State 'Closed' -DueOn $today } + $open = 1..$numOpenMilestones | ForEach-Object { $repo | New-GitHubMilestone -Title "Open $_" -State 'Open' -DueOn $nextWeek } + } + + AfterAll { + $closed | Remove-GitHubMilestone -Force + $open | Remove-GitHubMilestone -Force + } It 'Should have the expected number of milestones' { - $existingMilestones.Count | Should be 3 + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'All') + $milestones.Count | Should -Be ($numClosedMilestones + $numOpenMilestones) + } + + It 'Should have the expected number of open milestones' { + $milestones = @($repo | Get-GitHubMilestone -State 'Open') + $milestones.Count | Should -Be $numOpenMilestones + } + + It 'Should have the expected number of closed milestones' { + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'Closed') + $milestones.Count | Should -Be $numClosedMilestones + } + + It 'Should sort them the right way | DueOn, Descending' { + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'All' -Sort 'DueOn' -Direction 'Descending') + $milestones[0].state | Should -Be 'Open' } - It 'Should have the expected title text on the first milestone' { - $existingMilestones[0].title | Should be $defaultMilestoneTitle1 + It 'Should sort them the right way | DueOn, Ascending' { + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'All' -Sort 'DueOn' -Direction 'Ascending') + $milestones[0].state | Should -Be 'Closed' + } + } + } + + Describe 'Editing a milestone' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + + $createParams = @{ + 'Title' = 'Created Title' + 'State' = 'Open' + 'Description' = 'Created Description' + 'DueOn' = (Get-Date).ToUniversalTime() } - It 'Should have the expected issue in the first milestone' { - $existingMilestones[0].open_issues | should be 1 - $issue.milestone.number | Should be 1 + $editParams = @{ + 'Title' = 'Edited Title' + 'State' = 'Closed' + 'Description' = 'Edited Description' + 'DueOn' = (Get-Date).AddDays(7).ToUniversalTime() } } - Context 'For editing a milestone' { - $newMilestone = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle4 -Description $defaultMilestoneDescription - $editedMilestone = Set-GitHubMilestone -Uri $repo.svn_url -Milestone $newMilestone.number -Title $defaultEditedMilestoneTitle -Description $defaultEditedMilestoneDescription + AfterAll { + $repo | Remove-GitHubRepository -Force + } - It 'Should have a title/description that is not equal to the original title/description' { - $editedMilestone.title | Should not be $newMilestone.title - $editedMilestone.description | Should not be $newMilestone.description + Context 'Using the parameter' { + BeforeAll { + $milestone = New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name @createParams + $edited = Set-GitHubMilestone -Uri $milestone.RepositoryUrl -Milestone $milestone.MilestoneNumber @editParams } - It 'Should have the edited content' { - $editedMilestone.title | Should be $defaultEditedMilestoneTitle - $editedMilestone.description | Should be $defaultEditedMilestoneDescription + AfterAll { + $milestone | Remove-GitHubMilestone -Force + } + + It 'Should be editable via the parameter' { + $edited.id | Should -Be $milestone.id + $edited.title | Should -Be $editParams['Title'] + $edited.state | Should -Be $editParams['State'] + $edited.description | Should -Be $editParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $edited.due_on).Date | Should -Be $editParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $edited.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $edited.RepositoryUrl | Should -Be $repo.RepositoryUrl + $edited.MilestoneId | Should -Be $milestone.id + $edited.MilestoneNumber | Should -Be $milestone.number + $edited.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'For getting milestones from a repository and deleting them' { - $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State All -Sort Completeness -Direction Descending) + Context 'Using the pipeline' { + BeforeAll { + $milestone = New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name @createParams + $edited = $milestone | Set-GitHubMilestone @editParams + } - It 'Should have the expected number of milestones' { - $existingMilestones.Count | Should be 4 + AfterAll { + $milestone | Remove-GitHubMilestone -Force } - foreach($milestone in $existingMilestones) { - Remove-GitHubMilestone -Uri $repo.svn_url -Milestone $milestone.number -Confirm:$false + It 'Should be editable via the pipeline' { + $edited.id | Should -Be $milestone.id + $edited.title | Should -Be $editParams['Title'] + $edited.state | Should -Be $editParams['State'] + $edited.description | Should -Be $editParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $edited.due_on).Date | Should -Be $editParams['DueOn'].Date } - $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State All) - $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + It 'Should have the expected type and additional properties' { + $edited.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $edited.RepositoryUrl | Should -Be $repo.RepositoryUrl + $edited.MilestoneId | Should -Be $milestone.id + $edited.MilestoneNumber | Should -Be $milestone.number + $edited.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } - It 'Should have no milestones' { - $existingMilestones.Count | Should be 0 - $issue.milestone | Should be $null + Describe 'Deleting a milestone' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Using the parameter' { + $milestone = $repo | New-GitHubMilestone -Title 'Milestone title' -State "Closed" -DueOn $defaultMilestoneDueOn + Remove-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Milestone $milestone.MilestoneNumber -Force + + It 'Should be deleted' { + { Get-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Milestone $milestone.MilestoneNumber } | Should -Throw } } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Context 'Using the pipeline' { + $milestone = $repo | New-GitHubMilestone -Title 'Milestone title' -State "Closed" -DueOn $defaultMilestoneDueOn + $milestone | Remove-GitHubMilestone -Force + + It 'Should be deleted' { + { $milestone | Get-GitHubMilestone } | Should -Throw + } + } } } finally diff --git a/Tests/GitHubMiscellaneous.tests.ps1 b/Tests/GitHubMiscellaneous.tests.ps1 new file mode 100644 index 00000000..7be6eb96 --- /dev/null +++ b/Tests/GitHubMiscellaneous.tests.ps1 @@ -0,0 +1,352 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubMiscellaneous.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +Describe 'Get-GitHubRateLimit' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Is working' { + BeforeAll { + $result = Get-GitHubRateLimit + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.RateLimit' + } + } +} + +Describe 'ConvertFrom-GitHubMarkdown' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Works with the parameter' { + BeforeAll { + $markdown = '**PowerShellForGitHub**' + $expectedHtml = '

PowerShellForGitHub

' + } + + It 'Has the expected result as a parameter' { + $result = ConvertFrom-GitHubMarkdown -Content $markdown + + # Replace newlines with empty for comparison purposes + $result.Replace("`n", "").Replace("`r", "") | Should -Be $expectedHtml + } + + It 'Has the expected result with the pipeline' { + $result = $markdown | ConvertFrom-GitHubMarkdown + + # Replace newlines with empty for comparison purposes + $result.Replace("`n", "").Replace("`r", "") | Should -Be $expectedHtml + } + } +} + +Describe 'Get-GitHubLicense' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Can get the license for a repo with parameters' { + BeforeAll { + $result = Get-GitHubLicense -OwnerName 'PowerShell' -RepositoryName 'PowerShell' + } + + It 'Has the expected result' { + $result.license.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $result.LicenseKey | Should -Be $result.license.key + $result.license.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + } + } + + Context 'Can get the license for a repo with the repo on the pipeline' { + BeforeAll { + $result = Get-GitHubRepository -OwnerName 'PowerShell' -RepositoryName 'PowerShell' | Get-GitHubLicense + } + + It 'Has the expected result' { + $result.license.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $result.LicenseKey | Should -Be $result.license.key + $result.license.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + } + } + + Context 'Can get all of the licenses' { + BeforeAll { + $results = @(Get-GitHubLicense) + } + + It 'Has the expected result' { + # The number of licenses on GitHub is unlikely to remain static. + # Let's just make sure that we have a few results + $results.Count | Should -BeGreaterThan 3 + } + + It 'Has the expected type and additional properties' { + foreach ($license in $results) + { + $license.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + $license.LicenseKey | Should -Be $license.key + } + } + } + + Context 'Can get a specific license' { + BeforeAll { + $result = Get-GitHubLicense -Key 'mit' + $again = $result | Get-GitHubLicense + } + + It 'Has the expected result' { + $result.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + $result.LicenseKey | Should -Be $result.key + } + + It 'Has the expected result' { + $again.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $again.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + $again.LicenseKey | Should -Be $again.key + } + } +} + +Describe 'Get-GitHubEmoji' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Is working' { + BeforeAll { + $result = Get-GitHubEmoji + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Emoji' + } + } +} + +Describe 'Get-GitHubCodeOfConduct' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Can get the code of conduct for a repo with parameters' { + BeforeAll { + $result = Get-GitHubCodeOfConduct -OwnerName 'PowerShell' -RepositoryName 'PowerShell' + } + + It 'Has the expected result' { + $result.key | Should -Be 'other' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $result.CodeOfConductKey | Should -Be $result.key + } + } + + Context 'Can get the code of conduct for a repo with the repo on the pipeline' { + BeforeAll { + $result = Get-GitHubRepository -OwnerName 'PowerShell' -RepositoryName 'PowerShell' | Get-GitHubCodeOfConduct + } + + It 'Has the expected result' { + $result.key | Should -Be 'other' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $result.CodeOfConductKey | Should -Be $result.key + } + } + + Context 'Can get all of the codes of conduct' { + BeforeAll { + $results = @(Get-GitHubCodeOfConduct) + } + + It 'Has the expected results' { + # The number of codes of conduct on GitHub is unlikely to remain static. + # Let's just make sure that we have a couple results + $results.Count | Should -BeGreaterOrEqual 2 + } + + It 'Has the expected type and additional properties' { + foreach ($item in $results) + { + $item.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $item.CodeOfConductKey | Should -Be $item.key + } + } + } + + Context 'Can get a specific code of conduct' { + BeforeAll { + $key = 'contributor_covenant' + $result = Get-GitHubCodeOfConduct -Key $key + $again = $result | Get-GitHubCodeOfConduct + } + + It 'Has the expected result' { + $result.key | Should -Be $key + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $result.CodeOfConductKey | Should -Be $result.key + } + + It 'Has the expected result' { + $again.key | Should -Be $key + } + + It 'Has the expected type and additional properties' { + $again.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $again.CodeOfConductKey | Should -Be $again.key + } + } +} + +Describe 'Get-GitHubGitIgnore' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Gets all the known .gitignore files' { + BeforeAll { + $result = Get-GitHubGitIgnore + } + + It 'Has the expected values' { + # The number of .gitignore files on GitHub is unlikely to remain static. + # Let's just make sure that we have a bunch of results + $result.Count | Should -BeGreaterOrEqual 5 + } + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Gitignore' + } + } + + Context 'Gets a specific one via parameter' { + BeforeAll { + $name = 'C' + $result = Get-GitHubGitIgnore -Name $name + } + + It 'Has the expected value' { + $result.name | Should -Be $name + $result.source | Should -Not -BeNullOrEmpty + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Gitignore' + } + } + + Context 'Gets a specific one via the pipeline' { + BeforeAll { + $name = 'C' + $result = $name | Get-GitHubGitIgnore + } + + It 'Has the expected value' { + $result.name | Should -Be $name + $result.source | Should -Not -BeNullOrEmpty + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Gitignore' + } + } + + Context 'Gets a specific one as raw content via the pipeline' { + BeforeAll { + $name = 'C' + $result = $name | Get-GitHubGitIgnore -RawContent + } + + It 'Has the expected value' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Gitignore' + } + } +} diff --git a/Tests/GitHubOrganizations.tests.ps1 b/Tests/GitHubOrganizations.tests.ps1 new file mode 100644 index 00000000..42bc547b --- /dev/null +++ b/Tests/GitHubOrganizations.tests.ps1 @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubOrganizations.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # TODO once more capabilities exist in the module's API set + + # TODO: Re-enable these tests once the module has sufficient support getting the Organization + # and repository into the required state for testing, and to recover back to the original state + # at the conclusion of the test. + + # Describe 'Obtaining organization members' { + # $members = Get-GitHubOrganizationMember -OrganizationName $script:organizationName + + # It 'Should return expected number of organization members' { + # @($members).Count | Should -Be 1 + # } + # } + + # Describe 'Obtaining organization teams' { + # $teams = Get-GitHubTeam -OrganizationName $script:organizationName + + # It 'Should return expected number of organization teams' { + # @($teams).Count | Should -Be 2 + # } + # } + + # Describe 'Obtaining organization team members' { + # $members = Get-GitHubTeamMember -OrganizationName $script:organizationName -TeamName $script:organizationTeamName + + # It 'Should return expected number of organization team members' { + # @($members).Count | Should -Be 1 + # } + # } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubProjectCards.tests.ps1 b/Tests/GitHubProjectCards.tests.ps1 index 7875f4e7..54d80ec3 100644 --- a/Tests/GitHubProjectCards.tests.ps1 +++ b/Tests/GitHubProjectCards.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubProjectCards.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -29,10 +34,10 @@ try } $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $project = New-GitHubProject -Owner $script:ownerName -Repository $repo.name -Name $defaultProject + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultProject - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - $columntwo = New-GitHubProjectColumn -Project $project.id -Name $defaultColumnTwo + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn + $columntwo = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumnTwo $issue = New-GitHubIssue -Owner $script:ownerName -RepositoryName $repo.name -Title $defaultIssue @@ -41,10 +46,6 @@ try $card = New-GitHubProjectCard -Column $column.id -Note $defaultCard $cardArchived = New-GitHubProjectCard -Column $column.id -Note $defaultArchivedCard $null = Set-GitHubProjectCard -Card $cardArchived.id -Archive - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card - $cardArchived = $cardArchived } AfterAll { @@ -53,34 +54,103 @@ try Context 'Get cards for a column' { $results = @(Get-GitHubProjectCard -Column $column.id) + It 'Should get cards' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty + } + + It 'Should only have one card (since it defaults to not archived)' { + $results.Count | Should -Be 1 } It 'Note is correct' { - $results.note | Should be $defaultCard + $results[0].note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[0].CardId | Should -Be $results[0].id + $results[0].ProjectId | Should -Be $project.id + $results[0].ColumnId | Should -Be $column.id + $results[0].IssueNumber | Should -BeNullOrEmpty + $results[0].RepositoryUrl | Should -BeNullOrEmpty + $results[0].PullRequestNumber | Should -BeNullOrEmpty + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Get all cards for a column' { - $results = @(Get-GitHubProjectCard -Column $column.id -ArchivedState All) + $results = @(Get-GitHubProjectCard -Column $column.id -State All) + It 'Should get all cards' { - $results.Count | Should Be 2 + $results.Count | Should -Be 2 + } + + It 'Has the expected type and additional properties' { + foreach ($item in $results) + { + $item.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $item.CardId | Should -Be $item.id + $item.ProjectId | Should -Be $project.id + $item.ColumnId | Should -Be $column.id + $item.IssueNumber | Should -BeNullOrEmpty + $item.RepositoryUrl | Should -BeNullOrEmpty + $item.PullRequestNumber | Should -BeNullOrEmpty + $item.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } } Context 'Get archived cards for a column' { - $result = Get-GitHubProjectCard -Column $column.id -ArchivedState Archived + $result = Get-GitHubProjectCard -Column $column.id -State Archived It 'Should get archived card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Note is correct' { - $result.note | Should be $defaultArchivedCard + $result.note | Should -Be $defaultArchivedCard } It 'Should be archived' { - $result.Archived | Should be $true + $result.Archived | Should -Be $true + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get non-archived cards for a column (with column on pipeline)' { + $result = $column | Get-GitHubProjectCard -State NotArchived + + It 'Should get non-archived card' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Should have the right ID' { + $result.id | Should -Be $card.id + } + + It 'Should not be archived' { + $result.Archived | Should -Be $false + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } } @@ -90,11 +160,6 @@ try $card = New-GitHubProjectCard -Column $column.id -Note $defaultCard $cardTwo = New-GitHubProjectCard -Column $column.id -Note $defaultCardTwo $cardArchived = New-GitHubProjectCard -Column $column.id -Note $defaultArchivedCard - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card - $cardTwo = $cardTwo - $cardArchived = $cardArchived } AfterAll { @@ -106,11 +171,48 @@ try $result = Get-GitHubProjectCard -Card $card.id It 'Should get card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Note has been updated' { - $result.note | Should be $defaultCardUpdated + $result.note | Should -Be $defaultCardUpdated + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Modify card note (via card on pipeline)' { + $result = $card | Get-GitHubProjectCard + + It 'Should have the expected Note value' { + $result.note | Should -Be $defaultCardUpdated + } + + $null = $card | Set-GitHubProjectCard -Note $defaultCard + $result = $card | Get-GitHubProjectCard + + It 'Should have the updated Note' { + $result.note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } @@ -119,24 +221,24 @@ try $result = Get-GitHubProjectCard -Card $cardArchived.id It 'Should get card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Card is archived' { - $result.Archived | Should be $true + $result.Archived | Should -Be $true } } Context 'Restore a card' { - $null = Set-GitHubProjectCard -Card $cardArchived.id -Restore + $null = $cardArchived | Set-GitHubProjectCard -Restore $result = Get-GitHubProjectCard -Card $cardArchived.id It 'Should get card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Card is not archived' { - $result.Archived | Should be $false + $result.Archived | Should -Be $false } } @@ -145,7 +247,18 @@ try $results = @(Get-GitHubProjectCard -Column $column.id) It 'Card is now top' { - $results[0].note | Should be $defaultCardTwo + $results[0].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[0].CardId | Should -Be $results[0].id + $results[0].ProjectId | Should -Be $project.id + $results[0].ColumnId | Should -Be $column.id + $results[0].IssueNumber | Should -BeNullOrEmpty + $results[0].RepositoryUrl | Should -BeNullOrEmpty + $results[0].PullRequestNumber | Should -BeNullOrEmpty + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } @@ -154,7 +267,38 @@ try $results = @(Get-GitHubProjectCard -Column $column.id) It 'Card now exists in new column' { - $results[1].note | Should be $defaultCardTwo + $results[1].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[1].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[1].CardId | Should -Be $results[1].id + $results[1].ProjectId | Should -Be $project.id + $results[1].ColumnId | Should -Be $column.id + $results[1].IssueNumber | Should -BeNullOrEmpty + $results[1].RepositoryUrl | Should -BeNullOrEmpty + $results[1].PullRequestNumber | Should -BeNullOrEmpty + $results[1].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Move card using before parameter (card on pipeline)' { + $null = $cardTwo | Move-GitHubProjectCard -After $card.id + $results = @($column | Get-GitHubProjectCard) + + It 'Card now exists in new column' { + $results[1].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[1].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[1].CardId | Should -Be $results[1].id + $results[1].ProjectId | Should -Be $project.id + $results[1].ColumnId | Should -Be $column.id + $results[1].IssueNumber | Should -BeNullOrEmpty + $results[1].RepositoryUrl | Should -BeNullOrEmpty + $results[1].PullRequestNumber | Should -BeNullOrEmpty + $results[1].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } @@ -163,24 +307,52 @@ try $results = @(Get-GitHubProjectCard -Column $columnTwo.id) It 'Card now exists in new column' { - $results[0].note | Should be $defaultCardTwo + $results[0].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[0].CardId | Should -Be $results[0].id + $results[0].ProjectId | Should -Be $project.id + $results[0].ColumnId | Should -Be $columnTwo.id + $results[0].IssueNumber | Should -BeNullOrEmpty + $results[0].RepositoryUrl | Should -BeNullOrEmpty + $results[0].PullRequestNumber | Should -BeNullOrEmpty + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Move card to another column (with column on pipeline)' { + $null = ($column | Move-GitHubProjectCard -Card $cardTwo.id -Top) + $result = $cardTwo | Get-GitHubProjectCard + + It 'Card now exists in new column' { + $result.ColumnId | Should -Be $column.ColumnId + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Move command throws appropriate error' { It 'Appropriate error is thrown' { - { Move-GitHubProjectCard -Card $cardTwo.id -Top -Bottom } | Should Throw 'You must use one (and only one) of the parameters Top, Bottom or After.' + { Move-GitHubProjectCard -Card $cardTwo.id -Top -Bottom } | Should -Throw 'You must use one (and only one) of the parameters Top, Bottom or After.' } } } - Describe 'Create Project Cards' -tag new { + Describe 'Create Project Cards' { Context 'Create project card with note' { BeforeAll { $card = @{id = 0} - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card } AfterAll { @@ -192,20 +364,62 @@ try $result = Get-GitHubProjectCard -Card $card.id It 'Card exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Note is correct' { - $result.note | Should be $defaultCard + $result.note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'Create project card from issue' { + Context 'Create project card with note (with column object via pipeline)' { BeforeAll { $card = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProjectCard -Card $card.id -Confirm:$false + Remove-Variable -Name card + } + + $newCard = $column | New-GitHubProjectCard -Note $defaultCard + $card.id = $newCard.id + $result = $newCard | Get-GitHubProjectCard - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card + It 'Card exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Note is correct' { + $result.note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Create project card from issue' { + BeforeAll { + $card = @{id = 0} } AfterAll { @@ -213,31 +427,86 @@ try Remove-Variable -Name card } - $card.id = (New-GitHubProjectCard -Column $column.id -ContentId $issue.id -ContentType 'Issue').id + $card.id = (New-GitHubProjectCard -Column $column.id -IssueId $issue.id).id $result = Get-GitHubProjectCard -Card $card.id It 'Card exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Content url is for an issue' { - $result.content_url | Should match 'issues' + $result.content_url | Should -Match 'issues' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -Be $issue.number + $result.RepositoryUrl | Should -Be $issue.RepositoryUrl + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } + + Context 'Create project card from issue (with issue object on pipeline)' { + BeforeAll { + $card = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProjectCard -Card $card.id -Force + Remove-Variable -Name card + } + + $newCard = $issue | New-GitHubProjectCard -Column $column.id + $card.id = $newCard.id + $result = $newCard | Get-GitHubProjectCard + + It 'Card exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Content url is for an issue' { + $result.content_url | Should -Match 'issues' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -Be $issue.number + $result.RepositoryUrl | Should -Be $issue.RepositoryUrl + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + # TODO: Create a test that verifies cards created based on a pull request } Describe 'Remove card' { Context 'Remove card' { BeforeAll { $card = New-GitHubProjectCard -Column $column.id -Note $defaultCard - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card } $null = Remove-GitHubProjectCard -Card $card.id -Confirm:$false It 'Project card should be removed' { - {Get-GitHubProjectCard -Card $card.id} | Should Throw + {Get-GitHubProjectCard -Card $card.id} | Should -Throw + } + } + + Context 'Remove card (via pipeline)' { + BeforeAll { + $card = $column | New-GitHubProjectCard -Note $defaultCard + } + + $null = $card | Remove-GitHubProjectCard -Force + It 'Project card should be removed' { + {$card | Get-GitHubProjectCard} | Should -Throw } } } diff --git a/Tests/GitHubProjectColumns.tests.ps1 b/Tests/GitHubProjectColumns.tests.ps1 index 6eb3f047..a2fcbb09 100644 --- a/Tests/GitHubProjectColumns.tests.ps1 +++ b/Tests/GitHubProjectColumns.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubProjectColumns.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -22,14 +27,11 @@ try Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value } - $project = New-GitHubProject -UserProject -Name $defaultProject + $project = New-GitHubProject -UserProject -ProjectName $defaultProject Describe 'Getting Project Columns' { BeforeAll { - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn } AfterAll { @@ -39,27 +41,82 @@ try Context 'Get columns for a project' { $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Should get column' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty + } + + It 'Should only have one column' { + $results.Count | Should -Be 1 + } + + It 'Name is correct' { + $results[0].name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[0].ColumnId | Should -Be $results[0].id + $results[0].ColumnName | Should -Be $results[0].name + $results[0].ProjectId | Should -Be $project.id + } + } + + Context 'Get columns for a project (via pipeline)' { + $results = @($project | Get-GitHubProjectColumn) + It 'Should get column' { + $results | Should -Not -BeNullOrEmpty } It 'Should only have one column' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should Be $defaultColumn + $results[0].name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[0].ColumnId | Should -Be $results[0].id + $results[0].ColumnName | Should -Be $results[0].name + $results[0].ProjectId | Should -Be $project.id + } + } + + Context 'Get specific column' { + $result = Get-GitHubProjectColumn -Column $column.id + + It 'Should be the right column' { + $result.id | Should -Be $column.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id + } + } + + Context 'Get specific column (via pipeline)' { + $result = $column | Get-GitHubProjectColumn + + It 'Should be the right column' { + $result.id | Should -Be $column.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id } } } Describe 'Modify Project Column' { BeforeAll { - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - $columntwo = New-GitHubProjectColumn -Project $project.id -Name $defaultColumnTwo - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column - $columnTwo = $columnTwo + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn + $columntwo = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumnTwo } AfterAll { @@ -68,15 +125,22 @@ try } Context 'Modify column name' { - $null = Set-GitHubProjectColumn -Column $column.id -Name $defaultColumnUpdate + $null = Set-GitHubProjectColumn -Column $column.id -ColumnName $defaultColumnUpdate $result = Get-GitHubProjectColumn -Column $column.id It 'Should get column' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name has been updated' { - $result.name | Should Be $defaultColumnUpdate + $result.name | Should -Be $defaultColumnUpdate + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id } } @@ -85,11 +149,18 @@ try $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Should still have more than one column in the project' { - $results.Count | Should Be 2 + $results.Count | Should -Be 2 } It 'Column is now in the first position' { - $results[0].name | Should Be $defaultColumnTwo + $results[0].name | Should -Be $defaultColumnTwo + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[0].ColumnId | Should -Be $results[0].id + $results[0].ColumnName | Should -Be $results[0].name + $results[0].ProjectId | Should -Be $project.id } } @@ -98,13 +169,20 @@ try $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Column is now not in the first position' { - $results[1].name | Should Be $defaultColumnTwo + $results[1].name | Should -Be $defaultColumnTwo + } + + It 'Should have the expected type and additional properties' { + $results[1].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[1].ColumnId | Should -Be $columntwo.ColumnId + $results[1].ColumnName | Should -Be $columntwo.ColumnName + $results[1].ProjectId | Should -Be $project.id } } Context 'Move command throws appropriate error' { It 'Expected error returned' { - { Move-GitHubProjectColumn -Column $column.id -First -Last } | Should Throw 'You must use one (and only one) of the parameters First, Last or After.' + { Move-GitHubProjectColumn -Column $column.id -First -Last } | Should -Throw 'You must use one (and only one) of the parameters First, Last or After.' } } } @@ -113,9 +191,64 @@ try Context 'Create project column' { BeforeAll { $column = @{id = 0} + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column + AfterAll { + $null = Remove-GitHubProjectColumn -Column $column.id -Force + Remove-Variable -Name column + } + + $column.id = (New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn).id + $result = Get-GitHubProjectColumn -Column $column.id + + It 'Column exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id + } + } + + Context 'Create project column (object via pipeline)' { + BeforeAll { + $column = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProjectColumn -Column $column.id -Force + Remove-Variable -Name column + } + + $column.id = ($project | New-GitHubProjectColumn -ColumnName $defaultColumn).id + $result = Get-GitHubProjectColumn -Column $column.id + + It 'Column exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id + } + } + + Context 'Create project column (name via pipeline)' { + BeforeAll { + $column = @{id = 0} } AfterAll { @@ -123,15 +256,22 @@ try Remove-Variable -Name column } - $column.id = (New-GitHubProjectColumn -Project $project.id -Name $defaultColumn).id + $column.id = ($defaultColumn | New-GitHubProjectColumn -Project $project.id).id $result = Get-GitHubProjectColumn -Column $column.id It 'Column exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should Be $defaultColumn + $result.name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id } } } @@ -139,15 +279,23 @@ try Describe 'Remove project column' { Context 'Remove project column' { BeforeAll { - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn } $null = Remove-GitHubProjectColumn -Column $column.id -Confirm:$false It 'Project column should be removed' { - {Get-GitHubProjectColumn -Column $column.id} | Should Throw + {Get-GitHubProjectColumn -Column $column.id} | Should -Throw + } + } + + Context 'Remove project column (via pipeline)' { + BeforeAll { + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn + } + + $column | Remove-GitHubProjectColumn -Force + It 'Project column should be removed' { + {$column | Get-GitHubProjectColumn} | Should -Throw } } } diff --git a/Tests/GitHubProjects.tests.ps1 b/Tests/GitHubProjects.tests.ps1 index dec75e4a..ccde85d6 100644 --- a/Tests/GitHubProjects.tests.ps1 +++ b/Tests/GitHubProjects.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubProjects.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -37,10 +42,7 @@ try Describe 'Getting Project' { Context 'Get User projects' { BeforeAll { - $project = New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc } AfterAll { @@ -49,28 +51,31 @@ try $results = @(Get-GitHubProject -UserName $script:ownerName | Where-Object Name -eq $defaultUserProject) It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultUserProject + $results[0].name | Should -Be $defaultUserProject } It 'Description is correct' { - $results[0].body | Should be $defaultUserProjectDesc + $results[0].body | Should -Be $defaultUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $results[0].ProjectId | Should -Be $results[0].id } } Context 'Get Organization projects' { BeforeAll { - $project = New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc } AfterAll { @@ -79,28 +84,34 @@ try $results = @(Get-GitHubProject -OrganizationName $script:organizationName | Where-Object Name -eq $defaultOrgProject) It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultOrgProject + $results[0].name | Should -Be $defaultOrgProject } It 'Description is correct' { - $results[0].body | Should be $defaultOrgProjectDesc + $results[0].body | Should -Be $defaultOrgProjectDesc + } + + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $results[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -Be $repositoryUrl + $results[0].ProjectId | Should -Be $results[0].id } } Context 'Get Repo projects' { BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc } AfterAll { @@ -109,54 +120,139 @@ try $results = @(Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name | Where-Object Name -eq $defaultRepoProject) It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultRepoProject + $results[0].name | Should -Be $defaultRepoProject } It 'Description is correct' { - $results[0].body | Should be $defaultRepoProjectDesc + $results[0].body | Should -Be $defaultRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $results[0].ProjectId | Should -Be $results[0].id + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'Get a closed Repo project' { + Context 'Get a closed Repo project (via pipeline)' { BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultProjectClosed -Description $defaultProjectClosedDesc + $project = $repo | New-GitHubProject -ProjectName $defaultProjectClosed -Description $defaultProjectClosedDesc $null = Set-GitHubProject -Project $project.id -State Closed - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project } AfterAll { $null = Remove-GitHubProject -Project $project.id -Confirm:$false } - $results = @(Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -State 'Closed' | Where-Object Name -eq $defaultProjectClosed) + $results = @($repo | Get-GitHubProject -State 'Closed') It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultProjectClosed + $results[0].name | Should -Be $defaultProjectClosed } It 'Description is correct' { - $results[0].body | Should be $defaultProjectClosedDesc + $results[0].body | Should -Be $defaultProjectClosedDesc } It 'State is correct' { - $results[0].state | Should be "Closed" + $results[0].state | Should -Be "Closed" + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $results[0].ProjectId | Should -Be $results[0].id + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get a specific project (by parameter)' { + BeforeAll { + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + } + + $result = Get-GitHubProject -Project $project.id + It 'Should get project' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultRepoProject + } + + It 'Description is correct' { + $result.body | Should -Be $defaultRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $project.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get a specific project (by pipeline object)' { + BeforeAll { + $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + } + + AfterAll { + $project | Remove-GitHubProject -Force + } + + $result = $project | Get-GitHubProject + It 'Should get the right project' { + $result.id | Should -Be $project.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $project.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get a specific project (with ID via pipeline)' { + BeforeAll { + $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + } + + AfterAll { + $project | Remove-GitHubProject -Force + } + + $result = $project.id | Get-GitHubProject + It 'Should get the right project' { + $result.id | Should -Be $project.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $project.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } } @@ -164,10 +260,7 @@ try Describe 'Modify Project' { Context 'Modify User projects' { BeforeAll { - $project = New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc } AfterAll { @@ -177,24 +270,90 @@ try $null = Set-GitHubProject -Project $project.id -Description $modifiedUserProjectDesc $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultUserProject + $result.name | Should -Be $defaultUserProject } It 'Description should be updated' { - $result.body | Should be $modifiedUserProjectDesc + $result.body | Should -Be $modifiedUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'Modify Organization projects' { + Context 'Modify User projects (via ID in pipeline)' { BeforeAll { - $project = New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + } + + $null = $project.id | Set-GitHubProject -Description $modifiedUserProjectDesc + $result = Get-GitHubProject -Project $project.id + It 'Should get project' { + $result | Should -Not -BeNullOrEmpty + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + It 'Name is correct' { + $result.name | Should -Be $defaultUserProject + } + + It 'Description should be updated' { + $result.body | Should -Be $modifiedUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Modify User projects (via object in pipeline)' { + BeforeAll { + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + } + + $null = $project | Set-GitHubProject -Description $modifiedUserProjectDesc + $result = Get-GitHubProject -Project $project.id + It 'Should get project' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultUserProject + } + + It 'Description should be updated' { + $result.body | Should -Be $modifiedUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Modify Organization projects' { + BeforeAll { + $project = New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc } AfterAll { @@ -204,33 +363,39 @@ try $null = Set-GitHubProject -Project $project.id -Description $modifiedOrgProjectDesc -Private:$false -OrganizationPermission Admin $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultOrgProject + $result.name | Should -Be $defaultOrgProject } It 'Description should be updated' { - $result.body | Should be $modifiedOrgProjectDesc + $result.body | Should -Be $modifiedOrgProjectDesc } It 'Visibility should be updated to public' { - $result.private | Should be $false + $result.private | Should -Be $false } It 'Organization permission should be updated to admin' { - $result.organization_permission | Should be 'admin' + $result.organization_permission | Should -Be 'admin' } + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $result.html_url + $repositoryUrl = Join-GitHubUri @elements + + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } Context 'Modify Repo projects' { BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc } AfterAll { @@ -240,15 +405,22 @@ try $null = Set-GitHubProject -Project $project.id -Description $modifiedRepoProjectDesc $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultRepoProject + $result.name | Should -Be $defaultRepoProject } It 'Description should be updated' { - $result.body | Should be $modifiedRepoProjectDesc + $result.body | Should -Be $modifiedRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } } @@ -257,9 +429,38 @@ try Context 'Create User projects' { BeforeAll { $project = @{id = 0} + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + Remove-Variable project + } + + $project.id = (New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc).id + $result = Get-GitHubProject -Project $project.id + It 'Project exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultUserProject + } + + It 'Description should be updated' { + $result.body | Should -Be $defaultUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Create User project (title on pipeline)' { + BeforeAll { + $project = @{id = 0} } AfterAll { @@ -267,27 +468,31 @@ try Remove-Variable project } - $project.id = (New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc).id + $project.id = ($defaultUserProject | New-GitHubProject -UserProject -Description $defaultUserProjectDesc).id $result = Get-GitHubProject -Project $project.id It 'Project exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultUserProject + $result.name | Should -Be $defaultUserProject } It 'Description should be updated' { - $result.body | Should be $defaultUserProjectDesc + $result.body | Should -Be $defaultUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Create Organization projects' { BeforeAll { $project = @{id = 0} - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project } AfterAll { @@ -295,27 +500,34 @@ try Remove-Variable project } - $project.id = (New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc).id + $project.id = (New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc).id $result = Get-GitHubProject -Project $project.id It 'Project exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultOrgProject + $result.name | Should -Be $defaultOrgProject } It 'Description should be updated' { - $result.body | Should be $defaultOrgProjectDesc + $result.body | Should -Be $defaultOrgProjectDesc + } + + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $result.html_url + $repositoryUrl = Join-GitHubUri @elements + + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Create Repo projects' { BeforeAll { $project = @{id = 0} - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project } AfterAll { @@ -323,62 +535,91 @@ try Remove-Variable project } - $project.id = (New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc).id + $project.id = (New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc).id $result = Get-GitHubProject -Project $project.id It 'Project Exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultRepoProject + $result.name | Should -Be $defaultRepoProject } It 'Description should be updated' { - $result.body | Should be $defaultRepoProjectDesc + $result.body | Should -Be $defaultRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - } - Describe 'Remove Project' { - Context 'Remove User projects' { + Context 'Create Repo project (via pipeline)' { BeforeAll { - $project = New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc + $project = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + Remove-Variable project + } + + $project.id = ($repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc).id + $result = Get-GitHubProject -Project $project.id + It 'Project Exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultRepoProject + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + It 'Description should be updated' { + $result.body | Should -Be $defaultRepoProjectDesc } + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'Remove Project' { + Context 'Remove User projects' { + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc $null = Remove-GitHubProject -Project $project.id -Force It 'Project should be removed' { - {Get-GitHubProject -Project $project.id} | Should Throw + {Get-GitHubProject -Project $project.id} | Should -Throw } } Context 'Remove Organization projects' { - BeforeAll { - $project = New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project - } - + $project = New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc $null = Remove-GitHubProject -Project $project.id -Force It 'Project should be removed' { - {Get-GitHubProject -Project $project.id} | Should Throw + {Get-GitHubProject -Project $project.id} | Should -Throw } } Context 'Remove Repo projects' { - BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + It 'Project should be removed' { + {Get-GitHubProject -Project $project.id} | Should -Throw } + } - $null = Remove-GitHubProject -Project $project.id -Confirm:$false + Context 'Remove Repo project via pipeline' { + $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + $project | Remove-GitHubProject -Force It 'Project should be removed' { - {Get-GitHubProject -Project $project.id} | Should Throw + {$project | Get-GitHubProject} | Should -Throw } } } diff --git a/Tests/GitHubPullRequests.tests.ps1 b/Tests/GitHubPullRequests.tests.ps1 new file mode 100644 index 00000000..8df52250 --- /dev/null +++ b/Tests/GitHubPullRequests.tests.ps1 @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubPullRequests.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting pull request from repository' { + BeforeAll { + $repo = Get-GitHubRepository -OwnerName 'microsoft' -RepositoryName 'PowerShellForGitHub' + } + + Context 'When getting a pull request' { + $pullRequestNumber = 39 + $pullRequest = Get-GitHubPullRequest -OwnerName 'microsoft' -RepositoryName 'PowerShellForGitHub' -PullRequest $pullRequestNumber + + It 'Should be the expected pull request' { + $pullRequest.number | Should -Be $pullRequestNumber + } + + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $pullRequest.html_url + $repositoryUrl = Join-GitHubUri @elements + + $pullRequest.PSObject.TypeNames[0] | Should -Be 'GitHub.PullRequest' + $pullRequest.RepositoryUrl | Should -Be $repo.RepositoryUrl + $pullRequest.PullRequestId | Should -Be $pullRequest.id + $pullRequest.PullRequestNumber | Should -Be $pullRequest.number + $pullRequest.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $pullRequest.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $pullRequest.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $pullRequest.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $pullRequest.requested_teams[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Team' + $pullRequest.merged_by.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be refreshable via the pipeline' { + $refresh = $pullRequest | Get-GitHubPullRequest + $refresh.PullRequestNumber | Should -Be $pullRequest.PullRequestNumber + } + + It 'Should be retrievable by passing the repo on the pipeline' { + $pullRequest = $repo | Get-GitHubPullRequest -PullRequest $pullRequestNumber + $pullRequest.number | Should -Be $pullRequestNumber + } + + It 'Should fail when it the pull request does not exist' { + { $repo | Get-GitHubPullRequest -PullRequest 1 } | Should -Throw + } + } + } + + Describe 'Getting multiple pull requests from repository' { + BeforeAll { + $ownerName = 'microsoft' + $repositoryName = 'PowerShellForGitHub' + } + + Context 'All closed' { + $pullRequests = @(Get-GitHubPullRequest -OwnerName $ownerName -RepositoryName $repositoryName -State 'Closed') + + It 'Should return expected number of PRs' { + $pullRequests.Count | Should -BeGreaterOrEqual 140 + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubReleases.tests.ps1 b/Tests/GitHubReleases.tests.ps1 index 53d5ed31..10c1b42a 100644 --- a/Tests/GitHubReleases.tests.ps1 +++ b/Tests/GitHubReleases.tests.ps1 @@ -6,83 +6,134 @@ Tests for GitHubReleases.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - if ($accessTokenConfigured) - { - Describe 'Getting releases from repository' { - $ownerName = "dotnet" - $repositoryName = "core" - $releases = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName) + Describe 'Getting releases from repository' { + $ownerName = "dotnet" + $repositoryName = "core" + $releases = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName) + + Context 'When getting all releases' { + It 'Should return multiple releases' { + $releases.Count | Should -BeGreaterThan 1 + } - Context 'When getting all releases' { - It 'Should return multiple releases' { - $releases.Count | Should BeGreaterThan 1 - } + It 'Should have expected type and additional properties' { + $releases[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $releases[0].html_url.StartsWith($releases[0].RepositoryUrl) | Should -BeTrue + $releases[0].id | Should -Be $releases[0].ReleaseId } + } - Context 'When getting the latest releases' { - $latest = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest) + Context 'When getting the latest releases' { + $latest = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest) - It 'Should return one value' { - $latest.Count | Should Be 1 - } + It 'Should return one value' { + $latest.Count | Should -Be 1 + } - It 'Should return the first release from the full releases list' { - $latest[0].url | Should Be $releases[0].url - $latest[0].name | Should Be $releases[0].name - } + It 'Should return the first release from the full releases list' { + $latest[0].url | Should -Be $releases[0].url + $latest[0].name | Should -Be $releases[0].name } - Context 'When getting a specific release' { - $specificIndex = 5 - $specific = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) + It 'Should have expected type and additional properties' { + $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $latest[0].html_url.StartsWith($latest[0].RepositoryUrl) | Should -BeTrue + $latest[0].id | Should -Be $latest[0].ReleaseId + } + } - It 'Should return one value' { - $specific.Count | Should Be 1 - } + Context 'When getting the latest releases via the pipeline' { + $latest = @(Get-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName | + Get-GitHubRelease -Latest) - It 'Should return the correct release' { - $specific.name | Should Be $releases[$specificIndex].name - } + It 'Should return one value' { + $latest.Count | Should -Be 1 } - Context 'When getting a tagged release' { - $taggedIndex = 8 - $tagged = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) + It 'Should return the first release from the full releases list' { + $latest[0].url | Should -Be $releases[0].url + $latest[0].name | Should -Be $releases[0].name + } - It 'Should return one value' { - $tagged.Count | Should Be 1 - } + It 'Should have expected type and additional properties' { + $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $latest[0].html_url.StartsWith($latest[0].RepositoryUrl) | Should -BeTrue + $latest[0].id | Should -Be $latest[0].ReleaseId + } - It 'Should return the correct release' { - $tagged.name | Should Be $releases[$taggedIndex].name - } + $latestAgain = @($latest | Get-GitHubRelease) + It 'Should be the same release' { + $latest[0].ReleaseId | Should -Be $latestAgain[0].ReleaseId } } - Describe 'Getting releases from default owner/repository' { - $originalOwnerName = Get-GitHubConfiguration -Name DefaultOwnerName - $originalRepositoryName = Get-GitHubConfiguration -Name DefaultRepositoryName + Context 'When getting a specific release' { + $specificIndex = 5 + $specific = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) - try { - Set-GitHubConfiguration -DefaultOwnerName "dotnet" - Set-GitHubConfiguration -DefaultRepositoryName "core" - $releases = @(Get-GitHubRelease) + It 'Should return one value' { + $specific.Count | Should -Be 1 + } - Context 'When getting all releases' { - It 'Should return multiple releases' { - $releases.Count | Should BeGreaterThan 1 - } + It 'Should return the correct release' { + $specific.name | Should -Be $releases[$specificIndex].name + } + + It 'Should have expected type and additional properties' { + $specific[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $specific[0].html_url.StartsWith($specific[0].RepositoryUrl) | Should -BeTrue + $specific[0].id | Should -Be $specific[0].ReleaseId + } + } + + Context 'When getting a tagged release' { + $taggedIndex = 8 + $tagged = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) + + It 'Should return one value' { + $tagged.Count | Should -Be 1 + } + + It 'Should return the correct release' { + $tagged.name | Should -Be $releases[$taggedIndex].name + } + + It 'Should have expected type and additional properties' { + $tagged[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $tagged[0].html_url.StartsWith($tagged[0].RepositoryUrl) | Should -BeTrue + $tagged[0].id | Should -Be $tagged[0].ReleaseId + } + } + } + + Describe 'Getting releases from default owner/repository' { + $originalOwnerName = Get-GitHubConfiguration -Name DefaultOwnerName + $originalRepositoryName = Get-GitHubConfiguration -Name DefaultRepositoryName + + try { + Set-GitHubConfiguration -DefaultOwnerName "dotnet" + Set-GitHubConfiguration -DefaultRepositoryName "core" + $releases = @(Get-GitHubRelease) + + Context 'When getting all releases' { + It 'Should return multiple releases' { + $releases.Count | Should -BeGreaterThan 1 } - } finally { - Set-GitHubConfiguration -DefaultOwnerName $originalOwnerName - Set-GitHubConfiguration -DefaultRepositoryName $originalRepositoryName } + } finally { + Set-GitHubConfiguration -DefaultOwnerName $originalOwnerName + Set-GitHubConfiguration -DefaultRepositoryName $originalRepositoryName } } } diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 598361f7..3dc1bf81 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -409,7 +409,7 @@ try } It "Should have the expected new repository name - by URI" { - $renamedRepo = $repo | Rename-GitHubRepository -NewName $newRepoName -Force + $renamedRepo = Rename-GitHubRepository -Uri ($repo.RepositoryUrl) -NewName $newRepoName -Force $renamedRepo.name | Should -Be $newRepoName } @@ -418,6 +418,18 @@ try $renamedRepo.name | Should -Be $newRepoName } + It "Should work via the pipeline" { + $renamedRepo = $repo | Rename-GitHubRepository -NewName $newRepoName -Confirm:$false + $renamedRepo.name | Should -Be $newRepoName + $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It "Should be possible to rename with Update-GitHubRepository too" { + $renamedRepo = $repo | Update-GitHubRepository -NewName $newRepoName -Confirm:$false + $renamedRepo.name | Should -Be $newRepoName + $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + AfterEach -Scriptblock { Remove-GitHubRepository -Uri "$($repo.svn_url)$suffixToAddToRepo" -Confirm:$false } @@ -555,6 +567,74 @@ try } } + Describe 'Common user repository pipeline scenarios' { + Context 'For authenticated user' { + BeforeAll -Scriptblock { + $repo = ([Guid]::NewGuid().Guid) | New-GitHubRepository -AutoInit + } + + It "Should have expected additional properties and type after creation" { + $repo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $repo.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:ownerName -RepositoryName $repo.name) + $repo.RepositoryId | Should -Be $repo.id + $repo.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It "Should have expected additional properties and type after creation" { + $returned = ($repo | Get-GitHubRepository) + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $returned.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:ownerName -RepositoryName $returned.name) + $returned.RepositoryId | Should -Be $returned.id + $returned.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It "Should get the repository by user" { + $repos = @($script:ownerName | Get-GitHubUser | Get-GitHubRepository) + $repos.name | Should -Contain $repo.name + } + + It 'Should be removable by the pipeline' { + ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty + { $repo | Get-GitHubRepository } | Should -Throw + } + } + } + + Describe 'Common organization repository pipeline scenarios' { + Context 'For organization' { + BeforeAll -Scriptblock { + $org = [PSCustomObject]@{'OrganizationName' = $script:organizationName} + $repo = $org | New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + It "Should have expected additional properties and type after creation" { + $repo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $repo.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:organizationName -RepositoryName $repo.name) + $repo.RepositoryId | Should -Be $repo.id + $repo.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $repo.organization.PSObject.TypeNames[0] | Should -Be 'GitHub.Organization' + $repo.organization.OrganizationName | Should -Be $repo.organization.login + $repo.organization.OrganizationId | Should -Be $repo.organization.id + } + + It "Should have expected additional properties and type after creation" { + $returned = ($repo | Get-GitHubRepository) + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $returned.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:organizationName -RepositoryName $returned.name) + $returned.RepositoryId | Should -Be $returned.id + $returned.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $returned.organization.PSObject.TypeNames[0] | Should -Be 'GitHub.Organization' + $returned.organization.OrganizationName | Should -Be $returned.organization.login + $returned.organization.OrganizationId | Should -Be $returned.organization.id + } + + It 'Should be removable by the pipeline' { + ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty + { $repo | Get-GitHubRepository } | Should -Throw + } + } + } + Describe 'Get/set repository topic' { Context -Name 'For creating and getting a repository topic' -Fixture { @@ -563,17 +643,51 @@ try } It 'Should have the expected topic' { - Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Name $defaultRepoTopic + $null = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Topic $defaultRepoTopic $topic = Get-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $topic.names | Should -Be $defaultRepoTopic } It 'Should have no topics' { - Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Clear + $null = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Clear $topic = Get-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $topic.names | Should -BeNullOrEmpty } + It 'Should have the expected topic (using repo via pipeline)' { + $null = $repo | Set-GitHubRepositoryTopic -Topic $defaultRepoTopic + $topic = $repo | Get-GitHubRepositoryTopic + + $topic.names | Should -Be $defaultRepoTopic + $topic.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' + $topic.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + + It 'Should have the expected topic (using topic via pipeline)' { + $null = $defaultRepoTopic | Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $topic = $repo | Get-GitHubRepositoryTopic + + $topic.names | Should -Be $defaultRepoTopic + $topic.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' + $topic.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + + It 'Should have the expected multi-topic (using topic via pipeline)' { + $topics = @('one', 'two') + $null = $topics | Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $result = $repo | Get-GitHubRepositoryTopic + + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.names.count | Should -Be $topics.Count + foreach ($topic in $topics) + { + $result.names | Should -Contain $topic + } + } + AfterAll -ScriptBlock { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } @@ -595,6 +709,14 @@ try It 'Should contain PowerShell' { $languages = Get-GitHubRepositoryLanguage -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" $languages.PowerShell | Should -Not -BeNullOrEmpty + $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' + } + + It 'Should contain PowerShell (via pipeline)' { + $psfg = Get-GitHubRepository -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" + $languages = $psfg | Get-GitHubRepositoryLanguage + $languages.PowerShell | Should -Not -BeNullOrEmpty + $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' } AfterAll -ScriptBlock { @@ -615,11 +737,82 @@ try $tags | Should -BeNullOrEmpty } + It 'Should be empty (via pipeline)' { + $tags = $repo | Get-GitHubRepositoryTag + $tags | Should -BeNullOrEmpty + } + AfterAll -ScriptBlock { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } + + Describe 'Contributors for a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + $null = Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context -Name 'Obtaining contributors for repository' -Fixture { + $contributors = @(Get-GitHubRepositoryContributor -Uri $repo.RepositoryUrl) + + It 'Should return expected number of contributors' { + $contributors.Count | Should -Be 1 + $contributors[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + + Context -Name 'Obtaining contributors for repository (via pipeline)' -Fixture { + $contributors = @($repo | Get-GitHubRepositoryContributor -IncludeStatistics) + + It 'Should return expected number of contributors' { + $contributors.Count | Should -Be 1 + $contributors[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + + Context -Name 'Obtaining contributor statistics for repository' -Fixture { + $stats = @(Get-GitHubRepositoryContributor -Uri $repo.RepositoryUrl -IncludeStatistics) + + It 'Should return expected number of contributors' { + $stats.Count | Should -Be 1 + $stats[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributorStatistics' + $stats[0].author.PSObject.TypeNames[0] = 'GitHub.User' + } + } + } + + Describe 'Collaborators for a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + $null = Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context -Name 'Obtaining collaborators for repository' -Fixture { + $collaborators = @(Get-GitHubRepositoryCollaborator -Uri $repo.RepositoryUrl) + + It 'Should return expected number of collaborators' { + $collaborators.Count | Should -Be 1 + $collaborators[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + + Context -Name 'Obtaining collaborators for repository (via pipeline)' -Fixture { + $collaborators = @($repo | Get-GitHubRepositoryCollaborator) + + It 'Should return expected number of collaborators' { + $collaborators.Count | Should -Be 1 + $collaborators[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + } } finally { diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index c5267a20..a7f6d37f 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -32,7 +32,7 @@ try } AfterAll { - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + $repo | Remove-GitHubRepository -Force } $newForks = @(Get-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -Sort Newest) @@ -43,6 +43,36 @@ try # think that there's an existing clone out there and so may name this one "...-1" $ourFork.full_name.StartsWith("$($script:ownerName)/$script:upstreamRepositoryName") | Should -BeTrue } + + It 'Should have the expected additional type and properties' { + $ourFork.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $ourFork.RepositoryId | Should -Be $ourFork.id + } + } + + Context 'When a new fork is created (with the pipeline)' { + BeforeAll { + $upstream = Get-GitHubRepository -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName + $repo = $upstream | New-GitHubRepositoryFork + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + $newForks = @(Get-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -Sort Newest) + $ourFork = $newForks | Where-Object { $_.owner.login -eq $script:ownerName } + + It 'Should be in the list' { + # Doing this syntax, because due to odd timing with GitHub, it's possible it may + # think that there's an existing clone out there and so may name this one "...-1" + $ourFork.full_name.StartsWith("$($script:ownerName)/$script:upstreamRepositoryName") | Should -BeTrue + } + + It 'Should have the expected additional type and properties' { + $ourFork.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $ourFork.RepositoryId | Should -Be $ourFork.id + } } } @@ -53,7 +83,8 @@ try } AfterAll { - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Start-Sleep -Seconds 3 # Trying to avoid an issue with deleting the repo if it's still being created by GitHub + $repo | Remove-GitHubRepository -Force } $newForks = @(Get-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -Sort Newest) diff --git a/Tests/GitHubRepositoryTraffic.tests.ps1 b/Tests/GitHubRepositoryTraffic.tests.ps1 index ea542e19..7136ee0a 100644 --- a/Tests/GitHubRepositoryTraffic.tests.ps1 +++ b/Tests/GitHubRepositoryTraffic.tests.ps1 @@ -6,65 +6,102 @@ Tests for GitHubRepositoryTraffic.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - Describe 'Getting the referrer list' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the referrer traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there are no referrers' { - $referrerList = Get-GitHubReferrerTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + Context 'When initially created, there are no referrers' { It 'Should return expected number of referrers' { - $referrerList.Count | Should be 0 + $traffic = Get-GitHubReferrerTraffic -Uri $repo.svn_url + $traffic | Should -BeNullOrEmpty } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have the expected type (via pipeline)' { + $traffic = $repo | Get-GitHubReferrerTraffic + $traffic | Should -BeNullOrEmpty + } } } - Describe 'Getting the popular content over the last 14 days' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the path traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there are is no popular content' { - $pathList = Get-GitHubPathTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } - It 'Should return expected number of popular content' { - $pathList.Count | Should be 0 + Context 'Getting the popular content over the last 14 days' { + It 'Should have no traffic since it was just created' { + $traffic = Get-GitHubPathTraffic -Uri $repo.svn_url + $traffic | Should -BeNullOrEmpty } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have the expected type (via pipeline)' { + $traffic = $repo | Get-GitHubPathTraffic + $traffic | Should -BeNullOrEmpty + } } } - Describe 'Getting the views over the last 14 days' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the view traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there are no views' { - $viewList = Get-GitHubViewTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } - It 'Should return 0 in the count property' { - $viewList.Count | Should be 0 + Context 'Getting the views over the last 14 days' { + It 'Should have no traffic since it was just created' { + $traffic = Get-GitHubViewTraffic -Uri $repo.svn_url + $traffic.Count | Should -Be 0 } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have the expected type (via pipeline)' { + $traffic = $repo | Get-GitHubViewTraffic + $traffic.PSObject.TypeNames[0] | Should -Be 'GitHub.ViewTraffic' + } } } - Describe 'Getting the clones over the last 14 days' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the clone traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there is 0 clones' { - $cloneList = Get-GitHubCloneTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } - It 'Should return expected number of clones' { - $cloneList.Count | Should be 0 + Context 'Getting the clones over the last 14 days' { + It 'Should have no clones since it was just created' { + $traffic = Get-GitHubCloneTraffic -Uri $repo.svn_url + $traffic.Count | Should -Be 0 } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have no clones since it was just created (via pipeline)' { + $traffic = $repo | Get-GitHubCloneTraffic + $traffic.PSObject.TypeNames[0] | Should -Be 'GitHub.CloneTraffic' + } } } } diff --git a/Tests/GitHubTeams.tests.ps1 b/Tests/GitHubTeams.tests.ps1 new file mode 100644 index 00000000..21658992 --- /dev/null +++ b/Tests/GitHubTeams.tests.ps1 @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubTeams.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # TODO once more capabilities exist in the module's API set +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubUsers.tests.ps1 b/Tests/GitHubUsers.tests.ps1 new file mode 100644 index 00000000..837455b6 --- /dev/null +++ b/Tests/GitHubUsers.tests.ps1 @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubIssues.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting a user' { + Context 'Current user when additional properties are enabled' { + BeforeAll { + $currentUser = Get-GitHubUser -Current + } + + It 'Should have the expected type and additional properties' { + $currentUser.UserName | Should -Be $currentUser.login + $currentUser.UserId | Should -Be $currentUser.id + $currentUser.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Current user when additional properties are disabled' { + BeforeAll { + Set-GitHubConfiguration -DisablePipelineSupport + $currentUser = Get-GitHubUser -Current + } + + AfterAll { + Set-GitHubConfiguration -DisablePipelineSupport:$false + } + + It 'Should only have the expected type' { + $currentUser.UserName | Should -BeNullOrEmpty + $currentUser.UserId | Should -BeNullOrEmpty + $currentUser.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Specific user as a parameter' { + BeforeAll { + $user = Get-GitHubUser -UserName $script:ownerName + } + + It 'Should have the expected type and additional properties' { + $user.UserName | Should -Be $user.login + $user.UserId | Should -Be $user.id + $user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Specific user with the pipeline' { + BeforeAll { + $user = $script:ownerName | Get-GitHubUser + } + + It 'Should have the expected type and additional properties' { + $user.UserName | Should -Be $user.login + $user.UserId | Should -Be $user.id + $user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'Getting user context' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Checking context on a repo' { + It 'Should indicate ownership as a parameter' { + $context = Get-GitHubUserContextualInformation -UserName $script:ownerName -RepositoryId $repo.id + 'Owns this repository' | Should -BeIn $context.contexts.message + } + + It 'Should indicate ownership with the repo on the pipeline' { + $context = $repo | Get-GitHubUserContextualInformation -UserName $script:ownerName + 'Owns this repository' | Should -BeIn $context.contexts.message + } + + It 'Should indicate ownership with the username on the pipeline' { + $context = $script:ownerName | Get-GitHubUserContextualInformation -RepositoryId $repo.id + 'Owns this repository' | Should -BeIn $context.contexts.message + $context.contexts[0].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + + It 'Should indicate ownership with the user on the pipeline' { + $user = Get-GitHubUser -UserName $script:ownerName + $context = $user | Get-GitHubUserContextualInformation -RepositoryId $repo.id + 'Owns this repository' | Should -BeIn $context.contexts.message + $context.contexts[0].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + } + + Context 'Checking context on an issue with the pipeline' { + $issue = New-GitHubIssue -Uri $repo.RepositoryUrl -Title ([guid]::NewGuid().Guid) + $context = $issue | Get-GitHubUserContextualInformation -UserName $script:ownerName + + It 'Should indicate the user created the issue' { + $context.contexts[0].octicon | Should -Be 'issue-opened' + $context.contexts[0].IssueId | Should -Be $issue.IssueId + $context.contexts[0].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + + It 'Should indicate the user owns the repository' { + $context.contexts[1].message | Should -Be 'Owns this repository' + $context.contexts[1].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/USAGE.md b/USAGE.md index 844d1186..59cc3954 100644 --- a/USAGE.md +++ b/USAGE.md @@ -5,6 +5,9 @@ * [Logging](#logging) * [Telemetry](#telemetry) * [Examples](#examples) + * [Overview](#overview) + * [Embracing the pipeline](#embracing-the-pipeline) + * [Pipeline Example](#pipeline-example) * [Analytics](#analytics) * [Querying Issues](#querying-issues) * [Querying Pull Requests](#querying-pull-requests) @@ -40,12 +43,12 @@ * [Add assignee to an issue](#add-assignee-to-an-issue) * [Remove assignee from an issue](#remove-assignee-from-an-issue) * [Comments](#comments) - * [Get comments from an issue](#get-comments-from-an-issue) - * [Get comments from a repository](#get-comments-from-a-repository) - * [Get a single comment](#get-a-single-comment) - * [Adding a new comment to an issue](#adding-a-new-comment-to-an-issue) - * [Editing an existing comment](#editing-an-existing-comment) - * [Removing a comment](#removing-a-comment) + * [Get comments from an Issue](#get-comments-from-an-issue) + * [Get Issue comments from a repository](#get-issue-comments-from-a-repository) + * [Get a single Issue comment](#get-a-single-issue-comment) + * [Adding a new comment to an Issue](#adding-a-new-comment-to-an-issue) + * [Editing an existing Issue comment](#editing-an-existing-issue-comment) + * [Removing an Issue comment](#removing-an-issue-comment) * [Milestones](#milestones) * [Get milestones from a repository](#get-milestones-from-a-repository) * [Get a single milestone](#get-a-single-milestone) @@ -104,12 +107,6 @@ In order to track usage, gauge performance and identify areas for improvement, t employed during execution of commands within this module (via Application Insights). For more information, refer to the [Privacy Policy](README.md#privacy-policy). -> You may notice some needed assemblies for communicating with Application Insights being -> downloaded on first run of a command within each PowerShell session. The -> [automatic dependency downloads](#automatic-dependency-downloads) section of the setup -> documentation describes how you can avoid having to always re-download the telemetry assemblies -> in the future. - We recommend that you always leave the telemetry feature enabled, but a situation may arise where it must be disabled for some reason. In this scenario, you can disable telemetry by calling: @@ -161,6 +158,58 @@ us for analysis. We expose it here for complete transparency. ## Examples +### Overview + +#### Embracing the Pipeline + +One of the major benefits of PowerShell is its pipeline -- allowing you to "pipe" a saved value or +the output of a previous command directly into the next command. There is absolutely no requirement +to make use of it in order to use the module, but you will find that the module becomes increasingly +easier to use and more powerful if you do. + +Some of the examples that you find below will show how you might be able to use it to your advantage. + +#### Pipeline Example + +Most commands require you to pass in either a `Uri` for the repository or its elements (the +`OwnerName` and `RepositoryName`). If you keep around the repo that you're interacting with in +a local var (like `$repo`), then you can pipe that into any command to avoid having to specify that +information. Further, piping in a more specific object (like an `Issue`) allows you to avoid even +specifying the relevant Issue number. + +Without the pipeline, an interaction log might look like this: + +```powershell +# Find all of the issues that have the label "repro steps needed" and add a new comment to those +# issues asking for an update. +$issues = @(Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label 'repro steps needed') +foreach ($issue in $issues) +{ + $params = @{ + 'OwnerName' = 'microsoft' + 'RepositoryName' = 'PowerShellForGitHub' + 'Issue' = $issue.number + 'Body' = 'Any update on those repro steps?' + } + + New-GitHubIssueComment @params +} + +``` + +With the pipeline, a similar interaction log might look like this: + +```powershell +# Find all of the issues that have the label "repro steps needed" and add a new comment to those +# issues asking for an update. +Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | + Get-GitHubIssue -Label 'repro steps needed' | + New-GitHubIssueComment -Body 'Any update on those repro steps?' +``` + +We encourage you to explore how embracing the pipeline may simplify your code and interaction +with GitHub using this module! + ### Analytics #### Querying Issues @@ -204,8 +253,8 @@ $issueCounts | Sort-Object -Property Count -Descending #### Querying Pull Requests ```powershell -# Getting all of the pull requests from the Microsoft\PowerShellForGitHub repository -$issues = Get-GitHubIssue -OwnerName Microsoft -RepositoryName 'PowerShellForGitHub' +# Getting all of the pull requests from the microsoft\PowerShellForGitHub repository +$issues = Get-GitHubIssue -OwnerName microsoft -RepositoryName 'PowerShellForGitHub' ``` ```powershell @@ -324,12 +373,12 @@ Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryNam #### Removing a Label From an Issue ```powershell -Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -Issue 1 +Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -Issue 1 ``` #### Updating a Label With a New Name and Color ```powershell -Update-GitHubLabel -OwnerName Microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -NewName NewTestLabel -Color BBBB00 +Update-GitHubLabel -OwnerName microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -NewName NewTestLabel -Color BBBB00 ``` #### Bulk Updating Labels in a Repository @@ -354,7 +403,7 @@ Update-GitHubCurrentUser -Location 'Seattle, WA' -Hireable:$false #### Getting any user ```powershell -Get-GitHubUser -Name octocat +Get-GitHubUser -UserName octocat ``` #### Getting all users @@ -369,12 +418,12 @@ Get-GitHubUser #### Get all the forks for a repository ```powershell -Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Create a new fork ```powershell -New-GitHubRepositoryForm -OwnerName Microsoft -RepositoryName PowerShellForGitHub +New-GitHubRepositoryForm -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` ---------- @@ -383,22 +432,22 @@ New-GitHubRepositoryForm -OwnerName Microsoft -RepositoryName PowerShellForGitHu #### Get the referrer traffic for a repository ```powershell -Get-GitHubReferrerTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubReferrerTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get the popular content for a repository ```powershell -Get-GitHubPathTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubPathTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get the number of views for a repository ```powershell -Get-GitHubViewTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Per Week +Get-GitHubViewTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub -Per Week ``` #### Get the number of clones for a repository ```powershell -Get-GitHubCloneTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Per Day +Get-GitHubCloneTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub -Per Day ``` ---------- @@ -407,56 +456,56 @@ Get-GitHubCloneTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub #### Get assignees ```powershell -Get-GitHubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Check assignee permission ```powershell -$HasPermission = Test-GitHubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" +$HasPermission = Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" ``` #### Add assignee to an issue ```powershell -New-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 ``` #### Remove assignee from an issue ```powershell -Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +Remove-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 ``` ---------- ### Comments -#### Get comments from an issue +#### Get comments from an Issue ```powershell -Get-GitHubIssueComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 +Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 ``` -#### Get comments from a repository +#### Get Issue comments from a repository ```powershell -Get-GitHubRepositoryComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort Created -Direction Ascending -Since '2011-04-14T16:00:49Z' +Get-GitHubRepositoryComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Sort Created -Direction Ascending -Since '2011-04-14T16:00:49Z' ``` -#### Get a single comment +#### Get a single Issue comment ```powershell -Get-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 +Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -CommentID 1 ``` -#### Adding a new comment to an issue +#### Adding a new comment to an Issue ```powershell -New-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" +New-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" ``` -#### Editing an existing comment +#### Editing an existing Issue comment ```powershell -Set-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Body "Testing this API" +Set-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Body "Testing this API" ``` -#### Removing a comment +#### Removing an Issue comment ```powershell -Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 +Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -CommentID 1 ``` ---------- @@ -465,28 +514,28 @@ Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -C #### Get milestones from a repository ```powershell -Get-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort DueOn -Direction Ascending -DueOn '2011-04-14T16:00:49Z' +Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Sort DueOn -Direction Ascending -DueOn '2011-04-14T16:00:49Z' ``` #### Get a single milestone ```powershell -Get-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 +Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 ``` #### Assign an existing issue to a new milestone ```powershell -New-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" -Update-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 2 -Milestone 1 +New-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" +Update-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 2 -Milestone 1 ``` #### Editing an existing milestone ```powershell -Set-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API edited" +Set-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API edited" ``` #### Removing a milestone ```powershell -Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 +Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 ``` ---------- @@ -495,17 +544,17 @@ Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub #### Get events from a repository ```powershell -Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get events from an issue ```powershell -Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 +Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 ``` #### Get a single event ```powershell -Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub -EventID 1 +Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub -EventID 1 ``` ---------- @@ -514,7 +563,7 @@ Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub -EventI #### Get projects for a repository ```powershell -Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get projects for a user @@ -524,12 +573,12 @@ Get-GitHubProject -UserName octocat #### Create a project ```powershell -New-GitHubProject -OwnerName octocat -RepositoryName PowerShellForGitHub -Name TestProject +New-GitHubProject -OwnerName octocat -RepositoryName PowerShellForGitHub -ProjectName TestProject ``` #### Add a column to a project ```powershell -New-GitHubProjectColumn -Project 1 -Name 'To Do' +New-GitHubProjectColumn -Project 1 -ColumnName 'To Do' ``` #### Add a card to a column @@ -560,12 +609,15 @@ Move-GitHubProjectCard -Card 4 -ColumnId 6 -Bottom @LazyWinAdmin used this module to migrate his blog comments from Disqus to GitHub Issues. [See blog post](https://lazywinadmin.com/2019/04/moving_blog_comments.html) for full details. ```powershell +# Get your repo +$repo = Get-GitHubRepository -OwnerName -RepositoryName RepoName + # Create an issue -$IssueObject = New-GitHubIssue @githubsplat -Title $IssueTitle -Body $body -Label 'blog comments' +$issue = $repo | New-GitHubIssue -Title $IssueTitle -Body $body -Label 'blog comments' # Create Comment -New-GitHubComment @githubsplat -Issue $IssueObject.number -Body $CommentBody +$issue | New-GitHubIssueComment -Body $CommentBody # Close issue -Update-GitHubIssue @githubsplat -Issue $IssueObject.number -State Closed +$issue | Update-GitHubIssue -State Closed ``` diff --git a/UpdateCheck.ps1 b/UpdateCheck.ps1 index c76bb253..0773f60c 100644 --- a/UpdateCheck.ps1 +++ b/UpdateCheck.ps1 @@ -58,7 +58,8 @@ function Invoke-UpdateCheck if ($state -eq 'Failed') { # We'll just assume we're up-to-date if we failed to check today. - Write-Log -Message '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' -Level Verbose + $message = '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' + Write-Log -Message $message -Level Verbose $script:HasLatestVersion = $true # Clear out the job info (even though we don't need the result) @@ -81,22 +82,26 @@ function Invoke-UpdateCheck $script:HasLatestVersion = $latestVersion -eq $moduleVersion if ($script:HasLatestVersion) { - Write-Log "[$moduleName] update check complete. Running latest version: $latestVersion" -Level Verbose + $message = "[$moduleName] update check complete. Running latest version: $latestVersion" + Write-Log =Message $message -Level Verbose } elseif ($moduleVersion -gt $latestVersion) { - Write-Log "[$moduleName] update check complete. This version ($moduleVersion) is newer than the latest published version ($latestVersion)." -Level Verbose + $message = "[$moduleName] update check complete. This version ($moduleVersion) is newer than the latest published version ($latestVersion)." + Write-Log -Message $message -Level Verbose } else { - Write-Log "A newer version of $moduleName is available ($latestVersion). Your current version is $moduleVersion. Run 'Update-Module $moduleName' to get up-to-date." -Level Warning + $message = "A newer version of $moduleName is available ($latestVersion). Your current version is $moduleVersion. Run 'Update-Module $moduleName' to get up-to-date." + Write-Log -Message $message -Level Warning } } catch { # This could happen if the server returned back an invalid (non-XML) response for the request # for some reason. - Write-Log -Message "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." -Level Verbose + $message = "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." + Write-Log -Message $message -Level Verbose $script:HasLatestVersion = $true }