tests/Import-DFToolDb.Tests.ps1
|
BeforeAll { . "$PSScriptRoot/TestSupport.ps1" foreach ($f in Get-DFTestModuleFile) { . $f } } Describe 'Import-DFToolDb' { BeforeEach { $script:DFToolDb = $null # reset cache between tests # Build a temp tools directory with controlled JSON content $script:TmpTools = Join-Path $TestDrive 'tools' New-Item -ItemType Directory -Force -Path $script:TmpTools | Out-Null $validJson = @' { "name": "mytool", "executable": "mytool.exe" } '@ $validJson | Set-Content (Join-Path $script:TmpTools 'mytool.json') } It 'returns a hashtable keyed by tool name' { $db = Import-DFToolDb -ToolsPath $script:TmpTools $db | Should -BeOfType [hashtable] $db.ContainsKey('mytool') | Should -BeTrue } It 'always does a fresh read when -ToolsPath is given explicitly, even without -Force' { # An explicit -ToolsPath must never trust the shared cache -- a caller # who names their own directory (tests, a future sub-registry) always # sees that directory's current contents. $db1 = Import-DFToolDb -ToolsPath $script:TmpTools '{ "name": "extra", "executable": "extra.exe" }' | Set-Content (Join-Path $script:TmpTools 'extra.json') $db2 = Import-DFToolDb -ToolsPath $script:TmpTools $db2.ContainsKey('extra') | Should -BeTrue } It 'caches the default-location result across calls with no -ToolsPath' { # Seed the shared cache with a sentinel and call with NO -ToolsPath at # all -- if the default-location branch is really cache-backed, the # sentinel comes back untouched (no directory is ever scanned). $script:DFToolDb = @{ sentinel = $true } $db = Import-DFToolDb $db.ContainsKey('sentinel') | Should -BeTrue } It 'never overwrites the shared cache when -ToolsPath is given explicitly' { $script:DFToolDb = @{ sentinel = $true } Import-DFToolDb -ToolsPath $script:TmpTools | Out-Null $script:DFToolDb.ContainsKey('sentinel') | Should -BeTrue } It 'reloads when -Force is specified' { $db1 = Import-DFToolDb -ToolsPath $script:TmpTools '{ "name": "extra", "executable": "extra.exe" }' | Set-Content (Join-Path $script:TmpTools 'extra.json') $db2 = Import-DFToolDb -ToolsPath $script:TmpTools -Force $db2.ContainsKey('extra') | Should -BeTrue } It 'emits a warning and skips files that fail schema validation' { '{ "missingName": true }' | Set-Content (Join-Path $script:TmpTools 'bad.json') $db = Import-DFToolDb -ToolsPath $script:TmpTools -WarningVariable warns 3>$null $db.ContainsKey('mytool') | Should -BeTrue $warns | Should -Not -BeNullOrEmpty } Context '-Name (read only the requested records)' { BeforeEach { '{ "name": "other", "executable": "other.exe" }' | Set-Content (Join-Path $script:TmpTools 'other.json') '{ "name": "broken" }' | Set-Content (Join-Path $script:TmpTools 'broken.json') } It 'returns only the named tools and never opens the other files' { # Reading either of these would warn (unparseable / schema error), so a # clean warning stream proves they were never opened. No mocking needed. 'not json {' | Set-Content (Join-Path $script:TmpTools 'other.json') $db = Import-DFToolDb -ToolsPath $script:TmpTools -Name 'mytool' -WarningVariable w 3>$null @($db.Keys) | Should -Be @('mytool') $w | Should -BeNullOrEmpty } It 'matches names case-insensitively' { (Import-DFToolDb -ToolsPath $script:TmpTools -Name 'MYTOOL').Contains('mytool') | Should -BeTrue } It 'warns and skips a named tool whose file is missing or invalid' { $db = Import-DFToolDb -ToolsPath $script:TmpTools -Name 'mytool', 'nope', 'broken' -WarningVariable w 3>$null @($db.Keys) | Should -Be @('mytool') "$w" | Should -Match 'nope' "$w" | Should -Match 'broken' } } Context 'Get-DFToolNames' { It 'lists tool names from file names without reading any file' { $dir = Join-Path $TestDrive 'names-only' # fresh: $TestDrive outlives each test New-Item -ItemType Directory $dir -Force | Out-Null 'x' | Set-Content (Join-Path $dir 'mytool.json') 'x' | Set-Content (Join-Path $dir 'other.json') Mock Get-Content { throw 'must not read' } Get-DFToolNames -ToolsPath $dir | Sort-Object | Should -Be @('mytool', 'other') } It 'matches every shipped record''s name to its file name, so file names can stand in for names' { $mismatch = Get-ChildItem "$PSScriptRoot/../Tools" -Filter '*.json' | Where-Object { (Get-Content $_.FullName -Raw | ConvertFrom-Json).name -cne $_.BaseName } | ForEach-Object Name @($mismatch) | Should -BeNullOrEmpty } } It 'returns empty hashtable when ToolsPath does not exist' { $db = Import-DFToolDb -ToolsPath 'C:\nonexistent\tools' $db | Should -BeOfType [hashtable] $db.Count | Should -Be 0 } } # Characterization (improve-code-quality Phase 1, 2026-10-10): the caches as they # behave today. Both apply only to the module's own Tools/ folder. Describe 'Import-DFToolDb caches' { BeforeEach { $script:DFToolDb = $null $script:DFToolRecordCache = @{} # Count file reads; the record content doesn't matter here. Mock Read-DFToolRecordFile { [pscustomobject]@{ name = [IO.Path]::GetFileNameWithoutExtension($Path) } } } AfterEach { $script:DFToolDb = $null; $script:DFToolRecordCache = @{} } It 'reads a named record once, then serves it from the per-name cache' { $null = Import-DFToolDb -Name bat $null = Import-DFToolDb -Name bat Should -Invoke Read-DFToolRecordFile -Times 1 -Exactly } It 'serves a named record from a previous full load without reading it again' { $all = Import-DFToolDb $reads = @($all.Keys).Count (Import-DFToolDb -Name bat).bat | Should -Not -BeNullOrEmpty Should -Invoke Read-DFToolRecordFile -Times $reads -Exactly } It 'reuses a full load until -Force' { $first = Import-DFToolDb $second = Import-DFToolDb [object]::ReferenceEquals($first, $second) | Should -BeTrue $third = Import-DFToolDb -Force [object]::ReferenceEquals($first, $third) | Should -BeFalse } It 're-reads a named record with -Force' { $null = Import-DFToolDb -Name bat $null = Import-DFToolDb -Name bat -Force Should -Invoke Read-DFToolRecordFile -Times 2 -Exactly } It 'never caches a load from an explicit -ToolsPath, nor fills the default caches' { $dir = Join-Path $PSScriptRoot '..' 'Tools' $null = Import-DFToolDb -Name bat -ToolsPath $dir $null = Import-DFToolDb -Name bat -ToolsPath $dir Should -Invoke Read-DFToolRecordFile -Times 2 -Exactly $script:DFToolRecordCache.Count | Should -Be 0 $script:DFToolDb | Should -BeNullOrEmpty } } |