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')

    # get rid of this if-check once the fix for this is released
    if (Test-IsNanoServer)
    {
        Import-Module -Name 'Microsoft.Powershell.LocalAccounts'
    }

    # Commented out until the fix is released
    #Import-Module -Name 'Microsoft.Powershell.LocalAccounts'

    InModuleScope 'MSFT_UserResource' {

        $script:disposableObjects = @()

        try
        {
            $existingUserName = 'TestUserName12345'
            $existingUserPassword = 'StrongOne7.'
            $existingUserSecurePassword = ConvertTo-SecureString -String $existingUserPassword -AsPlainText -Force
            $existingUserTestCredential = New-Object PSCredential ($existingUserName, $existingUserSecurePassword)

            # Mock user object and properties used for most tests
            $existingUserValues = @{
                Name = $existingUserName
                Password = $existingUserTestCredential
                Description = 'Some Description'
                DisplayName = 'Test Existing User'
                FullName = 'Test Existing User'
                Enabled = $true
                PasswordNeverExpires = $false
                PasswordExpires = '01/03/2018 17:04:20'
                UserCannotChangePassword = $false
                UserMayChangePassword = $true
                PasswordChangeRequired = $false
            }

            $newUserName = 'NewTestUserName12345'
            $newUserPassword = 'NewStrongOne123.'
            $newUserSecurePassword = ConvertTo-SecureString -String $newUserPassword -AsPlainText -Force
            $newUserCredential = New-Object PSCredential ($newUserName, $newUserSecurePassword)

            # Mock user object and properties mainly used for checking updating values
            $newUserValues = @{
                Name = $newUserName
                Password = $newUserCredential
                Description = 'Some other Description'
                DisplayName = 'Test New User1'
                FullName = 'Test New User1'
                Enabled = $true
                PasswordNeverExpires = $true
                PasswordExpires = $null
                UserCannotChangePassword = $true
                UserMayChangePassword = $false
                PasswordChangeRequired = $false
            }
        
        
            $modifiableUserName = 'newUser1234'
            $modifiableUserPassword = 'ThisIsAStrongPassword543!'
            $modifiableUserSecurePassword = ConvertTo-SecureString -String $modifiableUserPassword -AsPlainText -Force
            $modifiableUserCredential = New-Object PSCredential ($modifiableUserName, $modifiableUserSecurePassword)
            
            # Mock user object that gets modified by the Set-TargetResourceOnNanoServer tests
            $modifiableUserValues = @{
                Name = $modifiableUserName
                Password = $modifiableUserCredential
                Description = 'Another Description'
                DisplayName = 'Test New User2'
                FullName = 'Another Description'
                Enabled = $false
                PasswordNeverExpires = $true
                PasswordExpires = $null
                UserCannotChangePassword = $true
                UserMayChangePassword = $false
                PasswordChangeRequired = $true
            }

            if (Test-IsNanoServer)
            {
                $script:UserObject = New-Object -TypeName 'System.Management.Automation.SecurityAccountsManager.LocalUser' `
                                                -ArgumentList @( $existingUserName )
                $script:disposableObjects += $script:UserObject

                # Properties that are only on Nano Server user object
                $script:UserObject.FullName = $existingUserValues.FullName
                $script:UserObject.UserMayChangePassword = $existingUserValues.UserMayChangePassword
                $script:UserObject.PasswordExpires = $existingUserValues.PasswordExpires
            }
            else
            {
                $script:testPrincipalContext = New-Object -TypeName 'System.DirectoryServices.AccountManagement.PrincipalContext' `
                                                          -ArgumentList @( [System.DirectoryServices.AccountManagement.ContextType]::Machine )
                $script:disposableObjects += $script:testPrincipalContext
                $script:UserObject = New-Object -TypeName 'System.DirectoryServices.AccountManagement.UserPrincipal' `
                                                -ArgumentList @( $testPrincipalContext )
                $script:disposableObjects += $script:UserObject

                # Properties that are only on FullSku user object
                $script:UserObject.DisplayName = $existingUserValues.DisplayName
                $script:UserObject.UserCannotChangePassword = $existingUserValues.UserCannotChangePassword
                $script:UserObject.PasswordNeverExpires = $existingUserValues.PasswordNeverExpires
                
            }

            # Properties that are used on both Nano and Full Sku
            $script:UserObject.Name = $existingUserValues.Name
            $script:UserObject.Description = $existingUserValues.Description
            $script:UserObject.Enabled = $existingUserValues.Enabled

            Describe 'UserResource/Get-TargetResource' {

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

                    It 'Should return the user as Present' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $existingUserValues }

                        $getTargetResourceResult = Get-TargetResource -UserName $existingUserValues.Name

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

                        $getTargetResourceResult['UserName']                 | Should Be $existingUserValues.Name
                        $getTargetResourceResult['Ensure']                   | Should Be 'Present'
                        $getTargetResourceResult['Description']              | Should Be $existingUserValues.Description
                        $getTargetResourceResult['FullName']                 | Should Be $existingUserValues.DisplayName
                        $getTargetResourceResult['PasswordChangeRequired']   | Should Be $null
                        $getTargetResourceResult['PasswordChangeNotAllowed'] | Should Be $existingUserValues.UserCannotChangePassword
                        $getTargetResourceResult['PasswordNeverExpires']     | Should Be $existingUserValues.PasswordNeverExpires
                        $getTargetResourceResult['Disabled']                 | Should Be (-not $existingUserValues.Enabled)
                    }

                    It 'Should return the user as Absent' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $null }

                        $getTargetResourceResult = Get-TargetResource -UserName 'NotAUserName'

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

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

                    It 'Should throw an Invalid Operaion exception' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { Throw }
                        Mock -CommandName New-InvalidOperationException -MockWith {}

                        Get-TargetResource -UserName 'DuplicateUser'

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-InvalidOperationException -Exactly 1 -Scope It
                    }
                }
                
                Context 'Tests on Nano Server' {

                    Mock -CommandName Test-IsNanoServer -MockWith { return $true }

                    It 'Should return the user as Present on Nano Server' {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $existingUserValues }

                        $getTargetResourceResult = Get-TargetResource -UserName $existingUserValues.Name

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It

                        $getTargetResourceResult['UserName']                 | Should Be $existingUserValues.Name
                        $getTargetResourceResult['Ensure']                   | Should Be 'Present'
                        $getTargetResourceResult['Description']              | Should Be $existingUserValues.Description
                        $getTargetResourceResult['FullName']                 | Should Be $existingUserValues.FullName
                        $getTargetResourceResult['PasswordChangeRequired']   | Should Be $null
                        $getTargetResourceResult['PasswordChangeNotAllowed'] | Should Be (-not $existingUserValues.UserMayChangePassword)
                        $getTargetResourceResult['PasswordNeverExpires']     | Should Be (-not $existingUserValues.PasswordExpires)
                        $getTargetResourceResult['Disabled']                 | Should Be (-not $existingUserValues.Enabled)
                    }

                    It 'Should return a different user as Present on Nano Server' {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $newUserValues }

                        $getTargetResourceResult = Get-TargetResource -UserName $newUserValues.Name

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It

                        $getTargetResourceResult['UserName']                 | Should Be $newUserValues.Name
                        $getTargetResourceResult['Ensure']                   | Should Be 'Present'
                        $getTargetResourceResult['Description']              | Should Be $newUserValues.Description
                        $getTargetResourceResult['FullName']                 | Should Be $newUserValues.FullName
                        $getTargetResourceResult['PasswordChangeRequired']   | Should Be $null
                        $getTargetResourceResult['PasswordChangeNotAllowed'] | Should Be (-not $newUserValues.UserMayChangePassword)
                        $getTargetResourceResult['PasswordNeverExpires']     | Should Be (-not $newUserValues.PasswordExpires)
                        $getTargetResourceResult['Disabled']                 | Should Be (-not $newUserValues.Enabled)
                    }

                    It 'Should return the user as Absent' {
                        Mock -CommandName Find-UserByNameOnNanoServer `
                             -MockWith { Write-Error -Message 'Test error message' -ErrorId  'UserNotFound' }

                        $getTargetResourceResult = Get-TargetResource -UserName 'NotAUserName'

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It

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

                    It 'Should throw an Invalid Operation exception' {
                        $exception = New-Object -TypeName 'InvalidOperationException' `
                                                -ArgumentList @($null)
                        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
                                                  -ArgumentList @($exception, 'MachineStateIncorrect', 'InvalidOperation', $null)
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { Throw }
                        { Get-TargetResource -UserName 'DuplicateUser' } | Should Throw $errorRecord

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It

                    }
                }
            }

            Describe 'UserResource/Set-TargetResource' {
                Context 'Tests on FullSKU' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $false }
                    Mock -CommandName Add-UserOnFullSku -MockWith { return $script:UserObject }
                    Mock -CommandName Remove-UserOnFullSku -MockWith {}
                    Mock -CommandName Save-UserOnFullSku -MockWith {}
                    Mock -CommandName Set-UserPasswordOnFullSku -MockWith {}
                    Mock -CommandName Revoke-UserPassword -MockWith {}
                    Mock -CommandName Remove-DisposableObject -MockWith {}

                    It 'Should remove the user' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $script:UserObject }
                        
                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Absent'

                        Assert-MockCalled -CommandName Add-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-UserOnFullSku -Exactly 1 -Scope It
                    }
                
                    It 'Should add a new user with a password' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $null }
                        
                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Password $existingUserValues.Password `
                                           -Ensure 'Present'

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Add-UserOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Remove-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Save-UserOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

                        $script:UserObject.Name | Should Be $existingUserValues.Name
                        $script:UserObject.DisplayName | Should Be ''
                    }

                    It 'Should update the user' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $script:UserObject }

                        $disabled = $true
                        $passwordNeverExpires = $true
                        $passwordChangeRequired = $false
                        $passwordChangeNotAllowed = $true

                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Password $existingUserValues.Password `
                                           -Ensure 'Present' `
                                           -FullName $newUserValues.DisplayName `
                                           -Description $newUserValues.Description `
                                           -Disabled $disabled `
                                           -PasswordNeverExpires $passwordNeverExpires `
                                           -PasswordChangeRequired $passwordChangeRequired `
                                           -PasswordChangeNotAllowed $passwordChangeNotAllowed

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Add-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-UserPasswordOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Revoke-UserPassword -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Save-UserOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

                        $script:UserObject.Name | Should Be $existingUserValues.Name
                        $script:UserObject.DisplayName | Should Be $newUserValues.DisplayName
                        $script:UserObject.UserCannotChangePassword | Should Be $passwordChangeNotAllowed
                        $script:UserObject.PasswordNeverExpires | Should Be $passwordNeverExpires
                        $script:UserObject.Description | Should Be $newUserValues.Description
                        $script:UserObject.Enabled | Should Be (-not $disabled)
                    }

                    It 'Should update the user again with different values' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $script:UserObject }

                        $disabled = $false
                        $passwordNeverExpires = $false
                        $passwordChangeRequired = $true
                        $passwordChangeNotAllowed = $false
                        
                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Present' `
                                           -FullName $modifiableUserValues.DisplayName `
                                           -Description $modifiableUserValues.Description `
                                           -Disabled $disabled `
                                           -PasswordNeverExpires $passwordNeverExpires `
                                           -PasswordChangeRequired $passwordChangeRequired `
                                           -PasswordChangeNotAllowed $passwordChangeNotAllowed
                    
                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Add-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-UserPasswordOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Revoke-UserPassword -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Save-UserOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

                        $script:UserObject.Name | Should Be $existingUserValues.Name
                        $script:UserObject.DisplayName | Should Be $modifiableUserValues.DisplayName
                        $script:UserObject.UserCannotChangePassword | Should Be $passwordChangeNotAllowed
                        $script:UserObject.PasswordNeverExpires | Should Be $passwordNeverExpires
                        $script:UserObject.Description | Should Be $modifiableUserValues.Description
                        $script:UserObject.Enabled | Should Be (-not $disabled)
                    }

                    It 'Should not update the user if no new values are passed in' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $script:UserObject }

                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Present'
                    
                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Add-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-UserPasswordOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Revoke-UserPassword -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Save-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

                        $script:UserObject.Name | Should Be $existingUserValues.Name
                        $script:UserObject.DisplayName | Should Be $modifiableUserValues.DisplayName
                        $script:UserObject.Description | Should Be $modifiableUserValues.Description
                    }

                    It 'Should not update the user if existing values are passed in' {
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $script:UserObject }

                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Present' `
                                           -FullName $modifiableUserValues.DisplayName `
                                           -Description $modifiableUserValues.Description
                    
                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Add-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-UserPasswordOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Revoke-UserPassword -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Save-UserOnFullSku -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-DisposableObject -Exactly 1 -Scope It

                        $script:UserObject.Name | Should Be $existingUserValues.Name
                        $script:UserObject.DisplayName | Should Be $modifiableUserValues.DisplayName
                        $script:UserObject.Description | Should Be $modifiableUserValues.Description
                    }

                    It 'Should throw an Invalid Operation exception' {
                        $exception = New-Object -TypeName 'InvalidOperationException' `
                                                -ArgumentList @($null)
                        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
                                                  -ArgumentList @($exception, 'MachineStateIncorrect', 'InvalidOperation', $null)
                        Mock -CommandName Find-UserByNameOnFullSku -MockWith { Throw }

                        { Set-TargetResource -UserName 'DuplicateUser' } | Should Throw $errorRecord

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Save-UserOnFullSku -Exactly 0 -Scope It
                    }
                }

                Context 'Tests on Nano Server' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $true }
                    Mock -CommandName Test-CredentialsValidOnNanoServer -MockWith { return $true }
                    # Mock -CommandName New-LocalUser -MockWith {}
                    # Mock -CommandName Remove-LocalUser -MockWith {}
                    # Mock -CommandName Disable-LocalUser -MockWith {}
                    # Mock -CommandName Enable-LocalUser -MockWith {}
                
                    It 'Should add a new user' -Skip:$true {
                        Mock -CommandName Set-LocalUser -MockWith { $modifiableUserValues.FullName = [String]::Empty}

                        Mock -CommandName Find-UserByNameOnNanoServer `
                             -MockWith { Write-Error -Message 'Test error message' -ErrorId  'UserNotFound' }

                        Set-TargetResource -UserName $modifiableUserValues.Name `
                                           -Ensure 'Present'
                        
                        # Set-LocalUser only called to set FullName
                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 1 -Scope It

                        $modifiableUserValues.Name | Should Be $modifiableUserValues.Name
                        $modifiableUserValues.FullName | Should Be ''
                    }

                    It 'Should remove the user' -Skip:$true {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $script:UserObject }
                        Mock -CommandName Set-LocalUser -MockWith {}
                        
                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Absent'

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 0 -Scope It
                    }

                    It 'Should not remove the user if the user does not exist' -Skip:$true {
                        Mock -CommandName Find-UserByNameOnNanoServer `
                             -MockWith { Write-Error -Message 'Test error message' -ErrorId  'UserNotFound' }
                        Mock -CommandName Set-LocalUser -MockWith {}
                        
                        Set-TargetResource -UserName 'NotAUserName' `
                                           -Ensure 'Absent'

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 0 -Scope It
                    }

                    It 'Should update all values of the user' -Skip:$true {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $existingUserValues }
                        Mock -CommandName Set-LocalUser -MockWith {}

                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Password $newUserValues.Password `
                                           -Ensure 'Present' `
                                           -FullName $newUserValues.FullName `
                                           -Description $newUserValues.Description `
                                           -Disabled $existingUserValues.Enabled `
                                           -PasswordNeverExpires (-not $existingUserValues.PasswordNeverExpires) `
                                           -PasswordChangeRequired (-not $existingUserValues.PasswordChangeRequired) `
                                           -PasswordChangeNotAllowed $existingUserValues.UserMayChangePassword
                        
                        <# Set-LocalUser called to:
                             Set the Password
                             Change the FullName
                             Change the Description
                             Change PasswordNeverExpires
                             Set PasswordChangeRequired (which sets the password to expire 'Now')
                             Set UserMayChangePassword
                        #>

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 6 -Scope It
                        Assert-MockCalled -CommandName Disable-LocalUser -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Enable-LocalUser -Exactly 0 -Scope It

                    }
                                        
                    It 'Should update the user with different values' -Skip:$true {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $modifiableUserValues }
                        $modifiableUserValues.FullName = 'new full name'
                        Mock -CommandName Set-LocalUser -MockWith {}

                        Set-TargetResource -UserName $modifiableUserValues.Name `
                                           -Password $modifiableUserValues.Password `
                                           -Ensure 'Present' `
                                           -FullName $null `
                                           -Description $null `
                                           -Disabled $modifiableUserValues.Enabled `
                                           -PasswordNeverExpires (-not $modifiableUserValues.PasswordNeverExpires) `
                                           -PasswordChangeRequired (-not $modifiableUserValues.PasswordChangeRequired) `
                                           -PasswordChangeNotAllowed $modifiableUserValues.UserMayChangePassword

                        <# Set-LocalUser called to:
                             Set the Password (even though it's the same)
                             Change the FullName
                             Change the Description
                             Change PasswordNeverExpires
                             Set UserMayChangePassword
                             (Set-LocalUser will not be called to set PasswordChangeRequired if it is set to false, )
                        #>

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 5 -Scope It
                        Assert-MockCalled -CommandName Disable-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Enable-LocalUser -Exactly 1 -Scope It
                    }

                    It 'Should not update the user if no new values are passed in' -Skip:$true {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $existingUserValues }
                        Mock -CommandName Set-LocalUser -MockWith {}

                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Present'
                    
                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Disable-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Enable-LocalUser -Exactly 0 -Scope It
                    }

                    It 'Should not update the user if existing values are passed in' -Skip:$true {
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $existingUserValues }
                        Mock -CommandName Set-LocalUser -MockWith {}

                        Set-TargetResource -UserName $existingUserValues.Name `
                                           -Ensure 'Present' `
                                           -FullName $existingUserValues.FullName `
                                           -Description $existingUserValues.Description `
                                           -Disabled (-not $existingUserValues.Enabled) `
                                           -PasswordNeverExpires $existingUserValues.PasswordNeverExpires `
                                           -PasswordChangeRequired $existingUserValues.PasswordChangeRequired `
                                           -PasswordChangeNotAllowed (-not $existingUserValues.UserMayChangePassword)

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Disable-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Enable-LocalUser -Exactly 0 -Scope It
                    }

                    It 'Should throw an Invalid Operation exception' -Skip:$true {
                        $exception = New-Object -TypeName 'InvalidOperationException' `
                                                -ArgumentList @($null)
                        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
                                                  -ArgumentList @($exception, 'MachineStateIncorrect', 'InvalidOperation', $null)
                        Mock -CommandName Find-UserByNameOnNanoServer -MockWith { Throw }

                        { Set-TargetResource -UserName 'DuplicateUser' } | Should Throw $errorRecord

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName New-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Remove-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Set-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Disable-LocalUser -Exactly 0 -Scope It
                        Assert-MockCalled -CommandName Enable-LocalUser -Exactly 0 -Scope It
                    }

                }
            }

            Describe 'UserResource/Test-TargetResource' {
                Context 'Tests on FullSKU' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $false }
                    Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $existingUserValues } `
                                                               -ParameterFilter { $UserName -eq $existingUserName }
                    $absentUserName = 'AbsentUserUserName123456789'
                    Mock -CommandName Find-UserByNameOnFullSku -MockWith { return $null } `
                                                               -ParameterFilter { $UserName -eq $absentUserName }
                    It 'Should return true when user Present and correct values' {
                        Mock -CommandName Test-UserPasswordOnFullSku -MockWith { return $true }
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserValues.Name `
                                                                        -Description $existingUserValues.Description `
                                                                        -Password $existingUserValues.Password `
                                                                        -Disabled (-not $existingUserValues.Enabled) `
                                                                        -PasswordNeverExpires $existingUserValues.PasswordNeverExpires `
                                                                        -PasswordChangeNotAllowed $existingUserValues.UserCanNotChangePassword
                        $testTargetResourceResult | Should Be $true

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Test-UserPasswordOnFullSku -Exactly 1 -Scope It
                    }
                    
                    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' {
                        Mock -CommandName Test-UserPasswordOnFullSku -MockWith { return $false }
                        
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Password $newUserValues.Password
                        $testTargetResourceResult | Should Be $false

                        Assert-MockCalled -CommandName Find-UserByNameOnFullSku -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Test-UserPasswordOnFullSku -Exactly 1 -Scope It
                    }
                    
                    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 $existingUserValues.Enabled
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordNeverExpires is incorrect' {
                        $testTargetResourceResult = Test-TargetResource `
                                              -UserName $existingUserName `
                                              -PasswordNeverExpires (-not $existingUserValues.PasswordNeverExpires)
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordChangeNotAllowed is incorrect' {
                        $testTargetResourceResult = Test-TargetResource `
                                               -UserName $existingUserName `
                                               -PasswordChangeNotAllowed $existingUserValues.UserMayChangePassword
                        $testTargetResourceResult | Should Be $false 
                    }
                }

                Context 'Tests on Nano Server' {
                    Mock -CommandName Test-IsNanoServer -MockWith { return $true }
                    Mock -CommandName Find-UserByNameOnNanoServer -MockWith { return $existingUserValues } `
                                                                  -ParameterFilter { $UserName -eq $existingUserName }
                    $absentUserName = 'AbsentUserUserName123456789'
                    Mock -CommandName Find-UserByNameOnNanoServer `
                         -MockWith { Write-Error -Message 'Test error message' -ErrorId  'UserNotFound' } `
                         -ParameterFilter { $UserName -eq $absentUserName }
                    $duplicateUserName = 'DuplicateUserName'
                    Mock -CommandName Find-UserByNameOnNanoServer -MockWith { Throw } `
                                                                  -ParameterFilter { $UserName -eq $duplicateUserName }
                    
                    It 'Should return true when user Present and correct values' {
                        Mock -CommandName Test-CredentialsValidOnNanoServer { return $true }
                        
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserValues.Name `
                                                                        -Description $existingUserValues.Description `
                                                                        -Password $existingUserValues.Password `
                                                                        -Disabled (-not $existingUserValues.Enabled) `
                                                                        -PasswordNeverExpires $existingUserValues.PasswordNeverExpires `
                                                                        -PasswordChangeNotAllowed $existingUserValues.UserCanNotChangePassword
                        $testTargetResourceResult | Should Be $true

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Test-CredentialsValidOnNanoServer -Exactly 1 -Scope It
                    }
                    
                    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' {
                        Mock -CommandName Test-CredentialsValidOnNanoServer { return $false }
                        
                        $testTargetResourceResult = Test-TargetResource -UserName $existingUserName `
                                                                        -Password $newUserValues.Password
                        $testTargetResourceResult | Should Be $false

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                        Assert-MockCalled -CommandName Test-CredentialsValidOnNanoServer -Exactly 1 -Scope It
                    }
                    
                    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 $existingUserValues.Enabled
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordNeverExpires is incorrect' {
                        $testTargetResourceResult = Test-TargetResource `
                                              -UserName $existingUserName `
                                              -PasswordNeverExpires (-not $existingUserValues.PasswordNeverExpires)
                        $testTargetResourceResult | Should Be $false 
                    }
                    
                    It 'Should return false when PasswordChangeNotAllowed is incorrect' {
                        $testTargetResourceResult = Test-TargetResource `
                                               -UserName $existingUserName `
                                               -PasswordChangeNotAllowed $existingUserValues.UserMayChangePassword
                        $testTargetResourceResult | Should Be $false 
                    }

                    It 'Should throw an Invalid Operation exception when there are multiple users with the given name' {
                        $exception = New-Object -TypeName 'InvalidOperationException' `
                                                -ArgumentList @($null)
                        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
                                                  -ArgumentList @($exception, 'MachineStateIncorrect', 'InvalidOperation', $null)

                        { Test-TargetResource -UserName $duplicateUserName } | Should Throw $errorRecord

                        Assert-MockCalled -CommandName Find-UserByNameOnNanoServer -Exactly 1 -Scope It
                    }
                }
            }

            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
        {
            foreach ($disposableObject in $script:disposableObjects)
            {
                $disposableObject.Dispose()
            }
        }
    }
}
finally
{
    Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment
}