From e275505e406fcceb0d6de51f479f9d816e1d39f8 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Tue, 14 Jul 2020 15:46:27 -0700 Subject: [PATCH] Completed all UT's Also: Add abilility to download gists Add Set-GitHubGistStar helper Re-order Content/FileName parameters Switch to [System.IO.File]::ReadAllText() for reading files Add gistfile detection/error handling Add confirmation for removing gist files Add helpers: Remove-GitHubGistFile Set-GitHubGistFile Rename-GitHubGistFile --- Formatters/GitHubGists.Format.ps1xml | 22 +- GitHubGists.ps1 | 819 +++++++++++++++++++-- PowerShellForGitHub.psd1 | 6 + Tests/GitHubGistComments.tests.ps1 | 10 +- Tests/GitHubGists.tests.ps1 | 1017 +++++++++++++++++++++++++- 5 files changed, 1768 insertions(+), 106 deletions(-) diff --git a/Formatters/GitHubGists.Format.ps1xml b/Formatters/GitHubGists.Format.ps1xml index bb002f06..3e02fa85 100644 --- a/Formatters/GitHubGists.Format.ps1xml +++ b/Formatters/GitHubGists.Format.ps1xml @@ -120,6 +120,9 @@ + + + @@ -145,6 +148,11 @@ id + + + ($_.owner.login) + + description @@ -203,7 +211,19 @@ - ($_.owner.login) + # There appears to be a bug in the GitHub API. + # Documentation says that the property should alway be "user", + # but it switches between "owner" and "user" depending on if it's a property + # of a gist, or the direct result from a gist forks query. + # https://github.community/t/gist-api-v3-documentation-incorrect-for-forks/122545 + if ($null -eq $_.user) + { + $_.owner.login + } + else + { + $_.user.login + } diff --git a/GitHubGists.ps1 b/GitHubGists.ps1 index 2441b119..854c1354 100644 --- a/GitHubGists.ps1 +++ b/GitHubGists.ps1 @@ -36,6 +36,12 @@ filter Get-GitHubGist .PARAMETER UserName Gets public gists for the specified user. + .PARAMETER Path + Download the files that are part of the specified gist to this path. + + .PARAMETER Force + If downloading files, this will overwrite any files with the same name in the provided path. + .PARAMETER Current Gets the authenticated user's gists. @@ -100,11 +106,17 @@ filter Get-GitHubGist ValueFromPipelineByPropertyName, ParameterSetName='Id', Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Download', + Position = 1)] [Alias('GistId')] [ValidateNotNullOrEmpty()] [string] $Gist, [Parameter(ParameterSetName='Id')] + [Parameter(ParameterSetName='Download')] [ValidateNotNullOrEmpty()] [string] $Sha, @@ -114,10 +126,22 @@ filter Get-GitHubGist [Parameter(ParameterSetName='Id')] [switch] $Commits, - [Parameter(ParameterSetName='User')] + [Parameter( + Mandatory, + ParameterSetName='User')] [ValidateNotNullOrEmpty()] [string] $UserName, + [Parameter( + Mandatory, + ParameterSetName='Download', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $Path, + + [Parameter(ParameterSetName='Download')] + [switch] $Force, + [Parameter(ParameterSetName='Current')] [switch] $Current, @@ -145,7 +169,7 @@ filter Get-GitHubGist $description = [String]::Empty $outputType = $script:GitHubGistTypeName - if ($PSCmdlet.ParameterSetName -eq 'Id') + if ($PSCmdlet.ParameterSetName -in ('Id', 'Download')) { $telemetryProperties['ById'] = $true @@ -196,7 +220,7 @@ filter Get-GitHubGist $telemetryProperties['CurrentUser'] = $true $outputType = $script:GitHubGistTypeName - if (Test-GitHubAuthenticationConfigured -or (-not [String]::IsNullOrEmpty($AccessToken))) + if ((Test-GitHubAuthenticationConfigured) -or (-not [String]::IsNullOrEmpty($AccessToken))) { if ($Starred) { @@ -249,21 +273,156 @@ filter Get-GitHubGist 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) } - $result = Invoke-GHRestMethodMultipleResult @params + $result = (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubGistAdditionalProperties -TypeName $outputType) + + if ($PSCmdlet.ParameterSetName -eq 'Download') + { + Save-GitHubGist -GistObject $result -Path $Path -Force:$Force + } + else + { + if ($result.truncated -eq $true) + { + $message = @( + 'Response has been truncated. The API will only return the first 3000 gist results', + 'the first 300 files within the gist, and the first 1 Mb of an individual', + 'file. If the file has been truncated, you can call', + '(Invoke-WebRequest -UseBasicParsing -Method Get -Uri ).Content)', + 'where is the value of raw_url for the file in question. Be aware that', + 'for files larger than 10 Mb, you''ll need to clone the gist via the URL provided', + 'by git_pull_url.') + + Write-Log -Message ($message -join ' ') -Level Warning + } + + return $result + } +} + +function Save-GitHubGist +{ +<# + .SYNOPSIS + Downloads the contents of a gist to the specified file path. + + .DESCRIPTION + Downloads the contents of a gist to the specified file path. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER GistObject + The Gist PSCustomObject + + .PARAMETER Path + Download the files that are part of the specified gist to this path. + + .PARAMETER Force + If downloading files, this will overwrite any files with the same name in the provided path. + + .NOTES + Internal-only helper +#> + [CmdletBinding(PositionalBinding = $false)] + param( + [Parameter(Mandatory)] + [PSCustomObject] $GistObject, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $Path, + + [switch] $Force + ) + + # First, check to see if the response is missing files. + if ($GistObject.truncated) + { + $message = @( + 'Gist response has been truncated. The API will only return information on', + 'the first 300 files within a gist. To download this entire gist,', + 'you''ll need to clone it via the URL provided by git_pull_url', + "[$($GistObject.git_pull_url)].") + + Write-Log -Message ($message -join ' ') -Level Error + throw $message + } - if ($result.truncated -eq $true) + # Then check to see if there are files we won't be able to download + $files = $GistObject.files | Get-Member -Type NoteProperty | Select-Object -ExpandProperty Name + foreach ($fileName in $files) { - $message = @" -Response has been truncated. The API will only return the first 3000 gist results, -the first 300 files within an individual gist, and the first 1 Mb of an individual file. -If the file has been truncated, you can call (Invoke-WebRequest -UseBasicParsing -Method Get -Uri ).Content) -where is the value of raw_url for the file in question. Be aware that for files larger -than 10 Mb, you'll need to clone the gist via the URL provided by git_pull_url. -"@ - Write-Log -Message $message -Level Warning + if ($GistObject.files.$fileName.truncated -and + ($GistObject.files.$fileName.size -gt 10mb)) + { + $message = @( + "At least one file ($fileName) in this gist is larger than 10mb.", + 'In order to download this gist, you''ll need to clone it via the URL', + "provided by git_pull_url [$($GistObject.git_pull_url)].") + + Write-Log -Message ($message -join ' ') -Level Error + throw $message + } } - return ($result | Add-GitHubGistAdditionalProperties -TypeName $outputType) + # Finally, we're ready to directly save the non-truncated files, + # and download the ones that are between 1 - 10mb. + $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol + [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 + try + { + $headers = @{} + $AccessToken = Get-AccessToken -AccessToken $AccessToken + if (-not [String]::IsNullOrEmpty($AccessToken)) + { + $headers['Authorization'] = "token $AccessToken" + } + + $Path = Resolve-UnverifiedPath -Path $Path + $null = New-Item -Path $Path -ItemType Directory -Force + foreach ($fileName in $files) + { + $filePath = Join-Path -Path $Path -ChildPath $fileName + if ((Test-Path -Path $filePath -PathType Leaf) -and (-not $Force)) + { + $message = "File already exists at path [$filePath]. Choose a different path or specify -Force" + Write-Log -Message $message -Level Error + throw $message + } + + if ($GistObject.files.$fileName.truncated) + { + # Disable Progress Bar in function scope during Invoke-WebRequest + $ProgressPreference = 'SilentlyContinue' + + $webRequestParams = @{ + UseBasicParsing = $true + Method = 'Get' + Headers = $headers + Uri = $GistObject.files.$fileName.raw_url + OutFile = $filePath + } + + Invoke-WebRequest @webRequestParams + } + else + { + $stream = New-Object -TypeName System.IO.StreamWriter -ArgumentList ($filePath) + try + { + $stream.Write($GistObject.files.$fileName.content) + } + finally + { + $stream.Close() + } + } + } + } + finally + { + [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol + } } filter Remove-GitHubGist @@ -449,6 +608,90 @@ filter Copy-GitHubGist return (Invoke-GHRestMethod @params | Add-GitHubGistAdditionalProperties) } +filter Set-GitHubGistStar +{ +<# + .SYNOPSIS + Changes the starred state of a gist on GitHub for the current authenticated user. + + .DESCRIPTION + Changes the starred state of a gist on GitHub for the current authenticated user. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific Gist that you wish to change the starred state for. + + .PARAMETER Star + Include this switch to star the gist. Exclude the switch (or use -Star:$false) to + remove the star. + + .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. + + .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.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistDetail + GitHub.GistFork + + .EXAMPLE + Set-GitHubGistStar -Gist 6cad326836d38bd3a7ae -Star + + Stars octocat's "hello_world.rb" gist. + + .EXAMPLE + Set-GitHubGistStar -Gist 6cad326836d38bd3a7ae + + Unstars octocat's "hello_world.rb" gist. + + .EXAMPLE + Get-GitHubGist -Gist 6cad326836d38bd3a7ae | Set-GitHubGistStar -Star:$false + + Unstars octocat's "hello_world.rb" gist. + +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [switch] $Star, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + $PSBoundParameters.Remove('Star') + if ($Star) + { + return Add-GitHubGistStar @PSBoundParameters + } + else + { + return Remove-GitHubGistStar @PSBoundParameters + } +} + filter Add-GitHubGistStar { <# @@ -483,7 +726,7 @@ filter Add-GitHubGistStar .EXAMPLE Add-GitHubGistStar -Gist 6cad326836d38bd3a7ae - STars octocat's "hello_world.rb" gist. + Stars octocat's "hello_world.rb" gist. .EXAMPLE Star-GitHubGist -Gist 6cad326836d38bd3a7ae @@ -652,10 +895,6 @@ filter Test-GitHubGistStar Returns $true if the gist is starred, or $false if isn't starred or couldn't be checked (due to permissions or non-existence). - - .NOTES - For some reason, this does not currently seem to be working correctly - (even though it matches the spec: https://developer.github.com/v3/gists/#check-if-a-gist-is-starred). #> [CmdletBinding(PositionalBinding = $false)] [OutputType([bool])] @@ -714,12 +953,12 @@ filter New-GitHubGist Use this when you have multiple files that should be part of a gist, or when you simply want to reference an existing file on disk. - .PARAMETER Content - The content of a single file that should be part of the gist. - .PARAMETER FileName The name of the file that Content should be stored in within the newly created gist. + .PARAMETER Content + The content of a single file that should be part of the gist. + .PARAMETER Description A descriptive name for this gist. @@ -743,7 +982,7 @@ filter New-GitHubGist GitHub.GitDetail .EXAMPLE - New-GitHubGist -Content 'Body of my file.' -FileName 'sample.txt' -Description 'This is my gist!' -Public + New-GitHubGist -FileName 'sample.txt' -Content 'Body of my file.' -Description 'This is my gist!' -Public Creates a new public gist with a single file named 'sample.txt' that has the body of "Body of my file." @@ -778,14 +1017,14 @@ filter New-GitHubGist ParameterSetName='Content', Position = 1)] [ValidateNotNullOrEmpty()] - [string] $Content, + [string] $FileName, [Parameter( Mandatory, ParameterSetName='Content', Position = 2)] [ValidateNotNullOrEmpty()] - [string] $FileName, + [string] $Content, [string] $Description, @@ -806,14 +1045,14 @@ filter New-GitHubGist foreach ($path in $File) { $path = Resolve-UnverifiedPath -Path $path - if (-not (Test-Path -Path $path)) + if (-not (Test-Path -Path $path -PathType Leaf)) { $message = "Specified file [$path] could not be found or was inaccessible." Write-Log -Message $message -Level Error throw $message } - $content = Get-Content -Path $path -Raw -Encoding UTF8 + $content = [System.IO.File]::ReadAllText($path) $fileName = (Get-Item -Path $path).Name if ($files.ContainsKey($fileName)) @@ -838,6 +1077,13 @@ filter New-GitHubGist $files[$FileName] = @{ 'content' = $Content } } + if (($files.Keys.StartsWith('gistfile') | Where-Object { $_ -eq $true }).Count -gt 0) + { + $message = "Don't name your files starting with 'gistfile'. This is the format of the automatic naming scheme that Gist uses internally." + Write-Log -Message $message -Level Error + throw $message + } + $hashBody = @{ 'description' = $Description 'public' = $Public.ToBool() @@ -896,6 +1142,9 @@ filter Set-GitHubGist .PARAMETER Description New description for this gist. + .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. @@ -922,9 +1171,9 @@ filter Set-GitHubGist Updates the description for the specified gist. .EXAMPLE - Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Delete 'hello_world.rb' + Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Delete 'hello_world.rb' -Force - Deletes the 'hello_world.rb' file from the specified gist. + Deletes the 'hello_world.rb' file from the specified gist without prompting for confirmation. .EXAMPLE Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Delete 'hello_world.rb' -Description 'This is my newer description' @@ -961,6 +1210,8 @@ filter Set-GitHubGist [string] $Description, + [switch] $Force, + [string] $AccessToken, [switch] $NoStatus @@ -972,10 +1223,18 @@ filter Set-GitHubGist $files = @{} + $shouldProcessMessage = 'Update gist' + # Mark the files that should be deleted. - foreach ($toDelete in $Delete) + if ($Delete.Count -gt 0) { - $files[$toDelete] = $null + $ConfirmPreference = 'Low' + $shouldProcessMessage = 'Update gist (and remove files)' + + foreach ($toDelete in $Delete) + { + $files[$toDelete] = $null + } } # Then figure out which ones need content updates and/or file renames @@ -998,20 +1257,20 @@ filter Set-GitHubGist { if (-not [String]::IsNullOrWhiteSpace($providedContent)) { - $message = "When updating a file [$currentFileName], you cannot provide both a path to a file [$providedPath] and the raw content." + $message = "When updating a file [$currentFileName], you cannot provide both a path to a file [$providedFilePath] and the raw content." Write-Log -Message $message -Level Error throw $message } - $providedPath = Resolve-Path -Path $providedPath - if (-not (Test-Path -Path $providedPath)) + $providedFilePath = Resolve-Path -Path $providedFilePath + if (-not (Test-Path -Path $providedFilePath -PathType Leaf)) { - $message = "Specified file [$providedPath] could not be found or was inaccessible." + $message = "Specified file [$providedFilePath] could not be found or was inaccessible." Write-Log -Message $message -Level Error throw $message } - $newContent = Get-Content -Path $providedFilePath -Raw -Encoding UTF8 + $newContent = [System.IO.File]::ReadAllText($providedFilePath) $files[$currentFileName] = @{ 'content' = $newContent } } @@ -1027,11 +1286,17 @@ filter Set-GitHubGist if (-not [String]::IsNullOrWhiteSpace($Description)) { $hashBody['description'] = $Description } if ($files.Keys.count -gt 0) { $hashBody['files'] = $files } - if (-not $PSCmdlet.ShouldProcess($Gist, 'Update gist')) + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (-not $PSCmdlet.ShouldProcess($Gist, $shouldProcessMessage)) { return } + $ConfirmPreference = 'None' $params = @{ 'UriFragment' = "gists/$Gist" 'Body' = (ConvertTo-Json -InputObject $hashBody) @@ -1060,48 +1325,413 @@ filter Set-GitHubGist } } -filter Add-GitHubGistAdditionalProperties +function Set-GitHubGistFile { <# .SYNOPSIS - Adds type name and additional properties to ease pipelining to GitHub Gist objects. + Updates content of file(s) in an existing gist on GitHub, + or adds them if they aren't already part of the gist. - .PARAMETER InputObject - The GitHub object to add additional properties to. + .DESCRIPTION + Updates content of file(s) in an existing gist on GitHub, + or adds them if they aren't already part of the gist. - .PARAMETER TypeName - The type that should be assigned to the object. + This is a helper function built on top of Set-GitHubGist. - .INPUTS - [PSCustomObject] + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - .OUTPUTS + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER File + An array of filepaths that should be part of this gist. + Use this when you have multiple files that should be part of a gist, or when you simply + want to reference an existing file on disk. + + .PARAMETER FileName + The name of the file that Content should be stored in within the newly created gist. + + .PARAMETER Content + The content of a single file that should be part of the gist. + + .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. + + .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.Gist + GitHub.GistComment GitHub.GistCommit GitHub.GistDetail GitHub.GistFork + + .OUTPUTS + GitHub.GistDetail + + .EXAMPLE + Set-GitHubGistFile -Gist 1234567 -Content 'Body of my file.' -FileName 'sample.txt' + + Adds a file named 'sample.txt' that has the body of "Body of my file." to the existing + specified gist, or updates the contents of 'sample.txt' in the gist if is already there. + + .EXAMPLE + Set-GitHubGistFile -Gist 1234567 -File 'c:\files\foo.txt' + + Adds the file 'foo.txt' to the existing specified gist, or updates its content if it + is already there. + + .EXAMPLE + Set-GitHubGistFile -Gist 1234567 -File ('c:\files\foo.txt', 'c:\other\bar.txt', 'c:\octocat.ps1') + + Adds all three files to the existing specified gist, or updates the contents of the files + in the gist if they are already there. #> - [CmdletBinding()] - [OutputType({$script:GitHubGistTypeName})] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Content', + PositionalBinding = $false)] [OutputType({$script:GitHubGistDetailTypeName})] - [OutputType({$script:GitHubGistFormTypeName})] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + [Alias('Add-GitHubGistFile')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="This is a helper method for Set-GitHubGist which will handle ShouldProcess.")] param( [Parameter( Mandatory, - ValueFromPipeline)] - [AllowNull()] - [AllowEmptyCollection()] - [PSCustomObject[]] $InputObject, - + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] [ValidateNotNullOrEmpty()] - [string] $TypeName = $script:GitHubGistTypeName - ) + [string] $Gist, - if ($TypeName -eq $script:GitHubGistCommitTypeName) - { - return Add-GitHubGistCommitAdditionalProperties -InputObject $InputObject - } + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='FileRef', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string[]] $File, + + [Parameter( + Mandatory, + ParameterSetName='Content', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $FileName, + + [Parameter( + Mandatory, + ParameterSetName='Content', + Position = 3)] + [ValidateNotNullOrEmpty()] + [string] $Content, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + begin + { + $files = @{} + } + + process + { + foreach ($path in $File) + { + $path = Resolve-UnverifiedPath -Path $path + if (-not (Test-Path -Path $path -PathType Leaf)) + { + $message = "Specified file [$path] could not be found or was inaccessible." + Write-Log -Message $message -Level Error + throw $message + } + + $fileName = (Get-Item -Path $path).Name + $files[$fileName] = @{ 'filePath' = $path } + } + } + + end + { + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + if ($PSCmdlet.ParameterSetName -eq 'Content') + { + $files[$FileName] = @{ 'content' = $Content } + } + + $params = @{ + 'Gist' = $Gist + 'Update' = $files + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Set-GitHubGist @params) + } +} + +function Remove-GitHubGistFile +{ +<# + .SYNOPSIS + Removes one or more files from an existing gist on GitHub. + + .DESCRIPTION + Removes one or more files from an existing gist on GitHub. + + This is a helper function built on top of Set-GitHubGist. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER FileName + An array of filenames (no paths, just names) to remove from the gist. + + .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. + + .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.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistDetail + GitHub.GistFork + + .OUTPUTS + GitHub.GistDetail + + .EXAMPLE + Remove-GitHubGistFile -Gist 1234567 -FileName ('foo.txt') + + Removes the file 'foo.txt' from the specified gist. + + .EXAMPLE + Remove-GitHubGistFile -Gist 1234567 -FileName ('foo.txt') -Force + + Removes the file 'foo.txt' from the specified gist without prompting for confirmation. + + .EXAMPLE + @('foo.txt', 'bar.txt') | Remove-GitHubGistFile -Gist 1234567 + + Removes the files 'foo.txt' and 'bar.txt' from the specified gist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistDetailTypeName})] + [Alias('Delete-GitHubGistFile')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="This is a helper method for Set-GitHubGist which will handle ShouldProcess.")] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + ValueFromPipeline, + Position = 2)] + [ValidateNotNullOrEmpty()] + [string[]] $FileName, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + begin + { + $files = @() + } + + process + { + foreach ($name in $FileName) + { + $files += $name + } + } + + end + { + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + $params = @{ + 'Gist' = $Gist + 'Delete' = $files + 'Force' = $Force + 'Confirm' = ($Confirm -eq $true) + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Set-GitHubGist @params) + } +} + +filter Rename-GitHubGistFile +{ +<# + .SYNOPSIS + Renames a file in a gist on GitHub. + + .DESCRIPTION + Renames a file in a gist on GitHub. + + This is a helper function built on top of Set-GitHubGist. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER FileName + The current file in the gist to be renamed. + + .PARAMETER NewName + The new name of the file for the gist. + + .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. + + .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.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistDetail + GitHub.GistFork + + .OUTPUTS + GitHub.GistDetail + + .EXAMPLE + Rename-GitHubGistFile -Gist 1234567 -FileName 'foo.txt' -NewName 'bar.txt' + + Renames the file 'foo.txt' to 'bar.txt' in the specified gist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistDetailTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="This is a helper method for Set-GitHubGist which will handle ShouldProcess.")] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $FileName, + + [Parameter( + Mandatory, + Position = 3)] + [ValidateNotNullOrEmpty()] + [string] $NewName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + $params = @{ + 'Gist' = $Gist + 'Update' = @{$FileName = @{ 'fileName' = $NewName }} + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Set-GitHubGist @params) +} + +filter Add-GitHubGistAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Gist 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.Gist + GitHub.GistCommit + GitHub.GistDetail + GitHub.GistFork +#> + [CmdletBinding()] + [OutputType({$script:GitHubGistTypeName})] + [OutputType({$script:GitHubGistDetailTypeName})] + [OutputType({$script:GitHubGistFormTypeName})] + [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:GitHubGistTypeName + ) + + if ($TypeName -eq $script:GitHubGistCommitTypeName) + { + return Add-GitHubGistCommitAdditionalProperties -InputObject $InputObject + } + elseif ($TypeName -eq $script:GitHubGistForkTypeName) + { + return Add-GitHubGistForkAdditionalProperties -InputObject $InputObject + } foreach ($item in $InputObject) { @@ -1119,15 +1749,14 @@ filter Add-GitHubGistAdditionalProperties } } - foreach ($fork in $item.forks) + if ($null -ne $item.forks) { - Add-Member -InputObject $fork -Name 'GistId' -Value $fork.id -MemberType NoteProperty -Force - $null = Add-GitHubUserAdditionalProperties -InputObject $fork.user + $item.forks = Add-GitHubGistForkAdditionalProperties -InputObject $item.forks } - foreach ($entry in $item.history) + if ($null -ne $item.history) { - $null = Add-GitHubGistCommitAdditionalProperties -InputObject $entry + $item.history = Add-GitHubGistCommitAdditionalProperties -InputObject $item.history } } @@ -1175,7 +1804,7 @@ filter Add-GitHubGistCommitAdditionalProperties if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) { $hostName = $(Get-GitHubConfiguration -Name 'ApiHostName') - if ($item.uri -match "^https?://(?:www\.|api\.|)$hostName/gists/([^/]+)/(.+)$") + if ($item.url -match "^https?://(?:www\.|api\.|)$hostName/gists/([^/]+)/(.+)$") { $id = $Matches[1] $sha = $Matches[2] @@ -1193,6 +1822,62 @@ filter Add-GitHubGistCommitAdditionalProperties $null = Add-GitHubUserAdditionalProperties -InputObject $item.user } + Write-Output $item + } +} + +filter Add-GitHubGistForkAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Gist Fork 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.GistFork +#> + [CmdletBinding()] + [OutputType({$script:GitHubGistForkTypeName})] + [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:GitHubGistForkTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'GistId' -Value $item.id -MemberType NoteProperty -Force + + # See here for why we need to work with both 'user' _and_ 'owner': + # https://github.community/t/gist-api-v3-documentation-incorrect-for-forks/122545 + @('user', 'owner') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + } + Write-Output $item } } \ No newline at end of file diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 90911a44..f5d17fee 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -136,6 +136,7 @@ 'Remove-GitHubComment', 'Remove-GitHubGist', 'Remove-GitHubGistComment', + 'Remove-GitHubGistFile', 'Remove-GitHubGistStar', 'Remove-GitHubIssueComment', 'Remove-GitHubIssueLabel', @@ -145,6 +146,7 @@ 'Remove-GitHubProjectCard', 'Remove-GitHubProjectColumn', 'Remove-GitHubRepository', + 'Rename-GitHubGistFile', 'Rename-GitHubRepository', 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', @@ -153,6 +155,8 @@ 'Set-GitHubContent', 'Set-GitHubGist', 'Set-GitHubGistComment', + 'Set-GitHubGistFile', + 'Set-GitHubGistStar', 'Set-GitHubIssue', 'Set-GitHubIssueComment', 'Set-GitHubIssueLabel', @@ -174,9 +178,11 @@ ) AliasesToExport = @( + 'Add-GitHubGistFile', 'Delete-GitHubComment', 'Delete-GitHubGist', 'Delete-GitHubGistComment', + 'Delete-GitHubGistFile', 'Delete-GitHubIssueComment', 'Delete-GitHubLabel', 'Delete-GitHubMilestone', diff --git a/Tests/GitHubGistComments.tests.ps1 b/Tests/GitHubGistComments.tests.ps1 index de64cd3c..ab14b510 100644 --- a/Tests/GitHubGistComments.tests.ps1 +++ b/Tests/GitHubGistComments.tests.ps1 @@ -24,7 +24,7 @@ try Context 'By parameters' { BeforeAll { - $gist = New-GitHubGist -Content 'Sample text' -Filename 'sample.txt' + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' $body = 'Comment body' } @@ -94,7 +94,7 @@ try Context 'Gist on the pipeline' { BeforeAll { - $gist = New-GitHubGist -Content 'Sample text' -Filename 'sample.txt' + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' $body = 'Comment body' } @@ -180,7 +180,7 @@ try Describe 'New-GitHubGistComment' { BeforeAll { - $gist = New-GitHubGist -Content 'Sample text' -Filename 'sample.txt' + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' $body = 'Comment body' } @@ -219,7 +219,7 @@ try Describe 'New-GitHubGistComment' { BeforeAll { - $gist = New-GitHubGist -Content 'Sample text' -Filename 'sample.txt' + $gist = New-GitHubGist -Filename 'sample.txt' -Content 'Sample text' $body = 'Comment body' $updatedBody = 'Updated comment body' } @@ -288,7 +288,7 @@ try Describe 'Remove-GitHubGistComment' { BeforeAll { - $gist = New-GitHubGist -Content 'Sample text' -Filename 'sample.txt' + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' $body = 'Comment body' } diff --git a/Tests/GitHubGists.tests.ps1 b/Tests/GitHubGists.tests.ps1 index 852152e3..cc5df85d 100644 --- a/Tests/GitHubGists.tests.ps1 +++ b/Tests/GitHubGists.tests.ps1 @@ -15,81 +15,438 @@ param() $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') +filter New-LargeFile +{ +<# + .SYNOPSIS + Creates a large dummy file with random conntent + + .DESCRIPTION + Creates a large dummy file with random conntent + Credits for the random content creation logic goes to Robert Robelo + + .PARAMETER Path + The full path to the file to create. + + .PARAMETER SizeMB + The size of the random file to be genrated. Default is one MB + + .PARAMETER Type + The type of file should be created. + + .PARAMETER Force + Will allow this to overwrite the target file if it already exists. + + .EXAMPLE + New-LargeFile -Path C:\Temp\LF\bigfile.txt -SizeMB 10 +#> + + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(ValueFromPipeline)] + [String] $Path, + + [ValidateRange(1, 5120)] + [UInt16] $SizeMB = 1, + + [ValidateSet('Text', 'Binary')] + [string] $Type = 'Text', + + [switch] $Force + ) + + $tempFile = New-TemporaryFile + + if ($Type -eq 'Text') + { + $streamWriter = New-Object -TypeName IO.StreamWriter -ArgumentList ($tempFile) + try + { + # get a 64 element Char[]; I added the - and \n to have 64 chars + [char[]]$chars = 'azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN0123456789-\n' + 1..$SizeMB | ForEach-Object { + # get 1MB of chars from 4 256KB strings + 1..4 | ForEach-Object { + $randomizedChars = $chars | Get-Random -Count $chars.Count + + # repeat random string 4096 times to get a 256KB string + $output = (-join $randomizedChars) * 4kb + + # write 256KB string to file + $streamWriter.Write($output) + + # release resources + Clear-Variable -Name @('randomizedChars', 'output') + } + } + } + catch + { + Remove-File -Path $tempFile -ErrorAction SilentlyContinue + } + finally + { + $streamWriter.Close() + $streamWriter.Dispose() + + # Force the immediate garbage collection of allocated resources + [GC]::Collect() + } + } + else + { + $content = New-Object -TypeName Byte[] -ArgumentList ($SizeMB * 1mb) + (New-Object -TypeName Random).NextBytes($content) + [IO.File]::WriteAllBytes($tempFile, $content) + } + + try + { + if ($PSBoundParameters.ContainsKey('Path')) + { + return (Move-Item -Path $tempFile -Destination $Path -Force:$Force) + } + else + { + return (Get-Item -Path $tempFile) + } + } + catch + { + Remove-File -Path $tempFile -ErrorAction SilentlyContinue + } +} + try { Describe 'Get-GitHubGist' { Context 'Specific Gist' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + $gist = Get-GitHubGist -Gist $id + It 'Should be the expected gist' { + $gist.id | Should -Be $id + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $gist.history[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistCommit' + $gist.forks[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistFork' + } + + $gist = $gist | Get-GitHubGist + It 'Should be the expected gist with the gist on the pipeline' { + $gist.id | Should -Be $id + } } - Context 'Specific Gist with Sha' { + Context 'Commits and specific Gist with Sha' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + + $gist = Get-GitHubGist -Gist $id + $commits = Get-GitHubGist -Gist $gist.id -Commits + + It 'Should have multiple commits' { + $commits.Count | Should -BeGreaterThan 1 + } + + It 'Should have the expected type and additional properties' { + foreach ($commit in $commits) + { + $commit.PSObject.TypeNames[0] | Should -Be 'GitHub.GistCommit' + $commit.GistId | Should -Be $gist.id + $commit.Sha | Should -Be $commit.version + $commit.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $oldestSha = $commits | Sort-Object -Property 'committed_at' | Select-Object -First 1 + + $firstGistCommit = Get-GitHubGist -Gist $gist.id -Sha $oldestSha.version + It 'Should be the expected commit' { + $firstGistCommit.created_at | Should -Be $oldestSha.committed_at + } + + It 'Should have the expected type and additional properties' { + $firstGistCommit.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $firstGistCommit.GistId | Should -Be $firstGistCommit.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $gist.history[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistCommit' + $gist.forks[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistFork' + } + + It 'Should fail if we specify Sha _and_ Commits' { + { Get-GitHubGist -Gist $gist.id -Commits -Sha $oldestSha.version } | Should -Throw + } + + It 'Should fail if we specify Sha _and_ Forks' { + { Get-GitHubGist -Gist $gist.id -Forks -Sha $oldestSha.version } | Should -Throw + } + + $firstGistCommit = $gist | Get-GitHubGist -Sha $oldestSha.version + It 'Should be the expected gist commit with the gist on the pipeline' { + $firstGistCommit.created_at | Should -Be $oldestSha.committed_at + } + + $firstGistCommit = $firstGistCommit | Get-GitHubGist + It 'Should be the expected gist commit with the gist commit on the pipeline' { + $firstGistCommit.created_at | Should -Be $oldestSha.committed_at + } } Context 'Forks' { - # Make sure you check for error when Sha specified - } + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + + $gist = Get-GitHubGist -Gist $id + $forks = Get-GitHubGist -Gist $gist.id -Forks + + It 'Should have multiple forks' { + $forks.Count | Should -BeGreaterThan 1 + } + + It 'Should have the expected type and additional properties' { + foreach ($fork in $forks) + { + $fork.PSObject.TypeNames[0] | Should -Be 'GitHub.GistFork' + $fork.GistId | Should -Be $fork.id + $fork.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $forks = $gist | Get-GitHubGist -Forks - Context 'Commits' { - # Make sure you check for error when Sha specified + It 'Should have multiple forks when gist is on the pipeline' { + $forks.Count | Should -BeGreaterThan 1 + } } Context 'All gists for a specific user' { - } + $username = 'octocat' + $gists = Get-GitHubGist -UserName $username - Context 'All starred gists for a specific user' { + It 'Should have multiple gists' { + $gists.Count | Should -BeGreaterThan 1 + } + + It 'Should have the expected type and additional properties' { + foreach ($gist in $gists) + { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $since = (Get-Date -Date '01/01/2016') + $sinceGists = Get-GitHubGist -UserName $username -Since $since + It 'Should have fewer results with using the since parameter' { + $sinceGists.Count | Should -BeGreaterThan 0 + $sinceGists.Count | Should -BeLessThan $gists.Count + } } Context 'All gists for the current authenticated user' { + $gist = New-GitHubGist -Filename 'sample.txt' -Content 'Sample text' + $gists = @(Get-GitHubGist) + It 'Should at least one gist including the one just created' { + $gists.Count | Should -BeGreaterOrEqual 1 + $gists.id | Should -Contain $gist.id + } + + It 'Should have the expected type and additional properties' { + foreach ($gist in $gists) + { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } } Context 'All gists for the current authenticated user, but not authenticated' { + # This would just be testing that an exception is thrown. + # There's no easy way to cover unauthenticated sessions in the UT's, + # so we'll just accept the lower code coverage here. + } + + Context 'All starred gists for the current authenticated user' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + Add-GitHubGistStar -Gist $id + + $gists = @(Get-GitHubGist -Starred) + It 'Should include the one we just starred' { + $gists.Count | Should -BeGreaterOrEqual 1 + $gists.id | Should -Contain $id + } + + Remove-GitHubGistStar -Gist $id } Context 'All starred gists for the current authenticated user, but not authenticated' { + # This would just be testing that an exception is thrown. + # There's no easy way to cover unauthenticated sessions in the UT's, + # so we'll just accept the lower code coverage here. } Context 'All public gists' { + # This would require 100 queries, taking over 2 minutes. + # Given the limited additional value that we'd get from this additional test relative + # to the time it would take to execute, we'll just accept the lower code coverage here. } } - Describe 'Get-GitHubGist' { - Context 'By files' { + Describe 'Get-GitHubGist/Download' { + BeforeAll { + # To get access to New-TemporaryDirectory + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Helpers.ps1') + $tempPath = New-TemporaryDirectory + } + + AfterAll { + Remove-Item -Path $tempPath -Recurse -ErrorAction SilentlyContinue -Force + } + + Context 'Download gist content' { BeforeAll { $tempFile = New-TemporaryFile $fileA = "$($tempFile.FullName).ps1" Move-Item -Path $tempFile -Destination $fileA - Out-File -FilePath $fileA -InputObject "fileA content" -Encoding utf8 + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 $tempFile = New-TemporaryFile $fileB = "$($tempFile.FullName).txt" Move-Item -Path $tempFile -Destination $fileB - Out-File -FilePath $fileB -InputObject "fileB content" -Encoding utf8 + $fileBName = (Get-Item -Path $fileB).Name + $fileBContent = 'fileB content' + Out-File -FilePath $fileB -InputObject $fileBContent -Encoding utf8 - $description = 'my description' + $tempFile = New-LargeFile -SizeMB 1 + $twoMegFile = "$($tempFile.FullName).bin" + Move-Item -Path $tempFile -Destination $twoMegFile + $twoMegFileName = (Get-Item -Path $twoMegFile).Name + + $gist = @($fileA, $fileB, $twoMegFile) | New-GitHubGist } AfterAll { - @($fileA, $fileB) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + $gist | Remove-GitHubGist -Force + @($fileA, $fileB, $twoMegFile) | + Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null } - $gist = New-GitHubGist -File $fileA -Description $description -Public - It 'Should have the expected result' { + It 'Should have no files at the download path' { + @(Get-ChildItem -Path $tempPath).Count | Should -Be 0 + } + Get-GitHubGist -Gist $gist.id -Path $tempPath + It 'Should download all of the files' { + @(Get-ChildItem -Path $tempPath).Count | Should -Be 3 + [System.IO.File]::ReadAllText($fileA).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileAName)).Trim()) + [System.IO.File]::ReadAllText($fileB).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileBName)).Trim()) + (Get-FileHash -Path $twoMegFile).Hash | + Should -Be (Get-FileHash -Path (Join-Path -Path $tempPath -ChildPath $twoMegFileName)).Hash } - It 'Should have the expected type and additional properties' { - $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' - $gist.GistId | Should -Be $gist.id - $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $gist | Get-GitHubGist -Path $tempPath -Force + It 'Should download all of the files with the gist on the pipeline and -Force' { + @(Get-ChildItem -Path $tempPath).Count | Should -Be 3 + [System.IO.File]::ReadAllText($fileA).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileAName)).Trim()) + [System.IO.File]::ReadAllText($fileB).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileBName)).Trim()) + (Get-FileHash -Path $twoMegFile).Hash | + Should -Be (Get-FileHash -Path (Join-Path -Path $tempPath -ChildPath $twoMegFileName)).Hash } } - } - Describe 'Remove-GitHubGist' { - Context 'By files' { + Context 'More than 300 files' { BeforeAll { + $files = @() + 1..301 | ForEach-Object { + $tempFile = New-TemporaryFile + $file = "$($tempFile.FullName)-$_.ps1" + Move-Item -Path $tempFile -Destination $file + $fileContent = "file-$_ content" + Out-File -FilePath $file -InputObject $fileContent -Encoding utf8 + $files += $file + } } AfterAll { + $files | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + # Marking this test to skip. It works just fine, but takes 26 second to execute. + # Not worth it for the moderate improvement to code coverage. + It 'Should throw an exception because there are too many files' -Skip:(-not ([string]::IsNullOrEmpty($env:ciAccessToken))) { + $gist = $files | New-GitHubGist + { $gist | Get-GitHubGist -Path $tempPath -Force } | Should -Throw + $gist | Remove-GitHubGist -Force + } + + } + + Context 'Download gist content' { + BeforeAll { + $tempFile = New-LargeFile -SizeMB 10 + $tenMegFile = "$($tempFile.FullName).bin" + Move-Item -Path $tempFile -Destination $tenMegFile + $tenMegFileName = (Get-Item -Path $tenMegFile).Name + } + + AfterAll { + @($tenMegFile) | + Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + # Marking this test to skip. It works just fine, but takes 82 second to execute. + # Not worth it for the moderate improvement to code coverage. + It 'Should throw an exception because the file is too large to download' -Skip:(-not ([string]::IsNullOrEmpty($env:ciAccessToken))) { + $gist = $tenMegFile | New-GitHubGist + { $gist | Get-GitHubGist -Path $tempPath -Force } | Should -Throw + $gist | Remove-GitHubGist -Force + } + } + } + + Describe 'Remove-GitHubGist' { + Context 'With parameters' { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + It 'Should be there' { + { Get-GitHubGist -Gist $gist.id } | Should -Not -Throw + } + + It 'Should remove the gist successfully' { + { Remove-GitHubGist -Gist $gist.id -Force } | Should -Not -Throw + } + + It 'Should be removed' { + { Get-GitHubGist -Gist $gist.id } | Should -Throw + } + } + + Context 'With the gist on the pipeline' { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + It 'Should be there' { + { $gist | Get-GitHubGist } | Should -Not -Throw + } + + It 'Should remove the gist successfully' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + It 'Should be removed' { + { $gist | Get-GitHubGist } | Should -Throw } } } @@ -148,9 +505,9 @@ try } } - Describe 'Add/Remove/Test-GitHubGistStar' { + Describe 'Add/Remove/Set/Test-GitHubGistStar' { BeforeAll { - $gist = New-GitHubGist -Content 'Sample text' -Filename 'sample.txt' + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' } AfterAll { @@ -165,18 +522,25 @@ try Add-GitHubGistStar -Gist $gist.id $starred = Test-GitHubGistStar -Gist $gist.id - It 'Should now be starred yet' { + It 'Should now be starred' { $starred | Should -BeTrue } + Remove-GitHubGistStar -Gist $gist.id $starred = Test-GitHubGistStar -Gist $gist.id - It 'Should not be starred yet' { + It 'Should no longer be starred' { + $starred | Should -BeFalse + } + + Set-GitHubGistStar -Gist $gist.id -Star + $starred = Test-GitHubGistStar -Gist $gist.id + It 'Should now be starred' { $starred | Should -BeTrue } - Remove-GitHubGistStar -Gist $gist.id + Set-GitHubGistStar -Gist $gist.id $starred = Test-GitHubGistStar -Gist $gist.id - It 'Should no longer be starred yet' { + It 'Should no longer be starred' { $starred | Should -BeFalse } } @@ -189,39 +553,626 @@ try $gist | Add-GitHubGistStar $starred = $gist | Test-GitHubGistStar - It 'Should now be starred yet' { + It 'Should now be starred' { $starred | Should -BeTrue } + $gist | Remove-GitHubGistStar + $starred = $gist | Test-GitHubGistStar + It 'Should no longer be starred' { + $starred | Should -BeFalse + } + + $gist | Set-GitHubGistStar -Star $starred = $gist | Test-GitHubGistStar - It 'Should not be starred yet' { + It 'Should now be starred' { $starred | Should -BeTrue } - $gist | Remove-GitHubGistStar + $gist | Set-GitHubGistStar $starred = $gist | Test-GitHubGistStar - It 'Should no longer be starred yet' { + It 'Should no longer be starred' { $starred | Should -BeFalse } } } Describe 'New-GitHubGist' { + Context 'By content' { + BeforeAll { + $content = 'This is my content' + $filename = 'sample.txt' + $description = 'my description' + } + + $gist = New-GitHubGist -FileName $filename -Content $content -Public + It 'Should have the expected result' { + $gist.public | Should -BeTrue + $gist.description | Should -BeNullOrEmpty + $gist.files.$filename.content | Should -Be $content + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + $gist = New-GitHubGist -FileName $filename -Content $content -Description $description -Public:$false + It 'Should have the expected result' { + $gist.public | Should -BeFalse + $gist.description | Should -Be $description + $gist.files.$filename.content | Should -Be $content + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should error if file starts with "gistfile"' { + { New-GitHubGist -FileName 'gistfile1' -Content $content } | Should -Throw + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + Context 'By files' { BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).ps1" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + + $tempFile = New-TemporaryFile + $fileB = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileB + $fileBName = (Get-Item -Path $fileB).Name + $fileBContent = 'fileB content' + Out-File -FilePath $fileB -InputObject $fileBContent -Encoding utf8 + + $description = 'my description' } AfterAll { + @($fileA, $fileB) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = New-GitHubGist -File @($fileA, $fileB) -Public + It 'Should have the expected result' { + $gist.public | Should -BeTrue + $gist.description | Should -BeNullOrEmpty + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + $gist.files.$fileBName.content.Trim() | Should -Be $fileBContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + $gist = New-GitHubGist -File @($fileA, $fileB) -Description $description -Public:$false + It 'Should have the expected result' { + $gist.public | Should -BeFalse + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + $gist.files.$fileBName.content.Trim() | Should -Be $fileBContent + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + $gist = @($fileA, $fileB) | New-GitHubGist -Description $description -Public:$false + It 'Should have the expected result with the files on the pipeline' { + $gist.public | Should -BeFalse + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + $gist.files.$fileBName.content.Trim() | Should -Be $fileBContent } } } Describe 'Set-GitHubGist' { - Context 'By files' { + BeforeAll { + $fileAName = 'foo.txt' + $fileAContent = 'foo content' + $fileAUpdatedContent = 'foo updated content' + $fileANewName = 'gamma.txt' + + $fileBName = 'bar.txt' + $fileBContent = 'bar content' + $fileBUpdatedContent = 'bar updated content' + + $fileCName = 'alpha.txt' + $fileCContent = 'alpha content' + $fileCUpdatedContent = 'alpha updated content' + $fileCNewName = 'gamma.txt' + + $tempFile = New-TemporaryFile + $fileD = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileD + $fileDName = (Get-Item -Path $fileD).Name + $fileDContent = 'fileD content' + Out-File -FilePath $fileD -InputObject $fileDContent -Encoding utf8 + $fileDUpdatedContent = 'fileD updated content' + + $description = 'my description' + $updatedDescription = 'updated description' + } + + AfterAll { + @($fileD) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + Context 'With parameters' { + BeforeAll { + $gist = New-GitHubGist -FileName $fileAName -Content $fileAContent -Description $description + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $fileBName -Content $fileBContent + It 'Should be in the expected, original state' { + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content | Should -Be $fileAContent + $gist.files.$fileBName.content | Should -Be $fileBContent + } + + $setParams = @{ + Gist = $gist.id + Description = $updatedDescription + Delete = @($fileBName) + Update = @{ + $fileAName = @{ + fileName = $fileANewName + content = $fileAUpdatedContent + } + $fileCName = @{ content = $fileCContent } + $fileDName = @{ filePath = $fileD } + } + } + + $gist = Set-GitHubGist @setParams -Force + It 'Should have been properly updated' { + $gist.description | Should -Be $updatedDescription + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 3 + $gist.files.$fileAName | Should -BeNullOrEmpty + $gist.files.$fileANewName.content | Should -Be $fileAContent + $gist.files.$fileBName | Should -BeNullOrEmpty + $gist.files.$fileCName.content | Should -Be $fileCContent + $gist.files.$fileDName.content.Trim() | Should -Be $fileDContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $setParams = @{ + Gist = $gist.id + Update = @{ + $fileDName = @{ + content = 'updated content' + filePath = $fileD + } + } + } + + It 'Should throw if updating a file with both a filePath and content' { + { $gist = Set-GitHubGist @setParams } | Should -Throw + } + } + + Context 'With the gist on the pipeline' { + BeforeAll { + $gist = New-GitHubGist -FileName $fileAName -Content $fileAContent -Description $description + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $fileBName -Content $fileBContent + It 'Should be in the expected, original state' { + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content | Should -Be $fileAContent + $gist.files.$fileBName.content | Should -Be $fileBContent + } + + $setParams = @{ + Description = $updatedDescription + Delete = @($fileBName) + Update = @{ + $fileAName = @{ + fileName = $fileANewName + content = $fileAUpdatedContent + } + $fileCName = @{ content = $fileCContent } + $fileDName = @{ filePath = $fileD } + } + } + + $gist = $gist | Set-GitHubGist @setParams -Confirm:$false + It 'Should have been properly updated' { + $gist.description | Should -Be $updatedDescription + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 3 + $gist.files.$fileAName | Should -BeNullOrEmpty + $gist.files.$fileANewName.content | Should -Be $fileAContent + $gist.files.$fileBName | Should -BeNullOrEmpty + $gist.files.$fileCName.content | Should -Be $fileCContent + $gist.files.$fileDName.content.Trim() | Should -Be $fileDContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $setParams = @{ + Update = @{ + $fileDName = @{ + content = 'updated content' + filePath = $fileD + } + } + } + + It 'Should throw if updating a file with both a filePath and content' { + { $gist = $gist | Set-GitHubGist @setParams } | Should -Throw + } + } + } + + Describe 'Set-GitHubGistFile' { + BeforeAll { + $origFileName = 'foo.txt' + $origContent = 'original content' + $updatedOrigContent = 'updated content' + + $newFileName = 'bar.txt' + $newContent = 'new content' + + $gist = New-GitHubGist -FileName $origFileName -Content $origContent + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + Context 'By content with parameters' { + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $updatedOrigContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $newFileName -Content $newContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName.content | Should -Be $newContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $newFileName -Force } | Should -Not -Throw + } + } + + Context 'By content with the gist on the pipeline' { + $gist = $gist | Set-GitHubGistFile -FileName $origFileName -Content $updatedOrigContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = $gist | Set-GitHubGistFile -FileName $newFileName -Content $newContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName.content | Should -Be $newContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $newFileName -Force } | Should -Not -Throw + } + } + + Context 'By files with parameters' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + $fileAUpdatedContent = 'fileA content updated' + } + + AfterAll { + @($fileA) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = Set-GitHubGistFile -Gist $gist.id -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Out-File -FilePath $fileA -InputObject $fileAUpdatedContent -Encoding utf8 + $gist = Set-GitHubGistFile -Gist $gist.id -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAUpdatedContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $fileAName -Force } | Should -Not -Throw + } + } + + Context 'By files with the gist on the pipeline' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + $fileAUpdatedContent = 'fileA content updated' + } + + AfterAll { + @($fileA) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = $gist | Set-GitHubGistFile -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Out-File -FilePath $fileA -InputObject $fileAUpdatedContent -Encoding utf8 + $gist = $gist | Set-GitHubGistFile -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAUpdatedContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $fileAName -Force } | Should -Not -Throw + } + } + + Context 'By files with the file on the pipeline' { BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + $fileAUpdatedContent = 'fileA content updated' } AfterAll { + @($fileA) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = $fileA | Set-GitHubGistFile -Gist $gist.id + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Out-File -FilePath $fileA -InputObject $fileAUpdatedContent -Encoding utf8 + $gist = $fileA | Set-GitHubGistFile -Gist $gist.id + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAUpdatedContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $fileAName -Force } | Should -Not -Throw + } + } + } + + Describe 'Rename-GitHubGistFile' { + BeforeAll { + $originalName = 'foo.txt' + $newName = 'bar.txt' + $content = 'sample content' + } + + Context 'With parameters' { + $gist = New-GitHubGist -FileName $originalName -Content $content + It 'Should have the expected file' { + $gist.files.$originalName.content | Should -Be $content + $gist.files.$newName | Should -BeNullOrEmpty + } + + $gist = Rename-GitHubGistFile -Gist $gist.id -FileName $originalName -NewName $newName + It 'Should have been renamed' { + $gist.files.$originalName | Should -BeNullOrEmpty + $gist.files.$newName.content | Should -Be $content + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + + Context 'With the gist on the pipeline' { + $gist = New-GitHubGist -FileName $originalName -Content $content + It 'Should have the expected file' { + $gist.files.$originalName.content | Should -Be $content + $gist.files.$newName | Should -BeNullOrEmpty + } + + $gist = $gist | Rename-GitHubGistFile -FileName $originalName -NewName $newName + It 'Should have been renamed' { + $gist.files.$originalName | Should -BeNullOrEmpty + $gist.files.$newName.content | Should -Be $content + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + } + + Describe 'Remove-GitHubGistFile' { + BeforeAll { + $fileName = 'sample.txt' + $content = 'sample' + } + + Context 'With parameters' { + $gist = New-GitHubGist -FileName $fileName -Content $content + It 'Should have the expected file' { + $gist.files.$fileName | Should -Not -BeNullOrEmpty + } + + $gist = Remove-GitHubGistFile -Gist $gist.id -FileName $fileName -Force + It 'Should have been removed' { + $gist.files.$fileName | Should -BeNullOrEmpty + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + + Context 'With the gist on the pipeline' { + $gist = New-GitHubGist -FileName $fileName -Content $content + It 'Should have the expected file' { + $gist.files.$fileName | Should -Not -BeNullOrEmpty + } + + $gist = $gist | Remove-GitHubGistFile -FileName $fileName -Confirm:$false + It 'Should have been removed' { + $gist.files.$fileName | Should -BeNullOrEmpty + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistDetail' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw } } }