tests/DBOpsPackage.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"

$slash = [IO.Path]::DirectorySeparatorChar
$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"
$fileObject1 = Get-Item $script1
$fileObject2 = Get-Item $script2
$fileObject3 = Get-Item $script3

Describe "DBOpsPackage class tests" -Tag $commandName, UnitTests, DBOpsPackage {
    AfterAll {
        if (Test-Path $packageName) { Remove-Item $packageName }
    }
    Context "validating DBOpsPackage creation" {
        AfterAll {
            if (Test-Path $packageName) { Remove-Item $packageName }
        }
        It "Should create new DBOpsPackage object" {
            $pkg = [DBOpsPackage]::new()
            $pkg.ScriptDirectory | Should Be 'content'
            $pkg.DeployFile.ToString() | Should Be 'Deploy.ps1'
            $pkg.DeployFile.GetContent() | Should BeLike '*Invoke-DBODeployment @params*'
            $pkg.Configuration.SchemaVersionTable | Should Be 'SchemaVersions'
            $pkg.FileName | Should BeNullOrEmpty
            $pkg.Version | Should BeNullOrEmpty
            { $pkg.SaveToFile($packageName, $true) } | Should Not Throw
        }
        $testResults = Get-ArchiveItem $packageName
        It "should contain module files" {
            foreach ($file in (Get-DBOModuleFileList)) {
                Join-PSFPath -Normalize 'Modules\dbops' $file.Path | Should BeIn $testResults.Path
            }
        }
        It "should contain config files" {
            'dbops.config.json' | Should BeIn $testResults.Path
            'dbops.package.json' | Should BeIn $testResults.Path
        }
        It "should contain deploy file" {
            'Deploy.ps1' | Should BeIn $testResults.Path
        }
    }
    Context "validate DBOpsPackage being loaded from file" {
        AfterAll {
            if (Test-Path $packageName) { Remove-Item $packageName }
        }
        BeforeAll {
            $pkg = [DBOpsPackage]::new()
            $pkg.SaveToFile($packageName, $force)
        }
        It "should load package from file" {
            $pkg = [DBOpsPackage]::new($packageName)
            $pkg.ScriptDirectory | Should Be 'content'
            $pkg.DeployFile.ToString() | Should Be 'Deploy.ps1'
            $pkg.DeployFile.GetContent() | Should BeLike '*Invoke-DBODeployment @params*'
            $pkg.ConfigurationFile.ToString() | Should Be 'dbops.config.json'
            ($pkg.ConfigurationFile.GetContent() | ConvertFrom-Json).SchemaVersionTable | Should Be 'SchemaVersions'
            $pkg.Configuration.SchemaVersionTable | Should Be 'SchemaVersions'
            $pkg.FileName | Should Be $packageName
            $pkg.Version | Should BeNullOrEmpty
            $pkg.PackagePath | Should BeNullOrEmpty
        }
    }
    Context "should validate DBOpsPackage methods" {
        AfterAll {
            if (Test-Path $packageName) { Remove-Item $packageName }
        }
        BeforeEach {
            $pkg = [DBOpsPackage]::new()
            $pkg.SaveToFile($packageName, $true)
        }
        It "Should test GetBuilds method" {
            $pkg.GetBuilds() | Should Be $null
            $pkg.Builds.Add([DBOpsBuild]::new('1.0'))
            $pkg.GetBuilds().Build | Should Be '1.0'
            $pkg.Builds.Add([DBOpsBuild]::new('2.0'))
            $pkg.GetBuilds().Build | Should Be @('1.0', '2.0')
        }
        It "Should test NewBuild method" {
            $b = $pkg.NewBuild('1.0')
            $b.Build | Should Be '1.0'
            $b.PackagePath | Should Be '1.0'
            $b.Parent.GetType().Name | Should Be 'DBOpsPackage'
            $b.Scripts | Should BeNullOrEmpty
            ([datetime]$b.CreatedDate).Date | Should Be ([datetime]::Now).Date
            $pkg.Version | Should Be '1.0'
        }
        It "Should test GetBuild method" {
            $null = $pkg.NewBuild('1.0')
            $null = $pkg.NewBuild('2.0')
            $b = $pkg.GetBuild('1.0')
            $b.Build | Should Be '1.0'
            $b.PackagePath | Should Be '1.0'
            $b.Parent.GetType().Name | Should Be 'DBOpsPackage'
            $b.Scripts | Should BeNullOrEmpty
            ([datetime]$b.CreatedDate).Date | Should Be ([datetime]::Now).Date
            $b2 = $pkg.GetBuild(@('1.0', '2.0'))
            $b2.Build | Should Be @('1.0', '2.0')
        }
        It "Should test AddBuild method" {
            $pkg.AddBuild('2.0')
            $b = $pkg.GetBuild('2.0')
            $b.Build | Should Be '2.0'
            $b.PackagePath | Should Be '2.0'
            $b.Parent.GetType().Name | Should Be 'DBOpsPackage'
            $b.Scripts | Should BeNullOrEmpty
            ([datetime]$b.CreatedDate).Date | Should Be ([datetime]::Now).Date
            $pkg.Version | Should Be '2.0'
        }
        It "Should test EnumBuilds method" {
            $pkg.Builds.Add([DBOpsBuild]::new('1.0'))
            $pkg.Builds.Add([DBOpsBuild]::new('2.0'))
            $pkg.EnumBuilds() | Should Be @('1.0', '2.0')
        }
        It "Should test GetVersion method" {
            $pkg.AddBuild('2.0')
            $pkg.GetVersion() | Should Be '2.0'
        }
        It "Should test RemoveBuild method" {
            $pkg.AddBuild('1.0')
            $pkg.AddBuild('2.0')
            $pkg.RemoveBuild('2.0')
            '2.0' | Should Not BeIn $pkg.EnumBuilds()
            $pkg.GetBuild('2.0') | Should BeNullOrEmpty
            $pkg.Version | Should Be '1.0'
            #Testing overloads
            $pkg.AddBuild('2.0')
            $b = $pkg.Builds | Where-Object Build -eq '2.0'
            '2.0' | Should BeIn $pkg.EnumBuilds()
            $pkg.Version | Should Be '2.0'
            $pkg.RemoveBuild($b)
            '2.0' | Should Not BeIn $pkg.EnumBuilds()
            $pkg.Builds | Where-Object Build -eq '2.0' | Should BeNullOrEmpty
            $pkg.Version | Should Be '1.0'
        }
        It "should test ScriptExists method" {
            $b = $pkg.NewBuild('1.0')
            $s = Join-PSFPath -Normalize "$here\etc\sqlserver-tests\success\1.sql"
            $f = [DBOpsFile]::new($fileObject1, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($s))
            $b.AddFile($f, 'Scripts')
            $pkg.ScriptExists($script1) | Should Be $true
            $pkg.ScriptExists($script2) | Should Be $false
            { $pkg.ScriptExists("Nonexisting\path") } | Should Throw
        }
        It "should test ScriptModified method" {
            $b = $pkg.NewBuild('1.0')
            $f = [DBOpsFile]::new($fileObject1, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f2 = [DBOpsFile]::new($fileObject2, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($script1))
            $b.AddFile($f, 'Scripts')
            $pkg.ScriptModified($script2, (Join-PSFPath -Normalize 'success\1.sql')) | Should Be $true
            $pkg.ScriptModified($script1, (Join-PSFPath -Normalize 'success\1.sql')) | Should Be $false
            $pkg.ScriptModified($f2) | Should Be $true
            $pkg.ScriptModified($f) | Should Be $false
        }
        It "should test PackagePathExists method" {
            $b = $pkg.NewBuild('1.0')
            $f = [DBOpsFile]::new($fileObject1, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($script1))
            $b.AddFile($f, 'Scripts')
            $pkg.PackagePathExists($f.PackagePath) | Should Be $true
            $pkg.PackagePathExists('foo') | Should Be $false
        }
        It "should test ExportToJson method" {
            $b = $pkg.NewBuild('1.0')
            $f = [DBOpsFile]::new($fileObject1, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($script1))
            $b.AddFile($f, 'Scripts')
            $j = $pkg.ExportToJson() | ConvertFrom-Json
            $j.Builds | Should Not BeNullOrEmpty
            $j.ConfigurationFile | Should Not BeNullOrEmpty
            $j.DeployFile | Should Not BeNullOrEmpty
            $j.ScriptDirectory | Should Not BeNullOrEmpty
            $j.psobject.properties.name | Should -BeIn @('ScriptDirectory', 'DeployFile', 'PreDeployFile', 'PostDeployFile', 'ConfigurationFile', 'Builds')
            foreach ($build in $j.Builds) {
                $build.psobject.properties.name | Should -BeIn @('Scripts', 'Build', 'PackagePath', 'CreatedDate')
                foreach ($script in $build.Scripts) {
                    $script.psobject.properties.name | Should -BeIn @('Hash', 'PackagePath')
                }
            }

        }
        It "Should test GetPackagePath method" {
            $pkg.GetPackagePath() | Should Be ''
        }
        It "Should test RefreshModuleVersion method" {
            $pkg.RefreshModuleVersion()
            $pkg.ModuleVersion | Should Be (Get-Module dbops).Version
        }
        It "Should test ReadMetadata method" {
            $b = $pkg.NewBuild('1.0')
            $f = [DBOpsFile]::new($fileObject1, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($script1))
            $b.AddFile($f, 'Scripts')
            $f = [DBOpsFile]::new($fileObject2, 'success/2.sql', $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($script2))
            $b.AddFile($f, 'Scripts')
            $j = $pkg.ExportToJson()
            $md = $pkg.ReadMetadata($j)
            $md.Builds.Scripts.PackagePath | Should -Be @("success$($slash)1.sql", "success$($slash)2.sql")
        }
        It "Should test RefreshFileProperties method" {
            $pkg.RefreshFileProperties()
            $FileObject = Get-Item $packageName
            $pkg.PSPath | Should Be $FileObject.PSPath.ToString()
            $pkg.PSParentPath | Should Be $FileObject.PSParentPath.ToString()
            $pkg.PSChildName | Should Be $FileObject.PSChildName.ToString()
            $pkg.PSDrive | Should Be $FileObject.PSDrive.ToString()
            $pkg.PSIsContainer | Should Be $FileObject.PSIsContainer
            $pkg.Mode | Should Be $FileObject.Mode
            $pkg.BaseName | Should Be $FileObject.BaseName
            $pkg.Name | Should Be $FileObject.Name
            $pkg.Length | Should Be $FileObject.Length
            $pkg.DirectoryName | Should Be $FileObject.DirectoryName
            $pkg.Directory | Should Be $FileObject.Directory.ToString()
            $pkg.IsReadOnly | Should Be $FileObject.IsReadOnly
            $pkg.Exists | Should Be $FileObject.Exists
            $pkg.FullName | Should Be $FileObject.FullName
            $pkg.Extension | Should Be $FileObject.Extension
            $pkg.CreationTime | Should Be $FileObject.CreationTime
            $pkg.CreationTimeUtc | Should Be $FileObject.CreationTimeUtc
            $pkg.LastAccessTime | Should Not BeNullOrEmpty
            $pkg.LastAccessTimeUtc | Should Not BeNullOrEmpty
            $pkg.LastWriteTime | Should Be $FileObject.LastWriteTime
            $pkg.LastWriteTimeUtc | Should Be $FileObject.LastWriteTimeUtc
            $pkg.Attributes | Should Be $FileObject.Attributes
        }

        It "Should test SetConfiguration method" {
            $config = @{ SchemaVersionTable = 'dbo.NewTable' } | ConvertTo-Json -Depth 1
            { $pkg.SetConfiguration([DBOpsConfig]::new($config)) } | Should Not Throw
            $pkg.Configuration.SchemaVersionTable | Should Be 'dbo.NewTable'
        }
        $oldResults = Get-ArchiveItem $packageName
        #Sleep 1 second to ensure that modification date is changed
        Start-Sleep -Seconds 2
        It "should test Save*/Alter methods" {
            $b = $pkg.NewBuild('1.0')
            $f = [DBOpsFile]::new($fileObject1, (Join-PSFPath -Normalize 'success\1.sql'), $true)
            $f.SetContent([DBOpsHelper]::GetBinaryFile($script1))
            $b.AddFile($f, 'Scripts')
            { $pkg.SaveToFile($packageName) } | Should Throw #File already exists
            { $pkg.Alter() } | Should Not Throw
            $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
            Join-PSFPath -Normalize 'content\1.0\success\1.sql' | Should BeIn $testResults.Path
        }
        # Testing file contents to be updated by the Save method
        $testResults = Get-ArchiveItem $packageName
        $saveTestsErrors = 0
        foreach ($testResult in $oldResults) {
            if ($testResult.LastWriteTime -ge ($testResults | Where-Object Path -eq $testResult.Path).LastWriteTime) {
                It "Should have updated Modified date for file $($testResult.Path)" {
                    $testResult.LastWriteTime -lt ($testResults | Where-Object Path -eq $testResult.Path).LastWriteTime | Should Be $true
                }
                $saveTestsErrors++
            }
        }
        if ($saveTestsErrors -eq 0) {
            It "Ran silently $($oldResults.Length) file modification tests" {
                $saveTestsErrors | Should be 0
            }
        }
    }
}