Files
story/scripts/smoke-context-preparation-failure.ps1

112 lines
7.0 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'
$tokenUrl = 'http://localhost:58180/realms/voice-local/protocol/openid-connect/token'
$tenant = '10000000-0000-0000-0000-000000000010'
$deviceId = '10000000-0000-0000-0000-000000000511'
$subjectInternalId = '10000000-0000-0000-0000-000000000401'
$deviceSecret = [Text.Encoding]::UTF8.GetBytes('local-simulated-device-secret-32-bytes-minimum')
$conversationId = $null
$originalProfileBase64 = $null
$idempotencyKey = 'context-preparation-failure-' + [Guid]::NewGuid().ToString('N')
function Scalar([string]$Sql) {
$value = docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -t -A -c $Sql
if ($LASTEXITCODE -ne 0) { throw '数据库查询失败。' }
return ($value | Out-String).Trim()
}
function Token() {
return (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
}
function SignedCreate([string]$Body, [string]$Key) {
$bytes = [Text.Encoding]::UTF8.GetBytes($Body)
$digest = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant()
$timestamp = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
$nonce = [Guid]::NewGuid().ToString('N')
$canonical = "POST`n/device/v2/conversations`n$timestamp`n$nonce`n$digest"
$hmac = [Security.Cryptography.HMACSHA256]::new($deviceSecret)
try {
$signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonical))).TrimEnd('=').Replace('+', '-').Replace('/', '_')
} finally {
$hmac.Dispose()
}
return Invoke-WebRequest -UseBasicParsing -SkipHttpErrorCheck -Method Post -Uri "$api/device/v2/conversations" `
-ContentType 'application/json' -Body $Body -Headers @{
'X-Device-Id' = $deviceId; 'X-Device-Timestamp' = $timestamp; 'X-Device-Nonce' = $nonce
'X-Device-Content-SHA256' = $digest; 'X-Device-Signature' = $signature; 'Idempotency-Key' = $Key
}
}
try {
if ((Scalar "SELECT count(*) FROM conversation c JOIN device d ON d.id=c.device_id WHERE d.public_id='$deviceId'::uuid AND c.status NOT IN('COMPLETED','FAILED','EXPIRED')") -ne '0') {
throw '测试设备存在未结束通话,拒绝改动背景资料。'
}
$originalProfileBase64 = Scalar "SELECT encode(convert_to(profile_data::text,'UTF8'),'base64') FROM subject_profile WHERE id='$subjectInternalId'::uuid"
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q -c `
"UPDATE subject_profile SET profile_data=jsonb_set(profile_data,'{background}','42'::jsonb,true) WHERE id='$subjectInternalId'::uuid;" | Out-Null
$body = @{
device_time = [DateTime]::UtcNow.ToString('o'); monotonic_ms = 42001
capabilities = @{ rtc = $true; audio_tracks = @('near', 'far'); upload_resume = $true }
network = @{ type = 'wifi'; rssi = -50 }; requested_goal = 'open_conversation'
} | ConvertTo-Json -Compress -Depth 6
$first = SignedCreate $body $idempotencyKey
if ($first.StatusCode -ne 422) { throw "首次请求未返回422:$($first.StatusCode) $($first.Content)" }
$firstError = ($first.Content | ConvertFrom-Json).error
$conversationId = [string]$firstError.details.conversation_id
if ($firstError.code -ne 'AGENT_CONTEXT_PREPARATION_FAILED' -or $firstError.details.status -ne 'FAILED' -or $firstError.details.failure_code -ne 'context_preparation_failed') {
throw "设备失败响应不正确:$($first.Content)"
}
$second = SignedCreate $body $idempotencyKey
if ($second.StatusCode -ne 422 -or $second.Headers['Idempotency-Replayed'] -ne 'true') { throw '失败结果没有按同一幂等键重放。' }
$secondError = ($second.Content | ConvertFrom-Json).error
if ($secondError.details.conversation_id -ne $conversationId) { throw '重放返回了不同通话编号。' }
$databaseState = Scalar "SELECT c.status||'|'||c.failure_code||'|'||xs.preparation_status||'|'||xs.preparation_failure_code||'|'||(NOT EXISTS(SELECT 1 FROM rtc_task rt WHERE rt.conversation_id=c.id))::text||'|'||(SELECT count(*) FROM conversation_state_history h WHERE h.conversation_id=c.id) FROM conversation c JOIN context_snapshot xs ON xs.id=c.context_snapshot_id WHERE c.public_id='$conversationId'::uuid"
if ($databaseState -ne 'FAILED|context_preparation_failed|FAILED|context_preparation_failed|true|2') { throw "权威失败状态不正确:$databaseState" }
$accessToken = Token
$detail = (Invoke-RestMethod -Uri "$api/api/v1/conversations/$conversationId" -Headers @{ Authorization = "Bearer $accessToken" }).data
if ($detail.status -ne 'FAILED' -or $detail.failure_code -ne 'context_preparation_failed' -or $detail.snapshots.context_preparation_status -ne 'FAILED' -or $null -ne $detail.rtc) {
throw '家庭通话详情与设备失败结果不一致。'
}
$diagnostic = (Invoke-RestMethod -Uri "$api/api/v1/ops/diagnostics/search?field=conversation_id&value=$conversationId" -Headers @{ Authorization = "Bearer $accessToken"; 'X-Tenant-Id' = $tenant }).data.items
if (@($diagnostic).Count -ne 1 -or $diagnostic[0].conversation_failure_code -ne 'context_preparation_failed' -or $diagnostic[0].provider_task_status) {
throw '运营诊断结果与设备失败结果不一致。'
}
Write-Host '背景资料准备失败烟测通过:设备422及幂等重放、FAILED权威记录、无RTC房间、家庭详情和运营诊断结果一致。'
} finally {
if ($originalProfileBase64) {
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q -c `
"UPDATE subject_profile SET profile_data=convert_from(decode('$originalProfileBase64','base64'),'UTF8')::jsonb WHERE id='$subjectInternalId'::uuid;" | Out-Null
}
if ($conversationId -match '^[0-9a-fA-F-]{36}$') {
$cleanup = @"
BEGIN;
CREATE TEMP TABLE context_failure_cleanup AS
SELECT id,context_snapshot_id,consent_snapshot_id FROM conversation WHERE public_id='$conversationId'::uuid;
DELETE FROM conversation_state_history WHERE conversation_id IN(SELECT id FROM context_failure_cleanup);
DELETE FROM audit_event WHERE resource_id IN(SELECT id FROM context_failure_cleanup);
DELETE FROM idempotency_record WHERE resource_id IN(SELECT id FROM context_failure_cleanup);
DELETE FROM conversation WHERE id IN(SELECT id FROM context_failure_cleanup);
DELETE FROM context_snapshot WHERE id IN(SELECT context_snapshot_id FROM context_failure_cleanup);
DELETE FROM consent_snapshot WHERE id IN(SELECT consent_snapshot_id FROM context_failure_cleanup);
COMMIT;
"@
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q -c $cleanup | Out-Null
}
Write-Host '已恢复合成长者背景并清理烟测通话、快照、审计和幂等记录。'
}