> ## Content Index
> Fetch the complete content index at: https://www.techbyjeff.net/llms.txt
> Use this file to discover other available public pages before exploring further.

# Testing Security Functions with Pester: A Real-World Example
- URL: https://www.techbyjeff.net/testing-security-functions-with-pester-a-real-world-example/
- Published: 2025-08-01T16:00:01.000Z
- Updated: 2026-08-19T21:59:34.000Z
- Description: A real path traversal validator, tested end to end with Pester. The test patterns, the edge cases that actually matter, and coverage that holds up once the function ships.
- Author: Jeffrey Stuhr
- Tags: PowerShell, DevOps, Security, Windows

When building security-focused PowerShell functions, comprehensive testing isn't just good practice—it's essential. Today, I'll walk you through a real-world example of how to use Pester to thoroughly test a path traversal validation function, demonstrating testing patterns that can be applied to any PowerShell security module.

The files this blog references are [Test-PathTraversal.ps1](https://github.com/fadwen/TechbyJeff/blob/main/Powershell/Active%20Directory/Find-UnknownSID/Private/Security/Test-PathTraversal.ps1?ref=techbyjeff.net) and its companion test [Test-PathTraversal.tests.ps1](https://github.com/fadwen/TechbyJeff/blob/main/Powershell/Active%20Directory/Find-UnknownSID/Tests/Unit/Private/Security/Test-PathTraversal.Tests.ps1?ref=techbyjeff.net) located on my GitHub.

## Pester Version Context

This example uses **Pester 3.4**, which is installed by default on Windows systems and remains widely used in enterprise environments. While Pester has evolved significantly with newer versions (currently at 5.x), understanding 3.4 syntax is valuable because:

- It's the default version on Windows Server and many corporate workstations
- Many existing test suites use this syntax
- Migration paths exist to newer versions when ready

**Key differences you'll notice in this Pester 3.4 example:**

- Uses `Should Be` instead of the newer `Should -Be` syntax
- Relies on script-scoped variables (`$script:`) for test data sharing
- Uses the older `BeforeAll`/`AfterAll` structure

**Benefits of upgrading to Pester 5.x include:**

- Improved performance and parallel test execution
- Better error reporting and debugging capabilities
- More flexible configuration options
- Enhanced mocking capabilities
- Cleaner syntax with pipeline-based assertions

## The Function Under Test

Our example centers around `Test-PathTraversal`, a PowerShell function designed to detect and prevent path traversal attacks—a common security vulnerability where malicious users attempt to access files outside of intended directories using patterns like `../../../etc/passwd`.

The function validates file paths against several criteria:

- Detects common traversal patterns (`..\`, `%2e%2e%2f`, etc.)
- Ensures paths remain within a specified base directory
- Handles both existing and non-existing paths
- Provides detailed validation results with correlation tracking

## Test Structure and Organization

The Pester test suite is organized into logical contexts that mirror real-world usage scenarios:

```powershell
Describe "Test-PathTraversal" {
    BeforeAll {
        # Set up test environment
        $env:PESTER_TESTING = $true
        
        # Create isolated test directory structure
        $script:TestBasePath = Join-Path $env:TEMP "PathTraversalTests"
        # ... setup test files and directories
    }

    Context "Parameter Validation" { }
    Context "Path Traversal Detection" { }
    Context "Security Validation" { }
    Context "Error Handling" { }
    Context "Batch Processing" { }
    Context "Pipeline Support" { }
    Context "Logging Integration" { }
    Context "Result Object Structure" { }
    Context "Performance Requirements" { }
}

```

This structure provides clear separation of concerns and makes it easy to understand what aspects of the function are being tested.

## Key Testing Patterns

### 1\. Isolated Test Environment

The tests create a completely isolated test environment in the temp directory:

```powershell
BeforeAll {
    $script:TestBasePath = Join-Path $env:TEMP "PathTraversalTests"
    if (Test-Path $script:TestBasePath) {
        Remove-Item $script:TestBasePath -Recurse -Force
    }
    New-Item -Path $script:TestBasePath -ItemType Directory -Force | Out-Null
    
    # Create nested directories and test files
    $script:SafeDir = Join-Path $script:TestBasePath "SafeDirectory"
    $script:SafeFile = Join-Path $script:SafeDir "safe.txt"
    # ...
}

```

This ensures tests are completely independent and don't interfere with the actual file system.

### 2\. Security-Focused Test Cases

The tests include comprehensive security validation scenarios:

```powershell
It "Should reject common traversal attack patterns" {
    $attackPatterns = @(
        "..\..\..\etc\passwd",
        "..\..\Windows\System32\config",
        "..\..\..\var\log\auth.log",
        "..%2f..%2f..%2fetc%2fpasswd"
    )
    
    foreach ($pattern in $attackPatterns) {
        $testPath = Join-Path $script:SafeDir $pattern
        $result = Test-PathTraversal -Path $testPath -BasePath $script:TestBasePath
        $result.ValidationResults[0].ContainsTraversalPattern | Should Be $true
    }
}

```

This pattern ensures the function can detect various attack vectors, including URL-encoded attempts.

### 3\. Pipeline Testing

PowerShell's pipeline capabilities are thoroughly tested:

```powershell
It "Should support pipeline input from Get-ChildItem" {
    $pipelineResult = Get-ChildItem $script:SafeDir -Recurse | 
        Select-Object -ExpandProperty FullName | 
        Test-PathTraversal -BasePath $script:TestBasePath
    
    $pipelineResult.TotalPaths | Should BeGreaterThan 0
    $pipelineResult.SafePaths | Should Be $pipelineResult.TotalPaths
    $pipelineResult.ValidationPassed | Should Be $true
}

```

This ensures the function works seamlessly with PowerShell's object pipeline.

### 4\. Performance Testing

Critical for security functions that might process large datasets:

```powershell
It "Should complete within acceptable time limits" {
    $largeBatch = 1..100 | ForEach-Object { $script:SafeFile }
    
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $result = Test-PathTraversal -Path $largeBatch -BasePath $script:TestBasePath
    $stopwatch.Stop()
    
    $stopwatch.ElapsedMilliseconds | Should BeLessThan 10000  # 10 seconds max
    $result.TotalPaths | Should Be 100
}

```

Performance tests help ensure the function remains responsive under load.

### 5\. Error Handling Validation

Robust error handling is crucial for security functions:

```powershell
It "Should handle invalid base path gracefully" {
    $invalidBasePath = "Z:\NonExistent\Path"
    { Test-PathTraversal -Path $script:SafeFile -BasePath $invalidBasePath } | Should Throw
}

It "Should handle non-existent paths gracefully" {
    $nonExistentPath = Join-Path $script:TestBasePath "nonexistent.txt"
    $result = Test-PathTraversal -Path $nonExistentPath -BasePath $script:TestBasePath
    $result.UnsafePaths | Should Be 1
    $result.ValidationPassed | Should Be $false
}

```

These tests ensure the function fails safely and provides meaningful feedback.

## Advanced Testing Techniques

### Mocking External Dependencies

The tests mock external logging functions to focus on the core functionality:

```powershell
BeforeAll {
    Mock Write-StructuredLog { }
}

```

This isolates the function under test from external dependencies.

### Environment-Aware Testing

The tests set environment variables to modify behavior during testing:

```powershell
BeforeAll {
    $env:PESTER_TESTING = $true
}

AfterAll {
    Remove-Item -Path "env:PESTER_TESTING" -ErrorAction SilentlyContinue
}

```

This allows the function to suppress warnings during testing while maintaining them in production.

### Comprehensive Result Validation

Tests validate not just the final result, but the structure and completeness of returned data:

```powershell
It "Should return properly structured result object" {
    $result = Test-PathTraversal -Path $script:SafeFile -BasePath $script:TestBasePath
    
    $result | Should Not BeNullOrEmpty
    $result.PSObject.TypeNames[0] | Should Be 'PathTraversalValidationResult'
    $result.ValidationResults | Should Not BeNullOrEmpty
    $result.TotalPaths | Should BeGreaterThan 0
}

```

This ensures the function contract is maintained across changes.

## Best Practices Demonstrated

1. **Complete Test Coverage**: Every public parameter and code path is tested
2. **Security-First Approach**: Attack scenarios are explicitly tested
3. **Performance Considerations**: Load and memory usage are validated
4. **Real-World Scenarios**: Pipeline usage and batch processing are covered
5. **Clean Setup/Teardown**: Tests are completely isolated and repeatable
6. **Meaningful Assertions**: Tests validate both positive and negative cases
7. **Error Scenario Coverage**: Invalid inputs and edge cases are handled

## Running the Tests

### With Pester 3.4 (Default on Windows)

These tests are designed for Pester 3.4, which comes pre-installed on Windows systems:

```powershell
# Check your Pester version
Get-Module -Name Pester -ListAvailable

# Run all tests (Pester 3.4 syntax)
Invoke-Pester -Script ".\Test-PathTraversal.Tests.ps1" -Verbose

# Run with detailed output
Invoke-Pester -Script ".\Test-PathTraversal.Tests.ps1" -OutputFormat NUnitXml -OutputFile "TestResults.xml"

```

When executed, you'll see Pester work through each test context systematically, validating everything from basic parameter handling to complex security scenarios:

![](https://storage.ghost.io/c/cb/cc/cbcc23a2-e533-4fde-820e-0d1cbb8edc67/content/images/2025/07/image.png)

The output shows each test context being executed (Parameter Validation, Path Traversal Detection, Security Validation, etc.) with individual test results. Notice how Pester provides clear feedback on which tests pass and gives you a comprehensive summary at the end showing total tests run, passed, failed, and execution time.

### Upgrading to Pester 5.x

If you want to upgrade to the latest Pester version for enhanced features:

```powershell
# Remove the built-in version and install latest
Remove-Module -Name Pester -Force -ErrorAction SilentlyContinue
Install-Module -Name Pester -Force -SkipPublisherCheck

# Verify new version
Get-Module -Name Pester -ListAvailable

# Run tests with Pester 5.x (requires syntax updates)
Invoke-Pester -Path ".\Test-PathTraversal.Tests.ps1" -Output Detailed

```

**Note:** Upgrading to Pester 5.x will require updating the test syntax. The core testing logic remains the same, but assertions change from `Should Be` to `Should -Be`, and configuration becomes more structured.

## Conclusion

This example demonstrates how Pester can be used to create comprehensive test suites for security-critical PowerShell functions. The key takeaways are:

- **Structure tests logically** using `Describe` and `Context` blocks
- **Test security scenarios explicitly** with known attack patterns
- **Validate performance characteristics** for functions that process large datasets
- **Test PowerShell-specific features** like pipeline support
- **Ensure complete isolation** between test runs
- **Mock external dependencies** to focus on core functionality

By following these patterns, you can build confidence in your PowerShell security modules and catch potential vulnerabilities before they reach production.

The comprehensive nature of these tests—covering 50+ test cases across 9 different contexts—demonstrates the level of rigor needed when testing security-focused code. This investment in testing pays dividends in reliability, maintainability, and security assurance.

Remember: when it comes to security functions, it's better to over-test than under-test. The extra effort spent creating comprehensive test suites like this one will save countless hours debugging production issues and security incidents.

## Related reading

- [Seeding an Okta Test Tenant When You Only Get Ten Users](https://www.techbyjeff.net/seeding-an-okta-test-tenant-when-you-only-get-ten-users/)
- [Beyond Basic Secrets: Building a Universal PowerShell Secret Management Module](https://www.techbyjeff.net/beyond-basic-secrets-building-a-universal-powershell-secret-management-module/)
- [From Plain Text Passwords to PowerShell SecretStore: Securing Your AD Test Environment](https://www.techbyjeff.net/from-plain-text-passwords-to-powershell-secretstore-securing-your-ad-test-environment/)