tests/DFInstallSource.Tests.ps1
|
BeforeAll { . "$PSScriptRoot/TestSupport.ps1" foreach ($f in Get-DFTestModuleFile) { . $f } function script:Rec([string]$Json) { ConvertTo-DFToolRecord ($Json | ConvertFrom-Json) } function script:New-Db { $db = @{} foreach ($r in @( (Rec '{ "name": "scoop", "executable": "scoop.cmd", "roles": { "package-manager": { "priority": 30 } }, "installs": { "from": "scoop", "command": ["scoop","install","{id}"], "batch": true } }'), (Rec '{ "name": "winget", "executable": "winget.exe", "roles": { "package-manager": { "priority": 20 } }, "installs": { "from": "winget", "command": ["winget","install","{id}"] } }'), (Rec '{ "name": "choco", "executable": "choco.exe", "roles": { "package-manager": { "priority": 10 } }, "installs": { "from": "choco", "command": ["choco","install","{id}"], "elevate": true } }'), (Rec '{ "name": "npm", "executable": "npm.cmd", "roles": { "js-package-manager": { "priority": 30 } }, "installs": { "from": "npm", "command": ["npm","i","-g","{id}"] } }'), (Rec '{ "name": "pnpm", "executable": "pnpm.cmd", "roles": { "js-package-manager": { "priority": 20 } }, "installs": { "from": "npm", "command": ["pnpm","add","-g","{id}"] } }'), (Rec '{ "name": "glow", "executable": "glow.exe", "packages": { "scoop": "glow", "winget": "charm.glow", "choco": "glow" }, "install": { "prefer": ["winget"] } }'), (Rec '{ "name": "ish", "executable": "is.cmd", "packages": { "npm": "@microsoft/inshellisense" } }') )) { $db[$r.name] = $r } $db } $script:All = { param($r) $true } } Describe 'Get-DFInstallSourceOrder' { BeforeEach { Set-DFTestConfig $null; $script:Db = New-Db } AfterEach { Set-DFTestConfig $null } It 'puts the tool spec''s preference first, then the built-in order' { Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db | Should -Be @('winget', 'scoop', 'choco') } It 'lets InstallOrder reorder what the tool spec didn''t pin' { Set-DFTestConfig @{ InstallOrder = @('choco', 'scoop') } Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db | Should -Be @('winget', 'choco', 'scoop') } It 'puts InstallVia above everything' { Set-DFTestConfig @{ InstallVia = @{ glow = 'choco' } } (Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db)[0] | Should -Be 'choco' } It 'puts a per-call -Via above the InstallVia setting' { Set-DFTestConfig @{ InstallVia = @{ glow = 'choco' } } (Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db -Via @{ glow = 'scoop' })[0] | Should -Be 'scoop' } It 'drops excluded sources, unless InstallVia names one' { Set-DFTestConfig @{ ExcludeSources = @('choco') } Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db | Should -Not -Contain 'choco' Set-DFTestConfig @{ ExcludeSources = @('choco'); InstallVia = @{ glow = 'choco' } } (Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db)[0] | Should -Be 'choco' } It 'warns about an InstallVia source the tool has no package for, and ignores it' { Set-DFTestConfig @{ InstallVia = @{ glow = 'crates' } } $o = Get-DFInstallSourceOrder -Tool $script:Db.glow -ToolDb $script:Db -WarningVariable w 3>$null $o[0] | Should -Be 'winget' "$w" | Should -Match "InstallVia.*glow.*crates" } } Describe 'Get-DFSourceManager' { BeforeEach { Set-DFTestConfig $null; $script:Db = New-Db } AfterEach { Set-DFTestConfig $null } It 'ranks a registry''s managers by priority, and Defaults first' { (Get-DFSourceManager -Source npm -ToolDb $script:Db).name | Should -Be @('npm', 'pnpm') Set-DFTestConfig @{ Defaults = @{ 'js-package-manager' = 'pnpm' } } (Get-DFSourceManager -Source npm -ToolDb $script:Db).name | Should -Be @('pnpm', 'npm') } } Describe 'Resolve-DFInstallSource' { BeforeEach { Set-DFTestConfig $null; $script:Db = New-Db } AfterEach { Set-DFTestConfig $null } It 'takes the first source whose manager is available' { $r = Resolve-DFInstallSource -Tool $script:Db.glow -ToolDb $script:Db -IsAvailable { param($m) $m.name -ne 'winget' } $r.Source | Should -Be 'scoop' $r.Manager.name | Should -Be 'scoop' $r.Ref.Id | Should -Be 'glow' $r.Gap | Should -BeNullOrEmpty } It 'counts a manager planned in an earlier stage as available' { $r = Resolve-DFInstallSource -Tool $script:Db.ish -ToolDb $script:Db -IsAvailable { param($m) $false } -Planned @('npm') $r.Manager.name | Should -Be 'npm' } It 'reports a gap, with the managers that could serve it, when nothing is available' { $r = Resolve-DFInstallSource -Tool $script:Db.ish -ToolDb $script:Db -IsAvailable { param($m) $false } $r.Gap | Should -Match 'npm' $r.Options | Should -Be @('npm', 'pnpm') } It 'uses the user''s choice for a gap source' { $r = Resolve-DFInstallSource -Tool $script:Db.ish -ToolDb $script:Db -IsAvailable { param($m) $false } -Choice @{ npm = 'pnpm' } $r.Manager.name | Should -Be 'pnpm' $r.Gap | Should -BeNullOrEmpty } It 'names the exclusion when it leaves no source' { $db = New-Db $db.only = Rec '{ "name": "only", "executable": "o.exe", "packages": { "choco": "only" } }' Set-DFTestConfig @{ ExcludeSources = @('choco') } (Resolve-DFInstallSource -Tool $db.only -ToolDb $db -IsAvailable $script:All).Gap | Should -Match 'no source left.*choco.*ExcludeSources' } It 'reports a tool with no packages at all' { $db = New-Db $db.bare = Rec '{ "name": "bare", "executable": "b.exe" }' (Resolve-DFInstallSource -Tool $db.bare -ToolDb $db -IsAvailable $script:All).Gap | Should -Match 'no package' } } Describe 'managers with several installs blocks' { BeforeEach { Set-DFTestConfig $null $script:Db = @{} foreach ($r in @( (Rec '{ "name": "uv", "executable": "uv.exe", "roles": { "version-manager": {}, "python-package-manager": { "priority": 30 } }, "installs": [ { "from": "pypi", "command": ["uv","tool","install","{id}"] }, { "from": "uv", "command": ["uv","python","install","--default","{id}"] } ] }'), (Rec '{ "name": "ruff", "executable": "ruff.exe", "packages": { "pypi": "ruff" } }'), (Rec '{ "name": "python", "executable": "python.exe", "packages": { "uv": "3" } }') )) { $script:Db[$r.name] = $r } } AfterEach { Set-DFTestConfig $null } It 'picks the block that installs from the chosen source' { $r = Resolve-DFInstallSource -Tool $script:Db.ruff -ToolDb $script:Db -IsAvailable { param($m) $true } $r.Manager.name | Should -Be 'uv' $r.Block.command | Should -Be @('uv', 'tool', 'install', '{id}') (Resolve-DFInstallSource -Tool $script:Db.python -ToolDb $script:Db -IsAvailable { param($m) $true }).Block.from | Should -Be 'uv' } It 'lists every block''s source in the built-in order' { Get-DFBuiltInSourceOrder -ToolDb $script:Db | Should -Contain 'pypi' Get-DFBuiltInSourceOrder -ToolDb $script:Db | Should -Contain 'uv' } } Describe 'shipped Python managers (install spec, Python tree)' { BeforeAll { $script:Ship = Import-DFToolDb -ToolsPath "$PSScriptRoot/../Tools" -Force } BeforeEach { Set-DFTestConfig $null } It 'serves PyPI with uv, then pipx, then pip' { (Get-DFSourceManager -Source pypi -ToolDb $script:Ship).name | Should -Be @('uv', 'pipx', 'pip') } It 'installs python through pymanager first, then uv, and checks it after the version managers' { $script:Ship.python.install.prefer[0..1] | Should -Be @('pymanager', 'uv') $script:Ship.python.after | Should -Contain 'role:version-manager' $script:Ship.python.roles.PSObject.Properties.Name | Should -Contain 'python-runtime' $script:Ship.pymanager.roles.PSObject.Properties.Name | Should -Contain 'version-manager' } It 'finds pipx whether scoop installed it (pipx.cmd) or pip did (pipx.exe)' { $script:Ship.pipx.executable | Should -Be 'pipx' } It 'makes pip come with python, and pipx need a Python runtime' { $script:Ship.pip.requires | Should -Contain 'python' $script:Ship.pipx.requires | Should -Contain 'role:python-runtime' } It 'gives a PyPI catalog hint from the top PyPI manager' { Mock Import-DFToolDb { $script:Ship } Get-DFInstallHint -Source pypi -Id ruff | Should -Be 'uv tool install ruff' } } |