- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步 - 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
241 lines
6.3 KiB
Go
241 lines
6.3 KiB
Go
package specs
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.zeroonesoft.cn/golib/xiaomihome/miot"
|
|
)
|
|
|
|
// buildTestSpec creates a mock SPEC instance for testing.
|
|
func buildTestSpec() *miot.MIoTSpecInstance {
|
|
return &miot.MIoTSpecInstance{
|
|
URN: "urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c1:1",
|
|
Type: "air-conditioner",
|
|
Description: "Xiaomi Air Conditioner",
|
|
DescriptionTrans: "小米空调",
|
|
Services: []*miot.MIoTSpecService{
|
|
{
|
|
IID: 1,
|
|
Type: "device-information",
|
|
Description: "Device Information",
|
|
Properties: []*miot.MIoTSpecProperty{
|
|
{SIID: 1, PIID: 1, Type: "", Format: "string", Access: []string{"read"}, Description: "manufacturer", DescriptionTrans: "制造商"},
|
|
},
|
|
},
|
|
{
|
|
IID: 2,
|
|
Type: "air-conditioner",
|
|
Description: "Air Conditioner",
|
|
Properties: []*miot.MIoTSpecProperty{
|
|
{SIID: 2, PIID: 1, Type: "on", Format: "bool", Access: []string{"read", "write", "notify"}, Description: "Power", DescriptionTrans: "开关"},
|
|
{SIID: 2, PIID: 2, Type: "mode", Format: "uint8", Access: []string{"read", "write", "notify"}, Description: "Mode", DescriptionTrans: "模式"},
|
|
{SIID: 2, PIID: 3, Type: "target-temperature", Format: "float", Access: []string{"read", "write", "notify"}, Description: "Target Temp", DescriptionTrans: "目标温度"},
|
|
{SIID: 2, PIID: 4, Type: "temperature", Format: "float", Access: []string{"read"}, Description: "Current Temp", DescriptionTrans: "当前温度"},
|
|
{SIID: 2, PIID: 5, Type: "fan-level", Format: "uint8", Access: []string{"read", "write", "notify"}, Description: "Fan Speed", DescriptionTrans: "风速"},
|
|
{SIID: 2, PIID: 6, Type: "swing-mode", Format: "uint8", Access: []string{"read", "write", "notify"}, Description: "Swing", DescriptionTrans: "摆风"},
|
|
},
|
|
Actions: []*miot.MIoTSpecAction{
|
|
{SIID: 2, AIID: 1, Type: "toggle", Description: "Toggle Power"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestNewPropertyResolver(t *testing.T) {
|
|
t.Run("valid spec", func(t *testing.T) {
|
|
spec := buildTestSpec()
|
|
r := NewPropertyResolver(spec)
|
|
if r == nil {
|
|
t.Fatal("resolver should not be nil")
|
|
}
|
|
if r.spec != spec {
|
|
t.Error("spec reference mismatch")
|
|
}
|
|
})
|
|
|
|
t.Run("nil spec", func(t *testing.T) {
|
|
r := NewPropertyResolver(nil)
|
|
if r != nil {
|
|
t.Error("resolver should be nil for nil spec")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFindByType(t *testing.T) {
|
|
r := NewPropertyResolver(buildTestSpec())
|
|
|
|
tests := []struct {
|
|
name string
|
|
propType string
|
|
wantSIID int
|
|
wantPIID int
|
|
wantOK bool
|
|
}{
|
|
{"on", "on", 2, 1, true},
|
|
{"mode", "mode", 2, 2, true},
|
|
{"target-temperature", "target-temperature", 2, 3, true},
|
|
{"temperature", "temperature", 2, 4, true},
|
|
{"fan-level", "fan-level", 2, 5, true},
|
|
{"swing-mode", "swing-mode", 2, 6, true},
|
|
{"nonexistent", "brightness", 0, 0, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
siid, piid, ok := r.FindByType(tt.propType)
|
|
if ok != tt.wantOK {
|
|
t.Errorf("ok = %v, want %v", ok, tt.wantOK)
|
|
}
|
|
if siid != tt.wantSIID {
|
|
t.Errorf("siid = %d, want %d", siid, tt.wantSIID)
|
|
}
|
|
if piid != tt.wantPIID {
|
|
t.Errorf("piid = %d, want %d", piid, tt.wantPIID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindByFormat(t *testing.T) {
|
|
r := NewPropertyResolver(buildTestSpec())
|
|
|
|
tests := []struct {
|
|
name string
|
|
format string
|
|
siid int
|
|
wantPIID int
|
|
wantOK bool
|
|
}{
|
|
{"bool in siid 2", "bool", 2, 1, true},
|
|
{"float in siid 2 (first match)", "float", 2, 3, true},
|
|
{"string in siid 1", "string", 1, 1, true},
|
|
{"nonexistent format", "int16", 2, 0, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
piid, ok := r.FindByFormat(tt.format, tt.siid)
|
|
if ok != tt.wantOK {
|
|
t.Errorf("ok = %v, want %v", ok, tt.wantOK)
|
|
}
|
|
if piid != tt.wantPIID {
|
|
t.Errorf("piid = %d, want %d", piid, tt.wantPIID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindByDescription(t *testing.T) {
|
|
r := NewPropertyResolver(buildTestSpec())
|
|
|
|
tests := []struct {
|
|
name string
|
|
desc string
|
|
wantSIID int
|
|
wantPIID int
|
|
wantOK bool
|
|
}{
|
|
{"by translated desc", "开关", 2, 1, true},
|
|
{"by raw desc", "Power", 2, 1, true},
|
|
{"nonexistent desc", "亮度", 0, 0, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
siid, piid, ok := r.FindByDescription(tt.desc)
|
|
if ok != tt.wantOK {
|
|
t.Errorf("ok = %v, want %v", ok, tt.wantOK)
|
|
}
|
|
if siid != tt.wantSIID {
|
|
t.Errorf("siid = %d, want %d", siid, tt.wantSIID)
|
|
}
|
|
if piid != tt.wantPIID {
|
|
t.Errorf("piid = %d, want %d", piid, tt.wantPIID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindAction(t *testing.T) {
|
|
r := NewPropertyResolver(buildTestSpec())
|
|
|
|
t.Run("existing action", func(t *testing.T) {
|
|
aiid, ok := r.FindAction(2, "toggle")
|
|
if !ok {
|
|
t.Error("toggle action should be found")
|
|
}
|
|
if aiid != 1 {
|
|
t.Errorf("aiid = %d, want 1", aiid)
|
|
}
|
|
})
|
|
|
|
t.Run("nonexistent action", func(t *testing.T) {
|
|
_, ok := r.FindAction(2, "nonexistent")
|
|
if ok {
|
|
t.Error("nonexistent action should not be found")
|
|
}
|
|
})
|
|
|
|
t.Run("nonexistent service", func(t *testing.T) {
|
|
_, ok := r.FindAction(99, "toggle")
|
|
if ok {
|
|
t.Error("action in nonexistent service should not be found")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetAllProperties(t *testing.T) {
|
|
r := NewPropertyResolver(buildTestSpec())
|
|
|
|
t.Run("siid 2 has 6 properties", func(t *testing.T) {
|
|
props := r.GetAllProperties(2)
|
|
if len(props) != 6 {
|
|
t.Errorf("len = %d, want 6", len(props))
|
|
}
|
|
})
|
|
|
|
t.Run("siid 1 has 1 property", func(t *testing.T) {
|
|
props := r.GetAllProperties(1)
|
|
if len(props) != 1 {
|
|
t.Errorf("len = %d, want 1", len(props))
|
|
}
|
|
})
|
|
|
|
t.Run("nonexistent siid", func(t *testing.T) {
|
|
props := r.GetAllProperties(99)
|
|
if len(props) != 0 {
|
|
t.Errorf("len = %d, want 0", len(props))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetProperty(t *testing.T) {
|
|
r := NewPropertyResolver(buildTestSpec())
|
|
|
|
t.Run("existing property", func(t *testing.T) {
|
|
prop := r.GetProperty(2, 3)
|
|
if prop == nil {
|
|
t.Fatal("property should be found")
|
|
}
|
|
if prop.Type != "target-temperature" {
|
|
t.Errorf("Type = %q", prop.Type)
|
|
}
|
|
})
|
|
|
|
t.Run("nonexistent property", func(t *testing.T) {
|
|
prop := r.GetProperty(2, 99)
|
|
if prop != nil {
|
|
t.Error("property should be nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSpec(t *testing.T) {
|
|
spec := buildTestSpec()
|
|
r := NewPropertyResolver(spec)
|
|
if r.Spec() != spec {
|
|
t.Error("Spec() should return the original spec")
|
|
}
|
|
}
|