functions/_Get-GistMapData.Tests.ps1
|
# <copyright file="_Get-GistMapData.Tests.ps1" company="Endjin Limited"> # Copyright (c) Endjin Limited. All rights reserved. # </copyright> Describe "_Get-GistMapData" { BeforeAll { # Define stubs for external module functions to avoid dependency and ensure mocking works function ConvertFrom-Yaml { param([switch]$Ordered) } function _Get-RemoteGistMap { } $sut = "$PSScriptRoot\_Get-GistMapData.ps1" . $sut $script:validGistMap = @{ 'group1' = @( @{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') } ) } } BeforeEach { # Reset cache between tests to avoid cross-test interference $script:cachedMap = $null $script:cacheTimestamp = [datetime]::MinValue } Context "Golden Path" { BeforeAll { Mock Test-Path { return $true } Mock Get-Content { return "group1:`n - name: gist1" } Mock ConvertFrom-Yaml { return @{ 'group1' = @(@{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') }) } } } It "Should return parsed hashtable when file exists" { $result = _Get-GistMapData -ScriptRoot '/fake/path' -NoCache $result | Should -BeOfType [hashtable] $result.ContainsKey('group1') | Should -Be $true $result['group1'][0].name | Should -Be 'gist1' } It "Should call Get-Content with correct path" { # Ensure path comparison is cross-platform compatible, since the 'Join-Path' inside '_Get-GistMapData' will change the slashes $fakePathRoot = Join-Path '/fake' 'path' _Get-GistMapData -ScriptRoot $fakePathRoot -NoCache $fakeFullPath = Join-Path $fakePathRoot 'gist-map.yml' Should -Invoke Get-Content -ParameterFilter { $Path -eq $fakeFullPath } } It "Should pipe content to ConvertFrom-Yaml" { _Get-GistMapData -ScriptRoot '/fake/path' -NoCache Should -Invoke ConvertFrom-Yaml -Times 1 } } Context "File Not Found" { BeforeAll { Mock Test-Path { return $false } Mock Get-Content { } Mock ConvertFrom-Yaml { } } It "Should return null when gist-map.yml doesn't exist" { $result = _Get-GistMapData -ScriptRoot '/nonexistent/path' -NoCache $result | Should -BeNullOrEmpty } It "Should not attempt to read file when it doesn't exist" { _Get-GistMapData -ScriptRoot '/nonexistent/path' -NoCache Should -Invoke Get-Content -Times 0 Should -Invoke ConvertFrom-Yaml -Times 0 } } Context "Schema Validation" { BeforeAll { Mock Test-Path { return $true } Mock Get-Content { return "yaml content" } } It "Should throw when gist entry is missing a required field" { Mock ConvertFrom-Yaml { return @{ 'test-group' = @( @{ name = 'broken-gist' ref = 'main' includePaths = @('**/*') # 'source' is missing } ) } } { _Get-GistMapData -ScriptRoot '/fake/path' -NoCache } | Should -Throw "*missing required field: source*" } It "Should accept valid gist entries without error" { Mock ConvertFrom-Yaml { return @{ 'test-group' = @( @{ name = 'valid-gist' source = 'https://github.com/org/repo.git' ref = 'main' includePaths = @('**/*') } ) } } { _Get-GistMapData -ScriptRoot '/fake/path' -NoCache } | Should -Not -Throw } } Context "Path Construction" { BeforeAll { $testScriptRoot = Join-Path $TestDrive 'module' 'functions' $expectedMapPath = Join-Path $testScriptRoot 'gist-map.yml' Mock Test-Path { return $true } -ParameterFilter { $Path -eq $expectedMapPath } Mock Test-Path { return $false } Mock Get-Content { return "yaml content" } Mock ConvertFrom-Yaml { return @{ 'group1' = @(@{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') }) } } } It "Should build correct path from ScriptRoot using Join-Path" { $testScriptRoot = Join-Path $TestDrive 'module' 'functions' $expectedMapPath = Join-Path $testScriptRoot 'gist-map.yml' _Get-GistMapData -ScriptRoot $testScriptRoot -NoCache Should -Invoke Test-Path -ParameterFilter { $Path -eq $expectedMapPath } } } Context "Caching" { BeforeAll { Mock Test-Path { return $true } Mock Get-Content { return "yaml content" } Mock ConvertFrom-Yaml { return @{ 'group1' = @( @{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') } ) } } } It "Should return cached data on subsequent calls within TTL" { _Get-GistMapData -ScriptRoot '/fake/path' _Get-GistMapData -ScriptRoot '/fake/path' Should -Invoke Get-Content -Times 1 } It "Should re-read file when -NoCache is specified" { _Get-GistMapData -ScriptRoot '/fake/path' _Get-GistMapData -ScriptRoot '/fake/path' -NoCache Should -Invoke Get-Content -Times 2 } } Context "Remote HTTP Golden Path" { BeforeAll { Mock Invoke-RestMethod { return "group1:`n - name: gist1" } Mock ConvertFrom-Yaml { return @{ 'group1' = @(@{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') }) } } Mock Test-Path { } Mock Get-Content { } Mock _Get-RemoteGistMap { } } It "Should return parsed data from HTTP fetch" { $result = _Get-GistMapData -GistMapUrl 'https://example.com/gist-map.yml' -NoCache $result | Should -BeOfType [hashtable] $result.ContainsKey('group1') | Should -Be $true } It "Should not attempt local file or vendir fetch on HTTP success" { _Get-GistMapData -GistMapUrl 'https://example.com/gist-map.yml' -ScriptRoot '/fake/path' -NoCache Should -Invoke Test-Path -Times 0 Should -Invoke Get-Content -Times 0 Should -Invoke _Get-RemoteGistMap -Times 0 } } Context "HTTP Fails, Vendir Fallback Succeeds" { BeforeAll { Mock Invoke-RestMethod { throw "HTTP 404" } Mock _Get-RemoteGistMap { return "group1:`n - name: gist1" } Mock ConvertFrom-Yaml { return @{ 'group1' = @(@{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') }) } } Mock Test-Path { } Mock Get-Content { } } It "Should fall back to vendir when HTTP fails" { $gitSource = @{ url = 'https://github.com/endjin/endjin-gists.git'; ref = 'main'; path = 'module/gist-map.yml' } $result = _Get-GistMapData -GistMapUrl 'https://example.com/gist-map.yml' -GistMapGitSource $gitSource -NoCache $result | Should -BeOfType [hashtable] Should -Invoke _Get-RemoteGistMap -Times 1 } It "Should not attempt local file fetch on vendir success" { $gitSource = @{ url = 'https://github.com/endjin/endjin-gists.git'; ref = 'main'; path = 'module/gist-map.yml' } _Get-GistMapData -GistMapUrl 'https://example.com/gist-map.yml' -GistMapGitSource $gitSource -ScriptRoot '/fake/path' -NoCache Should -Invoke Test-Path -Times 0 Should -Invoke Get-Content -Times 0 } } Context "Both Remote Methods Fail, Local Fallback Succeeds" { BeforeAll { Mock Invoke-RestMethod { throw "HTTP 404" } Mock _Get-RemoteGistMap { return $null } Mock Test-Path { return $true } Mock Get-Content { return "yaml content" } Mock ConvertFrom-Yaml { return @{ 'group1' = @(@{ name = 'gist1'; source = 'https://example.com'; ref = 'main'; includePaths = @('**/*') }) } } } It "Should fall back to local file when both remote methods fail" { $gitSource = @{ url = 'https://github.com/endjin/endjin-gists.git'; ref = 'main'; path = 'module/gist-map.yml' } $result = _Get-GistMapData -GistMapUrl 'https://example.com/gist-map.yml' -GistMapGitSource $gitSource -ScriptRoot '/fake/path' -NoCache $result | Should -BeOfType [hashtable] Should -Invoke Get-Content -Times 1 } } Context "All Three Sources Fail" { BeforeAll { Mock Invoke-RestMethod { throw "HTTP 404" } Mock _Get-RemoteGistMap { return $null } Mock Test-Path { return $false } Mock Get-Content { } Mock ConvertFrom-Yaml { } } It "Should return null when all sources fail" { $gitSource = @{ url = 'https://github.com/endjin/endjin-gists.git'; ref = 'main'; path = 'module/gist-map.yml' } $result = _Get-GistMapData -GistMapUrl 'https://example.com/gist-map.yml' -GistMapGitSource $gitSource -ScriptRoot '/nonexistent/path' -NoCache $result | Should -BeNullOrEmpty } } } |