tests/Unit/Public/Get-sqmAgentJobScheduleReport.Tests.ps1
|
#Requires -Modules Pester <# .SYNOPSIS Unit Tests fuer die Status-Ableitung von Get-sqmAgentJobScheduleReport. Get-DbaAgentJob und Invoke-DbaQuery sind gemockt - kein Serverzugriff noetig. .DESCRIPTION Hintergrund: die Funktion hat drei nachweisbare Fehler gehabt, alle gegen eine echte SQL 2022-Instanz reproduziert, bevor sie behoben wurden: 1. Ein Job ohne jemals gelaufene Historie wurde als 'Failed' gemeldet statt als "noch nie gelaufen" - die T-SQL-CASE-Anweisung fiel bei run_status = NULL in den ELSE-Zweig. 2. MAX() auf die Ergebnisstrings 'Success'/'Failed' aggregierte alphabetisch statt zeitlich ('Success' > 'Failed'): ein Job, dessen LETZTER Lauf tatsaechlich fehlgeschlagen ist, aber irgendwann vorher einmal erfolgreich war, wurde als 'Success' gemeldet. 3. Ein Job mit zwei oder mehr Zeitplaenen wurde durch den JOIN auf sysjobschedules vervielfacht - PowerShell bekam pro Job mehrere Zeilen und behandelte Felder wie LastRunDate/LastRunStatus faelschlich als Arrays statt als Einzelwerte (sichtbar als "Never" bei LastExecution und "System.Object[]" bei LastStatus in Berichten). Der SQL-seitige Teil (ROW_NUMBER-basierte Ermittlung des tatsaechlich letzten Laufs, ein reprsentativer Zeitplan je Job statt Vervielfachung) wurde live gegen SQL Server 2022 verifiziert und laesst sich hier nicht sinnvoll nachbilden - Mocks liefern bereits das fertige Abfrageergebnis. Diese Tests sichern die PowerShell-seitige Interpretation: die Drei-Zustands-Ableitung (Success/Failed/Never Run) und die Zeitplan-Anzeige bei mehreren Zeitplaenen. #> BeforeAll { . "$PSScriptRoot\..\..\..\tests\TestHelpers.ps1" Import-sqmTestModule } AfterAll { if (Get-Module sqmSQLTool) { Remove-Module sqmSQLTool -Force } $env:MSSQLTOOLS_SKIP_AUTO_UPDATE = $null } Describe 'Get-sqmAgentJobScheduleReport - Statusableitung' { BeforeAll { $script:FakeJobs = @( [PSCustomObject]@{ Name = 'JobNieGelaufen'; IsEnabled = $true } [PSCustomObject]@{ Name = 'JobErfolgreich'; IsEnabled = $true } [PSCustomObject]@{ Name = 'JobLetzterLaufFehlgeschlagen'; IsEnabled = $true } [PSCustomObject]@{ Name = 'JobMitZweiZeitplaenen'; IsEnabled = $true } ) $script:FakeHistory = @( # 1) Noch nie gelaufen: kein LastRun-Datensatz, keine Zeitplaene. [PSCustomObject]@{ JobName = 'JobNieGelaufen'; IsEnabled = $true; ScheduleCount = 0 ScheduleName = $null; FrequencyType = $null; FrequencyInterval = $null SubdayType = $null; SubdayInterval = $null; ActiveStartTime = $null LastRunDate = $null; LastRunTime = $null; LastRunStatus = $null AvgDurationSeconds = $null; LastErrorMessage = $null } # 2) Ein Zeitplan, letzter (und einziger) Lauf erfolgreich. [PSCustomObject]@{ JobName = 'JobErfolgreich'; IsEnabled = $true; ScheduleCount = 1 ScheduleName = 'Taeglich_0200'; FrequencyType = 4; FrequencyInterval = 1 SubdayType = 0; SubdayInterval = 0; ActiveStartTime = 20000 LastRunDate = 20260729; LastRunTime = 70000; LastRunStatus = 'Success' AvgDurationSeconds = 12.5; LastErrorMessage = $null } # 3) Simuliert das Ergebnis NACH dem ROW_NUMBER-Fix: der zurueckgegebene Datensatz # entspricht bereits dem tatsaechlich letzten (fehlgeschlagenen) Lauf, obwohl ein # frueherer Lauf erfolgreich war (das war der eigentliche MAX()-Bug). [PSCustomObject]@{ JobName = 'JobLetzterLaufFehlgeschlagen'; IsEnabled = $true; ScheduleCount = 0 ScheduleName = $null; FrequencyType = $null; FrequencyInterval = $null SubdayType = $null; SubdayInterval = $null; ActiveStartTime = $null LastRunDate = 20260729; LastRunTime = 71204; LastRunStatus = 'Failed' AvgDurationSeconds = 5.0; LastErrorMessage = 'Absichtlicher Testfehler' } # 4) Zwei Zeitplaene - die Abfrage liefert nur noch EINEN reprsentativen Zeitplan samt # Gesamtzahl, keine Vervielfachung mehr. [PSCustomObject]@{ JobName = 'JobMitZweiZeitplaenen'; IsEnabled = $true; ScheduleCount = 2 ScheduleName = 'Taeglich_0200'; FrequencyType = 4; FrequencyInterval = 1 SubdayType = 0; SubdayInterval = 0; ActiveStartTime = 20000 LastRunDate = 20260729; LastRunTime = 70412; LastRunStatus = 'Success' AvgDurationSeconds = 8.0; LastErrorMessage = $null } ) } # Mocks und Funktionsaufruf muessen im SELBEN InModuleScope-Block stehen - ein Mock, der in # einem separaten InModuleScope-Aufruf (z.B. in einem eigenen BeforeEach) gesetzt wird, gilt # nicht automatisch in einem weiteren InModuleScope-Aufruf im It-Block. It 'meldet einen Job ohne Historie als "Never Run", NICHT als "Failed"' { InModuleScope sqmSQLTool -Parameters @{ FakeJobs = $script:FakeJobs; FakeHistory = $script:FakeHistory } { param($FakeJobs, $FakeHistory) Mock Invoke-sqmLogging { } Mock Invoke-sqmOpenReport { } Mock Get-DbaAgentJob { $FakeJobs } Mock Invoke-DbaQuery { $FakeHistory } $data = Get-sqmAgentJobScheduleReport -SqlInstance 'SQL01' -OutputPath $TestDrive -EnableException -NoOpen $job = $data | Where-Object JobName -eq 'JobNieGelaufen' $job.LastStatus | Should -Be 'Never Run' $job.LastExecution | Should -Be 'Never' $job.Schedule | Should -Be 'No Schedule' } } It 'meldet einen erfolgreichen Job korrekt als "Success" mit lesbarem Zeitplan' { InModuleScope sqmSQLTool -Parameters @{ FakeJobs = $script:FakeJobs; FakeHistory = $script:FakeHistory } { param($FakeJobs, $FakeHistory) Mock Invoke-sqmLogging { } Mock Invoke-sqmOpenReport { } Mock Get-DbaAgentJob { $FakeJobs } Mock Invoke-DbaQuery { $FakeHistory } $data = Get-sqmAgentJobScheduleReport -SqlInstance 'SQL01' -OutputPath $TestDrive -EnableException -NoOpen $job = $data | Where-Object JobName -eq 'JobErfolgreich' $job.LastStatus | Should -Be 'Success' $job.LastExecution | Should -Be '2026-07-29 07:00:00' $job.Schedule | Should -Match 'Daily' } } It 'meldet den tatsaechlich letzten (fehlgeschlagenen) Lauf als "Failed" samt Fehlermeldung' { InModuleScope sqmSQLTool -Parameters @{ FakeJobs = $script:FakeJobs; FakeHistory = $script:FakeHistory } { param($FakeJobs, $FakeHistory) Mock Invoke-sqmLogging { } Mock Invoke-sqmOpenReport { } Mock Get-DbaAgentJob { $FakeJobs } Mock Invoke-DbaQuery { $FakeHistory } $data = Get-sqmAgentJobScheduleReport -SqlInstance 'SQL01' -OutputPath $TestDrive -EnableException -NoOpen $job = $data | Where-Object JobName -eq 'JobLetzterLaufFehlgeschlagen' $job.LastStatus | Should -Be 'Failed' $job.LastError | Should -Match 'Testfehler' } } It 'zeigt bei mehreren Zeitplaenen einen sauberen Skalarwert, nicht "System.Object[]", und nennt die Gesamtzahl' { InModuleScope sqmSQLTool -Parameters @{ FakeJobs = $script:FakeJobs; FakeHistory = $script:FakeHistory } { param($FakeJobs, $FakeHistory) Mock Invoke-sqmLogging { } Mock Invoke-sqmOpenReport { } Mock Get-DbaAgentJob { $FakeJobs } Mock Invoke-DbaQuery { $FakeHistory } $data = Get-sqmAgentJobScheduleReport -SqlInstance 'SQL01' -OutputPath $TestDrive -EnableException -NoOpen $job = $data | Where-Object JobName -eq 'JobMitZweiZeitplaenen' $job.LastStatus | Should -Be 'Success' $job.LastStatus | Should -BeOfType [string] $job.LastExecution | Should -Be '2026-07-29 07:04:12' $job.Schedule | Should -Match 'weitere' } } It 'zaehlt "Never Run" weder als Erfolg noch als Fehlschlag in der Zusammenfassung' { InModuleScope sqmSQLTool -Parameters @{ FakeJobs = $script:FakeJobs; FakeHistory = $script:FakeHistory } { param($FakeJobs, $FakeHistory) Mock Invoke-sqmLogging { } Mock Invoke-sqmOpenReport { } Mock Get-DbaAgentJob { $FakeJobs } Mock Invoke-DbaQuery { $FakeHistory } $data = Get-sqmAgentJobScheduleReport -SqlInstance 'SQL01' -OutputPath $TestDrive -EnableException -NoOpen $neverRunJobs = @($data | Where-Object LastStatus -eq 'Never Run') $neverRunJobs.Count | Should -BeGreaterThan 0 foreach ($j in $neverRunJobs) { $j.LastStatus | Should -Not -Be 'Success' $j.LastStatus | Should -Not -Be 'Failed' } } } } |