Files
story/internal/onboarding/device_control_test.go
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

240 lines
10 KiB
Go

package onboarding
import (
"data-terminal-cloud/internal/device"
"github.com/google/uuid"
"strings"
"sync"
"testing"
)
func TestControlInputValidation(t *testing.T) {
valid := DeviceControlReport{Firmware: "1.7.0", Revision: 1, Speaker: 70, RSSI: -55}
if err := validateControlReport(valid); err != nil {
t.Fatal(err)
}
for _, mutate := range []func(*DeviceControlReport){
func(v *DeviceControlReport) { v.Speaker = -1 }, func(v *DeviceControlReport) { v.Speaker = 101 },
func(v *DeviceControlReport) { v.Revision = -1 }, func(v *DeviceControlReport) { v.Firmware = "" },
func(v *DeviceControlReport) { v.RSSI = -128 },
func(v *DeviceControlReport) { s := "名称\n"; v.DisplayName = &s },
func(v *DeviceControlReport) { v.Power = &DevicePowerReport{Mode: "standby", MicrophonePaused: false} },
func(v *DeviceControlReport) {
v.Power = &DevicePowerReport{Mode: "standby", MicrophonePaused: true, BacklightPercent: 80}
},
func(v *DeviceControlReport) { v.Power = &DevicePowerReport{Mode: "off"} },
func(v *DeviceControlReport) { v.Power = &DevicePowerReport{Mode: "active", BacklightPercent: 101} },
} {
invalid := valid
mutate(&invalid)
if validateControlReport(invalid) == nil {
t.Fatalf("accepted %+v", invalid)
}
}
ack := DeviceControlAck{ID: uuid.NewString(), Status: "SUCCEEDED"}
if err := validateControlAck(ack); err != nil {
t.Fatal(err)
}
ack.Status = "QUEUED"
if validateControlAck(ack) == nil {
t.Fatal("accepted state regression")
}
ack.Status = "FAILED"
ack.Result = map[string]any{"body": strings.Repeat("x", 4097)}
if validateControlAck(ack) == nil {
t.Fatal("accepted oversized result")
}
}
func TestScreenWakeValidation(t *testing.T) {
in := DeviceControlReport{Firmware: "1.7.0-controls.32", Revision: 4, Speaker: 60, RSSI: -50,
Power: &DevicePowerReport{Mode: "active", BacklightPercent: 80},
ScreenWake: &DeviceScreenWake{ID: uuid.NewString(), Revision: 4}}
must(t, validateControlReport(in))
for _, mutation := range []func(*DeviceControlReport){
func(r *DeviceControlReport) { r.ScreenWake = &DeviceScreenWake{ID: "bad", Revision: 4} },
func(r *DeviceControlReport) { r.ScreenWake = &DeviceScreenWake{ID: uuid.NewString()} },
func(r *DeviceControlReport) { r.Power = nil },
func(r *DeviceControlReport) { r.Power = &DevicePowerReport{Mode: "standby", MicrophonePaused: true} },
} {
invalid := in
mutation(&invalid)
if validateControlReport(invalid) == nil {
t.Fatal("invalid screen wake accepted")
}
}
}
func TestScreenWakeReconcilesAndFencesNewerConfiguration(t *testing.T) {
f := newFixture(t)
_, err := f.s.pool.Exec(f.ctx, `UPDATE device_twin SET desired_revision=4,reported_revision=4,
desired_config='{"volume":{"speaker":31},"identity":{"display_name":"保留名称"},"power":{"mode":"standby"}}',
reported_config='{}' WHERE device_id=$1`, f.d.ID)
must(t, err)
in := DeviceControlReport{Firmware: "1.7.0-controls.32", Revision: 4, Speaker: 31, RSSI: -50,
Power: &DevicePowerReport{Mode: "active", BacklightPercent: 80},
ScreenWake: &DeviceScreenWake{ID: uuid.NewString(), Revision: 4}}
check := func(wantRevision int64, wantMode, wantResult string) {
t.Helper()
out, err := f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
if receipt := out["screen_wake"].(*DeviceScreenWakeReceipt); receipt.ID != in.ScreenWake.ID || receipt.Result != wantResult {
t.Fatal(receipt)
}
var revision, reported int64
var mode, name string
var speaker int
must(t, f.s.pool.QueryRow(f.ctx, `SELECT desired_revision,reported_revision,desired_config->'power'->>'mode',
desired_config->'identity'->>'display_name',(desired_config->'volume'->>'speaker')::int
FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&revision, &reported, &mode, &name, &speaker))
if revision != wantRevision || reported != in.Revision || mode != wantMode || name != "保留名称" || speaker != 31 {
t.Fatalf("revision=%d reported=%d mode=%s name=%s speaker=%d", revision, reported, mode, name, speaker)
}
}
check(5, "active", "applied")
check(5, "active", "applied") // Retry after a lost reply must not add a revision.
_, err = f.s.pool.Exec(f.ctx, `UPDATE device_twin SET desired_revision=6,
desired_config=jsonb_set(desired_config,'{power}','{"mode":"standby"}') WHERE device_id=$1`, f.d.ID)
must(t, err)
check(6, "standby", "applied") // The old successful event cannot undo a later standby.
in.ScreenWake = &DeviceScreenWake{ID: uuid.NewString(), Revision: 4}
check(6, "standby", "superseded") // An offline stale tap respects the newer config.
in.ScreenWake = &DeviceScreenWake{ID: uuid.NewString(), Revision: 6}
in.Revision = 6
var wg sync.WaitGroup
errs := make(chan error, 2)
for i := 0; i < 2; i++ {
wg.Add(1)
go func() { defer wg.Done(); _, err := f.s.DeviceControlSync(f.ctx, f.d, in); errs <- err }()
}
wg.Wait()
close(errs)
for err := range errs {
must(t, err)
}
check(7, "active", "applied")
in.ScreenWake = &DeviceScreenWake{ID: in.ScreenWake.ID, Revision: 7}
if _, err := f.s.DeviceControlSync(f.ctx, f.d, in); err == nil {
t.Fatal("receipt ID reused with a changed revision")
}
foreign := f.d
foreign.ID = uuid.NewString()
if _, err := f.s.DeviceControlSync(f.ctx, foreign, in); err == nil {
t.Fatal("foreign device altered power")
}
}
func TestControlNameReportsAreActualReadings(t *testing.T) {
f := newFixture(t)
_, err := f.s.pool.Exec(f.ctx, `UPDATE device_twin SET desired_revision=2,desired_config='{"identity":{"display_name":"云端名称"}}',reported_config='{}',reported_revision=NULL WHERE device_id=$1`, f.d.ID)
must(t, err)
in := DeviceControlReport{Firmware: "1.7.0", Speaker: 60, RSSI: -50}
_, err = f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
var name *string
must(t, f.s.pool.QueryRow(f.ctx, `SELECT reported_config->'identity'->>'display_name' FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&name))
if name != nil {
t.Fatal("old firmware falsely reported desired name")
}
actual := "设备实际名称"
in.DisplayName = &actual
_, err = f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
must(t, f.s.pool.QueryRow(f.ctx, `SELECT reported_config->'identity'->>'display_name' FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&name))
if name == nil || *name != actual {
t.Fatal("actual name lost or replaced by desired")
}
actual = ""
_, err = f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
must(t, f.s.pool.QueryRow(f.ctx, `SELECT reported_config->'identity'->>'display_name' FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&name))
if name == nil || *name != "" {
t.Fatal("explicit default name not reported")
}
}
func TestControlPowerActualReadings(t *testing.T) {
f := newFixture(t)
_, err := f.s.pool.Exec(f.ctx, `UPDATE device_twin SET desired_config=desired_config || '{"power":{"mode":"standby"}}'::jsonb WHERE device_id=$1`, f.d.ID)
must(t, err)
in := DeviceControlReport{Firmware: "1.7.0", Speaker: 60, RSSI: -50}
_, err = f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
var mode *string
must(t, f.s.pool.QueryRow(f.ctx, `SELECT reported_config->'power'->>'mode' FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&mode))
if mode != nil {
t.Fatal("old firmware falsely claimed standby")
}
for _, p := range []DevicePowerReport{{Mode: "active", BacklightPercent: 80, MicrophoneReads: 12}, {Mode: "standby", MicrophonePaused: true, BacklightPercent: 0, MicrophoneReads: 12}} {
in.Power = &p
_, err = f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
must(t, f.s.pool.QueryRow(f.ctx, `SELECT reported_config->'power'->>'mode' FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&mode))
if mode == nil || *mode != p.Mode {
t.Fatal("power report substituted by desired mode")
}
}
}
func TestDeviceControlReportsAndCommandIsolation(t *testing.T) {
f := newFixture(t)
_, err := f.s.pool.Exec(f.ctx, `UPDATE device_twin SET desired_revision=2,desired_config='{"volume":{"speaker":40}}',reported_revision=NULL WHERE device_id=$1`, f.d.ID)
must(t, err)
commandID := uuid.NewString()
_, err = f.s.pool.Exec(f.ctx, `INSERT INTO device_command(public_id,tenant_id,device_id,command_type,requested_by,idempotency_key,request_digest,expires_at)
VALUES($1,$2,$3,'diagnostics',$4,$5,repeat('d',64),now()+interval '5 minutes')`, commandID, f.tenant, f.d.ID, f.operatorID, uuid.NewString())
must(t, err)
in := DeviceControlReport{Firmware: "1.7.0", Speaker: 70, RSSI: -50, Busy: true}
out, err := f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
if len(out["commands"].([]device.Command)) != 0 {
t.Fatal("delivered during a call")
}
var actual int
var revision *int64
must(t, f.s.pool.QueryRow(f.ctx, `SELECT (reported_config->'volume'->>'speaker')::int,reported_revision FROM device_twin WHERE device_id=$1`, f.d.ID).Scan(&actual, &revision))
if actual != 70 || revision != nil {
t.Fatal("cloud desired value falsely reported as applied")
}
in.Speaker = 40
in.Revision = 2
in.Busy = false
out, err = f.s.DeviceControlSync(f.ctx, f.d, in)
must(t, err)
items := out["commands"].([]device.Command)
if len(items) != 1 || items[0].ID != commandID || items[0].Status != "DELIVERED" {
t.Fatal(items)
}
in.Revision = 3
if _, err = f.s.DeviceControlSync(f.ctx, f.d, in); err == nil {
t.Fatal("accepted future revision")
}
in.Revision = 1
if _, err = f.s.DeviceControlSync(f.ctx, f.d, in); err == nil {
t.Fatal("accepted stale revision")
}
ack := DeviceControlAck{ID: commandID, Status: "ACKNOWLEDGED"}
foreign := f.d
foreign.ID = uuid.NewString()
if _, err = f.s.DeviceControlAcknowledge(f.ctx, foreign, ack); err == nil {
t.Fatal("foreign device acknowledged command")
}
_, err = f.s.DeviceControlAcknowledge(f.ctx, f.d, ack)
must(t, err)
ack.Status = "SUCCEEDED"
ack.Result = map[string]any{"speaker": 40}
_, err = f.s.DeviceControlAcknowledge(f.ctx, f.d, ack)
must(t, err)
_, err = f.s.DeviceControlAcknowledge(f.ctx, f.d, ack)
must(t, err)
ack.Status = "FAILED"
if _, err = f.s.DeviceControlAcknowledge(f.ctx, f.d, ack); err == nil {
t.Fatal("changed terminal result")
}
blocked := f.d
blocked.Status = "revoked"
if _, err = f.s.DeviceControlSync(f.ctx, blocked, in); err == nil {
t.Fatal("revoked device synced")
}
}