Moc.Tests.ps1

BeforeAll {
    Import-Module Pester
    ipmo .\Moc.psm1
    Import-Module $PSScriptRoot\MocTestMocks.psm1
    $cluster = "s-cluster"
}

AfterAll {
    Remove-Module Moc
    Remove-Module MocTestMocks
}

# Describe 'Set-MocConfig' {
# BeforeAll {
# $global:config = @{}
# $global:config[$global:MocModule] = @{}
# }

# It 'Should be able to create ssh configuration and set-mocconfig without IP' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test"
# $sshConfig.Name | should -Be "test"
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $false
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -BeNullOrEmpty
# (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty
# }

# It 'Should be able to create ssh configuration and set-mocconfig with IP Address' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1')
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $false
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -BeNullOrEmpty
# (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1"
# }

# It 'Should be able to create ssh configuration and set-mocconfig with IP Addresses' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1', '127.0.0.2')
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $false
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -BeNullOrEmpty
# (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1,127.0.0.2"
# }

# It 'Should be able to create ssh configuration and set-mocconfig with CIDR' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25'
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $false
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25"
# (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty
# }

# It 'Should be able to create ssh configuration and set-mocconfig with sshRestriction' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $true
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25"
# (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty
# }

# It 'Should be able to create ssh configuration and set-mocconfig with public & private key' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# Set-Content -value "1234" -path "C:\test\temp.pub"
# Set-Content -value "4567" -path "C:\test\temp"
# $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands -sshPublicKey "C:\test\temp.pub" -sshPrivateKey "C:\test\temp"
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $true
# (Get-MocConfig).sshPublicKey | should -Be "C:\test\temp.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\test\temp"
# (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25"
# (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty
# }

# It 'Get-SSHAuthConfigSudoRestrictions with public & private key' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# Set-Content -value "1234" -path "C:\test\temp.pub"
# Set-Content -value "4567" -path "C:\test\temp"
# $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands -sshPublicKey "C:\test\temp.pub" -sshPrivateKey "C:\test\temp"
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $true
# (Get-MocConfig).sshPublicKey | should -Be "C:\test\temp.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\test\temp"
# (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25"
# (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty
# Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1/25"" 1234"
# }

# It 'Get-SSHAuthConfigSudoRestrictions without public & private key' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $true
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25"
# (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty
# $content = get-content -path "C:\AksHci\.ssh\akshci_rsa.pub"
# Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1/25"" $content"
# }

# It 'Get-SSHAuthConfigSudoRestrictions with ip Address' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1')
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $false
# (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa"
# (Get-MocConfig).sshCidr | should -BeNullOrEmpty
# (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1"
# $content = get-content -path "C:\AksHci\.ssh\akshci_rsa.pub"
# Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1"" $content"
# }

# It 'Get-SSHAuthConfigSudoRestrictions with ip Addresses' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1','127.0.0.2') -sshPublicKey "C:\test\temp.pub" -sshPrivateKey "C:\test\temp"
# Set-Content -value "1234" -path "C:\test\temp.pub"
# Set-Content -value "4567" -path "C:\test\temp"
# $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "")
# Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck
# (Get-MocConfig).sshConfigName | should -Be "test"
# (Get-MocConfig).sshRestrictCommands | should -Be $false
# (Get-MocConfig).sshPublicKey | should -Be "C:\test\temp.pub"
# (Get-MocConfig).sshPrivateKey | should -Be "C:\test\temp"
# (Get-MocConfig).sshCidr | should -BeNullOrEmpty
# (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1,127.0.0.2"
# Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1,127.0.0.2"" 1234"
# }

# It 'Set-MocConfigValue with invalid values' {
# Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test"
# { Set-MocConfigValue -name installState -value "invalid" } | Should -Throw
# Set-MocConfigValue -name installState -value [InstallState]::InstallFailed
# }
# }

