Tests/Integration/MSFT_MsiPackage.EndToEnd.Tests.ps1

<#
    Please note that some of these tests depend on each other.
    They must be run in the order given - if one test fails, subsequent tests may
    also fail.
#>


$errorActionPreference = 'Stop'
Set-StrictMode -Version 'Latest'

if ($PSVersionTable.PSVersion -lt [Version] '5.1')
{
    Write-Warning -Message 'Cannot run PSDscResources integration tests on PowerShell versions lower than 5.1'
    return
}

Describe 'MsiPackage End to End Tests' {
    BeforeAll {
        # Import CommonTestHelper
        $testsFolderFilePath = Split-Path $PSScriptRoot -Parent
        $testHelperFolderFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'TestHelpers'
        $commonTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'CommonTestHelper.psm1'
        Import-Module -Name $commonTestHelperFilePath

        $script:testEnvironment = Enter-DscResourceTestEnvironment `
            -DscResourceModuleName 'PSDscResources' `
            -DscResourceName 'MSFT_MsiPackage' `
            -TestType 'Integration'

        # Import MsiPackage resource module for Test-TargetResource
        $moduleRootFilePath = Split-Path -Path $testsFolderFilePath -Parent
        $dscResourcesFolderFilePath = Join-Path -Path $moduleRootFilePath -ChildPath 'DscResources'
        $msiPackageResourceFolderFilePath = Join-Path -Path $dscResourcesFolderFilePath -ChildPath 'MSFT_MsiPackage'
        $msiPackageResourceModuleFilePath = Join-Path -Path $msiPackageResourceFolderFilePath -ChildPath 'MSFT_MsiPackage.psm1'
        Import-Module -Name $msiPackageResourceModuleFilePath -Force

        # Import the MsiPackage test helper
        $packageTestHelperFilePath = Join-Path -Path $testHelperFolderFilePath -ChildPath 'MSFT_MsiPackageResource.TestHelper.psm1'
        Import-Module -Name $packageTestHelperFilePath -Force

        # Set up the paths to the test configurations
        $script:configurationFilePathNoOptionalParameters = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_MsiPackage_NoOptionalParameters'
        $script:configurationFilePathLogPath = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_MsiPackage_LogPath'

        <#
            This log file is used to log messages from the mock server which is important for debugging since
            most of the work of the mock server is done within a separate process.
        #>

        $script:logFile = Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt'
        $script:environmentInIncorrectStateErrorMessage = 'The current environment is not in the expected state for this test - either something was setup incorrectly on your machine or a previous test failed - results after this may be invalid.'

        $script:msiName = 'DSCSetupProject.msi'
        $script:msiLocation = Join-Path -Path $TestDrive -ChildPath $script:msiName

        $script:packageId = '{deadbeef-80c6-41e6-a1b9-8bdb8a05027f}'

        $null = New-TestMsi -DestinationPath $script:msiLocation

        $script:testHttpPort = Get-UnusedTcpPort
        $script:testHttpsPort = Get-UnusedTcpPort -ExcludePorts @($script:testHttpPort)

        # Clear the log file
        'Beginning integration tests' > $script:logFile
    }

    AfterAll {
        # Remove the test MSI if it is still installed
        if (Test-PackageInstalledById -ProductId $script:packageId)
        {
            $null = Start-Process -FilePath 'msiexec.exe' -ArgumentList @("/x$script:packageId", '/passive') -Wait
            $null = Start-Sleep -Seconds 1
        }

        if (Test-PackageInstalledById -ProductId $script:packageId)
        {
            throw 'Test package could not be uninstalled after running all tests. It may cause errors in subsequent test runs.'
        }

        Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment
    }

    Context 'Uninstall package that is already Absent' {
        $configurationName = 'RemoveAbsentMsiPackage'

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $script:msiLocation
            Ensure = 'Absent'
        }

        It 'Should return True from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeTrue

            if ($testTargetResourceInitialResult -ne $true)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should not exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }

        It 'Should compile and run configuration' {
            {
                . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                & $configurationName -OutputPath $TestDrive @msiPackageParameters
                Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
            } | Should -Not -Throw
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should not exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }
    }

    Context 'Install package that is not installed yet' {
        $configurationName = 'InstallMsiPackage'

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $script:msiLocation
            Ensure = 'Present'
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should not exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }

        It 'Should compile and run configuration' {
            {
                . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                & $configurationName -OutputPath $TestDrive @msiPackageParameters
                Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
            } | Should -Not -Throw
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }
    }

    Context 'Install package that is already installed' {
        $configurationName = 'InstallExistingMsiPackage'

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $script:msiLocation
            Ensure = 'Present'
        }

        It 'Should return True from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeTrue

            if ($testTargetResourceInitialResult -ne $true)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }

        It 'Should compile and run configuration' {
            {
                . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                & $configurationName -OutputPath $TestDrive @msiPackageParameters
                Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
            } | Should -Not -Throw
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }
    }

    Context 'Uninstall package that is installed' {
        $configurationName = 'UninstallExistingMsiPackage'

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $script:msiLocation
            Ensure = 'Absent'
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }

        It 'Should compile and run configuration' {
            {
                . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                & $configurationName -OutputPath $TestDrive @msiPackageParameters
                Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
            } | Should -Not -Throw
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should not exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }
    }

    Context 'Install package that is not installed and write to specified log file' {
        $configurationName = 'InstallWithLogFile'

        $logPath = Join-Path -Path $TestDrive -ChildPath 'TestMsiLog.txt'

        if (Test-Path -Path $logPath)
        {
            Remove-Item -Path $logPath -Force
        }

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $script:msiLocation
            Ensure = 'Present'
            LogPath = $logPath
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should not exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }

        It 'Should compile and run configuration' {
            {
                . $script:configurationFilePathLogPath -ConfigurationName $configurationName
                & $configurationName -OutputPath $TestDrive @msiPackageParameters
                Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
            } | Should -Not -Throw
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Should have created the log file' {
            Test-Path -Path $logPath | Should -BeTrue
        }

        It 'Package should exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }
    }

    Context 'Uninstall package that is installed and write to specified log file' {
        $configurationName = 'InstallWithLogFile'

        $logPath = Join-Path -Path $TestDrive -ChildPath 'TestMsiLog.txt'

        if (Test-Path -Path $logPath)
        {
            Remove-Item -Path $logPath -Force
        }

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $script:msiLocation
            Ensure = 'Absent'
            LogPath = $logPath
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }

        It 'Should compile and run configuration' {
            {
                . $script:configurationFilePathLogPath -ConfigurationName $configurationName
                & $configurationName -OutputPath $TestDrive @msiPackageParameters
                Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
            } | Should -Not -Throw
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Should have created the log file' {
            Test-Path -Path $logPath | Should -BeTrue
        }

        It 'Package should not exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }
    }

    Context 'Install package from HTTP Url' {
        $configurationName = 'UninstallExistingMsiPackageFromHttp'

        $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort)
        $uriBuilder.Path = 'package.msi'
        $msiUrl = $uriBuilder.Uri.AbsoluteUri

        $fileServerStarted = $null
        $job = $null

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $msiUrl
            Ensure = 'Present'
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should not exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }

        try
        {
            # Make sure no existing HTTP(S) test servers are running
            Stop-EveryTestServerInstance

            $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort
            $fileServerStarted = $serverResult.FileServerStarted
            $job = $serverResult.Job

            $fileServerStarted.WaitOne(30000)

            It 'Should compile and run configuration' {
                {
                    . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @msiPackageParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }
        }
        finally
        {
            <#
                This must be called after Start-Server to ensure the listening port is closed,
                otherwise subsequent tests may fail until the machine is rebooted.
            #>

            Stop-Server -FileServerStarted $fileServerStarted -Job $job
        }

        It 'Should return True from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }
    }

    Context 'Uninstall Msi package from HTTP Url' {
        $configurationName = 'InstallMsiPackageFromHttp'

        $uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort)
        $uriBuilder.Path = 'package.msi'
        $msiUrl = $uriBuilder.Uri.AbsoluteUri

        $fileServerStarted = $null
        $job = $null

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $msiUrl
            Ensure = 'Absent'
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }

        try
        {
            # Make sure no existing HTTP(S) test servers are running
            Stop-EveryTestServerInstance

            $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort
            $fileServerStarted = $serverResult.FileServerStarted
            $job = $serverResult.Job

            $fileServerStarted.WaitOne(30000)

            It 'Should compile and run configuration' {
                {
                    . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @msiPackageParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }
        }
        finally
        {
            <#
                This must be called after Start-Server to ensure the listening port is closed,
                otherwise subsequent tests may fail until the machine is rebooted.
            #>

            Stop-Server -FileServerStarted $fileServerStarted -Job $job
        }

        It 'Should return true from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should not exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }
    }

    Context 'Install Msi package from HTTPS Url' {
        $configurationName = 'InstallMsiPackageFromHttpS'

        $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort)
        $uriBuilder.Path = 'package.msi'
        $msiUrl = $uriBuilder.Uri.AbsoluteUri

        $fileServerStarted = $null
        $job = $null

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $msiUrl
            Ensure = 'Present'
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should not exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }

        try
        {
            # Make sure no existing HTTP(S) test servers are running
            Stop-EveryTestServerInstance

            $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort
            $fileServerStarted = $serverResult.FileServerStarted
            $job = $serverResult.Job

            $fileServerStarted.WaitOne(30000)

            It 'Should compile and run configuration' {
                {
                    . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @msiPackageParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }
        }
        finally
        {
            <#
                This must be called after Start-Server to ensure the listening port is closed,
                otherwise subsequent tests may fail until the machine is rebooted.
            #>

            Stop-Server -FileServerStarted $fileServerStarted -Job $job
        }

        It 'Should return true from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }
    }

    Context 'Uninstall Msi package from HTTPS Url' {
        $configurationName = 'UninstallMsiPackageFromHttps'

        $uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort)
        $uriBuilder.Path = 'package.msi'
        $msiUrl = $uriBuilder.Uri.AbsoluteUri

        $fileServerStarted = $null
        $job = $null

        $msiPackageParameters = @{
            ProductId = $script:packageId
            Path = $msiUrl
            Ensure = 'Absent'
        }

        It 'Should return False from Test-TargetResource with the same parameters before configuration' {
            $testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
            $testTargetResourceInitialResult | Should -BeFalse

            if ($testTargetResourceInitialResult -ne $false)
            {
                <#
                    Not throwing an error here since the tests should still run correctly after this,
                    we just want to notify the user that the tests aren't necessarily testing what
                    they should be
                #>

                Write-Error -Message $script:environmentInIncorrectStateErrorMessage
            }
        }

        It 'Package should exist on the machine before configuration is run' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeTrue
        }

        try
        {
            # Make sure no existing HTTP(S) test servers are running
            Stop-EveryTestServerInstance

            $serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true -HttpPort $script:testHttpPort -HttpsPort $script:testHttpsPort
            $fileServerStarted = $serverResult.FileServerStarted
            $job = $serverResult.Job

            $fileServerStarted.WaitOne(30000)

            It 'Should compile and run configuration' {
                {
                    . $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @msiPackageParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }
        }
        finally
        {
            <#
                This must be called after Start-Server to ensure the listening port is closed,
                otherwise subsequent tests may fail until the machine is rebooted.
            #>

            Stop-Server -FileServerStarted $fileServerStarted -Job $job
        }

        It 'Should return true from Test-TargetResource with the same parameters after configuration' {
            MSFT_MsiPackage\Test-TargetResource @msiPackageParameters | Should -BeTrue
        }

        It 'Package should not exist on the machine' {
            Test-PackageInstalledById -ProductId $script:packageId | Should -BeFalse
        }
    }
}