Tests/Unit/MSFT_SqlScript.Tests.ps1

<#
    .SYNOPSIS
        Automated unit test for MSFT_SqlScript DSC Resource
#>


# Suppression of this PSSA rule allowed in tests.
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
Param()

#region HEADER

# Unit Test Template Version: 1.2.0
$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
{
    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
}

Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force

$TestEnvironment = Initialize-TestEnvironment `
    -DSCModuleName 'SqlServerDsc' `
    -DSCResourceName 'MSFT_SqlScript'  `
    -TestType Unit

#endregion HEADER

function Invoke-TestSetup {
    Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs')
    Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Global -Force
}

function Invoke-TestCleanup {
    Restore-TestEnvironment -TestEnvironment $TestEnvironment
}


# Begin Testing
try
{
    Invoke-TestSetup

    InModuleScope 'MSFT_SqlScript' {
        $script:DSCModuleName       = 'SqlServerDsc'
        $resourceName     = 'MSFT_SqlScript'

        $testParameters = @{
            ServerInstance = $env:COMPUTERNAME
            SetFilePath = "set.sql"
            GetFilePath = "get.sql"
            TestFilePath = "test.sql"
        }

        $testParametersTimeout = @{
            ServerInstance = $env:COMPUTERNAME
            SetFilePath    = "set-timeout.sql"
            GetFilePath    = "get-timeout.sql"
            TestFilePath   = "test-timeout.sql"
            QueryTimeout   = 30
        }

        Describe "$resourceName\Get-TargetResource" {

            Context 'Get-TargetResource fails to import SQLPS module' {
                $throwMessage = "Failed to import SQLPS module."

                Mock -CommandName Import-SQLPSModule -MockWith {
                    throw $throwMessage
                }

                It 'Should throw the correct error from Import-Module' {
                    { Get-TargetResource @testParameters } | Should -Throw $throwMessage
                }
            }

            Context 'Get-TargetResource returns script results successfully' {
                Mock -CommandName Import-SQLPSModule
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    return ''
                }

                It 'Should return the expected results' {
                    $result = Get-TargetResource @testParameters
                    $result.ServerInstance | Should -Be $testParameters.ServerInstance
                    $result.SetFilePath | Should -Be $testParameters.SetFilePath
                    $result.GetFilePath | Should -Be $testParameters.GetFilePath
                    $result.TestFilePath | Should -Be $testParameters.TestFilePath
                    $result | Should -BeOfType Hashtable
                }
            }

            Context 'Get-TargetResource returns script results successfully with query timeout' {
                Mock -CommandName Import-SQLPSModule
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    return ''
                }

                It 'Should return the expected results' {
                    $result = Get-TargetResource @testParametersTimeout
                    $result.ServerInstance | Should -Be $testParametersTimeout.ServerInstance
                    $result.SetFilePath | Should -Be $testParametersTimeout.SetFilePath
                    $result.GetFilePath | Should -Be $testParametersTimeout.GetFilePath
                    $result.TestFilePath | Should -Be $testParametersTimeout.TestFilePath
                    $result | Should -BeOfType Hashtable
                }
            }

            Context 'Get-TargetResource throws an error when running the script in the GetFilePath parameter' {
                $errorMessage = "Failed to run SQL Script"

                Mock -CommandName Import-SQLPSModule
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    throw $errorMessage
                }

                It 'Should throw the correct error from Invoke-Sqlcmd' {
                    { Get-TargetResource @testParameters } | Should -Throw $errorMessage
                }
            }
        }

        Describe "$resourceName\Set-TargetResource" {

            Context 'Set-TargetResource fails to import SQLPS module' {
                $throwMessage = "Failed to import SQLPS module."

                Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage }

                It 'Should throw the correct error from Import-Module' {
                    { Set-TargetResource @testParameters } | Should -Throw $throwMessage
                }
            }

            Context 'Set-TargetResource runs script without issue' {
                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    return ''
                }

                It 'Should return the expected results' {
                    $result = Set-TargetResource @testParameters
                    $result | Should -Be ''
                }
            }

            Context 'Set-TargetResource runs script without issue using timeout' {
                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    return ''
                }

                It 'Should return the expected results' {
                    $result = Set-TargetResource @testParametersTimeout
                    $result | Should -Be ''
                }
            }

            Context 'Set-TargetResource throws an error when running the script in the SetFilePath parameter' {
                $errorMessage = "Failed to run SQL Script"

                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    throw $errorMessage
                }

                It 'Should throw the correct error from Invoke-Sqlcmd' {
                    { Set-TargetResource @testParameters } | Should -Throw $errorMessage
                }
            }
        }

        Describe "$resourceName\Test-TargetResource" {
            Context 'Test-TargetResource fails to import SQLPS module' {
                $throwMessage = 'Failed to import SQLPS module.'

                Mock -CommandName Import-SQLPSModule -MockWith {
                    throw $throwMessage
                }

                It 'Should throw the correct error from Import-Module' {
                    { Set-TargetResource @testParameters } | Should -Throw $throwMessage
                }
            }

            Context 'Test-TargetResource runs script without issue' {
                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {}

                It 'Should return true' {
                    $result = Test-TargetResource @testParameters
                    $result | Should -Be $true
                }
            }

            Context 'Test-TargetResource runs script without issue with timeout' {
                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {}

                It 'Should return true' {
                    $result = Test-TargetResource @testParametersTimeout
                    $result | Should -Be $true
                }
            }

            Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' {
                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    throw New-Object -TypeName Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException
                }

                It 'Should return false' {
                    $result = Test-TargetResource @testParameters
                    $result | Should -Be $false
                }
            }

            Context 'Test-TargetResource throws an unexpected error when running the script in the TestFilePath parameter' {
                $errorMessage = "Failed to run SQL Script"

                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    throw $errorMessage
                }

                It 'Should throw the correct error from Invoke-Sqlcmd' {
                    { Test-TargetResource @testParameters } | Should -Throw $errorMessage
                }
            }
        }

        Describe "$resourceName\Invoke-SqlScript" {
            $invokeScriptParameters = @{
                ServerInstance = $env:COMPUTERNAME
                SqlScriptPath = "set.sql"
            }

            Context 'Invoke-SqlScript fails to import SQLPS module' {
                $throwMessage = "Failed to import SQLPS module."

                Mock -CommandName Import-SQLPSModule -MockWith {
                    throw $throwMessage
                }

                It 'Should throw the correct error from Import-Module' {
                    { Invoke-SqlScript @invokeScriptParameters } | Should -Throw $throwMessage
                }
            }

            Context 'Invoke-SQlScript is called with credentials' {
                $passwordPlain = "password"
                $user = "User"

                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -ParameterFilter {
                    ($Username -eq $user) -and ($Password -eq $passwordPlain)
                }

                $password = ConvertTo-SecureString -String $passwordPlain -AsPlainText -Force
                $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password

                It 'Should call Invoke-Sqlcmd with correct parameters' {
                    $invokeScriptParameters.Add("Credential", $cred)
                    $null = Invoke-SqlScript @invokeScriptParameters

                    Assert-MockCalled -CommandName Invoke-Sqlcmd -ParameterFilter {
                        ($Username -eq $user) -and ($Password -eq $passwordPlain)
                    } -Times 1 -Exactly -Scope It
                }
            }

            Context 'Invoke-SqlScript fails to execute the SQL scripts' {
                $errorMessage = "Failed to run SQL Script"

                Mock -CommandName Import-SQLPSModule -MockWith {}
                Mock -CommandName Invoke-Sqlcmd -MockWith {
                    throw $errorMessage
                }

                It 'Should throw the correct error from Invoke-Sqlcmd' {
                    { Invoke-SqlScript @invokeScriptParameters } | Should -Throw $errorMessage
                }
            }
        }
    }
}
finally
{
    Invoke-TestCleanup
}