Merge pull request #157 from mhendric/FixMsiPackageTests4

MSFT_MsiPackage: Fix HttpListener Issues with MsiPackage Integration Tests
This commit is contained in:
Daniel Scott-Raynsford
2019-06-03 07:47:42 +12:00
committed by GitHub
7 changed files with 349 additions and 127 deletions
@@ -269,7 +269,7 @@ function Set-TargetResource
}
finally
{
if ($null -ne $responseStream)
if ((Test-Path -Path variable:responseStream) -and ($null -ne $responseStream))
{
Close-Stream -Stream $responseStream
}
+10
View File
@@ -577,6 +577,16 @@ The following parameters will be the same for each process in the set:
### Unreleased
* Fixes issue where MsiPackage Integration tests fail if the test HttpListener
fails to start. Moves the test HttpListener objects to dynamically assigned,
higher numbered ports to avoid conflicts with other services, and also checks
to ensure that the ports are available before using them. Adds checks to
ensure that no outstanding HTTP server jobs are running before attempting to
setup a new one. Also adds additional instrumentation to make it easier to
troubleshoot issues with the test HttpListener objects in the future.
Specifically fixes
[Issue #142](https://github.com/PowerShell/PSDscResources/issues/142)
### 2.11.0.0
* Fix Custom DSC Resource Kit PSSA Rule Failures
@@ -43,7 +43,7 @@ Describe 'MsiPackage End to End Tests' {
<#
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.
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.'
@@ -55,6 +55,9 @@ Describe 'MsiPackage End to End Tests' {
$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
}
@@ -85,7 +88,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return True from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $true
if ($testTargetResourceInitialResult -ne $true)
@@ -104,7 +107,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should compile and run configuration' {
{
{
. $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
& $configurationName -OutputPath $TestDrive @msiPackageParameters
Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
@@ -130,7 +133,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -149,7 +152,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should compile and run configuration' {
{
{
. $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
& $configurationName -OutputPath $TestDrive @msiPackageParameters
Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
@@ -175,7 +178,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return True from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $true
if ($testTargetResourceInitialResult -ne $true)
@@ -194,7 +197,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should compile and run configuration' {
{
{
. $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
& $configurationName -OutputPath $TestDrive @msiPackageParameters
Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
@@ -220,7 +223,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -239,7 +242,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should compile and run configuration' {
{
{
. $script:configurationFilePathNoOptionalParameters -ConfigurationName $configurationName
& $configurationName -OutputPath $TestDrive @msiPackageParameters
Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
@@ -273,7 +276,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -292,7 +295,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should compile and run configuration' {
{
{
. $script:configurationFilePathLogPath -ConfigurationName $configurationName
& $configurationName -OutputPath $TestDrive @msiPackageParameters
Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
@@ -330,7 +333,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -349,7 +352,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should compile and run configuration' {
{
{
. $script:configurationFilePathLogPath -ConfigurationName $configurationName
& $configurationName -OutputPath $TestDrive @msiPackageParameters
Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
@@ -366,14 +369,15 @@ Describe 'MsiPackage End to End Tests' {
It 'Package should not exist on the machine' {
Test-PackageInstalledById -ProductId $script:packageId | Should Be $false
}
}
}
Context 'Install package from HTTP Url' {
$configurationName = 'UninstallExistingMsiPackageFromHttp'
$baseUrl = 'http://localhost:1242/'
$msiUrl = "$baseUrl" + 'package.msi'
$uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort)
$uriBuilder.Path = 'package.msi'
$msiUrl = $uriBuilder.Uri.AbsoluteUri
$fileServerStarted = $null
$job = $null
@@ -385,7 +389,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -405,14 +409,17 @@ Describe 'MsiPackage End to End Tests' {
try
{
$serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false
# 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
$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
@@ -440,8 +447,9 @@ Describe 'MsiPackage End to End Tests' {
Context 'Uninstall Msi package from HTTP Url' {
$configurationName = 'InstallMsiPackageFromHttp'
$baseUrl = 'http://localhost:1242/'
$msiUrl = "$baseUrl" + 'package.msi'
$uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort)
$uriBuilder.Path = 'package.msi'
$msiUrl = $uriBuilder.Uri.AbsoluteUri
$fileServerStarted = $null
$job = $null
@@ -453,7 +461,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -470,17 +478,20 @@ Describe 'MsiPackage End to End Tests' {
It 'Package should exist on the machine before configuration is run' {
Test-PackageInstalledById -ProductId $script:packageId | Should Be $true
}
try
{
$serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false
# 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
@@ -508,8 +519,9 @@ Describe 'MsiPackage End to End Tests' {
Context 'Install Msi package from HTTPS Url' {
$configurationName = 'InstallMsiPackageFromHttpS'
$baseUrl = 'https://localhost:1243/'
$msiUrl = "$baseUrl" + 'package.msi'
$uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort)
$uriBuilder.Path = 'package.msi'
$msiUrl = $uriBuilder.Uri.AbsoluteUri
$fileServerStarted = $null
$job = $null
@@ -521,7 +533,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -538,17 +550,20 @@ Describe 'MsiPackage End to End Tests' {
It 'Package should not exist on the machine before configuration is run' {
Test-PackageInstalledById -ProductId $script:packageId | Should Be $false
}
try
{
$serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true
# 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
@@ -572,12 +587,13 @@ Describe 'MsiPackage End to End Tests' {
Test-PackageInstalledById -ProductId $script:packageId | Should Be $true
}
}
Context 'Uninstall Msi package from HTTPS Url' {
$configurationName = 'UninstallMsiPackageFromHttps'
$baseUrl = 'https://localhost:1243/'
$msiUrl = "$baseUrl" + 'package.msi'
$uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort)
$uriBuilder.Path = 'package.msi'
$msiUrl = $uriBuilder.Uri.AbsoluteUri
$fileServerStarted = $null
$job = $null
@@ -589,7 +605,7 @@ Describe 'MsiPackage End to End Tests' {
}
It 'Should return False from Test-TargetResource with the same parameters before configuration' {
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult = MSFT_MsiPackage\Test-TargetResource @msiPackageParameters
$testTargetResourceInitialResult | Should Be $false
if ($testTargetResourceInitialResult -ne $false)
@@ -609,14 +625,17 @@ Describe 'MsiPackage End to End Tests' {
try
{
$serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true
# 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
@@ -37,7 +37,7 @@ try
<#
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.
most of the work of the mock server is done within a separate process.
#>
$script:logFile = Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt'
@@ -49,6 +49,9 @@ try
$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
}
@@ -171,8 +174,11 @@ try
}
It 'Should correctly install and remove a package from a HTTP URL' {
$baseUrl = 'http://localhost:1242/'
$msiUrl = "$baseUrl" + 'package.msi'
$uriBuilder = [System.UriBuilder]::new('http', 'localhost', $script:testHttpPort)
$baseUrl = $uriBuilder.Uri.AbsoluteUri
$uriBuilder.Path = 'package.msi'
$msiUrl = $uriBuilder.Uri.AbsoluteUri
$fileServerStarted = $null
$job = $null
@@ -181,7 +187,10 @@ try
{
'Http tests:' >> $script:logFile
$serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $false
# 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
@@ -196,6 +205,12 @@ try
Set-TargetResource -Ensure 'Absent' -Path $msiUrl -ProductId $script:packageId
Test-PackageInstalledById -ProductId $script:packageId | Should Be $false
}
catch
{
Write-Warning -Message 'Caught exception performing HTTP server tests. Outputting HTTP server log.' -Verbose
Get-Content -Path $script:logFile | Write-Verbose -Verbose
throw $_
}
finally
{
<#
@@ -207,9 +222,11 @@ try
}
It 'Should correctly install and remove a package from a HTTPS URL' -Skip:$script:skipHttpsTest {
$uriBuilder = [System.UriBuilder]::new('https', 'localhost', $script:testHttpsPort)
$baseUrl = $uriBuilder.Uri.AbsoluteUri
$baseUrl = 'https://localhost:1243/'
$msiUrl = "$baseUrl" + 'package.msi'
$uriBuilder.Path = 'package.msi'
$msiUrl = $uriBuilder.Uri.AbsoluteUri
$fileServerStarted = $null
$job = $null
@@ -218,9 +235,12 @@ try
{
'Https tests:' >> $script:logFile
$serverResult = Start-Server -FilePath $script:msiLocation -LogPath $script:logFile -Https $true
# 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
$job = $serverResult.Job
# Wait for the file server to be ready to receive requests
$fileServerStarted.WaitOne(30000)
@@ -233,6 +253,12 @@ try
Set-TargetResource -Ensure 'Absent' -Path $msiUrl -ProductId $script:packageId
Test-PackageInstalledById -ProductId $script:packageId | Should Be $false
}
catch
{
Write-Warning -Message 'Caught exception performing HTTPS server tests. Outputting HTTPS server log.' -Verbose
Get-Content -Path $script:logFile | Write-Verbose -Verbose
throw $_
}
finally
{
<#
+88 -7
View File
@@ -157,7 +157,7 @@ function Invoke-ExpectedMocksAreCalledTest
Each item in the array is a hashtable that contains the name of the command
being mocked, the number of times it is called (can be 0) and, optionally,
an extra custom string to make the test name more descriptive. The custom
string will only work if the command has a corresponding variable in the
string will only work if the command has a corresponding variable in the
string data name.
.PARAMETER ShouldThrow
@@ -227,7 +227,7 @@ function Invoke-GenericUnitTest {
Each item in the array is a hashtable that contains the name of the command
being mocked, the number of times it is called (can be 0) and, optionally,
an extra custom string to make the test name more descriptive. The custom
string will only work if the command has a corresponding variable in the
string will only work if the command has a corresponding variable in the
string data name.
.PARAMETER ExpectedReturnValue
@@ -288,7 +288,7 @@ function Invoke-GetTargetResourceUnitTest
Each item in the array is a hashtable that contains the name of the command
being mocked, the number of times it is called (can be 0) and, optionally,
an extra custom string to make the test name more descriptive. The custom
string will only work if the command has a corresponding variable in the
string will only work if the command has a corresponding variable in the
string data name.
.PARAMETER ShouldThrow
@@ -354,7 +354,7 @@ function Invoke-SetTargetResourceUnitTest {
Each item in the array is a hashtable that contains the name of the command
being mocked, the number of times it is called (can be 0) and, optionally,
an extra custom string to make the test name more descriptive. The custom
string will only work if the command has a corresponding variable in the
string will only work if the command has a corresponding variable in the
string data name.
.PARAMETER ExpectedReturnValue
@@ -573,7 +573,7 @@ function Test-IsFileLocked
.PARAMETER ExpectedOutput
The output expected to be in the output from running WhatIf with the Set-TargetResource cmdlet.
If this parameter is empty or null, this cmdlet will check that there was no output from
Set-TargetResource with WhatIf specified.
Set-TargetResource with WhatIf specified.
#>
function Test-SetTargetResourceWithWhatIf
{
@@ -584,7 +584,7 @@ function Test-SetTargetResourceWithWhatIf
[Parameter(Mandatory = $true)]
[Hashtable]
$Parameters,
[String[]]
$ExpectedOutput
)
@@ -790,6 +790,86 @@ function Exit-DscResourceTestEnvironment
Restore-TestEnvironment -TestEnvironment $TestEnvironment
}
<#
.SYNOPSIS
Finds an unused TCP port in the specified port range. By default,
searches within ports 38473 - 38799, which at the time of writing, show
as unassigned in:
https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
.PARAMETER LowestPortNumber
The TCP port number at which to begin the unused port search. Must be
greater than 0.
.PARAMETER HighestPortNumber
The highest TCP port number to search for unused ports within. Must be
greater than 0, and greater than LowestPortNumber.
.PARAMETER ExcludePorts
TCP ports to exclude from the search, even if they fall within the
LowestPortNumber and HighestPortNumber range.
#>
function Get-UnusedTcpPort
{
[OutputType([System.UInt16])]
[CmdletBinding()]
param
(
[Parameter()]
[ValidateScript({$_ -gt 0})]
[System.UInt16]
$LowestPortNumber = 38473,
[Parameter()]
[ValidateScript({$_ -gt $0})]
[System.UInt16]
$HighestPortNumber = 38799,
[Parameter()]
[System.UInt16[]]
$ExcludePorts = @()
)
if ($HighestPortNumber -lt $LowestPortNumber)
{
throw 'HighestPortNumber must be greater than or equal to LowestPortNumber'
}
[System.UInt16] $unusedPort = 0
[System.Collections.ArrayList] $usedAndExcludedPorts = (Get-NetTCPConnection).LocalPort | Where-Object -FilterScript {
$_ -ge $LowestPortNumber -and $_ -le $HighestPortNumber
}
if (!(Test-Path -Path variable:usedAndExcludedPorts) -or ($null -eq $usedAndExcludedPorts))
{
[System.Collections.ArrayList] $usedAndExcludedPorts = @()
}
if (!(Test-Path -Path variable:ExcludePorts) -or ($null -eq $ExcludePorts))
{
$ExcludePorts = @()
}
$null = $usedAndExcludedPorts.Add($ExcludePorts)
foreach ($port in $LowestPortNumber..$HighestPortNumber)
{
if (!($usedAndExcludedPorts.Contains($port)))
{
$unusedPort = $port
break
}
}
if ($unusedPort -eq 0)
{
throw "Failed to find unused TCP port between ports $LowestPortNumber and $HighestPortNumber."
}
return $unusedPort
}
Export-ModuleMember -Function @(
'Test-GetTargetResourceResult', `
'Wait-ScriptBlockReturnTrue', `
@@ -802,5 +882,6 @@ Export-ModuleMember -Function @(
'Invoke-SetTargetResourceUnitTest', `
'Invoke-TestTargetResourceUnitTest', `
'Invoke-ExpectedMocksAreCalledTest', `
'Invoke-GenericUnitTest'
'Invoke-GenericUnitTest', `
'Get-UnusedTcpPort'
)
@@ -1,6 +1,8 @@
$errorActionPreference = 'Stop'
Set-StrictMode -Version 'Latest'
$testJobPrefix = 'MsiPackageTestJob'
<#
.SYNOPSIS
Tests if the package with the given Id is installed.
@@ -55,9 +57,15 @@ function Test-PackageInstalledById
.PARAMETER Https
Indicates whether the server should use Https. If True then the file server will use Https
and listen on port 'https://localhost:1243'. Otherwise the file server will use Http and
listen on port 'http://localhost:1242'
and listen on port 'https://localhost:HttpsPort'. Otherwise the file server will use Http and
listen on port 'http://localhost:HttpPort'
Default value is False (Http).
.PARAMETER HttpPort
Specifies the TCP port to register an Http based HttpListener on.
.PARAMETER HttspPort
Specifies the TCP port to register an Https based HttpListener on.
#>
function Start-Server
{
@@ -74,7 +82,17 @@ function Start-Server
$LogPath = (Join-Path -Path $PSScriptRoot -ChildPath 'PackageTestLogFile.txt'),
[System.Boolean]
$Https = $false
$Https = $false,
[Parameter(Mandatory = $true)]
[ValidateScript({$_ -gt 0})]
[System.UInt16]
$HttpPort,
[Parameter(Mandatory = $true)]
[ValidateScript({$_ -gt 0})]
[System.UInt16]
$HttpsPort
)
# Create an event object to let the client know when the server is ready to begin receiving requests.
@@ -82,7 +100,7 @@ function Start-Server
'HttpIntegrationTest.FileServerStarted')
$null = $fileServerStarted.Reset()
<#
<#
The server is run on a separate process so that it can receive requests
while the tests continue to run. It takes in the same parameterss that are passed
in to this function. All helper functions that the server uses have to be
@@ -90,7 +108,7 @@ function Start-Server
#>
$server =
{
param($FilePath, $LogPath, $Https)
param($FilePath, $LogPath, $Https, $HttpPort, $HttpsPort)
<#
.SYNOPSIS
@@ -101,6 +119,9 @@ function Start-Server
.PARAMETER Https
Indicates whether https was used and if so, removes the SSL binding.
.PARAMETER HttspPort
Specifies the TCP port to de-register an Https based HttpListener from.
#>
function Stop-Listener
{
@@ -109,16 +130,21 @@ function Start-Server
(
[Parameter(Mandatory = $true)]
[System.Net.HttpListener]
$HttpListener,
$HttpListener,
[Parameter(Mandatory = $true)]
[System.Boolean]
$Https
$Https,
[Parameter(Mandatory = $true)]
[ValidateScript({$_ -gt 0})]
[System.UInt16]
$HttpsPort
)
Write-Log -LogFile $LogPath -Message 'Finished listening for requests. Shutting down HTTP server.'
$ipPort = '0.0.0.0:1243'
$ipPort = "0.0.0.0:$HttpsPort"
if ($null -eq $HttpListener)
{
@@ -159,16 +185,25 @@ function Start-Server
<#
.SYNOPSIS
Creates and registers an SSL certificate for Https connections.
.PARAMETER HttspPort
Specifies the TCP port to register an Https based HttpListener on.
#>
function Register-Ssl
{
[CmdletBinding()]
param()
param
(
[Parameter(Mandatory = $true)]
[ValidateScript({$_ -gt 0})]
[System.UInt16]
$HttpsPort
)
# Create certificate
$certificate = New-SelfSignedCertificate -CertStoreLocation 'Cert:\LocalMachine\My' -DnsName localhost
Write-Log -LogFile $LogPath -Message 'Created certificate'
$hash = $certificate.Thumbprint
$certPassword = ConvertTo-SecureString -String 'password12345' -AsPlainText -Force
$tempPath = 'C:\certForTesting'
@@ -176,11 +211,11 @@ function Start-Server
$null = Export-PfxCertificate -Cert $certificate -FilePath $tempPath -Password $certPassword
$null = Import-PfxCertificate -CertStoreLocation 'Cert:\LocalMachine\Root' -FilePath 'C:\certForTesting' -Password $certPassword
Remove-Item -Path $tempPath
Write-Log -LogFile $LogPath -Message 'Finished importing certificate into root. About to bind it to port.'
# Use net shell command to directly bind certificate to designated testing port
$null = netsh http add sslcert ipport=0.0.0.0:1243 certhash=$hash appid='{833f13c2-319a-4799-9d1a-5b267a0c3593}' clientcertnegotiation=enable
$null = netsh http add sslcert ipport=0.0.0.0:$HttpsPort certhash=$hash appid='{833f13c2-319a-4799-9d1a-5b267a0c3593}' clientcertnegotiation=enable
}
<#
@@ -204,32 +239,32 @@ function Start-Server
# Add the CallbackEventBridge type if it's not already defined
if (-not ('CallbackEventBridge' -as [Type]))
{
Add-Type @'
using System;
public sealed class CallbackEventBridge {
public event AsyncCallback CallbackComplete = delegate { };
private CallbackEventBridge() {}
Add-Type @'
using System;
public sealed class CallbackEventBridge {
public event AsyncCallback CallbackComplete = delegate { };
private CallbackEventBridge() {}
private void CallbackInternal(IAsyncResult result)
{
CallbackComplete(result);
}
{
CallbackComplete(result);
}
public AsyncCallback Callback
{
get { return new AsyncCallback(CallbackInternal); }
}
{
get { return new AsyncCallback(CallbackInternal); }
}
public static CallbackEventBridge Create()
{
return new CallbackEventBridge();
}
}
{
return new CallbackEventBridge();
}
}
'@
}
$bridge = [CallbackEventBridge]::Create()
Register-ObjectEvent -InputObject $bridge -EventName 'CallbackComplete' -Action $Callback -MessageData $args > $null
$bridge.Callback
@@ -249,7 +284,7 @@ function Start-Server
.PARAMETER ScriptBlock
The code to execute.
#>
function Invoke-ConsoleCommand
{
@@ -277,7 +312,7 @@ function Start-Server
$message = ('Failed action ''{0}'' on target ''{1}'' (exit code {2}): {3}' -f $Action,$Target,$LASTEXITCODE,$output)
Write-Error -Message $message
Write-Log -LogFile $LogPath -Message "Error from Invoke-ConsoleCommand: $message"
}
}
else
{
$nonNullOutput = $output | Where-Object { $_ -ne $null }
@@ -309,7 +344,7 @@ function Start-Server
[String]
$Message
)
$Message >> $LogFile
}
@@ -332,11 +367,11 @@ function Start-Server
# Set up the listener
if ($Https)
{
$HttpListener.Prefixes.Add([Uri]'https://localhost:1243')
$HttpListener.Prefixes.Add([Uri] "https://localhost:$HttpsPort")
try
{
Register-SSL
Register-SSL -HttpsPort $HttpsPort
}
catch
{
@@ -349,7 +384,7 @@ function Start-Server
}
else
{
$HttpListener.Prefixes.Add([Uri]'http://localhost:1242')
$HttpListener.Prefixes.Add([Uri] "http://localhost:$HttpPort")
}
Write-Log -LogFile $LogPath -Message 'Finished listener setup - about to start listener'
@@ -374,7 +409,7 @@ function Start-Server
.PARAMETER Result
th IAsyncResult containing the listener object and path to the MSI file.
#>
$requestListener =
{
@@ -450,8 +485,22 @@ function Start-Server
catch
{
$errorMessage = "There were problems setting up the HTTP(s) listener. Error: $_"
Write-Log -LogFile $LogPath -Message $errorMessage
throw $errorMessage
'Error Record Info' >> $LogPath
$_ | ConvertTo-Xml -As String >> $LogPath
'Exception Info' >> $LogPath
$_.Exception | ConvertTo-Xml -As String >> $LogPath
'Running Process Info' >> $LogPath
Get-Process | Format-List | Out-String >> $LogPath
'Open TCP Connections Info' >> $LogPath
Get-NetTCPConnection | Format-List | Out-String >> $LogPath
throw $_
}
finally
{
@@ -459,19 +508,39 @@ function Start-Server
{
$fileServerStarted.Dispose()
}
Write-Log -LogFile $LogPath -Message 'Stopping the Server'
Stop-Listener -HttpListener $HttpListener -Https $Https
Stop-Listener -HttpListener $HttpListener -Https $Https -HttpsPort $HttpsPort
}
}
$job = Start-Job -ScriptBlock $server -ArgumentList @( $FilePath, $LogPath, $Https )
if ($Https)
{
$jobName = $testJobPrefix + 'Https'
}
else
{
$jobName = $testJobPrefix + 'Http'
}
$job = Start-Job -ScriptBlock $server -Name $jobName -ArgumentList @( $FilePath, $LogPath, $Https, $HttpPort, $HttpsPort )
# Verify that the job is receivable and does not contain an exception. If it does, re-throw it.
try
{
$receivedJob = $job | Receive-Job
}
catch
{
Write-Error -Message 'Failed to setup HTTP(S) listener for MsiPackage Tests'
throw $_
}
<#
Return the event object so that client knows when it can start sending requests and
Return the event object so that client knows when it can start sending requests and
the job object so that the client can stop the job once it is done sending requests.
#>
return @{
return @{
FileServerStarted = $fileServerStarted
Job = $job
}
@@ -501,7 +570,7 @@ function Stop-Server
$FileServerStarted,
[System.Management.Automation.Job]
$Job
$Job
)
if ($null -ne $FileServerStarted)
@@ -516,6 +585,20 @@ function Stop-Server
}
}
<#
.SYNOPSIS
Removes any jobs associated with HTTP(S) servers that were created
for MsiPackage tests.
#>
function Stop-EveryTestServerInstance
{
[CmdletBinding()]
param ()
Get-Job -Name "$($testJobPrefix)*" | Stop-Job
Get-Job -Name "$($testJobPrefix)*" | Remove-Job
}
<#
.SYNOPSIS
Creates a new MSI package for testing.
@@ -526,7 +609,7 @@ function Stop-Server
function New-TestMsi
{
[CmdletBinding()]
param
param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
@@ -1061,4 +1144,5 @@ Export-ModuleMember -Function `
New-TestMsi, `
Start-Server, `
Stop-Server, `
Test-PackageInstalledById
Test-PackageInstalledById, `
Stop-EveryTestServerInstance
+23 -21
View File
@@ -123,7 +123,7 @@ Describe 'MsiPackage Unit Tests' {
-ExpectedReturnValue $expectedReturnValue
}
}
Describe 'Set-TargetResource' {
$setTargetResourceParameters = @{
ProductId = 'TestProductId'
@@ -138,7 +138,7 @@ Describe 'MsiPackage Unit Tests' {
ServerCertificateValidationCallback = 'TestValidationCallback'
RunAsCredential = $script:testCredential
}
Mock -CommandName 'Convert-PathToUri' -MockWith { return $script:testUriNonUnc }
Mock -CommandName 'Convert-ProductIdToIdentifyingNumber' -MockWith { return $script:testIdentifyingNumber }
Mock -CommandName 'Assert-PathExtensionValid' -MockWith {}
@@ -158,7 +158,7 @@ Describe 'MsiPackage Unit Tests' {
Mock -CommandName 'Invoke-CimMethod' -MockWith {}
Mock -CommandName 'Get-ItemProperty' -MockWith { return $null }
Mock -CommandName 'Get-ProductEntry' -MockWith { return $script:mockProductEntry }
Context 'Uri scheme is non-UNC file and installation succeeds' {
$mocksCalled = @(
@{ Command = 'Convert-PathToUri'; Times = 1 }
@@ -219,13 +219,13 @@ Describe 'MsiPackage Unit Tests' {
@{ Command = 'Assert-PathExtensionValid'; Times = 1 }
@{ Command = 'New-LogFile'; Times = 1 }
@{ Command = 'New-PSDrive'; Times = 0 }
@{ Command = 'Test-Path'; Times = 2; Custom = 'to the package cache' }
@{ Command = 'Test-Path'; Times = 3; Custom = 'to the package cache' }
@{ Command = 'New-Item'; Times = 0; Custom = 'directory for the package cache' }
@{ Command = 'New-Object'; Times = 1; Custom = 'file stream to copy the response to' }
@{ Command = 'Get-WebRequestResponse'; Times = 1 }
@{ Command = 'Copy-ResponseStreamToFileStream'; Times = 1 }
@{ Command = 'Close-Stream'; Times = 2 }
@{ Command = 'Test-Path'; Times = 2; Custom = 'to the MSI file' }
@{ Command = 'Test-Path'; Times = 3; Custom = 'to the MSI file' }
@{ Command = 'Assert-FileValid'; Times = 1 }
@{ Command = 'Get-MsiProductCode'; Times = 1 }
@{ Command = 'Start-MsiProcess'; Times = 1 }
@@ -250,13 +250,13 @@ Describe 'MsiPackage Unit Tests' {
@{ Command = 'Assert-PathExtensionValid'; Times = 1 }
@{ Command = 'New-LogFile'; Times = 1 }
@{ Command = 'New-PSDrive'; Times = 0 }
@{ Command = 'Test-Path'; Times = 2; Custom = 'to the package cache' }
@{ Command = 'Test-Path'; Times = 3; Custom = 'to the package cache' }
@{ Command = 'New-Item'; Times = 0; Custom = 'directory for the package cache' }
@{ Command = 'New-Object'; Times = 1; Custom = 'file stream to copy the response to' }
@{ Command = 'Get-WebRequestResponse'; Times = 1 }
@{ Command = 'Copy-ResponseStreamToFileStream'; Times = 1 }
@{ Command = 'Close-Stream'; Times = 2 }
@{ Command = 'Test-Path'; Times = 2; Custom = 'to the MSI file' }
@{ Command = 'Test-Path'; Times = 3; Custom = 'to the MSI file' }
@{ Command = 'Assert-FileValid'; Times = 1 }
@{ Command = 'Get-MsiProductCode'; Times = 1 }
@{ Command = 'Start-MsiProcess'; Times = 1 }
@@ -349,7 +349,7 @@ Describe 'MsiPackage Unit Tests' {
-ErrorMessage ($script:localizedData.PostValidationError -f $setTargetResourceParameters.Path) `
-ErrorTestName $script:errorMessageTitles.PostValidationError
}
Mock -CommandName 'Get-MsiProductCode' -MockWith { return $script:testWrongProductId }
Context 'Product code from downloaded MSI package does not match specified ID' {
@@ -491,25 +491,27 @@ Describe 'MsiPackage Unit Tests' {
Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue
}
It 'Should return the expected URI when scheme is http' {
$filePath = 'http://localhost:1242/testMsi.msi'
$expectedReturnValue = [Uri] $filePath
Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue
It 'Should return the expected URI when scheme is http' {
$uriBuilder = [System.UriBuilder]::new('http', 'localhost')
$uriBuilder.Path = 'testMsi.msi'
$filePath = $uriBuilder.Uri.AbsoluteUri
Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri
}
It 'Should return the expected URI when scheme is https' {
$filePath = 'https://localhost:1243/testMsi.msi'
$expectedReturnValue = [Uri] $filePath
$uriBuilder = [System.UriBuilder]::new('https', 'localhost')
$uriBuilder.Path = 'testMsi.msi'
$filePath = $uriBuilder.Uri.AbsoluteUri
Convert-PathToUri -Path $filePath | Should Be $expectedReturnValue
Convert-PathToUri -Path $filePath | Should Be $uriBuilder.Uri
}
}
Context 'Invalid path passed in' {
It 'Should throw an error when uri scheme is invalid' {
$filePath = 'ht://localhost:1243/testMsi.msi'
$filePath = 'ht://localhost/testMsi.msi'
$expectedErrorMessage = ($script:localizedData.InvalidPath -f $filePath)
{ Convert-PathToUri -Path $filePath } | Should Throw $expectedErrorMessage
@@ -686,7 +688,7 @@ Describe 'MsiPackage Unit Tests' {
-ErrorTestName $script:errorMessageTitles.CouldNotOpenLog
}
}
Describe 'Get-WebRequestResponse' {
Mock -CommandName 'Get-WebRequest' -MockWith { return $script:mockWebRequest }
Mock -CommandName 'Get-ScriptBlock' -MockWith { return { Write-Verbose 'Hello World' } }
@@ -702,7 +704,7 @@ Describe 'MsiPackage Unit Tests' {
It 'Should return the expected response stream' {
Get-WebRequestResponse -Uri $script:testUriHttp | Should Be $script:mockStream
}
Invoke-ExpectedMocksAreCalledTest -MocksCalled $mocksCalled
}
@@ -716,7 +718,7 @@ Describe 'MsiPackage Unit Tests' {
It 'Should return the expected response stream' {
Get-WebRequestResponse -Uri $script:testUriHttps | Should Be $script:mockStream
}
Invoke-ExpectedMocksAreCalledTest -MocksCalled $mocksCalled
}
@@ -859,7 +861,7 @@ Describe 'MsiPackage Unit Tests' {
Describe 'Assert-FileSignatureValid' {
$mockThumbprint = 'mockThumbprint'
$mockSubject = 'mockSubject'
$mockSignature = @{
$mockSignature = @{
Status = [System.Management.Automation.SignatureStatus]::Valid
SignerCertificate = @{ Thumbprint = $mockThumbprint; Subject = $mockSubject }
}