Functions/New-Password.Tests.ps1

describe "BitTitan.Runbooks.Common/New-Password" -Tag "module", "unit" {

    # Define test constants
    $passwordTestRepetitions = 5

    # Import the function to test
    . "$($PSScriptRoot)\New-Password.ps1"

    # Create hash table for params
    $newPasswordParams = @{
        NumberOfCharacters      = 8
        MinLowercaseCharacters  = 1
        MinUppercaseCharacters  = 1
        MinDigits               = 1
        MinSpecialCharacters    = 1
    }

    # Declare the groups of characters
    $lowercaseCharacters = 97 .. 122 | ForEach-Object -Process {
        "$([Char]$_)"
    }
    $upperCaseCharacters = 65 .. 90 | ForEach-Object -Process {
        "$([Char]$_)"
    }
    $digits = 48 .. 57 | ForEach-Object -Process {
        "$([Char]$_)"
    }
    $defaultSpecialCharacters = ((33..33) + (35 .. 43) + (45..47) + (58 .. 64) + (91 .. 96)) | ForEach-Object -Process {
        "$([Char]$_)"
    }

    it "returns an empty string when the sum of the minimum numbers is greater than the total number of characters" {
        # Update the params
        $newPasswordParams.MinLowercaseCharacters = 3
        $newPasswordParams.MinUppercaseCharacters = 3
        $newPasswordParams.MinDigits = 3
        $newPasswordParams.MinSpecialCharacters = 3

        # Call the task
        $password = New-Password @newPasswordParams -ErrorAction SilentlyContinue

        # Verify the password
        $password | Should Be ""

        # Reset the params
        $newPasswordParams.MinLowercaseCharacters = 1
        $newPasswordParams.MinUppercaseCharacters = 1
        $newPasswordParams.MinDigits = 1
        $newPasswordParams.MinSpecialCharacters = 1
    }

    it "returns an empty string when no categories are indicated to be present in the password" {
        # Update the params
        $newPasswordParams.MinLowercaseCharacters = -1
        $newPasswordParams.MinUppercaseCharacters = -1
        $newPasswordParams.MinDigits = -1
        $newPasswordParams.MinSpecialCharacters = -1

        # Call the task
        $password = New-Password @newPasswordParams -ErrorAction SilentlyContinue

        # Verify the password
        $password | Should Be ""

        # Reset the params
        $newPasswordParams.MinLowercaseCharacters = 1
        $newPasswordParams.MinUppercaseCharacters = 1
        $newPasswordParams.MinDigits = 1
        $newPasswordParams.MinSpecialCharacters = 1
    }

    it "generates a password with no lowercase characters when no lowercase characters should be present" {
        # Update the params
        $newPasswordParams.MinLowercaseCharacters = -1

        # Run the test multiple times
        for ($i = 0; $i -lt $passwordTestRepetitions; ++$i) {
            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            $password.ToCharArray() | Where-Object { "$($_)" -cin $lowercaseCharacters } | Should Be $null
        }

        # Reset the params
        $newPasswordParams.MinLowercaseCharacters = 1
    }

    it "generates a password with the minimum number of lowercase characters when a minimum number of lowercase characters is specified" {
        # Try different values of the minimum number
        for ($i = 0; $i -lt 5; ++$i) {
            # Set the parameter
            $newPasswordParams.MinLowercaseCharacters = $i

            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $lowercaseCharacters }).length `
                | Should BeGreaterThan ($i - 1)
        }

        # Reset the params
        $newPasswordParams.MinLowercaseCharacters = 1
    }

    it "generates a password with no uppercase characters when no uppercase characters should be present" {
        # Update the params
        $newPasswordParams.MinUppercaseCharacters = -1

        # Run the test multiple times
        for ($i = 0; $i -lt $passwordTestRepetitions; ++$i) {
            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            $password.ToCharArray() | Where-Object { "$($_)" -cin $upperCaseCharacters } | Should Be $null
        }

        # Reset the params
        $newPasswordParams.MinUppercaseCharacters = 1
    }

    it "generates a password with the minimum number of uppercase characters when a minimum number of uppercase characters is specified" {
        # Try different values of the minimum number
        for ($i = 0; $i -lt 5; ++$i) {
            # Set the parameter
            $newPasswordParams.MinUppercaseCharacters = $i

            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $uppercaseCharacters }).length `
                | Should BeGreaterThan ($i - 1)
        }

        # Reset the params
        $newPasswordParams.MinUppercaseCharacters = 1
    }

    it "generates a password with no digits when no digits should be present" {
        # Update the params
        $newPasswordParams.MinDigits = -1

        # Run the test multiple times
        for ($i = 0; $i -lt $passwordTestRepetitions; ++$i) {
            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            $password.ToCharArray() | Where-Object { "$($_)" -cin $digits } | Should Be $null
        }

        # Reset the params
        $newPasswordParams.MinDigits = 1
    }

    it "generates a password with the minimum number of digits when a minimum number of digits is specified" {
        # Try different values of the minimum number
        for ($i = 0; $i -lt 5; ++$i) {
            # Set the parameter
            $newPasswordParams.MinDigits = $i

            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $digits }).length `
                | Should BeGreaterThan ($i - 1)
        }

        # Reset the params
        $newPasswordParams.MinDigits = 1
    }

    it "generates a password with no special characters when no special characters should be present" {
        # Update the params
        $newPasswordParams.MinSpecialCharacters = -1

        # Run the test multiple times
        for ($i = 0; $i -lt $passwordTestRepetitions; ++$i) {
            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            $password.ToCharArray() | Where-Object { "$($_)" -cin $defaultSpecialCharacters } | Should Be $null
        }

        # Reset the params
        $newPasswordParams.MinSpecialCharacters = 1
    }

    it "generates a password with the minimum number of special characters when a minimum number of special characters is specified" {
        # Try different values of the minimum number
        for ($i = 0; $i -lt 5; ++$i) {
            # Set the parameter
            $newPasswordParams.MinSpecialCharacters = $i

            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $defaultSpecialCharacters }).length `
                | Should BeGreaterThan ($i - 1)
        }

        # Reset the params
        $newPasswordParams.MinSpecialCharacters = 1
    }

    it "generates a password using the custom special character instead when a custom special character is specified" {
        # Declare the custom special character to use, which is not in the set of default special characters
        $customSpecialCharacters = @("~")

        # Try different values of the minimum number
        for ($i = 0; $i -lt 5; ++$i) {
            # Set the parameter
            $newPasswordParams.MinSpecialCharacters = $i

            # Call the task
            $password = New-Password @newPasswordParams -SpecialCharacters $customSpecialCharacters

            # Verify the password
            # The custom character should be present at least $i times
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $customSpecialCharacters }).length `
                | Should BeGreaterThan ($i - 1)

            # The default special characters should not be present
            $password.ToCharArray() | Where-Object { "$($_)" -cin $defaultSpecialCharacters } | Should Be $null
        }

        # Reset the params
        $newPasswordParams.MinSpecialCharacters = 1
    }

    it "generates a password with the minimum number of characters in each category when given a minimum number for each category" {
        # Set up the possible values for the minimum number in each category
        $possibleValues = @(-1, 0, 1, 2, 3)
        $possibleValuesPositive = @(0, 1, 2, 3)
        $newPasswordParams.NumberOfCharacters = 10

        # Run the test multiple times
        for ($i = 0; $i -lt $passwordTestRepetitions; ++$i) {
            # Generate random numbers for the first three
            $minSum = 0
            $newPasswordParams.MinLowercaseCharacters = Get-Random $possibleValues -Count 1
            $minSum += $newPasswordParams.MinLowercaseCharacters
            $newPasswordParams.MinUppercaseCharacters = Get-Random $possibleValues -Count 1
            $minSum += $newPasswordParams.MinUppercaseCharacters
            $newPasswordParams.MinDigits = Get-Random $possibleValues -Count 1
            $minSum += $newPasswordParams.MinDigits

            # All of the first three categories are disabled
            if ($minSum -eq -3) {
                $newPasswordParams.MinSpecialCharacters = Get-Random $possibleValuesPositive -Count 1
            }

            # The last category can be disabled
            else {
                if (10 - $minSum -lt 3) {
                    $maxPossibleValue = 10 - $minSum
                }
                else {
                    $maxPossibleValue = 3
                }
                $newPasswordParams.MinSpecialCharacters = Get-Random -Minimum -1 -Maximum $maxPossibleValue
            }

            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $lowercaseCharacters }).length `
                | Should BeGreaterThan ($newPasswordParams.MinLowercaseCharacters - 1)
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $upperCaseCharacters }).length `
                | Should BeGreaterThan ($newPasswordParams.MinUppercaseCharacters - 1)
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $digits }).length `
                | Should BeGreaterThan ($newPasswordParams.MinDigits - 1)
            ($password.ToCharArray() | Where-Object { "$($_)" -cin $defaultSpecialCharacters }).length `
                | Should BeGreaterThan ($newPasswordParams.MinSpecialCharacters - 1)
        }

        # Reset the params
        $newPasswordParams.MinLowercaseCharacters = 1
        $newPasswordParams.MinUppercaseCharacters = 1
        $newPasswordParams.MinDigits = 1
        $newPasswordParams.MinSpecialCharacters = 1
    }
}