Viscalyx.Assert.psm1
#Region '.\prefix.ps1' -1 $script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' Import-Module -Name $script:dscResourceCommonModulePath $script:viscalyxCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/Viscalyx.Common' Import-Module -Name $script:viscalyxCommonModulePath $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' #EndRegion '.\prefix.ps1' 8 #Region '.\Private\Get-TypeName.ps1' -1 <# .SYNOPSIS Gets the full type name of a value. .DESCRIPTION The `Get-TypeName` command returns the full type name of a value. If the value is `$null`, it returns the string 'null' instead of attempting to call GetType() on a null reference. .PARAMETER Value The value to get the type name for. .INPUTS None This command does not accept pipeline input. .OUTPUTS System.String Returns the full type name as a string, or 'null' if the value is null. .EXAMPLE Get-TypeName -Value 'Hello' Returns 'System.String'. .EXAMPLE Get-TypeName -Value $null Returns 'null'. .EXAMPLE Get-TypeName -Value 123 Returns 'System.Int32'. #> function Get-TypeName { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [AllowNull()] [System.Object] $Value ) if ($null -eq $Value) { return 'null' } return $Value.GetType().FullName } #EndRegion '.\Private\Get-TypeName.ps1' 57 #Region '.\Private\New-AssertionError.ps1' -1 <# .SYNOPSIS Creates a Pester assertion error record. .DESCRIPTION The `New-AssertionError` function creates a Pester-formatted error record with optional 'because' reasoning. This standardizes error creation across assertion commands. .PARAMETER Message The error message to include in the assertion error. .PARAMETER Because An optional reason or explanation for the assertion failure. .PARAMETER InvocationInfo The invocation information from the calling command, used to populate the error record with script name, line number, and line content. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.ErrorRecord Returns a Pester-formatted error record. .EXAMPLE New-AssertionError -Message "Property not found" -Because "it is required" -InvocationInfo $MyInvocation Creates an assertion error with a because clause. #> function New-AssertionError { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'This function does not change system state, it only creates an error record object.')] [CmdletBinding()] [OutputType([System.Management.Automation.ErrorRecord])] param ( [Parameter(Mandatory = $true)] [System.String] $Message, [Parameter()] [System.String] $Because, [Parameter(Mandatory = $true)] [System.Management.Automation.InvocationInfo] $InvocationInfo ) if ($Because) { # Insert 'because <reason>' before ', but' to follow Pester's pattern: # "Expected <value>, because <reason>, but got <actual>" if ($Message -match ',\s+but\s+') { $Message = $Message -replace ',\s+but\s+', (", {0} $Because, but " -f $script:localizedData.Common_WordBecause) } else { # Fallback: append at the end if no ', but' pattern found $Message += " {0} $Because" -f $script:localizedData.Common_WordBecause } } $errorRecord = [Pester.Factory]::CreateShouldErrorRecord( $Message, $InvocationInfo.ScriptName, $InvocationInfo.ScriptLineNumber, $InvocationInfo.Line.TrimEnd([System.Environment]::NewLine), $true ) return $errorRecord } #EndRegion '.\Private\New-AssertionError.ps1' 78 #Region '.\Private\Test-ObjectHasMethod.ps1' -1 <# .SYNOPSIS Tests whether an object has a specified method. .DESCRIPTION The `Test-ObjectHasMethod` function checks if the given object contains a method with the specified name. This function supports various object types including PSCustomObjects and .NET objects. .PARAMETER InputObject The object to test for the method. .PARAMETER MethodName The name of the method to check for. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Boolean Returns $true if the method exists, $false otherwise. .EXAMPLE Test-ObjectHasMethod -InputObject $myObject -MethodName 'ToString' Returns $true if $myObject has a method named 'ToString', otherwise $false. #> function Test-ObjectHasMethod { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.Object] $InputObject, [Parameter(Mandatory = $true)] [System.String] $MethodName ) $hasMethod = $false try { # Use Get-Member to check for method existence $member = $InputObject | Get-Member -Name $MethodName -MemberType Method, ScriptMethod -ErrorAction SilentlyContinue if ($null -ne $member) { $hasMethod = $true } else { # For dynamic objects and PSCustomObject, also check PSObject.Methods $methodMember = $InputObject.PSObject.Methods[$MethodName] if ($null -ne $methodMember) { $hasMethod = $true } else { # Final check: try to get the method directly for edge cases try { $methodInfo = $InputObject.GetType().GetMethod($MethodName) if ($null -ne $methodInfo) { $hasMethod = $true } } catch { # If reflection fails, the method doesn't exist $hasMethod = $false } } } } catch { $hasMethod = $false } return $hasMethod } #EndRegion '.\Private\Test-ObjectHasMethod.ps1' 88 #Region '.\Private\Test-ObjectHasProperty.ps1' -1 <# .SYNOPSIS Tests whether an object has a specified property. .DESCRIPTION The `Test-ObjectHasProperty` function checks if the given object contains a property with the specified name. This function supports various object types including hashtables, PSCustomObjects, and .NET objects. .PARAMETER InputObject The object to test for the property. .PARAMETER PropertyName The name of the property to check for. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Boolean Returns $true if the property exists, $false otherwise. .EXAMPLE Test-ObjectHasProperty -InputObject $myObject -PropertyName 'Name' Returns $true if $myObject has a property named 'Name', otherwise $false. #> function Test-ObjectHasProperty { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.Object] $InputObject, [Parameter(Mandatory = $true)] [System.String] $PropertyName ) $hasProperty = $false try { # For hashtables, check if the key exists if ($InputObject -is [System.Collections.IDictionary]) { $hasProperty = $InputObject.ContainsKey($PropertyName) } # For PSCustomObject and other objects, try PSObject.Properties first elseif ($null -ne $InputObject.PSObject.Properties[$PropertyName]) { $hasProperty = $true } # Use Get-Member as fallback for .NET objects else { $member = $InputObject | Get-Member -Name $PropertyName -MemberType Property, NoteProperty, ScriptProperty -ErrorAction SilentlyContinue if ($null -ne $member) { $hasProperty = $true } else { # Final check with direct property access for edge cases try { $null = $InputObject.$PropertyName # If we got here without exception, the property exists # But we need to make sure it's not just returning $null for non-existent properties $actualMember = $InputObject.PSObject.Members | Where-Object { $_.Name -eq $PropertyName } $hasProperty = $null -ne $actualMember } catch { $hasProperty = $false } } } } catch { $hasProperty = $false } return $hasProperty } #EndRegion '.\Private\Test-ObjectHasProperty.ps1' 91 #Region '.\Private\Test-ObjectType.ps1' -1 <# .SYNOPSIS Tests whether two values have the same type. .DESCRIPTION The `Test-ObjectType` function checks if two values are of the same type. This is used for strict type checking in assertion commands to ensure type compatibility before comparing values. .PARAMETER ActualValue The actual value to check the type of. .PARAMETER ExpectedValue The expected value to compare the type against. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Boolean Returns $true if the values have the same type, $false otherwise. .EXAMPLE Test-ObjectType -ActualValue 123 -ExpectedValue 456 Returns $true because both values are [System.Int32]. .EXAMPLE Test-ObjectType -ActualValue 123 -ExpectedValue '123' Returns $false because the types differ ([System.Int32] vs [System.String]). #> function Test-ObjectType { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [AllowNull()] [System.Object] $ActualValue, [Parameter(Mandatory = $true)] [AllowNull()] [System.Object] $ExpectedValue ) # Handle null cases if ($null -eq $ActualValue -and $null -eq $ExpectedValue) { # Both null - types match return $true } if ($null -eq $ActualValue -or $null -eq $ExpectedValue) { # One is null, the other is not - types don't match return $false } # Both are non-null, compare types try { $actualType = $ActualValue.GetType() } catch { $errorMessage = $script:localizedData.Test_ObjectType_GetTypeFailed -f $script:localizedData.Common_WordActual $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( [System.InvalidOperationException]::new($errorMessage), 'TOT0001', [System.Management.Automation.ErrorCategory]::InvalidOperation, $ActualValue ) ) } try { $expectedType = $ExpectedValue.GetType() } catch { $errorMessage = $script:localizedData.Test_ObjectType_GetTypeFailed -f $script:localizedData.Common_WordExpected $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( [System.InvalidOperationException]::new($errorMessage), 'TOT0001', [System.Management.Automation.ErrorCategory]::InvalidOperation, $ExpectedValue ) ) } return $actualType -eq $expectedType } #EndRegion '.\Private\Test-ObjectType.ps1' 103 #Region '.\Private\Test-ValueEquality.ps1' -1 <# .SYNOPSIS Tests whether two values are equal. .DESCRIPTION The `Test-ValueEquality` function performs equality comparison between two values, handling special cases such as null values and arrays. This function assumes type compatibility has already been validated when strict type checking is required. .PARAMETER ActualValue The actual value to compare. .PARAMETER ExpectedValue The expected value to compare against. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Boolean Returns $true if the values are equal, $false otherwise. .EXAMPLE Test-ValueEquality -ActualValue $actual -ExpectedValue $expected Compares $actual and $expected values for equality. .EXAMPLE Test-ValueEquality -ActualValue @(1, 2, 3) -ExpectedValue @(1, 2, 3) Returns $true because the arrays have the same elements in the same order. #> function Test-ValueEquality { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [AllowNull()] [System.Object] $ActualValue, [Parameter(Mandatory = $true)] [AllowNull()] [System.Object] $ExpectedValue ) $valuesAreEqual = $false if ($null -eq $ActualValue -and $null -eq $ExpectedValue) { $valuesAreEqual = $true } elseif ($null -eq $ActualValue -or $null -eq $ExpectedValue) { # One is null and the other is not $valuesAreEqual = $false } elseif ($ActualValue -is [System.Array] -and $ExpectedValue -is [System.Array]) { # Use StructuralEqualityComparer for element-wise array comparison (supports value-type arrays) $valuesAreEqual = [System.Collections.StructuralComparisons]::StructuralEqualityComparer.Equals($ActualValue, $ExpectedValue) } else { # Use PowerShell's built-in comparison $valuesAreEqual = $ActualValue -eq $ExpectedValue } return $valuesAreEqual } #EndRegion '.\Private\Test-ValueEquality.ps1' 76 #Region '.\Public\Assert-BlockString.ps1' -1 <# .SYNOPSIS Asserts that a string, here-string, or array of strings matches the expected value. .DESCRIPTION The `Assert-BlockString` command compares a string, here-string, or array of strings against an expected string, here-string, or array of strings. If they are not identical, it throws an error that includes the hex output. The comparison is case-sensitive. It is commonly used in unit testing scenarios to verify the correctness of string outputs at the byte level. .PARAMETER Actual The actual string, here-string, or array of strings to be compared with the expected value. This parameter accepts pipeline input. .PARAMETER Expected The expected string, here-string, or array of strings that the actual value should match. .PARAMETER Because An optional reason or explanation for the assertion. .PARAMETER Highlight An optional ANSI color code to highlight the difference between the expected and actual strings. The default value is '31m' (red text). .PARAMETER NoHexOutput Specifies whether to omit the hex columns and output only the character groups. .INPUTS System.Object Accepts strings or arrays of strings via the pipeline. .OUTPUTS None. This command does not return any output on success. .EXAMPLE PS> Assert-BlockString -Actual 'hello', 'world' -Expected 'Hello', 'World' This example asserts that the array of strings 'hello' and 'world' matches the expected array of strings 'Hello' and 'World'. If the assertion fails, an error is thrown. .EXAMPLE PS> 'hello', 'world' | Assert-BlockString -Expected 'Hello', 'World' This example demonstrates the usage of pipeline input. A string array containing 'hello' and 'world' are piped to `Assert-BlockString` and compared with the expected string array containing 'Hello' and 'World'. If the assertion fails, an error is thrown. .NOTES TODO: Is it possible to rename the command to `Should-BeBlockString`? Pester handles commands with the `Should` verb; however, it is unclear if this issue can be resolved here. See: https://github.com/pester/Pester/commit/c8bc9679bed19c8fbc4229caa01dd083f2d03d4f#diff-b7592dd925696de2521c9b12b966d65519d502045462f002c343caa7c0986936 and https://github.com/pester/Pester/commit/c8bc9679bed19c8fbc4229caa01dd083f2d03d4f#diff-460f64eafc16facefbed201eb00fb151c75eadf7cc58a504a01527015fb1c7cdR17 #> function Assert-BlockString { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples are syntactically correct. The rule does not seem to understand that there is pipeline input.')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '')] [CmdletBinding()] [Alias('Should-BeBlockString')] [OutputType()] param ( [Parameter(Position = 1, Mandatory = $true, ValueFromPipeline = $true)] [System.Object] $Actual, [Parameter(Position = 0, Mandatory = $true)] [System.Object] $Expected, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Because, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Highlight = '31m', [Parameter()] [System.Management.Automation.SwitchParameter] $NoHexOutput ) $hasPipelineInput = $MyInvocation.ExpectingInput if ($hasPipelineInput) { $Actual = @($local:Input) } # Verify if $Actual is a string or string array $isActualStringType = $Actual -is [System.String] -or ($Actual -is [System.Array] -and ($Actual.Count -eq 0 -or ($Actual.Count -gt 0 -and $Actual[0] -is [System.String]))) $isExpectedStringType = $Expected -is [System.String] -or ($Expected -is [System.Array] -and ($Expected.Count -eq 0 -or ($Expected.Count -gt 0 -and $Expected[0] -is [System.String]))) $stringsAreEqual = $isActualStringType -and $isExpectedStringType -and (-join $Actual) -ceq (-join $Expected) if (-not $stringsAreEqual) { if (-not $isActualStringType) { $message = $script:localizedData.Assert_BlockString_ActualInvalid } elseif (-not $isExpectedStringType) { $message = $script:localizedData.Assert_BlockString_ExpectedInvalid } else { $message = $script:localizedData.Assert_BlockString_StringsNotEqual $message += "{0}`r`n " -f $script:localizedData.Assert_BlockString_Difference $message += Out-Difference -Reference $Expected -Difference $Actual -ReferenceLabel 'Expected:' -DifferenceLabel 'But was:' -HighlightStart:$Highlight -NoHexOutput:$NoHexOutput.IsPresent | ForEach-Object -Process { "`e[0m$_`r`n" } } throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } #EndRegion '.\Public\Assert-BlockString.ps1' 132 #Region '.\Public\Assert-ObjectMethod.ps1' -1 <# .SYNOPSIS Asserts that an object contains a specified method. .DESCRIPTION The `Assert-ObjectMethod` command verifies that an object contains a specified method. This is commonly used in unit testing scenarios to verify object structure and ensure that required methods are available on objects before attempting to invoke them. .PARAMETER Method The name of the method to assert exists on the object. .PARAMETER Actual The object to be inspected for the method. This parameter accepts pipeline input. .PARAMETER Because An optional reason or explanation for the assertion. .PARAMETER Each When specified and the input is an array, asserts that each element in the array has the specified method. Without this parameter, the assertion checks the array object itself. .INPUTS System.Object Accepts any object via the pipeline for method inspection. .OUTPUTS None This command does not return any output on success. .EXAMPLE PS> Assert-ObjectMethod -Actual $myObject -Method 'ToString' This example asserts that the object in `$myObject` has a method named 'ToString'. If the method does not exist, an error is thrown. .EXAMPLE PS> $myObject | Assert-ObjectMethod -Method 'GetHashCode' This example demonstrates pipeline usage. The object `$myObject` is piped to `Assert-ObjectMethod` and checked for a method named 'GetHashCode'. .EXAMPLE PS> Assert-ObjectMethod -Method 'Connect' -Actual $connection -Because 'the connection object should support Connect method' This example asserts that `$connection` has a method named 'Connect', providing a reason for the assertion. .EXAMPLE PS> $collection | Assert-ObjectMethod -Method 'Add' This example verifies that a collection object has an 'Add' method, which is useful when you need to ensure you can add items to a collection. .EXAMPLE PS> $arrayOfObjects | Assert-ObjectMethod -Method 'ToString' -Each This example asserts that each object in the piped array has a 'ToString' method. The `-Each` parameter enables element-by-element checking. #> function Assert-ObjectMethod { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples are syntactically correct. The rule does not seem to understand that there is pipeline input.')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '')] [CmdletBinding()] [Alias('Should-HaveMethod')] [OutputType()] param ( [Parameter(Position = 0, Mandatory = $true)] [System.String] $Method, [Parameter(Position = 1, Mandatory = $true, ValueFromPipeline = $true)] [System.Object] $Actual, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Because, [Parameter()] [System.Management.Automation.SwitchParameter] $Each ) $hasPipelineInput = $MyInvocation.ExpectingInput if ($hasPipelineInput) { $Actual = @($local:Input) # If we're not using -Each and we have a single-element array, unwrap it # This handles the case where a single object is piped if (-not $Each.IsPresent -and $Actual.Count -eq 1) { $Actual = $Actual[0] } } # If Each is specified and we have an array, iterate through each element if ($Each.IsPresent -and $Actual -is [System.Array] -and $Actual.Count -gt 0) { foreach ($currentObject in $Actual) { # Check if the current object is null if ($null -eq $currentObject) { $message = $script:localizedData.Assert_ObjectMethod_ActualIsNull throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } # Check if the method exists on the current object $hasMethod = Test-ObjectHasMethod -InputObject $currentObject -MethodName $Method if (-not $hasMethod) { $message = $script:localizedData.Assert_ObjectMethod_MethodNotFound -f $Method throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } } else { # Single object case (direct parameter or not from pipeline) # Check if the actual value is null if ($null -eq $Actual) { $message = $script:localizedData.Assert_ObjectMethod_ActualIsNull throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } # Check if the method exists on the object $hasMethod = Test-ObjectHasMethod -InputObject $Actual -MethodName $Method if (-not $hasMethod) { $message = $script:localizedData.Assert_ObjectMethod_MethodNotFound -f $Method throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } } #EndRegion '.\Public\Assert-ObjectMethod.ps1' 150 #Region '.\Public\Assert-ObjectProperty.ps1' -1 <# .SYNOPSIS Asserts that an object contains a specified property. .DESCRIPTION The `Assert-ObjectProperty` command verifies that an object contains a specified property. It can optionally also verify that the property has a specific value. This is commonly used in unit testing scenarios to verify object structure and property values. .PARAMETER Property The name of the property to assert exists on the object. .PARAMETER Actual The object to be inspected for the property. This parameter accepts pipeline input. .PARAMETER Value The expected value of the property. If specified, the assertion will check both property existence and value equality. .PARAMETER Because An optional reason or explanation for the assertion. .PARAMETER Each When specified and the input is an array, asserts that each element in the array has the specified property (and optionally the specified value). Without this parameter, the assertion checks the array object itself. .PARAMETER NoTypeCheck When specified, allows PowerShell type coercion when comparing values (e.g., allows 123 to equal '123'). By default, value comparisons use strict type checking where types must match exactly. This parameter only applies when using the Value parameter. .INPUTS System.Object Accepts any object via the pipeline for property inspection. .OUTPUTS None This command does not return any output on success. .EXAMPLE PS> Assert-ObjectProperty -Actual $myObject -Property 'Enabled' This example asserts that the object in `$myObject` has a property named 'Enabled'. If the property does not exist, an error is thrown. .EXAMPLE PS> Assert-ObjectProperty -Actual $myObject -Property 'Enabled' -Value $true This example asserts that the object in `$myObject` has a property named 'Enabled' with the value `$true`. If the property does not exist or the value does not match, an error is thrown. .EXAMPLE PS> $myObject | Assert-ObjectProperty -Property 'Status' -Value 'Running' This example demonstrates pipeline usage. The object `$myObject` is piped to `Assert-ObjectProperty` and checked for a property named 'Status' with the value 'Running'. .EXAMPLE PS> Assert-ObjectProperty -Property 'Count' -Actual $collection -Value 5 -Because 'the collection should contain exactly 5 items' This example asserts that `$collection` has a property named 'Count' with the value 5, providing a reason for the assertion. .EXAMPLE PS> $arrayOfObjects | Assert-ObjectProperty -Property 'Name' -Each This example asserts that each object in the piped array has a property named 'Name'. The `-Each` parameter enables element-by-element checking. .EXAMPLE Assert-ObjectProperty -Actual $myObject -Property 'Value' -Value 123 -NoTypeCheck This example uses lenient type checking, so if the Value property contains the string '123', it will be considered equal to the number 123. #> function Assert-ObjectProperty { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples are syntactically correct. The rule does not seem to understand that there is pipeline input.')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '')] [CmdletBinding(DefaultParameterSetName = 'AssertProperty')] [Alias('Should-HaveProperty')] [OutputType()] param ( [Parameter(ParameterSetName = 'AssertProperty', Position = 0, Mandatory = $true)] [Parameter(ParameterSetName = 'AssertValue', Position = 0, Mandatory = $true)] [System.String] $Property, [Parameter(ParameterSetName = 'AssertProperty', Position = 1, Mandatory = $true, ValueFromPipeline = $true)] [Parameter(ParameterSetName = 'AssertValue', Position = 2, Mandatory = $true, ValueFromPipeline = $true)] [System.Object] $Actual, [Parameter(ParameterSetName = 'AssertValue', Position = 1, Mandatory = $true)] [AllowNull()] [System.Object] $Value, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Because, [Parameter()] [System.Management.Automation.SwitchParameter] $Each, [Parameter()] [System.Management.Automation.SwitchParameter] $NoTypeCheck ) $hasPipelineInput = $MyInvocation.ExpectingInput if ($hasPipelineInput) { $Actual = @($local:Input) # If we're not using -Each and we have a single-element array, unwrap it # This handles the case where a single hashtable or object is piped if (-not $Each.IsPresent -and $Actual.Count -eq 1) { $Actual = $Actual[0] } } # If Each is specified and we have an array, iterate through each element if ($Each.IsPresent -and $Actual -is [System.Array] -and $Actual.Count -gt 0) { foreach ($currentObject in $Actual) { # Check if the current object is null if ($null -eq $currentObject) { $message = $script:localizedData.Assert_ObjectProperty_ActualIsNull throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } # Check if the property exists on the current object $hasProperty = Test-ObjectHasProperty -InputObject $currentObject -PropertyName $Property if (-not $hasProperty) { $message = $script:localizedData.Assert_ObjectProperty_PropertyNotFound -f $Property throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } # If we're in the AssertValue parameter set, also check the value if ($PSCmdlet.ParameterSetName -eq 'AssertValue') { $actualValue = $currentObject.$Property # Check type compatibility first (unless NoTypeCheck is specified) if (-not $NoTypeCheck.IsPresent) { $typesMatch = Test-ObjectType -ActualValue $actualValue -ExpectedValue $Value if (-not $typesMatch) { $actualType = Get-TypeName -Value $actualValue $expectedType = Get-TypeName -Value $Value $message = $script:localizedData.Assert_ObjectProperty_TypeMismatch -f $Property, $expectedType, $actualType throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } # Then check value equality $valuesAreEqual = Test-ValueEquality -ActualValue $actualValue -ExpectedValue $Value if (-not $valuesAreEqual) { $message = $script:localizedData.Assert_ObjectProperty_ValueMismatch -f $Property, $Value, $actualValue throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } } } else { # Single object case (not an array or empty array) # Check if the actual value is null if ($null -eq $Actual) { $message = $script:localizedData.Assert_ObjectProperty_ActualIsNull throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } # Check if the property exists on the object $hasProperty = Test-ObjectHasProperty -InputObject $Actual -PropertyName $Property if (-not $hasProperty) { $message = $script:localizedData.Assert_ObjectProperty_PropertyNotFound -f $Property throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } # If we're in the AssertValue parameter set, also check the value if ($PSCmdlet.ParameterSetName -eq 'AssertValue') { $actualValue = $Actual.$Property # Check type compatibility first (unless NoTypeCheck is specified) if (-not $NoTypeCheck.IsPresent) { $typesMatch = Test-ObjectType -ActualValue $actualValue -ExpectedValue $Value if (-not $typesMatch) { $actualType = Get-TypeName -Value $actualValue $expectedType = Get-TypeName -Value $Value $message = $script:localizedData.Assert_ObjectProperty_TypeMismatch -f $Property, $expectedType, $actualType throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } # Then check value equality $valuesAreEqual = Test-ValueEquality -ActualValue $actualValue -ExpectedValue $Value if (-not $valuesAreEqual) { $message = $script:localizedData.Assert_ObjectProperty_ValueMismatch -f $Property, $Value, $actualValue throw (New-AssertionError -Message $message -Because $Because -InvocationInfo $MyInvocation) } } } } #EndRegion '.\Public\Assert-ObjectProperty.ps1' 237 |