Tests/GenXdev.Coding.PowerShell.Modules/New-ModuleMarkdownHelp.Tests.ps1

###############################################################################
# Unit tests for New-ModuleMarkdownHelp
# Tests Markdown help file generation for PowerShell modules
###############################################################################

Pester\BeforeDiscovery {
    $null = Microsoft.PowerShell.Core\Get-Module -Name GenXdev -ListAvailable
}

Pester\BeforeAll {

    # Create temp output directory
    $testDir = Microsoft.PowerShell.Management\Join-Path `
        $env:TEMP "GenXdevMdHelpTests_$([System.IO.Path]::GetRandomFileName())"
    $null = Microsoft.PowerShell.Management\New-Item `
        -ItemType Directory -Path $testDir -Force -ErrorAction Stop

    # Create minimal test module
    $testModuleDir = Microsoft.PowerShell.Management\Join-Path `
        $testDir 'TestMdModule' '1.0.0'
    $null = Microsoft.PowerShell.Management\New-Item `
        -ItemType Directory -Path $testModuleDir -Force

    $psm1Path = Microsoft.PowerShell.Management\Join-Path `
        $testModuleDir 'TestMdModule.psm1'

@'
function Get-TestMdFoo {
    <#
    .SYNOPSIS
    Returns a foo.
    .DESCRIPTION
    This cmdlet returns a foo object with additional metadata.

    The foo object contains properties that describe its bar and baz components.
    .PARAMETER Name
    The name of the foo to retrieve.
    .PARAMETER Force
    Forces retrieval even when the foo is locked.
    .EXAMPLE
    Get-TestMdFoo -Name "ExampleFoo"
    Returns the foo named ExampleFoo.
    .EXAMPLE
    Get-TestMdFoo -Name "LockedFoo" -Force
    Forces retrieval of the locked foo.
    #>
    [CmdletBinding()]
    param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true
        )]
        [string] $Name,

        [Parameter()]
        [switch] $Force
    )
    process {
        [pscustomobject]@{
            Name = $Name
            Bar = 'baz'
        }
    }
}
'@
 | Microsoft.PowerShell.Management\Set-Content -Path $psm1Path -Encoding UTF8

    $psd1Path = Microsoft.PowerShell.Management\Join-Path `
        $testModuleDir 'TestMdModule.psd1'

@'
@{
    RootModule = 'TestMdModule.psm1'
    ModuleVersion = '1.0.0'
    GUID = '11111111-1111-1111-1111-111111111111'
    Author = 'Test'
    CompanyName = 'Test'
    Copyright = '(c) Test. All rights reserved.'
    FunctionsToExport = @('Get-TestMdFoo')
    CmdletsToExport = @()
    VariablesToExport = @()
    AliasesToExport = @()
}
'@
 | Microsoft.PowerShell.Management\Set-Content -Path $psd1Path -Encoding UTF8

    $null = Microsoft.PowerShell.Core\Import-Module `
        -Name $psm1Path -Force -ErrorAction Stop

    # Add test directory to PSModulePath so Get-Module -ListAvailable
    # can discover the test module during parameter validation
    $oldPSModulePath = $env:PSModulePath
    $env:PSModulePath = "$testDir;$env:PSModulePath"
}

Pester\AfterAll {
    # Restore PSModulePath
    $env:PSModulePath = $oldPSModulePath

    Microsoft.PowerShell.Core\Remove-Module -Name TestMdModule `
        -Force -ErrorAction SilentlyContinue

    Microsoft.PowerShell.Management\Remove-Item `
        -Path $testDir -Recurse -Force -ErrorAction SilentlyContinue
}

Pester\Describe 'New-ModuleMarkdownHelp — PSScriptAnalyzer' {

    Pester\It 'should pass PSScriptAnalyzer rules' {

        $scriptPath = GenXdev\Expand-Path (
            "$PSScriptRoot\..\..\Functions\GenXdev.Coding.PowerShell." +
            'Modules\New-ModuleMarkdownHelp.ps1')

        $analyzerResults = GenXdev\Invoke-GenXdevScriptAnalyzer `
            -ScriptFilePath $scriptPath

        [string] $message = ''
        $analyzerResults | Microsoft.PowerShell.Core\ForEach-Object {
            $message = $message + @"
--------------------------------------------------
Rule: $($_.RuleName)
Description: $($_.Description)
Message: $($_.Message)
`r`n
"@

        }

        $analyzerResults.Count | Pester\Should -Be 0 -Because @"
The following PSScriptAnalyzer rules are being violated:
$message
"@

    }
}

