tests/QA/module.tests.ps1

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'

$modulePath = Resolve-Path "$here\..\.."
$moduleName = Split-Path -Path $modulePath -Leaf

Describe 'General module control' -Tags 'FunctionalQuality'   {

    It 'Should import without errors' {
        Write-Warning $modulePath.Path
        { Import-Module -Name $modulePath.Path -Force -ErrorAction Stop } | Should -Not -Throw
        Get-Module $moduleName | Should -Not -BeNullOrEmpty
    }

    It 'Should remove without error' {
        { Remove-Module -Name $moduleName -ErrorAction Stop} | Should -Not -Throw
        Get-Module $moduleName | Should -beNullOrEmpty
    }
}

#$PrivateFunctions = Get-ChildItem -Path "$modulePath\Private\*.ps1"
#$PublicFunctions = Get-ChildItem -Path "$modulePath\Public\*.ps1"
$allModuleFunctions = @()
$allModuleFunctions += Get-ChildItem -Path "$modulePath\Private\*.ps1" -ErrorAction SilentlyContinue
$allModuleFunctions += Get-ChildItem -Path "$modulePath\Public\*.ps1" -ErrorAction SilentlyContinue

if (Get-Command Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue) {
    $scriptAnalyzerRules = Get-ScriptAnalyzerRule
}
else {
    if ($ErrorActionPreference -ne 'Stop') {
        Write-Warning "ScriptAnalyzer not found!"
    }
    else {
        Throw "ScriptAnalyzer not found!"
    }
}

foreach ($function in $allModuleFunctions) {
    Describe "Quality for $($function.BaseName)" -Tags 'TestQuality' {
        It "$($function.BaseName) have a unit test" {
            Test-Path "$modulePath\tests\Unit\*\$($function.BaseName).tests.ps1" | Should -Be $true
        }

        if ($scriptAnalyzerRules) {
            It "Script Analyzer for $($function.BaseName)" {
                forEach ($scriptAnalyzerRule in $scriptAnalyzerRules) {
                    (Invoke-ScriptAnalyzer -Path $function.FullName -IncludeRule $scriptAnalyzerRule).count |
                         Should -Be 0
                }
            }
        }
    }

    Describe "Help for $($function.BaseName)" -Tags 'helpQuality' {
        $AbstractSyntaxTree = [System.Management.Automation.Language.Parser]::
            ParseInput((Get-Content -raw $function.FullName), [ref]$null, [ref]$null)
            $AstSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }
            $ParsedFunction = $AbstractSyntaxTree.FindAll( $AstSearchDelegate,$true )   |
                                Where-Object Name -eq $function.BaseName
        if ($ParsedFunction.GetHelpContent) {
            $FunctionHelp = $ParsedFunction.GetHelpContent()

            It 'Should have a SYNOPSIS' {
                $FunctionHelp.Synopsis | should -not -BeNullOrEmpty
            }

            It 'Should have a Description, with length > 40' {
                $FunctionHelp.Description.Length | Should -beGreaterThan 40
            }

            It 'Should have at least 1 example' {
                $FunctionHelp.Examples.Count | Should -beGreaterThan 0 
                $FunctionHelp.Examples[0] | Should -match ([regex]::Escape($function.BaseName))
                $FunctionHelp.Examples[0].Length | Should -BeGreaterThan ($function.BaseName.Length + 10)
            }

            if ($ParameterNames = $ParsedFunction.Body.ParamBlock.Parameters.name) {
                $parameters = $ParameterNames.VariablePath | ForEach-Object {$_.ToString() }
                foreach ($parameter in $parameters) {
                    It "Should have help for Parameter: $parameter" {
                        $FunctionHelp.Parameters.($parameter.ToUpper())        | Should -Not -BeNullOrEmpty
                        $FunctionHelp.Parameters.($parameter.ToUpper()).Length | Should -BeGreaterThan 25
                    }
                }
            }
        }
    }
}