主要变更: **类型系统重构** - HomeInfo.ID/RoomInfo.ID/HomeID 从 string 改为 int64,json tag 使用 ,string - GetHome/GetRooms/GetRoomDevices 接口同步修改 - 新增 int64FromStr 工具函数,更新 parseHomeInfo 解析逻辑 **新增 Speaker 设备** - 新增 KindSpeaker 类型 - classify 映射覆盖 speaker/bluetooth-speaker/alarm-clock/media-player/playback/music **设备驱动重构** - AC 驱动彻底重写:从硬编码常量改为基于 SPEC ValueList 的动态 valMapper - 所有设备(Fan/Humidifier/Light/OccupancySensor/Switch/TempHumiditySensor/Thermostat/Vacuum/WaterHeater/Cover)新增 GetState() 和 FetchState(ctx) 接口 - base.go 新增 specResolver.format() 方法 **MIoT 模块增强** - MIoTClient 新增 homeIDs 字段,支持按家庭过滤设备 - RefreshDevices 支持 homeIDs 参数 - SpecStdLib 引入 logger.Logger,log.Printf 全部迁移为结构化日志 **其他** - Config 新增 RedirectURL 和 HomeIDs 字段 - AuthInfo.ExpiresIn 类型从 int 改为 int32 - UUID 生成简化为 hex.EncodeToString - 新增 github.com/sirupsen/logrus 依赖 - .gitignore 新增 examples/*/data/ 忽略规则 - 删除已提交的 config.json 敏感配置
174 lines
3.5 KiB
Go
174 lines
3.5 KiB
Go
package devices
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"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,
|
|
},
|
|
{
|
|
"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")
|
|
}
|
|
}
|