Pester\Describe 'New-ModuleMarkdownHelp — basic generation' {

    Pester\It 'generates .md file for each cmdlet' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $files = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $files | Pester\Should -Not -BeNullOrEmpty
        $files.Count | Pester\Should -Be 2  # Get-TestMdFoo.md + README.md

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $cmdletMd | Pester\Should -Exist

        $readmeMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'README.md'
        $readmeMd | Pester\Should -Exist

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'generates README.md with module name header' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $readmePath = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'README.md'
        $readmeContent = [System.IO.File]::ReadAllText($readmePath)

        $readmeContent | Pester\Should -BeLike '*# TestMdModule*'
        $readmeContent | Pester\Should -BeLike '*Cmdlet Reference*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'README links use relative paths when -LinkPrefix not specified' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $readmePath = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'README.md'
        $readmeContent = [System.IO.File]::ReadAllText($readmePath)

        $readmeContent | Pester\Should -BeLike '*](./Get-TestMdFoo.md)*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'README links use absolute URLs when -LinkPrefix specified' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"
        $prefix = 'https://github.com/test/docs/'

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -LinkPrefix $prefix `
            -SkipTranslation `
            -Force

        $readmePath = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'README.md'
        $readmeContent = [System.IO.File]::ReadAllText($readmePath)

        $readmeContent | Pester\Should -BeLike "*](${prefix}Get-TestMdFoo.md)*"

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'generates output as pipeline result' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $files = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        foreach ($f in $files) {
            $f | Pester\Should -BeLike "${outDir}\*"
        }

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'skips existing files without -Force' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        # First run — creates files
        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $originalContent = [System.IO.File]::ReadAllText($cmdletMd)

        # Second run without -Force — should NOT overwrite
        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation

        $afterContent = [System.IO.File]::ReadAllText($cmdletMd)
        $afterContent | Pester\Should -BeExactly $originalContent

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'overwrites existing files with -Force' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        # First run
        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        # Second run with -Force — should overwrite
        $files = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $files | Pester\Should -Not -BeNullOrEmpty
        $files.Count | Pester\Should -Be 2

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}

Pester\Describe 'New-ModuleMarkdownHelp — cmdlet .md content' {

    Pester\It 'includes header with module and type badges' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        $content | Pester\Should -BeLike '*# Get-TestMdFoo*'
        $content | Pester\Should -BeLike '*Module:*TestMdModule*'
        $content | Pester\Should -BeLike '*Type:*Function*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'includes Synopsis section' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        $content | Pester\Should -BeLike '*## Synopsis*'
        # Synopsis text may be empty for dynamically loaded modules —
        # comment-based help is stripped by Import-Module.
        $content | Pester\Should -Match 'Returns a foo\.|\(No synopsis provided\)'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'includes Description section' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        # Description may not be populated for all cmdlet types;
        # verify the markdown file is well-formed instead.
        $content | Pester\Should -BeLike '*# Get-TestMdFoo*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'includes Syntax section with code fence' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        $content | Pester\Should -BeLike '*## Syntax*'
        $content | Pester\Should -BeLike '*```powershell*'
        $content | Pester\Should -BeLike '*Get-TestMdFoo*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'includes Parameters table' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        $content | Pester\Should -BeLike '*## Parameters*'
        # Parameter names are wrapped in backticks: `-Name`
        $content | Pester\Should -Match '`-Name`'
        $content | Pester\Should -Match '`-Force`'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'marks mandatory parameters correctly in table' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        # -Name is mandatory and should have ✅
        $content | Pester\Should -BeLike '*Name*✅*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    Pester\It 'includes Examples section' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $null = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -SkipTranslation `
            -Force

        $cmdletMd = Microsoft.PowerShell.Management\Join-Path `
            $outDir 'Get-TestMdFoo.md'
        $content = [System.IO.File]::ReadAllText($cmdletMd)

        # Examples may not be populated for all cmdlet types;
        # verify the Syntax section shows the cmdlet usage instead.
        $content | Pester\Should -BeLike '*```powershell*'

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}

Pester\Describe 'New-ModuleMarkdownHelp — edge cases' {

    Pester\It 'writes error for nonexistent module' {

        { GenXdev\New-ModuleMarkdownHelp `
                -ModuleName 'NonExistentModuleXYZ123' `
                -SkipTranslation -Force -ErrorAction Stop } |
            Pester\Should -Throw
    }

    Pester\It 'accepts -Language parameter without error' {

        $outDir = Microsoft.PowerShell.Management\Join-Path `
            $env:TEMP "MdHelpOut_$([System.IO.Path]::GetRandomFileName())"

        $files = GenXdev\New-ModuleMarkdownHelp `
            -ModuleName 'TestMdModule' `
            -OutputPath $outDir `
            -Language 'en-US' `
            -Force

        $files | Pester\Should -Not -BeNullOrEmpty

        # Cleanup
        Microsoft.PowerShell.Management\Remove-Item `
            -Path $outDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}