chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
Set-Location -LiteralPath $projectRoot
|
||||
$apiBase = 'http://localhost:58080'
|
||||
$tokenUrl = 'http://localhost:58180/realms/voice-local/protocol/openid-connect/token'
|
||||
$deviceId = '10000000-0000-0000-0000-000000000511'
|
||||
$originalTwin = $null
|
||||
$patchRequestId = $null
|
||||
$claimBindingId = $null
|
||||
$claimRequestId = $null
|
||||
|
||||
function Get-LocalAccessToken([string]$Username) {
|
||||
$response = Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @{
|
||||
grant_type = 'password'; client_id = 'voice-platform-web'; username = $Username
|
||||
password = 'LocalOnly123!'; scope = 'openid'
|
||||
}
|
||||
return $response.access_token
|
||||
}
|
||||
|
||||
function Invoke-JSONRequest([string]$Method, [string]$Uri, [string]$Token, [hashtable]$Headers = @{}, [object]$Body = $null) {
|
||||
$requestHeaders = @{ Authorization = "Bearer $Token" }
|
||||
foreach ($key in $Headers.Keys) { $requestHeaders[$key] = $Headers[$key] }
|
||||
$arguments = @{ Method = $Method; Uri = $Uri; Headers = $requestHeaders }
|
||||
if ($null -ne $Body) {
|
||||
$arguments.ContentType = 'application/json; charset=utf-8'
|
||||
$arguments.Body = $Body | ConvertTo-Json -Depth 12 -Compress
|
||||
}
|
||||
return Invoke-WebRequest @arguments
|
||||
}
|
||||
|
||||
function Assert-Status([scriptblock]$Operation, [int]$ExpectedStatus) {
|
||||
try { & $Operation | Out-Null; throw "请求不应成功,预期HTTP $ExpectedStatus。" }
|
||||
catch { if ($null -eq $_.Exception.Response -or [int]$_.Exception.Response.StatusCode -ne $ExpectedStatus) { throw } }
|
||||
}
|
||||
|
||||
try {
|
||||
$ownerToken = Get-LocalAccessToken 'owner'
|
||||
$editorToken = Get-LocalAccessToken 'editor'
|
||||
$outsiderToken = Get-LocalAccessToken 'outsider'
|
||||
|
||||
$devices = (Invoke-JSONRequest Get "$apiBase/api/v1/devices" $ownerToken).Content | ConvertFrom-Json
|
||||
if ($deviceId -notin @($devices.data.items.id)) { throw '所有者设备列表未包含合成设备。' }
|
||||
Assert-Status { Invoke-JSONRequest Get "$apiBase/api/v1/devices/$deviceId" $outsiderToken } 403
|
||||
|
||||
$twinResponse = Invoke-JSONRequest Get "$apiBase/api/v1/devices/$deviceId/twin" $editorToken
|
||||
$originalTwin = $twinResponse.Content | ConvertFrom-Json
|
||||
$originalETag = [string]$twinResponse.Headers.ETag
|
||||
$presenceResponse = Invoke-JSONRequest Get "$apiBase/api/v1/devices/$deviceId/presence" $ownerToken
|
||||
$presence = $presenceResponse.Content | ConvertFrom-Json
|
||||
if ($presence.data.device_id -ne $deviceId -or [string]$presenceResponse.Headers.'Cache-Control' -ne 'no-store') {
|
||||
throw '设备在线状态响应不符合契约。'
|
||||
}
|
||||
|
||||
Assert-Status { Invoke-JSONRequest Patch "$apiBase/api/v1/devices/$deviceId/twin" $editorToken @{ 'If-Match' = $originalETag } @{
|
||||
desired_config = @{ upload = @{ api_token = 'forbidden' } }
|
||||
} } 400
|
||||
|
||||
$patchedResponse = Invoke-JSONRequest Patch "$apiBase/api/v1/devices/$deviceId/twin" $editorToken @{ 'If-Match' = $originalETag } @{
|
||||
desired_config = @{
|
||||
volume = @{ speaker = 60 }
|
||||
interaction = @{ pace = 'normal' }
|
||||
upload = @{ policy = 'wifi_preferred' }
|
||||
policy_refs = @{ retention = 'local-v1' }
|
||||
}
|
||||
}
|
||||
$patched = $patchedResponse.Content | ConvertFrom-Json
|
||||
$patchRequestId = $patched.request_id
|
||||
if ($patched.data.desired_revision -ne ($originalTwin.data.desired_revision + 1) -or $patched.data.sync_state -ne 'pending') {
|
||||
throw '设备影子期望版本或同步状态不正确。'
|
||||
}
|
||||
Assert-Status { Invoke-JSONRequest Patch "$apiBase/api/v1/devices/$deviceId/twin" $editorToken @{ 'If-Match' = $originalETag } @{ desired_config = @{} } } 412
|
||||
|
||||
$claimBody = @{
|
||||
claim_code = 'LOCAL-CLAIM-0001'
|
||||
household_id = '10000000-0000-0000-0000-000000000211'
|
||||
subject_id = '10000000-0000-0000-0000-000000000411'
|
||||
}
|
||||
Assert-Status { Invoke-JSONRequest Post "$apiBase/api/v1/device-claims" $outsiderToken @{} $claimBody } 403
|
||||
$claimResponse = Invoke-JSONRequest Post "$apiBase/api/v1/device-claims" $ownerToken @{} $claimBody
|
||||
$claim = $claimResponse.Content | ConvertFrom-Json
|
||||
$claimBindingId = $claim.data.binding_id
|
||||
$claimRequestId = $claim.request_id
|
||||
$claimReplay = Invoke-JSONRequest Post "$apiBase/api/v1/device-claims" $ownerToken @{} $claimBody
|
||||
$claimReplayBody = $claimReplay.Content | ConvertFrom-Json
|
||||
if ([string]$claimReplay.Headers.'Idempotency-Replayed' -ne 'true' -or $claimReplayBody.data.binding_id -ne $claimBindingId) {
|
||||
throw '一次性设备绑定码安全重试结果不正确。'
|
||||
}
|
||||
Write-Host "设备管理烟测通过:设备 $deviceId,在线状态来源 $($presence.data.source),绑定 $claimBindingId。"
|
||||
} finally {
|
||||
if (-not [string]::IsNullOrWhiteSpace($claimBindingId)) {
|
||||
$deleteClaimAudit = if ([string]::IsNullOrWhiteSpace($claimRequestId)) { '' } else { "DELETE FROM audit_event WHERE request_id='$claimRequestId';" }
|
||||
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -c `
|
||||
"BEGIN; UPDATE device_claim SET used_at=NULL,used_binding_id=NULL WHERE public_id='10000000-0000-0000-0000-000000000911'::uuid; DELETE FROM device_binding WHERE public_id='$claimBindingId'::uuid; $deleteClaimAudit COMMIT;" | Out-Null
|
||||
}
|
||||
if ($null -ne $originalTwin) {
|
||||
$desiredJSON = ($originalTwin.data.desired_config | ConvertTo-Json -Depth 12 -Compress).Replace("'", "''")
|
||||
$updatedAt = ([DateTimeOffset]$originalTwin.data.updated_at).ToString('o')
|
||||
$deleteAudit = if ([string]::IsNullOrWhiteSpace($patchRequestId)) { '' } else { "DELETE FROM audit_event WHERE request_id='$patchRequestId';" }
|
||||
docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -c `
|
||||
"BEGIN; UPDATE device_twin SET desired_revision=$($originalTwin.data.desired_revision),desired_config='$desiredJSON'::jsonb,updated_at='$updatedAt'::timestamptz WHERE device_id=(SELECT id FROM device WHERE public_id='$deviceId'::uuid); $deleteAudit COMMIT;" | Out-Null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user