Describe "Set-DirectoryPermission" {
    It "Should not take more than -Timeout parameter" {
        $modulename = "Moc"

        $workingDir = Join-Path -Path (Get-Location) -ChildPath "test"
        $installationDirectory = Join-Path -Path $workingDir -ChildPath "1.0.23.10605"
        $yamlDirectory = Join-Path -Path $installationDirectory -ChildPath "yaml"
        $imageDir = Join-Path -Path $workingDir -ChildPath "images"
        $cloudConfigLocation = Join-Path -Path $workingDir -ChildPath "cloud"

        Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue
        New-Item -Path $workingDir -ItemType Directory -Force | Out-Null
        New-Item -Path $installationDirectory -ItemType Directory -Force | Out-Null
        New-Item -Path $yamlDirectory -ItemType Directory -Force | Out-Null
        New-Item -Path $imageDir -ItemType Directory -Force | Out-Null
        New-Item -Path $cloudConfigLocation -ItemType Directory -Force | Out-Null

        $global:config[$modulename]["workingDir"] = $workingDir
        $global:config[$modulename]["installationPackageDir"] = $installationDirectory
        $global:config[$modulename]["imageDir"] = $imageDir
        $global:config[$modulename]["cloudConfigLocation"] = $cloudConfigLocation

        # simulate Set-SecurePermissionFolder takes way longer
        Mock -ModuleName "Common" Start-Job {
            $scriptBlock = {
                Start-Sleep -Seconds 10
                return "Job completed"
            }
            $randStrings = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 4 | % {[char]$_})
            $jobname = $randStrings + "Permission"
            $job = Start-Job -name $jobname -ScriptBlock $scriptBlock
            return $job
        }

        # set start time
        $startTime = Get-Date
        Set-DirectoryPermission -timeoutSeconds 3
        $endTime = Get-Date
        # Set-DirectoryPermission should not take more than 3 seconds for each Set-SecurePermissionFolder job
        ($endTime - $startTime).TotalSeconds | Should -BeLessThan 20

        # clean up
        Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue
        $global:config[$modulename] = $null
    }
}

Describe "Test-InstallStateEligibility" -Tag "test" {
    <#
        InstallState Set-MocConfig Install-Moc
        NotInstalled Proceed Proceed
        Installing (has no active process) Proceed Proceed
        Installing (has active process) Exit Exit
        InstallFailed Proceed Proceed
        Updating Error Error
        UpdateFailed Error Error
        Uninstalling Error Error
        UninstallFailed Error Error
        Installed Exit Exit
    #>

    BeforeAll {
        $NoThrow = "NoThrow"
        $ShouldThrow = "ShouldThrow"
        $ShouldExit = "ShouldExit"
        function Assert-InstallStateEligibility {
            param(
                [string]$Activity,
                [ValidateSet('NoThrow', 'ShouldThrow', 'ShouldExit', IgnoreCase = $false)]
                [string]$ExpectedErrorType
            )
        
            switch ($ExpectedErrorType) {
                'NoThrow' {
                    { Test-InstallStateEligibility -activity $Activity } | Should -Not -Throw
                }
        
                default {
                    try {
                        Test-InstallStateEligibility -activity $Activity
                        throw "Expected exception of type '$ExpectedErrorType' but none was thrown."
                    } catch {
                        $_.Exception.ErrorType | Should -Be $ExpectedErrorType
                    }
                }
            }
        }
    }

    # set-mocconfig is not allowed but install-moc is allowed
    It "when installstate is NotInstalled" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::NotInstalled }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::NotInstalled }

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $NoThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $NoThrow
    }
    It "when installstate is InstallFailed" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::InstallFailed }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::InstallFailed }
        Mock -ModuleName "Moc" -CommandName "Uninstall-Moc" {}

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $NoThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $NoThrow
        Should -Invoke -CommandName Uninstall-Moc -ModuleName "Moc" -Times 1
    }
    It "when installstate is UninstallFailed" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::UninstallFailed }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::UninstallFailed }
        Mock -ModuleName "Moc" -CommandName "Uninstall-Moc" {}

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow
    }
    # Both set-mocconfig and install-moc are not allowed
    It "when installstate is Updating" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Updating }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Updating }

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow
    }
    It "when installstate is UpdateFailed" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::UpdateFailed }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::UpdateFailed }

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow
    }
    It "when installstate is Uninstalling" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Uninstalling }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Uninstalling }

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow
    }
    # Both set-mocconfig and install-moc are allowed
    It "when installstate is Installed" {
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Installed }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Installed }

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldExit
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldExit
    }
    # Both set-mocconfig and install-moc are allowed when there's no active process
    It "when installstate is Installing but there's no active process" {
        # there's no running process on the host
        Mock -ModuleName "Moc" -CommandName "Get-InvokedPSCommands" {}
        # however, installstate is Installing because its process was killed
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Installing }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Updating }

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $NoThrow
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $NoThrow
    }
    # Both set-mocconfig and install-moc are not allowed when there's active process
    It "when installstate is Installing and there's active processes of it" {
        $installMoc = "Install-Moc"
        $PIDRunningInBackground = 1234
        $hostname = "REDMOND-DESKTOP"

        # Install-Moc was recorded in $mocconfig.commands
        Mock -ModuleName "Common" -CommandName "Get-InvokedPSCommands" {
            return @([PSCustomObject]@{
                    cmdletName   = $installMoc
                    processId    = $PIDRunningInBackground
                    computerName = $hostname
                    startTime    = Get-Date
                })
            }
        # the process is still running
        Mock -ModuleName "Common" -CommandName "Get-Process" {
            return @(
                [PSCustomObject]@{
                    ProcessName = "powershell"
                    Id          = $PIDRunningInBackground
                }
            )
        }
        Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Installing }
        Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Installing }
        Mock -ModuleName "Moc" -CommandName "Uninitialize-MocEnvironment" {}

        Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldExit
        Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldExit
        Should -Invoke -CommandName Uninitialize-MocEnvironment -ModuleName "Moc" -Times 2
    }
}

