- 新增 xiaomi/examples/ 下 8 个示例程序(用户/家庭查询、设备列表、开关/灯光/空调控制、属性订阅、高级过滤分类、SPEC 解析) - miot_client_sub.go: 重构 SubProp/SubEvent,使用 buildPropTopic/buildEventTopic 支持通配符订阅(siid/piid=0 → +),并修复锁顺序问题(将 RequestRefreshProp 移到 Lock 外) - spec_parser.go: 新增 downloadSpecFile 方法,本地 SPEC 文件缺失时自动从 miot-spec.org 下载 - ARCH_PLAN.md: 架构设计从 Proposed 更新为 Accepted(v1.0→v1.1),补充设备分类/工厂/SPEC 映射等模块设计 - xiaomi/: 新增 miot 上层强类型封装模块,包含 Client 主入口、用户/家庭/设备 API、属性读写、动作调用、订阅通知,以及 devices/ 设备控制抽象(Switch/Light/AirConditioner/Fan/Cover/Humidifier/Vacuum/WaterHeater/Thermostat)和 specs/ SPEC 查询辅助 - 更多 xiaomi 示例(风扇/窗帘/传感器控制)
82 lines
1.9 KiB
Go
82 lines
1.9 KiB
Go
package xiaomi
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestSentinelErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
}{
|
|
{"ErrNotSupported", ErrNotSupported},
|
|
{"ErrDeviceNotFound", ErrDeviceNotFound},
|
|
{"ErrHomeNotFound", ErrHomeNotFound},
|
|
{"ErrRoomNotFound", ErrRoomNotFound},
|
|
{"ErrDeviceOffline", ErrDeviceOffline},
|
|
{"ErrNoSPEC", ErrNoSPEC},
|
|
{"ErrInvalidProp", ErrInvalidProp},
|
|
{"ErrNotInitialized", ErrNotInitialized},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.err == nil {
|
|
t.Errorf("%s sentinel error is nil", tt.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientError(t *testing.T) {
|
|
t.Run("with DID", func(t *testing.T) {
|
|
ce := &ClientError{Op: "GetProp", DID: "abc123", Err: ErrDeviceOffline}
|
|
expected := `xiaomi: GetProp device=abc123: xiaomi: device is offline`
|
|
if ce.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", ce.Error(), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("without DID", func(t *testing.T) {
|
|
ce := &ClientError{Op: "GetHomeList", Err: ErrHomeNotFound}
|
|
expected := `xiaomi: GetHomeList: xiaomi: home not found`
|
|
if ce.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", ce.Error(), expected)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWrapErr(t *testing.T) {
|
|
t.Run("wraps error", func(t *testing.T) {
|
|
err := wrapErr("SetProp", "did1", ErrDeviceOffline)
|
|
if err == nil {
|
|
t.Fatal("expected non-nil error")
|
|
}
|
|
ce, ok := err.(*ClientError)
|
|
if !ok {
|
|
t.Fatalf("expected *ClientError, got %T", err)
|
|
}
|
|
if ce.Op != "SetProp" {
|
|
t.Errorf("Op = %q, want SetProp", ce.Op)
|
|
}
|
|
if ce.DID != "did1" {
|
|
t.Errorf("DID = %q, want did1", ce.DID)
|
|
}
|
|
})
|
|
|
|
t.Run("nil returns nil", func(t *testing.T) {
|
|
err := wrapErr("op", "did", nil)
|
|
if err != nil {
|
|
t.Errorf("expected nil, got %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestClientErrorUnwrap(t *testing.T) {
|
|
inner := errors.New("inner error")
|
|
ce := &ClientError{Op: "test", Err: inner}
|
|
if !errors.Is(ce, inner) {
|
|
t.Error("errors.Is(ce, inner) should be true")
|
|
}
|
|
}
|