functions/Get-EndjinGist.Tests.ps1

# <copyright file="Get-EndjinGist.Tests.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>

Describe "Get-EndjinGist" {
    
    BeforeAll {
        # Define stubs for external module functions to avoid dependency and ensure mocking works
        function ConvertTo-Yaml { param($Data, $OutFile, [switch]$Force) }
        function ConvertFrom-Yaml { param([switch]$Ordered) }

        $sut = "$PSScriptRoot\Get-EndjinGist.ps1"
        . $sut

        # Mock Data
        $mockGistMap = @{
            'test-group' = @(
                @{
                    name = 'test-gist'
                    source = 'https://github.com/org/repo.git'
                    ref = 'main'
                    includePaths = @('path/to/include')
                }
            )
        }
    }

    Context "Prerequisites" {
        It "Should throw if 'vendir' is not in PATH" {
            Mock Get-Command { return $null } -ParameterFilter { $Name -eq 'vendir' }
            
            { Get-EndjinGist -Group 'any' -Name 'any' -GistMapPath 'dummy' } | Should -Throw "Please install 'vendir' to your PATH before using this tool."
        }
    }

    Context "Input Validation" {
        BeforeAll {
            Mock Get-Command { return $true } -ParameterFilter { $Name -eq 'vendir' }
            Mock Get-Content { return "yaml content" }
            Mock ConvertFrom-Yaml { return $mockGistMap }
        }

        It "Should throw if Group is unknown" {
            { Get-EndjinGist -Group 'unknown-group' -Name 'test-gist' -GistMapPath 'dummy' } | Should -Throw "Unknown gist group: unknown-group"
        }

        It "Should throw if Name is unknown in Group" {
            { Get-EndjinGist -Group 'test-group' -Name 'unknown-gist' -GistMapPath 'dummy' } | Should -Throw "Unknown gist 'unknown-gist' in group 'test-group'"
        }
    }

    Context "Execution" {
        BeforeAll {
            Mock Get-Command { return $true } -ParameterFilter { $Name -eq 'vendir' }
            Mock Get-Content { return "yaml content" }
            Mock ConvertFrom-Yaml { return $mockGistMap }
            Mock ConvertTo-Yaml { }
            Mock Remove-Item { }
            Mock Write-Host { }
        }

        It "Should invoke vendir with generated config" {
            Mock Invoke-Command { 
                $global:LASTEXITCODE = 0
                return "Success" 
            }

            Get-EndjinGist -Group 'test-group' -Name 'test-gist' -GistMapPath 'dummy'

            Assert-MockCalled ConvertTo-Yaml -Times 1
            Assert-MockCalled Invoke-Command -Times 1
        }

        It "Should handle vendir failure" {
             Mock Invoke-Command {
                # Simulate failure
                $global:LASTEXITCODE = 1
                return "Error message"
            }

            { Get-EndjinGist -Group 'test-group' -Name 'test-gist' -GistMapPath 'dummy' } | Should -Throw "Operation failed with exit code 1.`nError message"
        }
    }

    Context "MovePaths Processing" {
        BeforeAll {
            $mockGistMapWithMovePaths = @{
                'test-group' = @(
                    @{
                        name = 'test-gist'
                        source = 'https://github.com/org/repo.git'
                        ref = 'main'
                        includePaths = @('content/**/*')
                        movePaths = @(
                            @{
                                from = 'content/**/*'
                                to = '${repoRoot}/target/'
                            }
                        )
                    }
                )
            }

            Mock Get-Command { return $true } -ParameterFilter { $Name -eq 'vendir' }
            Mock Get-Content { return "yaml content" }
            Mock ConvertTo-Yaml { }
            Mock Write-Host { }
        }

        It "Should expand repoRoot variable in destination path" {
            Mock ConvertFrom-Yaml { return $mockGistMapWithMovePaths }
            Mock Invoke-Command { $global:LASTEXITCODE = 0; return "Success" }
            Mock Remove-Item { }

            # Mock git to return a specific repo root
            Mock git { return '/mock/repo/root' } -ParameterFilter { $args[0] -eq 'rev-parse' }

            # Mock Get-ChildItem to return no files (avoiding actual file operations)
            Mock Get-ChildItem { return @() }

            Get-EndjinGist -Group 'test-group' -Name 'test-gist' -GistMapPath 'dummy'

            # Verify the function executed without errors (variable expansion happened internally)
            Should -Invoke Invoke-Command -Times 1
        }

        It "Should not process moves if movePaths is not defined" {
            $mockGistMapWithoutMovePaths = @{
                'test-group' = @(
                    @{
                        name = 'test-gist'
                        source = 'https://github.com/org/repo.git'
                        ref = 'main'
                        includePaths = @('content/**/*')
                    }
                )
            }

            Mock ConvertFrom-Yaml { return $mockGistMapWithoutMovePaths }
            Mock Invoke-Command { $global:LASTEXITCODE = 0; return "Success" }
            Mock Remove-Item { }
            Mock Move-Item { }

            Get-EndjinGist -Group 'test-group' -Name 'test-gist' -GistMapPath 'dummy'

            # Move-Item should never be called when movePaths is not defined
            Should -Invoke Move-Item -Times 0
        }

        It "Should move files and preserve directory structure" {
            Mock ConvertFrom-Yaml { return $mockGistMapWithMovePaths }
            Mock Invoke-Command { $global:LASTEXITCODE = 0; return "Success" }
            Mock Remove-Item { }
            Mock git { return $TestDrive } -ParameterFilter { $args[0] -eq 'rev-parse' }

            # Setup source files in TestDrive
            $sourceDir = Join-Path $TestDrive '.endjin/test-group/test-gist/content'
            $subDir = Join-Path $sourceDir 'subfolder'
            New-Item -ItemType Directory -Path $subDir -Force | Out-Null
            Set-Content -Path (Join-Path $sourceDir 'file1.txt') -Value 'content1'
            Set-Content -Path (Join-Path $subDir 'file2.txt') -Value 'content2'

            # Run in TestDrive context
            Push-Location $TestDrive
            try {
                Get-EndjinGist -Group 'test-group' -Name 'test-gist' -GistMapPath 'dummy'
            }
            finally {
                Pop-Location
            }

            # Verify files were moved to target with preserved structure
            $targetFile1 = Join-Path $TestDrive 'target/file1.txt'
            $targetFile2 = Join-Path $TestDrive 'target/subfolder/file2.txt'

            Test-Path $targetFile1 | Should -Be $true
            Test-Path $targetFile2 | Should -Be $true
            # Use [System.IO.File] to bypass the Get-Content mock
            [System.IO.File]::ReadAllText($targetFile1).Trim() | Should -Be 'content1'
            [System.IO.File]::ReadAllText($targetFile2).Trim() | Should -Be 'content2'
        }

        It "Should clean up empty source directories after move" {
            Mock ConvertFrom-Yaml { return $mockGistMapWithMovePaths }
            Mock Invoke-Command { $global:LASTEXITCODE = 0; return "Success" }
            Mock Remove-Item { } -ParameterFilter { $Path -like '*vendir*' -or $Path -like '*.yml' }
            Mock git { return $TestDrive } -ParameterFilter { $args[0] -eq 'rev-parse' }

            # Setup source files in TestDrive
            $sourceDir = Join-Path $TestDrive '.endjin/test-group/test-gist/content'
            New-Item -ItemType Directory -Path $sourceDir -Force | Out-Null
            Set-Content -Path (Join-Path $sourceDir 'file.txt') -Value 'content'

            Push-Location $TestDrive
            try {
                Get-EndjinGist -Group 'test-group' -Name 'test-gist' -GistMapPath 'dummy'
            }
            finally {
                Pop-Location
            }

            # Source directory should be cleaned up (empty after move)
            Test-Path $sourceDir | Should -Be $false
        }
    }
}