Tests/cOctopusServer.Tests.ps1

#requires -Version 4.0
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] # these are tests, not anything that needs to be secure
param()

$moduleName = Split-Path ($PSCommandPath -replace '\.Tests\.ps1$', '') -Leaf
$modulePath = Resolve-Path "$PSCommandPath/../../DSCResources/$moduleName/$moduleName.psm1"
$module = $null

try
{
    $prefix = [guid]::NewGuid().Guid -replace '-'

    $module = Import-Module $modulePath -Prefix $prefix -PassThru -ErrorAction Stop

    InModuleScope $module.Name {

        Describe 'cOctopusServer' {
            BeforeAll {
                [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
                $mockConfig = [pscustomobject] @{
                    OctopusStorageExternalDatabaseConnectionString         = 'StubConnectionString'
                    OctopusWebPortalListenPrefixes                         = "https://octopus.example.com,http://localhost"
                }
                Mock Import-ServerConfig { return $mockConfig }

                # Get-Service is not available on mac/unix systems - fake it
                $getServiceCommand = Get-Command "Get-Service" -ErrorAction SilentlyContinue
                if ($null -eq $getServiceCommand) {
                    function Get-Service {
                        [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidOverwritingBuiltInCmdlets', '', Justification='Get-Service is not available on mac/unix systems, so without faking it, our builds fail')]
                        param()
                    }
                }

                function Get-DesiredConfiguration {
                    $pass = ConvertTo-SecureString "S3cur3P4ssphraseHere!" -AsPlainText -Force
                    $cred = New-Object System.Management.Automation.PSCredential ("Admin", $pass)

                    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
                    $desiredConfiguration = @{
                        Name                   = 'Stub'
                        Ensure                 = 'Present'
                        State                  = 'Started'
                        WebListenPrefix        = "http://localhost:80"
                        SqlDbConnectionString  = "conn-string"
                        OctopusAdminCredential = $cred
                    }
                    return $desiredConfiguration
                }
            }

            Context 'Get-TargetResource' {
                Context 'When reg key exists and service exists and service started' {
                    It 'Should have Ensure=Present and State=Started' {
                        Mock Get-ItemProperty { return @{ InstallLocation = "c:\Octopus\Octopus" }}
                        Mock Get-Service { return @{ Status = "Running" }}
                        $desiredConfiguration = Get-DesiredConfiguration
                        $config = Get-TargetResource @desiredConfiguration
                        $config['Ensure']                  | Should -Be 'Present'
                        $config['State']                   | Should -Be 'Started'
                    }
                }

                Context 'When reg key exists and service exists and service stopped' {
                    It 'Should have Ensure=Present and State=Stopped' {
                        Mock Get-ItemProperty { return @{ InstallLocation = "c:\Octopus\Octopus" }}
                        Mock Get-Service { return @{ Status = "Stopped" }}
                        $desiredConfiguration = Get-DesiredConfiguration
                        $config = Get-TargetResource @desiredConfiguration
                        $config['Ensure']                  | Should -Be 'Present'
                        $config['State']                   | Should -Be 'Stopped'
                    }
                }

                Context 'When reg key exists and service does not exist' {
                    It 'Should have Ensure=Present and State=Installed' {
                        Mock Get-ItemProperty { return @{ InstallLocation = "c:\Octopus\Octopus" }}
                        Mock Get-Service { return $null }
                        $desiredConfiguration = Get-DesiredConfiguration
                        $config = Get-TargetResource @desiredConfiguration
                        $config['Ensure']                  | Should -Be 'Present'
                        $config['State']                   | Should -Be 'Installed'
                    }
                }

                Context 'When reg key does not exist and service does not exist' {
                    It 'Should have Ensure=Absent and State=Stopped' {
                        Mock Get-ItemProperty { return $null }
                        Mock Get-Service { return $null }
                        $desiredConfiguration = Get-DesiredConfiguration
                        $config = Get-TargetResource @desiredConfiguration
                        $config['Ensure']                  | Should -Be 'Absent'
                        $config['State']                   | Should -Be 'Stopped'
                    }
                }

                Context 'When reg key does not exist and service started' {
                    It 'Should have Ensure=Present and State=Started' {
                        Mock Get-ItemProperty { return $null }
                        Mock Get-Service { return @{ Status = "Running" } }
                        $desiredConfiguration = Get-DesiredConfiguration
                        $config = Get-TargetResource @desiredConfiguration
                        $config['Ensure']                  | Should -Be 'Present'
                        $config['State']                   | Should -Be 'Started'
                    }
                }

                Context 'When reg key does not exist and service stopped' {
                    It 'Should have Ensure=Present and State=Stopped' {
                        Mock Get-ItemProperty { return $null }
                        Mock Get-Service { return @{ Status = "Stopped" } }
                        $desiredConfiguration = Get-DesiredConfiguration
                        $config = Get-TargetResource @desiredConfiguration
                        $config['Ensure']                  | Should -Be 'Present'
                        $config['State']                   | Should -Be 'Stopped'
                    }
                }

                function Get-PropertiesFromMof() {
                    $moduleName = Split-Path ($PSCommandPath -replace '\.Tests\.ps1$', '') -Leaf
                    $modulePath = Resolve-Path "$PSCommandPath/../../DSCResources/$moduleName/$moduleName.psm1"
                    $mofFile = Get-Item ($modulePath -replace ".psm1", ".schema.mof")
                    $schemaMofFileContent = Get-Content $mofFile

                    $properties = @()
                    foreach($line in $schemaMofFileContent) {
                        if ($line -match "\s*(\[.*\])\s*(.*) (.*);") {
                            $attributes = $matches[1];
                            if ($attributes -like '*MSFT_Credential*') {
                                $propertyType = 'PSCredential';
                            } else {
                                $propertyType = $matches[2];
                            }
                            $propertyName = $matches[3].Replace("[]", "");

                            $properties += @{ propertyType = $propertyType; propertyName = $propertyName };
                        }
                    }
                    return $properties
                }

                Context "Should return a hash table that lists all the resource properties as keys and the actual values" {
                    # Get-Service is not available on mac/unix systems - fake it
                    $getServiceCommand = Get-Command "Get-Service" -ErrorAction SilentlyContinue
                    if ($null -eq $getServiceCommand) {
                        function Get-Service {
                            [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidOverwritingBuiltInCmdlets', '', Justification='Get-Service is not available on mac/unix systems, so without faking it, our builds fail')]
                            param()
                        }
                    }

                    $desiredConfiguration = @{
                        Name                   = 'Stub'
                        Ensure                 = 'Present'
                        State                  = 'Started'
                        WebListenPrefix        = "http://localhost:80"
                        SqlDbConnectionString  = "conn-string"
                        OctopusAdminCredential = New-Object System.Management.Automation.PSCredential ("Admin", (ConvertTo-SecureString "S3cur3P4ssphraseHere!" -AsPlainText -Force))
                    }
                    $currentState = Get-TargetResource @desiredConfiguration
                    $expectedProperties = Get-PropertiesFromMof;
                    $testCases = @()
                    foreach($expectedProperty in $expectedProperties) {
                        $propertyReturned = $currentState.ContainsKey($expectedProperty.PropertyName);
                        $propertyMatches = $false;
                        if ($propertyReturned) {
                            $value = $currentState[$expectedProperty.PropertyName];
                            if ($null -ne $value) {
                                $propertyMatches = $value.GetType().Name -eq $expectedProperty.PropertyType;
                            }
                        }

                        $testCases += @{
                            propertyName = $expectedProperty.PropertyName;
                            PropertyType = $expectedProperty.PropertyType;
                            propertyReturned = $propertyReturned;
                            propertyMatches = $propertyMatches;
                         }
                    }

                    it "should return a hashtable with an entry for <propertyName>" -TestCases $testCases {
                        param($propertyName, $propertyReturned)
                        write-verbose "Property $propertyName should exist" # to keep psscriptanalyzer from complaining about PSReviewUnusedParameter
                        $propertyReturned | Should -Be $true
                    }

                    it "should return a <propertyType> value for <propertyName>" -TestCases $testCases {
                        param($propertyName, $propertyMatches)
                        write-verbose "Property $propertyName should exist" # to keep psscriptanalyzer from complaining about PSReviewUnusedParameter
                        $propertyMatches | Should -Be $true
                    }
                }
            }

            Context "Parameter Validation" {
                Context "Ensure = 'Present' and 'State' = 'Stopped'" {
                    It "Should throw if 'Name' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -DownloadUrl "blah2" } `
                            | Should -throw "Parameter 'Name' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'DownloadUrl' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" } `
                            | Should -throw "Parameter 'DownloadUrl' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'WebListenPrefix' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" } `
                            | Should -throw "Parameter 'WebListenPrefix' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3"} `
                            | Should -throw "Parameter 'SqlDbConnectionString' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'TaskCap' is less than 1" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds -TaskCap -1} `
                            | Should -throw "Parameter 'TaskCap' must be greater than 0 when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'TaskCap' is greater than 50" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds -TaskCap 51} `
                            | Should -throw "Parameter 'TaskCap' must be less than 50 when 'Ensure' is 'Present'."
                    }
                    It "Should not throw if 'OctopusAdminCredential' not supplied but 'OctopusMasterKey' is supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null -OctopusMasterKey $creds} | Should -not -throw
                    }
                    It "Should not throw if 'OctopusAdminCredential' is '[PSCredential]::Empty' but 'OctopusMasterKey' is supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusMasterKey $creds} | Should -not -throw
                    }
                    It "Should throw if 'OctopusAdminCredential' not supplied and 'OctopusMasterKey' is not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" } `
                            | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should throw if 'OctopusAdminCredential' is null and 'OctopusMasterKey' is null" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null -OctopusMasterKey $null } `
                            | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should throw if 'OctopusAdminCredential' is '[PSCredential]::Empty' and 'OctopusMasterKey' is '[PSCredential]::Empty'" {
                        $creds = [PSCredential]::Empty
                        $masterKey = [PSCredential]::Empty
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds -OctopusMasterKey $masterKey } `
                            | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should not throw if all params are supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds} | Should -not -throw
                    }
                }

                Context "Ensure = 'Present' and 'State' = 'Started'" {
                    It "Should throw if 'Name' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Stopped' -DownloadUrl "blah2" } | Should -throw "Parameter 'Name' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'DownloadUrl' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" } | Should -throw "Parameter 'DownloadUrl' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'WebListenPrefix' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" } | Should -throw "Parameter 'WebListenPrefix' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should throw if 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3"} | Should -throw "Parameter 'SqlDbConnectionString' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should not throw if 'OctopusAdminCredential' not supplied but 'OctopusMasterKey' is supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null -OctopusMasterKey $creds} | Should -not -throw
                    }
                    It "Should not throw if 'OctopusAdminCredential' is '[PSCredential]::Empty' but 'OctopusMasterKey' is supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusMasterKey $creds} | Should -not -throw
                    }
                    It "Should throw if 'OctopusAdminCredential' not supplied and 'OctopusMasterKey' is not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" } | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should throw if 'OctopusAdminCredential' is null and 'OctopusMasterKey' is null" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null -OctopusMasterKey $null } | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should throw if 'OctopusAdminCredential' is '[PSCredential]::Empty' and 'OctopusMasterKey' is '[PSCredential]::Empty'" {
                        $creds = [PSCredential]::Empty
                        $masterKey = [PSCredential]::Empty
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds -OctopusMasterKey $masterKey } | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should not throw if all params are supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds} | Should -not -throw
                    }
                }

                Context "Ensure = 'Present' and 'State' = 'Installed'" {
                    It "Should not throw if 'Name' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' -DownloadUrl "blah2" } | Should -not -throw
                    }
                    It "Should throw if 'DownloadUrl' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' } | Should -throw "Parameter 'DownloadUrl' must be supplied when 'Ensure' is 'Present'."
                    }
                    It "Should not throw if 'WebListenPrefix' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' -DownloadUrl "blah2" } | Should -not -throw
                    }
                    It "Should not throw if 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' -DownloadUrl "blah2" -WebListenPrefix "blah3"} | Should -not -throw
                    }
                    It "Should not throw if 'OctopusAdminCredential' not supplied and 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' -DownloadUrl "blah2" -WebListenPrefix "blah3" -OctopusAdminCredential $null } | Should -not -throw
                    }
                    It "Should not throw if 'OctopusAdminCredential' is '[PSCredential]::Empty' and 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' -DownloadUrl "blah2" -WebListenPrefix "blah3" } | Should -not -throw
                    }
                    It "Should not throw if all params are supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Present' -State 'Installed' -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds} | Should -not -throw
                    }
                }

                Context "Ensure = 'Present', 'State' = 'Installed', and 'SqlDbConnectionString' != 'null or empty'" {
                    It "Should throw if 'OctopusAdminCredential' not supplied and 'OctopusMasterKey' is not supplied" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" } | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should throw if 'OctopusAdminCredential' is null and 'OctopusMasterKey' is null" {
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null -OctopusMasterKey $null } | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                    It "Should throw if 'OctopusAdminCredential' is '[PSCredential]::Empty' and 'OctopusMasterKey' is '[PSCredential]::Empty'" {
                        $creds = [PSCredential]::Empty
                        $masterKey = [PSCredential]::Empty
                        { Test-ParameterSet -Ensure 'Present' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds -OctopusMasterKey $masterKey } | Should -throw "Parameter 'OctopusAdminCredential' must be supplied when 'Ensure' is 'Present' and you have not supplied a master key to use an existing database."
                    }
                }

                Context "Ensure = 'Absent' and 'State' = 'Stopped'" {
                    It "Should throw if 'Name' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' } | Should -throw "Parameter 'Name' must be supplied when 'Ensure' is 'Absent'."
                    }
                    It "Should not throw if 'DownloadUrl' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' -Name "blah1" } | Should -not -throw
                    }
                    It "Should not throw if 'WebListenPrefix' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" } | Should -not -throw
                    }
                    It "Should not throw if 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3"} | Should -not -throw
                    }
                    It "Should not throw if 'OctopusAdminCredential' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null} | Should -not -throw
                    }
                    It "Should not throw if 'OctopusAdminCredential' is '[PSCredential]::Empty'" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4"} | Should -not -throw
                    }
                    It "Should not throw if all params are supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Absent' -State 'Stopped' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds} | Should -not -throw
                    }
                }

                Context "Ensure = 'Absent' and 'State' = 'Started'" {
                    It "Should throw if 'Name' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' } | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'DownloadUrl' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' -Name "blah1"} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'WebListenPrefix' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' -Name "blah1" -DownloadUrl "blah2" } | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3"} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'OctopusAdminCredential' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'OctopusAdminCredential' is '[PSCredential]::Empty'" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4"} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if all params are supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Absent' -State 'Started' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be running at the same time. You probably want 'State = `"Stopped`"."
                    }
                }

                Context "Ensure = 'Absent' and 'State' = 'Installed'" {
                    It "Should throw if 'Name' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' } | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'DownloadUrl' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' -Name "blah1"} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'WebListenPrefix' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' -Name "blah1" -DownloadUrl "blah2" } | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'SqlDbConnectionString' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3"} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'OctopusAdminCredential' not supplied" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $null} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if 'OctopusAdminCredential' is '[PSCredential]::Empty'" {
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4"} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                    It "Should throw if all params are supplied" {
                        $creds = New-Object System.Management.Automation.PSCredential ("username", (new-object System.Security.SecureString))
                        { Test-ParameterSet -Ensure 'Absent' -State 'Installed' -Name "blah1" -DownloadUrl "blah2" -WebListenPrefix "blah3" -SqlDbConnectionString "blah4" -OctopusAdminCredential $creds} | Should -throw "Invalid configuration requested. You have asked for the service to not exist, but also be installed at the same time. You probably want 'State = `"Stopped`"."
                    }
                }
            }

            Context 'Test-TargetResource' {
                BeforeAll {
                    $response = @{ Ensure="Absent"; State="Stopped" }
                    Mock Get-TargetResource { return $response }
                }

                It 'Returns True when Ensure is set to Absent and Instance does not exist' {
                    $desiredConfiguration = Get-DesiredConfiguration
                    $desiredConfiguration['Ensure'] = 'Absent'
                    $desiredConfiguration['State'] = 'Stopped'
                    $response['Ensure'] = 'Absent'
                    $response['State'] = 'Stopped'

                    Test-TargetResource @desiredConfiguration | Should -Be $true
                }

               It 'Returns True when Ensure is set to Present and Instance exists' {
                $desiredConfiguration = Get-DesiredConfiguration
                    $desiredConfiguration['Ensure'] = 'Present'
                    $desiredConfiguration['State'] = 'Started'
                    $response['Ensure'] = 'Present'
                    $response['State'] = 'Started'

                    Test-TargetResource @desiredConfiguration | Should -Be $true
                }
            }

            Context 'Set-TargetResource' {
                It 'Throws an exception if .net 4.5.2 or above is not installed (no .net reg key found)' {
                    Mock Test-OctopusVersionRequiresDotNet472 { return $false } # older version (pre 2019.7.0)
                    Mock Invoke-MsiExec {}
                    Mock Get-LogDirectory {}
                    Mock Request-File {}
                    Mock Get-RegistryValue { return "" }
                    Mock Update-InstallState
                    $desiredConfiguration = Get-DesiredConfiguration
                    { Set-TargetResource @desiredConfiguration } | Should -throw "Octopus Server requires .NET 4.5.2. Please install it before attempting to install Octopus Server."
                }

                It 'Throws an exception if .net 4.5.2 or above is not installed (only .net 4.5.0 installed)' {
                    Mock Test-OctopusVersionRequiresDotNet472 { return $false } # older version (pre 2019.7.0)
                    Mock Invoke-MsiExec {}
                    Mock Get-LogDirectory {}
                    Mock Request-File {}
                    Mock Get-RegistryValue { return "378389" } # .NET Framework 4.5
                    Mock Update-InstallState
                    $desiredConfiguration = Get-DesiredConfiguration
                    { Set-TargetResource @desiredConfiguration } | Should -throw "Octopus Server requires .NET 4.5.2. Please install it before attempting to install Octopus Server."
                }

                It 'Throws an exception if .net 4.7.2 or above is not installed (no .net reg key found)' {
                    Mock Test-OctopusVersionRequiresDotNet472 { return $true }
                    Mock Invoke-MsiExec {}
                    Mock Get-LogDirectory {}
                    Mock Request-File {}
                    Mock Get-RegistryValue { return "" }
                    Mock Update-InstallState
                    $desiredConfiguration = Get-DesiredConfiguration
                    { Set-TargetResource @desiredConfiguration } | Should -throw "Octopus Server requires .NET 4.7.2. Please install it before attempting to install Octopus Server."
                }

                It 'Throws an exception if .net 4.7.2 or above is not installed (only .net 4.6.2 installed)' {
                    Mock Test-OctopusVersionRequiresDotNet472 { return $true }
                    Mock Invoke-MsiExec {}
                    Mock Get-LogDirectory {}
                    Mock Request-File {}
                    Mock Get-RegistryValue { return "394802" } # .NET Framework 4.6.2 on Windows Server 2016
                    Mock Update-InstallState
                    $desiredConfiguration = Get-DesiredConfiguration
                    { Set-TargetResource @desiredConfiguration } | Should -throw "Octopus Server requires .NET 4.7.2. Please install it before attempting to install Octopus Server."
                }
            }

            Context "Octopus server command line" {
                BeforeAll {
                    function Get-CurrentConfiguration ([string] $testName) {
                        & (Resolve-Path "$PSCommandPath/../../Tests/OctopusServerExeInvocationFiles/$testName/CurrentState.ps1")
                    }

                    function Get-RequestedConfiguration ([string] $testName) {
                        & (Resolve-Path "$PSCommandPath/../../Tests/OctopusServerExeInvocationFiles/$testName/RequestedState.ps1")
                    }

                    function Get-TempFolder {
                        if ("$($env:TmpDir)" -ne "") {
                            return $env:TmpDir
                        } else {
                            return $env:Temp
                        }
                    }
                }

                function ConvertTo-Hashtable
                {
                    param (
                        [Parameter(ValueFromPipeline = $true)]
                        [Object[]] $InputObject
                    )

                    process {
                        foreach ($object in $InputObject) {
                            $hash = @{}
                            foreach ($property in $object.PSObject.Properties) {
                                $hash[$property.Name] = $property.Value
                            }
                            $hash
                        }
                    }
                }

                function Assert-ExpectedResult ([string] $testName) {
                    # todo: test order of execution here
                    $invocations = & (Resolve-Path "$PSCommandPath/../../Tests/OctopusServerExeInvocationFiles/$testName/ExpectedResult.ps1")
                    $name = @{label="line";expression={$_}};
                    $cases = @($invocations | select-object -Property $name) | ConvertTo-HashTable
                    it "Should call octopus.server.exe $($invocations.count) times" {
                        Assert-MockCalled -CommandName 'Invoke-OctopusServerCommand' -Times $invocations.Count -Exactly
                    }
                    if ($cases.length -gt 0) {
                        it "Should call octopus.server.exe with args '<line>'" -TestCases $cases {
                            param($line)
                            write-verbose "Checking line $line" # workaround ReviewUnusedParameter does not capture parameter usage within a scriptblock. See https://github.com/PowerShell/PSScriptAnalyzer/issues/1472
                            Set-TargetResource @params
                            Assert-MockCalled -CommandName 'Invoke-OctopusServerCommand' -Times 1 -Exactly -ParameterFilter { ($cmdArgs -join ' ') -eq $line }
                        }
                    }
                }

                Context "New instance" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand {} #{write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "NewInstance" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true } # just assume we're the most recent version
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux

                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "NewInstance"
                    }
                    Assert-ExpectedResult "NewInstance"
                    it "Should download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File
                    }

                    it "Should install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec
                    }
                }

                Context "New instance with metrics" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "NewInstanceWithMetrics" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true } # just assume we're the most recent version
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux
                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "NewInstanceWithMetrics"
                    }
                    Assert-ExpectedResult "NewInstanceWithMetrics"
                    it "Should download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File
                    }
                    it "Should install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec
                    }
                }

                Context "When MasterKey is supplied on new instance" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "MasterKeySupplied" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true } # just assume we're the most recent version
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux
                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "MasterKeySupplied"
                    }
                    Assert-ExpectedResult "MasterKeySupplied"
                    it "Should download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File
                    }
                    it "Should install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec
                    }
                }

                Context "When uninstalling running instance" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{ param ($cmdArgs) write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "UninstallingRunningInstance" }
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Get-ExistingOctopusService { return @() }
                        Mock Get-LogDirectory { return Get-TempFolder }
                        Mock Test-Path -ParameterFilter { $path -eq "$($env:SystemDrive)\Octopus\Octopus-x64.msi" } { return $true }
                        Mock Start-Process { return @{ ExitCode = 0} }
                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "UninstallingRunningInstance"
                    }
                    Assert-ExpectedResult "UninstallingRunningInstance"
                    it "Should not download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File -Times 0 -Exactly
                    }
                    it "Should not try and install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec -Times 0 -Exactly
                    }
                    it "Should uninstall the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Start-Process -Times 1 -Exactly -ParameterFilter { $FilePath -eq "msiexec.exe" -and $ArgumentList -eq "/x $($env:SystemDrive)\Octopus\Octopus-x64.msi /quiet /l*v $(Get-TempFolder)\Octopus-x64.msi.uninstall.log"}
                    }
                }

                Context "Run-on-server user - new install" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{ param ($cmdArgs) write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "NewInstallWithBuiltInWorker" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true }
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux

                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "NewInstallWithBuiltInWorker"
                    }
                    Assert-ExpectedResult "NewInstallWithBuiltInWorker"
                    it "Should download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File
                    }
                    it "Should install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec
                    }
                }

                Context "Run-on-server user - existing install" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{ param ($cmdArgs) write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "EnableBuiltInWorkerOnExistingInstance" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true }
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux

                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "EnableBuiltInWorkerOnExistingInstance"
                    }
                    Assert-ExpectedResult "EnableBuiltInWorkerOnExistingInstance"
                    it "Should not download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File -Times 0 -Exactly
                    }
                    it "Should not try and install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec -Times 0 -Exactly
                    }
                }

                Context "Upgrade" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{ param ($cmdArgs) write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "UpgradeExistingInstance" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true }
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux

                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "UpgradeExistingInstance"
                    }
                    Assert-ExpectedResult "UpgradeExistingInstance"
                    it "Should download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File
                    }
                    it "Should install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec
                    }
                }

                Context "Change WebListenPrefix" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{ param ($cmdArgs) write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "ChangeWebListenPrefix" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true }
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux

                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "ChangeWebListenPrefix"
                    }
                    Assert-ExpectedResult "ChangeWebListenPrefix"
                    it "Should not download the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Request-File -Times 0 -Exactly
                    }
                    it "Should not try and install the MSI" {
                        Set-TargetResource @params
                        Assert-MockCalled Invoke-MsiExec -Times 0 -Exactly
                    }
                }

                Context "When nothing changes" {
                    BeforeAll {
                        Mock Invoke-OctopusServerCommand #{ param ($cmdArgs) write-host $cmdArgs}
                        Mock Get-TargetResource { return Get-CurrentConfiguration "WhenNothingChanges" }
                        Mock Get-RegistryValue { return "478389" } # checking .net 4.5
                        Mock Invoke-MsiExec {}
                        Mock Get-LogDirectory {}
                        Mock Request-File {}
                        Mock Update-InstallState {}
                        Mock Test-OctopusDeployServerResponding { return $true }
                        Mock Test-OctopusVersionNewerThan { return $true }
                        Mock ConvertFrom-SecureString { return "" } # mock this, as its not available on mac/linux
                        Mock Stop-OctopusDeployService

                        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification = "It is actually used, but pester's scoping is weird")]
                        $params = Get-RequestedConfiguration "WhenNothingChanges"
                    }
                    Assert-ExpectedResult "WhenNothingChanges"
                    it "Should not restart the service" {
                        Set-TargetResource @params
                        Assert-MockCalled Stop-OctopusDeployService -Times 0 -Exactly
                    }
                }
            }
        }
    }
}
finally
{
    if ($module) {
        Remove-Module -ModuleInfo $module
    }
}