Tests/Private/Install.Tests.ps1

BeforeDiscovery {
    Import-Module (Resolve-Path "$PSScriptRoot\..\..\Posh-YBKPIV.psd1") -Force
}

BeforeAll {
    Import-Module (Resolve-Path "$PSScriptRoot\..\..\Posh-YBKPIV.psd1") -Force
}

AfterAll {
    Remove-Module Posh-YBKPIV -ErrorAction SilentlyContinue
}

InModuleScope Posh-YBKPIV {

    Describe 'Test-PWSHYBKPIVElevated' -Tag Unit {
        It 'Returns a boolean' {
            Test-PWSHYBKPIVElevated | Should -BeOfType [bool]
        }
    }

    Describe 'Resolve-PWSHYBKPIVArchitecture' -Tag Unit {
        It 'Passes through "win64" unchanged' {
            Resolve-PWSHYBKPIVArchitecture -Architecture 'win64' | Should -Be 'win64'
        }

        It 'Passes through "win32" unchanged' {
            Resolve-PWSHYBKPIVArchitecture -Architecture 'win32' | Should -Be 'win32'
        }

        It 'Resolves "auto" based on the current OS bitness' {
            if ([Environment]::Is64BitOperatingSystem) {
                Resolve-PWSHYBKPIVArchitecture -Architecture 'auto' | Should -Be 'win64'
            } else {
                Resolve-PWSHYBKPIVArchitecture -Architecture 'auto' | Should -Be 'win32'
            }
        }

        It 'Throws on an unrecognized value' {
            { Resolve-PWSHYBKPIVArchitecture -Architecture 'arm64' } | Should -Throw -ExpectedMessage '*arm64*'
        }
    }

    Describe 'Get-PWSHYBKPIVInstallPath' -Tag Unit {
        BeforeEach {
            $script:Installation = [PSCustomObject]@{
                installPath        = 'C:\Program Files\Yubico\Yubico PIV Tool\bin\yubico-piv-tool.exe'
                installPathWow6432 = 'C:\Program Files (x86)\Yubico\Yubico PIV Tool\bin\yubico-piv-tool.exe'
            }
        }

        It 'Returns installPath for win64' {
            Get-PWSHYBKPIVInstallPath -Installation $script:Installation -Architecture 'win64' | Should -Be $script:Installation.installPath
        }

        It 'Returns installPathWow6432 for win32' {
            Get-PWSHYBKPIVInstallPath -Installation $script:Installation -Architecture 'win32' | Should -Be $script:Installation.installPathWow6432
        }
    }

    Describe 'Get-PWSHYBKPIVDownloadUrl' -Tag Unit {
        It 'Substitutes {version} and {arch} in the URL template' {
            $script:Installation = [PSCustomObject]@{
                urlTemplate = 'https://developers.yubico.com/yubico-piv-tool/Releases/yubico-piv-tool-{version}-{arch}.msi'
                version     = '2.7.3'
            }
            $result = Get-PWSHYBKPIVDownloadUrl -Installation $installation -Architecture 'win64'
            $result | Should -Be 'https://developers.yubico.com/yubico-piv-tool/Releases/yubico-piv-tool-2.7.3-win64.msi'
        }
    }

    Describe 'Save-PWSHYBKPIVDownloadFile' -Tag Unit {
        It 'Forces TLS 1.2 and calls Invoke-WebRequest with -UseBasicParsing' {
            Mock Invoke-WebRequest {}
            $originalProtocol = [Net.ServicePointManager]::SecurityProtocol
            try {
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls
                Save-PWSHYBKPIVDownloadFile -Uri 'https://example.test/file.msi' -OutFile 'C:\Temp\file.msi'
                ([Net.ServicePointManager]::SecurityProtocol -band [Net.SecurityProtocolType]::Tls12) | Should -Be ([Net.SecurityProtocolType]::Tls12)
                Should -Invoke Invoke-WebRequest -Times 1 -ParameterFilter {
                    $Uri -eq 'https://example.test/file.msi' -and $OutFile -eq 'C:\Temp\file.msi' -and $UseBasicParsing
                }
            } finally {
                [Net.ServicePointManager]::SecurityProtocol = $originalProtocol
            }
        }
    }

    Describe 'Invoke-PWSHYBKPIVMsiInstall' -Tag Unit {
        It 'Does not throw when msiexec exits with code 0' {
            Mock Start-Process { [PSCustomObject]@{ ExitCode = 0 } }
            { Invoke-PWSHYBKPIVMsiInstall -MsiPath 'C:\Temp\test.msi' } | Should -Not -Throw
        }

        It 'Does not throw when msiexec exits with code 3010 (reboot required)' {
            Mock Start-Process { [PSCustomObject]@{ ExitCode = 3010 } }
            { Invoke-PWSHYBKPIVMsiInstall -MsiPath 'C:\Temp\test.msi' } | Should -Not -Throw
        }

        It 'Throws when msiexec exits with a non-success code' {
            Mock Start-Process { [PSCustomObject]@{ ExitCode = 1603 } }
            { Invoke-PWSHYBKPIVMsiInstall -MsiPath 'C:\Temp\test.msi' } | Should -Throw -ExpectedMessage '*1603*'
        }

        It 'Invokes msiexec.exe with /i, /quiet, and /norestart' {
            Mock Start-Process { [PSCustomObject]@{ ExitCode = 0 } }
            Invoke-PWSHYBKPIVMsiInstall -MsiPath 'C:\Temp\test.msi'
            Should -Invoke Start-Process -Times 1 -ParameterFilter {
                $FilePath -eq 'msiexec.exe' -and ($ArgumentList -join ' ') -like '*"C:\Temp\test.msi"*' -and
                ($ArgumentList -contains '/quiet') -and ($ArgumentList -contains '/norestart')
            }
        }
    }
}