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]>
80 lines
4.7 KiB
PowerShell
80 lines
4.7 KiB
PowerShell
# Stand-ins for the Veeam Backup for Microsoft 365 cmdlets. Object shapes follow
|
|
# Get-Vb365CollectorDiagnostics.ps1 output from a real VB365 8.4 server: session and NextRun times
|
|
# are local, Statistics.TransferredData is a string ("187.2 MB"), log items carry their status as a
|
|
# Title prefix ("[Warning] ..."), and retries are separate sessions whose MainSessionId points at
|
|
# the session they retry (which has JobWillBeRetried = True).
|
|
#
|
|
# . ./tests/MockVb365.ps1
|
|
# ./Send-Vb365BackupStatus.ps1 -ConfigPath ./my-test-config.json -DryRun
|
|
|
|
# Fixed to the top of the hour, and IDs derived from names/times, so repeated runs see the same data
|
|
# 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 }
|
|
function New-StableGuid([string]$Seed) {
|
|
[guid]::new([Security.Cryptography.MD5]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($Seed)))
|
|
}
|
|
|
|
function New-MockOrg($Name) { [pscustomobject]@{ Name = $Name; OfficeName = $Name; Type = 'Office365'; IsBackedUp = $true } }
|
|
$acme = New-MockOrg 'acme.onmicrosoft.com'
|
|
$blue = New-MockOrg 'bluewater.onmicrosoft.com'
|
|
|
|
function New-MockJob($Name, $Org, $NextRun, [bool]$Enabled = $true) {
|
|
[pscustomobject]@{
|
|
Id = New-StableGuid $Name; Name = $Name; Organization = $Org; JobType = 'Backup'
|
|
IsEnabled = $Enabled; NextRun = $NextRun; LastStatus = 'Success'
|
|
LastRun = $(if ($NextRun) { $NextRun.AddDays(-1).ToUniversalTime() }) # UTC, as on the real server
|
|
}
|
|
}
|
|
|
|
function New-LogItem($Status, $Text, $At) {
|
|
[pscustomobject]@{ Id = [guid]::NewGuid(); USN = 1; Title = "[$Status] $Text"; CreationTime = $At; EndTime = $At }
|
|
}
|
|
|
|
function New-MockSession($Job, $Start, $End, $Status, $Transferred = '0 B', [long]$Objects = 0, $Log = @(),
|
|
$MainSessionId = [guid]::Empty, [bool]$WillRetry = $false) {
|
|
$logItems = @((New-LogItem 'Success' "Job started at: $($Start.ToString('G'))" $Start)) + @($Log)
|
|
[pscustomobject]@{
|
|
JobId = $Job.Id; JobName = $Job.Name; Status = $Status
|
|
Statistics = [pscustomobject]@{ ProcessingRate = '1.6 MB/s (4 items/s)'; TransferredData = $Transferred; ProcessedObjects = $Objects; Bottleneck = 'Target' }
|
|
JobSessionConfigType = 'Incremental'
|
|
Id = New-StableGuid "$($Job.Name)|$($Start.Ticks)"
|
|
CreationTime = $Start
|
|
EndTime = $(if ($End) { $End } else { [datetime]::MinValue })
|
|
Progress = 49; MainSessionId = $MainSessionId; JobWillBeRetried = $WillRetry
|
|
Log = $logItems
|
|
}
|
|
}
|
|
|
|
$global:MockVboJobs = @(
|
|
(New-MockJob 'Acme 365 Backup' $acme $now.Date.AddHours(17).AddDays($(if ($now.Hour -ge 17) { 1 } else { 0 }))),
|
|
(New-MockJob 'Bluewater 365 Backup' $blue $now.Date.AddHours(17.75).AddDays($(if ($now.Hour -ge 17) { 1 } else { 0 }))),
|
|
(New-MockJob 'Bluewater Old Mailboxes' $blue $now.AddDays(1) $false) # disabled
|
|
)
|
|
$acmeJob, $blueJob, $oldJob = $global:MockVboJobs
|
|
|
|
$y = $now.Date.AddDays(-1) # yesterday
|
|
$blueWarn = New-MockSession $blueJob $y.AddHours(17.75) $y.AddHours(17.75).AddMinutes(3.5) 'Warning' '32.9 MB' 47 @(
|
|
(New-LogItem 'Success' 'Found 94 objects' $y.AddHours(17.75)),
|
|
# A success line mentioning "error" must not end up in the message
|
|
(New-LogItem 'Success' 'Processing site: https://bluewater.sharepoint.com/sites/error-reports' $y.AddHours(17.76)),
|
|
(New-LogItem 'Warning' 'Processing mailbox [email protected] finished with warning: Item "Q3.pst" exceeds the maximum size' $y.AddHours(17.78))
|
|
) -WillRetry $true
|
|
|
|
$global:MockVboSessions = @(
|
|
(New-MockSession $acmeJob $y.AddHours(17) $y.AddHours(17).AddMinutes(4.8) 'Success' '187.2 MB' 568 @(
|
|
(New-LogItem 'Success' 'Found 49 objects' $y.AddHours(17)), (New-LogItem 'Success' 'Connected to organization' $y.AddHours(17)))),
|
|
(New-MockSession $acmeJob $now.AddMinutes(-20) $null 'Running' '0 B' 12),
|
|
$blueWarn,
|
|
# Its retry 13 minutes later, which succeeded
|
|
(New-MockSession $blueJob $y.AddHours(17.75).AddMinutes(13.5) $y.AddHours(17.75).AddMinutes(16.8) 'Success' '0 B' 0 -MainSessionId $blueWarn.Id),
|
|
(New-MockSession $blueJob $now.AddHours(-30) $now.AddHours(-29.9) 'Failed' '0 B' 0 @(
|
|
(New-LogItem 'Failed' 'Failed to connect to organization: The remote server returned an error: (401) Unauthorized.' $now.AddHours(-30)))),
|
|
(New-MockSession $oldJob $now.AddDays(-5) $now.AddDays(-5).AddHours(1) 'Success') # outside 48h lookback
|
|
)
|
|
|
|
function Get-VBOJob { $global:MockVboJobs }
|
|
function Get-VBOJobSession { param($Job) $global:MockVboSessions | Where-Object { -not $Job -or $_.JobId -eq $Job.Id } }
|
|
function Get-VBOOrganization { $acme, $blue }
|
|
function Get-VBOCopyJob { } # none, as on the real server
|
|
function Get-VBOCopyJobSession { }
|