536 lines
19 KiB
PowerShell
536 lines
19 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Semi-automates the Android -> TDesktop offline channel media recovery check.
|
|
|
|
.DESCRIPTION
|
|
This helper covers the stable parts of the local validation loop:
|
|
preflight checks, fixture generation/push, PostgreSQL state snapshots, server
|
|
log checks, and pass/fail assertions. It intentionally does not drive the
|
|
Android media picker or Telegram Desktop UI; those remain manual steps because
|
|
their coordinates and cached state are device/client dependent.
|
|
|
|
Typical flow:
|
|
1. Run -Phase Prepare to generate and push fixtures to Android.
|
|
2. Close DebugBob/TDesktop, then run -Phase BeforeSend.
|
|
3. Send photo, video, and document from Android/Alice.
|
|
4. Run -Phase AfterSend.
|
|
5. Start DebugBob/TDesktop, open the channel, confirm media is visible.
|
|
6. Run -Phase AfterBobOpen.
|
|
|
|
Use -Phase All for the same flow with interactive pauses.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[ValidateSet("Preflight", "Prepare", "BeforeSend", "AfterSend", "AfterBobOpen", "All")]
|
|
[string]$Phase = "Preflight",
|
|
|
|
[long]$ChannelId = 24,
|
|
[long]$AndroidUserId = 1780269504,
|
|
[long]$BobUserId = 1780269505,
|
|
|
|
[string]$AndroidPackage = "org.telegram.messenger.beta",
|
|
[string]$DeviceSerial,
|
|
|
|
[string]$PostgresContainer = "telesrv-postgres",
|
|
[string]$Database = "telesrv",
|
|
[string]$DbUser = "telesrv",
|
|
|
|
[string]$ServerLogPath,
|
|
[string]$StatePath,
|
|
[string]$FixtureDir,
|
|
|
|
[string]$PhotoFixture,
|
|
[string]$VideoFixture,
|
|
[string]$DocumentFixture,
|
|
|
|
[string]$RemotePictureDir = "/sdcard/Pictures/telesrv",
|
|
[string]$RemoteMovieDir = "/sdcard/Movies/telesrv",
|
|
[string]$RemoteDocumentDir = "/sdcard/Download/telesrv",
|
|
|
|
[int]$ExpectedNewMessages = 3,
|
|
|
|
[switch]$AllowMissingVideo,
|
|
[switch]$AllowMissingGenericDocument,
|
|
[switch]$SkipAdb
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
|
|
if (-not $ServerLogPath) {
|
|
$ServerLogPath = Join-Path $RepoRoot "logs\app-version-observe-20260609-165537.err.log"
|
|
}
|
|
if (-not $StatePath) {
|
|
$StatePath = Join-Path $RepoRoot "logs\android-offline-media-recovery-state.json"
|
|
}
|
|
if (-not $FixtureDir) {
|
|
$FixtureDir = Join-Path $RepoRoot "logs\media-fixtures"
|
|
}
|
|
|
|
$Failures = New-Object System.Collections.Generic.List[string]
|
|
|
|
function Write-Step {
|
|
param([string]$Message)
|
|
Write-Host ""
|
|
Write-Host "== $Message =="
|
|
}
|
|
|
|
function Write-Ok {
|
|
param([string]$Message)
|
|
Write-Host "[ok] $Message"
|
|
}
|
|
|
|
function Write-Warn {
|
|
param([string]$Message)
|
|
Write-Host "[warn] $Message"
|
|
}
|
|
|
|
function Add-Failure {
|
|
param([string]$Message)
|
|
$script:Failures.Add($Message) | Out-Null
|
|
Write-Host "[fail] $Message"
|
|
}
|
|
|
|
function Assert-Check {
|
|
param(
|
|
[bool]$Condition,
|
|
[string]$Message
|
|
)
|
|
if ($Condition) {
|
|
Write-Ok $Message
|
|
} else {
|
|
Add-Failure $Message
|
|
}
|
|
}
|
|
|
|
function Invoke-External {
|
|
param(
|
|
[string]$FilePath,
|
|
[string[]]$Arguments,
|
|
[switch]$AllowFailure
|
|
)
|
|
$oldErrorActionPreference = $ErrorActionPreference
|
|
$ErrorActionPreference = "Continue"
|
|
try {
|
|
$output = & $FilePath @Arguments 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
} finally {
|
|
$ErrorActionPreference = $oldErrorActionPreference
|
|
}
|
|
$text = ($output | ForEach-Object { $_.ToString() }) -join "`n"
|
|
if ($exitCode -ne 0 -and -not $AllowFailure) {
|
|
throw "$FilePath $($Arguments -join ' ') failed with exit code ${exitCode}:`n$text"
|
|
}
|
|
[pscustomobject]@{
|
|
ExitCode = $exitCode
|
|
Output = $text
|
|
}
|
|
}
|
|
|
|
function Assert-Command {
|
|
param([string]$Name)
|
|
$cmd = Get-Command $Name -ErrorAction SilentlyContinue
|
|
Assert-Check ([bool]$cmd) "$Name is available"
|
|
return [bool]$cmd
|
|
}
|
|
|
|
function Get-AdbArgs {
|
|
param([string[]]$Arguments)
|
|
if ($DeviceSerial) {
|
|
return @("-s", $DeviceSerial) + $Arguments
|
|
}
|
|
return $Arguments
|
|
}
|
|
|
|
function Invoke-Adb {
|
|
param([string[]]$Arguments)
|
|
Invoke-External "adb" (Get-AdbArgs $Arguments)
|
|
}
|
|
|
|
function Invoke-PsqlRows {
|
|
param([string]$Sql)
|
|
$args = @(
|
|
"exec", $PostgresContainer,
|
|
"psql", "-U", $DbUser, "-d", $Database,
|
|
"-v", "ON_ERROR_STOP=1",
|
|
"-At", "-F", "|",
|
|
"-c", $Sql
|
|
)
|
|
$result = Invoke-External "docker" $args
|
|
if ([string]::IsNullOrWhiteSpace($result.Output)) {
|
|
return @()
|
|
}
|
|
return @($result.Output -split "`r?`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
}
|
|
|
|
function Get-LogLineCount {
|
|
if (-not (Test-Path -LiteralPath $ServerLogPath)) {
|
|
return 0
|
|
}
|
|
return @((Get-Content -LiteralPath $ServerLogPath)).Count
|
|
}
|
|
|
|
function Get-LogLinesSince {
|
|
param([int]$Skip)
|
|
if (-not (Test-Path -LiteralPath $ServerLogPath)) {
|
|
Add-Failure "server log exists at $ServerLogPath"
|
|
return @()
|
|
}
|
|
return @(Get-Content -LiteralPath $ServerLogPath | Select-Object -Skip $Skip)
|
|
}
|
|
|
|
function Get-DialogState {
|
|
param([long]$UserId)
|
|
$rows = @(Invoke-PsqlRows @"
|
|
SELECT top_message_id, read_inbox_max_id, unread_count
|
|
FROM channel_dialogs
|
|
WHERE channel_id = $ChannelId AND user_id = $UserId;
|
|
"@)
|
|
if ($rows.Count -ne 1) {
|
|
Add-Failure "channel_dialogs has one row for channel_id=$ChannelId user_id=$UserId"
|
|
return $null
|
|
}
|
|
$parts = $rows[0] -split "\|"
|
|
[pscustomobject]@{
|
|
UserId = $UserId
|
|
TopMessageId = [int]$parts[0]
|
|
ReadInboxMaxId = [int]$parts[1]
|
|
UnreadCount = [int]$parts[2]
|
|
}
|
|
}
|
|
|
|
function Get-ChannelPts {
|
|
$rows = @(Invoke-PsqlRows "SELECT COALESCE(MAX(pts), 0) FROM channel_update_events WHERE channel_id = $ChannelId;")
|
|
if ($rows.Count -eq 0) {
|
|
return 0
|
|
}
|
|
return [int]$rows[0]
|
|
}
|
|
|
|
function Get-NewMessages {
|
|
param([int]$AfterMessageId)
|
|
$rows = @(Invoke-PsqlRows @"
|
|
SELECT
|
|
id,
|
|
sender_user_id,
|
|
media->>'kind',
|
|
COALESCE(media->'document'->>'mime_type', ''),
|
|
COALESCE((
|
|
SELECT attr->>'file_name'
|
|
FROM jsonb_array_elements(COALESCE(media->'document'->'attributes', '[]'::jsonb)) attr
|
|
WHERE attr->>'kind' = 'filename'
|
|
LIMIT 1
|
|
), '')
|
|
FROM channel_messages
|
|
WHERE channel_id = $ChannelId AND id > $AfterMessageId
|
|
ORDER BY id;
|
|
"@)
|
|
$items = @()
|
|
foreach ($row in $rows) {
|
|
$parts = $row -split "\|", 5
|
|
$items += [pscustomobject]@{
|
|
Id = [int]$parts[0]
|
|
SenderUserId = [long]$parts[1]
|
|
Kind = $parts[2]
|
|
MimeType = $parts[3]
|
|
FileName = $parts[4]
|
|
}
|
|
}
|
|
return $items
|
|
}
|
|
|
|
function Get-NewEventSummary {
|
|
param([int]$AfterPts)
|
|
$rows = @(Invoke-PsqlRows @"
|
|
SELECT COUNT(*), COALESCE(MAX(pts), 0)
|
|
FROM channel_update_events
|
|
WHERE channel_id = $ChannelId
|
|
AND pts > $AfterPts
|
|
AND event_type = 'new_channel_message';
|
|
"@)
|
|
if ($rows.Count -eq 0) {
|
|
return [pscustomobject]@{ Count = 0; MaxPts = 0 }
|
|
}
|
|
$parts = $rows[0] -split "\|"
|
|
[pscustomobject]@{
|
|
Count = [int]$parts[0]
|
|
MaxPts = [int]$parts[1]
|
|
}
|
|
}
|
|
|
|
function Save-State {
|
|
param([pscustomobject]$State)
|
|
$dir = Split-Path -Parent $StatePath
|
|
if ($dir) {
|
|
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
}
|
|
$State | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $StatePath -Encoding UTF8
|
|
Write-Ok "state saved to $StatePath"
|
|
}
|
|
|
|
function Load-State {
|
|
if (-not (Test-Path -LiteralPath $StatePath)) {
|
|
throw "state file not found: $StatePath. Run -Phase BeforeSend first."
|
|
}
|
|
Get-Content -LiteralPath $StatePath -Raw | ConvertFrom-Json
|
|
}
|
|
|
|
function New-PhotoFixture {
|
|
param([string]$Path)
|
|
Add-Type -AssemblyName System.Drawing
|
|
$bitmap = New-Object System.Drawing.Bitmap 900, 520
|
|
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
$graphics.Clear([System.Drawing.Color]::FromArgb(34, 137, 116))
|
|
$fontLarge = New-Object System.Drawing.Font("Arial", 36, [System.Drawing.FontStyle]::Bold)
|
|
$fontSmall = New-Object System.Drawing.Font("Arial", 22, [System.Drawing.FontStyle]::Regular)
|
|
$brushWhite = [System.Drawing.Brushes]::White
|
|
$brushYellow = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(245, 184, 60))
|
|
$graphics.DrawString("telesrv offline photo", $fontLarge, $brushWhite, 55, 85)
|
|
$graphics.DrawString((Get-Date -Format "yyyyMMdd-HHmmss"), $fontSmall, $brushWhite, 58, 150)
|
|
$graphics.FillRectangle($brushYellow, 58, 300, 780, 42)
|
|
$bitmap.Save($Path, [System.Drawing.Imaging.ImageFormat]::Jpeg)
|
|
$graphics.Dispose()
|
|
$bitmap.Dispose()
|
|
}
|
|
|
|
function New-DocumentFixture {
|
|
param([string]$Path)
|
|
$content = @"
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<telesrv-offline-media-recovery generated_at="$(Get-Date -Format o)">
|
|
<purpose>Android to TDesktop offline media recovery validation</purpose>
|
|
</telesrv-offline-media-recovery>
|
|
"@
|
|
Set-Content -LiteralPath $Path -Value $content -Encoding UTF8
|
|
}
|
|
|
|
function New-VideoFixture {
|
|
param([string]$Path)
|
|
$ffmpeg = Get-Command ffmpeg -ErrorAction SilentlyContinue
|
|
if (-not $ffmpeg) {
|
|
Write-Warn "ffmpeg not found; video fixture was not generated"
|
|
return $false
|
|
}
|
|
Invoke-External "ffmpeg" @(
|
|
"-y",
|
|
"-f", "lavfi",
|
|
"-i", "testsrc=size=640x360:rate=30",
|
|
"-t", "1",
|
|
"-pix_fmt", "yuv420p",
|
|
$Path
|
|
) | Out-Null
|
|
return $true
|
|
}
|
|
|
|
function Ensure-Fixtures {
|
|
New-Item -ItemType Directory -Force -Path $FixtureDir | Out-Null
|
|
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
if (-not $PhotoFixture) {
|
|
$script:PhotoFixture = Join-Path $FixtureDir "telesrv-offline-photo-$stamp.jpg"
|
|
New-PhotoFixture $script:PhotoFixture
|
|
}
|
|
if (-not $DocumentFixture) {
|
|
$script:DocumentFixture = Join-Path $FixtureDir "telesrv-offline-doc-$stamp.xml"
|
|
New-DocumentFixture $script:DocumentFixture
|
|
}
|
|
if (-not $VideoFixture) {
|
|
$candidate = Join-Path $FixtureDir "telesrv-offline-video-$stamp.mp4"
|
|
if (New-VideoFixture $candidate) {
|
|
$script:VideoFixture = $candidate
|
|
}
|
|
}
|
|
Assert-Check (Test-Path -LiteralPath $PhotoFixture) "photo fixture exists: $PhotoFixture"
|
|
Assert-Check (Test-Path -LiteralPath $DocumentFixture) "document fixture exists: $DocumentFixture"
|
|
if (-not $AllowMissingVideo) {
|
|
Assert-Check ($VideoFixture -and (Test-Path -LiteralPath $VideoFixture)) "video fixture exists: $VideoFixture"
|
|
} elseif ($VideoFixture) {
|
|
Assert-Check (Test-Path -LiteralPath $VideoFixture) "video fixture exists: $VideoFixture"
|
|
}
|
|
}
|
|
|
|
function Push-Fixtures {
|
|
if ($SkipAdb) {
|
|
Write-Warn "adb push skipped"
|
|
return
|
|
}
|
|
Invoke-Adb @("shell", "mkdir", "-p", $RemotePictureDir, $RemoteMovieDir, $RemoteDocumentDir) | Out-Null
|
|
Invoke-Adb @("push", $PhotoFixture, "$RemotePictureDir/") | Out-Null
|
|
Invoke-Adb @("push", $DocumentFixture, "$RemoteDocumentDir/") | Out-Null
|
|
Invoke-Adb @("shell", "am", "broadcast", "-a", "android.intent.action.MEDIA_SCANNER_SCAN_FILE", "-d", "file://$RemotePictureDir/$(Split-Path -Leaf $PhotoFixture)") | Out-Null
|
|
Invoke-Adb @("shell", "am", "broadcast", "-a", "android.intent.action.MEDIA_SCANNER_SCAN_FILE", "-d", "file://$RemoteDocumentDir/$(Split-Path -Leaf $DocumentFixture)") | Out-Null
|
|
if ($VideoFixture -and (Test-Path -LiteralPath $VideoFixture)) {
|
|
Invoke-Adb @("push", $VideoFixture, "$RemoteMovieDir/") | Out-Null
|
|
Invoke-Adb @("shell", "am", "broadcast", "-a", "android.intent.action.MEDIA_SCANNER_SCAN_FILE", "-d", "file://$RemoteMovieDir/$(Split-Path -Leaf $VideoFixture)") | Out-Null
|
|
}
|
|
Write-Ok "fixtures pushed to Android media folders"
|
|
}
|
|
|
|
function Run-Preflight {
|
|
Write-Step "Preflight"
|
|
if (-not $SkipAdb) {
|
|
if (Assert-Command "adb") {
|
|
$devices = Invoke-Adb @("devices")
|
|
$deviceLines = @($devices.Output -split "`r?`n" | Where-Object { $_ -match "\tdevice$" })
|
|
Assert-Check ($deviceLines.Count -ge 1) "adb has at least one connected device"
|
|
Assert-Check (($deviceLines.Count -eq 1) -or [bool]$DeviceSerial) "adb selects a single device or -DeviceSerial is set"
|
|
if (($deviceLines.Count -eq 1) -or [bool]$DeviceSerial) {
|
|
$pkg = Invoke-Adb @("shell", "dumpsys", "package", $AndroidPackage)
|
|
Assert-Check ($pkg.Output -match "versionName=") "Android package $AndroidPackage is installed"
|
|
$model = (Invoke-Adb @("shell", "getprop", "ro.product.model")).Output.Trim()
|
|
$sdk = (Invoke-Adb @("shell", "getprop", "ro.build.version.sdk")).Output.Trim()
|
|
Write-Host "Android device: model=$model sdk=$sdk"
|
|
}
|
|
}
|
|
} else {
|
|
Write-Warn "adb checks skipped"
|
|
}
|
|
if (Assert-Command "docker") {
|
|
Invoke-PsqlRows "SELECT 1;" | Out-Null
|
|
Write-Ok "PostgreSQL is reachable through docker container $PostgresContainer"
|
|
}
|
|
Assert-Check (Test-Path -LiteralPath $ServerLogPath) "server log exists: $ServerLogPath"
|
|
}
|
|
|
|
function Run-Prepare {
|
|
Write-Step "Prepare fixtures"
|
|
Run-Preflight
|
|
Ensure-Fixtures
|
|
Push-Fixtures
|
|
Write-Host "Manual step: close DebugBob/TDesktop, then send these files from Android/Alice:"
|
|
Write-Host " Photo: $PhotoFixture"
|
|
if ($VideoFixture) {
|
|
Write-Host " Video: $VideoFixture"
|
|
}
|
|
Write-Host " Document: $DocumentFixture"
|
|
}
|
|
|
|
function Run-BeforeSend {
|
|
Write-Step "BeforeSend snapshot"
|
|
$bob = Get-DialogState $BobUserId
|
|
$alice = Get-DialogState $AndroidUserId
|
|
$pts = Get-ChannelPts
|
|
$lineCount = Get-LogLineCount
|
|
Assert-Check ($null -ne $bob) "Bob dialog state can be read"
|
|
Assert-Check ($null -ne $alice) "Android/Alice dialog state can be read"
|
|
if ($bob) {
|
|
Write-Host "Bob before send: top=$($bob.TopMessageId) read=$($bob.ReadInboxMaxId) unread=$($bob.UnreadCount)"
|
|
}
|
|
if ($alice) {
|
|
Write-Host "Alice before send: top=$($alice.TopMessageId) read=$($alice.ReadInboxMaxId) unread=$($alice.UnreadCount)"
|
|
}
|
|
Write-Host "Channel pts before send: $pts"
|
|
$state = [pscustomobject]@{
|
|
ChannelId = $ChannelId
|
|
AndroidUserId = $AndroidUserId
|
|
BobUserId = $BobUserId
|
|
BaselineTopMessageId = if ($bob) { $bob.TopMessageId } else { 0 }
|
|
BaselineBobReadInboxMaxId = if ($bob) { $bob.ReadInboxMaxId } else { 0 }
|
|
BaselineBobUnreadCount = if ($bob) { $bob.UnreadCount } else { 0 }
|
|
BaselineChannelPts = $pts
|
|
BaselineLogLineCount = $lineCount
|
|
ServerLogPath = $ServerLogPath
|
|
CreatedAt = (Get-Date -Format o)
|
|
PhotoFixture = $PhotoFixture
|
|
VideoFixture = $VideoFixture
|
|
DocumentFixture = $DocumentFixture
|
|
}
|
|
Save-State $state
|
|
}
|
|
|
|
function Run-AfterSend {
|
|
Write-Step "AfterSend assertions"
|
|
$state = Load-State
|
|
$bob = Get-DialogState $BobUserId
|
|
$messages = @(Get-NewMessages ([int]$state.BaselineTopMessageId))
|
|
$events = Get-NewEventSummary ([int]$state.BaselineChannelPts)
|
|
foreach ($message in $messages) {
|
|
Write-Host ("new message id={0} sender={1} kind={2} mime={3} file={4}" -f $message.Id, $message.SenderUserId, $message.Kind, $message.MimeType, $message.FileName)
|
|
}
|
|
if ($bob) {
|
|
Write-Host "Bob after send: top=$($bob.TopMessageId) read=$($bob.ReadInboxMaxId) unread=$($bob.UnreadCount)"
|
|
}
|
|
Write-Host "new channel events after baseline: count=$($events.Count) max_pts=$($events.MaxPts)"
|
|
Assert-Check ($messages.Count -ge $ExpectedNewMessages) "at least $ExpectedNewMessages new channel messages were written"
|
|
Assert-Check (@($messages | Where-Object { $_.SenderUserId -eq $AndroidUserId }).Count -ge $ExpectedNewMessages) "new channel messages are from Android/Alice"
|
|
Assert-Check (@($messages | Where-Object { $_.Kind -eq "photo" }).Count -ge 1) "new messages include uploaded photo"
|
|
Assert-Check (@($messages | Where-Object { $_.Kind -eq "document" }).Count -ge 1) "new messages include uploaded document"
|
|
if (-not $AllowMissingVideo) {
|
|
Assert-Check (@($messages | Where-Object { $_.MimeType -eq "video/mp4" }).Count -ge 1) "new messages include video/mp4 document"
|
|
}
|
|
if (-not $AllowMissingGenericDocument) {
|
|
Assert-Check (@($messages | Where-Object { $_.Kind -eq "document" -and $_.MimeType -ne "video/mp4" }).Count -ge 1) "new messages include a generic non-video document"
|
|
}
|
|
if ($bob) {
|
|
Assert-Check ($bob.TopMessageId -gt [int]$state.BaselineTopMessageId) "Bob top_message_id advanced while offline"
|
|
Assert-Check ($bob.ReadInboxMaxId -eq [int]$state.BaselineBobReadInboxMaxId) "Bob read_inbox_max_id did not advance before opening TDesktop"
|
|
Assert-Check ($bob.UnreadCount -ge $ExpectedNewMessages) "Bob unread_count reflects offline messages"
|
|
}
|
|
Assert-Check ($events.Count -ge $ExpectedNewMessages) "durable channel_update_events exist for new messages"
|
|
}
|
|
|
|
function Run-AfterBobOpen {
|
|
Write-Step "AfterBobOpen assertions"
|
|
$state = Load-State
|
|
$bob = Get-DialogState $BobUserId
|
|
if ($bob) {
|
|
Write-Host "Bob after open: top=$($bob.TopMessageId) read=$($bob.ReadInboxMaxId) unread=$($bob.UnreadCount)"
|
|
Assert-Check ($bob.ReadInboxMaxId -ge $bob.TopMessageId) "Bob read_inbox_max_id catches up to current top"
|
|
Assert-Check ($bob.UnreadCount -eq 0) "Bob unread_count is cleared after opening the channel"
|
|
}
|
|
$lines = @(Get-LogLinesSince ([int]$state.BaselineLogLineCount))
|
|
$bad = @($lines | Where-Object { $_ -match "Unhandled RPC|NOT_IMPLEMENTED|bad_msg" })
|
|
Assert-Check ($bad.Count -eq 0) "server log has no new Unhandled RPC / NOT_IMPLEMENTED / bad_msg entries"
|
|
$expectedTDesktop = @(
|
|
"messages.getHistory",
|
|
"messages.getPeerDialogs",
|
|
"upload.getFile",
|
|
"channels.readHistory",
|
|
"updates.getChannelDifference"
|
|
)
|
|
foreach ($method in $expectedTDesktop) {
|
|
$hits = @($lines | Where-Object { $_ -like "*$method*" -and $_ -like '*client_type": "tdesktop"*' })
|
|
Assert-Check ($hits.Count -ge 1) "TDesktop issued $method after Bob opened the channel"
|
|
}
|
|
}
|
|
|
|
function Finish-Run {
|
|
if ($Failures.Count -gt 0) {
|
|
Write-Host ""
|
|
Write-Host "Validation failed:"
|
|
foreach ($failure in $Failures) {
|
|
Write-Host " - $failure"
|
|
}
|
|
exit 1
|
|
}
|
|
Write-Host ""
|
|
Write-Host "Validation phase '$Phase' passed."
|
|
}
|
|
|
|
switch ($Phase) {
|
|
"Preflight" {
|
|
Run-Preflight
|
|
}
|
|
"Prepare" {
|
|
Run-Prepare
|
|
}
|
|
"BeforeSend" {
|
|
Run-BeforeSend
|
|
}
|
|
"AfterSend" {
|
|
Run-AfterSend
|
|
}
|
|
"AfterBobOpen" {
|
|
Run-AfterBobOpen
|
|
}
|
|
"All" {
|
|
Run-Prepare
|
|
Run-BeforeSend
|
|
Read-Host "Close DebugBob/TDesktop if needed, send photo/video/document from Android, then press Enter"
|
|
Run-AfterSend
|
|
Read-Host "Start DebugBob/TDesktop, open the channel, confirm media renders, then press Enter"
|
|
Run-AfterBobOpen
|
|
}
|
|
}
|
|
|
|
Finish-Run
|