diff --git a/powershell-runtime/tests/helpers/AssertionHelpers.Tests.ps1 b/powershell-runtime/tests/helpers/AssertionHelpers.Tests.ps1 index 01ed98e..9d67d7c 100644 --- a/powershell-runtime/tests/helpers/AssertionHelpers.Tests.ps1 +++ b/powershell-runtime/tests/helpers/AssertionHelpers.Tests.ps1 @@ -446,4 +446,154 @@ Describe "Assert-FileExists" { { Assert-FileExists -Path $unreadableFile -ShouldBeFile } | Should -Not -Throw } } -} \ No newline at end of file +} + +Describe "Assert-PathEquals" { + Context "When paths are identical" { + It "Should pass for identical forward slash paths" { + { Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Not -Throw + } + + It "Should pass for identical backslash paths" { + { Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:\var\task\handler.ps1" } | Should -Not -Throw + } + + It "Should pass for identical relative paths" { + { Assert-PathEquals -Actual "lib/utilities.ps1" -Expected "lib/utilities.ps1" } | Should -Not -Throw + } + + It "Should pass for identical single file names" { + { Assert-PathEquals -Actual "handler.ps1" -Expected "handler.ps1" } | Should -Not -Throw + } + } + + Context "When paths differ only by separators" { + It "Should pass when actual uses backslashes and expected uses forward slashes" { + { Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:/var/task/handler.ps1" } | Should -Not -Throw + } + + It "Should pass when actual uses forward slashes and expected uses backslashes" { + { Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "\var\task\handler.ps1" } | Should -Not -Throw + } + + It "Should pass for mixed separators in both paths" { + { Assert-PathEquals -Actual "C:\var/task\handler.ps1" -Expected "C:/var\task/handler.ps1" } | Should -Not -Throw + } + + It "Should pass for nested directory paths with different separators" { + { Assert-PathEquals -Actual "lib\modules\aws\tools\handler.ps1" -Expected "lib/modules/aws/tools/handler.ps1" } | Should -Not -Throw + } + } + + Context "When paths are genuinely different" { + It "Should throw for different file names" { + { Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/task/different.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/different.ps1' but got '/var/task/handler.ps1'*" + } + + It "Should throw for different directory paths" { + { Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/runtime/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/runtime/handler.ps1' but got '/var/task/handler.ps1'*" + } + + It "Should throw for different number of path segments" { + { Assert-PathEquals -Actual "/var/task/lib/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/lib/handler.ps1'*" + } + + It "Should throw for completely different paths" { + { Assert-PathEquals -Actual "C:\Windows\System32\file.txt" -Expected "/usr/local/bin/script.sh" } | Should -Throw -ExpectedMessage "*Expected path '/usr/local/bin/script.sh' but got 'C:\Windows\System32\file.txt'*" + } + } + + Context "When using custom Because parameter" { + It "Should include custom reason in error message" { + { Assert-PathEquals -Actual "/var/task/wrong.ps1" -Expected "/var/task/handler.ps1" -Because "the script file path should match the handler configuration" } | Should -Throw -ExpectedMessage "*because the script file path should match the handler configuration*" + } + + It "Should include custom reason with normalized paths" { + { Assert-PathEquals -Actual "C:\var\task\wrong.ps1" -Expected "/var/task/handler.ps1" -Because "cross-platform paths should be normalized" } | Should -Throw -ExpectedMessage "*because cross-platform paths should be normalized*" + } + } + + Context "When handling edge cases" { + It "Should handle PowerShell parameter validation for empty strings" { + # PowerShell parameter validation prevents empty strings for mandatory parameters + { Assert-PathEquals -Actual "" -Expected "" } | Should -Throw -ExpectedMessage "*Cannot bind argument to parameter*" + } + + It "Should handle PowerShell parameter validation for empty actual path" { + { Assert-PathEquals -Actual "" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Cannot bind argument to parameter*" + } + + It "Should handle paths with trailing separators" { + { Assert-PathEquals -Actual "/var/task/" -Expected "/var/task" } | Should -Throw -ExpectedMessage "*Expected path '/var/task' but got '/var/task/'*" + } + + It "Should handle paths with multiple consecutive separators" { + { Assert-PathEquals -Actual "/var//task/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var//task/handler.ps1'*" + } + + It "Should handle paths with dots" { + { Assert-PathEquals -Actual "/var/task/./handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/./handler.ps1'*" + } + + It "Should handle paths with parent directory references" { + { Assert-PathEquals -Actual "/var/task/../task/handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/../task/handler.ps1'*" + } + } + + Context "When testing real-world Lambda scenarios" { + It "Should pass for typical Lambda task paths with different separators" { + { Assert-PathEquals -Actual "/var\task\handler.ps1" -Expected "/var/task/handler.ps1" } | Should -Not -Throw + } + + It "Should pass for module paths in subdirectories" { + { Assert-PathEquals -Actual "/var\task\lib\utilities.ps1" -Expected "/var/task/lib/utilities.ps1" } | Should -Not -Throw + } + + It "Should pass for PowerShell module manifest paths" { + { Assert-PathEquals -Actual "/opt\powershell\modules\MyModule\MyModule.psd1" -Expected "/opt/powershell/modules/MyModule/MyModule.psd1" } | Should -Not -Throw + } + + It "Should pass for Windows-style Lambda paths normalized to Linux" { + { Assert-PathEquals -Actual "C:\lambda\task\function.ps1" -Expected "C:/lambda/task/function.ps1" } | Should -Not -Throw + } + + It "Should handle layer paths correctly" { + { Assert-PathEquals -Actual "/opt\powershell\modules\AWS.Tools.Common\AWS.Tools.Common.psd1" -Expected "/opt/powershell/modules/AWS.Tools.Common/AWS.Tools.Common.psd1" } | Should -Not -Throw + } + } + + Context "When error messages show normalization details" { + It "Should show normalized paths in error message when normalization occurred" { + { Assert-PathEquals -Actual "C:\var\task\wrong.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*normalized: '/var/task/handler.ps1' vs 'C:/var/task/wrong.ps1'*" + } + + It "Should not show normalization details when no normalization was needed" { + { Assert-PathEquals -Actual "/var/task/wrong.ps1" -Expected "/var/task/handler.ps1" } | Should -Throw -ExpectedMessage "*Expected path '/var/task/handler.ps1' but got '/var/task/wrong.ps1'*" + } + + It "Should show normalization details only when actual path was normalized" { + { Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:/var/task/different.ps1" } | Should -Throw -ExpectedMessage "*normalized: 'C:/var/task/different.ps1' vs 'C:/var/task/handler.ps1'*" + } + } + + Context "When testing verbose output" { + It "Should write verbose message on successful assertion" { + Mock Write-Verbose { } + + Assert-PathEquals -Actual "/var/task/handler.ps1" -Expected "/var/task/handler.ps1" -Verbose + + Should -Invoke Write-Verbose -Exactly 1 -ParameterFilter { $Message -like "*Path assertion passed*" } + } + + It "Should write verbose message with both paths" { + Mock Write-Verbose { } + + # This should pass since the paths are equivalent after normalization + Assert-PathEquals -Actual "C:\var\task\handler.ps1" -Expected "C:/var/task/handler.ps1" -Verbose + + Should -Invoke Write-Verbose -Exactly 1 -ParameterFilter { + $Message -like "*'C:/var/task/handler.ps1' matches 'C:\var\task\handler.ps1'*" + } + } + } +} diff --git a/powershell-runtime/tests/helpers/AssertionHelpers.ps1 b/powershell-runtime/tests/helpers/AssertionHelpers.ps1 index a83677f..b377d0f 100644 --- a/powershell-runtime/tests/helpers/AssertionHelpers.ps1 +++ b/powershell-runtime/tests/helpers/AssertionHelpers.ps1 @@ -460,5 +460,61 @@ function Assert-FileExists { Write-Verbose "File existence assertion passed: '$Path'" } +<# +.SYNOPSIS + Verify path equality with cross-platform path separator handling. + +.DESCRIPTION + Asserts that two paths are equal by normalizing path separators to forward slashes, + allowing tests to pass on both Windows and Linux regardless of the path separator + used by System.IO.Path.Combine() in the runtime. + +.PARAMETER Actual + The actual path value from the test result. + +.PARAMETER Expected + The expected path value (should use forward slashes for consistency). + +.PARAMETER Because + Optional reason for the assertion failure. + +.EXAMPLE + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/handler.ps1" + Verifies that the script file path matches, regardless of platform path separators. + +.EXAMPLE + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/lib/utilities.ps1" -Because "subdirectory paths should be handled correctly" + Verifies path equality with a custom failure message. +#> +function Assert-PathEquals { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Actual, + + [Parameter(Mandatory)] + [string]$Expected, + + [string]$Because + ) + + # Normalize both paths to use forward slashes for comparison + $normalizedActual = $Actual -replace '\\', '/' + $normalizedExpected = $Expected -replace '\\', '/' + + if ($normalizedActual -ne $normalizedExpected) { + $message = "Expected path '$Expected' but got '$Actual'" + if ($normalizedActual -ne $Actual) { + $message += " (normalized: '$normalizedExpected' vs '$normalizedActual')" + } + if ($Because) { + $message += " because $Because" + } + throw $message + } + + Write-Verbose "Path assertion passed: '$Expected' matches '$Actual'" +} + # Functions are available when dot-sourced -# To use: . ./AssertionHelpers.ps1 \ No newline at end of file +# To use: . ./AssertionHelpers.ps1 diff --git a/powershell-runtime/tests/unit/Build/build-PwshRuntimeLayer.Tests.ps1 b/powershell-runtime/tests/unit/Build/build-PwshRuntimeLayer.Tests.ps1 index b1fc4ab..0a44c43 100644 --- a/powershell-runtime/tests/unit/Build/build-PwshRuntimeLayer.Tests.ps1 +++ b/powershell-runtime/tests/unit/Build/build-PwshRuntimeLayer.Tests.ps1 @@ -310,33 +310,6 @@ Describe "build-PwshRuntimeLayer.ps1" { $_.Exception.Message | Should -Not -BeNullOrEmpty } } - - It "Should handle read-only layer path appropriately" { - # Create a directory and make it read-only (if supported on platform) - $readOnlyPath = Join-Path $TestDrive "readonly-layer" - New-Item -Path $readOnlyPath -ItemType Directory -Force - - try { - # Try to make directory read-only (Windows only test) - if ($IsWindows) { - Set-ItemProperty -Path $readOnlyPath -Name IsReadOnly -Value $true - # Build script should handle this appropriately on Windows - { & $script:BuildScript -LayerPath $readOnlyPath -SkipRuntimeSetup 6>$null } | Should -Throw - } - else { - # On non-Windows platforms, test that build succeeds even with permission issues - # The build script should handle permission issues gracefully - { & $script:BuildScript -LayerPath $readOnlyPath -SkipRuntimeSetup 6>$null } | Should -Not -Throw - } - } - finally { - # Clean up read-only attribute - if ($IsWindows -and (Test-Path $readOnlyPath)) { - Set-ItemProperty -Path $readOnlyPath -Name IsReadOnly -Value $false - Remove-Item $readOnlyPath -Recurse -Force -ErrorAction SilentlyContinue - } - } - } } Context "When testing download logic without runtime setup" { @@ -754,4 +727,4 @@ function Production-Function { } -} \ No newline at end of file +} diff --git a/powershell-runtime/tests/unit/Private/Get-Handler.Tests.ps1 b/powershell-runtime/tests/unit/Private/Get-Handler.Tests.ps1 index 3775857..69c491f 100644 --- a/powershell-runtime/tests/unit/Private/Get-Handler.Tests.ps1 +++ b/powershell-runtime/tests/unit/Private/Get-Handler.Tests.ps1 @@ -53,7 +53,7 @@ Describe "Get-Handler" { $result | Should -Not -BeNullOrEmpty $result.handlerType | Should -Be 'Script' $result.scriptFileName | Should -Be "handler.ps1" - $result.scriptFilePath | Should -Be "/var/task/handler.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/handler.ps1" $result.PSObject.Properties['functionName'] | Should -BeNullOrEmpty $result.PSObject.Properties['moduleName'] | Should -BeNullOrEmpty } @@ -68,7 +68,7 @@ Describe "Get-Handler" { # Assert $result.handlerType | Should -Be 'Script' $result.scriptFileName | Should -Be "my-complex-handler.ps1" - $result.scriptFilePath | Should -Be "/var/task/my-complex-handler.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/my-complex-handler.ps1" } It "Should use LAMBDA_TASK_ROOT environment variable for script path" { @@ -80,7 +80,7 @@ Describe "Get-Handler" { $result = pwsh-runtime\Get-Handler # Assert - $result.scriptFilePath | Should -Be "/custom/task/root/test.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/custom/task/root/test.ps1" } It "Should handle script handler with subdirectory path" { @@ -93,7 +93,7 @@ Describe "Get-Handler" { # Assert $result.handlerType | Should -Be 'Script' $result.scriptFileName | Should -Be "subfolder/handler.ps1" - $result.scriptFilePath | Should -Be "/var/task/subfolder/handler.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/subfolder/handler.ps1" } } @@ -109,7 +109,7 @@ Describe "Get-Handler" { $result | Should -Not -BeNullOrEmpty $result.handlerType | Should -Be 'Function' $result.scriptFileName | Should -Be "handler.ps1" - $result.scriptFilePath | Should -Be "/var/task/handler.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/handler.ps1" $result.functionName | Should -Be "MyFunction" $result.PSObject.Properties['moduleName'] | Should -BeNullOrEmpty } @@ -125,7 +125,7 @@ Describe "Get-Handler" { $result.handlerType | Should -Be 'Function' $result.scriptFileName | Should -Be "my-script-file.ps1" $result.functionName | Should -Be "My-Complex-Function-Name" - $result.scriptFilePath | Should -Be "/var/task/my-script-file.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/my-script-file.ps1" } It "Should use LAMBDA_TASK_ROOT for function handler script path" { @@ -137,7 +137,7 @@ Describe "Get-Handler" { $result = pwsh-runtime\Get-Handler # Assert - $result.scriptFilePath | Should -Be "/custom/path/handler.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/custom/path/handler.ps1" } It "Should handle function handler with script in subdirectory" { @@ -151,7 +151,7 @@ Describe "Get-Handler" { $result.handlerType | Should -Be 'Function' $result.scriptFileName | Should -Be "lib/utilities.ps1" $result.functionName | Should -Be "Get-Data" - $result.scriptFilePath | Should -Be "/var/task/lib/utilities.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/lib/utilities.ps1" } } @@ -229,7 +229,7 @@ Describe "Get-Handler" { # Assert $result.scriptFileName | Should -Be "custom-handler.ps1" - $result.scriptFilePath | Should -Be "/var/task/custom-handler.ps1" + Assert-PathEquals -Actual $result.scriptFilePath -Expected "/var/task/custom-handler.ps1" } It "Should handle custom function handler parameter" { @@ -347,4 +347,4 @@ Describe "Get-Handler" { { pwsh-runtime\Get-Handler } | Should -Throw } } -} \ No newline at end of file +} diff --git a/powershell-runtime/tests/unit/Private/Set-PSModulePath.Tests.ps1 b/powershell-runtime/tests/unit/Private/Set-PSModulePath.Tests.ps1 index d3b463c..b377473 100644 --- a/powershell-runtime/tests/unit/Private/Set-PSModulePath.Tests.ps1 +++ b/powershell-runtime/tests/unit/Private/Set-PSModulePath.Tests.ps1 @@ -61,9 +61,9 @@ Describe "Set-PSModulePath" { $paths.Count | Should -BeGreaterOrEqual 3 # Verify the three required paths are present in correct order - $paths[0] | Should -Be '/opt/powershell/modules' - $paths[1] | Should -Be '/opt/modules' - $paths[2] | Should -Be '/var/task/modules' + Assert-PathEquals -Actual $paths[0] -Expected '/opt/powershell/modules' + Assert-PathEquals -Actual $paths[1] -Expected '/opt/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/var/task/modules' } It "Should include at position " -ForEach @( @@ -76,7 +76,7 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[$Position] | Should -Be $ExpectedPath + Assert-PathEquals -Actual $paths[$Position] -Expected $ExpectedPath } It "Should use colon as path separator" { @@ -99,7 +99,7 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[2] | Should -Be '/custom/task/root/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/custom/task/root/modules' } It "Should handle LAMBDA_TASK_ROOT with trailing slash" { @@ -111,7 +111,7 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[2] | Should -Be '/custom/task/root/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/custom/task/root/modules' } @@ -164,9 +164,9 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[0] | Should -Be '/opt/powershell/modules' - $paths[1] | Should -Be '/opt/modules' - $paths[2] | Should -Be '/var/task/modules' + Assert-PathEquals -Actual $paths[0] -Expected '/opt/powershell/modules' + Assert-PathEquals -Actual $paths[1] -Expected '/opt/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/var/task/modules' } } @@ -188,9 +188,9 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[0] | Should -Be '/opt/powershell/modules' - $paths[1] | Should -Be '/opt/modules' - $paths[2] | Should -Be '/var/task/modules' + Assert-PathEquals -Actual $paths[0] -Expected '/opt/powershell/modules' + Assert-PathEquals -Actual $paths[1] -Expected '/opt/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/var/task/modules' } } @@ -205,8 +205,11 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $firstIndex = [Array]::IndexOf($paths, $FirstPathValue) - $secondIndex = [Array]::IndexOf($paths, $SecondPathValue) + + # Normalize paths for cross-platform compatibility + $normalizedPaths = $paths | ForEach-Object { $_ -replace '\\', '/' } + $firstIndex = [Array]::IndexOf($normalizedPaths, $FirstPathValue) + $secondIndex = [Array]::IndexOf($normalizedPaths, $SecondPathValue) $firstIndex | Should -BeLessThan $secondIndex } @@ -252,9 +255,9 @@ Describe "Set-PSModulePath" { $env:PSModulePath | Should -Not -Match '/existing/path2' $paths = $env:PSModulePath -split ':' - $paths[0] | Should -Be '/opt/powershell/modules' - $paths[1] | Should -Be '/opt/modules' - $paths[2] | Should -Be '/var/task/modules' + Assert-PathEquals -Actual $paths[0] -Expected '/opt/powershell/modules' + Assert-PathEquals -Actual $paths[1] -Expected '/opt/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/var/task/modules' } It "Should set PSModulePath even when it was previously empty" { @@ -321,7 +324,7 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[2] | Should -Be '/path with spaces/and-dashes_and.dots/modules' + Assert-PathEquals -Actual $paths[2] -Expected '/path with spaces/and-dashes_and.dots/modules' } It "Should handle very long LAMBDA_TASK_ROOT path" { @@ -334,7 +337,7 @@ Describe "Set-PSModulePath" { # Assert $paths = $env:PSModulePath -split ':' - $paths[2] | Should -Be "$longPath/modules" + Assert-PathEquals -Actual $paths[2] -Expected "$longPath/modules" } } @@ -353,4 +356,4 @@ Describe "Set-PSModulePath" { $output | Should -BeNullOrEmpty } } -} \ No newline at end of file +}