Tests/Unit/MSFT_xWindowsOptionalFeature.Tests.ps1

Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'CommonTestHelper.psm1')

if (Test-SkipContinuousIntegrationTask -Type 'Unit')
{
    return
}

$script:testEnvironment = Enter-DscResourceTestEnvironment `
    -DscResourceModuleName 'xPSDesiredStateConfiguration' `
    -DscResourceName 'MSFT_xWindowsOptionalFeature' `
    -TestType 'Unit'

try
{
    InModuleScope 'MSFT_xWindowsOptionalFeature' {
        Describe 'xWindowsOptionalFeature Unit Tests' {
            BeforeAll {
                Import-Module -Name 'Dism'

                $script:testFeatureName = 'TestFeature'

                $script:fakeEnabledFeature = [System.Management.Automation.PSObject] @{
                    Name = $testFeatureName
                    State = 'Enabled'
                }

                $script:fakeDisabledFeature = [System.Management.Automation.PSObject] @{
                    Name = $testFeatureName
                    State = 'Disabled'
                }
            }

            <#
                This context block needs to stay at the top because of a bug in Pester on Nano server.
 
                Assert-ResourcePrerequisitesValid is mocked in most of the other contexts blocks.
                This causes errors to throw from the script blocks in this context since this function does
                not take any parameters, but Pester tries to pipe something into it.
 
                This bug does not occur on full server machines.
            #>

            Context 'Assert-ResourcePrerequisitesValid' {
                $fakeWin32OSObjects = @{
                    '7' = [System.Management.Automation.PSObject] @{
                        ProductType = 1
                        BuildNumber = 7601
                    }
                    'Server2008R2' = [System.Management.Automation.PSObject] @{
                        ProductType = 2
                        BuildNumber = 7601
                    }
                    'Server2012' = [System.Management.Automation.PSObject] @{
                        ProductType = 2
                        BuildNumber = 9200
                    }
                    '8.1' = [System.Management.Automation.PSObject] @{
                        ProductType = 1
                        BuildNumber = 9600
                    }
                    'Server2012R2' = [System.Management.Automation.PSObject] @{
                        ProductType = 2
                        BuildNumber = 9600
                    }
                }

                It 'Should throw when the DISM module is not available' {
                    Mock Import-Module -ParameterFilter { $Name -eq 'Dism' } -MockWith { Write-Error 'Cannot find module' }
                    { Assert-ResourcePrerequisitesValid } | Should -Throw -ExpectedMessage $script:localizedData.DismNotAvailable
                }

                Mock Import-Module -ParameterFilter { $Name -eq 'Dism' } -MockWith { }

                It 'Should throw when operating system is Server 2008 R2' {
                    Mock Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_OperatingSystem' } -MockWith { return $fakeWin32OSObjects['Server2008R2'] }
                    { Assert-ResourcePrerequisitesValid } | Should -Throw -ExpectedMessage $script:localizedData.NotSupportedSku
                }

                It 'Should throw when operating system is Server 2012' {
                    Mock Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_OperatingSystem' } -MockWith { return $fakeWin32OSObjects['Server2012'] }
                    { Assert-ResourcePrerequisitesValid } | Should -Throw -ExpectedMessage $script:localizedData.NotSupportedSku
                }

                It 'Should not throw when operating system is Windows 7' {
                    Mock Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_OperatingSystem' } -MockWith { return $fakeWin32OSObjects['7'] }
                    { Assert-ResourcePrerequisitesValid } | Should -Not -Throw
                }

                It 'Should not throw when operating system is Windows 8.1' {
                    Mock Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_OperatingSystem' } -MockWith { return $fakeWin32OSObjects['8.1'] }
                    { Assert-ResourcePrerequisitesValid } | Should -Not -Throw
                }

                It 'Should not throw when operating system is Server 2012 R2' {
                    Mock Get-CimInstance -ParameterFilter { $ClassName -eq 'Win32_OperatingSystem' } -MockWith { return $fakeWin32OSObjects['Server2012R2'] }
                    { Assert-ResourcePrerequisitesValid } | Should -Not -Throw
                }
            }

            Context 'Get-TargetResource - Feature Enabled' {
                Mock Assert-ResourcePrerequisitesValid -MockWith { }
                Mock Dism\Get-WindowsOptionalFeature { $FeatureName -eq $script:testFeatureName } -MockWith { return $script:fakeEnabledFeature }

                It 'Should return a Hashtable' {
                    $getTargetResourceResult = Get-TargetResource -Name $script:testFeatureName
                    $getTargetResourceResult -is [System.Collections.Hashtable] | Should -Be $true
                }

                It 'Should call Assert-ResourcePrerequisitesValid with the feature name' {
                    $getTargetResourceResult = Get-TargetResource -Name $script:testFeatureName
                    Assert-MockCalled Dism\Get-WindowsOptionalFeature -ParameterFilter { $FeatureName -eq $script:testFeatureName } -Scope It
                }

                It 'Should return Ensure as Present' {
                    $getTargetResourceResult = Get-TargetResource -Name $script:testFeatureName
                    $getTargetResourceResult.Ensure | Should -Be 'Present'
                }
            }


            Context 'Get-TargetResource - Feature Disabled' {
                Mock Assert-ResourcePrerequisitesValid -MockWith { }
                Mock Dism\Get-WindowsOptionalFeature { $FeatureName -eq $script:testFeatureName } -MockWith { return $script:fakeDisabledFeature }

                It 'Should return Ensure as Absent' {
                    $getTargetResourceResult = Get-TargetResource -Name $script:testFeatureName
                    $getTargetResourceResult.Ensure | Should -Be 'Absent'
                }
            }

            Context 'Test-TargetResource - Feature Enabled' {
                Mock Assert-ResourcePrerequisitesValid -MockWith { }
                Mock Dism\Get-WindowsOptionalFeature { $FeatureName -eq $script:testFeatureName } -MockWith { return $script:fakeEnabledFeature }

                It 'Should return true when Ensure set to Present' {
                    Test-TargetResource -Name $testFeatureName -Ensure 'Present' | Should -Be $true
                }

                It 'Should return false when Ensure set to Absent' {
                    Test-TargetResource -Name $testFeatureName -Ensure 'Absent' | Should -Be $false
                }

            }

            Context 'Test-TargetResource - Feature Disabled' {
                Mock Assert-ResourcePrerequisitesValid -MockWith { }
                Mock Dism\Get-WindowsOptionalFeature { $FeatureName -eq $script:testFeatureName } -MockWith { return $script:fakeDisabledFeature }

                It 'Should return false when Ensure set to Present' {
                    Test-TargetResource -Name $testFeatureName -Ensure 'Present' | Should -Be $false
                }

                It 'Should return true when Ensure set to Absent' {
                    Test-TargetResource -Name $testFeatureName -Ensure 'Absent' | Should -Be $true
                }
            }

            Context 'Test-TargetResource - Feature Missing' {
                Mock Assert-ResourcePrerequisitesValid -MockWith { }
                Mock Dism\Get-WindowsOptionalFeature { $FeatureName -eq $script:testFeatureName } -MockWith { }

                It 'Should return false when Ensure set to Present' {
                    Test-TargetResource -Name $testFeatureName -Ensure 'Present' | Should -Be $false
                }

                It 'Should return true when Ensure set to Absent' {
                    Test-TargetResource -Name $testFeatureName -Ensure 'Absent' | Should -Be $true
                }
            }

            Context 'Set-TargetResource' {
                Mock Assert-ResourcePrerequisitesValid -MockWith { }

                It 'Should call Enable-WindowsOptionalFeature with NoRestart set to true by default when Ensure set to Present' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $NoRestart -eq $true } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter { $NoRestart -eq $true } -Scope It
                }

                It 'Should call Disable-WindowsOptionalFeature with NoRestart set to true by default when Ensure set to Absent' {
                    Mock Dism\Disable-WindowsOptionalFeature -ParameterFilter { $NoRestart -eq $true } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -Ensure 'Absent'

                    Assert-MockCalled Dism\Disable-WindowsOptionalFeature -ParameterFilter  { $NoRestart -eq $true } -Scope It
                }

                It 'Should call Enable-WindowsOptionalFeature with Online by default as true when Ensure set to Present' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $Online -eq $true } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter { $Online -eq $true } -Scope It
                }

                It 'Should call Disable-WindowsOptionalFeature with Online set to true by default when Ensure set to Absent' {
                    Mock Dism\Disable-WindowsOptionalFeature -ParameterFilter { $Online -eq $true } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -Ensure 'Absent'

                    Assert-MockCalled Dism\Disable-WindowsOptionalFeature -ParameterFilter { $Online -eq $true } -Scope It
                }

                It 'Should call Enable-WindowsOptionalFeature with LogLevel set to WarningsInfo by default when Ensure set to Present' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'WarningsInfo' } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'WarningsInfo' } -Scope It
                }

                It 'Should call Enable-WindowsOptionalFeature with LogLevel set to Errors when Ensure set to Present and LogLevel set to ErrorsOnly' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'Errors' } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -LogLevel 'ErrorsOnly'

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'Errors' } -Scope It
                }

                It 'Should call Enable-WindowsOptionalFeature with LogLevel set to Warnings when Ensure set to Present and LogLevel set to ErrorsAndWarnings' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'Warnings' } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -LogLevel 'ErrorsAndWarning'

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'Warnings' } -Scope It
                }

                It 'Should call Disable-WindowsOptionalFeature with LogLevel set to WarningsInfo by default when Ensure set to Absent' {
                    Mock Dism\Disable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'WarningsInfo' } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -Ensure 'Absent'

                    Assert-MockCalled Dism\Disable-WindowsOptionalFeature -ParameterFilter { $LogLevel -eq 'WarningsInfo' } -Scope It
                }

                It 'Should call Enable-WindowsOptionalFeature without LimitAccess by default when Ensure set to Present' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LimitAccess -eq $null } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter  { $LimitAccess -eq $null } -Scope It
                }

                It 'Should call Enable-WindowsOptionalFeature with LimitAccess set to true when NoWindowsUpdateCheck is specified' {
                    Mock Dism\Enable-WindowsOptionalFeature -ParameterFilter { $LimitAccess -eq $true } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -NoWindowsUpdateCheck $true

                    Assert-MockCalled Dism\Enable-WindowsOptionalFeature -ParameterFilter  { $LimitAccess -eq $true } -Scope It
                }

                It 'Should call Disable-WindowsOptionalFeature with Remove set to true when Ensure set to Absent and RemoveFilesOnDisable specified' {
                    Mock Dism\Disable-WindowsOptionalFeature -ParameterFilter { $Remove -eq $true } -MockWith { }

                    Set-TargetResource -Name $script:testFeatureName -Ensure 'Absent' -RemoveFilesOnDisable $true

                    Assert-MockCalled Dism\Disable-WindowsOptionalFeature -ParameterFilter { $Remove -eq $true } -Scope It
                }

            }

            Context 'Convert-FeatureStateToEnsure' {
                It 'Should return Present when state is Enabled' {
                    Convert-FeatureStateToEnsure -State 'Enabled' | Should -Be 'Present'
                }

                It 'Should return Absent when state is Disabled' {
                    Convert-FeatureStateToEnsure -State 'Disabled' | Should -Be 'Absent'
                }

                It 'Should return the same state when state is not Enabled or Disabled' {
                    $originalWarningPreference = $WarningPreference
                    $WarningPreference = 'SilentlyContinue'

                    try
                    {
                        Convert-FeatureStateToEnsure -State 'UnknownState' | Should -Be 'UnknownState'
                    }
                    finally
                    {
                        $WarningPreference = $originalWarningPreference
                    }
                }
            }

            Context 'Convert-CustomPropertyArrayToStringArray' {
                [System.Management.Automation.PSObject[]] $psCustomObjects = @(
                    [System.Management.Automation.PSObject] @{
                        Name = 'Object 1'
                        Value = 'Value 1'
                        Path = 'Path 1'
                    },
                    [System.Management.Automation.PSObject] @{
                        Name = 'Object 2'
                        Value = 'Value 2'
                        Path = 'Path 2'
                    },
                    [System.Management.Automation.PSObject] @{
                        Name = 'Object 3'
                        Value = 'Value 3'
                        Path = 'Path 3'
                    },
                    $null
                )

                It 'Should return 3 strings from 3 PSCustomObjects and a null object' {
                    $propertiesAsStrings = Convert-CustomPropertyArrayToStringArray -CustomProperties $psCustomObjects
                    $propertiesAsStrings.Length | Should -Be 3
                }

                It 'Should return the correct string for each object' {
                    $propertiesAsStrings = Convert-CustomPropertyArrayToStringArray -CustomProperties $psCustomObjects

                    foreach ($objectNumber in @(1, 2, 3))
                    {
                        $propertiesAsStrings.Contains("Name = Object $objectNumber, Value = Value $objectNumber, Path = Path $objectNumber") | Should -Be $true
                    }
                }
            }
        }
    }
}
finally
{
    Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment
}