chore: establish local platform baseline

This commit is contained in:
Codex Local
2026-08-26 06:41:54 +08:00
commit 29bdf4900f
575 changed files with 95644 additions and 0 deletions
+104
View File
@@ -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
}
}