From e066db0ac85a0979974db0bf23fdc560c5985f74 Mon Sep 17 00:00:00 2001 From: RobertDutch <83699366+RobertDutch@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:42:30 +0200 Subject: [PATCH 1/6] Create Test-M365DSCPowershellDataFile.ps1 --- .../Public/Test-M365DSCPowershellDataFile.ps1 | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 source/Public/Test-M365DSCPowershellDataFile.ps1 diff --git a/source/Public/Test-M365DSCPowershellDataFile.ps1 b/source/Public/Test-M365DSCPowershellDataFile.ps1 new file mode 100644 index 0000000..31adeaf --- /dev/null +++ b/source/Public/Test-M365DSCPowershellDataFile.ps1 @@ -0,0 +1,364 @@ +Function Test-M365DSCPowershellDataFile { + <# + .Synopsis + Tests the specified object against the information defined in the ExampleData + from the M365DSC.CompositeResources module. + + .Description + This function tests the specified object against the information defined in the + ExampleData from the M365DSC.CompositeResources module. It creates a Pester test + to check if the specified data and types are correct, as specified in the example + data. + + .Parameter Test + Specifies the type of tests to run. Allowed values are: + 'TypeValue', 'Required', 'Mandatory', 'TypeValue/Required', 'TypeValue/Mandatory', 'Required/Mandatory', 'TypeValue/Required/Mandatory'. + + .Parameter InputObject + The object that contains the data object that needs to be tested. + + .Parameter MandatoryObject + The object that contains the Mandatory data. + + .Parameter Exclude_AvailableAsResource + All items that are available as a resource and have to be ignored. + + .Parameter Exclude_Required + Required items have to be ignored. + + .Parameter Verbosity + Specifies the verbosity level of the output. Allowed values are: + None', 'Detailed', 'Diagnostic'. Default is 'Detailed'. + + .Parameter StackTraceVerbosity + Specifies the verbosity level of the output. Allowed values are: + 'None', 'FirstLine', 'Filtered', 'Full'. Default is 'Firstline'. + + .Parameter PesterScript + If specified, the generated Pester script will be opened in an editor. + + .Example + $Result = Test-M365DSCPowershellDataFile -Test TypeValue/Required -InputObject $InputObject + + .Example + $Result = Test-M365DSCPowershellDataFile -Test TypeValue/Required/Mandatory ` + -InputObject $InputObject ` + -MandatoryObject $MandatoryObject ` + -Exclude_AvailableAsResource *CimInstance, *UniqueID, *IsSingleInstance ` + -Exclude_Required *CimInstance, *UniqueID ` + -Verbosity Detailed ` + -StackTraceVerbosity None ` + -PesterScript ` + -Verbose + + .NOTES + This function requires Modules: M365DSC.CompositeResources, ObjectGraphTools + + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateSet('TypeValue', 'Required', 'Mandatory', 'TypeValue/Required', 'TypeValue/Mandatory', 'Required/Mandatory', 'TypeValue/Required/Mandatory' )] + [String]$Test, + + [Parameter(Mandatory = $False)] + [ValidateSet('None', 'Detailed', 'Diagnostic')] + [String]$Verbosity = 'Detailed', + + [Parameter(Mandatory = $False)] + [ValidateSet('None', 'FirstLine', 'Filtered', 'Full')] + [String]$StackTraceVerbosity = 'FirstLine', + + [Parameter(Mandatory = $False)] + [Switch]$PesterScript + ) + + DynamicParam { + $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + + $AttributeCollection1 = New-Object System.Collections.ObjectModel.Collection[System.Attribute] + $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute + $ParameterAttribute.Mandatory = $true + $AttributeCollection1.Add($ParameterAttribute) + + $AttributeCollection2 = New-Object System.Collections.ObjectModel.Collection[System.Attribute] + $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute + $ParameterAttribute.Mandatory = $False + $AttributeCollection2.Add($ParameterAttribute) + + if ($Null -ne $Test ) { + $ParamName = 'InputObject' + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName, [System.Object], $AttributeCollection1) + $RuntimeParameterDictionary.Add($ParamName, $RuntimeParameter) + + $ParamName = 'Exclude_AvailableAsResource' + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName, [String[]], $AttributeCollection2) + $RuntimeParameterDictionary.Add($ParamName, $RuntimeParameter) + } + + if ($Test -like '*Mandatory*') { + $ParamName = 'MandatoryObject' + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName, [System.Object], $AttributeCollection1) + $RuntimeParameterDictionary.Add($ParamName, $RuntimeParameter) + } + + if ($Test -like '*Required*') { + $ParamName = 'Exclude_Required' + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName, [String[]], $AttributeCollection2) + $RuntimeParameterDictionary.Add($ParamName, $RuntimeParameter) + } + + return $RuntimeParameterDictionary + } + + begin { + + $InputObject = $PSboundParameters['InputObject'] + $MandatoryObject = $PSboundParameters['MandatoryObject'] + $Exclude_AvailableAsResource = $PSboundParameters['Exclude_AvailableAsResource'] + $Exclude_Required = $PSboundParameters['Exclude_Required'] + + Class M365DSC_Reference_Values { + [string]$Type + [string]$Required + [String]$Description + [String]$ValidateSet + + M365DSC_Reference_Values([string]$InputString) { + [array]$Result = $InputString.split('|').foreach{ $_.trim() } + $this.type = $Result[0] + $this.Required = $Result[1] + $this.Description = $Result[2] + $This.ValidateSet = $Result[3].foreach{ "'" + ($Result[3] -Replace '\s*\/\s*', "', '") + "'" } + } + } + function ShowElapsed { + param( [Switch]$Reset ) + if ($Reset) { + $global:TotalTime = get-date + $global:ElapsedTime = get-date + } + if (-not $global:TotalTime ) { $global:TotalTime = get-date } + if (-not $global:ElapsedTime ) { $global:ElapsedTime = get-date } + $Result = '^ Elapsed: {0} TotalTime: {1} seconds' -f "$(($(get-date) - $ElapsedTime).TotalSeconds) Seconds".PadRight(30, " ") , ($(get-date) - $TotalTime).TotalSeconds + $global:ElapsedTime = Get-date + return $Result + } + + Function Space { + param([string]$Depth) + if ($Depth -ge 3) { $Depth = 3 } + Return " " * ($Depth) + } + + function Test-IsGuid { + [CmdletBinding()][OutputType([bool])] + param([Parameter(Mandatory = $true, ValueFromPipeline = $True)][string]$StringGuid) + try { + $ObjectGuid = [System.Guid]::Empty + return [System.Guid]::TryParse($StringGuid, [ref]$ObjectGuid) + } + catch { + Write-Error "An error occurred while checking the GUID format: $_" + return $false + } + } + + function Pester_Type_Should_Command { + [CmdletBinding()][OutputType([System.String])] + param([Parameter(Mandatory = $true)][string]$Type) + try { + switch ($Type) { + 'SInt32' { return "should -match '^\d+$' -because 'Must be a positive Integer'" } + 'SInt64' { return "should -match '^\d+$' -because 'Must be a positive Integer'" } + 'UInt32' { return "should -BeOfType 'Int'" } + 'UInt64' { return "should -BeOfType 'Int'" } + 'Guid' { return "Test-IsGuid | should -Be 'True'" } + default { return "should -BeOfType '$Type'" } + } + } + catch { + Write-Error "An error occurred while generating Pester 'should' assertion: $_" + return $null + } + } + + Function Recursive_Node { + param ([psnode]$InputObject) + if ($InputObject.count -eq 0 ) { return } + + + $Childs = $InputObject | Get-ChildNode + # Required + if ($Test -like '*Required*') { + $LeafParent = $True + $Childs.foreach{ if ( $_ -isnot [PSLeafNode]) { $LeafParent = $false } } + if ($LeafParent) { + $RefChilds = Invoke-Expression $('$Obj_M365DataExample.{0}' -f $($node.Path) -replace "\[\d+\]", '[0]' ) + if ($RefChilds) { + $Required_Nodes = $RefChilds.GetEnumerator().Where{ $_.value -like '*| Required |*' }.name + foreach ($Required_Node in $Required_Nodes) { + $Exclude_R = $Exclude_Required | where-object { $('{0}.{1}' -f $Node.Path, $Required_Node) -like $_ } #Check if is Exclude + " {0}{3}`$InputObject.{1}.{2} | should -not -BeNullOrEmpty -Because 'Required setting'" -f $(space $node.depth), $Node.path, $Required_Node, $(if ($Exclude_R.Length) { "#" }) + } + } + } + } + + Foreach ($Node in $Childs) { + [Bool]$IsHashTable = $False + [bool]$IsArray = $False + + $Exclude_A = $Exclude_AvailableAsResource | where-object { $Node.path -like $_ } #Check if is Exclude + $HashKey = if ($Exclude_A.Length) { "#" } else { "" } + + if ($Node.count -eq 0 ) { return } + if ( $node.Depth -le 3 ) { ' {0}It "{1}" {2}{{' -f $(space $node.depth), $Node.path, $(if ($Exclude_A.Length) { "-skip" } )} + + + if ((-not $IsArray) -and (-not $IsHashTable) -and ($Test -like "*TypeValue*")) { + + [Bool]$IsHashTable = Invoke-Expression $('$Obj_M365DataExample.{0} -is [HashTable]' -f $($node.Path) -replace "\[\d+\]", '[0]' ) + if ($IsHashTable) { " {0}{1}`$InputObject.{2} -is [HashTable] | should -BeTrue " -f $(space $node.depth), $HashKey, $node.path } + else { + [bool]$IsArray = Invoke-Expression $('$Obj_M365DataExample.{0} -is [Object[]]' -f $($node.Path) -replace "\[\d+\]", '[0]' ) + if ($IsArray) { " {0}{1}`$InputObject.{2} -is [Array] | should -BeTrue " -f $(space $node.depth), $HashKey, $node.path } + } + + $RefResult = Invoke-Expression $('$Obj_M365DataExample.{0}' -f $($node.Path) -replace "\[\d+\]", '[0]' ) + if ($RefResult -is [string]) { + $Obj_RefResult = [M365DSC_Reference_Values]::new($RefResult) + if ($Obj_RefResult.type) { + # Type Validation + " {0}{1}`$InputObject.{2} | {3}" -f $(space $node.depth), $HashKey, $node.path , $(Pester_Type_Should_Command $Obj_RefResult.type) + if ($Obj_RefResult.ValidateSet) { + # ValidationSet Validation + " {0}{1}`$InputObject.{2} | should -beIn {3}" -f $(space $node.depth), $HashKey, $node.path, $Obj_RefResult.ValidateSet + } + } + } + if ($null -eq $RefResult ) { + " {0}{1}`$InputObject.{2} | should -BeNullOrEmpty -because 'Not available as Composite Resource'" -f $(space $node.depth), $HashKey , $node.path + } + + } + + if ($node -isnot 'PSLeafNode' ) { Recursive_Node $Node } # Recursive call + if ( $Node.Depth -eq 3 ) { ' {0}}}' -f $(space $node.depth) } + + } + } + + Function MandatoryObject { + param ($Workload) + if ($Test -like "*Mandatory*") { + ' It "{0} test Mandatory settings" {{' -f $Workload.name + $node_MandatoryObject = $MandatoryObject.NonNodeData.$($Workload.name) | Get-ChildNode -Leaf -Recurse + $node_MandatoryObject.ForEach{ + " `$InputObject.NonNodeData.{0}.{1} | should -be '{2}'" -f $($Workload.name), $_.path, $_.value + } + ' }' + } + } + + } + process { + $global:TotalTime = get-date + ShowElapsed -Reset | out-null + '-- Function Test-M365DSCPowershellDataFile ------------------------------ ' | Write-log + 'Tests selected: {0}' -f $Test | write-log + 'Exclude AvailableAsResource: {0}' -f ($Exclude_AvailableAsResource -join "; " ) | write-log + 'ExcludeRequired: {0}' -f ($Exclude_Required -join "; ") | write-log + + 'Load InputObject as type [psnode]' | Write-log + $Node_InputObject = $InputObject | get-node + ShowElapsed | write-log -Verbose + + if ($MandatoryObject) { + 'Load MandatoryObject as type [psnode] ' | Write-log + $Node_MandatoryObject = $MandatoryObject | get-node + ShowElapsed | write-log -Verbose + } + + 'Load Example data from module M365DSC.CompositeResources' | Write-log + $Obj_M365DataExample = @( + if (Get-Module M365DSC.CompositeResources) { + Import-PSDataFile -Path ((((Get-Module M365DSC.CompositeResources)).path | Split-Path) + '\M365ConfigurationDataExample.psd1') + } + else { + Import-PSDataFile -Path (((Get-Module -ListAvailable M365DSC.CompositeResources).path | Split-Path) + '\M365ConfigurationDataExample.psd1') + } + ) + ShowElapsed | write-log -Verbose + + # Generate pesterscript + 'Generate pesterscript' | Write-Log + $Pester_Config = @( + "#Requires -Modules Pester" + + "Describe '--- Check M365-DSC-CompositeResources configuration ---' {" + foreach ($Workload in $( $Node_InputObject | Get-node("NonNodeData") | Get-ChildNode)) { + + if ($Test -like "*TypeValue*" -or $Test -like "*Required*") { + " Context 'Workload {0}' {{" -f $Workload.Name + " Context 'Type/Validatieset/Required' {" + Recursive_Node -InputObject $($Workload | get-node) + " }" + } + + if ($Test -like "*Mandatory*") { + " Context 'Mandatory' {" + MandatoryObject -Workload $($Workload) + " }" + } + if ($Test -like "*TypeValue*" -or $Test -like "*Required*") { " }" } + } + "}" + ) + ShowElapsed | Write-Log -verbose + + try { + # Ensure that $Pester_Config is defined + if (-not $Pester_Config) { throw "The variable `\$Pester_Config` is not defined." } + + # Create a temporary Pester script file + $PesterScriptPath = [System.IO.Path]::ChangeExtension((New-TemporaryFile).FullName, '.tests.ps1') + $Pester_Config | Out-File -FilePath $PesterScriptPath -Force -Confirm:$false -Encoding ascii + + # Open the Pester script in VSCode + if ($PesterScript) { psedit $PesterScriptPath } + + # Set parameters for the Pester container + $PesterParams = [ordered]@{ Path = $PesterScriptPath } + $PesterContainer = New-PesterContainer @PesterParams + + # Configure Pester settings + $PesterConfiguration = [PesterConfiguration]@{ + Run = @{ Container = $PesterContainer; PassThru = $true } + Should = @{ ErrorAction = "Continue" } + Output = @{ Verbosity = $Verbosity; StackTraceVerbosity = $StackTraceVerbosity } + } + + # Execute Pester tests and store results + 'Execute pesterscript' | Write-Log + $PesterResult = Invoke-Pester -Configuration $PesterConfiguration + return $PesterResult + } + catch { + Write-Error "An error occurred: $_" + } + finally { + # Clean up the temporary file + if (Test-Path -Path $PesterScriptPath) { + Remove-Item -Path $PesterScriptPath -Force -ErrorAction SilentlyContinue + } + + ShowElapsed | Write-Log -Verbose + + if ( $pesterResult.FailedCount -gt 0) { $Splat = @{ Failure = $true } } else { $Splat = @{} } + 'Pester[{0}] Tests:{1} Passed:{2} Failed:{3} ' -f $PesterResult.version, $PesterResult.TotalCount, $PesterResult.PassedCount, $pesterResult.FailedCount | write-log @splat + + '-- End 2------------------------------------------------------------------ ' | Write-log + } + } +} From bc73c1c75ae426a55e8ea2332c6e90d496c53ae6 Mon Sep 17 00:00:00 2001 From: Robertkoers Date: Fri, 5 Jul 2024 14:05:39 +0200 Subject: [PATCH 2/6] Suppress PSAvoidUsingInvokeExpression --- source/Public/Test-M365DSCPowershellDataFile.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Public/Test-M365DSCPowershellDataFile.ps1 b/source/Public/Test-M365DSCPowershellDataFile.ps1 index 31adeaf..8db7b9f 100644 --- a/source/Public/Test-M365DSCPowershellDataFile.ps1 +++ b/source/Public/Test-M365DSCPowershellDataFile.ps1 @@ -56,6 +56,7 @@ Function Test-M365DSCPowershellDataFile { #> [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '',Justification='Need Invoke Expression for performance, input is validated')] param ( [Parameter(Mandatory = $true)] [ValidateSet('TypeValue', 'Required', 'Mandatory', 'TypeValue/Required', 'TypeValue/Mandatory', 'Required/Mandatory', 'TypeValue/Required/Mandatory' )] From ba742577f27a51b79dc149e109efc7cb52a1e750 Mon Sep 17 00:00:00 2001 From: Robertkoers Date: Fri, 5 Jul 2024 14:14:11 +0200 Subject: [PATCH 3/6] Add change to Changelog --- CHANGELOG.md | 3 ++ .../Public/Test-M365DSCPowershellDataFile.ps1 | 32 ++++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a59d72c..299603e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed issue in Convert-M365DSCExportToPowerShellDataFile where the created data file was in ASCII format, causing special characters to be displayed incorrectly. +### Changed +- Function Test-M365DSCPowershellDataFile added as replacement for Test-M365PowershellDataFile + ## [0.2.11] - 2024-06-19 ### Changed diff --git a/source/Public/Test-M365DSCPowershellDataFile.ps1 b/source/Public/Test-M365DSCPowershellDataFile.ps1 index 8db7b9f..de96eaa 100644 --- a/source/Public/Test-M365DSCPowershellDataFile.ps1 +++ b/source/Public/Test-M365DSCPowershellDataFile.ps1 @@ -56,7 +56,7 @@ Function Test-M365DSCPowershellDataFile { #> [CmdletBinding()] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '',Justification='Need Invoke Expression for performance, input is validated')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Need Invoke Expression for performance, input is validated')] param ( [Parameter(Mandatory = $true)] [ValidateSet('TypeValue', 'Required', 'Mandatory', 'TypeValue/Required', 'TypeValue/Mandatory', 'Required/Mandatory', 'TypeValue/Required/Mandatory' )] @@ -136,13 +136,13 @@ Function Test-M365DSCPowershellDataFile { function ShowElapsed { param( [Switch]$Reset ) if ($Reset) { - $global:TotalTime = get-date - $global:ElapsedTime = get-date + $script:TotalTime = get-date + $script:ElapsedTime = get-date } - if (-not $global:TotalTime ) { $global:TotalTime = get-date } - if (-not $global:ElapsedTime ) { $global:ElapsedTime = get-date } + if (-not $script:TotalTime ) { $script:TotalTime = get-date } + if (-not $script:ElapsedTime ) { $script:ElapsedTime = get-date } $Result = '^ Elapsed: {0} TotalTime: {1} seconds' -f "$(($(get-date) - $ElapsedTime).TotalSeconds) Seconds".PadRight(30, " ") , ($(get-date) - $TotalTime).TotalSeconds - $global:ElapsedTime = Get-date + $script:ElapsedTime = Get-date return $Result } @@ -155,13 +155,15 @@ Function Test-M365DSCPowershellDataFile { function Test-IsGuid { [CmdletBinding()][OutputType([bool])] param([Parameter(Mandatory = $true, ValueFromPipeline = $True)][string]$StringGuid) - try { - $ObjectGuid = [System.Guid]::Empty - return [System.Guid]::TryParse($StringGuid, [ref]$ObjectGuid) - } - catch { - Write-Error "An error occurred while checking the GUID format: $_" - return $false + process { + try { + $ObjectGuid = [System.Guid]::Empty + return [System.Guid]::TryParse($StringGuid, [ref]$ObjectGuid) + } + catch { + Write-Error "An error occurred while checking the GUID format: $_" + return $false + } } } @@ -214,7 +216,7 @@ Function Test-M365DSCPowershellDataFile { $HashKey = if ($Exclude_A.Length) { "#" } else { "" } if ($Node.count -eq 0 ) { return } - if ( $node.Depth -le 3 ) { ' {0}It "{1}" {2}{{' -f $(space $node.depth), $Node.path, $(if ($Exclude_A.Length) { "-skip" } )} + if ( $node.Depth -le 3 ) { ' {0}It "{1}" {2}{{' -f $(space $node.depth), $Node.path, $(if ($Exclude_A.Length) { "-skip" } ) } if ((-not $IsArray) -and (-not $IsHashTable) -and ($Test -like "*TypeValue*")) { @@ -264,7 +266,7 @@ Function Test-M365DSCPowershellDataFile { } process { - $global:TotalTime = get-date + $script:TotalTime = get-date ShowElapsed -Reset | out-null '-- Function Test-M365DSCPowershellDataFile ------------------------------ ' | Write-log 'Tests selected: {0}' -f $Test | write-log From d2a37734dd4a7f064bddc6b526ca58ae3b41cccd Mon Sep 17 00:00:00 2001 From: Robertkoers Date: Fri, 5 Jul 2024 15:26:17 +0200 Subject: [PATCH 4/6] Update --- CHANGELOG.md | 5 +- .../Public/Test-M365DSCPowershellDataFile.ps1 | 2 + .../Test-M365DSCPowerShellDataFile.tests.ps1 | 118 ++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 299603e..b552e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Function Test-M365DSCPowershellDataFile added as replacement for Test-M365PowershellDataFile + ## [0.2.12] - 2024-07-03 ### Fixed @@ -12,8 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed issue in Convert-M365DSCExportToPowerShellDataFile where the created data file was in ASCII format, causing special characters to be displayed incorrectly. -### Changed -- Function Test-M365DSCPowershellDataFile added as replacement for Test-M365PowershellDataFile ## [0.2.11] - 2024-06-19 diff --git a/source/Public/Test-M365DSCPowershellDataFile.ps1 b/source/Public/Test-M365DSCPowershellDataFile.ps1 index de96eaa..cf846ab 100644 --- a/source/Public/Test-M365DSCPowershellDataFile.ps1 +++ b/source/Public/Test-M365DSCPowershellDataFile.ps1 @@ -57,6 +57,8 @@ Function Test-M365DSCPowershellDataFile { #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Need Invoke Expression for performance, input is validated')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Bug powershell, Vars are declared')] + param ( [Parameter(Mandatory = $true)] [ValidateSet('TypeValue', 'Required', 'Mandatory', 'TypeValue/Required', 'TypeValue/Mandatory', 'Required/Mandatory', 'TypeValue/Required/Mandatory' )] diff --git a/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 b/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 new file mode 100644 index 0000000..9ede8d7 --- /dev/null +++ b/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 @@ -0,0 +1,118 @@ +BeforeAll { + $script:moduleName = 'M365DSCTools' + + # 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' > $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 Test-M365DSCPowershellDataFile { + BeforeAll { + $PesterPreference = [PesterConfiguration]::Default + $PesterPreference.Output.Verbosity = 'Detailed' + $PesterPreference.Output.StackTraceVerbosity = "Firstline" + + Mock -CommandName Get-Module -MockWith { return @{ Path = 'C:\Temp\module.psd1'} } + Mock -CommandName Import-PSDataFile -MockWith { + return @{ + NonNodeData = @{ + 'Exchange' = @{ + Resource1 = @{ + Item1 = 'String | Required | Optional String' + Item2 = 'SInt32 | Optional | Optional SInt32' + } + } + 'Teams' = @{ + Resource2 = @( + @{ + UniqueId = 'String | Required | Required String' + Item3 = 'Boolean | Required | Required Boolean' + Item4 = 'Guid | Optional | Optional Guid' + } + ) + } + } + } + } -RemoveParameterType 'Path' + } + + Context 'Test function Test-M365DSCPowershellDataFile' { + It 'Should successfully test of the inputted object' { + $Object = @{ + NonNodeData = @{ + 'Exchange' = @{ + Resource1 = @{ + Item1 = 'String' + Item2 = 5 + } + } + 'Teams' = @{ + Resource2 = @( + @{ + UniqueId = "Test" + Item3 = $true + Item4 = 'c25d6579-be90-4484-81ef-9280d4817440' + } + @{ + UniqueId = "Test2" + Item3 = $false + Item4 = 'c25d6579-be90-4484-81ef-9280d4817441' + } + ) + } + } + } + + $result = Test-M365DSCPowershellDataFile -Test TypeValue -InputObject $Object -Verbosity None + $result.Result | Should -Be 'Passed' + } + + It 'Should fail test of the inputted object' { + $Object = @{ + NonNodeData = @{ + 'Exchange' = @{ + Resource1 = @{ + Item1 = 'String' + Item2 = 5 + } + } + 'Teams' = @{ + Resource2 = @( + @{ + UniqueId = "Test" + Item3 = $true + Item4 = 'c25d6579-be90-4484-81ef-9280d4817440' + } + @{ + UniqueId = "Test2" + Item5 = $false + } + ) + } + } + } + + $result = Test-M365DSCPowershellDataFile -Test TypeValue -InputObject $Object -Verbosity None + $result.Result | Should -Be 'Failed' + } + } +} + From bb3cf971ac4975c714acd82c44ed723f29d0fc10 Mon Sep 17 00:00:00 2001 From: Robertkoers Date: Tue, 9 Jul 2024 13:37:58 +0200 Subject: [PATCH 5/6] add fix for ObjectGraphTools module --- source/Public/Test-M365DSCPowershellDataFile.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/Public/Test-M365DSCPowershellDataFile.ps1 b/source/Public/Test-M365DSCPowershellDataFile.ps1 index cf846ab..e99f7fa 100644 --- a/source/Public/Test-M365DSCPowershellDataFile.ps1 +++ b/source/Public/Test-M365DSCPowershellDataFile.ps1 @@ -121,6 +121,11 @@ Function Test-M365DSCPowershellDataFile { $Exclude_AvailableAsResource = $PSboundParameters['Exclude_AvailableAsResource'] $Exclude_Required = $PSboundParameters['Exclude_Required'] + # Test if the ObjectGraphTools module is loaded and the class is available + if (-not ([System.Management.Automation.PSTypeName]'PSNode').Type) { + Import-Module ObjectGraphTools -Force + } + Class M365DSC_Reference_Values { [string]$Type [string]$Required From 5ea330d992a2baf30c16f82e1a364e35edacdc55 Mon Sep 17 00:00:00 2001 From: Robertkoers Date: Tue, 9 Jul 2024 14:22:44 +0200 Subject: [PATCH 6/6] add mock to supress write-log --- tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 b/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 index 9ede8d7..0a5d8d0 100644 --- a/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 +++ b/tests/Unit/Public/Test-M365DSCPowerShellDataFile.tests.ps1 @@ -31,6 +31,7 @@ Describe Test-M365DSCPowershellDataFile { $PesterPreference.Output.StackTraceVerbosity = "Firstline" Mock -CommandName Get-Module -MockWith { return @{ Path = 'C:\Temp\module.psd1'} } + Mock -CommandName Write-Log -MockWith {} Mock -CommandName Import-PSDataFile -MockWith { return @{ NonNodeData = @{