tests/Multi-Target.Tests.ps1

#Requires -Version 7
#Requires -Modules Pester

BeforeAll {
    Import-Module (Join-Path $PSScriptRoot '../CpmfUipsPack.psd1') -Force
}

Describe 'Get-CpmfUipsToolPaths — version family branching' {

    Context '23.x classic path' {
        It 'returns extracted\tools\uipcli.exe for 23.x version' {
            InModuleScope CpmfUipsPack -Parameters @{ tb = 'C:\fake\tools' } {
                param($tb)
                $p = Get-CpmfUipsToolPaths -CliVersion '23.10.2.6' -ToolBase $tb
                $p.UipcliExe    | Should -BeLike '*\extracted\tools\uipcli.exe'
                $p.Generation   | Should -Be 'classic'
                $p.DotnetMarker | Should -Match 'Microsoft\.WindowsDesktop\.App\\6\.0\.36'
            }
        }
    }

    Context '25.x dotnet-tool path' {
        It 'returns uipcli.exe directly in CliToolDir for 25.x version' {
            InModuleScope CpmfUipsPack -Parameters @{ tb = 'C:\fake\tools' } {
                param($tb)
                $p = Get-CpmfUipsToolPaths -CliVersion '25.10.11' -ToolBase $tb
                $p.UipcliExe    | Should -BeLike "*\uipcli-25.10.11\uipcli.exe"
                $p.UipcliExe    | Should -Not -BeLike '*\extracted\*'
                $p.Generation   | Should -Be 'dotnet-tool'
                $p.DotnetMarker | Should -BeLike '*dotnet8\sdk'
            }
        }
    }
}

Describe 'Install-CpmfUipsPackCommandLineTool — version family routing' {

    BeforeEach {
        $script:tmpToolBase = Join-Path ([System.IO.Path]::GetTempPath()) "CpmfUipsPackTest-$(New-Guid)"
    }

    AfterEach {
        Remove-Item $script:tmpToolBase -Recurse -Force -ErrorAction SilentlyContinue
    }

    Context '23.x skips when already installed' {
        It 'does not download when both marker and exe exist' {
            InModuleScope CpmfUipsPack -Parameters @{ tb = $script:tmpToolBase } {
                param($tb)
                $p = Get-CpmfUipsToolPaths -CliVersion '23.10.2.6' -ToolBase $tb
                $null = New-Item -ItemType Directory -Path (Split-Path $p.DotnetMarker) -Force
                $null = New-Item -ItemType File      -Path $p.DotnetMarker              -Force
                $null = New-Item -ItemType Directory -Path (Split-Path $p.UipcliExe)    -Force
                $null = New-Item -ItemType File      -Path $p.UipcliExe                 -Force
                Mock Invoke-WebRequest { throw 'Should not be called' }
                { Install-CpmfUipsPackCommandLineTool -CliVersion '23.10.2.6' -ToolBase $tb } | Should -Not -Throw
                Should -Invoke Invoke-WebRequest -Times 0
            }
        }
    }

    Context '25.x skips when already installed' {
        It 'does not call dotnet when uipcli.exe already exists' {
            InModuleScope CpmfUipsPack -Parameters @{ tb = $script:tmpToolBase } {
                param($tb)
                $p = Get-CpmfUipsToolPaths -CliVersion '25.10.11' -ToolBase $tb
                $null = New-Item -ItemType Directory -Path (Split-Path $p.UipcliExe) -Force
                $null = New-Item -ItemType File      -Path $p.UipcliExe               -Force
                # Create dotnet.exe stub so prerequisite check passes
                $null = New-Item -ItemType Directory -Path $p.DotnetDir -Force
                $null = New-Item -ItemType File      -Path (Join-Path $p.DotnetDir 'dotnet.exe') -Force
                Mock Invoke-WebRequest { throw 'Should not be called' }
                { Install-CpmfUipsPackCommandLineTool -CliVersion '25.10.11' -ToolBase $tb } | Should -Not -Throw
                Should -Invoke Invoke-WebRequest -Times 0
            }
        }
    }
}

