$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$work = Join-Path $root ('temp\dispatch-flow-' + [guid]::NewGuid().ToString('N'))
$templateRoot = Join-Path $work 'templates'
$dbPath = Join-Path $work 'test.mdb'
$configPath = Join-Path $work 'test.ini'
$requestPath = Join-Path $work 'request.json'
$pickup = Join-Path $work 'pickup'
$scriptHost = Join-Path $env:SystemRoot 'SysWOW64\cscript.exe'
$powerShellX86 = Join-Path $env:SystemRoot 'SysWOW64\WindowsPowerShell\v1.0\powershell.exe'
function Call-X86([string]$File, [string[]]$Arguments) {
$result = & $powerShellX86 -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot $File) @Arguments
if ($LASTEXITCODE -ne 0) { throw "$File failed" }
return $result
}
function Count-Messages {
return @(Get-ChildItem -LiteralPath $pickup -Filter '*.eml' -File).Count
}
function Expect-Failure([string]$File, [string[]]$Arguments) {
$ErrorActionPreference = 'Continue'
$null = & $powerShellX86 -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot $File) @Arguments 2>$null
$failed = $LASTEXITCODE -ne 0
$ErrorActionPreference = 'Stop'
if (-not $failed) { throw "Expected $File to reject the action" }
}
try {
$null = New-Item -ItemType Directory -Path $templateRoot -Force
$null = New-Item -ItemType Directory -Path $pickup -Force
$example = Get-Content -LiteralPath (Join-Path $root 'config.example.ini') -Raw
$example.Replace('DB_PATH=data/MassMailer.mdb', ('DB_PATH=' + $dbPath)).Replace(
'TEMPLATE_ROOT=templates', ('TEMPLATE_ROOT=' + $templateRoot)).Replace(
'SMTP_TEST_TO=', 'SMTP_TEST_TO=owner@example.invalid').Replace(
'BUSINESS_POSTAL_ADDRESS=', 'BUSINESS_POSTAL_ADDRESS=123 Example Street, Test City, NY 10001') |
Set-Content -LiteralPath $configPath -Encoding UTF8
& $scriptHost //nologo (Join-Path $PSScriptRoot 'init-db.vbs') $configPath
if ($LASTEXITCODE -ne 0) { throw 'MDB setup failed' }
$null = Call-X86 'test-campaign-db.ps1' @('-ConfigPath',$configPath,'-Mode','Seed')
$templatePath = Join-Path $templateRoot 'offer.html'
[System.IO.File]::WriteAllText($templatePath, '
Postcard offer
',
[System.Text.Encoding]::UTF8)
$null = Call-X86 'import-template.ps1' @('-Path',$templatePath,'-ConfigPath',$configPath)
$request = @{ Name = 'Dispatch fixture'; Subject = 'Postcard advertising';
TemplateId = 1; ReviewToken = ''; TestReceived = $false }
$request | ConvertTo-Json -Compress | Set-Content -LiteralPath $requestPath -Encoding UTF8
$review = (Call-X86 'review-campaign.ps1' @('-RequestPath',$requestPath,'-ConfigPath',$configPath,
'-OutputRoot',(Join-Path $work 'previews'))) | ConvertFrom-Json
if ($review.Count -ne 3) { throw 'Expected three eligible contacts' }
$request.ReviewToken = $review.Token
$request | ConvertTo-Json -Compress | Set-Content -LiteralPath $requestPath -Encoding UTF8
$null = Call-X86 'send-campaign-test.ps1' @('-RequestPath',$requestPath,'-ConfigPath',$configPath,'-PickupDirectory',$pickup)
$request.TestReceived = $true
$request | ConvertTo-Json -Compress | Set-Content -LiteralPath $requestPath -Encoding UTF8
$null = Call-X86 'approve-campaign.ps1' @('-RequestPath',$requestPath,'-ConfigPath',$configPath)
$null = Get-ChildItem -LiteralPath $pickup -File | Remove-Item -Force
$null = Call-X86 'dispatch-control.ps1' @('-Action','Recover','-ConfigPath',$configPath)
$null = Call-X86 'dispatch-control.ps1' @('-Action','ReviewOptOut','-ConfigPath',$configPath)
$null = Call-X86 'dispatch-control.ps1' @('-Action','Resume','-CampaignId','1','-ConfigPath',$configPath)
$t0 = [DateTime]::UtcNow.AddSeconds(5)
Expect-Failure 'dispatch-once.ps1' @('-ConfigPath',$configPath,'-Send','-SessionPid','999999','-SessionToken','00000000000000000000000000000000')
$first = Call-X86 'dispatch-once.ps1' @('-ConfigPath',$configPath,'-PickupDirectory',$pickup,
'-TestNowUtc',$t0.ToString('o'))
if ($first -notmatch 'accepted' -or (Count-Messages) -ne 1) { throw 'First queued attempt failed' }
$settings = Get-Content -LiteralPath $configPath -Raw
$settings.Replace('QUEUE_INTERVAL=300', 'QUEUE_INTERVAL=600') |
Set-Content -LiteralPath $configPath -Encoding UTF8
$early = Call-X86 'dispatch-once.ps1' @('-ConfigPath',$configPath,'-PickupDirectory',$pickup,
'-TestNowUtc',$t0.AddSeconds(300).ToString('o'))
if ($early -notmatch 'Next attempt allowed' -or (Count-Messages) -ne 1) {
throw 'Longer interval did not protect the reserved slot'
}
$null = Call-X86 'test-campaign-db.ps1' @('-ConfigPath',$configPath,'-Mode','SuppressSecond')
$null = Call-X86 'dispatch-control.ps1' @('-Action','Recover','-ConfigPath',$configPath)
$closed = Call-X86 'dispatch-once.ps1' @('-ConfigPath',$configPath,'-PickupDirectory',$pickup,
'-TestNowUtc',$t0.AddSeconds(1200).ToString('o'))
if ($closed -notmatch 'paused' -or (Count-Messages) -ne 1) {
throw 'Recovery failed to pause dispatch after reopen'
}
$null = Call-X86 'dispatch-control.ps1' @('-Action','Resume','-CampaignId','1','-ConfigPath',$configPath)
$second = Call-X86 'dispatch-once.ps1' @('-ConfigPath',$configPath,'-PickupDirectory',$pickup,
'-TestNowUtc',$t0.AddSeconds(1200).ToString('o'),'-TestSimulateUnknownAfterSend')
if ($second -notmatch 'uncertain' -or (Count-Messages) -ne 2) {
throw 'Injected uncertain response did not pause after the second due attempt'
}
$sameSlot = Call-X86 'dispatch-once.ps1' @('-ConfigPath',$configPath,'-PickupDirectory',$pickup,
'-TestNowUtc',$t0.AddSeconds(1200).ToString('o'))
if ($sameSlot -notmatch 'paused' -or (Count-Messages) -ne 2) {
throw 'Uncertain outcome did not stop further sends'
}
Expect-Failure 'dispatch-control.ps1' @('-Action','Resume','-CampaignId','1','-ConfigPath',$configPath)
$status = (Call-X86 'dispatch-control.ps1' @('-Action','Status','-ConfigPath',$configPath)) | ConvertFrom-Json
if ($status.Counts.accepted -ne 1 -or $status.Counts.suppressed -ne 1 -or
$status.Counts.uncertain -ne 1) { throw 'Uncertain delivery counts are wrong' }
$null = Call-X86 'dispatch-control.ps1' @('-Action','ResolveAccepted','-DeliveryId','3','-ConfigPath',$configPath)
$status = (Call-X86 'dispatch-control.ps1' @('-Action','Status','-ConfigPath',$configPath)) | ConvertFrom-Json
if ($status.Counts.accepted -ne 2 -or $status.Counts.uncertain -ne 0) {
throw 'Manual uncertain reconciliation failed'
}
$reportFolder = Join-Path $work 'reports'
$null = Call-X86 'export-report.ps1' @('-CampaignId','1','-ConfigPath',$configPath,'-OutputRoot',$reportFolder)
$deliveryReport = @(Get-ChildItem -LiteralPath $reportFolder -Filter '*-deliveries.csv' -File)
$attemptReport = @(Get-ChildItem -LiteralPath $reportFolder -Filter '*-attempts.csv' -File)
if ($deliveryReport.Count -ne 1 -or $attemptReport.Count -ne 1) {
throw 'Campaign report files were not created'
}
$deliveryRows = @(Import-Csv -LiteralPath $deliveryReport[0].FullName)
$attemptRows = @(Import-Csv -LiteralPath $attemptReport[0].FullName)
if ($deliveryRows.Count -ne 3 -or $attemptRows.Count -ne 2 -or
@($deliveryRows | Where-Object Status -eq 'suppressed').Count -ne 1 -or
@($attemptRows | Where-Object Outcome -eq 'uncertain').Count -ne 1) {
throw 'Campaign report content is wrong'
}
$null = Call-X86 'dispatch-control.ps1' @('-Action','PauseAll','-ConfigPath',$configPath)
Write-Output 'Session guard, recovery, pacing, suppression, uncertainty, and reports passed'
}
finally {
$resolvedWork = [System.IO.Path]::GetFullPath($work)
$resolvedTemp = [System.IO.Path]::GetFullPath((Join-Path $root 'temp'))
if ($resolvedWork.StartsWith($resolvedTemp + [System.IO.Path]::DirectorySeparatorChar,
[StringComparison]::OrdinalIgnoreCase) -and (Test-Path -LiteralPath $resolvedWork)) {
Remove-Item -LiteralPath $resolvedWork -Recurse -Force
}
}