public/WinOS/Deploy-OSDCloud.Tests.ps1

BeforeAll {
    $moduleRoot = Join-Path $PSScriptRoot '..\..'
    $testModule = New-Module -Name DiskParameterDeploymentTests -ArgumentList $moduleRoot -ScriptBlock {
        param ($ModuleRoot)
        . "$ModuleRoot\public\WinOS\Deploy-OSDCloud.ps1"
        . "$ModuleRoot\private\Initialize-DeployOSDCloud.ps1"
        . "$ModuleRoot\private\Update-OSDCloudCoreCatalogOS.ps1"
        . "$ModuleRoot\private\core-license\Get-OSDCoreLicense.ps1"
        . "$ModuleRoot\private\core-license\Test-OSDCoreLicense.ps1"
        . "$ModuleRoot\private\core\Convert-KeyboardLayoutToLanguageCode.ps1"
        function Get-OSDCloudWorkflowRuntimeParameter {
            [CmdletBinding()]
            param ($ModuleBase, $WorkflowName, $ProcessorArchitecture)
            $parameters = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
            $attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
            $attributes.Add([System.Management.Automation.ParameterAttribute]::new())
            $parameters.Add('OSLanguageCode', [System.Management.Automation.RuntimeDefinedParameter]::new('OSLanguageCode', [string], $attributes))
            return $parameters
        }
        function Initialize-OSDCoreLicense { [CmdletBinding()] param () }
        function Initialize-OSDCoreDevice { [CmdletBinding()] param () }
        function Invoke-OSDCloudWorkflowTask { [CmdletBinding()] param () }
        function Invoke-OSDCloudWorkflowUI { [CmdletBinding()] param ($WorkflowName) }
    }
    Import-Module $testModule
    $originalDevice = $global:OSDCoreDevice
    $originalDeploy = $global:OSDCloudDeploy
    $originalLicense = $global:OSDCoreLicense
}

AfterAll {
    Remove-Module DiskParameterDeploymentTests
    $global:OSDCoreDevice = $originalDevice
    $global:OSDCloudDeploy = $originalDeploy
    $global:OSDCoreLicense = $originalLicense
}

