Search code examples
azurepowershellpester

The mock in my pester test keeps calling Connect-AzAccount


I dont understand why I cant test my Read-VaultSecret.

in my file appsettings.psm1, i have a this function

function Read-VaultSecret($vault, $secretId)
{
    try {
        return Read-SecureString((Get-AzKeyVaultSecret -VaultName $vault -Name $secretId).SecretValue)
    } catch {
        Write-Error "Error reading secret $secretId from vault $vault - do you have read access in $vault policies?"
        return
    }
}

function Read-SecureString {
    param (
        [Parameter(Mandatory = $true)]
        [System.Security.SecureString]$SecureString
    )

    try {
        # Convert SecureString to plain text , linux safe
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
        return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
    }
    catch {
        Write-Error "An error occurred while converting the SecureString: $_"
        throw  # Re-throw the exception if you want it to propagate further
    }
    finally {
        if ($BSTR) {
            # Ensure the allocated memory is freed even if there's an error
            [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
        }
    }
}

in my pester test appsettings.Tests.ps1 I have this test

    BeforeAll {
    # Import the module to be tested
    Import-Module "$PSScriptRoot/../appsettings.psm1" -Force

    # Mock Get-AzKeyVaultSecret to simulate secret retrieval
    function Get-AzKeyVaultSecret {
        param (
            [string]$VaultName,
            [string]$Name
        )

        # Return an object with a SecretValue that contains the SecureString
        return [PSCustomObject]@{
            SecretValue = ConvertTo-SecureString "SuperSecretValue" -AsPlainText -Force
        }
    }

}

Describe "Read-VaultSecret Function Tests" {

    It "should return the secret as a plain text string when valid vault and secretId are provided" {
        $vault = "TestVault"
        $secretId = "TestSecret"
        

        $result = Read-VaultSecret -vault $vault -secretId $secretId
        $result | Should -Be "SuperSecretValue"
    }
}

when i run the actual test I get these two error messages

enter image description here

Why is it complaining about "Run Connect-AzAccount to login." ? Shouldn't the function Get-AzKeyVaultSecret I have in my pester test bypass the normal azure command-lets called in Read-VaultSecret ?

Ultimately what I want to do is mock up a couple azure command-lets so i can run my tests without actually having to connect to azure infrastructure.

is that possible ?


Solution

  • So there 2 issues in your Pester test, one is described in this answer, essentially you will need to either use global: scope modifier for your function or have a helper psm1 that has this function and you import it before running the tests (using global: for this answer, both should work fine).

    Second issue is referencing Read-SecureString which doesn't seem to exist (?) or there is no reference of it in your question.
    Updated question shows the reference to this function. Even though the code to decrypt the SecureString is correct, the preferred method, for simplicity, is to use NetworkCredential as shown in this answer.

    Also, this isn't an issue but placing the code in a BeforeAll or BeforeDiscovery isn't really mandatory (you can still have it if you want).

    In summary, appsettings.psm1 can be:

    function Read-VaultSecret($vault, $secretId) {
        try {
            # using `NetworkCredential` to get the plain text password is the proper method here
            [System.Net.NetworkCredential]::new('',
                (Get-AzKeyVaultSecret -VaultName $vault -Name $secretId).SecretValue).Password
        }
        catch {
            Write-Error "Error reading secret $secretId from vault $vault - do you have read access in $vault policies?"
        }
    }
    

    Then your test file can be:

    # Import the module to be tested
    Import-Module "$PSScriptRoot/../appsettings.psm1" -Force
    
    # Mock Get-AzKeyVaultSecret to simulate secret retrieval
    function global:Get-AzKeyVaultSecret {
        param (
            [string] $VaultName,
            [string] $Name
        )
    
        # Return an object with a SecretValue that contains the SecureString
        [PSCustomObject]@{
            SecretValue = ConvertTo-SecureString 'SuperSecretValue' -AsPlainText -Force
        }
    }
    
    Describe 'Read-VaultSecret Function Tests' {
        It 'should return the secret as a plain text string when valid vault and secretId are provided' {
            $vault = 'TestVault'
            $secretId = 'TestSecret'
    
            $result = Read-VaultSecret -vault $vault -secretId $secretId
            $result | Should -Be 'SuperSecretValue'
        }
    }
    

    And when the test is invoked it should work correctly:

    PS ..\pwsh> Invoke-Pester -Path .\testscript.tests.ps1
    
    Starting discovery in 1 files.
    Discovery found 1 tests in 220ms.
    Running tests.
    [+] D:\...\pwsh\testscript.tests.ps1 694ms (112ms|407ms)
    Tests completed in 716ms
    Tests Passed: 1, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0