Describe "Remove-MocDirectories" {
    It "Should remove all directories with expected names" {
        $workingDir = Join-Path -Path (Get-Location) -ChildPath "test"
        $cloudConfigLocation = Join-Path -Path $workingDir -ChildPath "wssdcloudagent"
        $versionDir = Join-Path -Path $cloudConfigLocation -ChildPath "v0.26.4"
        $logDir = Join-Path -Path $cloudConfigLocation -ChildPath "log"
        $certificateDir = Join-Path -Path $versionDir -ChildPath "certificate"
        $clusterDir = Join-Path -Path $versionDir -ChildPath "cluster"

        Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue
        New-Item -Path $cloudConfigLocation -ItemType Directory -Force | Out-Null
        New-Item -Path $versionDir -ItemType Directory -Force | Out-Null
        New-Item -Path $logDir -ItemType Directory -Force | Out-Null
        New-Item -Path $certificateDir -ItemType Directory -Force | Out-Null
        New-Item -Path $clusterDir -ItemType Directory -Force | Out-Null

        Mock -ModuleName "Moc" Get-MocConfig {
            @{
                "cloudConfigLocation" = $cloudConfigLocation
            }
        }

        Remove-MocDirectories

        $dirs = @(
            $certificateDir,
            $clusterDir
        )

        foreach ($dir in $dirs) {
            Test-Path -Path $dir | Should -BeFalse
        }

        Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue

    }
}
Describe "Test-FailoverClusterNodeConnectivity" {
    It "Should fail if a node is not pingable" {

        Mock -ModuleName "Moc" Get-FailoverCluster {
            "cluster"
        }

        Mock -ModuleName "Moc" Invoke-Command {
            @{
                "PingSucceeded" = $false
            }
        } 

        Mock -ModuleName "Moc" Get-ClusterNode {
            @(
                @{
                    "name" = "abc-d120301"
                },
                @{
                    "name" = "abc-d120302"
                }
            )
        }
        $result = Test-FailoverClusterNodeConnectivity

        $result.TestResult | Should -Be "Failed"
    }

    It "Should fail if invoke-command can't be run on a node" {

        Mock -ModuleName "Moc" Get-FailoverCluster {
            "cluster"
        }

        Mock -ModuleName "Moc" Invoke-Command {
            throw "Mock exception"
        } 

        Mock -ModuleName "Moc" Get-ClusterNode {
            @(
                @{
                    "name" = "abc-d120301"
                },
                @{
                    "name" = "abc-d120302"
                }
            )
        }
        $result = Test-FailoverClusterNodeConnectivity

        $result.TestResult | Should -Be "Failed"
        #should have the exception
        $result.Details | Should -Be "Mock exception"
    }

    It "Should succeed if all nodes are pingable" {

        Mock -ModuleName "Moc" Get-FailoverCluster {
            "cluster"
        }

        Mock -ModuleName "Moc" Invoke-Command {
            @{
                "TcpTestSucceeded" = $true
            }
        }

        Mock -ModuleName "Moc" Get-ClusterNode {
            @(
                @{
                    "name" = "abc-d120301"
                },
                @{
                    "name" = "abc-d120302"
                }
            )
        }
        $result = Test-FailoverClusterNodeConnectivity

        $result.TestResult | Should -Be "Success"
    }

    It "Should skip if not a failover cluster" {
        Mock -ModuleName "Moc" Get-FailoverCluster {
            $null
        }
        $result = Test-FailoverClusterNodeConnectivity

        $result.TestResult | Should -Be "Skipped"
    }
}

