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") } }