Describe 'Invoke-CpmfUipsPack — multi-target' {

    BeforeEach {
        $script:tmpRoot     = Join-Path ([System.IO.Path]::GetTempPath()) "CpmfUipsPackTest-$(New-Guid)"
        $script:tmpFeed     = Join-Path ([System.IO.Path]::GetTempPath()) "CpmfUipsFeed-$(New-Guid)"
        $script:projectJson = Join-Path $script:tmpRoot 'project.json'
        $null = New-Item -ItemType Directory -Path $script:tmpRoot -Force
        Set-Content $script:projectJson '{"projectVersion": "1.0.0", "name": "TestProject"}'
    }

    AfterEach {
        Remove-Item $script:tmpRoot -Recurse -Force -ErrorAction SilentlyContinue
        Remove-Item $script:tmpFeed -Recurse -Force -ErrorAction SilentlyContinue
    }

    Context 'Single target (backward compat)' {
        It 'calls Install-CpmfUipsPackCommandLineTool once and returns one path' {
            InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
                param($pj, $feed)
                Mock Install-CpmfUipsPackCommandLineTool { }
                Mock Get-CpmfUipsToolPaths { @{ UipcliExe = 'fake.exe' } }
                Mock Invoke-WithFileLock { param($LockFile, $ScriptBlock); & $ScriptBlock }
                Mock Invoke-PackAndStage { 'C:\feed\TestProject.1.1.0.nupkg' }

                $result = @(Invoke-CpmfUipsPack -ProjectJson $pj -FeedPath $feed -Targets net6 -SkipInstall)
                $result.Count | Should -Be 1
                $result[0]    | Should -BeLike '*.nupkg'
                Should -Invoke Invoke-PackAndStage -Times 1
            }
        }
    }

    Context 'Multi-target' {
        It 'calls Install-CpmfUipsPackCommandLineTool and Invoke-PackAndStage once per target' {
            InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
                param($pj, $feed)
                Mock Install-CpmfUipsPackCommandLineTool { }
                Mock Get-CpmfUipsToolPaths { @{ UipcliExe = 'fake.exe' } }
                Mock Invoke-WithFileLock { param($LockFile, $ScriptBlock); & $ScriptBlock }
                $script:callCount = 0
                Mock Invoke-PackAndStage {
                    $script:callCount++
                    "C:\feed\TestProject.1.1.0.net$script:callCount.nupkg"
                }

                $result = Invoke-CpmfUipsPack -ProjectJson $pj -FeedPath $feed -Targets net6,net8 -SkipInstall
                $result.Count | Should -Be 2
                Should -Invoke Invoke-PackAndStage -Times 2
            }
        }

        It 'second target call passes -NoBump (version bumped by first target only)' {
            InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
                param($pj, $feed)
                Mock Install-CpmfUipsPackCommandLineTool { }
                Mock Get-CpmfUipsToolPaths { @{ UipcliExe = 'fake.exe' } }
                Mock Invoke-WithFileLock { param($LockFile, $ScriptBlock); & $ScriptBlock }
                $script:noBumpValues = @()
                Mock Invoke-PackAndStage {
                    param($ProjectJson, $FeedPath, $UipcliArgs, $NoBump, $UipcliExe, $TargetTag)
                    $script:noBumpValues += $NoBump.IsPresent
                    "C:\feed\fake.$TargetTag.nupkg"
                }

                Invoke-CpmfUipsPack -ProjectJson $pj -FeedPath $feed -Targets net6,net8 -SkipInstall | Out-Null
                $script:noBumpValues[0] | Should -Be $false  # first target: bump
                $script:noBumpValues[1] | Should -Be $true   # second target: no bump
            }
        }

        It '-MultiTfm calls Invoke-MultiTfmMerge and returns merged path' {
            InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
                param($pj, $feed)
                Mock Install-CpmfUipsPackCommandLineTool { }
                Mock Get-CpmfUipsToolPaths { @{ UipcliExe = 'fake.exe' } }
                Mock Invoke-WithFileLock { param($LockFile, $ScriptBlock); & $ScriptBlock }
                Mock Invoke-PackAndStage {
                    param($ProjectJson, $FeedPath, $UipcliArgs, $NoBump, $UipcliExe, $TargetTag)
                    "C:\feed\TestProject.1.1.0.$TargetTag.nupkg"
                }
                Mock Invoke-MultiTfmMerge { 'C:\feed\TestProject.1.1.0.nupkg' }

                $result = @(Invoke-CpmfUipsPack -ProjectJson $pj -FeedPath $feed -Targets net6,net8 -MultiTfm -SkipInstall)
                Should -Invoke Invoke-MultiTfmMerge -Times 1
                $result.Count | Should -Be 1
                $result[0]    | Should -Be 'C:\feed\TestProject.1.1.0.nupkg'
            }
        }
    }

    Context 'Deprecated -CliVersion' {
        It 'emits a warning and maps 23.x to CliVersionNet6' {
            InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
                param($pj, $feed)
                Mock Install-CpmfUipsPackCommandLineTool { }
                Mock Get-CpmfUipsToolPaths { @{ UipcliExe = 'fake.exe' } }
                Mock Invoke-WithFileLock { param($LockFile, $ScriptBlock); & $ScriptBlock }
                Mock Invoke-PackAndStage { 'C:\feed\fake.nupkg' }

                { Invoke-CpmfUipsPack -ProjectJson $pj -FeedPath $feed -CliVersion '23.10.2.6' -SkipInstall } |
                    Should -Not -Throw
                # Warning verification: just confirm it runs without error
            }
        }
    }
}

