125 lines
6.7 KiB
PowerShell
125 lines
6.7 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
$PSNativeCommandUseErrorActionPreference = $true
|
|
|
|
$projectRoot = Split-Path -Parent $PSScriptRoot
|
|
Set-Location -LiteralPath $projectRoot
|
|
|
|
$apiBaseUrl = 'http://localhost:58080'
|
|
$deviceInternalId = '95000000-0000-0000-0000-000000000501'
|
|
$deviceId = '95000000-0000-0000-0000-000000000511'
|
|
$commandInternalId = '95000000-0000-0000-0000-000000000601'
|
|
$commandId = '95000000-0000-0000-0000-000000000611'
|
|
$deviceSecret = [Text.Encoding]::UTF8.GetBytes('local-simulated-device-secret-32-bytes-minimum')
|
|
|
|
function Assert-Equal([object]$Actual, [object]$Expected, [string]$Message) {
|
|
if ([string]$Actual -ne [string]$Expected) {
|
|
throw "$Message。实际值:$Actual;预期值:$Expected"
|
|
}
|
|
}
|
|
|
|
function Invoke-Database([string]$Sql) {
|
|
$result = 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 ($result | Out-String).Trim()
|
|
}
|
|
|
|
function Invoke-SignedTelemetry([string]$Body, [string]$IdempotencyKey) {
|
|
$path = '/device/v2/telemetry:batch'
|
|
$bodyBytes = [Text.Encoding]::UTF8.GetBytes($Body)
|
|
$digest = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($bodyBytes)).ToLowerInvariant()
|
|
$timestamp = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
|
|
$nonce = [Guid]::NewGuid().ToString('N')
|
|
$canonical = "POST`n$path`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 -Method Post -Uri ($apiBaseUrl + $path) -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' = $IdempotencyKey
|
|
}
|
|
}
|
|
|
|
function Remove-TelemetryFixture {
|
|
$cleanup = @"
|
|
BEGIN;
|
|
DELETE FROM device_telemetry_event WHERE device_id='$deviceInternalId'::uuid;
|
|
DELETE FROM idempotency_record WHERE actor_key='device:$deviceId';
|
|
DELETE FROM device_presence_state WHERE device_id='$deviceInternalId'::uuid;
|
|
DELETE FROM device_command WHERE device_id='$deviceInternalId'::uuid;
|
|
DELETE FROM device_twin WHERE device_id='$deviceInternalId'::uuid;
|
|
DELETE FROM device WHERE id='$deviceInternalId'::uuid;
|
|
COMMIT;
|
|
"@
|
|
Invoke-Database $cleanup | Out-Null
|
|
}
|
|
|
|
try {
|
|
Remove-TelemetryFixture
|
|
$fixture = @"
|
|
BEGIN;
|
|
INSERT INTO device(id,public_id,tenant_id,serial_digest,model,status,credential_ref)
|
|
VALUES('$deviceInternalId'::uuid,'$deviceId'::uuid,'10000000-0000-0000-0000-000000000001',repeat('6',64),'telemetry-smoke','registered','local-dev://simulated-terminal-1');
|
|
INSERT INTO device_twin(device_id,tenant_id,desired_revision,reported_revision)
|
|
VALUES('$deviceInternalId'::uuid,'10000000-0000-0000-0000-000000000001',2,1);
|
|
INSERT INTO device_command(id,public_id,tenant_id,device_id,command_type,payload,status,requested_by,idempotency_key,request_digest,expires_at,delivery_attempts)
|
|
VALUES('$commandInternalId'::uuid,'$commandId'::uuid,'10000000-0000-0000-0000-000000000001','$deviceInternalId'::uuid,'diagnostics','{}','DELIVERED','10000000-0000-0000-0000-000000000101','telemetry-smoke',repeat('5',64),now()+interval '1 hour',1);
|
|
COMMIT;
|
|
"@
|
|
Invoke-Database $fixture | Out-Null
|
|
|
|
$baseTime = [DateTime]::UtcNow
|
|
$bodyObject = @{ events = @(
|
|
@{ event_key='smoke-status-1'; sequence_no=1; occurred_at=$baseTime.ToString('o'); event_type='status'; status=@{
|
|
firmware_version='telemetry-smoke-1.0.0'; reported_config_revision=1; network=@{type='wifi';rssi=-48};
|
|
rtc=@{joined=$false;uplink=$false;downlink=$false}; recording_queue=@{pending_segments=3;failed_segments=0}
|
|
}},
|
|
@{ event_key='smoke-error-2'; sequence_no=2; occurred_at=$baseTime.AddSeconds(1).ToString('o'); event_type='error'; error=@{code='RTC_RECONNECTING';severity='warning'} },
|
|
@{ event_key='smoke-command-3'; sequence_no=3; occurred_at=$baseTime.AddSeconds(2).ToString('o'); event_type='command_result'; command_result=@{
|
|
command_id=$commandId; status='SUCCEEDED'; result_summary=@{result_code='OK'}
|
|
}}
|
|
) }
|
|
$body = $bodyObject | ConvertTo-Json -Compress -Depth 8
|
|
$key = 'smoke-telemetry-' + [Guid]::NewGuid().ToString('N')
|
|
$response = Invoke-SignedTelemetry $body $key
|
|
Assert-Equal $response.StatusCode 200 '首次遥测批次失败'
|
|
$data = ($response.Content | ConvertFrom-Json).data
|
|
Assert-Equal $data.accepted_count 3 '首次遥测接收数量不正确'
|
|
Assert-Equal $data.duplicate_count 0 '首次遥测不应出现重复'
|
|
Assert-Equal $data.actions[0].type 'refresh_config' '设备配置落后时没有返回刷新动作'
|
|
|
|
$replay = Invoke-SignedTelemetry $body $key
|
|
Assert-Equal $replay.Headers['Idempotency-Replayed'][0] 'true' '遥测幂等重放缺少标记'
|
|
$duplicate = Invoke-SignedTelemetry $body ('smoke-telemetry-duplicate-' + [Guid]::NewGuid().ToString('N'))
|
|
$duplicateData = ($duplicate.Content | ConvertFrom-Json).data
|
|
Assert-Equal $duplicateData.accepted_count 0 '事件级重放被错误重复保存'
|
|
Assert-Equal $duplicateData.duplicate_count 3 '事件级重放没有全部判重'
|
|
|
|
$conflictObject = $body | ConvertFrom-Json
|
|
$conflictObject.events[1].error.code = 'RTC_FAILED'
|
|
try {
|
|
Invoke-SignedTelemetry ($conflictObject | ConvertTo-Json -Compress -Depth 8) ('smoke-telemetry-conflict-' + [Guid]::NewGuid().ToString('N')) | Out-Null
|
|
throw '同一遥测业务键对应不同内容不应成功。'
|
|
} catch {
|
|
if ($null -eq $_.Exception.Response -or [int]$_.Exception.Response.StatusCode -ne 409) { throw }
|
|
}
|
|
|
|
$evidence = Invoke-Database @"
|
|
SELECT dt.telemetry_sequence || '|' || dt.firmware_version || '|' || dt.last_error_code || '|' || dt.last_error_severity || '|' ||
|
|
dc.status || '|' || (SELECT count(*) FROM device_telemetry_event dte WHERE dte.device_id=dt.device_id)
|
|
FROM device_twin dt JOIN device_command dc ON dc.device_id=dt.device_id
|
|
WHERE dt.device_id='$deviceInternalId'::uuid;
|
|
"@
|
|
Assert-Equal $evidence '3|telemetry-smoke-1.0.0|RTC_RECONNECTING|warning|SUCCEEDED|3' '设备影子、错误、命令或事件落库结果不正确'
|
|
Write-Host '设备遥测冒烟通过:状态、错误、命令结果、批次幂等、事件判重、冲突回滚和配置刷新动作均正确。'
|
|
} finally {
|
|
Remove-TelemetryFixture
|
|
Write-Host '已清理遥测合成设备、命令、事件、在线状态和幂等记录。'
|
|
}
|