Initial Commit

This commit is contained in:
DrIOS
2024-03-25 08:34:43 -05:00
commit 0226b293b5
137 changed files with 9448 additions and 0 deletions

243
tests/QA/module.tests.ps1 Normal file
View File

@@ -0,0 +1,243 @@
BeforeDiscovery {
$projectPath = "$($PSScriptRoot)\..\.." | Convert-Path
<#
If the QA tests are run outside of the build script (e.g with Invoke-Pester)
the parent scope has not set the variable $ProjectName.
#>
if (-not $ProjectName)
{
# Assuming project folder name is project name.
$ProjectName = Get-SamplerProjectName -BuildRoot $projectPath
}
$script:moduleName = $ProjectName
Remove-Module -Name $script:moduleName -Force -ErrorAction SilentlyContinue
$mut = Get-Module -Name $script:moduleName -ListAvailable |
Select-Object -First 1 |
Import-Module -Force -ErrorAction Stop -PassThru
}
BeforeAll {
# Convert-Path required for PS7 or Join-Path fails
$projectPath = "$($PSScriptRoot)\..\.." | Convert-Path
<#
If the QA tests are run outside of the build script (e.g with Invoke-Pester)
the parent scope has not set the variable $ProjectName.
#>
if (-not $ProjectName)
{
# Assuming project folder name is project name.
$ProjectName = Get-SamplerProjectName -BuildRoot $projectPath
}
$script:moduleName = $ProjectName
$sourcePath = (
Get-ChildItem -Path $projectPath\*\*.psd1 |
Where-Object -FilterScript {
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) `
-and $(
try
{
Test-ModuleManifest -Path $_.FullName -ErrorAction Stop
}
catch
{
$false
}
)
}
).Directory.FullName
}
Describe 'Changelog Management' -Tag 'Changelog' {
It 'Changelog has been updated' -Skip:(
-not ([bool](Get-Command git -ErrorAction SilentlyContinue) -and
[bool](&(Get-Process -Id $PID).Path -NoProfile -Command 'git rev-parse --is-inside-work-tree 2>$null'))
) {
<#
Get the list of changed files compared with branch main to verify
that required files are changed.
#>
# Only run if there is a remote called origin
if (((git remote) -match 'origin'))
{
$headCommit = &git rev-parse HEAD
$defaultBranchCommit = &git rev-parse origin/main
$filesChanged = &git @('diff', "$defaultBranchCommit...$headCommit", '--name-only')
}
$filesStagedAndUnstaged = &git @('diff', 'HEAD', '--name-only') 2>&1
$filesChanged += $filesStagedAndUnstaged
# Only check if there are any changed files.
if ($filesChanged)
{
$filesChanged | Should -Contain 'CHANGELOG.md' -Because 'the CHANGELOG.md must be updated with at least one entry in the Unreleased section for each PR'
}
}
It 'Changelog format compliant with keepachangelog format' -Skip:(![bool](Get-Command git -EA SilentlyContinue)) {
{ Get-ChangelogData -Path (Join-Path $ProjectPath 'CHANGELOG.md') -ErrorAction Stop } | Should -Not -Throw
}
It 'Changelog should have an Unreleased header' -Skip:$skipTest {
(Get-ChangelogData -Path (Join-Path -Path $ProjectPath -ChildPath 'CHANGELOG.md') -ErrorAction Stop).Unreleased | Should -Not -BeNullOrEmpty
}
}
Describe 'General module control' -Tags 'FunctionalQuality' {
It 'Should import without errors' {
{ Import-Module -Name $script:moduleName -Force -ErrorAction Stop } | Should -Not -Throw
Get-Module -Name $script:moduleName | Should -Not -BeNullOrEmpty
}
It 'Should remove without error' {
{ Remove-Module -Name $script:moduleName -ErrorAction Stop } | Should -Not -Throw
Get-Module $script:moduleName | Should -BeNullOrEmpty
}
}
BeforeDiscovery {
# Must use the imported module to build test cases.
$allModuleFunctions = & $mut { Get-Command -Module $args[0] -CommandType Function } $script:moduleName
# Build test cases.
$testCases = @()
foreach ($function in $allModuleFunctions)
{
$testCases += @{
Name = $function.Name
}
}
}
Describe 'Quality for module' -Tags 'TestQuality' {
BeforeDiscovery {
if (Get-Command -Name Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue)
{
$scriptAnalyzerRules = Get-ScriptAnalyzerRule
}
else
{
if ($ErrorActionPreference -ne 'Stop')
{
Write-Warning -Message 'ScriptAnalyzer not found!'
}
else
{
throw 'ScriptAnalyzer not found!'
}
}
}
It 'Should have a unit test for <Name>' -ForEach $testCases {
Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty
}
It 'Should pass Script Analyzer for <Name>' -ForEach $testCases -Skip:(-not $scriptAnalyzerRules) {
$functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1"
$pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName)
$report = $pssaResult | Format-Table -AutoSize | Out-String -Width 110
$pssaResult | Should -BeNullOrEmpty -Because `
"some rule triggered.`r`n`r`n $report"
}
}
Describe 'Help for module' -Tags 'helpQuality' {
It 'Should have .SYNOPSIS for <Name>' -ForEach $testCases {
$functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1"
$scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName
$abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null)
$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }
$parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) |
Where-Object -FilterScript {
$_.Name -eq $Name
}
$functionHelp = $parsedFunction.GetHelpContent()
$functionHelp.Synopsis | Should -Not -BeNullOrEmpty
}
It 'Should have a .DESCRIPTION with length greater than 40 characters for <Name>' -ForEach $testCases {
$functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1"
$scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName
$abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null)
$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }
$parsedFunction = $abstractSyntaxTree.FindAll($astSearchDelegate, $true) |
Where-Object -FilterScript {
$_.Name -eq $Name
}
$functionHelp = $parsedFunction.GetHelpContent()
$functionHelp.Description.Length | Should -BeGreaterThan 40
}
It 'Should have at least one (1) example for <Name>' -ForEach $testCases {
$functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1"
$scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName
$abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null)
$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }
$parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) |
Where-Object -FilterScript {
$_.Name -eq $Name
}
$functionHelp = $parsedFunction.GetHelpContent()
$functionHelp.Examples.Count | Should -BeGreaterThan 0
$functionHelp.Examples[0] | Should -Match ([regex]::Escape($function.Name))
$functionHelp.Examples[0].Length | Should -BeGreaterThan ($function.Name.Length + 10)
}
It 'Should have described all parameters for <Name>' -ForEach $testCases {
$functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1"
$scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName
$abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null)
$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }
$parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) |
Where-Object -FilterScript {
$_.Name -eq $Name
}
$functionHelp = $parsedFunction.GetHelpContent()
$parameters = $parsedFunction.Body.ParamBlock.Parameters.Name.VariablePath.ForEach({ $_.ToString() })
foreach ($parameter in $parameters)
{
$functionHelp.Parameters.($parameter.ToUpper()) | Should -Not -BeNullOrEmpty -Because ('the parameter {0} must have a description' -f $parameter)
$functionHelp.Parameters.($parameter.ToUpper()).Length | Should -BeGreaterThan 25 -Because ('the parameter {0} must have descriptive description' -f $parameter)
}
}
}