Describe "Verify Test-ProxyEnvironmentVariableOnNode" {
    It "Should fail in case of throw from Invoke-Command [ERROR][WARNING]" {
        Mock -ModuleName "Moc" Invoke-Command {
            throw "Mock exception"
        }

        foreach ($proxyEnvVar in @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")){
            $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120302" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar
            $result.Severity | Should -Be "WARNING"
            $result.Status | Should -Be "ERROR"
            $result.TargetResourceName | Should -Be "abc-d120302"
            $result.Description | Should -Be "Unable to check $proxyEnvVar environment variable due to exception: Mock exception"
            $result.Remediation | Should -Be "Please ensure $proxyEnvVar environment variable value is correct on the node. See https://learn.microsoft.com/en-us/azure-stack/hci/manage/configure-proxy-settings-23h2#environment-variables-proxy-bypass-list-string-considerations for full details"
        }
    }

    It "Should succeed in case of valid proxy string [SUCCESS][INFORMATIONAL]" {
        Mock -ModuleName "Moc" Invoke-Command {
            "localhost,127.0.0.1,s-cluster"
        }

        foreach ($proxyEnvVar in @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")){
            $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120303" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar
            $result.Severity | Should -Be "INFORMATIONAL"
            $result.Status | Should -Be "SUCCESS"
            $result.TargetResourceName | Should -Be "abc-d120303"
        }
    }

    It "Should succeed in case of null proxy env var [SUCCESS][INFORMATIONAL]" {
        Mock -ModuleName "Moc" Invoke-Command {
            $null
        }

        foreach ($proxyEnvVar in @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")){
            $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120303" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar
            $result.Severity | Should -Be "INFORMATIONAL"
            $result.Status | Should -Be "SUCCESS"
            $result.TargetResourceName | Should -Be "abc-d120303"
            $result.Description | Should -Be "$proxyEnvVar environment variable is null on node, skipping checks"
        }
    }

    It "Should fail in case of invalid string contents [FAILURE][CRITICAL]" {
        Mock -ModuleName "Moc" Invoke-Command {
            "localhost,127.0.0.1;s-cluster"
        } -ParameterFilter { $ComputerName -eq  "abc-d120301"}

        Mock -ModuleName "Moc" Invoke-Command {
            "localhost,127.0.0.1;s-cluster,*"
        } -ParameterFilter { $ComputerName -eq  "abc-d120302"}

        Mock -ModuleName "Moc" Invoke-Command {
            "localhost,127.0.0.1,s-cluster,*"
        } -ParameterFilter { $ComputerName -eq  "abc-d120303"}

        $fixProxyRemediation = "Please correct {0} environment variable value. See https://learn.microsoft.com/en-us/azure-stack/hci/manage/configure-proxy-settings-23h2#environment-variables-proxy-bypass-list-string-considerations for full details"
        $proxyEnvironmentVariables = @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")

        foreach ($proxyEnvVar in $proxyEnvironmentVariables){
            $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120301" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar
            $result.Severity | Should -Be "CRITICAL"
            $result.Status | Should -Be "FAILURE"
            $result.TargetResourceName | Should -Be "abc-d120301"
            $result.Description | Should -Be "$proxyEnvVar environment variable contains invalid wildcard character ;"
            $result.Remediation | Should -Be $($fixProxyRemediation -f $proxyEnvVar)
        }

        foreach ($proxyEnvVar in $proxyEnvironmentVariables){
            $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120302" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar
            $result.Severity | Should -Be "CRITICAL"
            $result.Status | Should -Be "FAILURE"
            $result.TargetResourceName | Should -Be "abc-d120302"
            $result.Description | Should -Be "$proxyEnvVar environment variable contains invalid wildcard characters * and ;"
            $result.Remediation | Should -Be $($fixProxyRemediation -f $proxyEnvVar)
        }

        foreach ($proxyEnvVar in $proxyEnvironmentVariables){
            $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120303" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar
            $result.Severity | Should -Be "CRITICAL"
            $result.Status | Should -Be "FAILURE"
            $result.TargetResourceName | Should -Be "abc-d120303"
            $result.Description | Should -Be "$proxyEnvVar environment variable contains invalid wildcard character *"
            $result.Remediation | Should -Be $($fixProxyRemediation -f $proxyEnvVar)
        }
    }
}
Describe "Verify Test-ProxyEnvironmentVariables" {
    It "Should succeed if Test-ProxyEnvironmentVariableOnNode succeeds on all nodes" {
        Mock -ModuleName "Moc" Get-ClusterNode {
            @(
                @{
                    "Name" = "abc-d120302"
                },
                @{
                    "name" = "abc-d120303"
                }
            )
        }

        Mock -ModuleName "Moc" Test-ProxyEnvironmentVariableOnNode {
            @{
                "Status" = "SUCCESS"
                "TargetResourceName" = "abc-d120302"
            }
        } -ParameterFilter { $NodeName -eq  "abc-d120302"}

        Mock -ModuleName "Moc" Test-ProxyEnvironmentVariableOnNode {
            @{
                "Status" = "SUCCESS"
                "TargetResourceName" = "abc-d120303"
            }
        } -ParameterFilter { $NodeName -eq  "abc-d120303"}

        $result = Test-ProxyEnvironmentVariables -Cluster $cluster
        $result.count | Should -Be 6
        for ($i = 0; $i -lt 6; $i++) {
            $result[$i].Status | Should -Be "SUCCESS"
            if ($i -lt 3){
                $result[$i].TargetResourceName | Should -Be "abc-d120302"
            } else {
                $result[$i].TargetResourceName | Should -Be "abc-d120303"
            }
        }
    }
}

