Files
story/scripts/smoke-device-commands.ps1
T

9 lines
4.1 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
$projectRoot=Split-Path -Parent $PSScriptRoot;Set-Location -LiteralPath $projectRoot
$api='http://localhost:58080';$tokenUrl='http://localhost:58180/realms/voice-local/protocol/openid-connect/token'
$deviceId='10000000-0000-0000-0000-000000000511';$secret=[Text.Encoding]::UTF8.GetBytes('local-simulated-device-secret-32-bytes-minimum');$commandId=$null
function Token([string]$user){return(Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @{grant_type='password';client_id='voice-platform-web';username=$user;password='LocalOnly123!';scope='openid'}).access_token}
function Signed([string]$method,[string]$path,[string]$body=''){$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="$method`n$path`n$timestamp`n$nonce`n$digest";$h=[Security.Cryptography.HMACSHA256]::new($secret);try{$signature=[Convert]::ToBase64String($h.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonical))).TrimEnd('=').Replace('+','-').Replace('/','_')}finally{$h.Dispose()};$headers=@{'X-Device-Id'=$deviceId;'X-Device-Timestamp'=$timestamp;'X-Device-Nonce'=$nonce;'X-Device-Content-SHA256'=$digest;'X-Device-Signature'=$signature};$args=@{UseBasicParsing=$true;Method=$method;Uri=($api+$path);Headers=$headers};if($body){$args.ContentType='application/json';$args.Body=$body};return Invoke-WebRequest @args}
function Expected([scriptblock]$action,[int]$status){try{$r=&$action;if($r.StatusCode-ne$status){throw "预期$status,实际$($r.StatusCode)"}}catch{if($_.Exception.Response.StatusCode.value__-ne$status){throw}}}
try{$owner=Token 'owner';$outsider=Token 'outsider';$key='device-command-'+[Guid]::NewGuid().ToString('N');$body=@{type='diagnostics';payload=@{checks=@('audio','network')};expires_in_sec=300}|ConvertTo-Json -Compress -Depth 5;$headers=@{Authorization="Bearer $owner";'Idempotency-Key'=$key};$created=Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/devices/$deviceId/commands" -Headers $headers -ContentType 'application/json' -Body $body;$commandId=[string](($created.Content|ConvertFrom-Json).data.id);if($created.StatusCode-ne 202){throw '命令未排队'};$replay=Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/devices/$deviceId/commands" -Headers $headers -ContentType 'application/json' -Body $body;if($replay.StatusCode-ne 200-or[string]$replay.Headers.'Idempotency-Replayed'-ne'true'){throw '命令幂等重放失败'};Expected {Invoke-WebRequest -UseBasicParsing -Uri "$api/api/v1/devices/$deviceId/commands" -Headers @{Authorization="Bearer $outsider"}} 403;$pulled=(Signed 'GET' '/device/v2/commands?limit=10').Content|ConvertFrom-Json;$item=$pulled.data.items|Where-Object id -eq $commandId;if(!$item-or$item.status-ne'DELIVERED'){throw '设备未拉取到待执行命令'};$ackBody=@{status='ACKNOWLEDGED';result_summary=@{accepted=$true}}|ConvertTo-Json -Compress;Signed 'POST' "/device/v2/commands/$commandId/ack" $ackBody|Out-Null;$doneBody=@{status='SUCCEEDED';result_summary=@{audio='ok';network='ok'}}|ConvertTo-Json -Compress;Signed 'POST' "/device/v2/commands/$commandId/ack" $doneBody|Out-Null;$list=(Invoke-WebRequest -UseBasicParsing -Uri "$api/api/v1/devices/$deviceId/commands" -Headers @{Authorization="Bearer $owner"}).Content|ConvertFrom-Json;$final=$list.data.items|Where-Object id -eq $commandId;if($final.status-ne'SUCCEEDED'-or$final.delivery_attempts-lt 1){throw '命令终态或投递次数不正确'};Write-Host "设备命令端到端冒烟通过:$commandId(QUEUED → DELIVERED → ACKNOWLEDGED → SUCCEEDED)"}finally{if($commandId-match'^[0-9a-fA-F-]{36}$'){docker compose exec -T postgres psql -U voice -d voice_platform -q -c "DELETE FROM audit_event WHERE resource_id='$commandId'::uuid; DELETE FROM web_event WHERE resource_public_id='$commandId'::uuid; DELETE FROM device_command WHERE public_id='$commandId'::uuid;"|Out-Null;Write-Host "已清理设备命令冒烟数据:$commandId"}}