tests/Unit/Public/Test-sqmBackupStatus.Tests.ps1
|
#Requires -Modules Pester <# .SYNOPSIS Unit Tests fuer Test-sqmBackupStatus. Invoke-DbaQuery wird gemockt - getestet wird die Bewertung: Schwellwerte, sqm_BackupExclude, "nach dem letzten Lauf angelegt", Vorgaenger- Historie, Serveruhr statt Clientuhr, AG-Secondary liest beim Primary. Die echte Abfrage ist live gegen SQL Server 2025 geprueft. #> BeforeAll { . "$PSScriptRoot\..\..\..\tests\TestHelpers.ps1" Import-sqmTestModule $script:out = Join-Path $TestDrive 'bs' # Serveruhr bewusst weit weg von der Clientuhr: alle Alter muessen relativ dazu stimmen $script:srvNow = [datetime]'2030-01-15 12:00' function script:New-BkRow { param ([string]$Name, [int]$Id = 10, [datetime]$Created = '2029-01-01', [string]$Recovery = 'FULL', $Full, $Diff, $Log, $CopyOnly, $BeforeCreate, [string]$State = 'ONLINE', [int]$ReadOnly = 0, $Ag, $Role, $Primary) $n = [DBNull]::Value [PSCustomObject]@{ DatabaseName = $Name; DatabaseId = $Id; CreateDate = $Created; State = $State; RecoveryModel = $Recovery IsReadOnly = $ReadOnly; IsStandby = 0 AgName = $(if ($Ag) { $Ag } else { $n }); LocalRole = $(if ($Role) { $Role } else { $n }); PrimaryReplica = $(if ($Primary) { $Primary } else { $n }) LastFull = $(if ($Full) { [datetime]$Full } else { $n }); LastDiff = $(if ($Diff) { [datetime]$Diff } else { $n }) LastLog = $(if ($Log) { [datetime]$Log } else { $n }); LastCopyOnlyFull = $(if ($CopyOnly) { [datetime]$CopyOnly } else { $n }) LastFullBeforeCreate = $(if ($BeforeCreate) { [datetime]$BeforeCreate } else { $n }) ServerNow = $script:srvNow } } } AfterAll { if (Get-Module sqmSQLTool) { Remove-Module sqmSQLTool -Force } $env:SQMSQLTOOL_SKIP_AUTO_UPDATE = $null } Describe 'Test-sqmBackupStatus' { BeforeEach { Mock -ModuleName sqmSQLTool Invoke-sqmLogging { } Mock -ModuleName sqmSQLTool Invoke-sqmOpenReport { } Mock -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'sqm_BackupExclude' } { [PSCustomObject]@{ DatabaseName = 'Archiv'; Reason = 'Nur lesend, Band'; ExcludedBy = 'DOM\dba'; ExcludedAt = [datetime]'2029-06-01'; IsActive = 0; IsOrphaned = 0 } [PSCustomObject]@{ DatabaseName = 'Shop'; Reason = [DBNull]::Value; ExcludedBy = 'sa'; ExcludedAt = [datetime]'2029-06-01'; IsActive = 1; IsOrphaned = 0 } } Mock -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'WITH bk' } { New-BkRow -Name 'master' -Id 1 -Recovery 'SIMPLE' -Full '2030-01-15 01:00' New-BkRow -Name 'Shop' -Full '2030-01-14 21:15' -Log '2030-01-15 11:45' New-BkRow -Name 'Lager' -Full '2030-01-13 21:15' -Log '2030-01-15 11:45' New-BkRow -Name 'Kasse' -Full '2030-01-14 21:15' -Log '2030-01-15 09:00' New-BkRow -Name 'Archiv' -Recovery 'SIMPLE' New-BkRow -Name 'NeuDb' -Created '2030-01-15 08:00' New-BkRow -Name 'Ersetzt' -Recovery 'SIMPLE' -Created '2030-01-15 10:00' -BeforeCreate '2030-01-14 21:15' New-BkRow -Name 'Altlast' -Created '2029-12-01' New-BkRow -Name 'Offline' -State 'OFFLINE' } } It 'Bewertet je Datenbank gegen die Grenzen' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen $s = @{ }; foreach ($d in $r.DetailRows) { $s[$d.Database] = $d } $s['Shop'].Status | Should -Be 'OK' $s['Lager'].Status | Should -Be 'Critical' # Full 39 h alt > 26 h $s['Kasse'].Status | Should -Be 'Warning' # Log 180 min alt > 60 min $s['Altlast'].Status | Should -Be 'Critical' # nie gesichert, alt $s['Offline'].Status | Should -Be 'Skipped' $s.ContainsKey('master') | Should -Be $false $r.Status | Should -Be 'Critical' } It 'Rechnet gegen die Serveruhr, nicht gegen die Clientuhr' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen ($r.DetailRows | Where-Object Database -eq 'Shop').FullAgeHours | Should -Be 14.8 ($r.DetailRows | Where-Object Database -eq 'Kasse').LogAgeMinutes | Should -Be 180 } It 'Beruecksichtigt sqm_BackupExclude (nur IsActive=0) mit Grund' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen $a = $r.DetailRows | Where-Object Database -eq 'Archiv' $a.Status | Should -Be 'Excluded' $a.ExcludedFromBackup | Should -Be $true $a.Notes | Should -Match 'Nur lesend, Band' ($r.DetailRows | Where-Object Database -eq 'Shop').ExcludedFromBackup | Should -Be $false $r.ExcludedCount | Should -Be 1 } It '-IgnoreExcludeTable bewertet ausgeschlossene Datenbanken normal' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen -IgnoreExcludeTable ($r.DetailRows | Where-Object Database -eq 'Archiv').Status | Should -Be 'Critical' Should -Invoke -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'sqm_BackupExclude' } -Exactly 0 } It 'Vermerkt Datenbanken, die nach dem letzten Backup-Lauf angelegt wurden' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen $r.LastBackupRun | Should -Be ([datetime]'2030-01-14 21:15') # master zaehlt nicht $r.NewSinceLastBackup | Should -Be @('NeuDb', 'Ersetzt') $n = $r.DetailRows | Where-Object Database -eq 'NeuDb' $n.NewSinceLastBackup | Should -Be $true $n.Status | Should -Be 'Warning' $n.Notes | Should -Match 'Nach dem letzten Backup-Lauf \(2030-01-14 21:15\) angelegt am 2030-01-15 08:00' ($r.DetailRows | Where-Object Database -eq 'Altlast').NewSinceLastBackup | Should -Be $false (Get-Content $r.TxtFile -Raw) | Should -Match 'Nach dem letzten Backup-Lauf angelegt: NeuDb, Ersetzt' } It 'Erkennt Historie einer frueheren gleichnamigen Datenbank' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen $e = $r.DetailRows | Where-Object Database -eq 'Ersetzt' $e.LastFullBackup | Should -Be '(keins)' $e.Notes | Should -Match 'frueheren Datenbank gleichen Namens' } It '-ExcludeDatabase verschiebt den letzten Lauf nicht' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen -ExcludeDatabase 'Shop', 'Kasse' $r.LastBackupRun | Should -Be ([datetime]'2030-01-14 21:15') ($r.DetailRows | Where-Object Database -eq 'NeuDb').NewSinceLastBackup | Should -Be $true $r.DetailRows.Database | Should -Not -Contain 'Shop' } It 'AG-Secondary: liest die Historie beim Primary' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'WITH bk' -and [string]$SqlInstance -eq 'SQL01' } { New-BkRow -Name 'AgDb' -Ag 'AG1' -Role 'SECONDARY' -Primary 'SQL02' } Mock -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'WITH bk' -and [string]$SqlInstance -eq 'SQL02' } { New-BkRow -Name 'AgDb' -Ag 'AG1' -Role 'PRIMARY' -Primary 'SQL02' -Full '2030-01-14 21:15' -Log '2030-01-15 11:45' } $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen $d = $r.DetailRows | Where-Object Database -eq 'AgDb' $d.Status | Should -Be 'OK' $d.HistorySource | Should -Be 'Primary SQL02' $d.ReplicaRole | Should -Be 'SECONDARY' } It 'AG-Secondary: unerreichbarer Primary ergibt Skipped mit Hinweis' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'WITH bk' -and [string]$SqlInstance -eq 'SQL01' } { New-BkRow -Name 'AgDb' -Ag 'AG1' -Role 'SECONDARY' -Primary 'SQL02' } Mock -ModuleName sqmSQLTool Invoke-DbaQuery -ParameterFilter { $Query -match 'WITH bk' -and [string]$SqlInstance -eq 'SQL02' } { throw 'Der Netzwerkpfad wurde nicht gefunden' } $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen $d = $r.DetailRows | Where-Object Database -eq 'AgDb' $d.Status | Should -Be 'Skipped' $d.Notes | Should -Match 'SQL02 nicht erreichbar' } It 'Schreibt TXT, CSV und HTML' { $r = Test-sqmBackupStatus -SqlInstance 'SQL01' -OutputPath $script:out -NoOpen Test-Path $r.TxtFile | Should -Be $true Test-Path $r.CsvFile | Should -Be $true (Get-Content $r.HtmlFile -Raw) | Should -Match 'Backup-Kontrolle' } } |