View File

@@ -0,0 +1,27 @@
$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path
$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
$(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } )
}).BaseName
Import-Module $ProjectName
InModuleScope $ProjectName {
Describe Get-PrivateFunction {
Context 'Default' {
BeforeEach {
$return = Get-PrivateFunction -PrivateData 'string'
}
It 'Returns a single object' {
($return | Measure-Object).Count | Should -Be 1
}
It 'Returns a string based on the parameter PrivateData' {
$return | Should -Be 'string'
}
}
}
}

View File

@@ -0,0 +1,27 @@
$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path
$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
$(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } )
}).BaseName
Import-Module $ProjectName
InModuleScope $ProjectName {
Describe Get-PrivateFunction {
Context 'Default' {
BeforeEach {
$return = Get-PrivateFunction -PrivateData 'string'
}
It 'Returns a single object' {
($return | Measure-Object).Count | Should -Be 1
}
It 'Returns a string based on the parameter PrivateData' {
$return | Should -Be 'string'
}
}
}
}

View File

@@ -0,0 +1,27 @@
$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path
$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
$(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } )
}).BaseName
Import-Module $ProjectName
InModuleScope $ProjectName {
Describe Get-PrivateFunction {
Context 'Default' {
BeforeEach {
$return = Get-PrivateFunction -PrivateData 'string'
}
It 'Returns a single object' {
($return | Measure-Object).Count | Should -Be 1
}
It 'Returns a string based on the parameter PrivateData' {
$return | Should -Be 'string'
}
}
}
}

View File

