# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Describe "SxS Module Path Basic Tests" -tags "CI" {

    BeforeAll {
        if ($IsWindows)
        {
            $powershell = "$PSHOME\pwsh.exe"
            $ProductName = "WindowsPowerShell"
            if ($IsCoreCLR -and ($PSHOME -notlike "*Windows\System32\WindowsPowerShell\v1.0"))
            {
                $ProductName =  "PowerShell"
            }
            $expectedUserPath = Join-Path -Path $HOME -ChildPath "Documents\$ProductName\Modules"
            $expectedSharedPath = Join-Path -Path $env:ProgramFiles -ChildPath "$ProductName\Modules"
            $userConfigPath = "~/Documents/powershell/powershell.config.json"
        }
        else
        {
            $powershell = "$PSHOME/pwsh"
            $expectedUserPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("USER_MODULES")
            $expectedSharedPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("SHARED_MODULES")
            $userConfigPath = "~/.config/powershell/powershell.config.json"
        }

        $userConfigExists = $false
        if (Test-Path $userConfigPath) {
            $userConfigExists = $true
            Copy-Item $userConfigPath "$userConfigPath.backup" -Force -ErrorAction Ignore
        }

        $expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules'

        # Skip these tests in cases when there is no 'pwsh' executable (e.g. when framework dependent PS package is used)
        $skipNoPwsh = -not (Test-Path $powershell)

        if ($IsWindows)
        {
            $expectedWindowsPowerShellPSHomePath = Join-Path $env:windir "System32" "WindowsPowerShell" "v1.0" "Modules"
        }

        ## Setup a fake PSHome
        $fakePSHome = Join-Path -Path $TestDrive -ChildPath 'FakePSHome'
        $fakePSHomeModuleDir = Join-Path -Path $fakePSHome -ChildPath 'Modules'
        $fakePowerShell = Join-Path -Path $fakePSHome -ChildPath (Split-Path -Path $powershell -Leaf)
        $fakePSDepsFile = Join-Path -Path $fakePSHome -ChildPath "pwsh.deps.json"

        New-Item -Path $fakePSHome -ItemType Directory > $null
        New-Item -Path $fakePSHomeModuleDir -ItemType Directory > $null
    }

    AfterAll {
        if ($userConfigExists) {
            Move-Item "$userConfigPath.backup" $userConfigPath -Force -ErrorAction Ignore
        }
        else {
            Remove-Item "$userConfigPath" -Force -ErrorAction Ignore
        }
    }

    BeforeEach {
        $originalModulePath = $env:PSModulePath
    }

    AfterEach {
        $env:PSModulePath = $originalModulePath
    }

    It "validate sxs module path" -Skip:$skipNoPwsh {

        $env:PSModulePath = ""
        $defaultModulePath = & $powershell -nopro -c '$env:PSModulePath'
        $pathSeparator = [System.IO.Path]::PathSeparator

        $paths = $defaultModulePath.Replace("$pathSeparator$pathSeparator", "$pathSeparator") -split $pathSeparator

        if ($IsWindows)
        {
            $expectedPaths = 3 # user, shared, pshome
            $userPaths = [System.Environment]::GetEnvironmentVariable("PSModulePath", [System.EnvironmentVariableTarget]::User)
            $expectedPaths += $userPaths ? $userPaths.Split($pathSeparator).Count : 0
            $machinePaths = [System.Environment]::GetEnvironmentVariable("PSModulePath", [System.EnvironmentVariableTarget]::Machine)
            $expectedPaths += $machinePaths ? $machinePaths.Split($pathSeparator).Count : 0

            $paths.Count | Should -Be $expectedPaths
        }
        else
        {
            $paths.Count | Should -Be 3
        }

        $paths[0].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedUserPath
        $paths[1].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedSharedPath
        $paths[2].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedSystemPath
        $defaultModulePath | Should -BeLike "*$expectedWindowsPowerShellPSHomePath*"
    }

    It "Works with pshome module path derived from a different PowerShell instance" -Skip:(!$IsCoreCLR -or $skipNoPwsh) {

        ## Create 'powershell' and 'pwsh.deps.json' in the fake PSHome folder,
        ## so that the module path calculation logic would believe it's real.
        New-Item -Path $fakePowerShell -ItemType File -Force > $null
        New-Item -Path $fakePSDepsFile -ItemType File -Force > $null

        try {

            ## PSHome module path derived from another PowerShell instance should be ignored
            $env:PSModulePath = $fakePSHomeModuleDir
            $newModulePath = & $powershell -nopro -c '$env:PSModulePath'
            $paths = $newModulePath -split [System.IO.Path]::PathSeparator
            $paths.Count | Should -Be 4
            $paths[0] | Should -Be $expectedUserPath
            $paths[1] | Should -Be $expectedSharedPath
            $paths[2] | Should -Be $expectedSystemPath
            $paths[3] | Should -Be $fakePSHomeModuleDir
        } finally {

            ## Remove 'powershell' and 'pwsh.deps.json' from the fake PSHome folder
            Remove-Item -Path $fakePowerShell -Force -ErrorAction SilentlyContinue
            Remove-Item -Path $fakePSDepsFile -Force -ErrorAction SilentlyContinue
        }
    }

    It "keep non-pshome module path derived from PowerShell instance parent" -Skip:(!$IsCoreCLR -or $skipNoPwsh) {

        ## non-pshome module path derived from another PowerShell instance should be preserved
        $customeModules = Join-Path -Path $TestDrive -ChildPath 'CustomModules'
        $env:PSModulePath = $fakePSHomeModuleDir, $customeModules -join ([System.IO.Path]::PathSeparator)
        $newModulePath = & $powershell -nopro -c '$env:PSModulePath'
        $paths = $newModulePath -split [System.IO.Path]::PathSeparator
        $paths.Count | Should -Be 5
        $paths -contains $fakePSHomeModuleDir | Should -BeTrue
        $paths -contains $customeModules | Should -BeTrue
    }

    It 'Ensures $PSHOME\Modules is inserted correctly when launched from a different version of PowerShell' -Skip:(!($IsCoreCLR -and $IsWindows) -or $skipNoPwsh) {
        # When launched from a different version of PowerShell, PSModulePath contains the other version's PSHOME\Modules path
        # and the Windows PowerShell module path. The other version's module path should be removed and this version's
        # PSHOME\Modules path should be inserted before Windows PowerShell module path.
        $winpwshModulePath = [System.IO.Path]::Combine([System.Environment]::SystemDirectory, "WindowsPowerShell", "v1.0", "Modules");
        $pwshModulePath = Join-Path -Path $PSHOME -ChildPath 'Modules'

        # create a fake 'other version' $PSHOME and $PSHOME\Modules
        $fakeHome = Join-Path -Path $TestDrive -ChildPath 'fakepwsh'
        $fakeModulePath = Join-Path -Path $fakeHome -ChildPath 'Modules'

        $null = New-Item -Path $fakeHome -ItemType Directory
        $null = New-Item -Path $fakeModulePath -ItemType Directory

        # powershell looks for these to files to determine the directory is a pwsh directory.
        Set-Content -Path "$fakeHome\pwsh.exe" -Value "fake pwsh.exe"
        Set-Content -Path "$fakeHome\pwsh.deps.json" -Value 'fake pwsh.deps.json'

        # replace the actual pwsh module path with the fake one.
        $fakeModulePath = $env:PSModulePath.Replace($pwshModulePath, $fakeModulePath, [StringComparison]::OrdinalIgnoreCase)

        $newModulePath = & $powershell -nopro -c '$env:PSModulePath'
        $pwshIndex = $newModulePath.IndexOf($pwshModulePath, [StringComparison]::OrdinalIgnoreCase)
        $wpshIndex = $newModulePath.IndexOf($winpwshModulePath, [StringComparison]::OrdinalIgnoreCase)
        # ensure both module paths exist and the pwsh module path occurs before the Windows PowerShell module path
        $pwshIndex | Should -Not -Be -1
        $wpshIndex | Should -Not -Be -1
        $pwshIndex | Should -BeLessThan $wpshIndex
    }

    It 'Windows PowerShell does not inherit PowerShell paths' -Skip:(!$IsWindows) {
        $out = powershell.exe -noprofile -command '$env:PSModulePath'
        $out | Should -Not -BeLike "*$expectedUserPath*"
        $out | Should -Not -BeLike "*$expectedSharedPath*"
        $out | Should -Not -BeLike "*$expectedSystemPath*"
    }

    It 'Windows PowerShell inherits user added paths' -Skip:(!$IsWindows) {
        $env:PSModulePath += ";myPath"
        $out = powershell.exe -noprofile -command '$env:PSModulePath'
        $out | Should -BeLike '*;myPath'
    }

    It 'Windows PowerShell does not inherit path defined in powershell.config.json' -Skip:(!$IsWindows) {
        try {
            $userConfig = '{ "PSModulePath": "myUserPath" }'
            Set-Content -Path $userConfigPath -Value $userConfig -Force
            $out = & $powershell -noprofile -command 'powershell.exe -noprofile -command $env:PSModulePath'
            $out | Should -Not -BeLike 'myUserPath;*'
        }
        finally {
            Remove-Item -Path $userConfigPath -Force
        }
    }
}
