为什么:并入宿主项目后须与宿主日志体系一致——宿主统一用 pkg/logger 初始化 logrus 全局实例并直接 logrus.Xxx 包级调用;包一层 Logger 接口会让 logrus 的 caller 定位(报告调用文件:行号)失准。 改动: - 删除 logger 包(Logger 接口 / Default / SetDefault,默认本就是 logrus.StandardLogger) - 11 个结构体移除 lgr logger.Logger 字段、SetLogger 方法及构造中的 lgr 初始化 - 192 处 lgr.Xxxf 调用改为 logrus.Xxxf,日志消息文本保持不变 - xiaomi.Client 移除 Logger() 访问器,air_conditioner 回读日志去掉判空调用直连 logrus - miot_i18n.go 两处标准库 log.Printf 改为 logrus.Errorf,统一日志出口 - go.mod:logrus 从 indirect 提升为直接依赖 - goimports/goformat 全树规范化:此前部分文件未 gofmt(单行 if、对齐),本次顺带 格式化,纯空白/换行变化,无逻辑改动 验证:go build / go vet / go test ./... 全部通过(bridge、miot、xiaomi、devices、specs)
544 lines
17 KiB
Go
544 lines
17 KiB
Go
// Package miot provides white-box tests for miot_client_device.go device management.
|
|
package miot
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// ============================================================================
|
|
// GetDeviceInfo
|
|
// ============================================================================
|
|
|
|
func TestGetDeviceInfo_Exists(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did1"] = map[string]interface{}{
|
|
"did": "did1",
|
|
"name": "Test Device",
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
|
|
info := c.GetDeviceInfo("did1")
|
|
if info == nil {
|
|
t.Fatal("GetDeviceInfo should return non-nil for existing device")
|
|
}
|
|
if info["name"] != "Test Device" {
|
|
t.Errorf("name = %v, want 'Test Device'", info["name"])
|
|
}
|
|
}
|
|
|
|
func TestGetDeviceInfo_NotExists(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
info := c.GetDeviceInfo("nonexistent")
|
|
if info != nil {
|
|
t.Errorf("GetDeviceInfo should return nil for non-existent, got %v", info)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// GetDeviceInfos
|
|
// ============================================================================
|
|
|
|
func TestGetDeviceInfos(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did1"] = map[string]interface{}{"name": "dev1"}
|
|
c.deviceListCache["did2"] = map[string]interface{}{"name": "dev2"}
|
|
c.deviceListMu.Unlock()
|
|
|
|
infos := c.GetDeviceInfos()
|
|
if len(infos) != 2 {
|
|
t.Errorf("GetDeviceInfos len = %d, want 2", len(infos))
|
|
}
|
|
if infos["did1"]["name"] != "dev1" {
|
|
t.Errorf("did1 name = %v", infos["did1"]["name"])
|
|
}
|
|
|
|
// Should be a copy
|
|
infos["new"] = map[string]interface{}{}
|
|
if _, ok := c.GetDeviceInfos()["new"]; ok {
|
|
t.Error("GetDeviceInfos should return independent copies")
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// RefreshDevices — no HTTP client
|
|
// ============================================================================
|
|
|
|
func TestRefreshDevices_NoHTTP(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
err := c.RefreshDevices(nil)
|
|
if err == nil {
|
|
t.Error("RefreshDevices without HTTP client should error")
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Helper functions
|
|
// ============================================================================
|
|
|
|
func TestGetConnectType(t *testing.T) {
|
|
tests := []struct {
|
|
info map[string]interface{}
|
|
want int
|
|
}{
|
|
{map[string]interface{}{"connect_type": float64(23)}, 23},
|
|
{map[string]interface{}{"connect_type": float64(8)}, 8},
|
|
{map[string]interface{}{}, 0},
|
|
{nil, 0},
|
|
}
|
|
for _, tt := range tests {
|
|
got := getConnectType(tt.info)
|
|
if got != tt.want {
|
|
t.Errorf("getConnectType(%v) = %d, want %d", tt.info, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetOnline(t *testing.T) {
|
|
tests := []struct {
|
|
info map[string]interface{}
|
|
want bool
|
|
}{
|
|
{map[string]interface{}{"online": true}, true},
|
|
{map[string]interface{}{"online": false}, false},
|
|
{map[string]interface{}{}, false},
|
|
{nil, false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := getOnline(tt.info)
|
|
if got != tt.want {
|
|
t.Errorf("getOnline(%v) = %v, want %v", tt.info, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsSharedDevice(t *testing.T) {
|
|
tests := []struct {
|
|
info map[string]interface{}
|
|
want bool
|
|
}{
|
|
{map[string]interface{}{"owner": map[string]interface{}{"userid": "123"}}, true},
|
|
{map[string]interface{}{"owner": map[string]interface{}{"nickname": "user"}}, false},
|
|
{map[string]interface{}{}, false},
|
|
{map[string]interface{}{"owner": "not_a_map"}, false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := isSharedDevice(tt.info)
|
|
if got != tt.want {
|
|
t.Errorf("isSharedDevice(%v) = %v, want %v", tt.info, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloneMap(t *testing.T) {
|
|
orig := map[string]interface{}{"a": "1", "b": 2}
|
|
clone := cloneMap(orig)
|
|
if clone["a"] != "1" || clone["b"] != 2 {
|
|
t.Error("cloneMap should preserve values")
|
|
}
|
|
clone["a"] = "changed"
|
|
if orig["a"] != "1" {
|
|
t.Error("cloneMap should create independent copy")
|
|
}
|
|
}
|
|
|
|
func TestAggregateDeviceState(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did1"] = map[string]interface{}{"did": "did1", "online": false}
|
|
|
|
// All sources offline
|
|
c.deviceListCloud["did1"] = map[string]interface{}{"online": false}
|
|
c.aggregateDeviceState("did1")
|
|
if c.deviceListCache["did1"]["online"].(bool) {
|
|
t.Error("should be offline when all sources offline")
|
|
}
|
|
|
|
// Cloud online
|
|
c.deviceListCloud["did1"] = map[string]interface{}{"online": true}
|
|
c.aggregateDeviceState("did1")
|
|
if !c.deviceListCache["did1"]["online"].(bool) {
|
|
t.Error("should be online when cloud is online")
|
|
}
|
|
|
|
// Gateway online
|
|
c.deviceListCloud["did1"] = map[string]interface{}{"online": false}
|
|
c.deviceListGateway["did1"] = map[string]interface{}{"online": true}
|
|
c.aggregateDeviceState("did1")
|
|
if !c.deviceListCache["did1"]["online"].(bool) {
|
|
t.Error("should be online when gateway is online")
|
|
}
|
|
|
|
// LAN online
|
|
c.deviceListGateway["did1"] = map[string]interface{}{"online": false}
|
|
c.deviceListLan["did1"] = map[string]interface{}{"online": true}
|
|
c.aggregateDeviceState("did1")
|
|
if !c.deviceListCache["did1"]["online"].(bool) {
|
|
t.Error("should be online when LAN is online")
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
}
|
|
|
|
// ============================================================================
|
|
// ScheduleRefreshDevices
|
|
// ============================================================================
|
|
|
|
func TestScheduleRefreshDevices_SetsTimer(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.ScheduleRefreshDevices(100 * time.Millisecond)
|
|
|
|
c.mu.Lock()
|
|
timer := c.refreshCloudDevicesTimer
|
|
c.mu.Unlock()
|
|
|
|
if timer == nil {
|
|
t.Error("refreshCloudDevicesTimer should be set")
|
|
}
|
|
|
|
// Cancel to avoid actual refresh
|
|
if timer != nil {
|
|
timer.Stop()
|
|
}
|
|
}
|
|
|
|
func TestScheduleRefreshDevices_ReplacesTimer(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
firstTimer := time.NewTimer(time.Hour)
|
|
c.refreshCloudDevicesTimer = firstTimer
|
|
|
|
c.ScheduleRefreshDevices(100 * time.Millisecond)
|
|
|
|
c.mu.Lock()
|
|
secondTimer := c.refreshCloudDevicesTimer
|
|
c.mu.Unlock()
|
|
|
|
if secondTimer == firstTimer {
|
|
t.Error("ScheduleRefreshDevices should replace existing timer")
|
|
}
|
|
|
|
if secondTimer != nil {
|
|
secondTimer.Stop()
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// OnDeviceStateChanged
|
|
// ============================================================================
|
|
|
|
func TestOnDeviceStateChanged_UpdatesCache(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did1"] = map[string]interface{}{
|
|
"did": "did1",
|
|
"online": false,
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
|
|
c.OnDeviceStateChanged("did1", map[string]interface{}{
|
|
"online": true,
|
|
})
|
|
|
|
c.deviceListMu.RLock()
|
|
online := c.deviceListCache["did1"]["online"]
|
|
c.deviceListMu.RUnlock()
|
|
|
|
if online != true {
|
|
t.Errorf("online should be true, got %v", online)
|
|
}
|
|
}
|
|
|
|
func TestOnDeviceStateChanged_NonExistentDevice(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
// Should not panic for non-existent device
|
|
c.OnDeviceStateChanged("nonexistent", map[string]interface{}{
|
|
"online": true,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// GetDeviceInfo nil check
|
|
// ============================================================================
|
|
|
|
func TestGetDeviceInfo_EmptyCache(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
// Empty cache
|
|
info := c.GetDeviceInfo("anything")
|
|
if info != nil {
|
|
t.Errorf("GetDeviceInfo on empty cache should return nil, got %v", info)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// 米家删除设备必须从缓存中剔除
|
|
// ============================================================================
|
|
|
|
// TestPruneRemovedDevices_RemovesAbsentDevice 云端不再返回的设备要从三源 + 缓存里彻底剔除。
|
|
func TestPruneRemovedDevices_RemovesAbsentDevice(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did-keep"] = map[string]interface{}{"did": "did-keep", "home_id": "h1", "online": true}
|
|
c.deviceListCache["did-gone"] = map[string]interface{}{"did": "did-gone", "home_id": "h1", "online": true}
|
|
c.deviceListCloud["did-gone"] = map[string]interface{}{"did": "did-gone", "home_id": "h1", "online": true}
|
|
c.deviceListGateway["did-gone"] = map[string]interface{}{"did": "did-gone", "online": false}
|
|
c.deviceListLan["did-gone"] = map[string]interface{}{"did": "did-gone", "online": false}
|
|
|
|
removed := c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{
|
|
"did-keep": {"did": "did-keep", "home_id": "h1", "online": true},
|
|
}, nil)
|
|
|
|
if len(removed) != 1 || removed[0] != "did-gone" {
|
|
t.Fatalf("removed = %v, want [did-gone]", removed)
|
|
}
|
|
if _, ok := c.deviceListCache["did-gone"]; ok {
|
|
t.Error("deviceListCache 应剔除已删设备")
|
|
}
|
|
if _, ok := c.deviceListCloud["did-gone"]; ok {
|
|
t.Error("deviceListCloud 应剔除已删设备")
|
|
}
|
|
if _, ok := c.deviceListGateway["did-gone"]; ok {
|
|
t.Error("deviceListGateway 应剔除已删设备")
|
|
}
|
|
if _, ok := c.deviceListLan["did-gone"]; ok {
|
|
t.Error("deviceListLan 应剔除已删设备")
|
|
}
|
|
if _, ok := c.deviceListCache["did-keep"]; !ok {
|
|
t.Error("云端仍存在的设备不应被剔除")
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
}
|
|
|
|
// TestPruneRemovedDevices_StartupCacheOnly 回归:重启后 deviceListCloud 尚为空,
|
|
// 只遍历它(旧实现)会漏掉「停机期间在米家被删除」的设备,必须遍历缓存。
|
|
func TestPruneRemovedDevices_StartupCacheOnly(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did-keep"] = map[string]interface{}{"did": "did-keep", "home_id": "h1", "online": false}
|
|
c.deviceListCache["did-gone"] = map[string]interface{}{"did": "did-gone", "home_id": "h1", "online": false}
|
|
|
|
removed := c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{
|
|
"did-keep": {"did": "did-keep", "home_id": "h1", "online": true},
|
|
}, nil)
|
|
|
|
if len(removed) != 1 || removed[0] != "did-gone" {
|
|
t.Fatalf("removed = %v, want [did-gone]", removed)
|
|
}
|
|
if _, ok := c.deviceListCache["did-gone"]; ok {
|
|
t.Error("从 .dict 恢复的已删设备应被剔除")
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
}
|
|
|
|
// TestPruneRemovedDevices_KeepsDeviceReportedOnlineByGatewayOrLan 网关/局域网仍在线则保留。
|
|
func TestPruneRemovedDevices_KeepsDeviceReportedOnlineByGatewayOrLan(t *testing.T) {
|
|
for _, tc := range []string{"gateway", "lan"} {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did-x"] = map[string]interface{}{"did": "did-x", "home_id": "h1", "online": true}
|
|
if tc == "gateway" {
|
|
c.deviceListGateway["did-x"] = map[string]interface{}{"online": true, "push_available": true}
|
|
} else {
|
|
c.deviceListLan["did-x"] = map[string]interface{}{"online": true, "push_available": true}
|
|
}
|
|
|
|
removed := c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{
|
|
"did-other": {"did": "did-other", "home_id": "h1", "online": true},
|
|
}, nil)
|
|
|
|
if len(removed) != 0 {
|
|
t.Errorf("%s 仍在线时不应剔除,removed = %v", tc, removed)
|
|
}
|
|
if _, ok := c.deviceListCache["did-x"]; !ok {
|
|
t.Errorf("%s 仍在线时缓存应保留", tc)
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
c.Stop()
|
|
}
|
|
}
|
|
|
|
// TestPruneRemovedDevices_HomeScope 只刷新部分家庭时,不碰其它家庭与无 home 的共享设备。
|
|
func TestPruneRemovedDevices_HomeScope(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did-h1"] = map[string]interface{}{"did": "did-h1", "home_id": "h1", "online": true}
|
|
c.deviceListCache["did-h2"] = map[string]interface{}{"did": "did-h2", "home_id": "h2", "online": true}
|
|
c.deviceListCache["did-shared"] = map[string]interface{}{"did": "did-shared", "home_id": "", "online": true}
|
|
|
|
// 本次只刷新了 h1:h2 与共享设备不受影响
|
|
removed := c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{
|
|
"did-h1": {"did": "did-h1", "home_id": "h1", "online": true},
|
|
}, map[string]struct{}{"h1": {}})
|
|
|
|
if len(removed) != 0 {
|
|
t.Fatalf("家庭作用域内不应剔除,removed = %v", removed)
|
|
}
|
|
if _, ok := c.deviceListCache["did-h2"]; !ok {
|
|
t.Error("其它家庭的设备不应被剔除")
|
|
}
|
|
if _, ok := c.deviceListCache["did-shared"]; !ok {
|
|
t.Error("无 home_id 的共享设备不应被剔除")
|
|
}
|
|
|
|
// 全量刷新(scope 为空)时,h2 与共享设备都要按云端列表剔除
|
|
removed = c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{
|
|
"did-h1": {"did": "did-h1", "home_id": "h1", "online": true},
|
|
}, nil)
|
|
|
|
if len(removed) != 2 {
|
|
t.Fatalf("全量刷新应剔除 2 台,removed = %v", removed)
|
|
}
|
|
if _, ok := c.deviceListCache["did-h2"]; ok {
|
|
t.Error("did-h2 应被剔除")
|
|
}
|
|
if _, ok := c.deviceListCache["did-shared"]; ok {
|
|
t.Error("did-shared 应被剔除")
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
}
|
|
|
|
// TestPruneRemovedDevices_KeepsSubDevice 子设备(xxx.s1)被归并到父设备,不参与删除判定。
|
|
func TestPruneRemovedDevices_KeepsSubDevice(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["gateway1.s1"] = map[string]interface{}{"did": "gateway1.s1", "home_id": "h1", "online": true}
|
|
|
|
removed := c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{
|
|
"gateway1": {"did": "gateway1", "home_id": "h1", "online": true},
|
|
}, nil)
|
|
|
|
if len(removed) != 0 {
|
|
t.Fatalf("子设备不应被剔除,removed = %v", removed)
|
|
}
|
|
if _, ok := c.deviceListCache["gateway1.s1"]; !ok {
|
|
t.Error("子设备应保留在缓存中")
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
}
|
|
|
|
// TestPruneRemovedDevices_EmptyCloudListSkipsRemoval 云端返回空列表视为接口异常,不清库。
|
|
func TestPruneRemovedDevices_EmptyCloudListSkipsRemoval(t *testing.T) {
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did1"] = map[string]interface{}{"did": "did1", "home_id": "h1", "online": true}
|
|
|
|
removed := c.pruneRemovedDevicesLocked(map[string]map[string]interface{}{}, nil)
|
|
|
|
if len(removed) != 0 {
|
|
t.Errorf("空云列表不应剔除任何设备,removed = %v", removed)
|
|
}
|
|
if _, ok := c.deviceListCache["did1"]; !ok {
|
|
t.Error("空云列表时缓存应保持原样")
|
|
}
|
|
c.deviceListMu.Unlock()
|
|
}
|
|
|
|
// TestRefreshDevices_PrunesDeletedDevice 端到端:打桩云端接口,云端只剩 1 台设备,
|
|
// 缓存里的另一台(米家已删除)必须从 DeviceList() 中消失。
|
|
func TestRefreshDevices_PrunesDeletedDevice(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/homeroom/gethome"):
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"code": float64(0),
|
|
"result": map[string]interface{}{
|
|
"homelist": []interface{}{
|
|
map[string]interface{}{
|
|
"id": float64(100), "name": "网吧2", "uid": float64(7),
|
|
"roomlist": []interface{}{
|
|
map[string]interface{}{
|
|
"id": float64(200), "name": "客厅",
|
|
"dids": []interface{}{"did-keep"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
case strings.Contains(r.URL.Path, "/home/device_list_page"):
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"code": float64(0),
|
|
"result": map[string]interface{}{
|
|
"list": []interface{}{
|
|
map[string]interface{}{
|
|
"did": "did-keep", "name": "保留设备",
|
|
"spec_type": "urn:miot-spec-v2:device:plug:0000A001:x:1",
|
|
"model": "cuco.plug.v3",
|
|
"isOnline": true,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
httpCli, err := NewMIoTHttpClient("cn", "cid", "token")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer httpCli.Close()
|
|
httpCli.baseURL = server.URL
|
|
|
|
c := NewMIoTClient("test", nil, "uid", "cn", CtrlModeCloud)
|
|
defer c.Stop()
|
|
c.SetHTTPClient(httpCli)
|
|
|
|
// 预置缓存(等价于 .dict 恢复):云端已删除的 did-gone 仍在缓存里
|
|
c.deviceListMu.Lock()
|
|
c.deviceListCache["did-keep"] = map[string]interface{}{"did": "did-keep", "name": "保留设备", "home_id": "100", "online": false}
|
|
c.deviceListCache["did-gone"] = map[string]interface{}{"did": "did-gone", "name": "已删设备", "home_id": "100", "online": true}
|
|
c.deviceListCloud["did-gone"] = map[string]interface{}{"did": "did-gone", "name": "已删设备", "home_id": "100", "online": true}
|
|
c.deviceListMu.Unlock()
|
|
|
|
if err = c.RefreshDevices(nil); err != nil {
|
|
t.Fatalf("RefreshDevices: %v", err)
|
|
}
|
|
|
|
list := c.DeviceList()
|
|
if _, ok := list["did-gone"]; ok {
|
|
t.Error("DeviceList() 不应再返回米家已删除的设备")
|
|
}
|
|
if _, ok := list["did-keep"]; !ok {
|
|
t.Error("DeviceList() 应保留云端仍存在的设备")
|
|
}
|
|
}
|