Describe "Verify Test-Precheck" {
    It "Should throw exception if atleast one precheck result does not succeed and is critical" {
        $precheckResults = @(
            @{
                "Title" = "HTTPS_PROXY environment variable validity"
                "Description" = "HTTPS_PROXY environment variable contains invalid wildcard characters * and ;"
                "TargetResourceID" = "Cluster/" + $cluster + "/Node/abc-d120301"
                "Remediation" = "Please correct HTTPS_PROXY environment variable value."
                "Severity" = "CRITICAL"
                "Status" = "FAILURE"
            }
        )

        $expectedExceptionMessage = "Moc {0} Prechecks failed, unable to proceed with {0}. Please see the recommended actions for each failure. Precheck failures: " `
        + "`nFailed {0} precheck: $($precheckResults[0].Title) failed due to $($precheckResults[0].Description) on resource $($precheckResults[0].TargetResourceID), " `
        + "recommendation is: $($precheckResults[0].Remediation)"
        $expectedInstallExceptionMessage = $expectedExceptionMessage -f "Install"
        $expectedUpgradeExceptionMessage = $expectedExceptionMessage -f "Upgrade"

        { Test-Precheck -Results $precheckResults -Operation "Install" } | Should -Throw $expectedInstallExceptionMessage
        { Test-Precheck -Results $precheckResults -Operation "Upgrade" } | Should -Throw $expectedUpgradeExceptionMessage
    }

    It "Should not throw exception if there is no precheck result that does not succeed and is critical" {
        $precheckResults = @(
            @{
                "Severity" = "INFORMATIONAL"
                "Status" = "SUCCESS"
            },
            @{
                "Severity" = "WARNING"
                "Status" = "ERROR"
            }
        )

        { Test-Precheck -Results $precheckResults -Operation "Install" } | Should -Not -Throw
        { Test-Precheck -Results $precheckResults -Operation "Upgrade" } | Should -Not -Throw
    }
}

