Files
Backup-Dashboard/collectors/wsb/tests/MockWsb.ps1
T
kyleandClaude Opus 5.5 c5fe69a813 Add a Pester test suite for the collectors
84 tests in collectors/tests, run with Invoke-CollectorTests.ps1 (~15 s,
PowerShell 7.2+ and Pester 5.5+):

- Module.Tests: the shared module (property lookup, time format,
  config/lookback, sent-run state and pruning, dry runs, exit codes,
  401 detection)
- Veeam/Mars/Wsb/Vb365.Tests: each collector run in its own process
  against its mock and a stand-in ingest API, asserting the behaviours
  confirmed on real servers: VBR 13 live schedule, copy jobs, MARS UTC
  times and ErrorCode 0, WSB culture-formatted times and nested VMs,
  VB365 organization mapping, log prefixes and retries, state and
  resending, rejected keys
- Installers.Tests: config, files and scheduled task from each
  installer, with stand-ins for icacls / Task Scheduler / the key prompt

Collectors run under the test runner's PowerShell, or another shell via
-CollectorShell (e.g. powershell.exe, to test under Windows PowerShell
5.1 like the MARS, WSB and VB365 servers). The mocks take their "now"
from MOCK_NOW so expectations can't drift with the clock; the WSB mock
gains -RunningJobFinished for the running-to-finished case.

