Fix-SpeculativeMitigations.Tests.ps1

<#
.SYNOPSIS
    Pester tests for Fix-SpeculativeMitigations.ps1

.DESCRIPTION
    Author: Krishnaramanan.S
    License: No restriction in use or modify
    Tested in Dev Environment only. Test this module in your Dev Environment first before using in Production. User must make changes if required on their own risk.
#>


[CmdletBinding()]
param()

$scriptPath = Join-Path $PSScriptRoot 'Fix-SpeculativeMitigations.ps1'

Describe 'Fix-SpeculativeMitigations.ps1' {

    BeforeAll {
        . $scriptPath
    }

    Context 'Presets and mappings' {

        It 'has exactly 3 mitigation levels' {
            if ($Presets.Count -ne 3) { throw "Expected 3 presets, got $($Presets.Count)" }
        }

        It 'Level 1 has expected values' {
            $preset = $Presets.Values | Where-Object { $_.Description -like '*Level 1*' } | Select-Object -First 1
            if (-not $preset) { throw "Level 1 preset not found" }
            if ($preset.OverrideMask -ne 3) { throw "Level 1 OverrideMask mismatch" }
            if ($preset.Override -ne 0x00800048) { throw "Level 1 Override mismatch: expected 0x00800048, got 0x$($preset.Override.ToString('X8'))" }
        }

        It 'Level 2 has expected values' {
            $preset = $Presets.Values | Where-Object { $_.Description -like '*Level 2*' } | Select-Object -First 1
            if (-not $preset) { throw "Level 2 preset not found" }
            if ($preset.OverrideMask -ne 3) { throw "Level 2 OverrideMask mismatch" }
            if ($preset.Override -ne 0x00802048) { throw "Level 2 Override mismatch: expected 0x00802048, got 0x$($preset.Override.ToString('X8'))" }
        }

        It 'Level 3 has expected values' {
            $preset = $Presets.Values | Where-Object { $_.Description -like '*Level 3*' } | Select-Object -First 1
            if (-not $preset) { throw "Level 3 preset not found" }
            if ($preset.OverrideMask -ne 3) { throw "Level 3 OverrideMask mismatch" }
            if ($preset.Override -ne 0x00800000) { throw "Level 3 Override mismatch: expected 0x00800000, got 0x$($preset.Override.ToString('X8'))" }
        }

        It 'maps all expected CVEs to bits' {
            $expectedCves = @(
                'CVE-2017-5715','CVE-2017-5754','CVE-2018-3639',
                'CVE-2018-3620','CVE-2018-11091','CVE-2018-12126',
                'CVE-2018-12127','CVE-2018-12130','CVE-2022-21123',
                'CVE-2022-21125','CVE-2022-0001','CVE-2022-23825'
            )
            foreach ($cve in $expectedCves) {
                if ($CveToBit.Keys -notcontains $cve) { throw "Missing CVE mapping: $cve" }
            }
        }

        It 'does not map OS-only CVEs to registry bits' {
            $osOnlyCves = @(
                'CVE-2022-21127','CVE-2022-21166',
                'CVE-2023-20569','CVE-2023-20588',
                'CVE-2023-28746','CVE-2022-40982'
            )
            foreach ($cve in $osOnlyCves) {
                if ($CveToBit.Keys -contains $cve) { throw "Unexpected CVE mapping: $cve" }
            }
        }
    }

    Context 'Test-Administrator' {
        It 'returns a boolean' {
            $result = Test-Administrator
            if ($result.GetType().Name -ne 'Boolean') { throw "Expected Boolean, got $($result.GetType().Name)" }
        }
    }

    Context 'Get-RegistryValue' {
        It 'returns null for missing value' {
            $result = Get-RegistryValue -Name 'SpeculativeMitigation_DoesNotExist_12345'
            if ($null -ne $result) { throw "Expected null for missing registry value" }
        }
    }

    Context 'Set-RegistryDword' {
        $script:testName = 'SpecMit_PesterTest'

        It 'sets and reads back a DWORD' {
            if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
                Write-Warning 'Skipping registry write test - not running as Administrator'
                return
            }
            $set = Set-RegistryDword -Name $script:testName -Value 0x12345678
            if (-not $set) { throw "Failed to set registry value" }
            $read = Get-RegistryValue -Name $script:testName
            if ($read -ne 0x12345678) { throw "Read back mismatch: expected 0x12345678, got $read" }
        }

        AfterEach {
            if ($script:testName) {
                Remove-ItemProperty -Path $RegistryPath -Name $script:testName -ErrorAction SilentlyContinue
            }
        }
    }

    Context 'Test-HyperThreadingEnabled' {
        It 'returns a boolean' {
            $result = Test-HyperThreadingEnabled
            if ($result.GetType().Name -ne 'Boolean') { throw "Expected Boolean, got $($result.GetType().Name)" }
        }
    }

    Context 'Get-InstalledKbNumbers' {
        It 'returns an array' {
            $kbs = Get-InstalledKbNumbers
            if ($kbs.GetType().Name -ne 'Object[]') { throw "Expected array, got $($kbs.GetType().Name)" }
        }
    }

    Context 'Test-KbInstalled' {
        It 'returns a boolean for any KB string' {
            $result = Test-KbInstalled -kbNumber 'KB9999999'
            if ($result.GetType().Name -ne 'Boolean') { throw "Expected Boolean, got $($result.GetType().Name)" }
        }
    }

    Context 'Test-PendingReboot' {
        It 'returns a boolean' {
            $result = Test-PendingReboot
            if ($result.GetType().Name -ne 'Boolean') { throw "Expected Boolean, got $($result.GetType().Name)" }
        }
    }

    Context 'Test-DiskSpace' {
        It 'returns false when given an absurdly high requirement' {
            $result = Test-DiskSpace -MbRequired 999999999
            if ($result -ne $false) { throw "Expected false for impossible disk space requirement" }
        }
    }

    Context 'Get-CveRecommendation' {
        It 'returns a non-empty string for known CVE' {
            $rec = Get-CveRecommendation -CveId 'CVE-2017-5715'
            if ($rec -notmatch 'Registry') { throw "Expected Registry recommendation, got: $rec" }
        }

        It 'returns guidance string for OS-only CVE' {
            $rec = Get-CveRecommendation -CveId 'CVE-2022-21127'
            if ($rec -notmatch 'KB') { throw "Expected KB recommendation, got: $rec" }
        }

        It 'returns fallback for unknown CVE' {
            $rec = Get-CveRecommendation -CveId 'CVE-9999-9999'
            if ($rec -notmatch 'Unknown') { throw "Expected Unknown fallback, got: $rec" }
        }
    }

    Context 'Get-VulnerableCvesFromXml' {
        It 'returns only vulnerable CVEs from XML' {
            $xmlPath = Join-Path $env:TEMP 'SpeculativeMitigation_PesterTest.xml'
            $xmlContent = @'
<?xml version="1.0" encoding="utf-8"?>
<SpeculativeMitigationReport>
  <Mitigations>
    <CVE Name="CVE-2017-5715 (BTI)" Status="Mitigated">
      <HardwarePresent>True</HardwarePresent>
      <WindowsSupportEnabled>True</WindowsSupportEnabled>
    </CVE>
    <CVE Name="CVE-2022-21127 (SRBDS Update)" Status="Vulnerable - Update Required">
      <RequiredKB>KB5015018</RequiredKB>
      <MinimumBuild>20348</MinimumBuild>
      <CurrentBuild>19045</CurrentBuild>
    </CVE>
    <CVE Name="CVE-2018-3639 (SSBD)" Status="Not Vulnerable">
      <HardwarePresent>False</HardwarePresent>
      <OSEnabledSystemWide>False</OSEnabledSystemWide>
    </CVE>
  </Mitigations>
</SpeculativeMitigationReport>
'@

            Set-Content -Path $xmlPath -Value $xmlContent -Encoding UTF8
            try {
                $vulns = Get-VulnerableCvesFromXml -XmlPath $xmlPath
                if ($vulns -isnot [array]) { throw "Expected array, got $($vulns.GetType().FullName)" }
                if ($vulns.Count -ne 1) { throw "Expected 1 vulnerable CVE, got $($vulns.Count)" }
                if ($vulns[0].CveName -ne 'CVE-2022-21127 (SRBDS Update)') { throw "CVE name mismatch" }
                if ($vulns[0].RequiredKB -ne 'KB5015018') { throw "RequiredKB mismatch" }
            }
            finally {
                Remove-Item $xmlPath -ErrorAction SilentlyContinue
            }
        }

        It 'returns empty array for missing file' {
            $vulns = Get-VulnerableCvesFromXml -XmlPath 'Z:\DoesNotExist.xml'
            if ($vulns -isnot [array]) { throw "Expected array for missing file, got $($vulns.GetType().FullName)" }
            if ($vulns.Count -ne 0) { throw "Expected 0 results for missing file, got $($vulns.Count)" }
        }
    }
}