Checks/Script.Tests.ps1

param(
    [parameter(Mandatory = $true)]
    [string[]]$Source,

    [parameter(Mandatory = $false)]
    [string]$SonarQubeRules
)

Describe "Script Tests" {

    foreach ($scriptFile in $Source) {

        $scriptProperties = (Get-Item -Path $scriptFile)

        Context "Script : $($scriptProperties.Name) at $($scriptProperties.Directory)" {

            # This needs to get the content of the file or the content of the function inside the file
            $fileContent = Get-FileContent -File $scriptFile

            if (-not([string]::IsNullOrEmpty($fileContent))) {
                ($ParsedFile, $ErrorCount) = Get-ParsedContent -Content $fileContent
            }
            else {
                Write-Warning "File is empty"
                $ParsedFile = $null
                $ErrorCount = 1
            }

            It "check script has valid PowerShell syntax" -TestCases @{ 'ErrorCount' = $ErrorCount } {

                $ErrorCount | Should -Be 0

            }

            It "check help must contain required elements" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                {

                    $helpComments = ($ParsedFile | Where-Object { $_.Type -eq "Comment" } | Select-Object -First 1)
                    if ([string]::IsNullOrEmpty($helpComments)) {
                        throw "No help block found"
                    }
                    $helpTokens = Convert-Help -HelpComment $helpComments.Content
                    Test-HelpForRequiredTokens -HelpTokens $helpTokens

                } |
                    Should -Not -Throw

            }
            It "check help must not contain unspecified elements" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                {

                    $helpComments = ($ParsedFile | Where-Object { $_.Type -eq "Comment" } | Select-Object -First 1)
                    if ([string]::IsNullOrEmpty($helpComments)) {
                        throw "No help block found"
                    }
                    $helpTokens = Convert-Help -HelpComment $helpComments.Content
                    Test-HelpForUnspecifiedTokens -HelpTokens $helpTokens

                } |
                    Should -Not -Throw

            }

            It "check help elements text is not empty" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                {

                    $helpComments = ($ParsedFile | Where-Object { $_.Type -eq "Comment" } | Select-Object -First 1)
                    if ([string]::IsNullOrEmpty($helpComments)) {
                        throw "No help block found"
                    }
                    $helpTokens = Convert-Help -HelpComment $helpComments.Content
                    Test-HelpTokensTextIsValid -HelpTokens $helpTokens

                } | Should -Not -Throw

            }

            It "check help elements Min/Max counts are valid" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                {

                    $helpComments = ($ParsedFile | Where-Object { $_.Type -eq "Comment" } | Select-Object -First 1)
                    if ([string]::IsNullOrEmpty($helpComments)) {
                        throw "No help block found"
                    }
                    $helpTokens = Convert-Help -HelpComment $helpComments.Content
                    Test-HelpTokensCountIsValid -HelpTokens $helpTokens

                } | Should -Not -Throw

            }

            It "check script contains [CmdletBinding] attribute" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                $cmdletBindingCount = (@(Get-TokenMarker -ParsedFileContent $ParsedFile -Type "Attribute" -Content "CmdletBinding")).Count

                $cmdletBindingCount | Should -Be 1

            }

            It "check script contains [OutputType] attribute" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                $outputTypeCount = (@(Get-TokenMarker -ParsedFileContent $ParsedFile -Type "Attribute" -Content "OutputType")).Count

                $outputTypeCount | Should -Be 1

            }

            It "check script [OutputType] attribute is not empty" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                $outputTypeToken = (Get-Token -ParsedFileContent $ParsedFile -Type "Attribute" -Content "OutputType")

                $outputTypeValue = @($outputTypeToken | Where-Object { $_.Type -eq "Type" })

                $outputTypeValue | Should -Not -BeNullOrEmpty

            }

            It "check script contains param attribute" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                $paramCount = (@(Get-TokenMarker -ParsedFileContent $ParsedFile -Type "Keyword" -Content "param")).Count

                $paramCount | Should -Be 1

            }

            It "check script param block variables have type" -TestCases @{ 'ParsedFile' = $ParsedFile; 'fileContent' = $fileContent } {

                $parameterVariables = Get-ScriptParameters -Content $fileContent

                if ($parameterVariables.Count -eq 0) {

                    Set-ItResult -Inconclusive -Because "No parameters found"

                }

                {

                    Test-ParameterVariablesHaveType -ParameterVariables $parameterVariables

                } | Should -Not -Throw

            }

            It "check .PARAMETER help matches variables in param block" -TestCases @{ 'ParsedFile' = $ParsedFile; 'fileContent' = $fileContent } {

                {

                    $helpComments = ($ParsedFile | Where-Object { $_.Type -eq "Comment" } | Select-Object -First 1)
                    if ([string]::IsNullOrEmpty($helpComments)) {
                        throw "No help block found"
                    }
                    $parameterVariables = Get-ScriptParameters -Content $fileContent
                    $helpTokens = Convert-Help -HelpComment $helpComments.Content

                    Test-HelpTokensParamsMatch -HelpTokens $helpTokens -ParameterVariables $parameterVariables

                } | Should -Not -Throw

            }

            It "check script contains no PSScriptAnalyzer suppressions" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                $suppressCount = (@(Get-TokenMarker -ParsedFileContent $ParsedFile -Type "Attribute" -Content "Diagnostics.CodeAnalysis.SuppressMessageAttribute")).Count

                $suppressCount | Should -Be 0

            }

            It "check script contains no PSScriptAnalyzer failures" -TestCases @{ 'scriptFile' = $scriptProperties.FullName } {

                $AnalyserFailures = @(Invoke-ScriptAnalyzer -Path $scriptFile)

                ($AnalyserFailures | ForEach-Object { $_.Message }) | Should -BeNullOrEmpty

            }

            It "check script contains no PSScriptAnalyser SonarQube rule failures" -TestCases @{ 'scriptFile' = $scriptProperties.FullName } {

                if ( [string]::IsNullOrEmpty($SonarQubeRules) ) {

                    Set-ItResult -Inconclusive -Because "No SonarQube PSScriptAnalyzer rules folder specified"

                }

                if ( -not (Test-Path -Path $SonarQubeRules -ErrorAction SilentlyContinue)) {

                    Set-ItResult -Inconclusive -Because "SonarQube PSScriptAnalyzer rules not found"

                }

                $AnalyserFailures = @(Invoke-ScriptAnalyzer -Path $scriptFile -CustomRulePath $SonarQubeRules)

                $AnalyserFailures | ForEach-Object { $_.Message } | Should -BeNullOrEmpty

            }

            It "check Import-Module statements have valid format" -TestCases @{ 'ParsedFile' = $ParsedFile } {

                $importModuleTokens = @($ParsedFile | Where-Object { $_.Type -eq "Command" -and $_.Content -eq "Import-Module" })

                if ($importModuleTokens.Count -eq 0) {

                    Set-ItResult -Inconclusive -Because "No Import-Module statements found"

                }

                {

                    Test-ImportModuleIsValid -ParsedFile $ParsedFile -ImportModuleTokens $importModuleTokens

                } | Should -Not -Throw

            }

        }

    }

}