Each of 12 past bugs, put back one at a time, fails the test for its
behaviour (and the runner exits 1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 13:36:27 -06:00

105 lines
5.4 KiB
PowerShell

# Stand-ins for the WindowsServerBackup cmdlets. Object shapes follow Get-WsbCollectorDiagnostics.ps1
# output from a real Hyper-V host (Server 2019): job times are culture-formatted local-time strings
# to the minute ("10/6/2026 9:00 PM" on en-US), HRESULTs are strings, and a Hyper-V backup has one
# VirtualMachineList item whose SubItemList holds the VMs. When nothing is running, Get-WBJob
# returns an empty job (JobType None).
#
# . ./tests/MockWsb.ps1 # nothing running
# . ./tests/MockWsb.ps1 -WithRunningJob # a backup in progress
# . ./tests/MockWsb.ps1 -RunningJobFinished # that same backup, now finished
# ./Send-WsbBackupStatus.ps1 -ConfigPath ./my-test-config.json -DryRun
param([switch]$WithRunningJob, [switch]$RunningJobFinished)
# Fixed to the top of the hour so repeated runs see the same jobs
# MOCK_NOW (ISO 8601, set by the test suite) pins the time everything is relative to
$now = if ($env:MOCK_NOW) { [datetime]$env:MOCK_NOW } else { Get-Date -Minute 0 -Second 0 -Millisecond 0 }
# Same format as the real cmdlet: short date + short time in the current culture
function Format-WbTime($Value) {
if ($null -eq $Value) { return '' }
return ([datetime]$Value).ToString('g', [Globalization.CultureInfo]::CurrentCulture)
}
function New-MockItem($Name, $Type = 'Volume', [long]$Bytes = 0, [string]$HResult = '0', $ErrorDescription = '', $SubItems = $null) {
$failedSubs = @($SubItems | Where-Object { $_ -and $_.HResult -ne '0' }).Count
[pscustomobject]@{
Name = $Name
Type = $Type
State = $(if ($HResult -ne '0') { 'Failed' } else { 'Completed' })
HResult = $HResult
DetailedHResult = '0'
ErrorDescription = $ErrorDescription
BytesProcessed = $Bytes
TotalBytes = $Bytes
CurrentItem = $null
SubItemProcessed = [long]@($SubItems | Where-Object { $_ }).Count
SubItemFailed = [long]$failedSubs
TotalSubItem = [long]@($SubItems | Where-Object { $_ }).Count
SubItemList = $SubItems
}
}
function New-VmList([long]$VmBytes, [string]$VmHResult = '0', $VmError = '') {
$vms = @(
(New-MockItem 'AAOG-EDM-AD1' 'Component' $VmBytes $VmHResult $VmError),
(New-MockItem 'Host Component' 'Component' 447729)
)
New-MockItem 'VirtualMachineList' 'VirtualmachineList' ($VmBytes + 447729) -SubItems $vms
}
function New-MockJob($Type, $Start, $End, $State, [string]$HResult = '0', [string]$DetailedHResult = '0', $ErrorDescription = '', $Items = $null) {
[pscustomobject]@{
JobType = $Type
StartTime = Format-WbTime $Start
EndTime = Format-WbTime $End
JobState = $State
CurrentOperation = ''
HResult = $HResult
DetailedHResult = $DetailedHResult
ErrorDescription = $ErrorDescription
JobItems = $Items
VersionId = $(if ($End) { $Start.ToUniversalTime().ToString('MM/dd/yyyy-HH:mm', [Globalization.CultureInfo]::InvariantCulture) } else { '' })
SuccessLogPath = 'C:\Windows\Logs\WindowsServerBackup\Backup.log'
# Populated even for successful backups, as on the real server
FailureLogPath = 'C:\Windows\Logs\WindowsServerBackup\Backup_Error.log'
}
}
$global:MockWsbRunning = if ($WithRunningJob) {
@(New-MockJob 'Backup' $now.AddMinutes(-10) $null 'Running' -Items @(New-VmList 10GB))
} else {
@(New-MockJob 'None' $null $null 'Unknown')
}
$global:MockWsbPrevious = @(
(New-MockJob 'Backup' $now.AddHours(-12) $now.AddHours(-11.6) 'Completed' -Items @(New-VmList 124756706036)),
# Completed overall, but one VM failed
(New-MockJob 'Backup' $now.AddHours(-36) $now.AddHours(-35.6) 'Completed' '0' '0' '' @(
New-VmList 0 '-2147024891' 'Access is denied.')),
# Failed outright: backup target not found (0x80042302)
(New-MockJob 'Backup' $now.AddHours(-30) $now.AddHours(-29.9) 'Completed' '-2147212542' '0' `
'The backup storage location could not be found.'),
# A volume-based backup, for servers that aren't Hyper-V hosts
(New-MockJob 'Backup' $now.AddHours(-20) $now.AddHours(-19.5) 'Completed' -Items @(
(New-MockItem 'C:' 'Volume' 40GB), (New-MockItem 'D:' 'Volume' 200GB))),
(New-MockJob 'Recovery' $now.AddHours(-5) $now.AddHours(-4) 'Completed'), # a restore: must be ignored
(New-MockJob 'Backup' $now.AddDays(-5) $now.AddDays(-5).AddHours(1) 'Completed') # outside 48h lookback
)
if ($RunningJobFinished) {
$global:MockWsbPrevious += New-MockJob 'Backup' $now.AddMinutes(-10) $now.AddMinutes(-2) 'Completed' -Items @(New-VmList 10GB)
}
$global:MockWsbSummary = [pscustomobject]@{
NextBackupTime = $now.Date.AddHours(21).AddDays($(if ($now.Hour -ge 21) { 1 } else { 0 }))
NumberOfVersions = 1
LastSuccessfulBackupTime = $now.AddHours(-12)
LastBackupResultHR = [uint32]0
CurrentOperationStatus = $(if ($WithRunningJob) { 'BackupInProgress' } else { 'NoOperationInProgress' })
}
# Same limit as the real cmdlet
function Get-WBJob { param([ValidateRange(1, 100)][int]$Previous) if ($PSBoundParameters.ContainsKey('Previous')) { $global:MockWsbPrevious } else { $global:MockWsbRunning } }
function Get-WBSummary { $global:MockWsbSummary }
function Get-WBPolicy { [pscustomobject]@{ PolicyName = 'Scheduled' } }
function Get-WBSchedule { param($Policy) @($global:MockWsbSummary.NextBackupTime) }