Tests/Unit/MSFT_UserResource.Tests.ps1

# To run these tests, the currently logged on user must have rights to create a user
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
param ()

$errorActionPreference = 'Stop'
Set-StrictMode -Version 'Latest'

$script:testFolderPath = Split-Path -Path $PSScriptRoot -Parent
$script:testHelpersPath = Join-Path -Path $script:testFolderPath -ChildPath 'TestHelpers'
Import-Module -Name (Join-Path -Path $script:testHelpersPath -ChildPath 'CommonTestHelper.psm1')

$script:testEnvironment = Enter-DscResourceTestEnvironment `
    -DSCResourceModuleName 'PSDscResources' `
    -DSCResourceName 'MSFT_UserResource' `
    -TestType 'Unit'

try {

    Import-Module -Name (Join-Path -Path  $script:testHelpersPath `
                                   -ChildPath 'MSFT_UserResource.TestHelper.psm1')

    InModuleScope 'MSFT_UserResource' {
        # Used to skip the Nano server tests for the time being since they are not working on AppVeyor
        
        $script:skipMe = $true
    
        $existingUserName = 'TestUserName12345'
        $existingUserPassword = 'StrongOne7.'
        $existingDescription = 'Some Description'
        $existingSecurePassword = ConvertTo-SecureString $existingUserPassword -AsPlainText -Force
        $existingTestCredential = New-Object PSCredential ($existingUserName, $existingSecurePassword)
        
        New-User -Credential $existingTestCredential -Description $existingDescription
        
        $newUserName1 = 'NewTestUserName12345'
        $newUserPassword1 = 'NewStrongOne123.'
        $newFullName1 = 'Fullname1'
        $newUserDescription1 = 'New Description1'
        $newSecurePassword1 = ConvertTo-SecureString $newUserPassword1 -AsPlainText -Force
        $newCredential1 = New-Object PSCredential ($newUserName1, $newSecurePassword1)
        
        $newUserName2 = 'newUser1234'
        $newPassword2 = 'ThisIsAStrongPassword543!'
        $newFullName2 = 'Fullname2'
        $newUserDescription2 = 'New Description2'
        $newSecurePassword2 = ConvertTo-SecureString $newPassword2 -AsPlainText -Force
        $newCredential2 = New-Object PSCredential ($newUserName2, $newSecurePassword2)
        
        try {

            Describe 'UserResource/Get-TargetResource' {

                Context 'Tests on FullSKU' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $false }

                    It 'Should return the user as Present' {
                        $getTargetResourceResult = Get-TargetResource $existingUserName

                        $getTargetResourceResult['UserName']                | Should Be $existingUserName
                        $getTargetResourceResult['Ensure']                  | Should Be 'Present'
                        $getTargetResourceResult['Description']             | Should Be $existingDescription
                        $getTargetResourceResult['PasswordChangeRequired']  | Should Be $null
                    }

                    It 'Should return the user as Absent' {
                        $getTargetResourceResult = Get-TargetResource 'NotAUserName'

                        $getTargetResourceResult['UserName']                | Should Be 'NotAUserName'
                        $getTargetResourceResult['Ensure']                  | Should Be 'Absent'
                    }
                }

                Context 'Tests on Nano Server' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $true }

                    It 'Should return the user as Present on Nano Server' -Skip:$script:skipMe {
                        $getTargetResourceResult = Get-TargetResource $existingUserName

                        $getTargetResourceResult['UserName']                | Should Be $existingUserName
                        $getTargetResourceResult['Ensure']                  | Should Be 'Present'
                        $getTargetResourceResult['Description']             | Should Be $existingDescription
                        $getTargetResourceResult['PasswordChangeRequired']  | Should Be $null
                    }

                    It 'Should return the user as Absent' -Skip:$script:skipMe {
                        $getTargetResourceResult = Get-TargetResource 'NotAUserName'

                        $getTargetResourceResult['UserName']                | Should Be 'NotAUserName'
                        $getTargetResourceResult['Ensure']                  | Should Be 'Absent'
                    }
                }
            }

            Describe 'UserResource/Set-TargetResource' {
                Context 'Tests on FullSKU' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $false }
                    
                    try
                    {
                        New-User -Credential $newCredential1 -Description $newUserDescription1
                    
                        It 'Should remove the user' {
                            Test-User -UserName $newUserName1 | Should Be $true
                            Set-TargetResource -UserName $newUserName1 -Ensure 'Absent'
                            Test-User -UserName $newUserName1 | Should Be $false
                        }
                    
                        It 'Should add the new user' {
                            Set-TargetResource -UserName $newUserName2 -Password $newCredential2 -Ensure 'Present'
                            Test-User -UserName $newUserName2 | Should Be $true
                        }
                        
                        It 'Should update the user' {
                            $disabled = $false
                            $passwordNeverExpires = $true
                            $passwordChangeRequired = $false
                            $passwordChangeNotAllowed = $true
                            
                            Set-TargetResource -UserName $newUserName2 `
                                               -Password $newCredential2 `
                                               -Ensure 'Present' `
                                               -FullName $newFullName1 `
                                               -Description $newUserDescription1 `
                                               -Disabled $disabled `
                                               -PasswordNeverExpires $passwordNeverExpires `
                                               -PasswordChangeRequired $passwordChangeRequired `
                                               -PasswordChangeNotAllowed $passwordChangeNotAllowed
                        
                            Test-User -UserName $newUserName2 | Should Be $true
                            $testTargetResourceResult1 = 
                                    Test-TargetResource -UserName $newUserName2 `
                                                        -Password $newCredential2 `
                                                        -Ensure 'Present' `
                                                        -FullName $newFullName1 `
                                                        -Description $newUserDescription1 `
                                                        -Disabled $disabled `
                                                        -PasswordNeverExpires $passwordNeverExpires `
                                                        -PasswordChangeNotAllowed $passwordChangeNotAllowed
                            $testTargetResourceResult1 | Should Be $true
                        }
                        It 'Should update the user again with different values' {
                            $disabled = $false
                            $passwordNeverExpires = $false
                            $passwordChangeRequired = $true
                            $passwordChangeNotAllowed = $false
                            
                            Set-TargetResource -UserName $newUserName2 `
                                               -Password $newCredential1 `
                                               -Ensure 'Present' `
                                               -FullName $newFullName2 `
                                               -Description $newUserDescription2 `
                                               -Disabled $disabled `
                                               -PasswordNeverExpires $passwordNeverExpires `
                                               -PasswordChangeRequired $passwordChangeRequired `
                                               -PasswordChangeNotAllowed $passwordChangeNotAllowed
                        
                            Test-User -UserName $newUserName2 | Should Be $true
                            $testTargetResourceResult2 = 
                                    Test-TargetResource -UserName $newUserName2 `
                                                        -Password $newCredential1 `
                                                        -Ensure 'Present' `
                                                        -FullName $newFullName2 `
                                                        -Description $newUserDescription2 `
                                                        -Disabled $disabled `
                                                        -PasswordNeverExpires $passwordNeverExpires `
                                                        -PasswordChangeNotAllowed $passwordChangeNotAllowed
                            $testTargetResourceResult2 | Should Be $true
                        }
                    }
                    finally
                    {
                        Remove-User -UserName $newUserName1
                        Remove-User -UserName $newUserName2
                    }
                }

                Context 'Tests on Nano Server' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $true }
                    Mock -CommandName Test-CredentialsValidOnNanoServer { return $true }
                    
                    try
                    {
                        New-User -Credential $newCredential1 -Description $newUserDescription1
                    
                        It 'Should remove the user' -Skip:$script:skipMe {
                            Test-User -UserName $newUserName1 | Should Be $true
                            Set-TargetResource -UserName $newUserName1 -Ensure 'Absent'
                            Test-User -UserName $newUserName1 | Should Be $false
                        }
                    
                        It 'Should add the new user' -Skip:$script:skipMe {
                            Set-TargetResource -UserName $newUserName2 -Password $newCredential2 -Ensure 'Present'
                            Test-User -UserName $newUserName2 | Should Be $true
                        }
                        
                        It 'Should update the user' -Skip:$script:skipMe {
                            $disabled = $false
                            $passwordNeverExpires = $true
                            $passwordChangeRequired = $false
                            $passwordChangeNotAllowed = $true
                            
                            Set-TargetResource -UserName $newUserName2 `
                                               -Password $newCredential2 `
                                               -Ensure 'Present' `
                                               -FullName $newFullName1 `
                                               -Description $newUserDescription1 `
                                               -Disabled $disabled `
                                               -PasswordNeverExpires $passwordNeverExpires `
                                               -PasswordChangeRequired $passwordChangeRequired `
                                               -PasswordChangeNotAllowed $passwordChangeNotAllowed
                        
                            Test-User -UserName $newUserName2 | Should Be $true
                            $testTargetResourceResult1 = 
                                    Test-TargetResource -UserName $newUserName2 `
                                                        -Password $newCredential2 `
                                                        -Ensure 'Present' `
                                                        -FullName $newFullName1 `
                                                        -Description $newUserDescription1 `
                                                        -Disabled $disabled `
                                                        -PasswordNeverExpires $passwordNeverExpires `
                                                        -PasswordChangeNotAllowed $passwordChangeNotAllowed
                            $testTargetResourceResult1 | Should Be $true
                        }
                        It 'Should update the user again with different values' -Skip:$script:skipMe {
                            $disabled = $false
                            $passwordNeverExpires = $false
                            $passwordChangeRequired = $true
                            $passwordChangeNotAllowed = $false
                            
                            Set-TargetResource -UserName $newUserName2 `
                                               -Password $newCredential1 `
                                               -Ensure 'Present' `
                                               -FullName $newFullName2 `
                                               -Description $newUserDescription2 `
                                               -Disabled $disabled `
                                               -PasswordNeverExpires $passwordNeverExpires `
                                               -PasswordChangeRequired $passwordChangeRequired `
                                               -PasswordChangeNotAllowed $passwordChangeNotAllowed
                        
                            Test-User -UserName $newUserName2 | Should Be $true
                            $testTargetResourceResult2 = 
                                    Test-TargetResource -UserName $newUserName2 `
                                                        -Password $newCredential1 `
                                                        -Ensure 'Present' `
                                                        -FullName $newFullName2 `
                                                        -Description $newUserDescription2 `
                                                        -Disabled $disabled `
                                                        -PasswordNeverExpires $passwordNeverExpires `
                                                        -PasswordChangeNotAllowed $passwordChangeNotAllowed
                            $testTargetResourceResult2 | Should Be $true
                        }
                    }
                    finally
                    {
                        Remove-User -UserName $newUserName1
                        Remove-User -UserName $newUserName2
                    }
                }
            }

            Describe 'UserResource/Test-TargetResource' {
                Context 'Tests on FullSKU' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $false }
                    $absentUserName = 'AbsentUserUserName123456789'
                    
                    It 'Should return true when user Present and correct values' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Description $existingDescription `
                                                                        -Password $existingTestCredential `
                                                                        -Disabled $false `
                                                                        -PasswordNeverExpires $false `
                                                                        -PasswordChangeNotAllowed $false
                        $testTargetResourceResult | Should Be $true
                    }
                    
                    It 'Should return true when user Absent and Ensure = Absent' {
                        $testTargetResourceResult = Test-TargetResource -UserName $absentUserName `
                                                                        -Ensure 'Absent'
                        $testTargetResourceResult | Should Be $true
                    }

                    It 'Should return false when user Absent and Ensure = Present' {
                        $testTargetResourceResult = Test-TargetResource -UserName $absentUserName `
                                                                        -Ensure 'Present'
                        $testTargetResourceResult | Should Be $false
                    }
                    
                    It 'Should return false when user Present and Ensure = Absent' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Ensure 'Absent'
                        $testTargetResourceResult | Should Be $false
                    }
                    
                    It 'Should return false when Password is wrong' {
                        $badPassword = 'WrongPassword'
                        $secureBadPassword = ConvertTo-SecureString $badPassword -AsPlainText -Force
                        $badTestCredential = New-Object PSCredential ($existingUserName, $secureBadPassword)
                        
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Password $badTestCredential
                        $testTargetResourceResult | Should Be $false
                    }
                    
                    It 'Should return false when user Present and wrong Description' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Description 'Wrong description'
                        $testTargetResourceResult | Should Be $false
                    }

                    It 'Should return false when FullName is incorrect' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -FullName 'Wrong FullName'
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when Disabled is incorrect' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Disabled $true
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordNeverExpires is incorrect' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -PasswordNeverExpires $true
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordChangeNotAllowed is incorrect' {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -PasswordChangeNotAllowed $true
                        $testTargetResourceResult | Should Be $false 
                    }
                }
                
                Context 'Tests on Nano Server' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $true }
                    
                    $absentUserName = 'AbsentUserUserName123456789'
                    
                    It 'Should return true when user Present and correct values' -Skip:$script:skipMe {
                        Mock -CommandName Test-CredentialsValidOnNanoServer { return $true }
                        
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Description $existingDescription `
                                                                        -Password $existingTestCredential `
                                                                        -Disabled $false `
                                                                        -PasswordNeverExpires $false `
                                                                        -PasswordChangeNotAllowed $false
                        $testTargetResourceResult | Should Be $true
                    }
                    
                    It 'Should return true when user Absent and Ensure = Absent' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $absentUserName `
                                                                        -Ensure 'Absent'
                        $testTargetResourceResult | Should Be $true
                    }

                    It 'Should return false when user Absent and Ensure = Present' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $absentUserName `
                                                                        -Ensure 'Present'
                        $testTargetResourceResult | Should Be $false
                    }
                    
                    It 'Should return false when user Present and Ensure = Absent' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Ensure 'Absent'
                        $testTargetResourceResult | Should Be $false
                    }
                    
                    It 'Should return false when Password is wrong' -Skip:$script:skipMe {
                        Mock -CommandName Test-CredentialsValidOnNanoServer { return $false }
                        
                        $badPassword = 'WrongPassword'
                        $secureBadPassword = ConvertTo-SecureString $badPassword -AsPlainText -Force
                        $badTestCredential = New-Object PSCredential ($existingUserName, $secureBadPassword)
                        
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Password $badTestCredential
                        $testTargetResourceResult | Should Be $false
                    }
                    
                    It 'Should return false when user Present and wrong Description' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Description 'Wrong description'
                        $testTargetResourceResult | Should Be $false
                    }

                    It 'Should return false when FullName is incorrect' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -FullName 'Wrong FullName'
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when Disabled is incorrect' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Disabled $true
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordNeverExpires is incorrect' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -PasswordNeverExpires $true
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordChangeNotAllowed is incorrect' -Skip:$script:skipMe {
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -PasswordChangeNotAllowed $true
                        $testTargetResourceResult | Should Be $false 
                    }
                }
            }
            
            Describe 'UserResource/Assert-UserNameValid' {
                It 'Should not throw when username contains all valid chars' {
                    { Assert-UserNameValid -UserName 'abc123456!f_t-l098s' } | Should Not Throw
                }
                
                It 'Should throw InvalidArgumentError when username contains only whitespace and dots' {
                    $invalidName = ' . .. . '
                    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument
                    $errorId = 'UserNameHasOnlyWhiteSpacesAndDots'
                    $errorMessage = "The name $invalidName cannot be used."
                    $exception = New-Object System.ArgumentException $errorMessage;
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
                    { Assert-UserNameValid -UserName $invalidName } | Should Throw $errorRecord
                }
                
                It 'Should throw InvalidArgumentError when username contains an invalid char' {
                    $invalidName = 'user|name'
                    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument
                    $errorId = 'UserNameHasInvalidCharachter'
                    $errorMessage = "The name $invalidName cannot be used."
                    $exception = New-Object System.ArgumentException $errorMessage;
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
                    { Assert-UserNameValid -UserName $invalidName } | Should Throw $errorRecord
                }
            }
        }
        finally
        {
            Remove-User -UserName $existingUserName
        }
    }
}
finally
{
    Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment
}