package devices import ( "testing" "git.zeroonesoft.cn/golib/xiaomihome/miot" "git.zeroonesoft.cn/golib/xiaomihome/xiaomi" ) func TestToInt(t *testing.T) { tests := []struct { name string value interface{} want int }{ {"float64", float64(42), 42}, {"int", int(42), 42}, {"int64", int64(42), 42}, {"uint8", uint8(100), 100}, {"uint16", uint16(1000), 1000}, {"nil", nil, 0}, {"string", "hello", 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := toInt(tt.value) if got != tt.want { t.Errorf("toInt(%v) = %d, want %d", tt.value, got, tt.want) } }) } } func TestToFloat(t *testing.T) { tests := []struct { name string value interface{} want float64 }{ {"float64", float64(26.5), 26.5}, {"float32", float32(30.0), 30.0}, {"int", int(25), 25.0}, {"int64", int64(20), 20.0}, {"string", "26.5", 0.0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := toFloat(tt.value) if got != tt.want { t.Errorf("toFloat(%v) = %f, want %f", tt.value, got, tt.want) } }) } } func TestAirConditionerModeConstants(t *testing.T) { if ACModeCool != "cool" || ACModeHeat != "heat" || ACModeDry != "dry" || ACModeAuto != "auto" { t.Error("AC mode constants mismatch") } } func TestFanSpeedConstants(t *testing.T) { if ACFanLow != "low" || ACFanAuto != "auto" { t.Error("Fan speed constants mismatch") } } func TestSwingModeConstants(t *testing.T) { if ACSwingOff != "off" || ACSwingVertical != "vertical" { t.Error("Swing mode constants mismatch") } } func TestMatchURN(t *testing.T) { tests := []struct { name string urn string types []string want bool }{ { "air-conditioner", "urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c1:1", []string{"air-conditioner"}, true, }, { "light", "urn:miot-spec-v2:device:light:0000A001:philips-bulb:1", []string{"light"}, true, }, { "switch outlet", "urn:miot-spec-v2:device:outlet:0000A002:xiaomi-plug:1", []string{"switch", "outlet"}, true, }, { "fan", "urn:miot-spec-v2:device:fan:0000A003:xiaomi-fan:1", []string{"fan"}, true, }, { "curtain", "urn:miot-spec-v2:device:curtain:0000A005:aqara-c1:1", []string{"curtain"}, true, }, { "humidifier", "urn:miot-spec-v2:device:humidifier:0000A006:xiaomi-h1:1", []string{"humidifier"}, true, }, { "vacuum", "urn:miot-spec-v2:device:vacuum:0000A007:roborock-s5:1", []string{"vacuum"}, true, }, { "water-heater", "urn:miot-spec-v2:device:water-heater:0000A008:xiaomi-wh1:1", []string{"water-heater"}, true, }, { "heater thermostat", "urn:miot-spec-v2:device:heater:0000A009:xiaomi-h1:1", []string{"heater"}, true, }, { "tofan-wk01 not fan", // "tofan" 含 "fan" 子串,不得误匹配 fan 系列 "urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:1:0000C822", []string{"fan", "ceiling-fan", "ventilator", "air-fresh", "air-purifier"}, false, }, { "tofan-wk01 thermostat", "urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:1:0000C822", []string{"heater", "electric-blanket", "thermostat"}, true, }, { "no match", "urn:miot-spec-v2:device:unknown:0000A099:test:1", []string{"air-conditioner", "light", "switch"}, false, }, { "empty URN", "", []string{"light"}, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := matchURN(tt.urn, tt.types...) if got != tt.want { t.Errorf("matchURN(%q, %v) = %v, want %v", tt.urn, tt.types, got, tt.want) } }) } } func TestFactoryNilClientPanic(t *testing.T) { // Create with a nil client should panic (calling methods on nil client) // This is a documentation test noting expected behavior t.Skip("requires real or mock client") } func TestCreateNilInfo(t *testing.T) { client := &xiaomi.Client{} result, err := Create(client, nil) if result != nil { t.Errorf("expected nil result, got %v", result) } if err == nil { t.Error("expected error for nil info") } } func TestServiceShortName(t *testing.T) { cases := []struct { urn string want string }{ {"urn:miot-spec-v2:service:thermostat:0000784A:tofan-wk01:1", "thermostat"}, {"urn:miot-spec-v2:service:air-conditioner:0000780F:tofan-wk01:1", "air-conditioner"}, {"urn:tofan-spec:service:adv-setting:00007801:tofan-wk01:1", "adv-setting"}, {"urn:miot-spec-v2:property:on:00000006:tofan-wk01:1", "urn:miot-spec-v2:property:on:00000006:tofan-wk01:1"}, } for _, c := range cases { if got := serviceShortName(c.urn); got != c.want { t.Errorf("serviceShortName(%q) = %q, want %q", c.urn, got, c.want) } } } func TestValMapperLevelFallback(t *testing.T) { // klwdz1.airc.001 场景:Auto + Level1~Level5 vl := miot.MIoTSpecValueList{ {Value: 0, Desc: "Auto"}, {Value: 1, Desc: "Level1"}, {Value: 2, Desc: "Level2"}, {Value: 3, Desc: "Level3"}, {Value: 4, Desc: "Level4"}, {Value: 5, Desc: "Level5"}, } vm := newValMapperLevel(vl) cases := []struct { name string want int ok bool }{ {"auto", 0, true}, {"Auto", 0, true}, {"low", 1, true}, {"medium", 3, true}, {"high", 5, true}, {"level3", 3, true}, } for _, c := range cases { got, ok := vm.val(c.name) if ok != c.ok || (ok && got != c.want) { t.Errorf("val(%q) = (%d,%v), want (%d,%v)", c.name, got, ok, c.want, c.ok) } } // 反向解析(MQTT 上报值 → 语义枚举),与 val() 正向对称 rname := []struct { v int want string }{ {0, "auto"}, {1, "low"}, {3, "medium"}, {5, "high"}, {2, "Level2"}, } for _, c := range rname { if got := vm.name(c.v); got != c.want { t.Errorf("name(%d) = %q, want %q", c.v, got, c.want) } } // mode 枚举不受影响(无 low/medium/high fallback 副作用) mvl := miot.MIoTSpecValueList{ {Value: 0, Desc: "Auto"}, {Value: 1, Desc: "Cool"}, {Value: 2, Desc: "Heat"}, } mvm := newValMapper(mvl) if _, ok := mvm.val("low"); ok { t.Error("mode mapper should not resolve 'low'") } if v, ok := mvm.val("cool"); !ok || v != 1 { t.Errorf("mode val(cool) = (%d,%v), want (1,true)", v, ok) } }