Describe "Test-EventLogServiceOnNodes" {

    Context "When EventLog service is running on all nodes" {
        It "should return Success for all nodes" {
            Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"}
            Mock -ModuleName "Moc" Get-ClusterNode { @(@{ "Name" = "Node1" }, @{ "Name" = "Node2" }) }
            Mock -ModuleName "Moc" Invoke-Command {
                [PSCustomObject]@{ Status = "Running" }
            }
            $results = Test-EventLogServiceOnNodes
            $results | ForEach-Object {
                $_.TestResult | Should -Be "Success"
                $_.Details | Should -Match "EventLog service is running"
            }
        }
    }

    Context "When EventLog service is stopped on a node" {
        It "should return Failed for the node" {
            Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"}
            Mock -ModuleName "Moc" Get-ClusterNode { @(@{ "Name" = "Node1" }) }
            Mock -ModuleName "Moc" Invoke-Command {
                [PSCustomObject]@{ Status = "Stopped" }
            }
            $results = Test-EventLogServiceOnNodes
            $results[0].TestResult | Should -Be "Failed"
            $results[0].Details | Should -Match "EventLog service is not running"
            $results[0].Recommendation | Should -Match "Ensure EventLog service is running on all nodes. Start-Service -Name 'EventLog'"
        }
    }

    Context "When checking EventLog service throws an exception" {

        It "should return Failed with error details" {
            Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"}
            Mock -ModuleName "Moc" Get-ClusterNode { @(@{ "Name" = "Node1" }) }
            Mock -ModuleName "Moc" Invoke-Command { throw "Access denied" }
            $results = Test-EventLogServiceOnNodes
            $results[0].TestResult | Should -Be "Failed"
            $results[0].Details | Should -Match "Could not check EventLog service"
            $results[0].Recommendation | Should -Match "Ensure remote PowerShell access"
        }
    }

    Context "When EventLog service is running on one node and stopped on another" {
        It "should return Success for Node1 and Failed for Node2" {
            Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"}
            Mock -ModuleName "Moc" Get-ClusterNode { @(@{ Name = "Node1" }, @{ Name = "Node2" }) }
            Mock -ModuleName "Moc" Invoke-Command {
                param($ComputerName, $ScriptBlock)
                if ($ComputerName -eq "Node1") {
                    return [PSCustomObject]@{ Status = "Running" }
                } else {
                    return [PSCustomObject]@{ Status = "Stopped" }
                }
            }
            $results = Test-EventLogServiceOnNodes 
            $node1 = $results | Where-Object { $_.NodeName -eq "Node1" }
            $node2 = $results | Where-Object { $_.NodeName -eq "Node2" }

            $node1.TestResult | Should -Be "Success"
            $node1.Details | Should -Match "EventLog service is running"

            $node2.TestResult | Should -Be "Failed"
            $node2.Details | Should -Match "EventLog service is not running"
            $node2.Recommendation | Should -Match "Ensure EventLog service is running on all nodes. Start-Service -Name 'EventLog'"
        }
    }
}
# SIG # Begin signature block
# MIInJwYJKoZIhvcNAQcCoIInGDCCJxQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDPOqHNUQ28JWWG
# 5dGQCTgyIBaxBzCU9nx334AYfDcB0aCCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1
# OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8
# o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg
# 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4
# Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R
# X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk
# ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw
# HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg
# UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh
# MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9
# s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H
# VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3
# w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n
# 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs
# A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo
# Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb
# SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6
# 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z
# V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v
# 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs
# /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA
# AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow
# VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh
# emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h
# KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd
# M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp
# yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t
# Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5
# REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs
# 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK
# Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5
# pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW
# eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ
# 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI
# MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4
# NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh
# ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q
# hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU
# nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb
# H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z
# uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u
# vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW
# 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV
# DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10
# 1cY2L4A7GTQG1h32HHAvfQESWP0xghnDMIIZvwIBATBuMFcxCzAJBgNVBAYTAlVT
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w
# DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ
# KoZIhvcNAQkEMSIEIMbZYUVMHDWnkNf21rMTb/MViC5UrIIGYXEIZDoT5mAVMEIG
# CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAHo44FLqDaKhlpDV+
# IQ8uR23XaNApd1/Foxl9C3+StBi3vpNdc4FLovQtREAxuSRqqFIFPKSaV69sqgA0
# K9sawlmhqkqx/OxrVJ0ZlmaXkR54kTOhjLoM4YtxTjJls17aHGJln6uJWJjWjPfJ
# 5yhQlbH2T2k6vl11oJ0PdMfMLxJKHy6t6cgc+8X0VZowZkXFHDy8HyXRzTUBkeuS
# ez1wDKa6ZgLya+7ZF9PZzlkSNTZmaqH6DrLlsDrS/UwbOVZv9nhNDMUH+Wr1wH9T
# iLyvaSej5G4VeDV5FbClYnFKN5xXZrPOZ3pjiSWsCzDqgIeo8oqnpxMIMKEVnLkj
# 1L3YP6GCF5MwghePBgorBgEEAYI3AwMBMYIXfzCCF3sGCSqGSIb3DQEHAqCCF2ww
# ghdoAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9
# MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCCiXI+VYgsw2KKx
# 1MP3AtIy/qwHo5FLNHlTTFnKG2Mn3QIGahdQmW3gGBMyMDI2MDUyODAwNDE1OC42
# OThaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw
# JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNVBAMT
# HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHpMIIHIDCCBQigAwIBAgIT
# MwAAAh86cGnkojAulQABAAACHzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTM5NTFaFw0yNzA1MTcxOTM5NTFa
# MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk
# IFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDL
# O8XFOcfGqAqgiz0+AmQmFl3dZ0aTG4UFJkqqNdMHy28DaheCBs6ONufukye5x42C
# WkzgRIy9kE2VWwEntZ8ZkgyrykC0bIqsID7+6FxguseTXf1Vwvm1D8104VmetoBJ
# lJ4uGbuyJZUvXDx55nVh50ygLTzZ24WkQsnPpvRZv2kPc39f3bhLyHVtnHsa/W/8
# 6Vrftd+AfFveA+qN/EY+XGj5c/DPMXCYECb0arYb92dDJWtwzpyBrp4gfHlgY1UE
# pc4l4AGELrf2J4wrxTzTW+SM8XhV1dOOPrYjD080IbZqL8B+IF0RCdn269YXrGK6
# QIHipznKZcCS8jN30YAHnTJVN5Zzs6t/2YsqBGDquvDad7934FFTwzvUcO3VoIyd
# 93XWwvP8/SCFVJh21W8oGQTptGHyly+Fl4henVMVZF1v6osOtirX8GFTiEhnf8nR
# dOg7yZYAJ0xy9CtDfbXaTn/cf3Lq3N/GCYKFjC+5mUCE+AJhmxMuMdvSUGmKiAFd
# iPAjUTqsWWBBZJm0eCwgeGJFmmQA+V7/98BKcE+gUL7O9eWRDQwKeAcvo6rxNv2Y
# 4jKrHA6Z/wi3a/fKUhLCNZES8qGdrpDAm7qh+6FjYxytAbkiKM6uTNy/ULPlwtlY
# ZoAJDDQP7eYCywwVbNTbHXRBSS+NccC0sSB4W7U67wIDAQABo4IBSTCCAUUwHQYD
# VR0OBBYEFNk72sGDlH0r5DwvfGR5XwJI8B7bMB8GA1UdIwQYMBaAFJ+nFV0AXmJd
# g/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9z
# b2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGlt
# ZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0l
# AQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUA
# A4ICAQBlbu3IoynnPz0K1iPbeNnsej2b15l5sdl2FAFBBGT9lRdc2gNV8LAIusPY
# HHhUvRDcsx4lbMNhVKPGu4TDLaqNt/CI+SFtGuqdRLpVP1XE9cCLyKrKPpcJFJCq
# PpV+efoAtYBmIUQcxxwT7WIQ7gag8+rkKvrMkCoRqKS0mKv8J1sKfi85+G2uhZ/1
# RteSVdYZOZOj+Sb4wzonTCTj7EtgMN/BX35W5dTzd7wJdGepYkVi871dSrC2Tr1Z
# FzAR7S44drCWZpJ6phJabVNOsNxFJKgSykugOGWzQ318Rr3MTPg2s3Bns+pUPVgM
# ijd4bUOH2BlEsLMMwOcolTTZqg1HYrdY1jxpUAI9ipjBQRINL/O705Z+/f2LjNmJ
# QooCVJVX24adpZ519SsfazGoqXGt91bmqKo0fI09Il4sUHh4ih6rpiQDBlyL7vmv
# CejwVxYevY4qVwTZ/o3gvl+R0lFxYS9feIM4NeG0+WsDZ7jLci5MFeuNwosQY3z2
# 6Xg1oj0U9u+ncR9uTU+xBmJ8BtlCdhQ13RNMX5P+krRYPB3XCp9Jm6XaO1995q32
# AIZm1mzBGI6yHlviXaEC5TzGiO1LXuPtXZU2X93oQJbMoe3v8+5CPKrQalGWyYuh
# 2a3V1pwbj+W0FEmEFPpu8TI+qYO1IIQWUSRvFjXth5Ob02hMMjCCB3EwggVZoAMC
# AQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m
# dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIy
# NVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9
# DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2
# Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N
# 7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXc
# ag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJ
# j361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjk
# lqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37Zy
# L9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M
# 269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLX
# pyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLU
# HMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode
# 2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEA
# ATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYE
# FJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEB
# MEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# RG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEE
# AYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
# /zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEug
# SaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9N
# aWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsG
# AQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jv
# b0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt
# 4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsP
# MeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++
# Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9
# QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2
# wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aR
# AfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5z
# bcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nx
# t67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3
# Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+AN
# uOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/Z
# cGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNMMIICNAIBATCB+aGB0aSBzjCB
# yzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMc
# TWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBU
# U1MgRVNOOjM3MDMtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T
# dGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBLIMg1P7sNuCXpmbH2IXT2tXeE
# EKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3
# DQEBCwUAAgUA7cHPEjAiGA8yMDI2MDUyNzIwMTQxMFoYDzIwMjYwNTI4MjAxNDEw
# WjBzMDkGCisGAQQBhFkKBAExKzApMAoCBQDtwc8SAgEAMAYCAQACATIwBwIBAAIC
# ElcwCgIFAO3DIJICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK
# MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQsFAAOCAQEApymKn9dC
# gDdZM8h40XyO6a+eNPTMGaYhXk4wjB1lucVm3+OOdodg428AJrQdh4h7fvqF3stI
# Kh0pv02ARojCq/EQdBIseiFrhcDLd1osPDyae8DkV6GUoYUllHsag5gCS1mYsoXy
# IJ/hi7zVCyFYpC6NOMufI2FziKWcRU8VYz1/7Avhfc2KkoUIOM1FitbYKvAGNXYP
# VzGo9FrcagF54bfcqp11Tvv/LNXNcGHm+r96mbPVCHj/F8bdhgqOAadtg9aRIKSv
# uM5iSvRlq6iGflQDEiRAihz1qy3CH+W+cHbMpcWtDsiK0meewDEl4j0T8NX6n5l+
# DMxvOSp3QJ9s3zGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwAhMzAAACHzpwaeSiMC6VAAEAAAIfMA0GCWCGSAFlAwQCAQUAoIIBSjAa
# BgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIIrt+cw3
# yFnQ/w0Dl/Xz8EGBcTS1X4R1EeMy75EDQd69MIH6BgsqhkiG9w0BCRACLzGB6jCB
# 5zCB5DCBvQQgsCQK31aQKwy1RGQW7pNjQ/dRd1GcKJi49mF7fKQt/BQwgZgwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAh86cGnkojAulQAB
# AAACHzAiBCBGJKDQ2aDjA3SkStukBUN5FOZnFFjvObT3dwpZgGC+/TANBgkqhkiG
# 9w0BAQsFAASCAgBxFxoengaJlSWHtYEeuGiIl1WCrLuupYl5HVD7DOqE/RyV0/4M
# Es/TGD5tzJalCOYY2QWd8VYfR87v9LImIx64/5FcYLS0RQ28AEXZPBg510SrELp7
# CjhaQ53XJZ82+rGcboD1JHYYi4gp7eJrQeA1ceA6Edz6IF4V7q/exs3iUMPfeRWf
# tsD1naEAaqFPoVPbAdt5lC3M7jKucj57zmXxk+HizvfpNN2UqHxrVWmIe7/UhiLJ
# cBwpw6Ec1QvOPRvMBMHejbCJ7S22EiaLzWSbC1H980xtTz2Ab0WVZoYbondbJzFJ
# HDxHoqmNUfGiPJEIBoKy8fDAbxGB+cNvr7390cDEJB6Nec/IDbfHUaf6It43iTsS
# ZcGPwgMmXZKnrXJB3Neo0+77oN/Dmc7AhWhXOY3AekUAc+LT3rvq+flGWeHJq9Vy
# Y56NPLMyrLEj5mzNBRjxmGFoaPEAIaGgYyfRSSupsjR9KpkpJJSFgN7zaBFzEMYJ
# W/+Pix/64ZrunMdbLtgHUMopPPvKmVYlsi1+dIdWQV4h+nl/QIOvSh7MwSUaadmQ
# WO4nSC1t/nRbZMO7h+fq07I79TOkovzMSuPPGgskLnVOjW7direPO11vvzU8Y0y+
# 3Bb18XsksBpwziJ4inzdDcGDl5h9HePUe+Pqu0aS9uSyxH5tWTP2z0rZkg==
# SIG # End signature block