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

110 lines
8.9 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
$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记录;本地接收器只保留进程内验签样本。' }
}