github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/tests/plugins/node/node_test.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package node
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/e154/smart-home/adaptors"
    29  	"github.com/e154/smart-home/common/events"
    30  	"github.com/e154/smart-home/plugins/node"
    31  	"github.com/e154/smart-home/system/bus"
    32  	"github.com/e154/smart-home/system/mqtt"
    33  	"github.com/e154/smart-home/system/scripts"
    34  	"github.com/e154/smart-home/system/supervisor"
    35  	"github.com/e154/smart-home/system/zigbee2mqtt"
    36  	. "github.com/e154/smart-home/tests/plugins"
    37  	. "github.com/smartystreets/goconvey/convey"
    38  )
    39  
    40  func TestNode(t *testing.T) {
    41  
    42  	Convey("node", t, func(ctx C) {
    43  		_ = container.Invoke(func(adaptors *adaptors.Adaptors,
    44  			scriptService scripts.ScriptService,
    45  			supervisor supervisor.Supervisor,
    46  			zigbee2mqtt zigbee2mqtt.Zigbee2mqtt,
    47  			mqttServer mqtt.MqttServ,
    48  			eventBus bus.Bus) {
    49  
    50  			// register plugins
    51  			err := AddPlugin(adaptors, "node")
    52  			ctx.So(err, ShouldBeNil)
    53  
    54  			serviceCh := WaitService(eventBus, time.Second*5, "Supervisor", "Mqtt")
    55  			pluginsCh := WaitPlugins(eventBus, time.Second*5, "node")
    56  			go mqttServer.Start()
    57  			supervisor.Start(context.Background())
    58  			defer mqttServer.Shutdown()
    59  			defer supervisor.Shutdown(context.Background())
    60  			So(<-serviceCh, ShouldBeTrue)
    61  			So(<-pluginsCh, ShouldBeTrue)
    62  
    63  			// add entity
    64  			// ------------------------------------------------
    65  
    66  			nodeEnt := GetNewNode("main")
    67  			err = adaptors.Entity.Add(context.Background(), nodeEnt)
    68  			ctx.So(err, ShouldBeNil)
    69  
    70  			eventBus.Publish("system/models/entities/"+nodeEnt.Id.String(), events.EventCreatedEntityModel{
    71  				EntityId: nodeEnt.Id,
    72  			})
    73  
    74  			time.Sleep(time.Second)
    75  
    76  			// common
    77  			// ------------------------------------------------
    78  			var closed = false
    79  			ch := make(chan events.EventStateChanged)
    80  			defer close(ch)
    81  			fn := func(topic string, msg interface{}) {
    82  				switch v := msg.(type) {
    83  				case events.EventStateChanged:
    84  					if !closed {
    85  						ch <- v
    86  					}
    87  				}
    88  			}
    89  			eventBus.Subscribe("system/entities/+", fn, false)
    90  			defer eventBus.Unsubscribe("system/entities/+", fn)
    91  			defer func() {
    92  				closed = true
    93  			}()
    94  			// ------------------------------------------------
    95  
    96  			// wait message
    97  			_, ok := WaitT[events.EventStateChanged](time.Second*2, ch)
    98  			ctx.So(ok, ShouldBeTrue)
    99  
   100  			now := time.Now()
   101  
   102  			t.Run("ping", func(t *testing.T) {
   103  				Convey("case", t, func(ctx C) {
   104  
   105  					b, err := json.Marshal(node.MessageStatus{
   106  						Status:    "enabled",
   107  						Thread:    1,
   108  						Rps:       2,
   109  						Min:       3,
   110  						Max:       4,
   111  						StartedAt: now,
   112  					})
   113  					ctx.So(err, ShouldBeNil)
   114  					err = mqttServer.Publish("system/plugins/node/main/ping", b, 0, false)
   115  					ctx.So(err, ShouldBeNil)
   116  
   117  					// wait message
   118  					msg, ok := WaitT[events.EventStateChanged](time.Second*2, ch)
   119  					ctx.So(ok, ShouldBeTrue)
   120  
   121  					ctx.So(msg.OldState.State, ShouldNotBeNil)
   122  					ctx.So(msg.OldState.State.Name, ShouldEqual, "wait")
   123  					ctx.So(msg.NewState.State, ShouldNotBeNil)
   124  					ctx.So(msg.NewState.State.Name, ShouldEqual, "connected")
   125  					ctx.So(msg.NewState.Attributes[node.AttrThread].Int64(), ShouldEqual, 1)
   126  					ctx.So(msg.NewState.Attributes[node.AttrRps].Int64(), ShouldEqual, 2)
   127  					ctx.So(msg.NewState.Attributes[node.AttrMin].Int64(), ShouldEqual, 3)
   128  					ctx.So(msg.NewState.Attributes[node.AttrMax].Int64(), ShouldEqual, 4)
   129  					ctx.So(msg.NewState.Attributes[node.AttrStartedAt].Time(), ShouldEqual, now)
   130  					ctx.So(ok, ShouldBeTrue)
   131  				})
   132  			})
   133  
   134  			mqttCli := mqttServer.NewClient("cli")
   135  
   136  			t.Run("request", func(t *testing.T) {
   137  				Convey("case", t, func(ctx C) {
   138  					ch := make(chan struct{})
   139  					defer close(ch)
   140  					_ = mqttCli.Subscribe("system/plugins/node/main/req/#", func(client mqtt.MqttCli, message mqtt.Message) {
   141  						req := node.MessageRequest{}
   142  						err = json.Unmarshal(message.Payload, &req)
   143  						ctx.So(err, ShouldBeNil)
   144  						ctx.So(req.EntityId, ShouldEqual, "plugin.test")
   145  						ctx.So(req.DeviceType, ShouldEqual, "test")
   146  						ch <- struct{}{}
   147  					})
   148  					defer mqttCli.Unsubscribe("home/node/main/req/#")
   149  
   150  					req := node.MessageRequest{
   151  						EntityId:   "plugin.test",
   152  						DeviceType: "test",
   153  						Properties: nil,
   154  						Command:    nil,
   155  					}
   156  					eventBus.Publish(fmt.Sprintf("system/plugins/node/main/req/%s", nodeEnt.Id), req)
   157  
   158  					// wait message
   159  					_, ok := WaitT[struct{}](time.Second*2, ch)
   160  					ctx.So(ok, ShouldBeTrue)
   161  
   162  				})
   163  			})
   164  
   165  			t.Run("response", func(t *testing.T) {
   166  				Convey("case", t, func(ctx C) {
   167  
   168  					ch := make(chan struct{})
   169  					defer close(ch)
   170  					topic := fmt.Sprintf("system/plugins/node/main/resp/%s", "plugin.test")
   171  					fn := func(topic string, resp node.MessageResponse) {
   172  						ctx.So(topic, ShouldEqual, "system/plugins/node/main/resp/plugin.test")
   173  						ctx.So(resp.EntityId, ShouldEqual, "plugin.test")
   174  						ctx.So(resp.DeviceType, ShouldEqual, "test")
   175  						ctx.So(resp.Status, ShouldEqual, "success")
   176  						ch <- struct{}{}
   177  					}
   178  					_ = eventBus.Subscribe(topic, fn)
   179  					defer func() { _ = eventBus.Unsubscribe(topic, fn) }()
   180  
   181  					time.Sleep(time.Millisecond * 500)
   182  
   183  					b, err := json.Marshal(node.MessageResponse{
   184  						EntityId:   "plugin.test",
   185  						DeviceType: "test",
   186  						Properties: nil,
   187  						Response:   nil,
   188  						Status:     "success",
   189  					})
   190  					ctx.So(err, ShouldBeNil)
   191  
   192  					_ = mqttCli.Publish(fmt.Sprintf("system/plugins/node/main/resp/%s", "plugin.test"), b)
   193  
   194  					// wait message
   195  					_, ok := WaitT[struct{}](time.Second*2, ch)
   196  					ctx.So(ok, ShouldBeTrue)
   197  
   198  					time.Sleep(time.Millisecond * 500)
   199  				})
   200  			})
   201  		})
   202  	})
   203  }