$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 } }