tests/Unit/Public/SearchAndAgRemoval.Tests.ps1
|
#Requires -Modules Pester <# .SYNOPSIS Unit Tests fuer Test-sqmOlaInstallation, Find-sqmDatabaseObject, Get-sqmLinkedServerUsage, Test-sqmBackupIntegrity und Remove-sqmDatabaseFromAG. dbatools wird gemockt; geprueft werden Auswertung, gebaute SQL-Abfragen (Escaping, Klammerung) und dass -WhatIf bzw. Fehler nichts loeschen. #> BeforeAll { . "$PSScriptRoot\..\..\..\tests\TestHelpers.ps1" Import-sqmTestModule InModuleScope sqmSQLTool { $script:dbatoolsAvailable = $true } } AfterAll { Remove-Variable -Name SqmQueries -Scope Global -ErrorAction SilentlyContinue if (Get-Module sqmSQLTool) { Remove-Module sqmSQLTool -Force } $env:MSSQLTOOLS_SKIP_AUTO_UPDATE = $null } Describe 'Test-sqmOlaInstallation' { BeforeEach { Mock -ModuleName sqmSQLTool Connect-DbaInstance { [pscustomobject]@{ JobServer = [pscustomobject]@{ ServiceStatus = 'Running' } } } } It 'erkennt eine vollstaendige Backup-Installation mit laufendem Agent' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { [pscustomobject]@{ cnt = 1 } } $r = Test-sqmOlaInstallation -SqlInstance 'SQL01' $r.IsInstalled | Should -BeTrue $r.AgentRunning | Should -BeTrue $r.PresentObjects | Should -Be @('DatabaseBackup') $r.Warnings | Should -BeNullOrEmpty } It 'meldet fehlende Prozedur und fehlende CommandLog-Tabelle' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { [pscustomobject]@{ cnt = 0 } } $r = Test-sqmOlaInstallation -SqlInstance 'SQL01' $r.IsInstalled | Should -BeFalse $r.Message | Should -Match 'DatabaseBackup' $r.Warnings | Should -Match 'CommandLog' } It 'prueft je nach -RequiredSet weitere Prozeduren' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { [pscustomobject]@{ cnt = 1 } } (Test-sqmOlaInstallation -SqlInstance 'SQL01' -RequiredSet Integrity).PresentObjects | Should -Be @('DatabaseBackup', 'DatabaseIntegrityCheck') (Test-sqmOlaInstallation -SqlInstance 'SQL01' -RequiredSet Index).PresentObjects | Should -Be @('DatabaseBackup', 'DatabaseIntegrityCheck', 'IndexOptimize') } It 'AgentRunning ist false, wenn der Agent steht' { Mock -ModuleName sqmSQLTool Connect-DbaInstance { [pscustomobject]@{ JobServer = [pscustomobject]@{ ServiceStatus = 'Stopped' } } } Mock -ModuleName sqmSQLTool Invoke-DbaQuery { [pscustomobject]@{ cnt = 1 } } (Test-sqmOlaInstallation -SqlInstance 'SQL01').AgentRunning | Should -BeFalse } It 'meldet einen Verbindungsfehler in Message' { Mock -ModuleName sqmSQLTool Connect-DbaInstance { throw 'timeout' } $r = Test-sqmOlaInstallation -SqlInstance 'SQL01' -ErrorAction SilentlyContinue $r.IsInstalled | Should -BeFalse $r.Message | Should -Match 'timeout' } } Describe 'Find-sqmDatabaseObject' { BeforeEach { $global:SqmQueries = [System.Collections.Generic.List[string]]::new() Mock -ModuleName sqmSQLTool Invoke-sqmLogging { } Mock -ModuleName sqmSQLTool Get-DbaDatabase { @([pscustomobject]@{ Name = 'Sales' }, [pscustomobject]@{ Name = 'Hr' }, [pscustomobject]@{ Name = 'Sales2' }) } Mock -ModuleName sqmSQLTool Invoke-DbaQuery { $global:SqmQueries.Add($Query) if ("$Database" -ne 'Hr') { [pscustomobject]@{ DatabaseName = "$Database"; SchemaName = 'dbo'; ObjectName = 'Orders'; ObjectType = 'USER_TABLE'; DefinitionPreview = $null } } } } It 'sucht in allen passenden Datenbanken und liefert die Treffer' { $r = @(Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName 'Orders') $r.Count | Should -Be 2 $r.Database | Should -Be @('Sales', 'Sales2') $r[0].SqlInstance | Should -Be 'SQL01' } It '-Database filtert per Wildcard' { $r = @(Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName 'Orders' -Database 'Sales2') $r.Database | Should -Be @('Sales2') } It 'verdoppelt Hochkommas im Suchbegriff und in -ObjectType' { Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName "O'Brien" -ObjectType "A'B", 'USER_TABLE' | Out-Null $q = $global:SqmQueries[0] $q | Should -Match "LIKE 'O''Brien'" $q | Should -Match "IN \('A''B','USER_TABLE'\)" } It 'klammert bei -SearchDefinition die OR-Bedingung, damit -ObjectType fuer beide Teile gilt' { Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName 'x' -SearchDefinition -ObjectType 'SQL_STORED_PROCEDURE' | Out-Null $q = $global:SqmQueries[0] $q | Should -Match "WHERE \(o\.name LIKE 'x' OR m\.definition LIKE '%x%'\)\s+AND type_desc IN" } It 'ohne -SearchDefinition wird die Definition nicht durchsucht' { Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName 'x' | Out-Null $global:SqmQueries[0] | Should -Not -Match 'm\.definition LIKE' } It 'schluckt Fehler einzelner Datenbanken, wirft sie aber mit -EnableException' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { throw 'offline' } { Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName 'x' } | Should -Not -Throw { Find-sqmDatabaseObject -SqlInstance 'SQL01' -ObjectName 'x' -EnableException } | Should -Throw '*offline*' } } Describe 'Get-sqmLinkedServerUsage' { BeforeEach { $global:SqmQueries = [System.Collections.Generic.List[string]]::new() Mock -ModuleName sqmSQLTool Get-Module { [pscustomobject]@{ Name = 'dbatools' } } -ParameterFilter { $ListAvailable } Mock -ModuleName sqmSQLTool Get-DbaDatabase { @([pscustomobject]@{ Name = 'Sales' }) } Mock -ModuleName sqmSQLTool Invoke-DbaQuery { $global:SqmQueries.Add($Query) if ($Query -like '*sys.sql_modules*') { [pscustomobject]@{ DatabaseName = 'Sales'; SchemaName = 'dbo'; ObjectName = 'usp_Load'; ObjectType = 'SQL_STORED_PROCEDURE' } } elseif ($Query -like '*sysjobsteps*') { [pscustomobject]@{ job_id = 'job-1'; step_name = 'Step 1'; command = 'SELECT * FROM [LS1].db.dbo.t' } } } Mock -ModuleName sqmSQLTool Get-DbaAgentJob { [pscustomobject]@{ Id = 'job-1'; Name = 'Nightly load' } } } It 'wandelt * in % um und liefert Modul-Treffer' { $r = @(Get-sqmLinkedServerUsage -SqlInstance 'SQL01' -LinkedServer 'LS*') $global:SqmQueries[0] | Should -Match "LIKE '%LS%%'" $r.Count | Should -Be 1 $r[0].ObjectName | Should -Be 'usp_Load' } It 'verdoppelt Hochkommas im Namen des Linked Servers' { Get-sqmLinkedServerUsage -SqlInstance 'SQL01' -LinkedServer "L'S" | Out-Null $global:SqmQueries[0] | Should -Match "LIKE '%L''S%'" } It '-IncludeJobs ergaenzt Jobschritte mit dem Jobnamen' { $r = @(Get-sqmLinkedServerUsage -SqlInstance 'SQL01' -LinkedServer 'LS1' -IncludeJobs) $step = $r | Where-Object ObjectType -eq 'JOBSTEP' $step.ObjectName | Should -Be 'Nightly load' $step.Database | Should -Be 'msdb' } It 'ohne -IncludeJobs wird msdb nicht abgefragt' { Get-sqmLinkedServerUsage -SqlInstance 'SQL01' -LinkedServer 'LS1' | Out-Null $global:SqmQueries | Where-Object { $_ -like '*sysjobsteps*' } | Should -BeNullOrEmpty } } Describe 'Test-sqmBackupIntegrity' { BeforeEach { Mock -ModuleName sqmSQLTool Invoke-sqmLogging { } Mock -ModuleName sqmSQLTool Get-sqmConfig { $null } } It 'verifiziert jede angegebene Sicherung' { Mock -ModuleName sqmSQLTool Restore-DbaDatabase { } $r = @(Test-sqmBackupIntegrity -SqlInstance 'SQL01' -BackupPath 'C:\b\a.bak', 'C:\b\b.bak') $r.Count | Should -Be 2 $r.Verified | Should -Be @($true, $true) Should -Invoke -ModuleName sqmSQLTool Restore-DbaDatabase -Times 2 -Exactly -ParameterFilter { $VerifyOnly } } It 'meldet eine kaputte Sicherung als nicht verifiziert und macht weiter' { Mock -ModuleName sqmSQLTool Restore-DbaDatabase { throw 'Backup beschaedigt' } -ParameterFilter { $Path -like '*bad.bak' } Mock -ModuleName sqmSQLTool Restore-DbaDatabase { } -ParameterFilter { $Path -like '*good.bak' } $r = @(Test-sqmBackupIntegrity -SqlInstance 'SQL01' -BackupPath 'C:\b\bad.bak', 'C:\b\good.bak') ($r | Where-Object BackupFile -like '*bad.bak').Verified | Should -BeFalse ($r | Where-Object BackupFile -like '*bad.bak').Message | Should -Match 'beschaedigt' ($r | Where-Object BackupFile -like '*good.bak').Verified | Should -BeTrue } It '-EnableException wirft beim ersten Fehler' { Mock -ModuleName sqmSQLTool Restore-DbaDatabase { throw 'kaputt' } { Test-sqmBackupIntegrity -SqlInstance 'SQL01' -BackupPath 'C:\b\a.bak' -EnableException } | Should -Throw '*kaputt*' } It '-FileListOnly liest nur den Header und rechnet die Groesse um' { Mock -ModuleName sqmSQLTool Restore-DbaDatabase { } Mock -ModuleName sqmSQLTool Read-DbaBackupHeader { [pscustomobject]@{ LogicalName = 'Sales'; PhysicalName = 'D:\Sales.mdf'; Type = 'D'; FileGroupName = 'PRIMARY'; Size = 2048 } } $r = Test-sqmBackupIntegrity -SqlInstance 'SQL01' -BackupPath 'C:\b\a.bak' -FileListOnly $r.LogicalName | Should -Be 'Sales' $r.SizeMB | Should -Be 2 Should -Invoke -ModuleName sqmSQLTool Restore-DbaDatabase -Times 0 -Exactly } It 'nimmt den Pfad aus der Modulkonfiguration' { Mock -ModuleName sqmSQLTool Get-sqmConfig { 'E:\Backup' } -ParameterFilter { $Key -eq 'BackupDirectory' } Mock -ModuleName sqmSQLTool Restore-DbaDatabase { } (Test-sqmBackupIntegrity -SqlInstance 'SQL01').BackupFile | Should -Be 'E:\Backup' } It 'faellt auf das Sicherungsverzeichnis der Instanz zurueck' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { [pscustomobject]@{ BackupDirectory = 'F:\SqlBackup' } } Mock -ModuleName sqmSQLTool Restore-DbaDatabase { } (Test-sqmBackupIntegrity -SqlInstance 'SQL01').BackupFile | Should -Be 'F:\SqlBackup' } It 'bricht ab, wenn nirgends ein Pfad zu finden ist' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { throw 'kein Zugriff' } { Test-sqmBackupIntegrity -SqlInstance 'SQL01' } | Should -Throw '*BackupPath*' } } Describe 'Remove-sqmDatabaseFromAG' { BeforeEach { Mock -ModuleName sqmSQLTool Get-DbaAvailabilityGroup { [pscustomobject]@{ Name = 'AG1' } } Mock -ModuleName sqmSQLTool Get-DbaAgDatabase { if ("$Database" -eq 'Orphan') { return } [pscustomobject]@{ Name = if ($Database) { "$Database" } else { 'Sales' }; AvailabilityGroupName = 'AG1' } } Mock -ModuleName sqmSQLTool Get-DbaAgReplica { @( [pscustomobject]@{ Name = 'SQL01'; AvailabilityGroupName = 'AG1'; Role = 'Primary' } [pscustomobject]@{ Name = 'SQL02'; AvailabilityGroupName = 'AG1'; Role = 'Secondary' } [pscustomobject]@{ Name = 'SQL03'; AvailabilityGroupName = 'AG2'; Role = 'Secondary' } ) } Mock -ModuleName sqmSQLTool Remove-DbaAgDatabase { } Mock -ModuleName sqmSQLTool Remove-DbaDatabase { } } It 'nimmt die Datenbank aus der AG und loescht sie nur auf den Secondaries derselben AG' { $r = @(Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Database 'Sales' -Confirm:$false) ($r.Status) | Should -Be @('RemovedFromAG', 'DroppedOnSecondary') Should -Invoke -ModuleName sqmSQLTool Remove-DbaAgDatabase -Times 1 -Exactly Should -Invoke -ModuleName sqmSQLTool Remove-DbaDatabase -Times 1 -Exactly -ParameterFilter { "$SqlInstance" -eq 'SQL02' } Should -Invoke -ModuleName sqmSQLTool Remove-DbaDatabase -Times 0 -Exactly -ParameterFilter { "$SqlInstance" -in 'SQL01', 'SQL03' } } It 'meldet NotInAG fuer Datenbanken ausserhalb einer AG und aendert nichts' { $r = Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Database 'Orphan' -Confirm:$false $r.Status | Should -Be 'NotInAG' Should -Invoke -ModuleName sqmSQLTool Remove-DbaAgDatabase -Times 0 -Exactly Should -Invoke -ModuleName sqmSQLTool Remove-DbaDatabase -Times 0 -Exactly } It '-WhatIf entfernt und loescht nichts' { $r = @(Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Database 'Sales' -WhatIf) $r.Status | Should -Be 'RemoveSkipped' Should -Invoke -ModuleName sqmSQLTool Remove-DbaAgDatabase -Times 0 -Exactly Should -Invoke -ModuleName sqmSQLTool Remove-DbaDatabase -Times 0 -Exactly } It 'loescht die Secondary-Kopie nicht, wenn das Entfernen aus der AG scheitert' { Mock -ModuleName sqmSQLTool Remove-DbaAgDatabase { throw 'AG nicht erreichbar' } $r = @(Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Database 'Sales' -Confirm:$false) $r.Status | Should -Be 'RemoveFromAGFailed' Should -Invoke -ModuleName sqmSQLTool Remove-DbaDatabase -Times 0 -Exactly } It '-EnableException wirft statt Fehlerzeile' { Mock -ModuleName sqmSQLTool Remove-DbaAgDatabase { throw 'AG nicht erreichbar' } { Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Database 'Sales' -Confirm:$false -EnableException } | Should -Throw '*AG nicht erreichbar*' } It 'ohne -All und ohne -Database kommt ein GlobalError' { $r = Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Confirm:$false $r.Status | Should -Be 'GlobalError' $r.Message | Should -Match 'Weder -All noch -Database' } It 'meldet eine Warnung ohne Verfuegbarkeitsgruppen' { Mock -ModuleName sqmSQLTool Get-DbaAvailabilityGroup { } $r = Remove-sqmDatabaseFromAG -SqlInstance 'SQL01' -Database 'Sales' -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue $r | Should -BeNullOrEmpty "$w" | Should -Match 'Keine Verfuegbarkeitsgruppen' } } |