Passwords.Tests.ps1

####################################################################################################
# Declarations
####################################################################################################

# Set InformationPreference
$InformationPreference = "Continue"

####################################################################################################
# Functions
####################################################################################################

# Add modules folder to path if not already added
if (!$env:PSModulePath.EndsWith(";$(Get-Location)\Modules")) {
    $env:PSModulePath += ";$(Get-Location)\Modules"
}

# Import functions from BitTitan.Runbooks.Modules
Import-Module BitTitan.Runbooks.Common -Force

####################################################################################################
# The tests
####################################################################################################

describe "Modules/BitTitan.Runbooks.Common/Passwords/New-Password" -Tags "module", "common", "unit" {

    # 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 .. 47) + (58 .. 64) + (91 .. 96)) | ForEach-Object -Process {
        "$([Char]$_)"
    }

    context "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

        it "returns an empty string" {
            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            $password | Should Be ""
        }

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

    context "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

        it "returns an empty string" {
            # Call the task
            $password = New-Password @newPasswordParams

            # Verify the password
            $password | Should Be ""
        }

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

    context "when it is indicated that no lowercase characters should be present" {
        # Update the params
        $newPasswordParams.MinLowercaseCharacters = -1

        it "generates a password with no lowercase characters" {
            # Run 1000 times to be sure
            for ($i = 0; $i -lt 1000; ++$i) {
                # Call the task
                $password = New-Password @newPasswordParams

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

        # Reset the params
        $newPasswordParams.MinLowercaseCharacters = 1
    }

    context "when a minimum number of lowercase characters is specified" {

        it "generates a password with the minimum number of lowercase characters" {
            # 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
    }

    context "when it is indicated that no uppercase characters should be present" {
        # Update the params
        $newPasswordParams.MinUppercaseCharacters = -1

        it "generates a password with no uppercase characters" {
            # Run 1000 times to be sure
            for ($i = 0; $i -lt 1000; ++$i) {
                # Call the task
                $password = New-Password @newPasswordParams

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

        # Reset the params
        $newPasswordParams.MinUppercaseCharacters = 1
    }

    context "when a minimum number of uppercase characters is specified" {

        it "generates a password with the minimum number of uppercase characters" {
            # 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
    }

    context "when it is indicated that no digits should be present" {
        # Update the params
        $newPasswordParams.MinDigits = -1

        it "generates a password with no digits" {
            # Run 1000 times to be sure
            for ($i = 0; $i -lt 1000; ++$i) {
                # Call the task
                $password = New-Password @newPasswordParams

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

        # Reset the params
        $newPasswordParams.MinDigits = 1
    }

    context "when a minimum number of digits is specified" {

        it "generates a password with the minimum number of digits" {
            # 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
    }

    context "when it is indicated that no special characters should be present" {
        # Update the params
        $newPasswordParams.MinSpecialCharacters = -1

        it "generates a password with no special characters" {
            # Run 1000 times to be sure
            for ($i = 0; $i -lt 1000; ++$i) {
                # Call the task
                $password = New-Password @newPasswordParams

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

        # Reset the params
        $newPasswordParams.MinSpecialCharacters = 1
    }

    context "when a minimum number of special characters is specified" {

        it "generates a password with the minimum number of special characters" {
            # 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
    }

    context "when a custom set of special characters is specified" {
        $customSpecialCharacters = @("~", "?")

        it "generates a password with the minimum number of custom special characters" {
            # 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
                ($password.ToCharArray() | Where-Object { "$($_)" -cin $customSpecialCharacters }).length `
                    | Should BeGreaterThan ($i - 1)
            }
        }

        # Reset the params
        $newPasswordParams.MinSpecialCharacters = 1
    }

    context "when given a minimum number for each category" {

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

            # Run 1000 times to be sure
            for ($i = 0; $i -lt 1000; ++$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 $possibleValuesNoNegative -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
    }
}