$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' $device = '10000000-0000-0000-0000-000000000511' $key = 'integration-issue-smoke-' + [Guid]::NewGuid().ToString('N') $requestPrefix = 'integration-issue-smoke-' + [Guid]::NewGuid().ToString('N') $issue = $null function Get-Token([string]$name) { (Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @{ grant_type = 'password'; client_id = 'voice-platform-web'; username = $name; password = 'LocalOnly123!'; scope = 'openid' }).access_token } function Invoke-IssuePatch([string]$id, [int]$version, [hashtable]$body, [string]$token, [string]$suffix) { Start-Sleep -Milliseconds 550 Invoke-WebRequest -UseBasicParsing -Method Patch -Uri "$api/api/v1/ops/integration-issues/$id" -Headers @{ Authorization = "Bearer $token"; 'X-Tenant-Id' = $tenant; 'If-Match' = '"v' + $version + '"'; 'X-Request-Id' = $requestPrefix + '-' + $suffix } -ContentType 'application/json' -Body ($body | ConvertTo-Json -Compress) } try { $owner = Get-Token 'owner' $outsider = Get-Token 'outsider' $baselineSummary = ((Invoke-WebRequest -UseBasicParsing -Uri "$api/api/v1/ops/integration-issues" -Headers @{Authorization = "Bearer $owner"; 'X-Tenant-Id' = $tenant}).Content | ConvertFrom-Json).data.summary $body = @{ phenomenon = '模拟设备已入房,但供应商任务未在预期时间进入活动状态' severity = 'HIGH' device_id = $device occurred_at = [DateTime]::UtcNow.AddMinutes(-1).ToString('o') request_id = $requestPrefix + '-source' responsible_party = 'Agent研发组' } | ConvertTo-Json -Compress $headers = @{Authorization = "Bearer $owner"; 'X-Tenant-Id' = $tenant; 'Idempotency-Key' = $key; 'X-Request-Id' = $requestPrefix + '-create'} $createdResponse = Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/ops/integration-issues" -Headers $headers -ContentType 'application/json' -Body $body $issue = ($createdResponse.Content | ConvertFrom-Json).data if ($createdResponse.StatusCode -ne 201 -or $createdResponse.Headers.ETag -ne '"v1"' -or $issue.status -ne 'OPEN' -or $issue.severity -ne 'HIGH') { throw '联调问题创建结果不正确' } $replayResponse = Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/ops/integration-issues" -Headers $headers -ContentType 'application/json' -Body $body if ($replayResponse.StatusCode -ne 200 -or $replayResponse.Headers.'Idempotency-Replayed' -ne 'true' -or ($replayResponse.Content | ConvertFrom-Json).data.id -ne $issue.id) { throw '联调问题幂等重放不正确' } $listData = ((Invoke-WebRequest -UseBasicParsing -Uri "$api/api/v1/ops/integration-issues?status=OPEN" -Headers @{Authorization = "Bearer $owner"; 'X-Tenant-Id' = $tenant}).Content | ConvertFrom-Json).data $listed = $listData.items if (@($listed | Where-Object {$_.id -eq $issue.id}).Count -ne 1) { throw '联调问题状态筛选未返回新记录' } if ($listData.summary.release_blocking -ne ($baselineSummary.release_blocking + 1) -or $listData.summary.release_gate_satisfied) { throw '高严重度问题未进入发布阻断汇总' } try { Invoke-WebRequest -UseBasicParsing -Uri "$api/api/v1/ops/integration-issues" -Headers @{Authorization = "Bearer $outsider"; 'X-Tenant-Id' = $tenant} | Out-Null throw '跨客户组织读取不应成功' } catch { if ($null -eq $_.Exception.Response -or [int]$_.Exception.Response.StatusCode -ne 403) { throw } } $taken = (Invoke-IssuePatch $issue.id 1 @{status='IN_PROGRESS';responsible_party='Agent研发组'} $owner 'take').Content | ConvertFrom-Json $resolved = (Invoke-IssuePatch $issue.id 2 @{status='RESOLVED';resolution_result='修复供应商回调路由并重启模拟任务';root_cause='回调路由误用了旧供应商事件类型';fixed_version='0.49.0-rc.1+build.36'} $owner 'resolve').Content | ConvertFrom-Json $verified = (Invoke-IssuePatch $issue.id 3 @{status='VERIFIED';regression_conclusion='连续三次模拟通话均进入ACTIVE且无遗留任务';regression_evidence=($requestPrefix + '-regression;脱敏诊断包 artifact-36;重复3次')} $owner 'verify').Content | ConvertFrom-Json $closed = (Invoke-IssuePatch $issue.id 4 @{status='CLOSED'} $owner 'close').Content | ConvertFrom-Json if ($taken.data.version -ne 2 -or $resolved.data.version -ne 3 -or $verified.data.version -ne 4 -or $closed.data.version -ne 5 -or $closed.data.status -ne 'CLOSED' -or -not $verified.data.regression_evidence) { throw '联调问题闭环状态或版本不正确' } Start-Sleep -Milliseconds 550 $closedSummary = ((Invoke-WebRequest -UseBasicParsing -Uri "$api/api/v1/ops/integration-issues" -Headers @{Authorization = "Bearer $owner"; 'X-Tenant-Id' = $tenant}).Content | ConvertFrom-Json).data.summary if ($closedSummary.release_blocking -ne $baselineSummary.release_blocking -or $closedSummary.release_gate_satisfied -ne $baselineSummary.release_gate_satisfied) { throw '关闭高严重度问题后发布阻断汇总未恢复' } try { Invoke-IssuePatch $issue.id 1 @{status='IN_PROGRESS'} $owner 'stale' | Out-Null throw '旧ETag更新不应成功' } catch { if ($null -eq $_.Exception.Response -or [int]$_.Exception.Response.StatusCode -ne 412) { throw } } Write-Host '联调问题清单烟测通过:严重度阻断、根因、修复版本、回归证据、闭环汇总、租户隔离和旧ETag拒绝均正确。' } finally { if ($null -ne $issue -and $issue.id -match '^[0-9a-fA-F-]{36}$') { docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -c "BEGIN; CREATE TEMP TABLE issue_cleanup AS SELECT id FROM integration_issue WHERE public_id='$($issue.id)'::uuid; DELETE FROM audit_event WHERE request_id LIKE '$requestPrefix%' OR (resource_type='integration_issue' AND resource_id IN(SELECT id FROM issue_cleanup)); DELETE FROM integration_issue WHERE id IN(SELECT id FROM issue_cleanup); DELETE FROM idempotency_record WHERE idempotency_key='$key'; COMMIT;" | Out-Null } Write-Host '已清理联调问题烟测记录、审计和幂等数据。' }