tests/general/Help.Tests.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<#
    .NOTES
        The original test this is based upon was written by June Blender.
        After several rounds of modifications it stands now as it is, but the honor remains hers.
 
        Thank you June, for all you have done!
 
    .DESCRIPTION
        This test evaluates the help for all commands in a module.
 
    .PARAMETER SkipTest
        Disables this test.
     
    .PARAMETER CommandPath
        List of paths under which the script files are stored.
        This test assumes that all functions have their own file that is named after themselves.
        These paths are used to search for commands that should exist and be tested.
        Will search recursively and accepts wildcards, make sure only functions are found
 
    .PARAMETER ModuleName
        Name of the module to be tested.
        The module must already be imported
 
    .PARAMETER ExceptionsFile
        File in which exceptions and adjustments are configured.
        In it there should be two arrays and a hashtable defined:
            $global:FunctionHelpTestExceptions
            $global:HelpTestEnumeratedArrays
            $global:HelpTestSkipParameterType
        These can be used to tweak the tests slightly in cases of need.
        See the example file for explanations on each of these usage and effect.
#>

[CmdletBinding()]
Param (
    [switch]
    $SkipTest,
    
    [string[]]
    $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions"),
    
    [string]
    $ModuleName = "EbookBuilder",
    
    [string]
    $ExceptionsFile = "$global:testroot\general\Help.Exceptions.ps1"
)
if ($SkipTest) { return }
. $ExceptionsFile

$includedNames = (Get-ChildItem $CommandPath -Recurse -File | Where-Object Name -like "*.ps1").BaseName
$commandTypes = @('Cmdlet', 'Function')
if ($PSVersionTable.PSEdition -eq 'Desktop' ) { $commandTypes += 'Workflow' }
$commands = Get-Command -Module (Get-Module $ModuleName) -CommandType $commandTypes | Where-Object Name -In $includedNames

## When testing help, remember that help is cached at the beginning of each session.
## To test, restart session.


foreach ($command in $commands) {
    $commandName = $command.Name
    
    # Skip all functions that are on the exclusions list
    if ($global:FunctionHelpTestExceptions -contains $commandName) { continue }
    
    # The module-qualified command fails on Microsoft.PowerShell.Archive cmdlets
    $Help = Get-Help $commandName -ErrorAction SilentlyContinue
    
    Describe "Test help for $commandName" {
        
        # If help is not found, synopsis in auto-generated help is the syntax diagram
        It "should not be auto-generated" -TestCases @{ Help = $Help } {
            $Help.Synopsis | Should -Not -BeLike '*`[`<CommonParameters`>`]*'
        }
        
        # Should be a description for every function
        It "gets description for $commandName" -TestCases @{ Help = $Help } {
            $Help.Description | Should -Not -BeNullOrEmpty
        }
        
        # Should be at least one example
        It "gets example code from $commandName" -TestCases @{ Help = $Help } {
            ($Help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty
        }
    
        # Should be at least one example description
        It "gets example help from $commandName" -TestCases @{ Help = $Help } {
            ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty
        }
        
        Context "Test parameter help for $commandName" {
            
            $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable'
            
            $parameters = $command.ParameterSets.Parameters | Sort-Object -Property Name -Unique | Where-Object Name -notin $common
            $parameterNames = $parameters.Name
            $HelpParameterNames = $Help.Parameters.Parameter.Name | Sort-Object -Unique
            foreach ($parameter in $parameters) {
                $parameterName = $parameter.Name
                $parameterHelp = $Help.parameters.parameter | Where-Object Name -EQ $parameterName
            
                # Should be a description for every parameter
                It "gets help for parameter: $parameterName : in $commandName" -TestCases @{ parameterHelp = $parameterHelp } {
                    $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty
                }
                
                $codeMandatory = $parameter.IsMandatory.toString()
                It "help for $parameterName parameter in $commandName has correct Mandatory value" -TestCases @{ parameterHelp = $parameterHelp; codeMandatory = $codeMandatory } {
                    $parameterHelp.Required | Should -Be $codeMandatory
                }
                
                if ($HelpTestSkipParameterType[$commandName] -contains $parameterName) { continue }
                
                $codeType = $parameter.ParameterType.Name
                
                if ($parameter.ParameterType.IsEnum) {
                    # Enumerations often have issues with the typename not being reliably available
                    $names = $parameter.ParameterType::GetNames($parameter.ParameterType)
                        # Parameter type in Help should match code
                    It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } {
                        $parameterHelp.parameterValueGroup.parameterValue | Should -be $names
                    }
                }
                elseif ($parameter.ParameterType.FullName -in $HelpTestEnumeratedArrays) {
                    # Enumerations often have issues with the typename not being reliably available
                    $names = [Enum]::GetNames($parameter.ParameterType.DeclaredMembers[0].ReturnType)
                    It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } {
                        $parameterHelp.parameterValueGroup.parameterValue | Should -be $names
                    }
                }
                else {
                    # To avoid calling Trim method on a null object.
                    $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() }
                    # Parameter type in Help should match code
                    It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ helpType = $helpType; codeType = $codeType } {
                        $helpType | Should -be $codeType
                    }
                }
            }
            foreach ($helpParm in $HelpParameterNames) {
                # Shouldn't find extra parameters in help.
                It "finds help parameter in code: $helpParm" -TestCases @{ helpParm = $helpParm; parameterNames = $parameterNames } {
                    $helpParm -in $parameterNames | Should -Be $true
                }
            }
        }
    }
}