@@ -0,0 +1,31 @@
BeforeAll {
$script:dscModuleName = 'M365FoundationsCISReport'
Import-Module -Name $script:dscModuleName
}
AfterAll {
# Unload the module being tested so that it doesn't impact any other tests.
Get-Module -Name $script:dscModuleName -All | Remove-Module -Force
}
Describe Get-PrivateFunction {
Context 'When calling the function with string value' {
It 'Should return a single object' {
InModuleScope -ModuleName $dscModuleName {
$return = Get-PrivateFunction -PrivateData 'string'
($return | Measure-Object).Count | Should -Be 1
}
}
It 'Should return a string based on the parameter PrivateData' {
InModuleScope -ModuleName $dscModuleName {
$return = Get-PrivateFunction -PrivateData 'string'
$return | Should -Be 'string'
}
}
}
}

View File

@@ -0,0 +1,91 @@
BeforeAll {
$script:dscModuleName = 'M365FoundationsCISReport'
Import-Module -Name $script:dscModuleName
}
AfterAll {
# Unload the module being tested so that it doesn't impact any other tests.
Get-Module -Name $script:dscModuleName -All | Remove-Module -Force
}
Describe Get-Something {
BeforeAll {
Mock -CommandName Get-PrivateFunction -MockWith {
# This return the value passed to the Get-PrivateFunction parameter $PrivateData.
$PrivateData
} -ModuleName $dscModuleName
}
Context 'When passing values using named parameters' {
It 'Should call the private function once' {
{ Get-Something -Data 'value' } | Should -Not -Throw
Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 1 -Scope It -ModuleName $dscModuleName
}
It 'Should return a single object' {
$return = Get-Something -Data 'value'
($return | Measure-Object).Count | Should -Be 1
}
It 'Should return the correct string value' {
$return = Get-Something -Data 'value'
$return | Should -Be 'value'
}
}
Context 'When passing values over the pipeline' {
It 'Should call the private function two times' {
{ 'value1', 'value2' | Get-Something } | Should -Not -Throw
Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 2 -Scope It -ModuleName $dscModuleName
}
It 'Should return an array with two items' {
$return = 'value1', 'value2' | Get-Something
$return.Count | Should -Be 2
}
It 'Should return an array with the correct string values' {
$return = 'value1', 'value2' | Get-Something
$return[0] | Should -Be 'value1'
$return[1] | Should -Be 'value2'
}
It 'Should accept values from the pipeline by property name' {
$return = 'value1', 'value2' | ForEach-Object {
[PSCustomObject]@{
Data = $_
OtherProperty = 'other'
}
} | Get-Something
$return[0] | Should -Be 'value1'
$return[1] | Should -Be 'value2'
}
}
Context 'When passing WhatIf' {
It 'Should support the parameter WhatIf' {
(Get-Command -Name 'Get-Something').Parameters.ContainsKey('WhatIf') | Should -Be $true
}
It 'Should not call the private function' {
{ Get-Something -Data 'value' -WhatIf } | Should -Not -Throw
Should -Invoke -CommandName Get-PrivateFunction -Exactly -Times 0 -Scope It -ModuleName $dscModuleName
}
It 'Should return $null' {
$return = Get-Something -Data 'value' -WhatIf
$return | Should -BeNullOrEmpty
}
}
}

View File

@@ -0,0 +1,71 @@
BeforeAll {
$script:moduleName = '<% $PLASTER_PARAM_ModuleName %>'
# If the module is not found, run the build task 'noop'.
if (-not (Get-Module -Name $script:moduleName -ListAvailable))
{
# Redirect all streams to $null, except the error stream (stream 2)
& "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null
}
# Re-import the module using force to get any code changes between runs.
Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop'
$PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName
$PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName
$PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName
}
AfterAll {
$PSDefaultParameterValues.Remove('Mock:ModuleName')
$PSDefaultParameterValues.Remove('InModuleScope:ModuleName')
$PSDefaultParameterValues.Remove('Should:ModuleName')
Remove-Module -Name $script:moduleName
}
Describe Get-Something {
Context 'Return values' {
BeforeEach {
$return = Get-Something -Data 'value'
}
It 'Returns a single object' {
($return | Measure-Object).Count | Should -Be 1
}
}
Context 'Pipeline' {
It 'Accepts values from the pipeline by value' {
$return = 'value1', 'value2' | Get-Something
$return[0] | Should -Be 'value1'
$return[1] | Should -Be 'value2'
}
It 'Accepts value from the pipeline by property name' {
$return = 'value1', 'value2' | ForEach-Object {
[PSCustomObject]@{
Data = $_
OtherProperty = 'other'
}
} | Get-Something
$return[0] | Should -Be 'value1'
$return[1] | Should -Be 'value2'
}
}
Context 'ShouldProcess' {
It 'Supports WhatIf' {
(Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true
{ Get-Something -Data 'value' -WhatIf } | Should -Not -Throw
}
}
}