Public/Tests/Import-ModuleFromFlatTextAndManifest.Tests.ps1

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.utils.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.utils -Force -ErrorAction SilentlyContinue
}

Describe 'Import-ModuleFromFlatTextAndManifest' {
    BeforeAll {
        $ModuleSource = Join-Path -Path $TestDrive -ChildPath 'src/my.module'
        $Files = [ordered]@{
            'my.module.psd1'       = "@{ ModuleVersion = '1.0.0' }`n"
            'Public/Get-Thing.ps1' = "function Get-Thing {`n 'thing'`n}`n"
            'no-newline.txt'       = 'last line'
            'blank-lines.txt'      = "`n`nmiddle`n`n"
            'crlf.txt'             = "one`r`ntwo`r`n"
            'empty.txt'            = ''
        }
        foreach ($name in $Files.Keys) {
            $path = Join-Path -Path $ModuleSource -ChildPath $name
            $null = New-Item -Path (Split-Path -Path $path -Parent) -ItemType Directory -Force
            Set-Content -Path $path -Value $Files[$name] -NoNewline
        }
        $null = New-Item -Path (Join-Path -Path $ModuleSource -ChildPath 'EmptyFolder') -ItemType Directory -Force
        $ExportPath = Join-Path -Path $TestDrive -ChildPath 'export'
        Export-ModuleToFlatTextAndManifest -ModulePath $ModuleSource -OutputPath $ExportPath
        $ManifestPath = Join-Path -Path $ExportPath -ChildPath 'my.module_manifest.json'
        $FlatTextPath = Join-Path -Path $ExportPath -ChildPath 'my.module_flat.txt'
    }

    It 'Recreates every file with exactly the exported content' {
        $importPath = Join-Path -Path $TestDrive -ChildPath 'import'
        Import-ModuleFromFlatTextAndManifest -ManifestPath $ManifestPath -FlatTextPath $FlatTextPath -OutputPath $importPath
        foreach ($name in $Files.Keys) {
            $path = Join-Path -Path (Join-Path -Path $importPath -ChildPath 'my.module') -ChildPath $name
            Test-Path -Path $path | Should -BeTrue -Because "$name should be recreated"
            $content = Get-Content -Path $path -Raw
            if ($null -eq $content) { $content = '' }
            $content | Should -BeExactly $Files[$name] -Because "$name should round-trip exactly"
        }
    }

    It 'Recreates empty folders' {
        $importPath = Join-Path -Path $TestDrive -ChildPath 'import-folders'
        Import-ModuleFromFlatTextAndManifest -ManifestPath $ManifestPath -FlatTextPath $FlatTextPath -OutputPath $importPath
        Test-Path -Path (Join-Path -Path $importPath -ChildPath 'my.module/EmptyFolder') -PathType Container | Should -BeTrue
    }

    It 'Accepts manifests with Windows-style relative paths' {
        $windowsManifest = Join-Path -Path $TestDrive -ChildPath 'windows_manifest.json'
        $windowsFlat = Join-Path -Path $TestDrive -ChildPath 'windows_flat.txt'
        @{
            ModuleRoot = 'win.module'
            Items      = @(
                @{ RelativePath = 'Public'; ParentPath = ''; ItemType = 'Directory' }
                @{ RelativePath = 'Public\Get-Win.ps1'; ParentPath = 'Public'; ItemType = 'File' }
            )
        } | ConvertTo-Json -Depth 5 | Set-Content -Path $windowsManifest
        Set-Content -Path $windowsFlat -Value "###FILE-START:Public\Get-Win.ps1###`r`nfunction Get-Win { }`r`n`r`n###FILE-END:Public\Get-Win.ps1###`r`n" -NoNewline
        $importPath = Join-Path -Path $TestDrive -ChildPath 'import-windows'
        Import-ModuleFromFlatTextAndManifest -ManifestPath $windowsManifest -FlatTextPath $windowsFlat -OutputPath $importPath
        $file = Join-Path -Path $importPath -ChildPath 'win.module/Public/Get-Win.ps1'
        Test-Path -Path $file | Should -BeTrue
        Get-Content -Path $file -Raw | Should -BeExactly "function Get-Win { }`r`n"
    }

    It 'Does not write outside the output folder' {
        $unsafeManifest = Join-Path -Path $TestDrive -ChildPath 'unsafe_manifest.json'
        $unsafeFlat = Join-Path -Path $TestDrive -ChildPath 'unsafe_flat.txt'
        @{
            ModuleRoot = 'unsafe.module'
            Items      = @(@{ RelativePath = '../escaped.txt'; ParentPath = '..'; ItemType = 'File' })
        } | ConvertTo-Json -Depth 5 | Set-Content -Path $unsafeManifest
        Set-Content -Path $unsafeFlat -Value "###FILE-START:../escaped.txt###`nbad`n###FILE-END:../escaped.txt###`n" -NoNewline
        $importPath = Join-Path -Path $TestDrive -ChildPath 'import-unsafe'
        Import-ModuleFromFlatTextAndManifest -ManifestPath $unsafeManifest -FlatTextPath $unsafeFlat -OutputPath $importPath -WarningVariable importWarnings -WarningAction SilentlyContinue
        Test-Path -Path (Join-Path -Path $importPath -ChildPath 'escaped.txt') | Should -BeFalse
        $importWarnings | Should -Not -BeNullOrEmpty
    }

    It 'Throws when the manifest does not exist' {
        { Import-ModuleFromFlatTextAndManifest -ManifestPath (Join-Path -Path $TestDrive -ChildPath 'missing.json') -FlatTextPath $FlatTextPath -OutputPath $TestDrive } | Should -Throw
    }
}