Describe 'Deploy-OSDCloud disk parameters' {
    BeforeEach {
        $global:OSDCoreDevice = [pscustomobject]@{
            OSDManufacturer = 'HP'; OSDModel = 'Test'; OSDProduct = 'Test'
            ProcessorArchitecture = 'amd64'; KeyboardLayout = $null
        }
        $global:OSDCoreLicense = [pscustomobject]@{ IsRegistered = $true }
        $global:OSDCloudDeploy = [pscustomobject]@{ TimeStart = $null }
        Mock Get-OSDCoreLicense {
            if ($global:OSDCoreLicense.IsIncognito -ne $true) { [pscustomobject]@{ IsValid = $true } }
        } -ModuleName DiskParameterDeploymentTests
        Mock Convert-KeyboardLayoutToLanguageCode { 'fr-FR' } -ModuleName DiskParameterDeploymentTests
        Mock Initialize-OSDCoreLicense {} -ModuleName DiskParameterDeploymentTests
        Mock Initialize-OSDCoreDevice {} -ModuleName DiskParameterDeploymentTests
        Mock Initialize-DeployOSDCloud {} -ModuleName DiskParameterDeploymentTests
        Mock Invoke-OSDCloudWorkflowTask {} -ModuleName DiskParameterDeploymentTests
        Mock Invoke-OSDCloudWorkflowUI {} -ModuleName DiskParameterDeploymentTests
        Mock Export-Clixml {} -ModuleName DiskParameterDeploymentTests
        Mock Out-File {} -ModuleName DiskParameterDeploymentTests
        Mock Test-Path { $true } -ModuleName DiskParameterDeploymentTests
        Mock Remove-Item {} -ModuleName DiskParameterDeploymentTests
        Mock Write-Host {} -ModuleName DiskParameterDeploymentTests
        Mock Out-Host {} -ModuleName DiskParameterDeploymentTests
    }

    It 'forwards all explicit disk parameters for CLI and GUI launches: CLI=<CLI>' -TestCases @(
        @{ CLI = $true }
        @{ CLI = $false }
    ) {
        param ($CLI)
        Deploy-OSDCloud -CLI:$CLI -DiskNumber 0 -DiskLabelOS Windows -DiskLabelRecovery WinRE `
            -DiskSizeSystem 1GB -DiskSizeOS 32GB -DiskSizeRecovery 3GB
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter {
            $DiskNumber -eq 0 -and $DiskLabelOS -eq 'Windows' -and $DiskLabelRecovery -eq 'WinRE' -and
            $DiskSizeSystem -eq 1GB -and $DiskSizeOS -eq 32GB -and $DiskSizeRecovery -eq 3GB
        }
    }

    It 'does not bind omitted disk parameters in the initializer' {
        Deploy-OSDCloud -CLI
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter {
            -not $PesterBoundParameters.ContainsKey('DiskSizeOS') -and
            -not $PesterBoundParameters.ContainsKey('DiskLabelOS') -and
            -not $PesterBoundParameters.ContainsKey('DiskSizeSystem') -and
            -not $PesterBoundParameters.ContainsKey('DiskSizeRecovery')
        }
    }

    It 'forces unregistered state before device and deployment initialization: CLI=<CLI>' -TestCases @(
        @{ CLI = $true }
        @{ CLI = $false }
    ) {
        param ($CLI)
        $savedLicense = [pscustomobject]@{ Email = 'test@example.invalid'; LicenseGuid = 'test-license' }
        Mock Initialize-OSDCoreLicense {
            $global:OSDCoreLicense = [pscustomobject]@{ IsRegistered = $true; License = $savedLicense }
        } -ModuleName DiskParameterDeploymentTests
        Mock Initialize-OSDCoreDevice {
            $global:OSDCoreLicense.IsRegistered | Should -BeFalse
            $global:OSDCoreLicense.IsRegistered = $true
        } -ModuleName DiskParameterDeploymentTests
        Mock Initialize-DeployOSDCloud {
            $global:OSDCoreLicense.IsRegistered | Should -BeFalse
        } -ModuleName DiskParameterDeploymentTests
        Deploy-OSDCloud -CLI:$CLI -Incognito
        $global:OSDCoreLicense.IsRegistered | Should -BeFalse
        $global:OSDCoreLicense.License | Should -Be $savedLicense
        Should -Invoke Initialize-OSDCoreLicense -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly
    }

    It 'reloads normal registration on the next launch without Incognito' {
        Mock Initialize-OSDCoreLicense {
            $global:OSDCoreLicense = [pscustomobject]@{ IsRegistered = $true }
        } -ModuleName DiskParameterDeploymentTests
        Deploy-OSDCloud -CLI -Incognito
        $global:OSDCoreLicense.IsRegistered | Should -BeFalse
        $global:OSDCoreLicense.IsIncognito | Should -BeTrue
        Deploy-OSDCloud -CLI
        $global:OSDCoreLicense.IsRegistered | Should -BeTrue
        $global:OSDCoreLicense.IsIncognito | Should -BeFalse
        Should -Invoke Initialize-OSDCoreLicense -ModuleName DiskParameterDeploymentTests -Times 2 -Exactly
    }

    It 'does not override normal registration when Incognito is explicitly false' {
        Deploy-OSDCloud -CLI -Incognito:$false
        $global:OSDCoreLicense.IsRegistered | Should -BeTrue
        $global:OSDCoreLicense.IsIncognito | Should -BeFalse
    }

    It 'skips real OS catalog updates with no discovery during Incognito: CLI=<CLI>' -TestCases @(
        @{ CLI = $true }
        @{ CLI = $false }
    ) {
        param ($CLI)
        Mock Invoke-RestMethod { throw 'Unexpected catalog request' } -ModuleName DiskParameterDeploymentTests
        Mock Invoke-WebRequest { throw 'Unexpected catalog download' } -ModuleName DiskParameterDeploymentTests
        Mock Get-Volume { throw 'Unexpected USB discovery' } -ModuleName DiskParameterDeploymentTests
        Mock Get-ChildItem { throw 'Unexpected license/catalog discovery' } -ModuleName DiskParameterDeploymentTests
        Mock Initialize-DeployOSDCloud {
            $global:OSDCoreLicense.IsIncognito | Should -BeTrue
            Get-OSDCoreLicense | Should -BeNullOrEmpty
            Update-OSDCloudCoreCatalogOS | Should -BeNullOrEmpty
        } -ModuleName DiskParameterDeploymentTests
        Deploy-OSDCloud -CLI:$CLI -Incognito -WarningVariable warnings
        @($warnings).Count | Should -Be 0
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly
        Should -Invoke Invoke-RestMethod -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Invoke-WebRequest -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Get-Volume -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Get-ChildItem -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Write-Host -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly -ParameterFilter {
            $Object -like '*updating the OS catalog*' -or $Object -like '*OS catalog saved to*'
        }
    }

    It 'skips registered keyboard language conversion in Incognito mode' {
        $global:OSDCoreDevice.KeyboardLayout = '00000409'
        Deploy-OSDCloud -CLI -Incognito
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter { $OSLanguageCode -eq 'en-US' }
        Should -Invoke Convert-KeyboardLayoutToLanguageCode -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
    }

    It 'infers language with a valid unregistered license: CLI=<CLI>, override=<Override>' -TestCases @(
        @{ CLI = $true; Override = $false }
        @{ CLI = $false; Override = $false }
        @{ CLI = $true; Override = $true }
        @{ CLI = $false; Override = $true }
    ) {
        param ($CLI, $Override)
        $global:OSDCoreLicense.IsRegistered = $false
        $global:OSDCoreDevice.KeyboardLayout = '0000040C'
        $parameters = @{ CLI = $CLI }
        if ($Override) { $parameters.KeyboardLayout = '0000040C' }
        Deploy-OSDCloud @parameters -WarningVariable warnings
        @($warnings).Count | Should -Be 0
        Should -Invoke Convert-KeyboardLayoutToLanguageCode -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter {
            $KeyboardLayout -eq '0000040C' -and $FallbackLanguageCode -eq 'en-US'
        }
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter { $OSLanguageCode -eq 'fr-FR' }
        Should -Invoke Write-Host -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter {
            $Object -like '*has set the OSLanguageCode to fr-FR*'
        }
    }

    It 'quietly skips registered language inference for <Case>: CLI=<CLI>' -TestCases @(
        @{ CLI = $true; Case = 'missing' }
        @{ CLI = $false; Case = 'missing' }
        @{ CLI = $true; Case = 'invalid' }
        @{ CLI = $false; Case = 'invalid' }
        @{ CLI = $true; Case = 'registration only' }
        @{ CLI = $false; Case = 'registration only' }
        @{ CLI = $true; Case = 'failed lookup' }
        @{ CLI = $false; Case = 'failed lookup' }
    ) {
        param ($CLI, $Case)
        $global:OSDCoreDevice.KeyboardLayout = '0000040C'
        Mock Get-OSDCoreLicense {
            switch ($Case) {
                'missing' { $null }
                'invalid' { [pscustomobject]@{ IsValid = $false } }
                'registration only' { [pscustomobject]@{ IsRegistered = $true } }
                'failed lookup' { throw 'synthetic-private-license-data' }
            }
        } -ModuleName DiskParameterDeploymentTests
        $output = @(Deploy-OSDCloud -CLI:$CLI -KeyboardLayout '0000040C' -Verbose -WarningVariable warnings 4>&1 3>&1 2>&1)
        @($warnings).Count | Should -Be 0
        @($output | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }).Count | Should -Be 0
        "$output" | Should -Not -Match 'synthetic-private-license-data'
        Should -Invoke Convert-KeyboardLayoutToLanguageCode -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter { $OSLanguageCode -eq 'en-US' }
        Should -Invoke Write-Host -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly -ParameterFilter {
            $Object -like '*has set the OSLanguageCode*'
        }
    }

    It 'preserves explicit language selection without checking a license: CLI=<CLI>' -TestCases @(
        @{ CLI = $true }
        @{ CLI = $false }
    ) {
        param ($CLI)
        $global:OSDCoreDevice.KeyboardLayout = '0000040C'
        Mock Get-OSDCoreLicense { throw 'Unexpected license lookup' } -ModuleName DiskParameterDeploymentTests
        Deploy-OSDCloud -CLI:$CLI -OSLanguageCode 'de-de' -KeyboardLayout '0000040C'
        Should -Invoke Get-OSDCoreLicense -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Convert-KeyboardLayoutToLanguageCode -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter { $OSLanguageCode -eq 'de-de' }
    }

    It 'keeps en-US when the keyboard layout is unavailable with a valid license' {
        Deploy-OSDCloud -CLI
        Should -Invoke Convert-KeyboardLayoutToLanguageCode -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 1 -Exactly -ParameterFilter { $OSLanguageCode -eq 'en-US' }
    }

    It 'surfaces licensed conversion failures instead of silently using a fallback' {
        $global:OSDCoreDevice.KeyboardLayout = '0000040C'
        Mock Convert-KeyboardLayoutToLanguageCode { throw 'Synthetic conversion failure' } -ModuleName DiskParameterDeploymentTests
        { Deploy-OSDCloud -CLI } | Should -Throw '*Synthetic conversion failure*'
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
    }

    It 'documents every new parameter in the bundled external help' {
        [xml]$helpXml = Get-Content -LiteralPath "$moduleRoot\en-US\OSDCloud-help.xml" -Raw
        $namespaces = [System.Xml.XmlNamespaceManager]::new($helpXml.NameTable)
        $namespaces.AddNamespace('command', 'http://schemas.microsoft.com/maml/dev/command/2004/10')
        $namespaces.AddNamespace('maml', 'http://schemas.microsoft.com/maml/2004/10')
        $deployHelp = $helpXml.SelectSingleNode("//command:command[command:details/command:name='Deploy-OSDCloud']", $namespaces)
        $description = $deployHelp.SelectSingleNode('maml:description', $namespaces).InnerText
        $description | Should -Match 'valid Recast license.*Test-OSDCoreLicense.*IsValid = true'
        $description | Should -Not -Match 'require Initialize-OSDCoreLicense to report IsRegistered'
        foreach ($name in 'Incognito', 'DiskLabelOS', 'DiskLabelRecovery', 'DiskSizeSystem', 'DiskSizeOS', 'DiskSizeRecovery') {
            $deployHelp.SelectNodes("command:parameters/command:parameter[maml:name='$name']", $namespaces).Count | Should -Be 1
            $deployHelp.SelectNodes("command:syntax/command:syntaxItem/command:parameter[maml:name='$name']", $namespaces).Count | Should -Be 1
        }
    }

    It 'rejects invalid public input before initialization: <Name>' -TestCases @(
        @{ Name = 'DiskLabelOS'; Value = ' ' }
        @{ Name = 'DiskLabelRecovery'; Value = "bad`nlabel" }
        @{ Name = 'DiskSizeSystem'; Value = 99MB }
        @{ Name = 'DiskSizeOS'; Value = 0 }
        @{ Name = 'DiskSizeOS'; Value = 32GB - 1 }
        @{ Name = 'DiskSizeRecovery'; Value = 349MB }
    ) {
        param ($Name, $Value)
        $parameters = @{ $Name = $Value }
        { Deploy-OSDCloud -CLI @parameters } | Should -Throw
        Should -Invoke Initialize-DeployOSDCloud -ModuleName DiskParameterDeploymentTests -Times 0 -Exactly
    }
}