Tests/Unit/xExchangeHelper.tests.ps1
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() #region HEADER $script:DSCModuleName = 'xExchange' $script:DSCHelperName = "xExchangeHelper" # Unit Test Template Version: 1.2.2 $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Modules' -ChildPath 'xExchangeHelper.psm1')) -Force #endregion HEADER function Invoke-TestSetup { } function Invoke-TestCleanup { } # Begin Testing try { Invoke-TestSetup InModuleScope $script:DSCHelperName { # Get a unique Guid that doesn't resolve to a local path # Use System.Guid, as New-Guid isn't available in PS4 and below do { $guid1 = [System.Guid]::NewGuid().ToString() } while (Test-Path -Path $guid1) # Get a unique Guid that doesn't resolve to a local path do { $guid2 = [System.Guid]::NewGuid().ToString() } while ((Test-Path -Path $guid2) -or $guid1 -like $guid2) Describe 'xExchangeHelper\Get-ExchangeInstallStatus' -Tag 'Helper' { # Used for calls to Get-InstallStatus $getInstallStatusParams = @{ Path = 'C:\Exchange\setup.exe' Arguments = '/mode:Install /role:Mailbox /Iacceptexchangeserverlicenseterms' } AfterEach { Assert-MockCalled -CommandName Test-ShouldInstallUMLanguagePack -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-ExchangeSetupRunning -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-ExchangeSetupComplete -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-ExchangePresent -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-ShouldUpgradeExchange -Exactly -Times 1 -Scope It } Context 'When Exchange is not present on the system' { It 'Should only recommend starting the install' { Mock -CommandName Test-ShouldInstallUMLanguagePack -MockWith { return $false } Mock -CommandName Test-ExchangeSetupRunning -MockWith { return $false } Mock -CommandName Test-ExchangeSetupComplete -MockWith { return $false } Mock -CommandName Test-ExchangePresent -MockWith { return $false } Mock -CommandName Test-ShouldUpgradeExchange -MockWith { return $false } $installStatus = Get-ExchangeInstallStatus @getInstallStatusParams $installStatus.ShouldInstallLanguagePack | Should -Be $false $installStatus.SetupRunning | Should -Be $false $installStatus.SetupComplete | Should -Be $false $installStatus.ExchangePresent | Should -Be $false $installStatus.ShouldUpgrade | Should -Be $false $installStatus.ShouldStartInstall | Should -Be $true } } Context 'When Exchange Setup has fully completed' { It 'Should indicate setup is complete and Exchange is present' { Mock -CommandName Test-ShouldInstallUMLanguagePack -MockWith { return $false } Mock -CommandName Test-ExchangeSetupRunning -MockWith { return $false } Mock -CommandName Test-ExchangeSetupComplete -MockWith { return $true } Mock -CommandName Test-ExchangePresent -MockWith { return $true } Mock -CommandName Test-ShouldUpgradeExchange -MockWith { return $false } $installStatus = Get-ExchangeInstallStatus @getInstallStatusParams $installStatus.ShouldInstallLanguagePack | Should -Be $false $installStatus.SetupRunning | Should -Be $false $installStatus.SetupComplete | Should -Be $true $installStatus.ExchangePresent | Should -Be $true $installStatus.ShouldUpgrade | Should -Be $false $installStatus.ShouldStartInstall | Should -Be $false } } Context 'When Exchange Setup has partially completed' { It 'Should indicate that Exchange is present, but setup is not complete, and recommend starting an install' { Mock -CommandName Test-ShouldInstallUMLanguagePack -MockWith { return $false } Mock -CommandName Test-ExchangeSetupRunning -MockWith { return $false } Mock -CommandName Test-ExchangeSetupComplete -MockWith { return $false } Mock -CommandName Test-ExchangePresent -MockWith { return $true } Mock -CommandName Test-ShouldUpgradeExchange -MockWith { return $false } $installStatus = Get-ExchangeInstallStatus @getInstallStatusParams $installStatus.ShouldInstallLanguagePack | Should -Be $false $installStatus.SetupRunning | Should -Be $false $installStatus.SetupComplete | Should -Be $false $installStatus.ExchangePresent | Should -Be $true $installStatus.ShouldUpgrade | Should -Be $false $installStatus.ShouldStartInstall | Should -Be $true } } Context 'When Exchange Setup is currently running' { It 'Should indicate that Exchange is present and that setup is running' { Mock -CommandName Test-ShouldInstallUMLanguagePack -MockWith { return $false } Mock -CommandName Test-ExchangeSetupRunning -MockWith { return $true } Mock -CommandName Test-ExchangeSetupComplete -MockWith { return $false } Mock -CommandName Test-ExchangePresent -MockWith { return $true } Mock -CommandName Test-ShouldUpgradeExchange -MockWith { return $false } $installStatus = Get-ExchangeInstallStatus @getInstallStatusParams $installStatus.ShouldInstallLanguagePack | Should -Be $false $installStatus.SetupRunning | Should -Be $true $installStatus.SetupComplete | Should -Be $false $installStatus.ExchangePresent | Should -Be $true $installStatus.ShouldUpgrade | Should -Be $false $installStatus.ShouldStartInstall | Should -Be $false } } Context 'When a Language Pack install is requested, and the Language Pack has not been installed' { It 'Should indicate that setup has completed and a language pack Should -Be installed' { Mock -CommandName Test-ShouldInstallUMLanguagePack -MockWith { return $true } Mock -CommandName Test-ExchangeSetupRunning -MockWith { return $false } Mock -CommandName Test-ExchangeSetupComplete -MockWith { return $true } Mock -CommandName Test-ExchangePresent -MockWith { return $true } Mock -CommandName Test-ShouldUpgradeExchange -MockWith { return $false } $installStatus = Get-ExchangeInstallStatus @getInstallStatusParams $installStatus.ShouldInstallLanguagePack | Should -Be $true $installStatus.SetupRunning | Should -Be $false $installStatus.SetupComplete | Should -Be $true $installStatus.ExchangePresent | Should -Be $true $installStatus.ShouldUpgrade | Should -Be $false $installStatus.ShouldStartInstall | Should -Be $true } } Context 'When Exchange upgrade is requested' { It 'Should recommend starting the install' { Mock -CommandName Test-ShouldInstallUMLanguagePack -MockWith { return $false } Mock -CommandName Test-ExchangeSetupRunning -MockWith { return $false } Mock -CommandName Test-ExchangeSetupComplete -MockWith { return $false } Mock -CommandName Test-ExchangePresent -MockWith { return $true } Mock -CommandName Test-ShouldUpgradeExchange -MockWith { return $true } $getInstallStatusParams = @{ Path = 'C:\Exchange\setup.exe' Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' } $installStatus = Get-ExchangeInstallStatus @getInstallStatusParams $installStatus.ShouldInstallLanguagePack | Should -Be $false $installStatus.SetupRunning | Should -Be $false $installStatus.SetupComplete | Should -Be $false $installStatus.ExchangePresent | Should -Be $true $installStatus.ShouldUpgrade | Should -Be $true $installStatus.ShouldStartInstall | Should -Be $true } } } Describe 'xExchangeHelper\Get-PreviousError' -Tag 'Helper' { Context 'After an error occurs' { It 'Should retrieve the most recent error' { # First get whatever error is currently on top of the stack $initialError = Get-PreviousError # Cause an error by trying to get a non-existent item Get-ChildItem -Path $guid1 -ErrorAction SilentlyContinue $firstError = Get-PreviousError # Cause another error by trying to get a non-existent item Get-ChildItem -Path $guid2 -ErrorAction SilentlyContinue $secondError = Get-PreviousError $initialError -ne $firstError | Should -Be $true $secondError -ne $firstError | Should -Be $true $firstError -eq $null | Should -Be $false $secondError -eq $null | Should -Be $false } } Context 'When an error has not occurred' { It 'Should return the same previous error with each call' { # Run Get-PreviousError twice in a row so we can later ensure results are the same $error1 = Get-PreviousError $error2 = Get-PreviousError # Run a command that should always succeed Get-ChildItem | Out-Null # Get the previous error one more time $error3 = Get-PreviousError $error1 -eq $error2 | Should -Be $true $error1 -eq $error3 | Should -Be $true } } } Describe 'xExchangeHelper\Assert-NoNewError' -Tag 'Helper' { Context 'After a new, unique error occurs' { It 'Should throw an exception' { # First get whatever error is currently on top of the stack $initialError = Get-PreviousError # Cause an error by trying to get a non-existent item Get-ChildItem $guid1 -ErrorAction SilentlyContinue { Assert-NoNewError -CmdletBeingRun "Get-ChildItem" -PreviousError $initialError } | Should -Throw } } Context 'When an error has not occurred' { It 'Should not throw an exception' { # First get whatever error is currently on top of the stack $initialError = Get-PreviousError # Run a command that should always succeed Get-ChildItem | Out-Null { Assert-NoNewError -CmdletBeingRun "Get-ChildItem" -PreviousError $initialError } | Should -Not -Throw } } } Describe 'xExchangeHelper\Assert-IsSupportedWithExchangeVersion' -Tag 'Helper' { $supportedVersionTestCases = @( @{Name='2013 Operation Supported on 2013'; ExchangeVersion='2013'; SupportedVersions='2013'} @{Name='2013 Operation Supported on 2013,2019'; ExchangeVersion='2013'; SupportedVersions='2013', '2019'} ) $notSupportedVersionTestCases = @( @{Name='2013 Operation Not Supported on 2016'; ExchangeVersion='2013'; SupportedVersions='2016'} @{Name='2013 Operation Not Supported on 2016,2019'; ExchangeVersion='2013'; SupportedVersions='2016', '2019'} ) Context 'When a supported version is passed' { It 'Should not throw an exception' -TestCases $supportedVersionTestCases { param($Name, $ExchangeVersion, $SupportedVersions) Mock -CommandName Get-ExchangeVersionYear -MockWith { return $ExchangeVersion } { Assert-IsSupportedWithExchangeVersion -ObjectOrOperationName $Name -SupportedVersions $SupportedVersions } | Should -Not -Throw } } Context 'When an unsupported version is passed' { It 'Should throw an exception' -TestCases $notSupportedVersionTestCases { param($Name, $ExchangeVersion, $SupportedVersions) Mock -CommandName Get-ExchangeVersionYear -MockWith { return $ExchangeVersion } { Assert-IsSupportedWithExchangeVersion -ObjectOrOperationName $Name -SupportedVersions $SupportedVersions } | Should -Throw } } } Describe 'xExchangeHelper\Compare-ADObjectIdToSmtpAddressString' -Tag 'Helper' { <# Define an empty function for Get-Recipient, so Pester has something to Mock. This cmdlet is normally loaded as part of GetRemoteExchangeSession. #> function Get-Recipient {} AfterEach { Assert-VerifiableMock } # Setup test objects for calls to Compare-ADObjectIdToSmtpAddressString $testADObjectID = New-Object -TypeName PSObject -Property @{DistinguishedName='CN=TestUser,DC=contoso,DC=local'} $testAddress = 'testuser@contoso.local' $testBadAddress = 'baduser@contoso.local' $testRecipient = New-Object -TypeName PSObject -Property @{ EmailAddresses = New-Object -TypeName PSObject -Property @{ AddressString = $testAddress } } Context 'When comparing an ADObjectID to a corresponding SMTP address' { It 'Should return $true' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } Mock -CommandName Get-Recipient -Verifiable -MockWith { return $testRecipient } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $testADObjectID -AddressString $testAddress $compareResults | Should -Be $true } } Context 'When comparing an ADObjectID to a non-corresponding SMTP address' { It 'Should return $false' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } Mock -CommandName Get-Recipient -Verifiable -MockWith { return $testRecipient } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $testADObjectID -AddressString $testBadAddress $compareResults | Should -Be $false } } Context 'When comparing an ADObjectID to an empty SMTP address' { It 'Should return $false' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $testADObjectID -AddressString '' $compareResults | Should -Be $false } } Context 'When comparing an ADObjectID to a null SMTP address' { It 'Should return $false' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $testADObjectID -AddressString $null $compareResults | Should -Be $false } } Context 'When comparing a null ADObjectID to an empty SMTP address' { It 'Should return $true' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $null -AddressString '' $compareResults | Should -Be $true } } Context 'When comparing a null ADObjectID to a null SMTP address' { It 'Should return $true' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $null -AddressString $null $compareResults | Should -Be $true } } Context 'When comparing a null ADObjectID to any SMTP address' { It 'Should return $false' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } $compareResults = Compare-ADObjectIdToSmtpAddressString -ADObjectId $null -AddressString $testAddress $compareResults | Should -Be $false } } Context 'When Get-Recipient returns $null' { It 'Should throw an exception' { Mock -CommandName Get-Command -Verifiable -MockWith { return '' } Mock -CommandName Get-Recipient -Verifiable -MockWith { return $null } { Compare-ADObjectIdToSmtpAddressString -ADObjectId $testADObjectID -AddressString $testBadAddress | Out-Null } | Should -Throw } } Context 'When Get-Command returns $null' { It 'Should throw an exception' { Mock -CommandName Get-Command -Verifiable -MockWith { return $null } { Compare-ADObjectIdToSmtpAddressString -ADObjectId $testADObjectID -AddressString $testBadAddress | Out-Null } | Should -Throw } } } Describe 'xExchangeHelper\Invoke-DotSourcedScript' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Invoke-DotSourcedScript is called with no parameters' { It 'Should execute fine' { $processes = Invoke-DotSourcedScript -ScriptPath 'Get-Process' $processes.Count | Should -BeGreaterThan 0 } } Context 'When Invoke-DotSourcedScript is called with parameters' { It 'Should execute fine' { $testProcess = 'svchost' $scriptParams = @{ Name = $testProcess } $processes = Invoke-DotSourcedScript -ScriptPath 'Get-Process' -ScriptParams $scriptParams ($processes | Where-Object -FilterScript {$_.ProcessName -like $testProcess}).Count | Should -BeGreaterThan 0 } } Context 'When Invoke-DotSourcedScript is called with SnapinsToRemove' { It 'Should call Remove-HelperSnapin' { Mock -CommandName Remove-HelperSnapin -Verifiable Invoke-DotSourcedScript -ScriptPath 'Get-Process' -SnapinsToRemove 'SomeSnapin' | Out-Null } } Context 'When Invoke-DotSourcedScript is called and CommandsToExecuteInScope is passed' { It 'Should execute the commands and return values from those commands' { $commandToExecuteAfterDotSourcing = @('Get-Process') $commandParamsToExecuteAfterDotSourcing = @{ 'Get-Process' = @{ Name = 'svchost' } } $returnValues = Invoke-DotSourcedScript -ScriptPath 'Start-Sleep' -ScriptParams @{Seconds=0} -CommandsToExecuteInScope $commandToExecuteAfterDotSourcing -ParamsForCommandsToExecuteInScope $commandParamsToExecuteAfterDotSourcing $returnValues.Count -gt 0 -and $returnValues.ContainsKey('Get-Process') -and $null -ne $returnValues['Get-Process'] | Should -Be $true } } } Describe 'xExchangeHelper\Remove-HelperSnapin' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Remove-HelperSnapin is called and a snapin is loaded' { It 'Should remove the snapin' { Mock -CommandName Get-PSSnapin -Verifiable -MockWith { return $true } Mock -CommandName Remove-PSSnapin -Verifiable Remove-HelperSnapin -SnapinsToRemove 'FakeSnapin' } } Context 'When Remove-HelperSnapin is called and a snapin is not loaded' { It 'Should execute without attempting to remove a snapin' { Mock -CommandName Get-PSSnapin -Verifiable Mock -CommandName Remove-PSSnapin Remove-HelperSnapin -SnapinsToRemove 'FakeSnapin' Assert-MockCalled -CommandName Remove-PSSnapin -Times 0 } } } Describe 'xExchangeHelper\Test-ExchangePresent' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $validExchangeYears = @( @{Year='2013'} @{Year='2016'} @{Year='2019'} ) Context 'When Test-ExchangePresent is called with a valid Exchange Version' { It 'Should return True' -TestCases $validExchangeYears { param ( [System.Object] $Year ) Mock -CommandName Get-ExchangeVersionYear -Verifiable -MockWith { return $Year } Test-ExchangePresent | Should -Be $true } } $inValidExchangeYears = @( @{Year='2012'} @{Year=''} @{Year='N/A'} @{Year=$null} ) Context 'When Test-ExchangePresent is called with an invalid Exchange Version' { It 'Should return False' -TestCases $inValidExchangeYears { param ( [System.Object] $Year ) Mock -CommandName Get-ExchangeVersionYear -Verifiable -MockWith { return $Year } Test-ExchangePresent | Should -Be $false } } } Describe 'xExchangeHelper\Test-ExchangeSetupComplete' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $setupCompleteCases = @( @{ ExchangePresent = $true PartiallyComplete = $false } ) Context 'When Test-ExchangeSetupComplete is called and setup is fully complete' { It 'Should return true' -TestCases $setupCompleteCases { param ( [System.Boolean] $ExchangePresent, [System.Boolean] $PartiallyComplete ) Mock -CommandName Test-ExchangePresent -Verifiable -MockWith { return $ExchangePresent } Mock -CommandName Test-ExchangeSetupPartiallyCompleted -Verifiable -MockWith { return $PartiallyComplete } Test-ExchangeSetupComplete | Should -Be $true } } $setupIncompleteCases = @( @{ ExchangePresent = $false PartiallyComplete = $false } @{ ExchangePresent = $true PartiallyComplete = $true } @{ # This last one shouldn't be possible, but let's test for it anyways ExchangePresent = $false PartiallyComplete = $true } ) Context 'When Test-ExchangeSetupComplete is called and setup is not fully complete' { It 'Should return false' -TestCases $setupIncompleteCases { param ( [System.Boolean] $ExchangePresent, [System.Boolean] $PartiallyComplete ) Mock -CommandName Test-ExchangePresent -Verifiable -MockWith { return $ExchangePresent } Mock -CommandName Test-ExchangeSetupPartiallyCompleted -Verifiable -MockWith { return $PartiallyComplete } Test-ExchangeSetupComplete | Should -Be $false } } } Describe 'xExchangeHelper\Test-ExchangeSetupPartiallyCompleted' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Test-ExchangeSetupPartiallyCompleted is called and no setup progress related registry keys remain' { It 'Should return false' { Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{ UnpackedVersion = 1 ConfiguredVersion = 1 } } Test-ExchangeSetupPartiallyCompleted | Should -Be $false } } $partiallyCompletedTestCases = @( @{ UnpackedVersion = 1 ConfiguredVersion = 1 Action = 1 } @{ UnpackedVersion = 1 ConfiguredVersion = 1 Watermark = 1 } @{ UnpackedVersion = 1 ConfiguredVersion = 1 Action = 1 Watermark = 1 } @{ UnpackedVersion = 1 Action = 1 Watermark = 1 } ) Context 'When Test-ExchangeSetupPartiallyCompleted is called and setup progress related registry keys remain' { It 'Should return true' -TestCases $partiallyCompletedTestCases { param ( [Nullable[System.Int32]] $UnpackedVersion, [Nullable[System.Int32]] $ConfiguredVersion, [Nullable[System.Int32]] $Action, [Nullable[System.Int32]] $Watermark ) Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{ UnpackedVersion = $UnpackedVersion ConfiguredVersion = $ConfiguredVersion Action = $Action Watermark = $Watermark } } Mock -CommandName Write-Warning -Verifiable Test-ExchangeSetupPartiallyCompleted | Should -Be $true } } } Describe 'xExchangeHelper\Set-WSManConfigStatus' -Tag 'Helper' { # Define an empty winrm function so we can Mock calls to the winrm executable function winrm {} AfterEach { Assert-VerifiableMock } Context 'When Set-WSManConfigStatus is called and the UpdatedConfig key does not exist' { It 'Should attempt to configure WinRM' { Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{SomeOtherProp = 'SomeOtherValue'} } Mock -CommandName Set-Location -Verifiable Mock -CommandName winrm -Verifiable Set-WSManConfigStatus } } Context 'When Set-WSManConfigStatus is called and the UpdatedConfig key exists' { It 'Should execute without attempting to configure WinRM' { Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{UpdatedConfig = 'SomeValue'} } Mock -CommandName winrm Set-WSManConfigStatus Assert-MockCalled -CommandName winrm -Times 0 } } Context 'When Set-WSManConfigStatus is called and the WSMan key does not exist' { It 'Should throw an exception' { Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return $null } { Set-WSManConfigStatus } | Should -Throw -ExpectedMessage 'Unable to find registry key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN' } } } Describe 'xExchangeHelper\Test-UMLanguagePackInstalled' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $testCultureName = 'TestCulture' Context 'When Test-UMLanguagePackInstalled is called and the specified Culture key exists' { It 'Should return true' { Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{TestCulture = $testCultureName} } Test-UMLanguagePackInstalled -Culture $testCultureName | Should -Be $true } } Context 'When Test-UMLanguagePackInstalled is called and the specified Culture key does not exist' { It 'Should return false' { Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{SomeOtherCulture = 'SomeOtherCulture'} } Test-UMLanguagePackInstalled -Culture $testCultureName | Should -Be $false } } } Describe 'xExchangeHelper\Test-ShouldInstallUMLanguagePack' -Tag 'Helper' { $noLanguagePackArgs = '/IAcceptExchangeServerLicenseTerms /mode:Install /r:MB' $singleLanguagePackArgs = '/AddUmLanguagePack:ja-JP /s:d:\Exchange\UMLanguagePacks /IAcceptExchangeServerLicenseTerms' $multiLanguagePackArgs = '/AddUmLanguagePack:es-MX,de-DE /s:d:\Exchange\UMLanguagePacks /IAcceptExchangeServerLicenseTerms' AfterEach { Assert-VerifiableMock } Context 'When Test-ShouldInstallUMLanguagePack is called and AddUMLanguagePack is not specified' { It 'Should return false' { Test-ShouldInstallUMLanguagePack -Arguments $noLanguagePackArgs | Should -Be $false } } Context 'When Test-ShouldInstallUMLanguagePack is called with AddUMLanguagePack, a language is specified, and the language pack is not installed' { It 'Should return true' { Mock -CommandName Test-UMLanguagePackInstalled -Verifiable -MockWith { return $false } Test-ShouldInstallUMLanguagePack -Arguments $singleLanguagePackArgs | Should -Be $true } It 'Should return true' { Mock -CommandName Test-UMLanguagePackInstalled -Verifiable -MockWith { return $false } Test-ShouldInstallUMLanguagePack -Arguments $multiLanguagePackArgs | Should -Be $true } } Context 'When Test-ShouldInstallUMLanguagePack is called with AddUMLanguagePack, a language is specified, and the language pack is installed' { It 'Should return false' { Mock -CommandName Test-UMLanguagePackInstalled -Verifiable -MockWith { return $true } Test-ShouldInstallUMLanguagePack -Arguments $singleLanguagePackArgs | Should -Be $false } It 'Should return false' { Mock -CommandName Test-UMLanguagePackInstalled -Verifiable -MockWith { return $true } Test-ShouldInstallUMLanguagePack -Arguments $multiLanguagePackArgs | Should -Be $false } } } Describe 'xExchangeHelper\Test-ExchangeSetupRunning' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Test-ExchangeSetupRunning is called and the setup process is running' { It 'Should return true' { Mock -CommandName Get-Process -Verifiable -MockWith { return 'SomeProcess' } Test-ExchangeSetupRunning | Should -Be $true } } Context 'When Test-ExchangeSetupRunning is called and the setup process is not running' { It 'Should return false' { Mock -CommandName Get-Process -Verifiable -MockWith { return $null } Test-ExchangeSetupRunning | Should -Be $false } } } Describe 'xExchangeHelper\Wait-ForProcessStart' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Wait-ForProcessStart is called and the given process is detected' { It 'Should return true' { Mock -CommandName Get-Process -Verifiable -MockWith { return 'SomeProcess' } Wait-ForProcessStart -ProcessName 'SomeProcess' | Should -Be $true } } Context 'When Wait-ForProcessStart is called and the given process is not detected' { It 'Should return false' { Mock -CommandName Get-Process -Verifiable -MockWith { return $null } Wait-ForProcessStart -ProcessName 'SomeProcess' -SecondsPerSleep 0 -MaxSleepCycles 1 | Should -Be $false } } } Describe 'xExchangeHelper\Wait-ForProcessStop' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Wait-ForProcessStop is called and the given process stops' { It 'Should return true' { Mock -CommandName Get-Process -Verifiable -MockWith { return $null } Wait-ForProcessStop -ProcessName 'SomeProcess' | Should -Be $true } } Context 'When Wait-ForProcessStop is called and the given process does not stop' { It 'Should return false' { Mock -CommandName Get-Process -Verifiable -MockWith { return 'SomeProcess' } Wait-ForProcessStop -ProcessName 'SomeProcess' -SecondsPerSleep 0 -MaxSleepCycles 1 | Should -Be $false } } } Describe 'xExchangeHelper\Assert-ExchangeSetupArgumentsComplete' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Assert-ExchangeSetupArgumentsComplete is called and setup is complete' { It 'Should execute without throwing an exception' { Mock -CommandName Test-Path -Verifiable -MockWith { return $true } Mock -CommandName Get-ExchangeInstallStatus -Verifiable -MockWith { return @{ SetupComplete = $true } } { Assert-ExchangeSetupArgumentsComplete -Path 'c:\Exchange\setup.exe' -Arguments 'SetupArgs' } | Should -Not -Throw } } Context 'When Assert-ExchangeSetupArgumentsComplete is called and setup is not complete' { It 'Should throw an exception' { Mock -CommandName Test-Path -Verifiable -MockWith { return $true } Mock -CommandName Get-ExchangeInstallStatus -Verifiable -MockWith { return @{ SetupComplete = $false } } { Assert-ExchangeSetupArgumentsComplete -Path 'c:\Exchange\setup.exe' -Arguments 'SetupArgs' } | Should -Throw -ExpectedMessage 'Exchange setup did not complete successfully. See "<system drive>\ExchangeSetupLogs\ExchangeSetup.log" for details.' } } Context 'When Assert-ExchangeSetupArgumentsComplete is called with wrong file path' { It 'Should throw an exception' { Mock -CommandName Test-Path -Verifiable -MockWith { return $false } { Assert-ExchangeSetupArgumentsComplete -Path 'c:\Exchange\setup.exe' -Arguments 'SetupArgs' } | Should -Throw -ExpectedMessage "Path to Exchange setup 'c:\Exchange\setup.exe' does not exists." } } } Describe 'xExchangeHelper\Get-ExchangeVersionYear' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $validProductVersions = @( @{ VersionMajor = 15 VersionMinor = 0 Year = '2013' } @{ VersionMajor = 15 VersionMinor = 1 Year = '2016' } @{ VersionMajor = 15 VersionMinor = 2 Year = '2019' } ) $invalidProductVersions = @( @{ VersionMajor = 15 VersionMinor = 7 Year = $null } @{ VersionMajor = 14 VersionMinor = 0 Year = $null } ) Context 'When Get-ExchangeVersionYear is called and finds a valid VersionMajor and VersionMinor' { It 'Should return the correct Exchange year' -TestCases $validProductVersions { param ( [System.Int32] $VersionMajor, [System.Int32] $VersionMinor, [System.String] $Year ) Mock -CommandName Get-DetailedInstalledVersion -Verifiable -MockWith { return @{ VersionMajor = $VersionMajor VersionMinor = $VersionMinor } } Get-ExchangeVersionYear | Should -Be $Year } } Context 'When Get-ExchangeVersionYear is called and finds an invalid VersionMajor or VersionMinor without ThrowIfUnknownVersion' { It 'Should return <Year>' -TestCases $invalidProductVersions { param ( [System.Int32] $VersionMajor, [System.Int32] $VersionMinor, [System.String] $Year ) Mock -CommandName Get-DetailedInstalledVersion -Verifiable -MockWith { return @{ VersionMajor = $VersionMajor VersionMinor = $VersionMinor } } Get-ExchangeVersionYear | Should -Be $null } } Context 'When Get-ExchangeVersionYear is called and finds an invalid VersionMajor or VersionMinor and ThrowIfUnknownVersion is specified' { It 'Should throw an exception' -TestCases $invalidProductVersions { param ( [System.Int32] $VersionMajor, [System.Int32] $VersionMinor, [System.String] $Year ) Mock -CommandName Get-DetailedInstalledVersion -Verifiable -MockWith { return @{ VersionMajor = $VersionMajor VersionMinor = $VersionMinor } } { Get-ExchangeVersionYear -ThrowIfUnknownVersion $true } | Should -Throw -ExpectedMessage 'Failed to discover a known Exchange Version' } } } Describe 'xExchangeHelper\Get-ExchangeUninstallKey' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Get-ExchangeUninstallKey is called and Exchange 2016 or 2019 is installed' { It 'Should return the 2016/2019 uninstall key' { Mock -CommandName Get-Item -Verifiable -ParameterFilter {$Path -like 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{CD981244-E9B8-405A-9026-6AEB9DCEF1F1}'} -MockWith { return $true } Get-ExchangeUninstallKey | Should -Be -Not $Null } } Context 'When Get-ExchangeUninstallKey is called and Exchange 2013 is installed' { It 'Should return the 2016/2019 uninstall key' { Mock -CommandName Get-Item -Verifiable -ParameterFilter {$Path -like 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{CD981244-E9B8-405A-9026-6AEB9DCEF1F1}'} -MockWith { return $null } Mock -CommandName Get-Item -Verifiable -ParameterFilter {$Path -like 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{4934D1EA-BE46-48B1-8847-F1AF20E892C1}'} -MockWith { return $true } Get-ExchangeUninstallKey | Should -Be -Not $Null } } Context 'When Get-ExchangeUninstallKey is called and no Exchange is installed' { It 'Should return NULL' { Mock -CommandName Get-Item -Verifiable -ParameterFilter {$Path -like 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{CD981244-E9B8-405A-9026-6AEB9DCEF1F1}'} -MockWith { return $null } Mock -CommandName Get-Item -Verifiable -ParameterFilter {$Path -like 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{4934D1EA-BE46-48B1-8847-F1AF20E892C1}'} -MockWith { return $null } Get-ExchangeUninstallKey | Should -Be $Null } } } Describe 'xExchangeHelper\Get-DetailedInstalledVersion' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When DetailedInstalledVersion is called and a valid key is returned by Get-ExchangeUninstallKey' { It 'Should return custom object with VersionMajor and VersionMinor properties' { Mock -CommandName Get-ExchangeUninstallKey -Verifiable -MockWith { return @{Name = 'SomeKeyName'} } Mock -CommandName Get-ItemProperty -Verifiable -ParameterFilter {$Name -eq 'DisplayVersion'} -MockWith { return [PSCustomObject] @{DisplayVersion = '15.1.1531.13'} } Mock -CommandName Get-ItemProperty -Verifiable -ParameterFilter {$Name -eq 'VersionMajor'} -MockWith { return [PSCustomObject] @{VersionMajor = 15} } Mock -CommandName Get-ItemProperty -Verifiable -ParameterFilter {$Name -eq 'VersionMinor'} -MockWith { return [PSCustomObject] @{ VersionMinor = 1 } } $installedVersionDetails = Get-DetailedInstalledVersion $installedVersionDetails.VersionMajor | Should -Be 15 $installedVersionDetails.VersionMinor | Should -Be 1 $installedVersionDetails.VersionBuild | Should -Be 1531 $installedVersionDetails.DisplayVersion | Should -Be '15.1.1531.13' } } Context 'When DetailedInstalledVersion is called and no valid key is returned by Get-ExchangeUninstallKey' { It 'Should return NULL' { Mock -CommandName Get-ExchangeUninstallKey -Verifiable -MockWith { return $null } Get-DetailedInstalledVersion | Should -Be $null } } } Describe 'Test-ShouldUpgradeExchange' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $cases = @( @{ Case = 'Setup.exe is newer. Commandline Argment is /mode:Upgrade' SetupVersionMajor = 15 SetupVersionMinor = 1 SetupVersionBuild = 2000 ExchangeVersionMajor = 15 ExchangeVersionMinor = 1 ExchangeVersionBuild = 1800 Result = $true Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' } @{ Case = 'Setup.exe is newer. Commandline Argment is /m:Upgrade' SetupVersionMajor = 15 SetupVersionMinor = 1 SetupVersionBuild = 2000 ExchangeVersionMajor = 15 ExchangeVersionMinor = 1 ExchangeVersionBuild = 1800 Result = $true Arguments = '/m:upgrade /Iacceptexchangeserverlicenseterms' } @{ Case = 'Setup.exe and installed Exchange version is the same.' SetupVersionMajor = 15 SetupVersionMinor = 1 SetupVersionBuild = 2000 ExchangeVersionMajor = 15 ExchangeVersionMinor = 1 ExchangeVersionBuild = 2000 Result = $false Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' } @{ Case = 'Installed Exchange version is different than the setup.exe. e.g. 2013, 2016' SetupVersionMajor = 15 SetupVersionMinor = 1 SetupVersionBuild = 2000 ExchangeVersionMajor = 15 ExchangeVersionMinor = 0 ExchangeVersionBuild = 2000 Result = $false Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' } @{ Case = 'Setup.exe version is different than the installed Exchange. e.g. 2013, 2016' SetupVersionMajor = 15 SetupVersionMinor = 0 SetupVersionBuild = 2000 ExchangeVersionMajor = 15 ExchangeVersionMinor = 1 ExchangeVersionBuild = 2000 Result = $false Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' } ) Context 'When Test-ShouldUpgradeExchange is called for different cases.' { It 'For case <Case> should return <Result>' -TestCases $cases { Param( [System.String] $Case, [System.Int32] $SetupVersionMajor, [System.Int32] $SetupVersionMinor, [System.Int32] $SetupVersionBuild, [System.Int32] $ExchangeVersionMajor, [System.Int32] $ExchangeVersionMinor, [System.Int32] $ExchangeVersionBuild, [System.Boolean] $Result, [System.String] $Arguments ) Mock -CommandName Get-SetupExeVersion -Verifiable -MockWith { return [PSCustomObject] @{ VersionMajor = $SetupVersionMajor VersionMinor = $SetupVersionMinor VersionBuild = $SetupVersionBuild } } Mock -CommandName Get-DetailedInstalledVersion -Verifiable -MockWith { return [PSCustomObject] @{ VersionMajor = $ExchangeVersionMajor VersionMinor = $ExchangeVersionMinor VersionBuild = $ExchangeVersionBuild } } Test-ShouldUpgradeExchange -Path 'test' -Arguments $Arguments | Should -Be $Result } } Context 'When Get-SetupExeVersion returns null within Test-ShouldUpgradeExchange.' { It 'Should return $false' { $Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' Mock -CommandName Get-SetupExeVersion -Verifiable -MockWith { return $null } Mock -CommandName Write-Error -Verifiable -MockWith {} Test-ShouldUpgradeExchange -Path 'test' -Arguments $Arguments | Should -Be $false } } Context 'When Get-DetailedInstalledVersion returns null within Test-ShouldUpgradeExchange.' { It 'Should return with $false' { $Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' Mock -CommandName Write-Error -Verifiable -MockWith {} Mock -CommandName Get-SetupExeVersion -Verifiable -MockWith { return [PSCustomObject] @{ VersionMajor = 15 VersionMinor = 1 VersionBuild = 1234 } } Mock -CommandName Get-DetailedInstalledVersion -Verifiable -MockWith { return $null } Test-ShouldUpgradeExchange -Path 'test' -Arguments $Arguments | Should -Be $false } } Context 'When Get-DetailedInstalledVersion and Get-SetupExeVersion return null within Test-ShouldUpgradeExchange.' { It 'Should return with $false' { $Arguments = '/mode:Upgrade /Iacceptexchangeserverlicenseterms' Mock -CommandName Write-Error -Verifiable -MockWith {} Mock -CommandName Get-SetupExeVersion -Verifiable -MockWith { return $false } Test-ShouldUpgradeExchange -Path 'test' -Arguments $Arguments | Should -Be $false } } Context 'When calling Test-ShouldUpgradeExchange with commandline arguments, which belongs to a simple install not to an upgrade.' { It 'Should return with $false' { $Arguments = '/mode:Install /role:Mailbox /IAcceptExchangeServerLicenseTerms' Test-ShouldUpgradeExchange -Path 'test' -Arguments $Arguments | Should -Be $false } } } Describe 'xExchangeHelper\Get-SetupExeVersion' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Get-SetupExeVersion is called and the setup executable is found.' { It 'Should return the file version information.' { Mock -CommandName Test-Path -Verifiable -MockWith { return $true } Mock -CommandName Get-ChildItem -Verifiable -MockWith { @{ VersionInfo = @{ ProductMajorPart = 1 ProductMinorPart = 2 ProductBuildPart = 3 } } } $version = Get-SetupExeVersion -Path 'SomePath' $version.VersionMajor | Should -Be 1 $version.VersionMinor | Should -Be 2 $version.VersionBuild | Should -Be 3 } } Context 'When Get-SetupExeVersion is called and the setup executable is not found' { It 'Should return NULL.' { Mock -CommandName Test-Path -Verifiable -MockWith { return $false } Get-SetupExeVersion -Path 'SomePath' | Should -Be $null } } } Describe 'xExchangeHelper\Get-ExistingRemoteExchangeSession' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Get-ExistingRemoteExchangeSession is called and there is an existing Remote Exchange Session in an Opened state' { It 'Should return the session' { Mock -CommandName Get-PSSession -Verifiable -MockWith { return @{ State = 'Opened' } } Get-ExistingRemoteExchangeSession | Should -Be -Not $null } } Context 'When Get-ExistingRemoteExchangeSession is called and there is an existing Remote Exchange Session in a state other than Opened' { It 'Should return null' { Mock -CommandName Get-PSSession -Verifiable -MockWith { return @{ State = 'Broken' } } Mock -CommandName Remove-RemoteExchangeSession -Verifiable Get-ExistingRemoteExchangeSession | Should -Be $null } } Context 'When Get-ExistingRemoteExchangeSession is called and there is not an existing Remote Exchange Session' { It 'Should return null' { Mock -CommandName Get-PSSession -Verifiable -MockWith { return $null } Get-ExistingRemoteExchangeSession | Should -Be $null } } } Describe 'xExchangeHelper\Get-RemoteExchangeSession' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Get-RemoteExchangeSession is called and Exchange setup is running' { It 'Should throw an exception' { Mock -CommandName Test-ExchangeSetupRunning -Verifiable -MockWith { return $true } { Get-RemoteExchangeSession } | ` Should -Throw -ExpectedMessage 'Exchange Setup is currently running. Preventing creation of new Remote PowerShell session to Exchange.' } } Context 'When Get-RemoteExchangeSession is called, setup is not running, and an existing session exists' { It 'Should return the existing session' { Mock -CommandName Test-ExchangeSetupRunning -Verifiable -MockWith { return $false } Mock -CommandName Get-ExistingRemoteExchangeSession -Verifiable -MockWith { return @{ State = 'Opened' } } Mock -CommandName New-RemoteExchangeSession Mock -CommandName Import-RemoteExchangeSession Get-RemoteExchangeSession Assert-MockCalled -CommandName New-RemoteExchangeSession -Times 0 } } Context 'When Get-RemoteExchangeSession is called, setup is not running, and no existing session exists' { It 'Should create and return the existing session' { Mock -CommandName Test-ExchangeSetupRunning -Verifiable -MockWith { return $false } Mock -CommandName Get-ExistingRemoteExchangeSession -Verifiable -MockWith { return $null } Mock -CommandName New-RemoteExchangeSession -Verifiable -MockWith { return @{ State = 'Opened' } } Mock -CommandName Import-RemoteExchangeSession -Verifiable Get-RemoteExchangeSession } } Context 'When Get-RemoteExchangeSession is called, setup is not running, no existing session exists, and creation of the session fails' { It 'Should throw an exception' { Mock -CommandName Test-ExchangeSetupRunning -Verifiable -MockWith { return $false } Mock -CommandName Get-ExistingRemoteExchangeSession -Verifiable -MockWith { return $null } Mock -CommandName New-RemoteExchangeSession -Verifiable -MockWith { return $null } { Get-RemoteExchangeSession } | Should -Throw -ExpectedMessage 'Failed to establish remote PowerShell session to local server.' } } } Describe 'xExchangeHelper\New-RemoteExchangeSession' -Tag 'Helper' { # Define empty function _NewExchangeRunspace so Mock can override it function _NewExchangeRunspace {} AfterEach { Assert-VerifiableMock } Context 'When New-RemoteExchangeSession is called and Exchange is not installed' { It 'Should throw an exception' { Mock -CommandName Test-ExchangeSetupComplete -Verifiable -MockWith { return $false } { New-RemoteExchangeSession } | ` Should -Throw -ExpectedMessage 'A supported version of Exchange is either not present, or not fully installed on this machine.' } } Context 'When New-RemoteExchangeSession is called and Exchange is installed' { It 'Should create and return a PSSession' { Mock -CommandName Test-ExchangeSetupComplete -Verifiable -MockWith { return $true } Mock -CommandName Get-CimInstance -Verifiable -MockWith { return @{ Domain = 'contoso.local' } } Mock -CommandName Get-ItemProperty -Verifiable -MockWith { return @{ MsiInstallPath = 'C:\Program Files\Microsoft\Exchange Server\V15\' } } Mock -CommandName Invoke-DotSourcedScript -Verifiable -MockWith { return @{ '_NewExchangeRunspace' = @{ Name = 'NewSession' } } } New-RemoteExchangeSession | Should -Be -Not $null } } } Describe 'xExchangeHelper\Import-RemoteExchangeSession' -Tag 'Helper' { # Override functions which have non-Mockable parameter types function Import-PSSession {} function Import-Module {} AfterEach { Assert-VerifiableMock } $commandToLoad = 'Get-ExchangeServer' $commandsToLoad = @($commandToLoad) Context 'When Import-RemoteExchangeSession is called and CommandsToLoad is passed' { It 'Should import the session and load the commands' { Mock ` -CommandName Import-PSSession ` -Verifiable ` -ParameterFilter {$CommandsToLoad.Count -eq 1 -and $CommandsToLoad[0] -like $commandToLoad} -MockWith { return $true } Mock -CommandName Import-Module -Verifiable Import-RemoteExchangeSession -Session 'SomeSession' -CommandsToLoad $commandsToLoad } } Context 'When Import-RemoteExchangeSession is called and CommandsToLoad is not passed' { It 'Should import the session and load all commands' { Mock ` -CommandName Import-PSSession ` -Verifiable ` -ParameterFilter {$CommandsToLoad.Count -eq 1 -and $CommandsToLoad[0] -like '*'} -MockWith { return $true } Mock -CommandName Import-Module -Verifiable Import-RemoteExchangeSession -Session 'SomeSession' } } } Describe 'xExchangeHelper\Remove-RemoteExchangeSession' -Tag 'Helper' { # Override functions which have non-Mockable parameter types function Remove-PSSession {} AfterEach { Assert-VerifiableMock } Context 'When Remove-RemoteExchangeSession is called and sessions exist' { It 'Should remove the sessions' { Mock -CommandName Get-ExistingRemoteExchangeSession -Verifiable -MockWith { return 'SomeSession' } Mock -CommandName Remove-PSSession -Verifiable Remove-RemoteExchangeSession } } } Describe 'xExchangeHelper\Compare-StringToString' -Tag 'Helper' { $trueCaseInsensitiveCases = @( @{ String1 = 'aBc' String2 = 'AbC' } @{ String1 = 'abc' String2 = 'Abc' } @{ String1 = '' String2 = '' } @{ String1 = '' String2 = $null } @{ String1 = $null String2 = $null } ) Context 'When Compare-StringToString is called with the Ignore case switch and the strings are like each other' { It 'Should return true' -TestCases $trueCaseInsensitiveCases { param ( [System.String] $String1, [System.String] $String2 ) Compare-StringToString -String1 $String1 -String2 $String2 -IgnoreCase | Should -Be $true } } $trueCaseSensitiveCases = @( @{ String1 = 'ABC' String2 = 'ABC' } @{ String1 = 'abc' String2 = 'abc' } ) Context 'When Compare-StringToString is called without the Ignore case switch and the strings are equal to each other' { It 'Should return true' -TestCases $trueCaseSensitiveCases { param ( [System.String] $String1, [System.String] $String2 ) Compare-StringToString -String1 $String1 -String2 $String2 | Should -Be $true } } $falseCaseInsensitiveCases = @( @{ String1 = 'aBcd' String2 = 'AbC' } @{ String1 = 'abcd' String2 = 'Abc' } ) Context 'When Compare-StringToString is called with the Ignore case switch and the strings are not like each other' { It 'Should return false' -TestCases $falseCaseInsensitiveCases { param ( [System.String] $String1, [System.String] $String2 ) Compare-StringToString -String1 $String1 -String2 $String2 -IgnoreCase | Should -Be $false } } $falseCaseSensitiveCases = @( @{ String1 = 'abc' String2 = 'ABC' } ) Context 'When Compare-StringToString is called without the Ignore case switch and the strings are not equal to each other' { It 'Should return false' -TestCases $falseCaseSensitiveCases { param ( [System.String] $String1, [System.String] $String2 ) Compare-StringToString -String1 $String1 -String2 $String2 | Should -Be $false } } } Describe 'xExchangeHelper\Compare-BoolToBool' -Tag 'Helper' { $trueBooleanTestCases = @( @{ Bool1 = $true Bool2 = $true } @{ Bool1 = $false Bool2 = $false } @{ Bool1 = $null Bool2 = $false } @{ Bool1 = $false Bool2 = $null } ) Context 'When Compare-BoolToBool is called and both Booleans are like each other' { It 'Should return true' -TestCases $trueBooleanTestCases { param ( [Nullable[System.Boolean]] $Bool1, [Nullable[System.Boolean]] $Bool2 ) Compare-BoolToBool -Bool1 $Bool1 -Bool2 $Bool2 | Should -Be $true } } $falseBooleanTestCases = @( @{ Bool1 = $true Bool2 = $false } @{ Bool1 = $false Bool2 = $true } @{ Bool1 = $true Bool2 = $null } @{ Bool1 = $null Bool2 = $true } ) Context 'When Compare-BoolToBool is called and both Booleans are not like each other' { It 'Should return false' -TestCases $falseBooleanTestCases { param ( [Nullable[System.Boolean]] $Bool1, [Nullable[System.Boolean]] $Bool2 ) Compare-BoolToBool -Bool1 $Bool1 -Bool2 $Bool2 | Should -Be $false } } } Describe 'xExchangeHelper\Compare-UnlimitedToString' -Tag 'Helper' { # Override functions which have non-Mockable parameter types function Compare-ByteQuantifiedSizeToString {} AfterEach { Assert-VerifiableMock } $unlimitedUnlimited = @{ IsUnlimited = $true } Context 'When Compare-UnlimitedToString is called and the Unlimited is set to Unlimited' { It 'Should call Compare-StringToString, passing Unlimited as the first string, and the input string as the second' { Mock ` -CommandName Compare-StringToString ` -ParameterFilter {$String2 -eq 'unlimitedUnlimitedComp'} ` -Verifiable ` -MockWith { return $true } Compare-UnlimitedToString -Unlimited $unlimitedUnlimited -String 'unlimitedUnlimitedComp' } } $unlimitedInt32 = @{ IsUnlimited = $false Value = [System.Int32] 1 } Context 'When Compare-UnlimitedToString is called, the Unlimited is not set to Unlimited, and the string equals Unlimited' { It 'Should return false' { Mock -CommandName Compare-StringToString -ParameterFilter {$String2 -eq 'Unlimited'} -Verifiable -MockWith { return $true } Compare-UnlimitedToString -Unlimited $unlimitedInt32 -String 'Unlimited' | Should -Be $false } } Context 'When Compare-UnlimitedToString is called, the Unlimited is not set to Unlimited, and the Unlimited Value is an Int32' { It 'Should call Compare-StringToString, passing the Unlimited value as the first string, and the input string as the second' { Mock ` -CommandName Compare-StringToString ` -ParameterFilter {$String1 -eq $unlimitedInt32.Value.ToString() -and $String2 -eq '2'} ` -Verifiable ` -MockWith { return $true } Compare-UnlimitedToString -Unlimited $unlimitedInt32 -String '2' } } $unlimitedOther = @{ IsUnlimited = $false } Context 'When Compare-UnlimitedToString is called, the Unlimited is not set to Unlimited, and the Unlimited Value is not an Int32' { It 'Should call Compare-ByteQuantifiedSizeToString' { Mock -CommandName Compare-StringToString -Verifiable -MockWith { return $false } Mock -CommandName Compare-ByteQuantifiedSizeToString -Verifiable Compare-UnlimitedToString -Unlimited $unlimitedOther -String '2' } } } Describe 'xExchangeHelper\Convert-StringToArray' -Tag 'Helper' { Context 'When Convert-StringToArray is called, the string does not contain the separator, and does not contain whitespace' { It 'Should return the same string' { $outputArray = Convert-StringToArray -StringIn 'Test' -Separator ',' $outputArray.Count -eq 1 -and $outputArray[0] -ceq 'Test' } } Context 'When Convert-StringToArray is called, the string does not contain the separator, and does contains whitespace' { It 'Should return the string without whitespace' { $outputArray = Convert-StringToArray -StringIn ' Test ' -Separator ',' $outputArray.Count -eq 1 -and $outputArray[0] -ceq 'Test' } } Context 'When Convert-StringToArray is called, the string contains a separator, and substrings have a mix of whitespace and no whitespace' { It 'Should return the split, trimmed strings' { $outputArray = Convert-StringToArray -StringIn 'Abc, deF, ghi ,jkl ,mno' -Separator ',' $outputArray.Count | Should -Be 5 $outputArray.Contains('Abc') | Should -Be $true $outputArray.Contains('deF') | Should -Be $true $outputArray.Contains('ghi') | Should -Be $true $outputArray.Contains('jkl') | Should -Be $true $outputArray.Contains('mno') | Should -Be $true } } Context 'When Convert-StringToArray is called with a null string' { It 'Should return an array with 1 empty string' { $outputArray = Convert-StringToArray -StringIn $null -Separator ',' $outputArray.Count -eq 1 -and [String]::IsNullOrEmpty($outputArray[0]) | Should -Be $true } } } Describe 'xExchangeHelper\Convert-StringArrayToLowerCase' -Tag 'Helper' { Context 'When Convert-StringArrayToLowerCase is called' { It 'All input array members show be converted to lower case, and null members should be converted to empty strings' { [System.String[]] $inputArray = @('ABC', 'dEf', $null, 'GhI', 'jkl', '', 'mnO') $outputArray = Convert-StringArrayToLowerCase -Array $inputArray $outputArray.Count | Should -Be 7 $outputArray.Contains('abc') | Should -Be $true $outputArray.Contains('def') | Should -Be $true $outputArray.Contains('ghi') | Should -Be $true $outputArray.Contains('jkl') | Should -Be $true $outputArray.Contains('mno') | Should -Be $true $outputArray[2] | Should -Be '' $outputArray[5] | Should -Be '' } } } Describe 'xExchangeHelper\Compare-ArrayContent' -Tag 'Helper' { $trueCaseInsensitiveCases = @( @{ Array1Param = @('aBc', '', 'deF') Array1Lower = @('abc', '', 'def') Array2Param = @('', 'DEF', 'AbC') Array2Lower = @('', 'def', 'abc') } @{ Array1Param = @() Array1Lower = @() Array2Param = @() Array2Lower = @() } @{ Array1Param = @('abc', 'def') Array1Lower = @('abc', 'def') Array2Param = @('abc', 'def') Array2Lower = @('abc', 'def') } ) Context 'When Compare-ArrayContent is called with IgnoreCase and the arrays contain the same contents' { It 'Should return true' -TestCases $trueCaseInsensitiveCases { param ( [System.String[]] $Array1Param, [System.String[]] $Array1Lower, [System.String[]] $Array2Param, [System.String[]] $Array2Lower ) Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array1Param -DifferenceObject $Array1 )} ` -Verifiable ` -MockWith { return $Array1Lower } Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array2Param -DifferenceObject $Array2 )} ` -Verifiable ` -MockWith { return $Array2Lower } Compare-ArrayContent -Array1 $Array1Param -Array2 $Array2Param -IgnoreCase | Should -Be $true } } $falseCaseInsensitiveCases = @( @{ Array1Param = @('aBc', '', 'deF') Array1Lower = @('abc', '', 'def') Array2Param = @('DEF', 'AbC') Array2Lower = @('def', 'abc') } @{ Array1Param = @() Array1Lower = @() Array2Param = @('') Array2Lower = @('') } @{ Array1Param = @('abc', 'def') Array1Lower = @('abc', 'def') Array2Param = @('abc', 'def', 'GHI') Array2Lower = @('abc', 'def', 'ghi') } ) Context 'When Compare-ArrayContent is called with IgnoreCase and the arrays do not contain the same contents' { It 'Should return false' -TestCases $falseCaseInsensitiveCases { param ( [System.String[]] $Array1Param, [System.String[]] $Array1Lower, [System.String[]] $Array2Param, [System.String[]] $Array2Lower ) Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array1Param -DifferenceObject $Array1 )} ` -Verifiable ` -MockWith { return $Array1Lower } Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array2Param -DifferenceObject $Array2 )} ` -Verifiable ` -MockWith { return $Array2Lower } Compare-ArrayContent -Array1 $Array1Param -Array2 $Array2Param -IgnoreCase | Should -Be $false } } $trueCaseSensitiveCases = @( @{ Array1 = @('aBc', '', 'deF') Array2 = @('', 'aBc', 'deF') } @{ Array1 = @() Array2 = @() } @{ Array1 = @('abc', 'def') Array2 = @('def', 'abc') } ) Context 'When Compare-ArrayContent is called without IgnoreCase and the arrays contain the same contents' { It 'Should return true' -TestCases $trueCaseSensitiveCases { param ( [System.String[]] $Array1, [System.String[]] $Array2 ) Mock -CommandName Convert-StringArrayToLowerCase Compare-ArrayContent -Array1 $Array1 -Array2 $Array2 | Should -Be $true Assert-MockCalled Convert-StringArrayToLowerCase -Times 0 } } $falseCaseSensitiveCases = @( @{ Array1 = @('aBc', '', 'deF') Array2 = @('aBc', 'deF') } @{ Array1 = @('') Array2 = @() } @{ Array1 = @('abc', 'def') Array2 = @('ABC', 'DEF') } @{ Array1 = @('abc', 'def') Array2 = @('DEF', 'abc') } ) Context 'When Compare-ArrayContent is called without IgnoreCase and the arrays do not contain the same contents' { It 'Should return false' -TestCases $falseCaseSensitiveCases { param ( [System.String[]] $Array1, [System.String[]] $Array2 ) Mock -CommandName Convert-StringArrayToLowerCase Compare-ArrayContent -Array1 $Array1 -Array2 $Array2 | Should -Be $false Assert-MockCalled Convert-StringArrayToLowerCase -Times 0 } } } Describe 'xExchangeHelper\Test-ArrayElementsInSecondArray' -Tag 'Helper' { $trueCaseInsensitiveCases = @( @{ Array1Param = @('aBc', '', 'deF') Array1Lower = @('abc', '', 'def') Array2Param = @('', 'DEF', 'AbC') Array2Lower = @('', 'def', 'abc') } @{ Array1Param = @() Array1Lower = @() Array2Param = @() Array2Lower = @() } @{ Array1Param = @() Array1Lower = @() Array2Param = @('ABC') Array2Lower = @('abc') } @{ Array1Param = @('abc', 'def') Array1Lower = @('abc', 'def') Array2Param = @('abc', 'GHI', 'def') Array2Lower = @('abc', 'ghi', 'def') } ) Context 'When Test-ArrayElementsInSecondArray is called with IgnoreCase and the arrays contain the same contents' { It 'Should return true' -TestCases $trueCaseInsensitiveCases { param ( [System.String[]] $Array1Param, [System.String[]] $Array1Lower, [System.String[]] $Array2Param, [System.String[]] $Array2Lower ) Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array1Param -DifferenceObject $Array1 )} ` -Verifiable ` -MockWith { return $Array1Lower } Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array2Param -DifferenceObject $Array2 )} ` -Verifiable ` -MockWith { return $Array2Lower } Test-ArrayElementsInSecondArray -Array1 $Array1Param -Array2 $Array2Param -IgnoreCase | Should -Be $true } } $falseCaseInsensitiveCases = @( @{ Array1Param = @('ABC') Array1Lower = @('abc') Array2Param = @() Array2Lower = @() } @{ Array1Param = @('abc', 'GHI', 'def') Array1Lower = @('abc', 'ghi', 'def') Array2Param = @('abc', 'def') Array2Lower = @('abc', 'def') } ) Context 'When Test-ArrayElementsInSecondArray is called with IgnoreCase and the arrays do not contain the same contents' { It 'Should return false' -TestCases $falseCaseInsensitiveCases { param ( [System.String[]] $Array1Param, [System.String[]] $Array1Lower, [System.String[]] $Array2Param, [System.String[]] $Array2Lower ) Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array1Param -DifferenceObject $Array1 )} ` -Verifiable ` -MockWith { return $Array1Lower } Mock ` -CommandName Convert-StringArrayToLowerCase ` -ParameterFilter {$null -eq (Compare-Object -ReferenceObject $Array2Param -DifferenceObject $Array2 )} ` -Verifiable ` -MockWith { return $Array2Lower } Test-ArrayElementsInSecondArray -Array1 $Array1Param -Array2 $Array2Param -IgnoreCase | Should -Be $false } } $trueCaseSensitiveCases = @( @{ Array1 = @('aBc', '', 'deF') Array2 = @('', 'aBc', 'deF') } @{ Array1 = @() Array2 = @() } @{ Array1 = @('abc', 'def') Array2 = @('def', 'abc', '', 'GHI') } ) Context 'When Test-ArrayElementsInSecondArray is called without IgnoreCase and the arrays contain the same contents' { It 'Should return true' -TestCases $trueCaseSensitiveCases { param ( [System.String[]] $Array1, [System.String[]] $Array2 ) Mock -CommandName Convert-StringArrayToLowerCase Test-ArrayElementsInSecondArray -Array1 $Array1 -Array2 $Array2 | Should -Be $true Assert-MockCalled Convert-StringArrayToLowerCase -Times 0 } } $falseCaseSensitiveCases = @( @{ Array1 = @('aBc', '', 'deF') Array2 = @('', 'ABC', 'deF') } @{ Array1 = @('ABC') Array2 = @('abc') } @{ Array1 = @('def', 'abc', '', 'GHI') Array2 = @('abc', 'def') } ) Context 'When Test-ArrayElementsInSecondArray is called without IgnoreCase and the arrays do not contain the same contents' { It 'Should return true' -TestCases $falseCaseSensitiveCases { param ( [System.String[]] $Array1, [System.String[]] $Array2 ) Mock -CommandName Convert-StringArrayToLowerCase Test-ArrayElementsInSecondArray -Array1 $Array1 -Array2 $Array2 | Should -Be $false Assert-MockCalled Convert-StringArrayToLowerCase -Times 0 } } } Describe 'xExchangeHelper\Add-ToPSBoundParametersFromHashtable' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Add-ToPSBoundParametersFromHashtable is called, a parameter is added, and a parameter is changed' { It 'Should add a new parameter and change the existing parameter' { $param1 = 'abc' $param2 = $null $param2new = 'notnull' $param3 = 'def' $param4 = 'ghi' $psBoundParametersIn = @{ Param1 = $param1 Param2 = $param2 Param3 = $param3 } $paramsToAdd = @{ Param2 = $param2new Param4 = $param4 } Add-ToPSBoundParametersFromHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToAdd $paramsToAdd $psBoundParametersIn.ContainsKey('Param1') -and $psBoundParametersIn['Param1'] -eq $param1 | Should -Be $true $psBoundParametersIn.ContainsKey('Param2') -and $psBoundParametersIn['Param2'] -eq $param2new | Should -Be $true $psBoundParametersIn.ContainsKey('Param3') -and $psBoundParametersIn['Param3'] -eq $param3 | Should -Be $true $psBoundParametersIn.ContainsKey('Param4') -and $psBoundParametersIn['Param4'] -eq $param4 | Should -Be $true } } } Describe 'xExchangeHelper\Remove-FromPSBoundParametersUsingHashtable' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Remove-FromPSBoundParametersUsingHashtable is called and both ParamsToKeep and ParamsToRemove are specified' { It 'Should throw an exception' { { Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn @{} -ParamsToKeep @('Param1') -ParamsToRemove @('Param2') } | ` Should -Throw -ExpectedMessage 'Remove-FromPSBoundParametersUsingHashtable does not support using both ParamsToKeep and ParamsToRemove' } } Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToKeep' { It 'Should remove any parameter not specified in ParamsToKeep' { Mock -CommandName Convert-StringArrayToLowerCase -Verifiable -MockWith { return @('param1', 'param2') } $psBoundParametersIn = @{ Param1 = 1 Param2 = 2 Param3 = 3 } $paramsToKeep = @('Param1', 'Param2') Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToKeep $paramsToKeep $psBoundParametersIn.ContainsKey('Param1') | Should -Be $true $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true $psBoundParametersIn.ContainsKey('Param3') | Should -Be $false } } Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToRemove' { It 'Should remove any parameter specified in ParamsToRemove' { $psBoundParametersIn = @{ Param1 = 1 Param2 = 2 Param3 = 3 } $paramsToRemove = @( 'Param1', 'param2' ) Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToRemove $paramsToRemove $psBoundParametersIn.ContainsKey('Param1') | Should -Be $false $psBoundParametersIn.ContainsKey('Param2') | Should -Be $false $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true } } } Describe 'xExchangeHelper\Remove-NotApplicableParamsForVersion' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Remove-NotApplicableParamsForVersion is called and the parameter exists in the current Exchange version' { It 'Should not modify the input PSBoundParameters' { Mock -CommandName Get-ExchangeVersionYear -Verifiable -MockWith { return '2016' } $psBoundParametersIn = @{ Param1 = 1 Param2 = 2 Param3 = 3 } Remove-NotApplicableParamsForVersion ` -PSBoundParametersIn $psBoundParametersIn ` -ParamName 'Param1' ` -ResourceName 'xExchangeHelper.tests.ps1' ` -ParamExistsInVersion @('2016', '2019') $psBoundParametersIn.ContainsKey('Param1') | Should -Be $true $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true } } Context 'When Remove-NotApplicableParamsForVersion is called and the parameter does not exist in the current Exchange version' { It 'Should remove the parameter from the input PSBoundParameters, but leave the other parameters' { Mock -CommandName Get-ExchangeVersionYear -Verifiable -MockWith { return '2016' } Mock -CommandName Write-Warning -Verifiable $psBoundParametersIn = @{ Param1 = 1 Param2 = 2 Param3 = 3 } Remove-NotApplicableParamsForVersion ` -PSBoundParametersIn $psBoundParametersIn ` -ParamName 'Param1' ` -ResourceName 'xExchangeHelper.tests.ps1' ` -ParamExistsInVersion @('2013', '2019') $psBoundParametersIn.ContainsKey('Param1') | Should -Be $false $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true } } } Describe 'xExchangeHelper\Set-EmptyStringParamsToNull' -Tag 'Helper' { Context 'When Set-EmptyStringParamsToNull is called and the input hashtable contains empty strings' { It 'Should set the empty strings to null and not modify any other parameters' { $psBoundParametersIn = @{ Param1 = 1 Param2 = '' Param3 = 'abc' Param4 = $null } Set-EmptyStringParamsToNull -PSBoundParametersIn $psBoundParametersIn $psBoundParametersIn.ContainsKey('Param1') -and $psBoundParametersIn['Param1'] -eq 1 | Should -Be $true $psBoundParametersIn.ContainsKey('Param2') -and $psBoundParametersIn['Param2'] -eq $null | Should -Be $true $psBoundParametersIn.ContainsKey('Param3') -and $psBoundParametersIn['Param3'] -eq 'abc' | Should -Be $true $psBoundParametersIn.ContainsKey('Param4') -and $psBoundParametersIn['Param4'] -eq $null | Should -Be $true } } } Describe 'xExchangeHelper\Write-InvalidSettingVerbose' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Write-InvalidSettingVerbose is called' { It 'Should call Write-Verbose, and the message should contain the input values' { $setting = 'TestSetting' $expected = 'ExpectedTestValue' $actual = 'ActualTestValue' Mock -CommandName Write-Verbose -ParameterFilter {$Message.Contains($setting) -and $Message.Contains($expected) -and $Message.Contains($actual)} -Verifiable Write-InvalidSettingVerbose -SettingName $setting -ExpectedValue $expected -ActualValue $actual } } } Describe 'xExchangeHelper\Write-FunctionEntry' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $functionName = 'Test-Function' Context 'When Write-FunctionEntry is called without parameters' { It 'Should write the calling function name and no parameters' { Mock -CommandName Get-PSCallStack -Verifiable -MockWith { return @( @{FunctionName = 'Bottom-O-Stack'}, @{FunctionName = $functionName} ) } Mock -CommandName Write-Verbose -ParameterFilter {$Message.Contains($functionName) -and !$Message.Contains('parameters')} -Verifiable Write-FunctionEntry } } Context 'When Write-FunctionEntry is called with parameters' { It 'Should write the calling function name and parameters' { Mock -CommandName Get-PSCallStack -Verifiable -MockWith { return @( @{FunctionName = 'Bottom-O-Stack'}, @{FunctionName = $functionName} ) } Mock ` -CommandName Write-Verbose ` -ParameterFilter {$Message.Contains($functionName) -and $Message.Contains('Param1') -and $Message.Contains('123') -and $Message.Contains('Param2') -and $Message.Contains('321')} ` -Verifiable Write-FunctionEntry -Parameters @{Param1 = 123; Param2 = 321} } } } Describe 'xExchangeHelper\Test-CmdletHasParameter' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Test-CmdletHasParameter is called and the cmdlet has the input parameter' { It 'Should return true' { $targetParam = 'TestParam' Mock -CommandName Get-Command -Verifiable -MockWith { return @{ Parameters = @{ $targetParam = 1 } } } Test-CmdletHasParameter -CmdletName 'TestCmdlet' -ParameterName $targetParam | Should -Be $true } } Context 'When Test-CmdletHasParameter is called and the cmdlet does not have the input parameter' { It 'Should return false' { $targetParam = 'TestParam' Mock -CommandName Get-Command -Verifiable -MockWith { return @{ Parameters = @{ SomeOtherParam = 1 } } } Test-CmdletHasParameter -CmdletName 'TestCmdlet' -ParameterName $targetParam | Should -Be $false } } } Describe 'xExchangeHelper\Test-ExchangeSetting' -Tag 'Helper' { # Override functions that require types loaded by Exchange DLLs function Compare-TimespanToString {} function Compare-ByteQuantifiedSizeToString {} function Compare-SmtpAddressToString {} function Compare-PSCredential {} AfterEach { Assert-VerifiableMock } Context 'When Test-ExchangeSetting is called and the target type is not handled by the function' { It 'Should throw an exception' { { Test-ExchangeSetting -Name 'Setting' -Type 'MissingType' -ExpectedValue 1 -ActualValue 2 -PSBoundParametersIn @{Setting = 1} } | ` Should -Throw -ExpectedMessage 'Type not found: MissingType' } } $simpleTypeFunctionComparisons = @( @{ Type = 'String' Function = 'Compare-StringToString' }, @{ Type = 'Boolean' Function = 'Compare-BoolToBool' }, @{ Type = 'Array' Function = 'Compare-ArrayContent' }, @{ Type = 'Unlimited' Function = 'Compare-UnlimitedToString' }, @{ Type = 'Timespan' Function = 'Compare-TimespanToString' }, @{ Type = 'ADObjectID' Function = 'Compare-ADObjectIdToSmtpAddressString' }, @{ Type = 'ByteQuantifiedSize' Function = 'Compare-ByteQuantifiedSizeToString' }, @{ Type = 'IPAddress' Function = 'Compare-IPAddressToString' }, @{ Type = 'IPAddresses' Function = 'Compare-IPAddressesToArray' }, @{ Type = 'SMTPAddress' Function = 'Compare-SmtpAddressToString' }, @{ Type = 'PSCredential' Function = 'Compare-PSCredential' } ) Context 'When Test-ExchangeSetting is called and the results are determined by a call to simple function, when the function returns true' { It 'Should return true' -TestCases $simpleTypeFunctionComparisons { param ( [System.String] $Type, [System.String] $Function ) Mock -CommandName $Function -Verifiable -MockWith { return $true } Test-ExchangeSetting -Name 'Setting' -Type $Type -ExpectedValue 1 -ActualValue 2 -PSBoundParametersIn @{Setting = 1} | Should -Be $true } } Context 'When Test-ExchangeSetting is called and the results are determined by a call to simple function, when the function returns false' { It 'Should return false' -TestCases $simpleTypeFunctionComparisons { param ( [System.String] $Type, [System.String] $Function ) Mock -CommandName $Function -Verifiable -MockWith { return $false } Mock -CommandName Write-InvalidSettingVerbose -Verifiable Test-ExchangeSetting -Name 'Setting' -Type $Type -ExpectedValue 1 -ActualValue 2 -PSBoundParametersIn @{Setting = 1} | Should -Be $false } } Context 'When Test-ExchangeSetting is called, the Type is Int, and the types are equal' { It 'Should return true' { Test-ExchangeSetting -Name 'Setting' -Type 'Int' -ExpectedValue 1 -ActualValue 1 -PSBoundParametersIn @{Setting = 1} | Should -Be $true } } Context 'When Test-ExchangeSetting is called, the Type is Int, and the types are not equal' { It 'Should return false' { Mock -CommandName Write-InvalidSettingVerbose -Verifiable Test-ExchangeSetting -Name 'Setting' -Type 'Int' -ExpectedValue 1 -ActualValue 2 -PSBoundParametersIn @{Setting = 1} | Should -Be $false } } Context 'When Test-ExchangeSetting is called, the Type is ExtendedProtection, the ExpectedValue array contains "none", and the ActualValue is empty' { It 'Should return true' { Mock -CommandName Convert-StringArrayToLowerCase -Verifiable -MockWith { return @('none') } Test-ExchangeSetting -Name 'Setting' -Type 'ExtendedProtection' -ExpectedValue 1 -ActualValue '' -PSBoundParametersIn @{Setting = 1} | Should -Be $true } } Context 'When Test-ExchangeSetting is called, the Type is ExtendedProtection, the ExpectedValue array contains "none", and the ActualValue is not empty' { It 'Should return false' { Mock -CommandName Convert-StringArrayToLowerCase -Verifiable -MockWith { return @('none') } Mock -CommandName Write-InvalidSettingVerbose -Verifiable Test-ExchangeSetting -Name 'Setting' -Type 'ExtendedProtection' -ExpectedValue 1 -ActualValue 'notempty' -PSBoundParametersIn @{Setting = 1} | Should -Be $false } } Context 'When Test-ExchangeSetting is called, the Type is ExtendedProtection, the ExpectedValue array does not contain "none", and Compare-ArrayContent returns true' { It 'Should return true' { Mock -CommandName Compare-ArrayContent -Verifiable -MockWith { return $true } Test-ExchangeSetting -Name 'Setting' -Type 'ExtendedProtection' -ExpectedValue 1 -ActualValue '' -PSBoundParametersIn @{Setting = 1} | Should -Be $true } } Context 'When Test-ExchangeSetting is called, the Type is ExtendedProtection, the ExpectedValue array does not contain "none", and Compare-ArrayContent returns false' { It 'Should return false' { Mock -CommandName Compare-ArrayContent -Verifiable -MockWith { return $false } Mock -CommandName Write-InvalidSettingVerbose -Verifiable Test-ExchangeSetting -Name 'Setting' -Type 'ExtendedProtection' -ExpectedValue 1 -ActualValue '' -PSBoundParametersIn @{Setting = 1} | Should -Be $false } } } Describe 'xExchangeHelper\Compare-IPAddressToString' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $nullNotNullComparisons = @( @{ IPAddress = $null String = '192.168.0.1' }, @{ IPAddress = [System.Net.IPAddress] '192.168.1.1' String = $null }, @{ IPAddress = [System.Net.IPAddress] '192.168.1.1' String = '' } ) Context 'When Compare-IPAddressToString is called, the IPAddress is null and the string is not, or vice versa' { It 'Should return false' -TestCases $nullNotNullComparisons { param ( [System.Net.IPAddress] $IPAddress, [System.String] $String ) Compare-IPAddressToString -IPAddress $IPAddress -String $String | Should -Be $false } } $nullNullComparisons = @( @{ IPAddress = $null String = '' }, @{ IPAddress = $null String = $null } ) Context 'When Compare-IPAddressToString is called, the IPAddress is null and the string is null or empty' { It 'Should return true' -TestCases $nullNullComparisons { param ( [System.Net.IPAddress] $IPAddress, [System.String] $String ) Compare-IPAddressToString -IPAddress $IPAddress -String $String | Should -Be $true } } $actualIPToStringComps = @( @{ IPAddress = [System.Net.IPAddress] '192.168.1.1' String = '192.168.1.1' Result = $true }, @{ IPAddress = [System.Net.IPAddress] '192.168.1.1' String = '192.168.01.01' Result = $true }, @{ IPAddress = [System.Net.IPAddress] '192.168.1.2' String = '192.168.1.1' Result = $false } ) Context 'When Compare-IPAddressToString is called and the IPAddress and string are not empty' { It 'Should compare properly' -TestCases $actualIPToStringComps { param ( [System.Net.IPAddress] $IPAddress, [System.String] $String, [System.Boolean] $Result ) Compare-IPAddressToString -IPAddress $IPAddress -String $String | Should -Be $Result } } } Describe 'xExchangeHelper\Compare-IPAddressesToArray' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $trueIPAddressArrayComps = @( @{ IPAddressObjects = @( [System.Net.IPAddress] '192.168.1.1', [System.Net.IPAddress] '192.168.1.2' ) IPAddressStrings = @( '192.168.1.1' '192.168.1.2' ) }, @{ IPAddressObjects = @( [System.Net.IPAddress] '192.168.1.1', [System.Net.IPAddress] '192.168.1.2' ) IPAddressStrings = @( '192.168.1.02' '192.168.01.1' ) }, @{ IPAddressObjects = @() IPAddressStrings = @() } ) Context 'When Compare-IPAddressesToArray is called and the IPAddress and String arrays contain similar contents' { It 'Should return true' -TestCases $trueIPAddressArrayComps { param ( [System.Net.IPAddress[]] $IPAddressObjects, [System.String[]] $IPAddressStrings ) Compare-IPAddressesToArray -IPAddressObjects $IPAddressObjects -IPAddressStrings $IPAddressStrings | Should -Be $true } } $falseIPAddressArrayComps = @( @{ IPAddressObjects = @( [System.Net.IPAddress] '192.168.1.1', [System.Net.IPAddress] '192.168.1.2' ) IPAddressStrings = @( '192.168.1.1' ) }, @{ IPAddressObjects = @( [System.Net.IPAddress] '192.168.1.1', [System.Net.IPAddress] '192.168.1.2' ) IPAddressStrings = @( '192.168.1.03' '192.168.01.4' ) }, @{ IPAddressObjects = @( [System.Net.IPAddress] '192.168.1.1', [System.Net.IPAddress] '192.168.1.2' ) IPAddressStrings = @() }, @{ IPAddressObjects = @() IPAddressStrings = @( '192.168.1.03' '192.168.01.4' ) } ) Context 'When Compare-IPAddressesToArray is called and the IPAddress and String arrays do not contain similar contents' { It 'Should return false' -TestCases $falseIPAddressArrayComps { param ( [System.Net.IPAddress[]] $IPAddressObjects, [System.String[]] $IPAddressStrings ) Compare-IPAddressesToArray -IPAddressObjects $IPAddressObjects -IPAddressStrings $IPAddressStrings | Should -Be $false } } } Describe 'xExchangeHelper\Restart-ExistingAppPool' -Tag 'Helper' { # Allow override of IIS commands function Get-WebAppPoolState {} function Restart-WebAppPool {} AfterEach { Assert-VerifiableMock } Context 'When Restart-ExistingAppPool is called and the application pool exists' { It 'Should restart the application pool' { Mock -CommandName Get-WebAppPoolState -Verifiable -MockWith { return $true } Mock -CommandName Restart-WebAppPool -Verifiable Restart-ExistingAppPool -Name 'SomeAppPool' } } Context 'When Restart-ExistingAppPool is called and the application pool does not exist' { It 'Should not attempt to restart the application pool' { Mock -CommandName Get-WebAppPoolState -Verifiable -MockWith { return $null } Mock -CommandName Restart-WebAppPool Restart-ExistingAppPool -Name 'SomeAppPool' Assert-MockCalled -CommandName Restart-WebAppPool -Times 0 } } } Describe 'xExchangeHelper\Compare-PSCredential' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $password1 = ConvertTo-SecureString 'Password1' -AsPlainText -Force $password1Upper = ConvertTo-SecureString 'PASSWORD1' -AsPlainText -Force $user1 = 'user1' $user1Upper = 'USER1' $user2 = 'user2' $trueCredentialComps = @( @{ Cred1 = New-Object System.Management.Automation.PSCredential ($user1, $password1) Cred2 = New-Object System.Management.Automation.PSCredential ($user1, $password1) }, @{ Cred1 = New-Object System.Management.Automation.PSCredential ($user1, $password1) Cred2 = New-Object System.Management.Automation.PSCredential ($user1Upper, $password1) }, @{ Cred1 = $null Cred2 = $null } ) Context 'When Compare-PSCredential is called and the credentials are equal' { It 'Should return true' -TestCases $trueCredentialComps { param ( [System.Management.Automation.PSCredential] $Cred1, [System.Management.Automation.PSCredential] $Cred2 ) Compare-PSCredential -Cred1 $Cred1 -Cred2 $Cred2 | Should -Be $true } } $falseCredentialComps = @( @{ Cred1 = New-Object System.Management.Automation.PSCredential ($user1, $password1) Cred2 = New-Object System.Management.Automation.PSCredential ($user1, $password1Upper) }, @{ Cred1 = New-Object System.Management.Automation.PSCredential ($user1, $password1) Cred2 = New-Object System.Management.Automation.PSCredential ($user2, $password1) }, @{ Cred1 = New-Object System.Management.Automation.PSCredential ($user1, $password1) Cred2 = $null }, @{ Cred1 = $null Cred2 = New-Object System.Management.Automation.PSCredential ($user2, $password1) } ) Context 'When Compare-PSCredential is called and the credentials are not equal' { It 'Should return false' -TestCases $falseCredentialComps { param ( [System.Management.Automation.PSCredential] $Cred1, [System.Management.Automation.PSCredential] $Cred2 ) Compare-PSCredential -Cred1 $Cred1 -Cred2 $Cred2 | Should -Be $false } } } Describe 'xExchangeHelper\Start-ExchangeScheduledTask' -Tag 'Helper' { # Override functions with non-Mockable parameter types function Register-ScheduledTask {} function Set-ScheduledTask {} function Start-ScheduledTask {} AfterEach { Assert-VerifiableMock } $functionArgs = @{ Path = 'ExeLocation' Arguments = 'Args' Credential = New-Object System.Management.Automation.PSCredential ('SomeUser', (ConvertTo-SecureString 'Password1' -AsPlainText -Force)) TaskName = 'TaskName' WorkingDirectory = 'WorkingLocation' } $taskAction = @{ WorkingDirectory = $functionArgs.WorkingDirectory } Context 'When Start-ExchangeScheduledTask is called and no errors are encountered while setting up the task' { It 'Should register the task, set settings on it, and start it' { Mock -CommandName New-ScheduledTaskAction -Verifiable -MockWith { return $taskAction } Mock -CommandName Get-PreviousError -Verifiable -MockWith { return $Error } Mock -CommandName Assert-NoNewError -Verifiable Mock -CommandName Register-ScheduledTask -Verifiable -MockWith { return @{ Settings = @{ ExecutionLimit = 'PT5M' Priority = 4 } TaskName = $functionArgs.TaskName State = 'Ready' } } Mock -CommandName Start-ScheduledTask -Verifiable Start-ExchangeScheduledTask @functionArgs } } $badTaskCases = @( @{ Task = $null }, @{ Task = @{ State = 'Bad' } } ) Context 'When Start-ExchangeScheduledTask is called and the task fails to registery correctly' { It 'Should throw an exception' -TestCases $badTaskCases { param ( [System.Collections.Hashtable] $Task ) Mock -CommandName New-ScheduledTaskAction -Verifiable -MockWith { return $taskAction } Mock -CommandName Get-PreviousError -Verifiable -MockWith { return $Error } Mock -CommandName Assert-NoNewError -Verifiable Mock -CommandName Register-ScheduledTask -Verifiable -MockWith { return $Task } Mock -CommandName Start-ScheduledTask { Start-ExchangeScheduledTask @functionArgs } | Should -Throw -ExpectedMessage 'Failed to register Scheduled Task' Assert-MockCalled -CommandName Start-ScheduledTask -Times 0 } } } Describe 'xExchangeHelper\Test-ExtendedProtectionSPNList' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $noneInvalidCases = @( @{ Flags = @('None', 'AllowDotlessSPN') FlagsLower = @('none', 'allowdotlessspn') }, @{ Flags = @('None', 'NoServiceNameCheck') FlagsLower = @('none', 'noservicenamecheck') }, @{ Flags = @('None', 'Proxy') FlagsLower = @('none', 'proxy') }, @{ Flags = @('None', 'ProxyCoHosting') FlagsLower = @('none', 'proxycohosting') } ) Context 'When Test-ExtendedProtectionSPNList is called with the none flag, as well as other flags' { It 'Should return false' -TestCases $noneInvalidCases { param ( [System.String[]] $Flags, [System.String[]] $FlagsLower ) Mock -CommandName Convert-StringArrayToLowerCase -Verifiable -MockWith { return $FlagsLower } Test-ExtendedProtectionSPNList -SPNList @() -Flags $Flags | Should -Be $false } } $invalidSPNCases = @( @{ SPNList = @('http\backslash.local') Flags = @() FlagsLower = @() }, @{ SPNList = @('http/name') Flags = @('None') FlagsLower = @('none') }, @{ SPNList = @('http/name.local', 'http/name') Flags = @('None') FlagsLower = @('none') }, @{ SPNList = @('name.local') Flags = @() FlagsLower = @() }, @{ SPNList = @() Flags = @('Proxy') FlagsLower = @('proxy') } ) Context 'When Test-ExtendedProtectionSPNList is called with straight invalid SPNs, or invalid SPNs combined with the given flags' { It 'Should return false' -TestCases $invalidSPNCases { param ( [System.String[]] $SPNList, [System.String[]] $Flags, [System.String[]] $FlagsLower ) Mock -CommandName Convert-StringArrayToLowerCase -MockWith { return $FlagsLower } Test-ExtendedProtectionSPNList -SPNList $SPNList -Flags $Flags | Should -Be $false } } $validSPNCases = @( @{ SPNList = @('http/backslash.local') Flags = @() FlagsLower = @() }, @{ SPNList = @('http/backslash.local') Flags = @('None') FlagsLower = @('none') }, @{ SPNList = @('http/backslash.local') Flags = @('NoServiceNameCheck') FlagsLower = @('noservicenamecheck') }, @{ SPNList = @('http/backslash') Flags = @('AllowDotlessSPN') FlagsLower = @('allowdotlessspn') } ) Context 'When Test-ExtendedProtectionSPNList is called with straight valid SPNs, or valid SPNs combined with the given flags' { It 'Should return true' -TestCases $validSPNCases { param ( [System.String[]] $SPNList, [System.String[]] $Flags, [System.String[]] $FlagsLower ) Mock -CommandName Convert-StringArrayToLowerCase -MockWith { return $FlagsLower } Test-ExtendedProtectionSPNList -SPNList $SPNList -Flags $Flags | Should -Be $true } } } Describe 'xExchangeHelper\Get-StringFromHashtable' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } Context 'When Get-StringFromHashtable is called with a hashtable containing keys and values' { It 'Should return a semicolon separated string of key/value pairs' { $hashtable = @{ a = 1 b = 2 c = 3 } Get-StringFromHashtable -Hashtable $hashtable | Should -Be 'a=1;b=2;c=3' } } } Describe 'xExchangeHelper\Get-DomainDNFromFQDN' -Tag 'Helper' { AfterEach { Assert-VerifiableMock } $testCases = @( @{ FQDN = 'domain1.local' DN = 'dc=domain1,dc=local' } @{ FQDN = 'sub.domain1.local' DN = 'dc=sub,dc=domain1,dc=local' } @{ FQDN = 'domain1' DN = 'dc=domain1' } ) Context 'When Get-DomainDNFromFQDN is called' { It 'Should return the input domain in DN format' -TestCases $testCases { param ( [System.String] $FQDN, [System.String] $DN ) Get-DomainDNFromFQDN -Fqdn $FQDN | Should -Be $DN } } } Describe 'xExchangeHelper\Set-DSCMachineStatus' -Tag 'Helper' { Context 'When Set-DSCMachineStatus is called' { It 'Should set the desired DSCMachineStatus value' { # A new value we'll attempt to set to $global:DSCMachineStatus $newValue = 100 # Store the previous $global:DSCMachineStatus value $prevDSCMachineStatus = $global:DSCMachineStatus # Set and test for the new value Set-DSCMachineStatus -NewDSCMachineStatus $newValue $global:DSCMachineStatus | Should -Be $newValue # Revert to previous $global:DSCMachineStatus value $global:DSCMachineStatus = $prevDSCMachineStatus } } } } } finally { Invoke-TestCleanup } |