tests/Get-WindowsUpdateStatus.Tests.ps1

BeforeAll {
    $modulePath = (Resolve-Path "$PSScriptRoot\..\modules\System.psm1").Path
    Import-Module $modulePath -Force
}

AfterAll {
    Remove-Module System -Force -ErrorAction SilentlyContinue
}

Describe "Get-WindowsUpdateStatus" {
    Context "Successful Update Search" {
        It "returns PSCustomObject with update counts when updates available" {
            InModuleScope System {
                $mockUpdate1 = New-Object PSObject -Property @{
                    Categories = New-Object PSObject -Property @{
                        Name = @('Security Updates')
                    }
                }

                $mockUpdate2 = New-Object PSObject -Property @{
                    Categories = New-Object PSObject -Property @{
                        Name = @('Critical Updates')
                    }
                }

                $mockUpdate3 = New-Object PSObject -Property @{
                    Categories = New-Object PSObject -Property @{
                        Name = @('Updates')
                    }
                }

                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @($mockUpdate1, $mockUpdate2, $mockUpdate3)
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus

                $result | Should -Not -BeNullOrEmpty
                $result.AvailableUpdates | Should -Be 3
                $result.SecurityUpdates | Should -Be 1
                $result.CriticalUpdates | Should -Be 1
                $result.OtherUpdates | Should -Be 1
            }
        }

        It "returns zero counts when no updates available" {
            InModuleScope System {
                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @()
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus

                $result.AvailableUpdates | Should -Be 0
                $result.SecurityUpdates | Should -Be 0
                $result.CriticalUpdates | Should -Be 0
            }
        }

        It "logs search completion message" {
            InModuleScope System {
                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @()
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                Get-WindowsUpdateStatus

                Assert-MockCalled -CommandName Write-Log -Times 2
            }
        }
    }

    Context "Error Handling" {
        It "logs and throws error when COM object creation fails" {
            InModuleScope System {
                Mock -CommandName New-Object -MockWith {
                    throw [System.Exception]::new("COM object not available")
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                { Get-WindowsUpdateStatus } | Should -Throw "COM object not available"
            }
        }

        It "throws terminating error on exception" {
            InModuleScope System {
                Mock -CommandName New-Object -MockWith {
                    throw [System.Exception]::new("Test error")
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                { Get-WindowsUpdateStatus } | Should -Throw "Test error"
            }
        }
    }

    Context "Update Categorization" {
        It "correctly identifies security updates" {
            InModuleScope System {
                $mockSecurityUpdate = New-Object PSObject -Property @{
                    Categories = New-Object PSObject -Property @{
                        Name = @('Security Updates')
                    }
                }

                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @($mockSecurityUpdate)
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus

                $result.SecurityUpdates | Should -Be 1
                $result.CriticalUpdates | Should -Be 0
                $result.SecurityUpdatesList | Should -Not -BeNullOrEmpty
                @($result.SecurityUpdatesList).Count | Should -Be 1
            }
        }

        It "correctly identifies critical updates" {
            InModuleScope System {
                $mockCriticalUpdate = New-Object PSObject -Property @{
                    Categories = New-Object PSObject -Property @{
                        Name = @('Critical Updates')
                    }
                }

                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @($mockCriticalUpdate)
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus

                $result.CriticalUpdates | Should -Be 1
                $result.SecurityUpdates | Should -Be 0
                $result.CriticalUpdatesList | Should -Not -BeNullOrEmpty
                @($result.CriticalUpdatesList).Count | Should -Be 1
            }
        }

        It "separates other updates from security and critical" {
            InModuleScope System {
                $mockOtherUpdate = New-Object PSObject -Property @{
                    Categories = New-Object PSObject -Property @{
                        Name = @('Updates')
                    }
                }

                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @($mockOtherUpdate)
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus

                $result.OtherUpdates | Should -Be 1
                $result.SecurityUpdates | Should -Be 0
                $result.CriticalUpdates | Should -Be 0
            }
        }
    }

    Context "WhatIf Support" {
        It "supports -WhatIf parameter" {
            InModuleScope System {
                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @()
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus -WhatIf

                $result | Should -Not -BeNullOrEmpty
            }
        }
    }

    Context "Output Format" {
        It "returns object with required properties" {
            InModuleScope System {
                $mockSearchResult = New-Object PSObject -Property @{
                    Updates = @()
                }

                $mockUpdateSearcher = New-Object PSObject
                $mockUpdateSearcher | Add-Member -MemberType ScriptMethod -Name "Search" -Value {
                    param($query)
                    return $mockSearchResult
                }

                $mockUpdateSession = New-Object PSObject
                $mockUpdateSession | Add-Member -MemberType ScriptMethod -Name "CreateUpdateSearcher" -Value {
                    return $mockUpdateSearcher
                }

                Mock -CommandName New-Object -MockWith {
                    param($ComObject)
                    if ($ComObject -eq 'Microsoft.Update.Session') {
                        return $mockUpdateSession
                    }
                    return & $true @args
                } -ParameterFilter { $ComObject -eq 'Microsoft.Update.Session' }

                Mock -CommandName Write-Log

                $result = Get-WindowsUpdateStatus

                $result.PSObject.Properties.Name | Should -Contain 'AvailableUpdates'
                $result.PSObject.Properties.Name | Should -Contain 'SecurityUpdates'
                $result.PSObject.Properties.Name | Should -Contain 'CriticalUpdates'
                $result.PSObject.Properties.Name | Should -Contain 'OtherUpdates'
                $result.PSObject.Properties.Name | Should -Contain 'AllUpdates'
                $result.PSObject.Properties.Name | Should -Contain 'SecurityUpdatesList'
                $result.PSObject.Properties.Name | Should -Contain 'CriticalUpdatesList'
            }
        }
    }
}