tests/DBOpsConfig.class.Tests.ps1

Param (
    [switch]$Batch
)

if ($PSScriptRoot) { $commandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", ""); $here = $PSScriptRoot }
else { $commandName = "_ManualExecution"; $here = (Get-Item . ).FullName }

if (!$Batch) {
    # Is not a part of the global batch => import module
    #Explicitly import the module for testing
    Import-Module "$here\..\dbops.psd1" -Force; Get-DBOModuleFileList -Type internal | ForEach-Object { . $_.FullName }
}
else {
    # Is a part of a batch, output some eye-catching happiness
    Write-Host "Running $commandName tests" -ForegroundColor Cyan
}

Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
. "$here\..\internal\classes\DBOpsHelper.class.ps1"
. "$here\..\internal\classes\DBOps.class.ps1"

$packageName = Join-PSFPath -Normalize "$here\etc\$commandName.zip"

$script1 = Join-PSFPath -Normalize "$here\etc\sqlserver-tests\success\1.sql"
$script2 = Join-PSFPath -Normalize "$here\etc\sqlserver-tests\success\2.sql"
$script3 = Join-PSFPath -Normalize "$here\etc\sqlserver-tests\success\3.sql"
$outConfig = Join-PSFPath -Normalize "$here\etc\out_full_config.json"
$fullConfig = Join-PSFPath -Normalize "$here\etc\tmp_full_config.json"
$fullConfigSource = Join-PSFPath -Normalize "$here\etc\full_config.json"
$testPassword = 'TestPassword'
$securePassword = $testPassword | ConvertTo-SecureString -Force -AsPlainText
$encryptedString = $securePassword | ConvertTo-EncryptedString 3>$null

Describe "DBOpsConfig class tests" -Tag $commandName, UnitTests, DBOpsConfig {
    BeforeAll {
        (Get-Content $fullConfigSource -Raw) -replace 'replaceMe', $encryptedString | Out-File $fullConfig -Force
    }
    AfterAll {
        if (Test-Path $fullConfig) { Remove-Item $fullConfig }
        if (Test-Path $outConfig) { Remove-Item $outConfig }
    }
    Context "tests DBOpsConfig constructors" {
        It "Should return an empty config by default" {
            $testResult = [DBOpsConfig]::new()
            foreach ($prop in $testResult.psobject.properties.name) {
                $testResult.$prop | Should Be (Get-PSFConfigValue -FullName dbops.$prop)
            }
        }
        It "Should return empty configuration from empty config file" {
            $testResult = [DBOpsConfig]::new((Get-Content "$here\etc\empty_config.json" -Raw))
            $testResult.ApplicationName | Should Be $null
            $testResult.SqlInstance | Should Be $null
            $testResult.Database | Should Be $null
            $testResult.DeploymentMethod | Should Be $null
            $testResult.ConnectionTimeout | Should Be $null
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential | Should Be $null
            $testResult.Username | Should Be $null
            $testResult.Password | Should Be $null
            $testResult.SchemaVersionTable | Should Be $null
            $testResult.Silent | Should Be $null
            $testResult.Variables | Should Be $null
            $testResult.Schema | Should BeNullOrEmpty
        }
        It "Should return all configurations from the config file" {
            $testResult = [DBOpsConfig]::new((Get-Content $fullConfig -Raw))
            $testResult.ApplicationName | Should Be "MyTestApp"
            $testResult.SqlInstance | Should Be "TestServer"
            $testResult.Database | Should Be "MyTestDB"
            $testResult.DeploymentMethod | Should Be "SingleTransaction"
            $testResult.ConnectionTimeout | Should Be 40
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential.UserName | Should Be 'CredentialUser'
            $testResult.Credential.GetNetworkCredential().Password  | Should Be $testPassword
            $testResult.Username | Should Be "TestUser"
            [PSCredential]::new('test', $testResult.Password).GetNetworkCredential().Password | Should Be "TestPassword"
            $testResult.SchemaVersionTable | Should Be "test.Table"
            $testResult.Silent | Should Be $true
            $testResult.Variables.foo | Should -Be 'bar'
            $testResult.Variables.boo | Should -Be 'far'
            $testResult.Schema | Should Be "testschema"
        }
    }
    Context "tests other methods of DBOpsConfig" {
        It "should test AsHashtable method" {
            $testResult = [DBOpsConfig]::new((Get-Content $fullConfig -Raw)).AsHashtable()
            $testResult.GetType().Name | Should Be 'hashtable'
            $testResult.ApplicationName | Should Be "MyTestApp"
            $testResult.SqlInstance | Should Be "TestServer"
            $testResult.Database | Should Be "MyTestDB"
            $testResult.DeploymentMethod | Should Be "SingleTransaction"
            $testResult.ConnectionTimeout | Should Be 40
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential.UserName | Should Be 'CredentialUser'
            $testResult.Credential.GetNetworkCredential().Password  | Should Be $testPassword
            $testResult.Username | Should Be "TestUser"
            [PSCredential]::new('test', $testResult.Password).GetNetworkCredential().Password | Should Be "TestPassword"
            $testResult.SchemaVersionTable | Should Be "test.Table"
            $testResult.Silent | Should Be $true
            $testResult.Variables.foo | Should -Be 'bar'
            $testResult.Variables.boo | Should -Be 'far'
            $testResult.Schema | Should Be "testschema"
        }
        It "should test SetValue method" {
            $config = [DBOpsConfig]::new((Get-Content $fullConfig -Raw))
            #String property
            $config.SetValue('ApplicationName', 'MyApp2')
            $config.ApplicationName | Should Be 'MyApp2'
            $config.SetValue('ApplicationName', $null)
            $config.ApplicationName | Should Be $null
            $config.SetValue('ApplicationName', 123)
            $config.ApplicationName | Should Be '123'
            #Int property
            $config.SetValue('ConnectionTimeout', 11)
            $config.ConnectionTimeout | Should Be 11
            $config.SetValue('ConnectionTimeout', $null)
            $config.ConnectionTimeout | Should Be $null
            $config.SetValue('ConnectionTimeout', '123')
            $config.ConnectionTimeout | Should Be 123
            { $config.SetValue('ConnectionTimeout', 'string') } | Should Throw
            #Bool property
            $config.SetValue('Silent', $false)
            $config.Silent | Should Be $false
            $config.SetValue('Silent', $null)
            $config.Silent | Should Be $null
            $config.SetValue('Silent', 2)
            $config.Silent | Should Be $true
            $config.SetValue('Silent', 0)
            $config.Silent | Should Be $false
            $config.SetValue('Silent', 'string')
            $config.Silent | Should Be $true
            #SecureString property
            $config.SetValue('Password', $encryptedString)
            [PSCredential]::new('test', $config.Password).GetNetworkCredential().Password | Should Be $testPassword
            $config.SetValue('Password', $null)
            $config.Password | Should Be $null
            #PSCredential property
            $config.SetValue('Credential', ([pscredential]::new('CredentialUser', $securePassword)))
            $config.Credential.UserName | Should Be 'CredentialUser'
            $config.Credential.GetNetworkCredential().Password  | Should Be $testPassword
            $config.SetValue('Credential', $null)
            $config.Credential | Should Be $null
            #hashtable
            $config.SetValue('ConnectionAttribute', @{ 'Connection Timeout' = 10 })
            $config.ConnectionAttribute.'Connection Timeout' | Should -Be 10
            #Negatives
            { $config.SetValue('AppplicationName', 'MyApp3') } | Should Throw
        }
        It "should test ExportToJson method" {
            $testResult = [DBOpsConfig]::new((Get-Content $fullConfig -Raw)).ExportToJson() | ConvertFrom-Json -ErrorAction Stop
            $testResult.ApplicationName | Should Be "MyTestApp"
            $testResult.SqlInstance | Should Be "TestServer"
            $testResult.Database | Should Be "MyTestDB"
            $testResult.DeploymentMethod | Should Be "SingleTransaction"
            $testResult.ConnectionTimeout | Should Be 40
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential.UserName | Should Be 'CredentialUser'
            [PSCredential]::new('test', ($testResult.Credential.Password | ConvertFrom-EncryptedString)).GetNetworkCredential().Password | Should Be $testPassword
            $testResult.Username | Should Be "TestUser"
            [PSCredential]::new('test', ($testResult.Password | ConvertFrom-EncryptedString)).GetNetworkCredential().Password | Should Be "TestPassword"
            $testResult.SchemaVersionTable | Should Be "test.Table"
            $testResult.Silent | Should Be $true
            $testResult.Variables.foo | Should -Be 'bar'
            $testResult.Variables.boo | Should -Be 'far'
            $testResult.Schema | Should Be "testschema"
        }
        It "should test Merge method into full config" {
            $config = [DBOpsConfig]::new((Get-Content $fullConfig -Raw))
            $hashtable = @{
                ApplicationName   = 'MyTestApp2'
                ConnectionTimeout = 0
                SqlInstance       = $null
                Silent            = $false
                ExecutionTimeout  = 20
                Schema            = "test3"
                Password          = $null
                Credential        = $null
                Variables         = @{
                    foo = "bar2"
                    goo = "yarr"
                }
            }
            $config.Merge($hashtable)
            $config.ApplicationName | Should Be "MyTestApp2"
            $config.SqlInstance | Should Be $null
            $config.Database | Should Be "MyTestDB"
            $config.DeploymentMethod | Should Be "SingleTransaction"
            $config.ConnectionTimeout | Should Be 0
            $config.ExecutionTimeout | Should Be 20
            $config.Encrypt | Should Be $null
            $config.Credential | Should Be $null
            $config.Username | Should Be "TestUser"
            $config.Password | Should Be $null
            $config.SchemaVersionTable | Should Be "test.Table"
            $config.Silent | Should Be $false
            $config.Variables.foo | Should Be "bar2"
            $config.Variables.boo | Should Be "far"
            $config.Variables.goo | Should Be "yarr"
            $config.Schema | Should Be "test3"
            #negative
            { $config.Merge(@{foo = 'bar'}) } | Should Throw
            { $config.Merge($null) } | Should Throw
        }
        It "should test Merge method into empty config" {
            $config = [DBOpsConfig]::new()
            $hashtable = [DBOpsConfig]::new((Get-Content $fullConfig -Raw)).AsHashtable()
            $config.Merge($hashtable)
            $config.ApplicationName | Should Be "MyTestApp"
            $config.SqlInstance | Should Be "TestServer"
            $config.Database | Should Be "MyTestDB"
            $config.DeploymentMethod | Should Be "SingleTransaction"
            $config.ConnectionTimeout | Should Be 40
            $config.ExecutionTimeout | Should Be 0
            $config.Encrypt | Should Be $null
            $config.Credential.UserName | Should Be 'CredentialUser'
            $config.Credential.GetNetworkCredential().Password  | Should Be $testPassword
            $config.Username | Should Be "TestUser"
            [PSCredential]::new('test', $config.Password).GetNetworkCredential().Password | Should Be "TestPassword"
            $config.SchemaVersionTable | Should Be "test.Table"
            $config.Silent | Should Be $true
            $config.Variables.foo | Should -Be 'bar'
            $config.Variables.boo | Should -Be 'far'
            $config.Schema | Should Be "testschema"
            #negative
            { $config.Merge(@{foo = 'bar'}) } | Should Throw
            { $config.Merge($null) } | Should Throw
        }
    }
    Context "tests Save/Alter methods" {
        AfterAll {
            if (Test-Path $packageName) { Remove-Item $packageName }
        }
        It "should test Save method" {
            #Generate new package file
            $pkg = [DBOpsPackage]::new()
            $pkg.Configuration.ApplicationName = 'TestApp2'
            $pkg.SaveToFile($packageName)

            #Open zip file stream
            $writeMode = [System.IO.FileMode]::Open
            $stream = [FileStream]::new($packageName, $writeMode)
            try {
                #Open zip file
                $zip = [ZipArchive]::new($stream, [ZipArchiveMode]::Update)
                try {
                    #Initiate saving
                    $pkg.Configuration.Save($zip)
                }
                catch {
                    throw $_
                }
                finally {
                    #Close archive
                    $zip.Dispose()
                }
            }
            catch {
                throw $_
            }
            finally {
                #Close archive
                $stream.Dispose()
            }
            $testResults = Get-ArchiveItem $packageName
            foreach ($file in (Get-DBOModuleFileList)) {
                Join-PSFPath -Normalize 'Modules\dbops' $file.Path | Should BeIn $testResults.Path
            }
            'dbops.config.json' | Should BeIn $testResults.Path
            'dbops.package.json' | Should BeIn $testResults.Path
            'Deploy.ps1' | Should BeIn $testResults.Path
        }
        It "Should load package successfully after saving it" {
            $pkg = [DBOpsPackage]::new($packageName)
            $pkg.Configuration.ApplicationName | Should Be 'TestApp2'
        }
        It "should test Alter method" {
            $pkg = [DBOpsPackage]::new($packageName)
            $pkg.Configuration.ApplicationName = 'TestApp3'
            $pkg.Configuration.Alter()
            $testResults = Get-ArchiveItem "$packageName"
            foreach ($file in (Get-DBOModuleFileList)) {
                Join-PSFPath -Normalize 'Modules\dbops' $file.Path | Should BeIn $testResults.Path
            }
            'dbops.config.json' | Should BeIn $testResults.Path
            'dbops.package.json' | Should BeIn $testResults.Path
            'Deploy.ps1' | Should BeIn $testResults.Path
        }
        It "Should load package successfully after saving it" {
            $p = [DBOpsPackage]::new($packageName)
            $p.Configuration.ApplicationName | Should Be 'TestApp3'
        }
        It "should test SaveToFile method" {
            [DBOpsConfig]::new((Get-Content $fullConfig -Raw)).SaveToFile($outConfig)
            $testResult = Get-Content $outConfig -Raw | ConvertFrom-Json -ErrorAction Stop
            $testResult.ApplicationName | Should Be "MyTestApp"
            $testResult.SqlInstance | Should Be "TestServer"
            $testResult.Database | Should Be "MyTestDB"
            $testResult.DeploymentMethod | Should Be "SingleTransaction"
            $testResult.ConnectionTimeout | Should Be 40
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential.UserName | Should Be 'CredentialUser'
            [PSCredential]::new('test', ($testResult.Credential.Password | ConvertFrom-EncryptedString)).GetNetworkCredential().Password | Should Be $testPassword
            $testResult.Username | Should Be "TestUser"
            [PSCredential]::new('test', ($testResult.Password | ConvertFrom-EncryptedString)).GetNetworkCredential().Password | Should Be "TestPassword"
            $testResult.SchemaVersionTable | Should Be "test.Table"
            $testResult.Silent | Should Be $true
            $testResult.Variables | Should -Not -BeNullOrEmpty
            $testResult.Schema | Should Be "testschema"
        }
    }
    Context "tests static methods of DBOpsConfig" {
        It "should test static GetDeployFile method" {
            $f = [DBOpsConfig]::GetDeployFile()
            $f.Type | Should Be 'Misc'
            $f.Path | Should BeLike (Join-PSFPath -Normalize '*\Deploy.ps1')
            $f.Name | Should Be 'Deploy.ps1'
        }
        It "should test static FromFile method" {
            $testResult = [DBOpsConfig]::FromFile($fullConfig)
            $testResult.ApplicationName | Should Be "MyTestApp"
            $testResult.SqlInstance | Should Be "TestServer"
            $testResult.Database | Should Be "MyTestDB"
            $testResult.DeploymentMethod | Should Be "SingleTransaction"
            $testResult.ConnectionTimeout | Should Be 40
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential.UserName | Should Be 'CredentialUser'
            $testResult.Credential.GetNetworkCredential().Password  | Should Be $testPassword
            $testResult.Username | Should Be "TestUser"
            [PSCredential]::new('test', $testResult.Password).GetNetworkCredential().Password | Should Be "TestPassword"
            $testResult.SchemaVersionTable | Should Be "test.Table"
            $testResult.Silent | Should Be $true
            $testResult.Variables | Should -Not -BeNullOrEmpty
            $testResult.Schema | Should Be "testschema"
            #negatives
            { [DBOpsConfig]::FromFile((Join-PSFPath -Normalize "$here\etc\notajsonfile.json")) } | Should Throw
            { [DBOpsConfig]::FromFile((Join-PSFPath -Normalize "nonexisting\file")) } | Should Throw
            { [DBOpsConfig]::FromFile($null) } | Should Throw
        }
        It "should test static FromJsonString method" {
            $testResult = [DBOpsConfig]::FromJsonString((Get-Content $fullConfig -Raw))
            $testResult.ApplicationName | Should Be "MyTestApp"
            $testResult.SqlInstance | Should Be "TestServer"
            $testResult.Database | Should Be "MyTestDB"
            $testResult.DeploymentMethod | Should Be "SingleTransaction"
            $testResult.ConnectionTimeout | Should Be 40
            $testResult.ExecutionTimeout | Should Be 0
            $testResult.Encrypt | Should Be $null
            $testResult.Credential.UserName | Should Be 'CredentialUser'
            $testResult.Credential.GetNetworkCredential().Password  | Should Be $testPassword
            $testResult.Username | Should Be "TestUser"
            [PSCredential]::new('test', $testResult.Password).GetNetworkCredential().Password | Should Be "TestPassword"
            $testResult.SchemaVersionTable | Should Be "test.Table"
            $testResult.Silent | Should Be $true
            $testResult.Variables | Should -Not -BeNullOrEmpty
            $testResult.Schema | Should Be "testschema"
            #negatives
            { [DBOpsConfig]::FromJsonString((Get-Content "$here\etc\notajsonfile.json" -Raw)) } | Should Throw
            { [DBOpsConfig]::FromJsonString($null) } | Should Throw
            { [DBOpsConfig]::FromJsonString('') } | Should Throw
        }
    }
}