From 1db68b743e35b6364ec0acc7dc466ebcb5aa8aba Mon Sep 17 00:00:00 2001 From: phbits Date: Wed, 9 Jun 2021 18:54:28 +0000 Subject: [PATCH] Update Invoke-Git to use System.Diagnostics.Process --- CHANGELOG.md | 3 + source/Private/Invoke-Git.ps1 | 54 ------ source/Public/Invoke-Git.ps1 | 90 +++++++++ source/Public/Publish-WikiContent.ps1 | 96 ++++++---- .../DscResource.DocGenerator.strings.psd1 | 5 + .../Publish_GitHub_Wiki_Content.build.ps1 | 8 +- tests/unit/private/Invoke-Git.Tests.ps1 | 85 --------- tests/unit/public/Invoke-Git.Tests.ps1 | 127 +++++++++++++ .../unit/public/Publish-WikiContent.Tests.ps1 | 178 +++++++++++++++--- ...ublish_GitHub_Wiki_Content.build.Tests.ps1 | 56 +++++- 10 files changed, 493 insertions(+), 209 deletions(-) delete mode 100644 source/Private/Invoke-Git.ps1 create mode 100644 source/Public/Invoke-Git.ps1 delete mode 100644 tests/unit/private/Invoke-Git.Tests.ps1 create mode 100644 tests/unit/public/Invoke-Git.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ac561fa..1d335ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - If a class-based resource has a parent class that contains DSC resource properties they will now also be returned as part of the DSC resource parameters ([issue #62](https://github.com/dsccommunity/DscResource.DocGenerator/issues/62)). +- `Invoke-Git` + - Converted to public function. + - Updated to use `System.Diagnostics.Process` for improved error handling. ### Fixed diff --git a/source/Private/Invoke-Git.ps1 b/source/Private/Invoke-Git.ps1 deleted file mode 100644 index dcfad0a..0000000 --- a/source/Private/Invoke-Git.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -<# - .SYNOPSIS - Invokes the git command. - - .PARAMETER Arguments - The arguments to pass to the Git executable. - - .EXAMPLE - Invoke-Git clone https://github.com/X-Guardian/xActiveDirectory.wiki.git --quiet - - Invokes the Git executable to clone the specified repository to the current working directory. -#> - -function Invoke-Git -{ - [CmdletBinding()] - param - ( - [Parameter(ValueFromRemainingArguments = $true)] - [System.String[]] - $Arguments - ) - - $argumentsJoined = $Arguments -join ' ' - - # Trying to remove any access token from the debug output. - if ($argumentsJoined -match ':[\d|a-f].*@') - { - $argumentsJoined = $argumentsJoined -replace ':[\d|a-f].*@', ':RedactedToken@' - } - - Write-Debug -Message ($localizedData.InvokingGitMessage -f $argumentsJoined) - - try - { - & git @Arguments - - <# - Assuming the error code 1 from git is warnings or informational like - "nothing to commit, working tree clean" and those are returned instead - of throwing an exception. - #> - if ($LASTEXITCODE -gt 1) - { - throw $LASTEXITCODE - } - } - catch - { - throw $_ - } - - return $LASTEXITCODE -} diff --git a/source/Public/Invoke-Git.ps1 b/source/Public/Invoke-Git.ps1 new file mode 100644 index 0000000..911a43e --- /dev/null +++ b/source/Public/Invoke-Git.ps1 @@ -0,0 +1,90 @@ +<# + .SYNOPSIS + Invokes the git command. + + .PARAMETER WorkingDirectory + The path to the git working directory. + + .PARAMETER Timeout + Seconds to wait for process to exit. + + .PARAMETER Arguments + The arguments to pass to the Git executable. + + .EXAMPLE + Invoke-Git clone https://github.com/X-Guardian/xActiveDirectory.wiki.git --quiet + + Invokes the Git executable to clone the specified repository to the current working directory. +#> + +function Invoke-Git +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $WorkingDirectory, + + [Parameter(Mandatory = $false)] + [System.Int32] + $TimeOut = 120, + + [Parameter(ValueFromRemainingArguments = $true)] + [System.String[]] + $Arguments + ) + + $returnValue = @{ + 'ExitCode' = -1 + 'StandardOutput' = '' + 'StandardError' = '' + } + + $argumentsJoined = $Arguments -join ' ' + + # Trying to remove any access token from the debug output. + if ($argumentsJoined -match ':[\d|a-f].*@') + { + $argumentsJoined = $argumentsJoined -replace ':[\d|a-f].*@', ':RedactedToken@' + } + + Write-Debug -Message ($localizedData.InvokingGitMessage -f $argumentsJoined) + + try + { + $process = New-Object -TypeName System.Diagnostics.Process + $process.StartInfo.Arguments = $Arguments + $process.StartInfo.CreateNoWindow = $true + $process.StartInfo.FileName = 'git.exe' + $process.StartInfo.RedirectStandardOutput = $true + $process.StartInfo.RedirectStandardError = $true + $process.StartInfo.UseShellExecute = $false + $process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden + $process.StartInfo.WorkingDirectory = $WorkingDirectory + + if ($process.Start() -eq $true) + { + if ($process.WaitForExit($TimeOut) -eq $true) + { + $returnValue.ExitCode = $process.ExitCode + $returnValue.StandardOutput = $process.StandardOutput.ReadToEnd() + $returnValue.StandardError = $process.StandardError.ReadToEnd() + } + } + } + catch + { + throw $_ + } + finally + { + if ($process) + { + $process.Dispose() + } + } + + return $returnValue +} diff --git a/source/Public/Publish-WikiContent.ps1 b/source/Public/Publish-WikiContent.ps1 index 356e418..f9aabf8 100644 --- a/source/Public/Publish-WikiContent.ps1 +++ b/source/Public/Publish-WikiContent.ps1 @@ -93,77 +93,95 @@ function Publish-WikiContent $ErrorActionPreference = 'Stop' - $headers = @{ - 'Content-type' = 'application/json' - } - Write-Verbose -Message $script:localizedData.CreateTempDirMessage $tempPath = New-TempFolder try { - Push-Location + $wikiRepoName = "https://github.com/$OwnerName/$RepositoryName.wiki.git" - Write-Verbose -Message $script:localizedData.ConfigGlobalGitMessage + Write-Verbose -Message ($script:localizedData.CloneWikiGitRepoMessage -f $WikiRepoName) - if ($PSBoundParameters.ContainsKey('GlobalCoreAutoCrLf')) + $gitCloneResult = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'clone', $wikiRepoName, $tempPath, '--quiet' ) + + if ($gitCloneResult.ExitCode -eq 0) { - $null = Invoke-Git -Arguments 'config', '--global', 'core.autocrlf', $GlobalCoreAutoCrLf - } + Write-Verbose -Message $script:localizedData.ConfigGlobalGitMessage - $wikiRepoName = "https://github.com/$OwnerName/$RepositoryName.wiki.git" + if ($PSBoundParameters.ContainsKey('GlobalCoreAutoCrLf')) + { + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'config', '--local', 'core.autocrlf', $GlobalCoreAutoCrLf ) + } - Write-Verbose -Message ($script:localizedData.CloneWikiGitRepoMessage -f $WikiRepoName) + $copyWikiFileParameters = @{ + Path = $Path + DestinationPath = $tempPath + Force = $true + } - $null = Invoke-Git -Arguments 'clone', $wikiRepoName, $tempPath, '--quiet' + Copy-WikiFolder @copyWikiFileParameters - $copyWikiFileParameters = @{ - Path = $Path - DestinationPath = $tempPath - Force = $true - } + New-WikiSidebar -ModuleName $ModuleName -Path $tempPath + New-WikiFooter -Path $tempPath - Copy-WikiFolder @copyWikiFileParameters + Set-Location -Path $tempPath - New-WikiSidebar -ModuleName $ModuleName -Path $tempPath - New-WikiFooter -Path $tempPath + Write-Verbose -Message $script:localizedData.ConfigLocalGitMessage - Set-Location -Path $tempPath + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'config', '--local', 'user.email', $GitUserEmail ) - Write-Verbose -Message $script:localizedData.ConfigLocalGitMessage + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'config', '--local', 'user.name', $GitUserName ) - $null = Invoke-Git -Arguments 'config', '--local', 'user.email', $GitUserEmail - $null = Invoke-Git -Arguments 'config', '--local', 'user.name', $GitUserName - $null = Invoke-Git -Arguments 'remote', 'set-url', 'origin', "https://$($GitUserName):$($GitHubAccessToken)@github.com/$OwnerName/$RepositoryName.wiki.git" + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'remote', 'set-url', 'origin', "https://$($GitUserName):$($GitHubAccessToken)@github.com/$OwnerName/$RepositoryName.wiki.git" ) - Write-Verbose -Message $localizedData.AddWikiContentToGitRepoMessage + Write-Verbose -Message $localizedData.AddWikiContentToGitRepoMessage - $null = Invoke-Git -Arguments 'add', '*' + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'add', '*' ) - Write-Verbose -Message ($localizedData.CommitAndTagRepoChangesMessage -f $ModuleVersion) + Write-Verbose -Message ($localizedData.CommitAndTagRepoChangesMessage -f $ModuleVersion) - $invokeGitResult = Invoke-Git -Arguments 'commit', '--message', ($localizedData.UpdateWikiCommitMessage -f $ModuleVersion), '--quiet' - if ($invokeGitResult -eq 0) - { - $null = Invoke-Git -Arguments 'tag', '--annotate', $ModuleVersion, '--message', $ModuleVersion + $gitCommitResult = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'commit', '--message', "`"$($localizedData.UpdateWikiCommitMessage -f $ModuleVersion)`"", '--quiet' ) + + if ($gitCommitResult.ExitCode -eq 0) + { + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'tag', '--annotate', $ModuleVersion, '--message', $ModuleVersion ) - Write-Verbose -Message $localizedData.PushUpdatedRepoMessage + Write-Verbose -Message $localizedData.PushUpdatedRepoMessage - $null = Invoke-Git -Arguments 'push', 'origin', '--quiet' - $null = Invoke-Git -Arguments 'push', 'origin', $ModuleVersion, '--quiet' + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'push', 'origin', '--quiet' ) - Write-Verbose -Message $localizedData.PublishWikiContentCompleteMessage + $null = Invoke-Git -WorkingDirectory $tempPath.FullName ` + -Arguments @( 'push', 'origin', $ModuleVersion, '--quiet' ) + + Write-Verbose -Message $localizedData.PublishWikiContentCompleteMessage + } + else + { + Write-Warning -Message $localizedData.NothingToCommitToWiki + } } else { - Write-Warning -Message $localizedData.NothingToCommitToWiki + Write-Verbose -Message $script:localizedData.WikiGitCloneFailMessage + + Write-Debug -Message ($script:localizedData.WikiGitCloneFailMessageDebug -f $wikiRepoName) + Write-Debug -Message ($script:localizedData.InvokeGitStandardOutput -f $gitCloneResult.StandardOutput) + Write-Debug -Message ($script:localizedData.InvokeGitStandardError -f $gitCloneResult.StandardError) + Write-Debug -Message ($script:localizedData.InvokeGitExitCodeMessage -f $gitCloneResult.ExitCode) } } finally { - Pop-Location - Remove-Item -Path $tempPath -Recurse -Force } } diff --git a/source/en-US/DscResource.DocGenerator.strings.psd1 b/source/en-US/DscResource.DocGenerator.strings.psd1 index 3903a39..1ffd17a 100644 --- a/source/en-US/DscResource.DocGenerator.strings.psd1 +++ b/source/en-US/DscResource.DocGenerator.strings.psd1 @@ -30,4 +30,9 @@ ConvertFrom-StringData @' ClassBasedCommentBasedHelpMessage = Reading comment-based help from source file '{0}'. FoundResourceExamplesMessage = Found {0} examples. IgnoreAstParseErrorMessage = Errors was found during parsing of comment-based help. These errors were ignored: {0} + WikiGitCloneFailMessage = Failed to clone wiki. Ensure the feature is enabled and the first page has been created. + WikiGitCloneFailMessageDebug = Wiki clone URL '{0}' + InvokeGitStandardOutputMessage = git standard output: '{0}' + InvokeGitStandardErrorMessage = git standard error: '{0}' + InvokeGitExitCodeMessage = git exit code: '{0}' '@ diff --git a/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 b/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 index 5c20b48..4233e66 100644 --- a/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 +++ b/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 @@ -167,7 +167,13 @@ task Publish_GitHub_Wiki_Content { } } - $remoteURL = git remote get-url origin + $gitRemoteResult = Invoke-Git -WorkingDirectory $BuildRoot ` + -Arguments @( 'remote', 'get-url', 'origin' ) + + if ($gitRemoteResult.ExitCode -eq 0) + { + $remoteURL = $gitRemoteResult.StandardOutput + } # Parse the URL for owner name and repository name. if ($remoteURL -match 'github') diff --git a/tests/unit/private/Invoke-Git.Tests.ps1 b/tests/unit/private/Invoke-Git.Tests.ps1 deleted file mode 100644 index bbe2853..0000000 --- a/tests/unit/private/Invoke-Git.Tests.ps1 +++ /dev/null @@ -1,85 +0,0 @@ -#region HEADER -$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path -$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { - ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and - $(try - { - Test-ModuleManifest -Path $_.FullName -ErrorAction Stop - } - catch - { - $false - }) - }).BaseName - -$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 -Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' - -Import-Module $script:moduleName -Force -ErrorAction 'Stop' -#endregion HEADER - -InModuleScope $script:moduleName { - Describe 'Invoke-Git' { - BeforeAll { - # stub for git so we can mock it. - function git {} - } - - Context 'When calling Invoke-Git' { - BeforeAll { - Mock -CommandName 'git' - } - - It 'Should call git without throwing' { - { Invoke-Git -Arguments 'config', '--local', 'user.email', 'user@host.com' } | Should -Not -Throw - } - } - - Context 'When calling Invoke-Git with an access token' { - BeforeAll { - Mock -CommandName 'git' - Mock -CommandName Write-Debug - } - - It 'Should call git but mask access token in debug message' { - { Invoke-Git -Arguments 'remote', 'set-url', 'origin', 'https://name:5ea239f132736de237492ff3@github.com/repository.wiki.git' -Debug } | Should -Not -Throw - - Assert-MockCalled -CommandName Write-Debug -ParameterFilter { - $Message -match 'https://name:RedactedToken@github.com/repository.wiki.git' - } -Exactly -Times 1 -Scope It - } - } - - Context 'When git exits with error code 1' { - BeforeAll { - Mock -CommandName 'git' -MockWith { - $script:LASTEXITCODE = 1 - } - } - - AfterAll { - $script:LASTEXITCODE = 0 - } - - It 'Should not throw an exception' { - { Invoke-Git -Arguments 'status' } | Should -Not -Throw - } - } - - Context 'When git exits with an error code higher than 1' { - BeforeAll { - Mock -CommandName 'git' -MockWith { - $script:LASTEXITCODE = 2 - } - } - - AfterAll { - $script:LASTEXITCODE = 0 - } - - It 'Should throw an exception with the correct exit code' { - { Invoke-Git -Arguments 'status' } | Should -Throw '2' - } - } - } -} diff --git a/tests/unit/public/Invoke-Git.Tests.ps1 b/tests/unit/public/Invoke-Git.Tests.ps1 new file mode 100644 index 0000000..640200b --- /dev/null +++ b/tests/unit/public/Invoke-Git.Tests.ps1 @@ -0,0 +1,127 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +InModuleScope $script:moduleName { + Describe 'Invoke-Git' { + BeforeAll { + $mockWorkingDirectory = @{ [System.String] 'FullName' = "$TestDrive\TestWorkingDirectory" } + $mockProcess = New-MockObject -Type System.Diagnostics.Process + $mockProcess | Add-Member -MemberType ScriptMethod -Name 'Start' -Value { $true } -Force + $mockProcess | Add-Member -MemberType ScriptMethod -Name 'WaitForExit' -Value { $true } -Force + $mockProcess | Add-Member -MemberType ScriptProperty -Name ExitCode -Value { 0 } -Force + $mockProcess | Add-Member -MemberType ScriptProperty -Name WorkingDirectory -Value { '' } -Force + + Mock -CommandName New-Object -MockWith { return $mockProcess } -ParameterFilter { $TypeName -eq 'System.Diagnostics.Process' } -Verifiable + } + + Context 'When calling Invoke-Git' { + BeforeAll { + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'StandardOutput' -Value { + New-Object -TypeName 'Object' | ` + Add-Member -MemberType ScriptMethod -Name 'ReadToEnd' -Value { 'Standard Output Message 0' } -PassThru -Force + } -Force + + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'StandardError' -Value { + New-Object -TypeName 'Object' | ` + Add-Member -MemberType ScriptMethod -Name 'ReadToEnd' -Value { 'Standard Error Message 0' } -PassThru -Force + } -Force + } + It 'Should complete with ExitCode=0' { + + $result = Invoke-Git -WorkingDirectory $mockWorkingDirectory.FullName ` + -Arguments @( 'config', '--local', 'user.email', 'user@host.com' ) + + $result.ExitCode | Should -BeExactly 0 + + $result.StandardOutput | Should -BeExactly 'Standard Output Message 0' + + $result.StandardError | Should -BeExactly 'Standard Error Message 0' + + Assert-VerifiableMock + } + } + + Context 'When calling Invoke-Git with an access token' { + BeforeAll { + $mockProcess | Add-Member -MemberType ScriptProperty -Name ExitCode -Value { 1 } -Force + + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'StandardOutput' -Value { + New-Object -TypeName 'Object' | ` + Add-Member -MemberType ScriptMethod -Name 'ReadToEnd' -Value { 'Standard Output Message 1' } -PassThru -Force + } -Force + + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'StandardError' -Value { + New-Object -TypeName 'Object' | ` + Add-Member -MemberType ScriptMethod -Name 'ReadToEnd' -Value { 'Standard Error Message 1' } -PassThru -Force + } -Force + + Mock -CommandName Write-Debug + } + + It 'Should complete with ExitCode=1 and mask access token in debug message' { + + $result = Invoke-Git -WorkingDirectory $mockWorkingDirectory.FullName ` + -Arguments @( 'remote', 'set-url', 'origin', 'https://name:5ea239f132736de237492ff3@github.com/repository.wiki.git' ) ` + -Debug + + $result.ExitCode | Should -BeExactly 1 + + $result.StandardOutput | Should -BeExactly 'Standard Output Message 1' + + $result.StandardError | Should -BeExactly 'Standard Error Message 1' + + Assert-MockCalled -CommandName Write-Debug -ParameterFilter { + $Message -match 'https://name:RedactedToken@github.com/repository.wiki.git' + } -Exactly -Times 1 -Scope It + + Assert-VerifiableMock + } + } + + Context 'When git exits with an error code higher than 1' { + BeforeAll { + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'ExitCode' -Value { 128 } -Force + + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'StandardOutput' -Value { + New-Object -TypeName 'Object' | ` + Add-Member -MemberType ScriptMethod -Name 'ReadToEnd' -Value { 'Standard Output Message 128' } -PassThru -Force + } -Force + + $mockProcess | Add-Member -MemberType ScriptProperty -Name 'StandardError' -Value { + New-Object -TypeName 'Object' | ` + Add-Member -MemberType ScriptMethod -Name 'ReadToEnd' -Value { 'Standard Error Message 128' } -PassThru -Force + } -Force + } + + It 'Should complete with ExitCode=128' { + $result = Invoke-Git -WorkingDirectory $mockWorkingDirectory.FullName ` + -Arguments @( 'status' ) + + $result.ExitCode | Should -BeExactly 128 + + $result.StandardOutput | Should -BeExactly 'Standard Output Message 128' + + $result.StandardError | Should -BeExactly 'Standard Error Message 128' + + Assert-VerifiableMock + } + } + } +} diff --git a/tests/unit/public/Publish-WikiContent.Tests.ps1 b/tests/unit/public/Publish-WikiContent.Tests.ps1 index 62ac1c2..4781035 100644 --- a/tests/unit/public/Publish-WikiContent.Tests.ps1 +++ b/tests/unit/public/Publish-WikiContent.Tests.ps1 @@ -20,18 +20,30 @@ Import-Module $script:moduleName -Force -ErrorAction 'Stop' InModuleScope $script:moduleName { Describe 'Publish-WikiContent' { - Context 'When publishing Wiki content' { + Context 'When cloning Wiki content fails' { BeforeAll { - Mock -CommandName Push-Location - Mock -CommandName Pop-Location - Mock -CommandName Set-Location Mock -CommandName Copy-WikiFolder Mock -CommandName New-WikiSidebar Mock -CommandName New-WikiFooter Mock -CommandName Remove-Item Mock -CommandName Invoke-Git -MockWith { - return 0 + return @{ + 'ExitCode' = 0 + 'StandardOutput' = 'Standard Output 0' + 'StandardError' = 'Standard Error 0' + } + } + + Mock -CommandName Invoke-Git -MockWith { + return @{ + 'ExitCode' = 128 + 'StandardOutput' = 'Standard Output 128' + 'StandardError' = 'fatal: remote error: access denied or repository not exported: /335792891.wiki.git' + } + } -ParameterFilter { + $Arguments[0] -eq 'clone' -and + $Arguments[1] -eq "https://github.com/$($mockPublishWikiContentParameters.OwnerName)/$($mockPublishWikiContentParameters.RepositoryName).wiki.git" } } @@ -50,28 +62,130 @@ InModuleScope $script:moduleName { { Publish-WikiContent @mockPublishWikiContentParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'clone' -and + $Arguments[1] -eq "https://github.com/$($mockPublishWikiContentParameters.OwnerName)/$($mockPublishWikiContentParameters.RepositoryName).wiki.git" + } -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'config' -and - $Arguments[1] -eq '--global' -and + $Arguments[1] -eq '--local' -and $Arguments[2] -eq 'core.autocrlf' -and $Arguments[3] -eq 'true' - } -Exactly -Times 1 -Scope It + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Copy-WikiFolder -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName New-WikiSidebar -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName New-WikiFooter -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'config' -and + $Arguments[1] -eq '--local' -and + $Arguments[2] -eq 'user.email' -and + $Arguments[3] -eq $mockPublishWikiContentParameters.GitUserEmail + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'config' -and + $Arguments[1] -eq '--local' -and + $Arguments[2] -eq 'user.name' -and + $Arguments[3] -eq $mockPublishWikiContentParameters.GitUserName + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'remote' -and + $Arguments[1] -eq 'set-url' -and + $Arguments[2] -eq 'origin' -and + $Arguments[3] -eq "https://$($mockPublishWikiContentParameters.GitUserName):$($mockPublishWikiContentParameters.GithubAccessToken)@github.com/$($mockPublishWikiContentParameters.OwnerName)/$($mockPublishWikiContentParameters.RepositoryName).wiki.git" + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'add' -and + $Arguments[1] -eq '*' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'commit' -and + $Arguments[1] -eq '--message' -and + $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" -and + $Arguments[3] -eq '--quiet' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'tag' -and + $Arguments[1] -eq '--annotate' -and + $Arguments[2] -eq $mockPublishWikiContentParameters.ModuleVersion -and + $Arguments[3] -eq '--message' -and + $Arguments[4] -eq $mockPublishWikiContentParameters.ModuleVersion + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'push' -and + $Arguments[1] -eq 'origin' -and + $Arguments[2] -eq '--quiet' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'push' -and + $Arguments[1] -eq 'origin' -and + $Arguments[2] -eq $mockPublishWikiContentParameters.ModuleVersion -and + $Arguments[3] -eq '--quiet' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Remove-Item -Exactly -Times 1 -Scope It + } + } + Context 'When publishing Wiki content' { + BeforeAll { + Mock -CommandName Copy-WikiFolder + Mock -CommandName New-WikiSidebar + Mock -CommandName New-WikiFooter + Mock -CommandName Remove-Item + + Mock -CommandName Invoke-Git -MockWith { + return @{ + 'ExitCode' = 0 + 'StandardOutput' = 'Standard Output 0' + 'StandardError' = 'Standard Error 0' + } + } + } + + It 'Should not throw an exception and call the expected mocks' { + $mockPublishWikiContentParameters = @{ + Path = $TestDrive + OwnerName = 'owner' + RepositoryName = 'repo' + ModuleName = 'TestModule' + ModuleVersion = '1.0.0' + GitHubAccessToken = 'token' + GitUserEmail = 'user@host.com' + GitUserName = 'User' + GlobalCoreAutoCrLf = 'true' + } + + { Publish-WikiContent @mockPublishWikiContentParameters } | Should -Not -Throw Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'clone' -and $Arguments[1] -eq "https://github.com/$($mockPublishWikiContentParameters.OwnerName)/$($mockPublishWikiContentParameters.RepositoryName).wiki.git" } -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'config' -and + $Arguments[1] -eq '--local' -and + $Arguments[2] -eq 'core.autocrlf' -and + $Arguments[3] -eq 'true' + } -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Copy-WikiFolder -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName New-WikiSidebar -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName New-WikiFooter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Set-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'config' -and $Arguments[1] -eq '--local' -and @@ -101,7 +215,7 @@ InModuleScope $script:moduleName { Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'commit' -and $Arguments[1] -eq '--message' -and - $Arguments[2] -eq ($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion) -and + $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" -and $Arguments[3] -eq '--quiet' } -Exactly -Times 1 -Scope It @@ -126,17 +240,12 @@ InModuleScope $script:moduleName { $Arguments[3] -eq '--quiet' } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Remove-Item -Exactly -Times 1 -Scope It } } Context 'When there is no new content to publishing to Wiki' { BeforeAll { - Mock -CommandName Push-Location - Mock -CommandName Pop-Location - Mock -CommandName Set-Location Mock -CommandName Copy-WikiFolder Mock -CommandName New-WikiSidebar Mock -CommandName New-WikiFooter @@ -144,8 +253,25 @@ InModuleScope $script:moduleName { Mock -CommandName Set-WikiModuleVersion Mock -CommandName Invoke-Git -MockWith { - return 1 + return @{ + 'ExitCode' = 0 + 'StandardOutput' = 'Standard Output 0' + 'StandardError' = 'Standard Error 0' + } } + + Mock -CommandName Invoke-Git -MockWith { + return @{ + 'ExitCode' = 1 + 'StandardOutput' = 'Standard Output 1' + 'StandardError' = 'Standard Error 1' + } + } -ParameterFilter { + $Arguments[0] -eq 'commit' -and + $Arguments[1] -eq '--message' -and + $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $ModuleVersion)`"" -and + $Arguments[3] -eq '--quiet' + } } It 'Should not throw an exception and call the expected mocks' { @@ -163,28 +289,24 @@ InModuleScope $script:moduleName { { Publish-WikiContent @mockPublishWikiContentParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { + $Arguments[0] -eq 'clone' -and + $Arguments[1] -eq "https://github.com/$($mockPublishWikiContentParameters.OwnerName)/$($mockPublishWikiContentParameters.RepositoryName).wiki.git" + } -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'config' -and - $Arguments[1] -eq '--global' -and + $Arguments[1] -eq '--local' -and $Arguments[2] -eq 'core.autocrlf' -and $Arguments[3] -eq 'true' } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { - $Arguments[0] -eq 'clone' -and - $Arguments[1] -eq "https://github.com/$($mockPublishWikiContentParameters.OwnerName)/$($mockPublishWikiContentParameters.RepositoryName).wiki.git" - } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Copy-WikiFolder -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName New-WikiSidebar -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName New-WikiFooter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Set-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'config' -and $Arguments[1] -eq '--local' -and @@ -214,7 +336,7 @@ InModuleScope $script:moduleName { Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'commit' -and $Arguments[1] -eq '--message' -and - $Arguments[2] -eq ($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion) -and + $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" -and $Arguments[3] -eq '--quiet' } -Exactly -Times 1 -Scope It @@ -239,8 +361,6 @@ InModuleScope $script:moduleName { $Arguments[3] -eq '--quiet' } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Remove-Item -Exactly -Times 1 -Scope It } } diff --git a/tests/unit/tasks/Publish_GitHub_Wiki_Content.build.Tests.ps1 b/tests/unit/tasks/Publish_GitHub_Wiki_Content.build.Tests.ps1 index 2034ef7..1580a74 100644 --- a/tests/unit/tasks/Publish_GitHub_Wiki_Content.build.Tests.ps1 +++ b/tests/unit/tasks/Publish_GitHub_Wiki_Content.build.Tests.ps1 @@ -26,6 +26,18 @@ Describe 'Publish_GitHub_Wiki_Content' { return $true } + Mock -CommandName Invoke-Git -MockWith { + return @{ + 'ExitCode' = 0 + 'StandardOutput' = 'https://github.com/Owner/MyModule.git' + 'StandardError' = 'Standard Error 0' + } + } -ParameterFilter { + $Arguments[0] -eq 'remote' -and + $Arguments[1] -eq 'get-url' -and + $Arguments[2] -eq 'origin' + } + Mock -Command Get-BuiltModuleVersion -MockWith { return [PSCustomObject]@{ Version = '1.0.0-preview1' @@ -53,7 +65,7 @@ Describe 'Publish_GitHub_Wiki_Content' { } AfterAll { - Remove-Variable -Name 'GitHubToken' + $GitHubToken = $null } It 'Should export the build script alias' { @@ -81,6 +93,48 @@ Describe 'Publish_GitHub_Wiki_Content' { } | Should -Not -Throw Assert-MockCalled -CommandName Publish-WikiContent -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Invoke-Git -Exactly -Times 1 -Scope It + } + } + + Context 'When $GitHubToken is specified and Invoke-Git failed'{ + BeforeAll { + <# + The build task only run if this is set. This sets the variable in the + parent scope so Invoke-Build can use it. This way we don't mess with + any real environment variables. + #> + $GitHubToken = 'anytoken' + + Mock -CommandName Invoke-Git -MockWith { + return @{ + 'ExitCode' = 128 + 'StandardOutput' = 'Standard Output 128' + 'StandardError' = 'fatal: not a git repository (or any of the parent directories): .git' + } + } -ParameterFilter { + $Arguments[0] -eq 'remote' -and + $Arguments[1] -eq 'get-url' -and + $Arguments[2] -eq 'origin' + } + } + + AfterAll { + $GitHubToken = $null + } + + It 'Should run the build task and throw' { + { + $taskParameters = @{ + ProjectName = 'MyModule' + SourcePath = $TestDrive + } + + Invoke-Build -Task $buildTaskName -File $script:buildScript.Definition @taskParameters + } | Should -Throw + + Assert-MockCalled -CommandName Publish-WikiContent -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Invoke-Git -Exactly -Times 1 -Scope It } }