Tests/Module.Tests.ps1
Set-StrictMode -Version Latest $TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent $ModuleRoot = Resolve-Path "$TestScriptRoot\.." $ModuleManifest = "$ModuleRoot\AtomicTestHarnesses.psd1" Remove-Module [A]tomicTestHarnesses $Module = Import-Module $ModuleManifest -Force -ErrorAction Stop -PassThru $ExportedFunctionList = $Module.ExportedCommands.Keys Describe 'Module-wide tests' -Tag 'Module' { foreach ($FunctionName in $ExportedFunctionList) { Context "Required exported command naming scheme for $FunctionName" { It 'should have an "ATH" noun prefix' -Tag 'Module' { $FunctionInfo = Get-Item -Path Function:$FunctionName $FunctionInfo.Noun.Substring(0, 3) | Should -BeExactly 'ATH' } -TestCases @(@{ FunctionName = $FunctionName }) } Context "Pester test checks for $FunctionName" { It 'should have test code written for the exported function' -Tag 'Module' { $FunctionInfo = Get-Item -Path Function:$FunctionName $FunctionFilePath = $FunctionInfo.ScriptBlock.File $FunctionFileInfo = Get-Item -Path $FunctionFilePath $FunctionDirectory = $FunctionFileInfo.DirectoryName $FunctionFileBaseName = $FunctionFileInfo.BaseName # The filename without the extension $TestFilePath = Join-Path -Path $FunctionDirectory -ChildPath "$FunctionFileBaseName.Tests.ps1" Test-Path -Path $TestFilePath -PathType Leaf | Should -BeTrue $TestFileInfo = Get-Item -Path $TestFilePath $TestFileInfo | Should -Not -BeNullOrEmpty # For now, just validate that it isn't an empty file. # Until Pester can better discover tests without executing them, we'll hold off on incorporating more thorough logic. $TestFileInfo.Length | Should -BeGreaterThan 0 } -TestCases @(@{ FunctionName = $FunctionName }) } Context "Comment-based help for: $FunctionName" { $Script:Help = Get-Help -Name $FunctionName -Full # Parse the function using AST $Script:AST = [Management.Automation.Language.Parser]::ParseInput((Get-Content Function:$FunctionName), [ref]$null, [ref]$null) It 'should contain a .SYNOPSIS block' -Tag 'Module' { $Help = Get-Help -Name $FunctionName -Full $Help.Synopsis | Should -Not -BeNullOrEmpty } -TestCases @(@{ FunctionName = $FunctionName }) It 'should have a .SYNOPSIS block that ends with a well-formatted attack technique ID' -Tag 'Module' { $Help = Get-Help -Name $FunctionName -Full $Help.Synopsis.Split("`r`n")[-1] -match '^(?-i:Technique ID: )(?<TechniqueID>\S+) (?<TechniqueDescription>\(.+\))$' | Should -BeTrue } -TestCases @(@{ FunctionName = $FunctionName }) It 'should contain a .DESCRIPTION block' -Tag 'Module' { $Help = Get-Help -Name $FunctionName -Full $Help.Description | Should -Not -BeNullOrEmpty } -TestCases @(@{ FunctionName = $FunctionName }) # Examples It 'should contain at least one .EXAMPLE block' -Tag 'Module' { $Help = Get-Help -Name $FunctionName -Full @($Help.Examples.Example.Code).Count | Should -BeGreaterThan 0 } -TestCases @(@{ FunctionName = $FunctionName }) It 'should contain a matching number of .PARAMETER blocks for all defined parameters and be documented accordingly' -Tag 'Module' { $Help = Get-Help -Name $FunctionName -Full $HelpParameters = @($Help.Parameters.Parameter) $AST = [Management.Automation.Language.Parser]::ParseInput((Get-Content Function:$FunctionName), [ref]$null, [ref]$null) $ASTParameters = @($AST.ParamBlock.Parameters.Name.Variablepath.Userpath) $NamedArgs = try { $AST.ParamBlock.Attributes.NamedArguments } catch { $null } if ($NamedArgs -and $NamedArgs.ArgumentName -contains 'SupportsShouldProcess') { $Count = $ASTParameters.Count + 2 # Accounting for -WhatIf and -Confirm } else { $Count = $ASTParameters.Count } $HelpParameters.Count | Should -Be $Count # Each defined parameter in help should have a defined description $HelpParameters | ForEach-Object { if ($ASTParameters -contains $_.Name) { $_.Description | Should -Not -BeNullOrEmpty } } } -TestCases @(@{ FunctionName = $FunctionName }) } } } |