functions/Update-ForgeDscModule.Tests.ps1

Describe 'Update-ForgeDscModule' -Tag 'Unit' {
  BeforeDiscovery {
    $ModuleRootPath = Split-Path -Parent $PSCommandPath |
      Split-Path -Parent
    Import-Module "$ModuleRootPath/Puppet.Dsc.psd1"
  }

  InModuleScope puppet.dsc {
    Context 'Basic verification' {
      BeforeAll {
        $ForgeModules = @(
          [PSCustomObject]@{
            Name                 = 'foo'
            Releases             = @('1.2.3-0-1', '1.2.3-0-0', '1.2.0-0-0', '1.1.0-0-0')
            PowerShellModuleInfo = @{ Name = 'Foo' }
          }
          [PSCustomObject]@{
            Name                 = 'bar'
            Releases             = @('2.1.0-0-0', '2.0.0-0-0', '1.2.0-0-0', '1.1.0-0-0')
            PowerShellModuleInfo = @{ Name = 'Bar' }
          }
          [PSCustomObject]@{
            Name                 = 'baz'
            Releases             = @('2.1.0-0-0', '2.0.0-0-0', '1.0.0-0-0', '0.1.0-0-0')
            PowerShellModuleInfo = @{ Name = 'Baz' }
          }
        )
        Mock Get-ForgeModuleInfo { return $ForgeModules }
        Mock Get-ForgeModuleInfo -ParameterFilter { ![string]::IsNullOrEmpty($Name) } {
          If ($ForgeModules.Count -gt 1) {
            return $ForgeModules | Where-Object -FilterScript { $_.Name -in $Name }
          } Else {
            return $ForgeModules | Where-Object -FilterScript { $_.Name -eq $Name }
          }
        }
        Mock Get-ForgeModuleInfo -ParameterFilter { $ForgeNameSpace -eq 'EmptyNameSpace' } {}
        # Pester equivalent of mock-and-call-original
        Mock ConvertTo-StandardizedVersionString {
          $GetLatestBuildFunction = Get-Command ConvertTo-StandardizedVersionString -CommandType Function
          return & $GetLatestBuildFunction -Version $Version
        }
        # Pester equivalent of mock-and-call-original
        Mock Get-LatestBuild {
          $GetLatestBuildFunction = Get-Command Get-LatestBuild -CommandType Function
          return & $GetLatestBuildFunction -Version $Version
        }
        Mock New-PuppetDscModule {
          return [PSCustomObject]@{ FullName = "TestDrive:\import\$(Get-PuppetizedModuleName -Name $PowerShellModuleName)\" }
        }
        Mock Set-PuppetModuleVersion
        Mock Publish-PuppetModule
        Mock Write-PSFMessage
        Mock Stop-PSFFunction
        Mock Start-Sleep
        Update-ForgeDscModule -ForgeNameSpace 'foo'
      }

      It 'requires the ForgeNameSpace parameter' {
        $Command = Get-Command Update-ForgeDscModule
        $Command.Parameters.ForgeNameSpace.Attributes |
          Where-Object -FilterScript { $_.TypeId.Name -eq 'ParameterAttribute' } |
          Select-Object -ExpandProperty Mandatory -Unique | Should -Be $true
      }

      It 'searches the specified Forge namespace for released modules' {
        Should -Invoke Get-ForgeModuleInfo -Scope Context -Times 1 -ParameterFilter { $ForgeNameSpace -eq 'foo' }
      }

      It 'puppetizes each released version only once' {
        Should -Invoke New-PuppetDscModule -Scope Context -Times 3 -ParameterFilter { $PowerShellModuleName -eq 'Foo' }
        Should -Invoke New-PuppetDscModule -Scope Context -Times 11
      }

      It 'builds and puppetizes each released version with the updated build number' {
        Should -Invoke Set-PuppetModuleVersion -Scope Context -Times 1 -ParameterFilter { $Version -match '-2$' }
        Should -Invoke Set-PuppetModuleVersion -Scope Context -Times 10 -ParameterFilter { $Version -match '-1$' }
      }

      Context 'when something goes wrong' {
        BeforeAll {
          Mock Get-ForgeModuleInfo -ParameterFilter { $ForgeNameSpace -eq 'will_error' } {
            return [PSCustomObject]@{
              Name                 = 'will_error'
              Releases             = @('1.2.3-0-0')
              PowerShellModuleInfo = @{ Name = 'WillError' }
            }
          }
          Mock New-PuppetDscModule -ParameterFilter { $PowerShellModuleName -eq 'WillError' } {
            Throw 'Error! Here are some details.'
          }
        }

        It 'calls Stop-PSFunction for error-handling' {
          Update-ForgeDscModule -ForgeNameSpace 'will_error'
          Should -Invoke Stop-PSFFunction -ParameterFilter {
            $Message -match 'will_error' -and $Message -match '1.2.3-0'
          }
        }

        It 'calls Stop-PSFunction with EnableException if ErrorAction is "Stop"' {
          Update-ForgeDscModule -ForgeNameSpace 'will_error' -ErrorAction Stop
          Should -Invoke Stop-PSFFunction -ParameterFilter {
            $EnableException -eq $True
          }
        }

        Context 'when the SleepAfterFailure parameter is specified' {
          It 'sleeps for the specified duration before the next iteration' {
            Update-ForgeDscModule -ForgeNameSpace 'will_error' -SleepAfterFailure 5
            Should -Invoke Start-Sleep -ParameterFilter {
              $Seconds -eq 5
            }
          }
        }
      }

      Context 'when the Name parameter is specified' {
        It 'searches for modules to update by Name' {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -Name 'foo'
          Should -Invoke Get-ForgeModuleInfo -Times 1 -ParameterFilter { $Name -eq 'foo' }
        }
      }

      Context 'when the Version parameter is specified' {
        It 'requires exactly one Name to be passed' {
          { Update-ForgeDscModule -ForgeNameSpace 'foo' -Name 'foo', 'bar' -Version '1.2.3' } | Should -Throw 'Specified a Version with multiple Names*'
          Should -Invoke New-PuppetDscModule -Times 0
        }
        It 'filters the list of ReleasesToRebuild to match only the specified Version' {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -Name 'foo' -Version '1.2.3'
          Should -Invoke New-PuppetDscModule -Times 1 -ParameterFilter { $PowerShellModuleVersion -eq '1.2.3.0' }
          Should -Invoke New-PuppetDscModule -Times 0 -ParameterFilter { $PowerShellModuleVersion -ne '1.2.3.0' }
        }
      }

      Context 'when the ForgeApiUri parameter is specified' -Tag 'api' {
        BeforeAll {
          $TestForgeApiUri = 'https://myforge.acme.inc/v3'
          Update-ForgeDscModule -ForgeNameSpace 'foo' -ForgeApiUri $TestForgeApiUri
        }

        It 'passes the specified URI for search' {
          Should -Invoke Get-ForgeModuleInfo -Scope Context -Times 1 -ParameterFilter { $ForgeSearchUri -eq "$TestForgeApiUri/modules" }
        }

        It 'passes the specified URI for publish' {
          Should -Invoke Publish-PuppetModule -Scope Context -Times 1 -ParameterFilter { $ForgeUploadUrl -eq "$TestForgeApiUri/releases" }
        }
      }

      Context 'when the ForgeToken parameter is specified' {
        BeforeAll {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -ForgeToken 'MyToken' -Verbose
        }

        It 'passes the token to Publish-PuppetModule' {
          Should -Invoke Publish-PuppetModule -Scope Context -Times 1 -ParameterFilter { $ForgeToken -eq 'MyToken' }
        }

        It 'does not leak the token in logging' {
          Should -Invoke Write-PSFMessage -Scope Context
          Should -Invoke Write-PSFMessage -Scope Context -Times 0 -ParameterFilter { $Message -match 'MyToken' }
        }
      }

      Context 'when the BuildFolderPath parameter is specified' {
        BeforeAll {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -BuildFolder 'TestDrive:\import'
        }

        It 'passes the path as the OutputDirectory to New-PuppetDscModule' {
          Should -Invoke New-PuppetDscModule -Scope Context -ParameterFilter { $OutputDirectory -eq 'TestDrive:\import' }
        }

        It 'leverages the path for Publish-PuppetModule' {
          Should -Invoke Publish-PuppetModule -Scope Context -ParameterFilter { $PuppetModuleFolderPath -match '^TestDrive:\\import' }
        }
      }

      Context 'when the PackageFolderPath parameter is specified' {
        It 'passes the path as the ExportFolderPath to Publish-PuppetModule' {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -PackageFolderPath 'TestDrive:\packages'
          Should -Invoke Publish-PuppetModule -ParameterFilter { $ExportFolderPath -eq 'TestDrive:\packages' }
        }
      }

      Context 'when the LatestMajorVersionOnly parameter is specified' {
        It 'only updates module releases for the latest major version' {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -LatestMajorVersionOnly
          Should -Invoke New-PuppetDscModule -Times 2 -ParameterFilter { $PowerShellModuleName -eq 'Bar' -and $PowerShellModuleVersion -match '^2\.' }
          Should -Invoke New-PuppetDscModule -Times 0 -ParameterFilter { $PowerShellModuleName -eq 'Bar' -and $PowerShellModuleVersion -notmatch '^2\.' }
        }
      }

      Context 'when the MaximumVersionCountToRebuild parameter is specified' {
        It 'only updates up to the specified number of releases for each module' {
          Update-ForgeDscModule -ForgeNameSpace 'foo' -MaximumVersionCountToRebuild 1
          Should -Invoke New-PuppetDscModule -Times 3
          Should -Invoke New-PuppetDscModule -Times 1 -ParameterFilter { $PowerShellModuleName -eq 'Foo' }
          Should -Invoke New-PuppetDscModule -Times 1 -ParameterFilter { $PowerShellModuleName -eq 'Bar' }
          Should -Invoke New-PuppetDscModule -Times 1 -ParameterFilter { $PowerShellModuleName -eq 'Baz' }
        }
      }
    }
  }
}