From 3200089fa4e754d8c79314d247da2b2071a2dbd3 Mon Sep 17 00:00:00 2001 From: m1saka Date: Wed, 22 Jul 2026 17:06:04 +0800 Subject: [PATCH] fix: accept non-controllable device properties --- device_test.go | 44 ++++++++++++++++++++++++ testdata/miot-spec-non-controllable.html | 6 ++++ 2 files changed, 50 insertions(+) create mode 100644 testdata/miot-spec-non-controllable.html diff --git a/device_test.go b/device_test.go index b6bf1df..a6fcd99 100644 --- a/device_test.go +++ b/device_test.go @@ -104,6 +104,15 @@ func loadSpecFixture(t *testing.T) []byte { return fixture } +func loadNonControllableSpecFixture(t *testing.T) []byte { + t.Helper() + fixture, err := os.ReadFile("testdata/miot-spec-non-controllable.html") + if err != nil { + t.Fatal(err) + } + return fixture +} + func TestGetDeviceInfoParsesSpecAndCaches(t *testing.T) { testServer := newDeviceTestServer(t, loadSpecFixture(t), nil) httpClient := testServer.server.Client() @@ -171,6 +180,41 @@ func TestParseDeviceInfoRejectsUnknownActionInput(t *testing.T) { } } +func TestGetDeviceInfoPreservesNonControllableProperties(t *testing.T) { + testServer := newDeviceTestServer(t, loadNonControllableSpecFixture(t), nil) + cacheDir := t.TempDir() + info, err := GetDeviceInfo(context.Background(), testServer.server.Client(), "test.sensor.v1", cacheDir) + if err != nil { + t.Fatal(err) + } + if len(info.Properties) != 2 || info.Properties[0].Name != "event" || info.Properties[0].RW != "" || info.Properties[1].Name != "command" || info.Properties[1].RW != "" { + t.Fatalf("properties = %#v", info.Properties) + } + if len(info.Actions) != 1 || len(info.Actions[0].Inputs) != 1 || !reflect.DeepEqual(info.Actions[0].Inputs[0], info.Properties[1]) { + t.Fatalf("actions = %#v", info.Actions) + } + + testServer.fixture = nil + cached, err := GetDeviceInfo(context.Background(), testServer.server.Client(), "test.sensor.v1", cacheDir) + if err != nil { + t.Fatal(err) + } + if len(cached.Properties) != 2 || cached.Properties[0].RW != "" || cached.Properties[1].RW != "" || + len(cached.Actions) != 1 || len(cached.Actions[0].Inputs) != 1 || !reflect.DeepEqual(cached.Actions[0].Inputs[0], cached.Properties[1]) || testServer.specCalls != 1 { + t.Fatalf("cached = %#v, spec calls = %d", cached, testServer.specCalls) + } +} + +func TestDeviceRejectsGetSetForNonControllableProperty(t *testing.T) { + device := Device{properties: map[string]PropertySpec{"event": {Name: "event", Type: "string", SIID: 2, PIID: 1}}} + if _, err := device.Get(context.Background(), "event"); err == nil || !strings.Contains(err.Error(), "不可读取") { + t.Fatalf("Get() error = %v", err) + } + if err := device.Set(context.Background(), "event", "value"); err == nil || !strings.Contains(err.Error(), "不可写入") { + t.Fatalf("Set() error = %v", err) + } +} + func TestDeviceActionsDeepCopyInputs(t *testing.T) { device := Device{actions: map[string]ActionSpec{"toggle": {Inputs: []PropertySpec{{Range: []json.Number{"1", "2"}, ValueList: []ValueListItem{{Value: "1"}}}}}}} actions := device.Actions() diff --git a/testdata/miot-spec-non-controllable.html b/testdata/miot-spec-non-controllable.html new file mode 100644 index 0000000..00fb009 --- /dev/null +++ b/testdata/miot-spec-non-controllable.html @@ -0,0 +1,6 @@ + + + + + +