Files
story/scripts/smoke-alert-delivery-test.ps1
T

110 lines
8.9 KiB
PowerShell
Raw Permalink Normal View History

2026-08-26 06:41:07 +08:00
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$root = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $root
$api = 'http://localhost:58080'
$receiver = 'http://localhost:59210'
$tokenUrl = 'http://localhost:58180/realms/voice-local/protocol/openid-connect/token'
$tenant = '10000000-0000-0000-0000-000000000010'
$key = 'alert-delivery-test-smoke-' + [Guid]::NewGuid().ToString('N')
$routeKey = 'alert-delivery-route-smoke-' + [Guid]::NewGuid().ToString('N')
$requestID = 'alert-delivery-test-smoke-' + [Guid]::NewGuid().ToString('N')
$temporaryRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('alert-receiver-conformance-' + [Guid]::NewGuid().ToString('N'))
$routesPath = Join-Path $temporaryRoot 'routes.json'
$testsPath = Join-Path $temporaryRoot 'tests.json'
$incidentPath = Join-Path $temporaryRoot 'incident.json'
$capturePath = Join-Path $temporaryRoot 'receiver-capture.json'
$reportPath = Join-Path $temporaryRoot 'receiver-report.json'
$test = $null
$route = $null
$createdRoute = $false
try {
New-Item -ItemType Directory -Path $temporaryRoot | Out-Null
$token = (Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @{
grant_type = 'password'; client_id = 'voice-platform-web'; username = 'owner'; password = 'LocalOnly123!'; scope = 'openid'
}).access_token
$headers = @{Authorization = "Bearer $token"; 'X-Tenant-Id' = $tenant}
$routes = (Invoke-RestMethod -Method Get -Uri "$api/api/v1/ops/alert-routing-policies" -Headers $headers).data.items
$route = @($routes | Where-Object status -eq 'ACTIVE' | Select-Object -First 1)[0]
if ($null -eq $route) {
$routeBody = @{
category = 'TECHNICAL'; severity = 'HIGH'; receiver_group = 'local-platform-oncall'; response_minutes = 15
escalation_after_minutes = 30; escalation_receiver_group = 'local-incident-command'
runbook_ref = 'runbook://local/alert-delivery-test'; change_summary = '本地告警接收器联通烟测临时路由'
} | ConvertTo-Json -Compress
$routeResponse = Invoke-RestMethod -Method Post -Uri "$api/api/v1/ops/alert-routing-policies" -Headers ($headers + @{'Idempotency-Key' = $routeKey; 'X-Request-Id' = $requestID}) -ContentType 'application/json' -Body $routeBody
$route = $routeResponse.data
$createdRoute = $true
}
$createHeaders = $headers.Clone()
$createHeaders['Idempotency-Key'] = $key
$createHeaders['X-Request-Id'] = $requestID
$body = @{reason = '本地真实Worker、HMAC签名与接收器联通验证'} | ConvertTo-Json -Compress
$created = Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/ops/alert-routing-policies/$($route.id)/delivery-tests" -Headers $createHeaders -ContentType 'application/json' -Body $body
$test = ($created.Content | ConvertFrom-Json).data
if ($created.StatusCode -ne 201 -or $test.status -ne 'PENDING' -or -not $test.id -or -not $test.test_ref) { throw '联通测试创建结果不正确' }
$deadline = (Get-Date).AddSeconds(15)
do {
Start-Sleep -Milliseconds 400
$listed = (Invoke-RestMethod -Method Get -Uri "$api/api/v1/ops/alert-delivery-tests" -Headers $headers).data.items
$current = @($listed | Where-Object id -eq $test.id | Select-Object -First 1)[0]
} while (($null -eq $current -or @('PENDING','PROCESSING') -contains $current.status) -and (Get-Date) -lt $deadline)
if ($null -eq $current -or $current.status -ne 'DELIVERED' -or $current.attempt_count -lt 1) { throw "Worker未完成联通测试投递,当前状态=$($current.status)" }
$received = (Invoke-RestMethod -Method Get -Uri "$receiver/events?test_ref=$($test.test_ref)").items
if (@($received).Count -ne 1 -or $received[0].event_type -ne 'alert.webhook_test_requested' -or $received[0].data.action -ne 'TEST') { throw '验签接收器未记录唯一联通测试事件' }
$incidentCount = (docker compose exec -T postgres psql -U voice -d voice_platform -tAc "SELECT count(*) FROM alert_incident WHERE title='告警接收器联通测试' OR summary LIKE '这是一条由运营工作台发起的受控测试通知%';").Trim()
if ($incidentCount -ne '0') { throw '联通测试错误地创建了正式告警事件' }
$replayed = Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/ops/alert-routing-policies/$($route.id)/delivery-tests" -Headers $createHeaders -ContentType 'application/json' -Body $body
if ($replayed.StatusCode -ne 200 -or $replayed.Headers.'Idempotency-Replayed' -ne 'true' -or ($replayed.Content | ConvertFrom-Json).data.id -ne $test.id) { throw '联通测试幂等重放不正确' }
$now = [DateTime]::UtcNow
$mockIncidentId = [Guid]::NewGuid().ToString()
$mockReceivedAt = $now.AddMinutes(-2)
$mockAcknowledgedAt = $now.AddMinutes(-1)
$buildDigest = (docker image inspect data-terminal-cloud-api --format '{{.Id}}').Trim()
$jsonOptions = @{Depth = 12; Compress = $false}
[System.IO.File]::WriteAllText($routesPath, (@{items = @($route)} | ConvertTo-Json @jsonOptions), [System.Text.UTF8Encoding]::new($false))
[System.IO.File]::WriteAllText($testsPath, (@{items = @($current)} | ConvertTo-Json @jsonOptions), [System.Text.UTF8Encoding]::new($false))
$incident = @{
id = $mockIncidentId; status = 'RESOLVED'; receiver_group = $route.receiver_group
first_detected_at = $now.AddMinutes(-3).ToString('o'); resolved_at = $now.ToString('o')
actions = @(@{action_type = 'ACKNOWLEDGED'; created_at = $mockAcknowledgedAt.ToString('o')})
}
[System.IO.File]::WriteAllText($incidentPath, ($incident | ConvertTo-Json @jsonOptions), [System.Text.UTF8Encoding]::new($false))
$capture = @{
schema_version = 'alert-receiver-capture-v1'; evidence_source = 'MOCK'; candidate_version = '0.63.0-rc.local'; build_digest = $buildDigest
receiver_system_ref = 'mock-alert-receiver:local'; collected_at = $now.ToString('o')
group_mappings = @(
@{receiver_group = $route.receiver_group; mapped_target_refs = @('local-sink:primary'); verified_at = $now.AddMinutes(-1).ToString('o')},
@{receiver_group = $route.escalation_receiver_group; mapped_target_refs = @('local-sink:escalation'); verified_at = $now.AddMinutes(-1).ToString('o')}
)
test_receipts = @(@{test_ref = $test.test_ref; event_id = $received[0].event_id; receiver_group = $received[0].data.receiver_group; delivery_count = 1; signature_verified = $true; received_at = ([DateTime]$received[0].received_at).ToUniversalTime().ToString('o')})
formal_drill = @{incident_id = $mockIncidentId; receiver_group = $route.receiver_group; delivery_count = 1; signature_verified = $true; controlled_drill = $true; received_at = $mockReceivedAt.ToString('o'); acknowledged_at = $mockAcknowledgedAt.ToString('o'); acknowledgement_ref = 'mock-ticket:local-only'}
}
[System.IO.File]::WriteAllText($capturePath, ($capture | ConvertTo-Json @jsonOptions), [System.Text.UTF8Encoding]::new($false))
$nativePreference = $PSNativeCommandUseErrorActionPreference
$PSNativeCommandUseErrorActionPreference = $false
go run ./cmd/alert-receiver-conformance -routes $routesPath -tests $testsPath -incident $incidentPath -receiver $capturePath -candidate-version '0.63.0-rc.local' -build-digest $buildDigest -output $reportPath
$conformanceExit = $LASTEXITCODE
$PSNativeCommandUseErrorActionPreference = $nativePreference
$report = Get-Content -Raw -LiteralPath $reportPath | ConvertFrom-Json
if ($conformanceExit -ne 0) { throw "接收器一致性校验失败:$((@($report.checks | Where-Object status -eq 'FAIL').code) -join ',');coverage=$($report.route_coverage | ConvertTo-Json -Compress);cloud=$($current.id)/$($current.test_ref);receipt=$($received[0].event_id)/$($received[0].test_ref)" }
if ($report.result -ne 'PASS' -or $report.evidence_scope -ne 'MOCK_RECEIVER' -or -not $report.external_validation_required -or $report.active_route_count -ne 1 -or -not $report.formal_drill_verified) { throw '本地接收器一致性报告未保持模拟证据边界' }
Write-Host "告警接收器联通烟测通过:路由=$($route.id),任务=$($test.id),真实Worker投递、HMAC验签、唯一回执和映射一致;报告范围=MOCK_RECEIVER,真实网关门禁仍待验。"
} finally {
if ($null -ne $test) {
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -c "BEGIN; DELETE FROM audit_event WHERE request_id='$requestID'; DELETE FROM idempotency_record WHERE idempotency_key IN ('$key','$routeKey'); DELETE FROM outbox_event WHERE public_id='$($test.id)'::uuid; COMMIT;" | Out-Null
}
if ($createdRoute -and $null -ne $route) {
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -c "DELETE FROM alert_routing_policy WHERE public_id='$($route.id)'::uuid;" | Out-Null
}
if (Test-Path -LiteralPath $temporaryRoot) { Remove-Item -LiteralPath $temporaryRoot -Recurse -Force }
if ($null -ne $test -or $createdRoute) { Write-Host '已清理联通烟测的临时路由、审计、幂等和Outbox记录;本地接收器只保留进程内验签样本。' }
}