diff --git a/README.md b/README.md index 3f5f822..9cdef24 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,12 @@ This runtime uses Lambda's [custom runtime](https://docs.aws.amazon.com/lambda/l ### Testing -The runtime includes comprehensive unit tests. See [powershell-runtime/tests/README.md](powershell-runtime/tests/README.md) for testing documentation and commands. +The runtime includes unit tests and integration tests: + +* **Unit Tests**: Automated tests covering runtime functions and build processes +* **Integration Tests**: Manual tests with real AWS Lambda functions for end-to-end validation + +See [powershell-runtime/tests/README.md](powershell-runtime/tests/README.md) for testing documentation and commands. ## Building and Deploying diff --git a/powershell-runtime/readme.md b/powershell-runtime/readme.md index 70885c5..5329e9f 100644 --- a/powershell-runtime/readme.md +++ b/powershell-runtime/readme.md @@ -1,12 +1,12 @@ -# PowerShell-runtime +# PowerShell Runtime Contains the PowerShell custom runtime based on `provided.al2023` with deployment methods. Deploy the example [demo-runtime-layer-function](../examples/demo-runtime-layer-function/) to explore how the runtime and PowerShell function work. -## Deploying the PowerShell custom runtime +## Deploying the PowerShell Custom Runtime -The recommended deployment method is AWS SAM, though other infrastructure-as-code tools are also supported. +AWS SAM is the deployment method, though other infrastructure-as-code tools are also supported. ## AWS SAM @@ -58,7 +58,7 @@ Enter a **Stack Name** such as `powershell-runtime` and accept the remaining ini ## Development and Testing -See [tests/README.md](tests/README.md) for comprehensive testing documentation and commands. +See [tests/README.md](tests/README.md) for testing documentation and commands. ## Powershell runtime information @@ -121,7 +121,7 @@ The PowerShell runtime imports the specified ``. This allows you to ### PowerShell module support -You can include additional PowerShell modules either via a Lambda Layer, or within your function code package, or container image. Using Lambda layers provides a convenient way to package and share modules that you can use with your Lambda functions. Layers reduce the size of uploaded deployment archives and make it faster to deploy your code. +You can include PowerShell modules via a Lambda Layer, within your function code package, or container image. Using Lambda layers provides a way to package and share modules that you can use with your Lambda functions. Layers reduce the size of uploaded deployment archives and make it faster to deploy your code. The `PSModulePath` environment variable contains a list of folder locations that are searched to find user-supplied modules. This is configured during the runtime initialization. Folders are specified in the following order: diff --git a/powershell-runtime/source/PowerShellLambdaContext.cs b/powershell-runtime/source/PowerShellLambdaContext.cs index 2e23f83..396894b 100644 --- a/powershell-runtime/source/PowerShellLambdaContext.cs +++ b/powershell-runtime/source/PowerShellLambdaContext.cs @@ -106,7 +106,7 @@ double DeadlineMS { this.FunctionName = FunctionName; this.FunctionVersion = FunctionVersion; - this.InvokedFunctionArn = FunctionName; + this.InvokedFunctionArn = InvokedFunctionArn; this.MemoryLimitInMB = MemoryLimitInMB; this.AwsRequestId = AwsRequestId; this.LogGroupName = LogGroupName; diff --git a/powershell-runtime/source/modules/Private/Set-LambdaContext.ps1 b/powershell-runtime/source/modules/Private/Set-LambdaContext.ps1 index 00a09f9..00e42a6 100644 --- a/powershell-runtime/source/modules/Private/Set-LambdaContext.ps1 +++ b/powershell-runtime/source/modules/Private/Set-LambdaContext.ps1 @@ -18,7 +18,7 @@ function Private:Set-LambdaContext { $private:LambdaContext = [Amazon.Lambda.PowerShell.Internal.LambdaContext]::new( $env:AWS_LAMBDA_FUNCTION_NAME, $env:AWS_LAMBDA_FUNCTION_VERSION, - $env:AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN, + $env:AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN, [int]$env:AWS_LAMBDA_FUNCTION_MEMORY_SIZE, $env:AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID, $env:AWS_LAMBDA_LOG_GROUP_NAME, diff --git a/powershell-runtime/tests/Invoke-Tests.ps1 b/powershell-runtime/tests/Invoke-Tests.ps1 index 399d70f..3644c7d 100644 --- a/powershell-runtime/tests/Invoke-Tests.ps1 +++ b/powershell-runtime/tests/Invoke-Tests.ps1 @@ -11,7 +11,12 @@ - BuiltModule: Tests the built/merged module (validation) .PARAMETER TestType - Specifies which type of tests to run. Valid values: 'All', 'Unit', 'Build' + Specifies which type of tests to run. Valid values: 'LocalUnit', 'Unit', 'Build', 'Private', 'Integration' + - LocalUnit: Runs all local unit tests (Build, Module, and Private) - Default + - Unit: Runs Module and Private unit tests + - Build: Runs only Build tests + - Private: Runs only Private unit tests + - Integration: Runs only integration tests .PARAMETER Path Specifies specific test files or directories to run @@ -31,18 +36,46 @@ .PARAMETER DetailedOutput Enables detailed Pester output for debugging +.PARAMETER StackName + Name of the CloudFormation stack for integration tests (default: "powershell-runtime-integration-test-infrastructure") + Only used when TestType is 'Integration' + +.PARAMETER Region + AWS region for infrastructure deployment (default: "us-east-1") + Only used when TestType is 'Integration' + +.PARAMETER ProfileName + AWS profile name for authentication + Only used when TestType is 'Integration' + .EXAMPLE ./Invoke-Tests.ps1 - Runs all tests in Source mode (fastest for development) + Runs all local unit tests (Build, Module, Private) in Source mode (fastest for development) .EXAMPLE ./Invoke-Tests.ps1 -TestBuiltModule -Coverage - Runs all tests against built module with coverage analysis + Runs all local unit tests against built module with coverage analysis + +.EXAMPLE + ./Invoke-Tests.ps1 -TestType Private + Runs only Private unit tests + +.EXAMPLE + ./Invoke-Tests.ps1 -TestType Build + Runs only Build tests .EXAMPLE ./Invoke-Tests.ps1 -Path './tests/unit/Private/Get-Handler.Tests.ps1' Runs a specific test file +.EXAMPLE + ./Invoke-Tests.ps1 -TestType Integration + Runs integration tests with default stack name and region, automatically sets up and cleans up environment + +.EXAMPLE + ./Invoke-Tests.ps1 -TestType Integration -StackName "my-test-stack" -Region "us-west-2" -ProfileName "dev" + Runs integration tests with custom stack, region, and AWS profile + .EXAMPLE ./Invoke-Tests.ps1 -CI -OutputFormat NUnitXml Runs all tests in CI mode with NUnit XML output @@ -50,8 +83,8 @@ [CmdletBinding()] param( - [ValidateSet('All', 'Unit', 'Build')] - [string]$TestType = 'All', + [ValidateSet('LocalUnit', 'Unit', 'Build', 'Private', 'Integration')] + [string]$TestType = 'LocalUnit', [string[]]$Path, [switch]$TestBuiltModule, @@ -61,7 +94,12 @@ param( [ValidateSet('NUnitXml', 'JUnitXml', 'Console')] [string]$OutputFormat = 'Console', - [switch]$DetailedOutput + [switch]$DetailedOutput, + + # Integration test parameters + [string]$StackName = "powershell-runtime-integration-test-infrastructure", + [string]$Region = "us-east-1", + [string]$ProfileName ) # Set error action preference for consistent behavior @@ -140,7 +178,7 @@ function Initialize-TestFramework { function Get-CoveragePaths { param($TestPaths, $ProjectRoot, $TestBuiltModule, $TestType) - # For Build tests, always include the build script + # Build tests - only cover build script if ($TestType -eq 'Build') { $buildScript = Join-Path $ProjectRoot "build-PwshRuntimeLayer.ps1" if (Test-Path $buildScript) { @@ -152,20 +190,42 @@ function Get-CoveragePaths { } } + # Private tests - only cover private source functions + if ($TestType -eq 'Private') { + $privatePath = Join-Path $ProjectRoot "source/modules/Private" + if (Test-Path $privatePath) { + return Get-ChildItem -Path $privatePath -Filter "*.ps1" -Recurse | ForEach-Object { $_.FullName } + } + else { + Write-Warning "Private source path not found at: $privatePath" + return @() + } + } + + # For Unit/LocalUnit tests: + # - If TestBuiltModule: only cover built module + # - If source testing: cover all source files if ($TestBuiltModule) { $builtModule = Join-Path $ProjectRoot "layers/runtimeLayer/modules/pwsh-runtime.psm1" if (Test-Path $builtModule) { return @($builtModule) } + else { + Write-Warning "Built module not found at: $builtModule" + return @() + } } - - # For source testing, include all source files - $sourcePath = Join-Path $ProjectRoot "source/modules" - if (Test-Path $sourcePath) { - return Get-ChildItem -Path $sourcePath -Filter "*.ps1" -Recurse | ForEach-Object { $_.FullName } + else { + # Source testing - cover all source files + $sourcePath = Join-Path $ProjectRoot "source/modules" + if (Test-Path $sourcePath) { + return Get-ChildItem -Path $sourcePath -Filter "*.ps1" -Recurse | ForEach-Object { $_.FullName } + } + else { + Write-Warning "Source path not found at: $sourcePath" + return @() + } } - - return @() } function Write-CIDiagnostics { @@ -199,6 +259,98 @@ function Invoke-CICleanup { Write-Host "CI Cleanup: Completed" -ForegroundColor Green } +function Initialize-IntegrationTestEnvironment { + [CmdletBinding()] + param( + [string]$StackName, + [string]$Region, + [string]$ProfileName + ) + + Write-Host "Setting up integration test environment..." -ForegroundColor Yellow + + # Import required AWS modules + Write-Host "Importing required AWS modules..." -ForegroundColor Yellow + if (-not (Get-Module -Name AWS.Tools.CloudFormation -ListAvailable)) { + throw "AWS.Tools.CloudFormation module is not installed. Please install it with: Install-Module -Name AWS.Tools.CloudFormation -Force" + } + Import-Module AWS.Tools.CloudFormation -ErrorAction Stop + Write-Host "AWS modules imported successfully" -ForegroundColor Green + + # Set up authentication parameters + $awsParams = @{ + Region = $Region + } + + # Add profile if specified + if ($ProfileName) { + $awsParams.ProfileName = $ProfileName + } + + Write-Host "Retrieving stack outputs for $StackName..." -ForegroundColor Yellow + + try { + $stack = Get-CFNStack -StackName $StackName @awsParams + } + catch { + Write-Error "Failed to retrieve stack information: $_" + throw "Could not find stack '$StackName'. Make sure the stack exists and you have permission to access it." + } + + # Convert outputs to environment variables + $outputCount = 0 + foreach ($output in ($stack.Outputs | Sort-Object -Property OutputKey)) { + $envVarName = "PWSH_TEST_$($output.OutputKey.ToUpper())" + Write-Host "Setting $envVarName = $($output.OutputValue)" -ForegroundColor Cyan + [Environment]::SetEnvironmentVariable($envVarName, $output.OutputValue, "Process") + $outputCount++ + } + + # Set a flag indicating infrastructure is available + [Environment]::SetEnvironmentVariable("PWSH_TEST_INFRASTRUCTURE_DEPLOYED", "TRUE", "Process") + + # Set configuration environment variables for use by other scripts/tests + Write-Host "Setting configuration environment variables..." -ForegroundColor Yellow + [Environment]::SetEnvironmentVariable("PWSH_TEST_STACK_NAME", $StackName, "Process") + Write-Host "Setting PWSH_TEST_STACK_NAME = $StackName" -ForegroundColor Cyan + + [Environment]::SetEnvironmentVariable("PWSH_TEST_AWS_REGION", $Region, "Process") + Write-Host "Setting PWSH_TEST_AWS_REGION = $Region" -ForegroundColor Cyan + + if ($ProfileName) { + [Environment]::SetEnvironmentVariable("PWSH_TEST_PROFILE_NAME", $ProfileName, "Process") + Write-Host "Setting PWSH_TEST_PROFILE_NAME = $ProfileName" -ForegroundColor Cyan + } + + Write-Host "Successfully set $outputCount stack output environment variables for integration tests" -ForegroundColor Green + Write-Host "Environment variable PWSH_TEST_INFRASTRUCTURE_DEPLOYED has been set to TRUE" -ForegroundColor Green + Write-Host "Configuration environment variables (PWSH_TEST_STACK_NAME, PWSH_TEST_AWS_REGION$(if ($ProfileName) { ', PWSH_TEST_PROFILE_NAME' })) have been set" -ForegroundColor Green +} + +function Clear-IntegrationTestEnvironment { + [CmdletBinding()] + param() + + Write-Host "Cleaning up integration test environment variables..." -ForegroundColor Yellow + + # Get all environment variables that start with PWSH_TEST_ + $testEnvVars = [Environment]::GetEnvironmentVariables("Process").Keys | Where-Object { $_ -like "PWSH_TEST_*" } + + $cleanedCount = 0 + foreach ($envVar in $testEnvVars) { + Write-Host "Removing $envVar" -ForegroundColor Cyan + [Environment]::SetEnvironmentVariable($envVar, $null, "Process") + $cleanedCount++ + } + + if ($cleanedCount -gt 0) { + Write-Host "Successfully cleaned up $cleanedCount integration test environment variables" -ForegroundColor Green + } + else { + Write-Host "No integration test environment variables found to clean up" -ForegroundColor Yellow + } +} + function Write-GitHubStepSummary { @@ -358,15 +510,39 @@ try { Initialize-TestFramework + # Set up integration test environment if needed + if ($TestType -eq 'Integration') { + try { + Initialize-IntegrationTestEnvironment -StackName $StackName -Region $Region -ProfileName $ProfileName + } + catch { + Write-Error "Failed to initialize integration test environment: $_" + throw "Integration test environment setup failed. Please ensure the CloudFormation stack '$StackName' exists in region '$Region' and you have appropriate permissions." + } + } + # Configure paths $TestPaths = if ($Path) { $Path | Where-Object { Test-Path $_ } } else { switch ($TestType) { - 'Unit' { Join-Path $script:ProjectRoot 'tests/unit' } + 'Unit' { + @( + (Join-Path $script:ProjectRoot 'tests/unit/Module'), + (Join-Path $script:ProjectRoot 'tests/unit/Private') + ) + } 'Build' { Join-Path $script:ProjectRoot 'tests/unit/Build' } - default { Join-Path $script:ProjectRoot 'tests/unit' } + 'Private' { Join-Path $script:ProjectRoot 'tests/unit/Private' } + 'Integration' { Join-Path $script:ProjectRoot 'tests/integration' } + 'LocalUnit' { + @( + (Join-Path $script:ProjectRoot 'tests/unit/Build'), + (Join-Path $script:ProjectRoot 'tests/unit/Module'), + (Join-Path $script:ProjectRoot 'tests/unit/Private') + ) + } } } @@ -391,12 +567,17 @@ try { $Config.CodeCoverage.Enabled = $true $Config.CodeCoverage.OutputFormat = 'JaCoCo' $Config.CodeCoverage.OutputPath = Join-Path $script:ProjectRoot "CodeCoverage.xml" - $Config.CodeCoverage.Path = Get-CoveragePaths -TestPaths $TestPaths -ProjectRoot $script:ProjectRoot -TestBuiltModule $TestBuiltModule -TestType $TestType + $coveragePaths = Get-CoveragePaths -TestPaths $TestPaths -ProjectRoot $script:ProjectRoot -TestBuiltModule $TestBuiltModule -TestType $TestType + $Config.CodeCoverage.Path = $coveragePaths $coverageThreshold = $script:TestConfig.PrivateData.TestSettings.CodeCoverage.Threshold $Config.CodeCoverage.CoveragePercentTarget = $coverageThreshold - Write-Host "Coverage enabled: $($Config.CodeCoverage.Path.Count) files" -ForegroundColor Yellow + Write-Host "Coverage enabled for $($coveragePaths.Count) files:" -ForegroundColor Yellow + foreach ($path in $coveragePaths) { + $relativePath = $path -replace [regex]::Escape($script:ProjectRoot), '.' + Write-Host " - $relativePath" -ForegroundColor Cyan + } } # Configure output @@ -461,4 +642,15 @@ catch { Invoke-CICleanup } Exit-TestScript -ExitCode 1 -ExitMessage "Test execution failed: $_" -} \ No newline at end of file +} +finally { + # Always clean up integration test environment variables + if ($TestType -eq 'Integration') { + try { + Clear-IntegrationTestEnvironment + } + catch { + Write-Warning "Failed to clean up integration test environment: $_" + } + } +} diff --git a/powershell-runtime/tests/README.md b/powershell-runtime/tests/README.md index 1ae8e6b..2ba409a 100644 --- a/powershell-runtime/tests/README.md +++ b/powershell-runtime/tests/README.md @@ -1,43 +1,56 @@ # PowerShell Lambda Runtime Tests -This directory contains unit tests for the AWS Lambda PowerShell runtime using Pester as the testing framework. +This directory contains unit tests and integration tests for the AWS Lambda PowerShell runtime using Pester as the testing framework. -## Test Coverage Details +## Test Coverage -The test suite provides validation of: +The test suite validates: -* ✅ **HTTP API interactions** with a mocked Lambda Runtime API via TestLambdaRuntimeServer -* ✅ **Module loading and function export** validation for the main runtime module (`pwsh-runtime.psm1`) -* ✅ **Build process validation** for the build script (`build-PwshRuntimeLayer.ps1`) -* ✅ **Environment variable management** and Lambda context object creation -* ✅ **Handler detection logic** for Script, Function, and Module handler types -* ✅ **Response formatting and encoding** including JSON serialization and UTF-8 handling -* ⏳ **Integration tests** are planned for future implementation +* **HTTP API interactions** with a mocked Lambda Runtime API via TestLambdaRuntimeServer +* **Module loading and function export** validation for the main runtime module (`pwsh-runtime.psm1`) +* **Build process validation** for the build script (`build-PwshRuntimeLayer.ps1`) +* **Environment variable management** and Lambda context object creation +* **Handler detection logic** for Script, Function, and Module handler types +* **Response formatting and encoding** including JSON serialization and UTF-8 handling +* **Integration tests** with real AWS Lambda functions (manual execution only, not included in CI) ## Directory Structure ```text tests/ -├── unit/ # Unit tests +├── unit/ # Unit tests (automated in CI) │ ├── Private/ # Private runtime function tests │ ├── Module/ # Runtime module tests │ └── Build/ # Build script tests +├── integration/ # Integration tests (manual execution only) +│ ├── Lambda-Integration.Tests.ps1 # Integration test suite +│ └── infrastructure/ # Test infrastructure (CloudFormation) ├── helpers/ # Test utilities and helpers -├── fixtures/ # Test data and mock objects ├── Invoke-Tests.ps1 # Test runner script +├── PesterSettings.psd1 # Pester configuration └── README.md # This file ``` ## Test Categories -### Unit Tests +### Unit Tests (Automated) -Unit tests validate individual functions and components in isolation: +Unit tests validate individual functions and components in isolation and run automatically in CI: * **Private Function Tests**: Test all private runtime functions including handler detection, API calls, response handling, and environment setup * **Module Tests**: Test the main runtime module interface and public functions * **Build Tests**: Test build scripts and deployment processes +### Integration Tests (Manual Only) + +Integration tests validate end-to-end functionality with real AWS infrastructure: + +* **Purpose**: Test runtime with actual Lambda functions in AWS +* **Execution**: Manual only - not included in CI/CD pipelines +* **Infrastructure**: Requires deployed CloudFormation stacks +* **Handler Types**: Tests Script, Function, and Module handlers +* **Use Cases**: Pre-release validation, troubleshooting, development verification + ### Helper Utilities Test utilities and helpers support the test suite: @@ -50,15 +63,24 @@ Test utilities and helpers support the test suite: ### Prerequisites +**All Tests:** + * **PowerShell 7.0 or later** (see main README for installation) * Pester 5.7.1 or later (automatically installed if missing) -### Basic Usage +**Integration Tests Only:** + +* AWS SAM CLI installed and configured +* AWS credentials configured with appropriate permissions +* CloudFormation permissions for stack operations +* Lambda execution permissions + +### Automated Tests (CI) ```powershell cd powershell-runtime/tests/ -# Run all tests (default: source file testing for fast development) +# Run all automated tests (unit and build) pwsh -NoProfile -Command "& './Invoke-Tests.ps1'" # Run only unit tests @@ -73,12 +95,25 @@ pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -TestBuiltModule" # Run with code coverage analysis pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -Coverage" -# Run with code coverage against built module -pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -Coverage -TestBuiltModule" - -# Run in CI mode with full validation +# Run in CI mode with validation pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -CI" +``` +### Integration Tests (Manual Only) + +Integration tests require manual execution and AWS infrastructure setup: + +```powershell +# Basic integration test execution +pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -TestType Integration" + +# Integration tests with specific stack and region +pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -TestType Integration -StackName 'my-test-stack' -Region 'us-east-1' -ProfileName 'dev'" +``` + +### Additional Test Options + +```powershell # Run specific test files pwsh -NoProfile -Command "& './Invoke-Tests.ps1' -Path './unit/Private/Get-Handler.Tests.ps1'" @@ -95,38 +130,68 @@ The `Invoke-Tests.ps1` script supports the following parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `TestType` | String | 'All' | Test category to run: 'All', 'Unit', 'Build' | +| `TestType` | String | 'All' | Test category: 'All', 'Unit', 'Build', 'Integration' | | `Path` | String[] | - | Specific test files or directories to run | | `TestBuiltModule` | Switch | False | Test against built module instead of source files | | `CI` | Switch | False | Enable CI mode with enhanced validation and cleanup | | `Coverage` | Switch | False | Enable code coverage analysis with JaCoCo output | | `OutputFormat` | String | 'Console' | Test result format: 'Console', 'NUnitXml', 'JUnitXml' | | `DetailedOutput` | Switch | False | Enable detailed Pester output for debugging | +| `StackName` | String | 'powershell-runtime-integration-test-infrastructure' | CloudFormation stack name (integration tests only) | +| `Region` | String | 'us-east-1' | AWS region (integration tests only) | +| `ProfileName` | String | - | AWS profile name (integration tests only) | -### Test Types and Execution Modes +### Test Execution Modes -#### Test Categories +#### Automated vs Manual Testing -* **Unit Tests**: Test individual functions and components in isolation using the TestLambdaRuntimeServer for HTTP API calls -* **Build Tests**: Test build scripts and deployment processes including PowerShell runtime download and module merging -* **Helper Tests**: Test the testing infrastructure itself (test utilities, assertion helpers, test server) +* **Automated Tests (CI)**: Unit and Build tests run automatically in CI/CD +* **Manual Tests**: Integration tests require manual execution with AWS setup +* **CI Mode**: Automatically excludes integration tests and enables built module testing -#### Execution Modes +#### Source vs Built Module Testing The test runner supports two execution modes: -* **Source Mode (Default)**: Tests source files directly for fast development iteration +* **Source Mode (Default)**: Tests source files directly for development iteration * Uses individual source files from `source/modules/` - * Faster execution, ideal for development + * Execution for development * Coverage analysis includes source files * Command: `./Invoke-Tests.ps1` * **Built Module Mode**: Tests the built/merged module for validation * Uses the built module from `layers/runtimeLayer/modules/pwsh-runtime.psm1` - * Slower execution, validates final deployment artifact + * Validates deployment artifact * Coverage analysis includes the built module * Command: `./Invoke-Tests.ps1 -TestBuiltModule` - * Automatically builds the module if needed + * Builds the module if needed + +## Integration Test Infrastructure Setup + +Integration tests require a 3-step manual deployment process: + +### Step 1: Deploy PowerShell Runtime + +```bash +cd powershell-runtime/ +sam build +sam deploy +``` + +### Step 2: Deploy Integration Test Infrastructure + +```bash +cd powershell-runtime/tests/integration/infrastructure/ +sam build +sam deploy --parameter-overrides PowerShellRuntimeLayerArn= +``` + +### Step 3: Execute Integration Tests + +```powershell +cd powershell-runtime/tests/ +pwsh -NoProfile -c "./Invoke-Tests.ps1 -TestType Integration -StackName powershell-runtime-integration-test-infrastructure -Region us-west-2 -ProfileName default" +``` ## Test Configuration @@ -148,8 +213,7 @@ Test settings are configured in: ### Test File Naming Convention * Unit tests: `.Tests.ps1` - -### Test Structure +* Integration tests: `-Integration.Tests.ps1` ### Test Pattern Example @@ -171,13 +235,15 @@ Describe "Get-Handler" { } ``` -For HTTP API testing, use `TestLambdaRuntimeServer` instead of mocking .NET types. See existing test files for complete patterns. +For HTTP API testing, use `TestLambdaRuntimeServer` instead of mocking .NET types. See existing test files for patterns. ### Testing Approach **Key Testing Principles:** * **Use TestLambdaRuntimeServer**: All HTTP-based tests use the test server instead of mocking .NET types +* **Test Isolation**: Use proper setup and cleanup in test containers +* **Real Infrastructure**: Integration tests use actual AWS Lambda functions **Available Helper Functions:** @@ -189,29 +255,36 @@ For HTTP API testing, use `TestLambdaRuntimeServer` instead of mocking .NET type * **Target**: 80% minimum code coverage * **Scope**: Runtime functions, modules, and build scripts +* **Exclusions**: Integration tests do not contribute to coverage metrics ## Troubleshooting ### Common Issues +**All Tests:** + 1. **Pester not found**: The test runner will automatically install Pester 5.7.1+ 1. **Module import errors**: Ensure the runtime module exists at `source/modules/pwsh-runtime.psm1` 1. **Test server issues**: Check that port 9001 is available or specify a different port 1. **Coverage issues**: Ensure the module is built before running coverage tests -1. **HTTP timeout errors**: Increase timeout settings in test configuration -## Test Coverage +**Integration Tests Only:** -Tests cover runtime functions, HTTP API interactions, environment management, response handling, logging, build processes, and module loading. +1. **Stack not found**: Verify CloudFormation stack exists and you have access permissions +1. **AWS credential errors**: Ensure AWS credentials are configured and valid +1. **Region mismatch**: Verify the specified region matches your stack deployment +1. **Profile not found**: Check that the specified AWS profile exists and is configured +1. **Lambda timeout errors**: Integration tests may take longer due to cold starts ## Contributing When adding new tests: -1. **Follow the established directory structure** - Unit tests in `unit/`, helpers in `helpers/` +1. **Follow the established directory structure** - Unit tests in `unit/`, integration tests in `integration/` 1. **Use the TestLambdaRuntimeServer** - Make real HTTP calls instead of mocking .NET types 1. **Include scenarios** - Test success paths, error handling, and edge cases 1. **Proper test isolation** - Use `BeforeAll`/`AfterAll` for setup/cleanup 1. **Descriptive test names** - Clearly describe what is being tested 1. **Update documentation** - Update this README when adding new test categories 1. **Follow established patterns** - Reference existing test files for structure and conventions +1. **Integration test considerations** - Ensure integration tests clean up AWS resources and handle failures gracefully diff --git a/powershell-runtime/tests/helpers/LambdaIntegrationHelpers.psm1 b/powershell-runtime/tests/helpers/LambdaIntegrationHelpers.psm1 new file mode 100644 index 0000000..a701bc5 --- /dev/null +++ b/powershell-runtime/tests/helpers/LambdaIntegrationHelpers.psm1 @@ -0,0 +1,504 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +<# +.SYNOPSIS + Helper functions for Lambda integration tests. + +.DESCRIPTION + This module provides consolidated validation functions, schema-based validation, + and utilities to reduce code duplication in Lambda integration tests. +#> + +# Lambda Context validation schema +$script:LambdaContextSchema = @{ + AwsRequestId = @{ + Type = 'String' + Required = $true + Validator = 'UUID' + } + FunctionName = @{ + Type = 'String' + Required = $true + MatchesInput = $true + } + FunctionVersion = @{ + Type = 'String' + Required = $true + Validator = 'VersionFormat' + } + InvokedFunctionArn = @{ + Type = 'String' + Required = $true + Validator = 'ARN' + } + MemoryLimitInMB = @{ + Type = 'Long' + Required = $true + Validator = 'LambdaMemory' + } + LogGroupName = @{ + Type = 'String' + Required = $false + Validator = 'LogGroupFormat' + } + LogStreamName = @{ + Type = 'String' + Required = $false + Validator = 'LogStreamFormat' + } + Identity = @{ + Type = 'Object' + Required = $false + AllowNull = $true + } + ClientContext = @{ + Type = 'Object' + Required = $false + AllowNull = $true + } + RemainingTimeMs = @{ + Type = 'Double' + Required = $true + Validator = 'PositiveTime' + } + RemainingTimeMillisMethod = @{ + Type = 'Double' + Required = $true + Validator = 'PositiveTime' + } + RemainingTimeSpan = @{ + Type = 'String' + Required = $true + } + HasValidRequestId = @{ + Type = 'Boolean' + Required = $true + ExpectedValue = $true + } + HasValidFunctionName = @{ + Type = 'Boolean' + Required = $true + ExpectedValue = $true + } + TimeMethodsConsistent = @{ + Type = 'Boolean' + Required = $true + ExpectedValue = $true + } +} + +# Validation rules +$script:ValidationRules = @{ + UUID = @{ + Pattern = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + Description = 'valid UUID format' + } + ARN = @{ + Pattern = '^arn:aws:lambda:' + Description = 'valid Lambda ARN format' + AdditionalChecks = @('FunctionArnStructure') + } + VersionFormat = @{ + CustomValidator = 'Test-VersionFormat' + Description = 'valid version format ($LATEST or number)' + } + LambdaMemory = @{ + CustomValidator = 'Test-LambdaMemorySize' + Description = 'valid Lambda memory configuration' + } + LogGroupFormat = @{ + Pattern = '^/aws/lambda/' + Description = 'valid CloudWatch log group format' + } + LogStreamFormat = @{ + Pattern = '^\d{4}/\d{2}/\d{2}/\[.*\]' + Description = 'valid CloudWatch log stream format' + } + PositiveTime = @{ + CustomValidator = 'Test-PositiveTime' + Description = 'positive time value' + } +} + +# Response cache for reducing Lambda invocations +$script:HandlerResponseCache = @{} + +function Initialize-HandlerResponseCache { + <# + .SYNOPSIS + Initializes the handler response cache by invoking all Lambda functions once. + #> + param( + [Parameter(Mandatory)] + [array]$HandlerConfigs, + + [Parameter(Mandatory)] + [string]$Region, + + [string]$ProfileName + ) + + $awsAuth = @{ + Region = $Region + } + if ($ProfileName) { + $awsAuth['ProfileName'] = $ProfileName + } + + $script:HandlerResponseCache = @{} + + foreach ($config in $HandlerConfigs) { + Write-Verbose "Caching response for $($config.HandlerType) handler" + + $payload = New-TestPayload -TestKey $config.TestKey -AdditionalData $config.TestData + $response = Invoke-TestLambdaFunction -FunctionName $config.FunctionName -Payload $payload $awsAuth + + $script:HandlerResponseCache[$config.HandlerType] = @{ + Response = $response + Config = $config + } + } +} + +function Get-CachedHandlerResponse { + <# + .SYNOPSIS + Retrieves a cached handler response. + #> + param( + [Parameter(Mandatory)] + [string]$HandlerType + ) + + if (-not $script:HandlerResponseCache.ContainsKey($HandlerType)) { + throw "No cached response found for handler type: $HandlerType" + } + + return $script:HandlerResponseCache[$HandlerType] +} + +function Assert-DeepEqual { + <# + .SYNOPSIS + Performs deep equality comparison between actual and expected values. + #> + param( + $Actual, + $Expected, + [string]$Path = "root" + ) + + if ($null -eq $Expected) { + $Actual | Should -BeNullOrEmpty -Because "$Path should be null or empty" + return + } + + switch ($Expected.GetType().Name) { + 'Object[]' { + $Actual | Should -Not -BeNullOrEmpty -Because "$Path array should not be null" + $Actual.Count | Should -Be $Expected.Count -Because "$Path array length should match" + + for ($i = 0; $i -lt $Expected.Count; $i++) { + Assert-DeepEqual -Actual $Actual[$i] -Expected $Expected[$i] -Path "$Path[$i]" + } + } + 'Boolean' { + if ($Expected) { + $Actual | Should -BeTrue -Because "$Path should be true" + } + else { + $Actual | Should -BeFalse -Because "$Path should be false" + } + } + 'Hashtable' { + $Actual | Should -Not -BeNullOrEmpty -Because "$Path hashtable should not be null" + + foreach ($key in $Expected.Keys) { + $Actual.PSObject.Properties.Name | Should -Contain $key -Because "$Path should contain key '$key'" + Assert-DeepEqual -Actual $Actual.$key -Expected $Expected[$key] -Path "$Path.$key" + } + } + default { + $Actual | Should -Be $Expected -Because "$Path value should match expected" + } + } +} + +function Test-VersionFormat { + <# + .SYNOPSIS + Validates Lambda function version format. + #> + param([string]$Version) + + $isLatest = $Version -eq '$LATEST' + $isNumber = $Version -match '^\d+$' + + return ($isLatest -or $isNumber) +} + +function Test-LambdaMemorySize { + <# + .SYNOPSIS + Validates Lambda memory size configuration. + #> + param([long]$MemoryMB) + + if ($MemoryMB -lt 128 -or $MemoryMB -gt 10240) { + return $false + } + + # Memory must be in 1 MB increments from 128 MB to 3,008 MB + # and in 64 MB increments from 3,008 MB to 10,240 MB + if ($MemoryMB -le 3008) { + return (($MemoryMB - 128) % 1 -eq 0) + } + else { + return (($MemoryMB - 3008) % 64 -eq 0) + } +} + +function Test-PositiveTime { + <# + .SYNOPSIS + Validates that time value is positive and reasonable. + #> + param([double]$TimeMs) + + return ($TimeMs -gt 0 -and $TimeMs -lt 900000) # Less than 15 minutes +} + +function Test-TimeConsistency { + <# + .SYNOPSIS + Validates that time methods return consistent values. + #> + param( + [double]$RemainingTimeMs, + [double]$RemainingTimeMillisMethod, + [double]$ToleranceMs = 100 + ) + + $difference = [Math]::Abs($RemainingTimeMs - $RemainingTimeMillisMethod) + return ($difference -lt $ToleranceMs) +} + +function Assert-ValidationRule { + <# + .SYNOPSIS + Applies a validation rule to a value. + #> + param( + $Value, + [string]$RuleName, + [string]$PropertyName + ) + + $rule = $script:ValidationRules[$RuleName] + if (-not $rule) { + throw "Unknown validation rule: $RuleName" + } + + if ($rule.Pattern) { + $Value | Should -Match $rule.Pattern -Because "$PropertyName should have $($rule.Description)" + } + + if ($rule.CustomValidator) { + $isValid = & $rule.CustomValidator $Value + $isValid | Should -BeTrue -Because "$PropertyName should have $($rule.Description)" + } + + if ($rule.AdditionalChecks) { + foreach ($check in $rule.AdditionalChecks) { + switch ($check) { + 'FunctionArnStructure' { + $Value | Should -Match ':function:' -Because "$PropertyName should contain ':function:'" + } + } + } + } +} + +function Assert-LambdaContextSchema { + <# + .SYNOPSIS + Validates Lambda context against the defined schema. + #> + param( + [Parameter(Mandatory)] + $ContextInfo, + + [Parameter(Mandatory)] + [string]$FunctionName + ) + + # Validate all schema properties + foreach ($propertyName in $script:LambdaContextSchema.Keys) { + $schema = $script:LambdaContextSchema[$propertyName] + $actualValue = $ContextInfo.$propertyName + + # Check if property exists + $ContextInfo.PSObject.Properties.Name | Should -Contain $propertyName -Because "Context should contain $propertyName property" + + # Handle optional properties that can be null + if (-not $schema.Required -and $schema.AllowNull -and $null -eq $actualValue) { + continue + } + + # Validate required properties are not null + if ($schema.Required) { + $actualValue | Should -Not -BeNullOrEmpty -Because "$propertyName should not be null or empty" + } + + # Type validation + if ($schema.Type -and $actualValue) { + switch ($schema.Type) { + 'String' { $actualValue | Should -BeOfType [string] -Because "$propertyName should be a string" } + 'Long' { $actualValue | Should -BeOfType [long] -Because "$propertyName should be a long integer" } + 'Double' { $actualValue | Should -BeOfType [double] -Because "$propertyName should be a double" } + 'Boolean' { $actualValue | Should -BeOfType [bool] -Because "$propertyName should be a boolean" } + } + } + + # Expected value validation + if ($schema.ExpectedValue) { + $actualValue | Should -Be $schema.ExpectedValue -Because "$propertyName should be $($schema.ExpectedValue)" + } + + # Custom validation rules + if ($schema.Validator -and $actualValue) { + Assert-ValidationRule -Value $actualValue -RuleName $schema.Validator -PropertyName $propertyName + } + + # Special validations + if ($schema.MatchesInput -and $propertyName -eq 'FunctionName') { + $actualValue | Should -Be $FunctionName -Because "FunctionName should match the invoked function" + } + } + + # Validate time consistency + $timeConsistent = Test-TimeConsistency -RemainingTimeMs $ContextInfo.RemainingTimeMs -RemainingTimeMillisMethod $ContextInfo.RemainingTimeMillisMethod + $timeConsistent | Should -BeTrue -Because "Time methods should return consistent values" +} + +function Test-LambdaHandlerValidation { + <# + .SYNOPSIS + Performs complete validation of a Lambda handler response. + #> + param( + [Parameter(Mandatory)] + $Response, + + [Parameter(Mandatory)] + [hashtable]$Config + ) + + # Basic response validation + $Response | Should -Not -BeNullOrEmpty -Because "Response should not be null" + $Response.statusCode | Should -Be 200 -Because "Response should have status code 200" + $Response.body | Should -Not -BeNullOrEmpty -Because "Response body should not be empty" + + $responseBody = $Response.body | ConvertFrom-Json + + # Message validation + $responseBody.message | Should -Be $Config.ExpectedMessage -Because "Response message should match expected" + + # Input validation using deep equality + $responseBody.input | Should -Not -BeNullOrEmpty -Because "Response should contain input data" + $responseBody.input.testKey | Should -Be $Config.TestKey -Because "Test key should match" + + # Validate test data using deep comparison + Assert-DeepEqual -Actual $responseBody.input -Expected (@{ testKey = $Config.TestKey } + $Config.TestData) + + # Context validation + $responseBody.contextInfo | Should -Not -BeNullOrEmpty -Because "Response should contain context info" + Assert-LambdaContextSchema -ContextInfo $responseBody.contextInfo -FunctionName $Config.FunctionName +} + +function New-TestPayload { + <# + .SYNOPSIS + Creates a test payload for Lambda function invocation. + #> + param( + [Parameter(Mandatory)] + [string]$TestKey, + + [hashtable]$AdditionalData = @{} + ) + + $payload = @{ + "testKey" = $TestKey + "timestamp" = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + } + + foreach ($key in $AdditionalData.Keys) { + $payload[$key] = $AdditionalData[$key] + } + + return ($payload | ConvertTo-Json -Depth 10) +} + +function Get-AwsAuthParameters { + <# + .SYNOPSIS + Creates a standardized AWS authentication hashtable for Lambda operations. + + .EXAMPLE + $awsAuth = Get-AwsAuthParameters + Invoke-LMFunction -FunctionName $functionName -Payload $payload @awsAuth + #> + [CmdletBinding()] + param() + + # Set up AWS region from test environment + $region = $env:PWSH_TEST_AWS_REGION + if (-not $region) { + $region = "us-east-1" + } + + $awsAuth = @{ + Region = $region + } + + if ($env:PWSH_TEST_PROFILE_NAME) { + $awsAuth['ProfileName'] = $env:PWSH_TEST_PROFILE_NAME + } + + return $awsAuth +} + +function Invoke-TestLambdaFunction { + <# + .SYNOPSIS + Invokes a Lambda function and returns the parsed response. + #> + param( + [Parameter(Mandatory)] + [string]$FunctionName, + + [Parameter(Mandatory)] + [string]$Payload, + + [Parameter(Mandatory)] + [hashtable]$AwsAuth + ) + + $result = Invoke-LMFunction -FunctionName $FunctionName -Payload $Payload @AwsAuth + $response = [System.IO.StreamReader]::new($result.Payload).ReadToEnd() | ConvertFrom-Json + return $response +} + +# Export functions +Export-ModuleMember -Function @( + 'Assert-DeepEqual', + 'Assert-LambdaContextSchema', + 'Get-AwsAuthParameters', + 'Get-CachedHandlerResponse', + 'Initialize-HandlerResponseCache', + 'Invoke-TestLambdaFunction', + 'New-TestPayload', + 'Test-LambdaHandlerValidation' +) diff --git a/powershell-runtime/tests/integration/ErrorHandling.Tests.ps1 b/powershell-runtime/tests/integration/ErrorHandling.Tests.ps1 new file mode 100644 index 0000000..6be7c92 --- /dev/null +++ b/powershell-runtime/tests/integration/ErrorHandling.Tests.ps1 @@ -0,0 +1,140 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +<# +.SYNOPSIS + Integration tests for PowerShell Lambda Runtime error handling. + +.DESCRIPTION + This file contains integration tests that verify the PowerShell Lambda Runtime + correctly handles and reports errors from PowerShell execution. These tests focus + on ensuring PowerShell execution failures are properly reported back from Lambda. + + The tests use a response caching mechanism to minimize Lambda function invocations. +#> + +BeforeAll { + # Import required modules + Import-Module AWS.Tools.Lambda -ErrorAction Stop + Import-Module AWS.Tools.CloudWatchLogs -ErrorAction Stop + Import-Module "$PSScriptRoot/../helpers/LambdaIntegrationHelpers.psm1" -Force -ErrorAction Stop + + # Check if required environment variables are set + $requiredEnvVars = @( + 'PWSH_TEST_SCRIPTHANDLERFAILINGFUNCTIONNAME', + 'PWSH_TEST_INFRASTRUCTURE_DEPLOYED' + ) + + $missingVars = $requiredEnvVars | Where-Object { -not (Test-Path "env:$_") } + if ($missingVars.Count -gt 0) { + throw "Missing required environment variables: $($missingVars -join ', '). Please run Set-IntegrationTestEnvironment.ps1 first." + } + + # Get AWS authentication parameters from helper function + $script:awsAuth = Get-AwsAuthParameters + + # Define failing function configuration + $script:FailingFunctionName = $env:PWSH_TEST_SCRIPTHANDLERFAILINGFUNCTIONNAME + + # Initialize error response cache to minimize Lambda invocations + Write-Host "Initializing error response cache..." + $script:ErrorResponseCache = $null + + # Make a single invocation to the failing function and cache the response + try { + $payload = New-TestPayload -TestKey "errorTest" -AdditionalData @{ test = "CommandNotFound" } + $response = Invoke-LMFunction -FunctionName $script:FailingFunctionName -Payload $payload @script:awsAuth + + # Cache both the raw response and the parsed error + $script:ErrorResponseCache = @{ + RawResponse = $response + ParsedError = [System.IO.StreamReader]::new($response.Payload).ReadToEnd() | ConvertFrom-Json + } + + Write-Host "Error response cached successfully." + } + catch { + throw "Failed to cache error response: $_. Tests cannot continue without a cached response." + } +} + +Describe "PowerShell Lambda Runtime Error Handling" { + Context "PowerShell Execution Failures" { + It "Should report CommandNotFoundException errors correctly" { + # Use cached response - no additional Lambda invocation needed + $errorResponse = $script:ErrorResponseCache.ParsedError + Write-Verbose "Using cached error response" + + # Validate error response + $errorResponse | Should -Not -BeNullOrEmpty -Because "Error response should not be null" + + # The error should be in the expected format + $errorResponse.errorType | Should -Be "CommandNotFoundException" -Because "Error type should match the PowerShell error" + $errorResponse.errorMessage | Should -Match "Invoke-NonExistentFunction" -Because "Error message should contain the non-existent function name" + $errorResponse.errorMessage | Should -Match "is not recognized as a name of a cmdlet" -Because "Error message should contain the standard PowerShell error text" + + # Verify the error message matches the expected format + $expectedErrorPattern = "The term 'Invoke-NonExistentFunction' is not recognized as a name of a cmdlet, function, script file, or executable program." + $errorResponse.errorMessage | Should -Match $expectedErrorPattern -Because "Error message should match the standard PowerShell CommandNotFoundException format" + } + } + + Context "Lambda Error Response Structure" { + It "Should return error responses with the correct structure" { + # Use cached response - no additional Lambda invocation needed + $errorResponse = $script:ErrorResponseCache.ParsedError + $rawResponse = $script:ErrorResponseCache.RawResponse + Write-Verbose "Using cached error response" + + # Validate error response structure + $errorResponse | Should -Not -BeNullOrEmpty -Because "Error response should not be null" + + # Check for required error properties + $errorResponse.PSObject.Properties.Name | Should -Contain "errorType" -Because "Error response should contain errorType property" + $errorResponse.PSObject.Properties.Name | Should -Contain "errorMessage" -Because "Error response should contain errorMessage property" + + # Verify the function execution status + $rawResponse.StatusCode | Should -Be 200 -Because "Lambda invocation should return HTTP 200 even for function errors" + $rawResponse.FunctionError | Should -Be "Unhandled" -Because "Function error should be marked as Unhandled" + } + } + + Context "CloudWatch Logging Integration" { + It "Should log error details to CloudWatch" { + # Use cached response to get the request ID and function name + $errorResponse = $script:ErrorResponseCache.ParsedError + Write-Verbose "Using cached error response" + + # Validate error response + $errorResponse | Should -Not -BeNullOrEmpty -Because "Error response should not be null" + $errorResponse.errorType | Should -Be "CommandNotFoundException" -Because "Error type should match the PowerShell error" + + # Construct the log group name based on the function name + $cwSplat = @{ + LogGroupName = "/aws/lambda/$($script:FailingFunctionName)" + } + + # Get the log streams for this function, sorted by last event time (most recent first) + $logStreams = Get-CWLLogStream @cwSplat @script:awsAuth | Sort-Object -Property LastEventTimestamp -Descending + $logStreams | Should -Not -BeNullOrEmpty -Because "Function should have log streams" + + # Get the most recent log stream + $latestLogStream = $logStreams | Select-Object -First 1 + $latestLogStream | Should -Not -BeNullOrEmpty -Because "Function should have a recent log stream" + + # Get the log events from the most recent stream + $logEvents = Get-CWLLogEvent -LogStreamName $latestLogStream.LogStreamName @cwSplat @script:awsAuth + $logEvents | Should -Not -BeNullOrEmpty -Because "Log stream should contain events" + + # Extract the log messages + $logMessages = $logEvents.Events.Message + + # Verify that error details are logged + $errorTypeLogged = $logMessages | Where-Object { $_ -match "CommandNotFoundException" } + $errorTypeLogged | Should -Not -BeNullOrEmpty -Because "Error type should be logged to CloudWatch" + + $errorMessageLogged = $logMessages | Where-Object { $_ -match "Invoke-NonExistentFunction" -and $_ -match "is not recognized as a name of a cmdlet" } + $errorMessageLogged | Should -Not -BeNullOrEmpty -Because "Error message should be logged to CloudWatch" + } + } +} \ No newline at end of file diff --git a/powershell-runtime/tests/integration/Lambda-Integration.Tests.ps1 b/powershell-runtime/tests/integration/Lambda-Integration.Tests.ps1 new file mode 100644 index 0000000..bbefb77 --- /dev/null +++ b/powershell-runtime/tests/integration/Lambda-Integration.Tests.ps1 @@ -0,0 +1,150 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +<# +.SYNOPSIS + Integration tests for PowerShell Lambda Runtime. + +.DESCRIPTION + This file contains integration tests that verify the PowerShell Lambda Runtime + works correctly with AWS Lambda. These tests require AWS resources to be deployed + and environment variables to be set using the Set-IntegrationTestEnvironment.ps1 script. +#> + +BeforeAll { + # Import required modules + Import-Module AWS.Tools.Lambda -ErrorAction Stop + Import-Module "$PSScriptRoot/../helpers/LambdaIntegrationHelpers.psm1" -Force -ErrorAction Stop + + # Check if required environment variables are set + $requiredEnvVars = @( + 'PWSH_TEST_SCRIPTHANDLERFUNCTIONNAME', + 'PWSH_TEST_FUNCTIONHANDLERFUNCTIONNAME', + 'PWSH_TEST_MODULEHANDLERFUNCTIONNAME', + 'PWSH_TEST_INFRASTRUCTURE_DEPLOYED' + ) + + $missingVars = $requiredEnvVars | Where-Object { -not (Test-Path "env:$_") } + if ($missingVars.Count -gt 0) { + throw "Missing required environment variables: $($missingVars -join ', '). Please run Set-IntegrationTestEnvironment.ps1 first." + } + + # Get AWS authentication parameters from helper function + $script:awsAuth = Get-AwsAuthParameters + + # Define handler configurations + $script:HandlerConfigs = @( + @{ + HandlerType = 'Script' + FunctionName = $env:PWSH_TEST_SCRIPTHANDLERFUNCTIONNAME + ExpectedMessage = "Hello from PowerShell script handler!" + TestKey = "scriptTest" + TestData = @{ number = 42; string = "test"; bool = $true } + } + @{ + HandlerType = 'Function' + FunctionName = $env:PWSH_TEST_FUNCTIONHANDLERFUNCTIONNAME + ExpectedMessage = "Hello from PowerShell function handler!" + TestKey = "functionTest" + TestData = @{ array = @(1, 2, 3); string = "function"; bool = $false } + } + @{ + HandlerType = 'Module' + FunctionName = $env:PWSH_TEST_MODULEHANDLERFUNCTIONNAME + ExpectedMessage = "Hello from PowerShell module handler!" + TestKey = "moduleTest" + TestData = @{ nested = @{ key = "value" }; number = 123; bool = $true } + } + ) + + # Initialize response cache to reduce Lambda invocations + Write-Host "Initializing handler response cache..." + Initialize-HandlerResponseCache -HandlerConfigs $script:HandlerConfigs @script:awsAuth + Write-Host "Response cache initialized successfully." +} + +Describe "PowerShell Lambda Runtime Integration Tests" { + Context "Handler Invocation and Input Validation Tests" { + It "Should successfully invoke and validate handler" -ForEach $script:HandlerConfigs { + # Get cached response to avoid duplicate Lambda invocations + $cachedResult = Get-CachedHandlerResponse -HandlerType $_.HandlerType + + # Perform complete validation using consolidated helper function + Test-LambdaHandlerValidation -Response $cachedResult.Response -Config $cachedResult.Config + } + } + + Context "Lambda Context Property Validation" { + It "Should validate all Lambda context properties for handler" -ForEach $script:HandlerConfigs { + # Get cached response - no additional Lambda invocation needed + $cachedResult = Get-CachedHandlerResponse -HandlerType $_.HandlerType + $responseBody = $cachedResult.Response.body | ConvertFrom-Json + + # Use schema-based validation instead of repetitive assertions + Assert-LambdaContextSchema -ContextInfo $responseBody.contextInfo -FunctionName $_.FunctionName + } + } + + Context "Input Data Type Preservation Tests" { + It "Should preserve data types correctly for handler" -ForEach @( + @{ HandlerType = 'Script'; DataType = 'Number'; TestValue = 42; PropertyName = 'number' } + @{ HandlerType = 'Script'; DataType = 'Boolean'; TestValue = $true; PropertyName = 'bool' } + @{ HandlerType = 'Function'; DataType = 'Array'; TestValue = @(1, 2, 3); PropertyName = 'array' } + @{ HandlerType = 'Function'; DataType = 'Boolean'; TestValue = $false; PropertyName = 'bool' } + @{ HandlerType = 'Module'; DataType = 'Nested Object'; TestValue = @{ key = "value" }; PropertyName = 'nested' } + @{ HandlerType = 'Module'; DataType = 'Number'; TestValue = 123; PropertyName = 'number' } + ) { + # Get cached response + $cachedResult = Get-CachedHandlerResponse -HandlerType $_.HandlerType + $responseBody = $cachedResult.Response.body | ConvertFrom-Json + + # Use deep equality validation for data type preservation + $actualValue = $responseBody.input.($_.PropertyName) + Assert-DeepEqual -Actual $actualValue -Expected $_.TestValue -Path "input.$($_.PropertyName)" + } + } + + Context "Response Structure Validation" { + It "Should have consistent response structure across all handler types" { + foreach ($config in $script:HandlerConfigs) { + $cachedResult = Get-CachedHandlerResponse -HandlerType $config.HandlerType + $response = $cachedResult.Response + $responseBody = $response.body | ConvertFrom-Json + + # Validate response structure + $response.statusCode | Should -Be 200 -Because "$($config.HandlerType) handler should return status 200" + + # Validate required response body properties + $requiredProperties = @('message', 'input', 'contextInfo') + foreach ($property in $requiredProperties) { + $responseBody.PSObject.Properties.Name | Should -Contain $property -Because "$($config.HandlerType) response should contain $property" + } + + # Validate message content + $responseBody.message | Should -Be $config.ExpectedMessage -Because "$($config.HandlerType) should return expected message" + + # Validate input echo + $responseBody.input.testKey | Should -Be $config.TestKey -Because "$($config.HandlerType) should echo test key correctly" + } + } + } + + Context "Performance and Consistency Validation" { + It "Should have reasonable execution times and consistent context values" { + foreach ($config in $script:HandlerConfigs) { + $cachedResult = Get-CachedHandlerResponse -HandlerType $config.HandlerType + $responseBody = $cachedResult.Response.body | ConvertFrom-Json + $contextInfo = $responseBody.contextInfo + + # Validate execution time is reasonable + $contextInfo.RemainingTimeMs | Should -BeGreaterThan 0 -Because "$($config.HandlerType) should have positive remaining time" + $contextInfo.RemainingTimeMs | Should -BeLessThan 900000 -Because "$($config.HandlerType) should complete within reasonable time" + + # Validate context consistency flags + $contextInfo.HasValidRequestId | Should -BeTrue -Because "$($config.HandlerType) should have valid request ID" + $contextInfo.HasValidFunctionName | Should -BeTrue -Because "$($config.HandlerType) should have valid function name" + $contextInfo.TimeMethodsConsistent | Should -BeTrue -Because "$($config.HandlerType) time methods should be consistent" + } + } + } +} diff --git a/powershell-runtime/tests/integration/infrastructure/README.md b/powershell-runtime/tests/integration/infrastructure/README.md new file mode 100644 index 0000000..2c07ab8 --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/README.md @@ -0,0 +1,69 @@ +# PowerShell Runtime Integration Test Infrastructure + +This directory contains AWS SAM template and function code for PowerShell Lambda Runtime integration tests. + +## Directory Structure + +```text +infrastructure/ +├── function/ # Lambda function code and utilities +│ ├── Get-LambdaContextInfo.ps1 # Lambda context utility function +│ ├── Makefile # Build configuration +│ ├── test-function-handler.ps1 # Function handler test +│ ├── test-script-handler.ps1 # Script handler test +│ ├── test-failing-script-handler.ps1 # Failing script handler test +│ └── modules/ # PowerShell modules +│ └── test-module-handler/ # Module handler test module +│ └── 1.0.0/ +│ ├── test-module-handler.psd1 +│ └── test-module-handler.psm1 +├── template.yml # CloudFormation template +└── README.md # This file +``` + +## Infrastructure Components + +The template creates AWS resources for integration testing, such as Lambda functions and associated resources. + +## Prerequisites + +* **AWS SAM CLI** installed and configured +* **AWS credentials** configured with appropriate permissions +* **PowerShell 7.0 or later** (see main README for installation) + +## Deployment + +### Build Resources + +```bash +sam build +``` + +### Deploy Stack + +```bash +sam deploy +``` + +### Deploy with Existing Runtime Layer + +```bash +sam deploy --parameter-overrides PowerShellRuntimeLayerArn=arn:aws:lambda:us-east-1:123456789012:layer:powershell-runtime:1 +``` + +### Clean Up + +```bash +sam delete +``` + +## Test Functions + +The infrastructure includes four Lambda functions testing each PowerShell handler type: + +| Handler Type | Function Name | Handler | Purpose | +|--------------|---------------|---------|---------| +| Script | `ScriptHandlerFunction` | `test-script-handler.ps1` | Direct PowerShell script execution | +| Function | `FunctionHandlerFunction` | `test-function-handler.ps1::Invoke-TestFunction` | PowerShell function execution within a script | +| Module | `ModuleHandlerFunction` | `Module::test-module-handler::Invoke-TestModuleHandler` | PowerShell module function execution | +| Failing Script | `ScriptHandlerFailingFunction` | `test-failing-script-handler.ps1` | Error handling and failure scenarios | diff --git a/powershell-runtime/tests/integration/infrastructure/function/Get-LambdaContextInfo.ps1 b/powershell-runtime/tests/integration/infrastructure/function/Get-LambdaContextInfo.ps1 new file mode 100644 index 0000000..506e1e9 --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/Get-LambdaContextInfo.ps1 @@ -0,0 +1,32 @@ +function Get-LambdaContextInfo { + param($LambdaContext) + + return @{ + # Core identification properties + AwsRequestId = $LambdaContext.AwsRequestId + FunctionName = $LambdaContext.FunctionName + FunctionVersion = $LambdaContext.FunctionVersion + InvokedFunctionArn = $LambdaContext.InvokedFunctionArn + + # Resource properties + MemoryLimitInMB = $LambdaContext.MemoryLimitInMB + + # Logging properties (can be null) + LogGroupName = $LambdaContext.LogGroupName + LogStreamName = $LambdaContext.LogStreamName + + # Optional properties (can be null) + Identity = $LambdaContext.Identity + ClientContext = $LambdaContext.ClientContext + + # Timing properties - both methods for validation + RemainingTimeMs = $LambdaContext.RemainingTime.TotalMilliseconds + RemainingTimeMillisMethod = $LambdaContext.GetRemainingTimeInMillis() + RemainingTimeSpan = $LambdaContext.RemainingTime.ToString() + + # Basic validation flags + HasValidRequestId = (-not [string]::IsNullOrEmpty($LambdaContext.AwsRequestId)) + HasValidFunctionName = (-not [string]::IsNullOrEmpty($LambdaContext.FunctionName)) + TimeMethodsConsistent = [Math]::Abs($LambdaContext.RemainingTime.TotalMilliseconds - $LambdaContext.GetRemainingTimeInMillis()) -lt 100 + } +} diff --git a/powershell-runtime/tests/integration/infrastructure/function/Makefile b/powershell-runtime/tests/integration/infrastructure/function/Makefile new file mode 100644 index 0000000..13e063b --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/Makefile @@ -0,0 +1,15 @@ +build-FunctionHandlerFunction: + cp -R . $(ARTIFACTS_DIR) + rm $(ARTIFACTS_DIR)/Makefile + +build-ModuleHandlerFunction: + cp -R . $(ARTIFACTS_DIR) + rm $(ARTIFACTS_DIR)/Makefile + +build-ScriptHandlerFunction: + cp -R . $(ARTIFACTS_DIR) + rm $(ARTIFACTS_DIR)/Makefile + +build-ScriptHandlerFailingFunction: + cp -R . $(ARTIFACTS_DIR) + rm $(ARTIFACTS_DIR)/Makefile diff --git a/powershell-runtime/tests/integration/infrastructure/function/modules/test-module-handler/1.0.0/test-module-handler.psd1 b/powershell-runtime/tests/integration/infrastructure/function/modules/test-module-handler/1.0.0/test-module-handler.psd1 new file mode 100644 index 0000000..5fbbf1e --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/modules/test-module-handler/1.0.0/test-module-handler.psd1 @@ -0,0 +1,24 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +@{ + RootModule = 'test-module-handler.psm1' + ModuleVersion = '1.0.0' + GUID = '12345678-1234-1234-1234-123456789012' + Author = 'AWS' + CompanyName = 'Amazon Web Services' + Copyright = 'Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.' + Description = 'Test module for PowerShell Lambda Runtime integration tests' + PowerShellVersion = '7.0' + FunctionsToExport = @('Invoke-TestModuleHandler') + CmdletsToExport = @() + VariablesToExport = @() + AliasesToExport = @() + PrivateData = @{ + PSData = @{ + Tags = @('AWS', 'Lambda', 'PowerShell', 'Test') + LicenseUri = 'https://github.com/awslabs/aws-lambda-powershell-runtime/blob/main/LICENSE' + ProjectUri = 'https://github.com/awslabs/aws-lambda-powershell-runtime' + } + } +} \ No newline at end of file diff --git a/powershell-runtime/tests/integration/infrastructure/function/modules/test-module-handler/1.0.0/test-module-handler.psm1 b/powershell-runtime/tests/integration/infrastructure/function/modules/test-module-handler/1.0.0/test-module-handler.psm1 new file mode 100644 index 0000000..36d251d --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/modules/test-module-handler/1.0.0/test-module-handler.psm1 @@ -0,0 +1,30 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# This is a simple module handler for testing the PowerShell Lambda Runtime + +. "$env:LAMBDA_TASK_ROOT/Get-LambdaContextInfo.ps1" + +function Invoke-TestModuleHandler { + param( + $LambdaInput, + $LambdaContext + ) + + # Create a response object + $response = @{ + statusCode = 200 + headers = @{ + "Content-Type" = "application/json" + } + body = @{ + message = "Hello from PowerShell module handler!" + input = $LambdaInput + + contextInfo = Get-LambdaContextInfo -LambdaContext $LambdaContext + } | ConvertTo-Json -Depth 10 + } + + # Return the response + $response | ConvertTo-Json -Depth 10 +} diff --git a/powershell-runtime/tests/integration/infrastructure/function/test-failing-script-handler.ps1 b/powershell-runtime/tests/integration/infrastructure/function/test-failing-script-handler.ps1 new file mode 100644 index 0000000..144ace4 --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/test-failing-script-handler.ps1 @@ -0,0 +1,9 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# This is a simple script handler for testing PowerShell failures in the PowerShell Lambda Runtime + +param ($LambdaInput, $LambdaContext) + +$ErrorActionPreference = 'Stop' +Invoke-NonExistentFunction diff --git a/powershell-runtime/tests/integration/infrastructure/function/test-function-handler.ps1 b/powershell-runtime/tests/integration/infrastructure/function/test-function-handler.ps1 new file mode 100644 index 0000000..2925d4a --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/test-function-handler.ps1 @@ -0,0 +1,30 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# This is a simple function handler for testing the PowerShell Lambda Runtime + +. ./Get-LambdaContextInfo.ps1 + +function Invoke-TestFunction { + param( + $LambdaInput, + $LambdaContext + ) + + # Create a response object + $response = @{ + statusCode = 200 + headers = @{ + "Content-Type" = "application/json" + } + body = @{ + message = "Hello from PowerShell function handler!" + input = $LambdaInput + + contextInfo = Get-LambdaContextInfo -LambdaContext $LambdaContext + } | ConvertTo-Json -Depth 10 + } + + # Return the response + $response | ConvertTo-Json -Depth 10 +} diff --git a/powershell-runtime/tests/integration/infrastructure/function/test-script-handler.ps1 b/powershell-runtime/tests/integration/infrastructure/function/test-script-handler.ps1 new file mode 100644 index 0000000..ae45624 --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/function/test-script-handler.ps1 @@ -0,0 +1,26 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# This is a simple script handler for testing the PowerShell Lambda Runtime + +# $LambdaInput and $LambdaContext are automatically available to the script +param ($LambdaInput, $LambdaContext) + +. ./Get-LambdaContextInfo.ps1 + +# Create a response object +$response = @{ + statusCode = 200 + headers = @{ + "Content-Type" = "application/json" + } + body = @{ + message = "Hello from PowerShell script handler!" + input = $LambdaInput + + contextInfo = Get-LambdaContextInfo -LambdaContext $LambdaContext + } | ConvertTo-Json -Depth 10 +} + +# Return the response +$response | ConvertTo-Json -Depth 10 diff --git a/powershell-runtime/tests/integration/infrastructure/template.yml b/powershell-runtime/tests/integration/infrastructure/template.yml new file mode 100644 index 0000000..59f0c13 --- /dev/null +++ b/powershell-runtime/tests/integration/infrastructure/template.yml @@ -0,0 +1,280 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + PowerShell Runtime Integration Test Resources + + This template creates resources needed for integration testing of the PowerShell runtime: + - Lambda functions for all three handler types (Script, Function, Module) + - S3 bucket for test artifacts and deployment packages + - IAM roles with minimal required permissions + - CloudWatch Log Groups for Lambda function logging + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Environment Configuration" + Parameters: + - Environment + - Label: + default: "Runtime Configuration" + Parameters: + - PowerShellRuntimeLayerArn + ParameterLabels: + Environment: + default: "Environment Name" + PowerShellRuntimeLayerArn: + default: "PowerShell Runtime Layer ARN" + +# Global parameters +Parameters: + Environment: + Type: String + Default: IntegrationTest + Description: Environment name for resource tagging + AllowedValues: + - IntegrationTest + - Development + - Production + + PowerShellRuntimeLayerArn: + Type: String + Description: ARN of the PowerShell runtime layer + AllowedPattern: "^$|^arn:aws:lambda:[a-z0-9-]+:[0-9]{12}:layer:[a-zA-Z0-9-_]+:[0-9]+$" + ConstraintDescription: "Must be a valid Lambda layer ARN" + +# Global resource properties +Globals: + Function: + Timeout: 30 + MemorySize: 512 + Runtime: provided.al2023 + Architectures: + - x86_64 + Environment: + Variables: + POWERSHELL_RUNTIME_VERBOSE: "TRUE" + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "1" + Tags: + Environment: !Ref Environment + Purpose: PowerShellRuntimeIntegrationTest + +Resources: + # S3 bucket for test artifacts and deployment packages + IntegrationTestBucket: + Type: AWS::S3::Bucket + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + LifecycleConfiguration: + Rules: + - Id: DeleteAfter7Days + Status: Enabled + ExpirationInDays: 7 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-integration-test-bucket" + - Key: Environment + Value: !Ref Environment + - Key: Purpose + Value: PowerShellRuntimeIntegrationTest + + # IAM Role for Lambda execution with minimal permissions + LambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-lambda-execution-role" + - Key: Environment + Value: !Ref Environment + - Key: Purpose + Value: PowerShellRuntimeIntegrationTest + + # Function Handler Lambda Function + FunctionHandlerFunction: + Type: AWS::Serverless::Function + Properties: + Description: PowerShell Function Handler Test Function + CodeUri: function/ + Handler: test-function-handler.ps1::Invoke-TestFunction + Role: !GetAtt LambdaExecutionRole.Arn + Layers: + - !Ref PowerShellRuntimeLayerArn + Tags: + HandlerType: Function + Name: !Sub "${AWS::StackName}-function-handler" + + # Module Handler Lambda Function + ModuleHandlerFunction: + Type: AWS::Serverless::Function + Properties: + Description: PowerShell Module Handler Test Function + CodeUri: function/ + Handler: Module::test-module-handler::Invoke-TestModuleHandler + Role: !GetAtt LambdaExecutionRole.Arn + Layers: + - !Ref PowerShellRuntimeLayerArn + Tags: + Name: !Sub "${AWS::StackName}-module-handler" + HandlerType: Module + + # Script Handler Lambda Function + ScriptHandlerFunction: + Type: AWS::Serverless::Function + Properties: + Description: PowerShell Script Handler Test Function + CodeUri: function/ + Handler: test-script-handler.ps1 + Role: !GetAtt LambdaExecutionRole.Arn + Layers: + - !Ref PowerShellRuntimeLayerArn + Tags: + Name: !Sub "${AWS::StackName}-script-handler" + HandlerType: Script + + # Script Handler Lambda Function to test execution failures + ScriptHandlerFailingFunction: + Type: AWS::Serverless::Function + Properties: + Description: PowerShell Script Handler Failing Test Function + CodeUri: function/ + Handler: test-failing-script-handler.ps1 + Role: !GetAtt LambdaExecutionRole.Arn + Layers: + - !Ref PowerShellRuntimeLayerArn + Tags: + Name: !Sub "${AWS::StackName}-failing-script-handler" + HandlerType: Script + + FunctionHandlerLogGroup: + Type: AWS::Logs::LogGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + LogGroupName: !Sub "/aws/lambda/${FunctionHandlerFunction}" + RetentionInDays: 7 + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-function-handler-logs" + - Key: Environment + Value: !Ref Environment + - Key: Purpose + Value: PowerShellRuntimeIntegrationTest + - Key: HandlerType + Value: Function + + ModuleHandlerLogGroup: + Type: AWS::Logs::LogGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + LogGroupName: !Sub "/aws/lambda/${ModuleHandlerFunction}" + RetentionInDays: 7 + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-module-handler-logs" + - Key: Environment + Value: !Ref Environment + - Key: Purpose + Value: PowerShellRuntimeIntegrationTest + - Key: HandlerType + Value: Module + + ScriptHandlerLogGroup: + Type: AWS::Logs::LogGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + LogGroupName: !Sub "/aws/lambda/${ScriptHandlerFunction}" + RetentionInDays: 7 + Tags: + - Key: Environment + Value: !Ref Environment + - Key: Purpose + Value: PowerShellRuntimeIntegrationTest + - Key: Name + Value: !Sub "${AWS::StackName}-script-handler-logs" + - Key: HandlerType + Value: Script + + ScriptHandlerFailingLogGroup: + Type: AWS::Logs::LogGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + LogGroupName: !Sub "/aws/lambda/${ScriptHandlerFailingFunction}" + RetentionInDays: 7 + Tags: + - Key: Environment + Value: !Ref Environment + - Key: Purpose + Value: PowerShellRuntimeIntegrationTest + - Key: Name + Value: !Sub "${AWS::StackName}-script-handler-failing-logs" + - Key: HandlerType + Value: Script + +# Stack outputs for integration tests +Outputs: + FunctionHandlerFunctionArn: + Description: ARN of the Function Handler Lambda function + Value: !GetAtt FunctionHandlerFunction.Arn + + ModuleHandlerFunctionArn: + Description: ARN of the Module Handler Lambda function + Value: !GetAtt ModuleHandlerFunction.Arn + + ScriptHandlerFunctionArn: + Description: ARN of the Script Handler Lambda function + Value: !GetAtt ScriptHandlerFunction.Arn + + ScriptHandlerFailingFunctionArn: + Description: ARN of the Script Handler Lambda function designed to fail + Value: !GetAtt ScriptHandlerFailingFunction.Arn + + FunctionHandlerFunctionName: + Description: Name of the Function Handler Lambda function + Value: !Ref FunctionHandlerFunction + + ModuleHandlerFunctionName: + Description: Name of the Module Handler Lambda function + Value: !Ref ModuleHandlerFunction + + ScriptHandlerFunctionName: + Description: Name of the Script Handler Lambda function + Value: !Ref ScriptHandlerFunction + + ScriptHandlerFailingFunctionName: + Description: Name of the Script Handler Lambda function designed to fail + Value: !Ref ScriptHandlerFailingFunction + + IntegrationTestBucketName: + Description: Name of the S3 bucket for test artifacts + Value: !Ref IntegrationTestBucket + + PowerShellRuntimeLayerArn: + Description: ARN of the PowerShell runtime layer + Value: !Ref PowerShellRuntimeLayerArn + + LambdaExecutionRoleArn: + Description: ARN of the Lambda execution role + Value: !GetAtt LambdaExecutionRole.Arn diff --git a/powershell-runtime/tests/unit/Private/Set-LambdaContext.Tests.ps1 b/powershell-runtime/tests/unit/Private/Set-LambdaContext.Tests.ps1 index d51ba6c..99087a0 100644 --- a/powershell-runtime/tests/unit/Private/Set-LambdaContext.Tests.ps1 +++ b/powershell-runtime/tests/unit/Private/Set-LambdaContext.Tests.ps1 @@ -47,16 +47,16 @@ Describe "Set-LambdaContext" { BeforeEach { # Set up valid environment variables for Lambda context Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'test-function' - 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:test-function' - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'test-request-id-12345' - 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/test-function' - 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]abcdef123456' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'test-cognito-identity' - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'test-client-context' - 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(5).ToUnixTimeMilliseconds()).ToString() + 'AWS_LAMBDA_FUNCTION_NAME' = 'test-function' + 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:test-function' + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'test-request-id-12345' + 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/test-function' + 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]abcdef123456' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'test-cognito-identity' + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'test-client-context' + 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(5).ToUnixTimeMilliseconds()).ToString() } } @@ -84,7 +84,8 @@ Describe "Set-LambdaContext" { # Assert - Note: There's a bug in the C# code where InvokedFunctionArn is set to FunctionName instead of the ARN # This test documents the current behavior - $result.InvokedFunctionArn | Should -Be 'test-function' + Write-Verbose $result.InvokedFunctionArn -Verbose + $result.InvokedFunctionArn | Should -Be 'arn:aws:lambda:us-east-1:123456789012:function:test-function' } It "Should create functional RemainingTime property" { @@ -136,16 +137,16 @@ Describe "Set-LambdaContext" { It "Should handle null/empty environment variables gracefully" { # Arrange - Set some variables to null/empty Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'test-function' - 'AWS_LAMBDA_FUNCTION_VERSION' = '' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = $null - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '256' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'test-request' - 'AWS_LAMBDA_LOG_GROUP_NAME' = $null - 'AWS_LAMBDA_LOG_STREAM_NAME' = '' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = $null - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = $null - 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(1).ToUnixTimeMilliseconds()).ToString() + 'AWS_LAMBDA_FUNCTION_NAME' = 'test-function' + 'AWS_LAMBDA_FUNCTION_VERSION' = '' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = $null + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '256' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'test-request' + 'AWS_LAMBDA_LOG_GROUP_NAME' = $null + 'AWS_LAMBDA_LOG_STREAM_NAME' = '' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = $null + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = $null + 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(1).ToUnixTimeMilliseconds()).ToString() } # Act @@ -164,16 +165,16 @@ Describe "Set-LambdaContext" { It "Should handle different memory sizes correctly" { # Arrange Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'memory-test' - 'AWS_LAMBDA_FUNCTION_VERSION' = '2.0' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = 'arn:aws:lambda:us-west-2:123456789012:function:memory-test' - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '1024' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'memory-test-request' - 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/memory-test' - 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]memory123' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'memory-cognito' - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'memory-client' - 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(2).ToUnixTimeMilliseconds()).ToString() + 'AWS_LAMBDA_FUNCTION_NAME' = 'memory-test' + 'AWS_LAMBDA_FUNCTION_VERSION' = '2.0' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = 'arn:aws:lambda:us-west-2:123456789012:function:memory-test' + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '1024' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'memory-test-request' + 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/memory-test' + 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]memory123' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'memory-cognito' + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'memory-client' + 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(2).ToUnixTimeMilliseconds()).ToString() } # Act @@ -188,16 +189,16 @@ Describe "Set-LambdaContext" { It "Should handle invalid memory size gracefully" { # Arrange - Set invalid memory size Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'invalid-memory-test' - 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:invalid-memory-test' - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = 'invalid' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'invalid-memory-request' - 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/invalid-memory-test' - 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]invalid123' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'invalid-cognito' - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'invalid-client' - 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(1).ToUnixTimeMilliseconds()).ToString() + 'AWS_LAMBDA_FUNCTION_NAME' = 'invalid-memory-test' + 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:invalid-memory-test' + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = 'invalid' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'invalid-memory-request' + 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/invalid-memory-test' + 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]invalid123' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'invalid-cognito' + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'invalid-client' + 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(1).ToUnixTimeMilliseconds()).ToString() } # Act & Assert - Should throw when trying to convert invalid memory size @@ -207,16 +208,16 @@ Describe "Set-LambdaContext" { It "Should handle invalid deadline gracefully" { # Arrange - Set invalid deadline Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'invalid-deadline-test' - 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:invalid-deadline-test' - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'invalid-deadline-request' - 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/invalid-deadline-test' - 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]invalid456' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'invalid-deadline-cognito' - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'invalid-deadline-client' - 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = 'not-a-number' + 'AWS_LAMBDA_FUNCTION_NAME' = 'invalid-deadline-test' + 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:invalid-deadline-test' + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'invalid-deadline-request' + 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/invalid-deadline-test' + 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]invalid456' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'invalid-deadline-cognito' + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'invalid-deadline-client' + 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = 'not-a-number' } # Act & Assert - Should throw when trying to convert invalid deadline @@ -230,15 +231,15 @@ Describe "Set-LambdaContext" { BeforeEach { # Set up environment variables Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'time-test-function' - 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:time-test-function' - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'time-test-request' - 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/time-test-function' - 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]timetest' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'time-test-cognito' - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'time-test-client' + 'AWS_LAMBDA_FUNCTION_NAME' = 'time-test-function' + 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:time-test-function' + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'time-test-request' + 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/time-test-function' + 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]timetest' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'time-test-cognito' + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'time-test-client' } } @@ -304,17 +305,17 @@ Describe "Set-LambdaContext" { BeforeEach { # Set up environment variables with verbose logging enabled Set-TestEnvironmentVariables -Variables @{ - 'AWS_LAMBDA_FUNCTION_NAME' = 'verbose-test' - 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' - 'AWS_LAMBDA_RUNTIME_INVOKE_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:verbose-test' - 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' - 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'verbose-request' - 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/verbose-test' - 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]verbose' - 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'verbose-cognito' - 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'verbose-client' - 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(1).ToUnixTimeMilliseconds()).ToString() - 'POWERSHELL_RUNTIME_VERBOSE' = 'TRUE' + 'AWS_LAMBDA_FUNCTION_NAME' = 'verbose-test' + 'AWS_LAMBDA_FUNCTION_VERSION' = '1.0' + 'AWS_LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN' = 'arn:aws:lambda:us-east-1:123456789012:function:verbose-test' + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' = '512' + 'AWS_LAMBDA_RUNTIME_AWS_REQUEST_ID' = 'verbose-request' + 'AWS_LAMBDA_LOG_GROUP_NAME' = '/aws/lambda/verbose-test' + 'AWS_LAMBDA_LOG_STREAM_NAME' = '2023/01/01/[$LATEST]verbose' + 'AWS_LAMBDA_RUNTIME_COGNITO_IDENTITY' = 'verbose-cognito' + 'AWS_LAMBDA_RUNTIME_CLIENT_CONTEXT' = 'verbose-client' + 'AWS_LAMBDA_RUNTIME_DEADLINE_MS' = ([DateTimeOffset]::UtcNow.AddMinutes(1).ToUnixTimeMilliseconds()).ToString() + 'POWERSHELL_RUNTIME_VERBOSE' = 'TRUE' } }