Describe 'Invoke-PackAndStage — TargetTag naming' {

    BeforeEach {
        $script:tmpRoot     = Join-Path ([System.IO.Path]::GetTempPath()) "CpmfUipsPackTest-$(New-Guid)"
        $script:tmpFeed     = Join-Path ([System.IO.Path]::GetTempPath()) "CpmfUipsFeed-$(New-Guid)"
        $script:projectJson = Join-Path $script:tmpRoot 'project.json'
        $null = New-Item -ItemType Directory -Path $script:tmpRoot -Force
        Set-Content $script:projectJson '{"projectVersion": "1.0.0", "name": "TestProject"}'
    }

    AfterEach {
        Remove-Item $script:tmpRoot -Recurse -Force -ErrorAction SilentlyContinue
        Remove-Item $script:tmpFeed -Recurse -Force -ErrorAction SilentlyContinue
    }

    It 'appends TargetTag to staged filename when tag is non-empty' {
        InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
            param($pj, $feed)
            Mock Invoke-UipcliPack {
                param($UipcliExe, $PackArgs)
                $outDir = $PackArgs[$PackArgs.IndexOf('-o') + 1]
                New-Item -ItemType File -Path (Join-Path $outDir 'TestProject.1.1.0.nupkg') -Force | Out-Null
                return 0
            }
            $result = Invoke-PackAndStage -ProjectJson $pj -FeedPath $feed -UipcliArgs @() -UipcliExe 'fake.exe' -TargetTag 'net6'
            $result | Should -BeLike '*.net6.nupkg'
        }
    }

    It 'uses original filename when TargetTag is empty' {
        InModuleScope CpmfUipsPack -Parameters @{ pj = $script:projectJson; feed = $script:tmpFeed } {
            param($pj, $feed)
            Mock Invoke-UipcliPack {
                param($UipcliExe, $PackArgs)
                $outDir = $PackArgs[$PackArgs.IndexOf('-o') + 1]
                New-Item -ItemType File -Path (Join-Path $outDir 'TestProject.1.1.0.nupkg') -Force | Out-Null
                return 0
            }
            $result = Invoke-PackAndStage -ProjectJson $pj -FeedPath $feed -UipcliArgs @() -UipcliExe 'fake.exe' -TargetTag ''
            $result | Should -BeLike '*TestProject.1.1.0.nupkg'
            $result | Should -Not -BeLike '*.net*.nupkg'
        }
    }
}