github.com/simpleiot/simpleiot@v0.18.3/client/rule_test.go (about) 1 package client_test 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/nats-io/nats.go" 9 "github.com/simpleiot/simpleiot/client" 10 "github.com/simpleiot/simpleiot/data" 11 "github.com/simpleiot/simpleiot/server" 12 ) 13 14 type ruleTestServer struct { 15 t *testing.T 16 root data.NodeEdge 17 nc *nats.Conn 18 stop func() 19 vin client.Variable 20 vin2 client.Variable 21 vout client.Variable 22 r client.Rule 23 c client.Condition 24 c2 client.Condition 25 a client.Action 26 a2 client.ActionInactive 27 voutGet func() client.Variable 28 voutStop func() 29 lastvout float64 30 lastCheck string 31 } 32 33 func (rts *ruleTestServer) checkVout(expected float64, msg string, pointKey string) { 34 rts.lastCheck = msg 35 if rts.lastvout == expected { 36 // vout is not changing, so delay here to make sure the rule 37 // has time to run before we check the result 38 time.Sleep(time.Millisecond * 75) 39 } 40 41 start := time.Now() 42 for { 43 if rts.voutGet().Value[pointKey] == expected { 44 rts.lastvout = expected 45 // all is well 46 break 47 } 48 if time.Since(start) > time.Second { 49 rts.t.Fatalf("vout failed, expected: %v, test: %v", expected, msg) 50 } 51 <-time.After(time.Millisecond * 10) 52 } 53 } 54 55 func (rts *ruleTestServer) sendPoint(id string, point data.Point) { 56 point.Origin = "test" 57 err := client.SendNodePoint(rts.nc, id, point, true) 58 59 if err != nil { 60 rts.t.Errorf("Error sending point: %v, last check: %v", err, rts.lastCheck) 61 } 62 } 63 64 func setupRuleTest(t *testing.T, numConditions int) (ruleTestServer, error) { 65 66 var r ruleTestServer 67 var err error 68 69 r.t = t 70 71 r.nc, r.root, r.stop, err = server.TestServer() 72 73 if err != nil { 74 return r, fmt.Errorf("Error starting test server: %w", err) 75 } 76 // send test nodes to Db 77 r.vin = client.Variable{ 78 ID: "ID-varin", 79 Parent: r.root.ID, 80 Description: "var in", 81 } 82 83 err = client.SendNodeType(r.nc, r.vin, "test") 84 if err != nil { 85 return r, fmt.Errorf("Error sending vin node: %w", err) 86 } 87 88 r.vout = client.Variable{ 89 ID: "ID-varout", 90 Parent: r.root.ID, 91 Description: "var out", 92 } 93 94 err = client.SendNodeType(r.nc, r.vout, "test") 95 if err != nil { 96 return r, fmt.Errorf("Error sending vout node: %w", err) 97 } 98 99 r.r = client.Rule{ 100 ID: "ID-rule", 101 Parent: r.root.ID, 102 Description: "test rule", 103 Disabled: false, 104 } 105 106 err = client.SendNodeType(r.nc, r.r, "test") 107 if err != nil { 108 return r, fmt.Errorf("Error sending r node: %w", err) 109 } 110 111 r.c = client.Condition{ 112 ID: "ID-condition", 113 Parent: r.r.ID, 114 Description: "cond vin high", 115 ConditionType: data.PointValuePointValue, 116 PointType: data.PointTypeValue, 117 ValueType: data.PointValueOnOff, 118 NodeID: r.vin.ID, 119 Operator: data.PointValueEqual, 120 Value: 1, 121 } 122 123 err = client.SendNodeType(r.nc, r.c, "test") 124 if err != nil { 125 return r, fmt.Errorf("Error sending c node: %w", err) 126 } 127 128 if numConditions > 1 { 129 // send test nodes to Db 130 r.vin2 = client.Variable{ 131 ID: "ID-varin2", 132 Parent: r.root.ID, 133 Description: "var in2", 134 } 135 136 err = client.SendNodeType(r.nc, r.vin2, "test") 137 if err != nil { 138 return r, fmt.Errorf("Error sending vin2 node: %w", err) 139 } 140 141 r.c2 = client.Condition{ 142 ID: "ID-condition2", 143 Parent: r.r.ID, 144 Description: "cond vin2 high", 145 ConditionType: data.PointValuePointValue, 146 PointType: data.PointTypeValue, 147 ValueType: data.PointValueOnOff, 148 NodeID: r.vin2.ID, 149 Operator: data.PointValueEqual, 150 Value: 1, 151 } 152 153 err = client.SendNodeType(r.nc, r.c2, "test") 154 if err != nil { 155 return r, fmt.Errorf("Error sending c node: %w", err) 156 } 157 } 158 159 r.a = client.Action{ 160 ID: "ID-action-active", 161 Parent: r.r.ID, 162 Description: "action active", 163 Action: data.PointValueSetValue, 164 PointType: data.PointTypeValue, 165 NodeID: r.vout.ID, 166 Value: 1, 167 } 168 169 err = client.SendNodeType(r.nc, r.a, "test") 170 if err != nil { 171 return r, fmt.Errorf("Error sending a node: %w", err) 172 } 173 174 // FIXME: 175 // this delay is required to work around a bug in the manager 176 // where it is resetting and does not see the ActionInactive points 177 // See https://github.com/simpleiot/simpleiot/issues/630 178 // the tools/test-rules.sh script can be used to test a fix for this 179 // problem 180 time.Sleep(100 * time.Millisecond) 181 182 r.a2 = client.ActionInactive{ 183 ID: "ID-action-inactive", 184 Parent: r.r.ID, 185 Description: "action inactive", 186 Action: data.PointValueSetValue, 187 PointType: data.PointTypeValue, 188 NodeID: r.vout.ID, 189 Value: 0, 190 } 191 192 err = client.SendNodeType(r.nc, r.a2, "test") 193 if err != nil { 194 return r, fmt.Errorf("Error sending a2 node: %w", err) 195 } 196 197 // set up a node watcher to watch the output variable 198 r.voutGet, r.voutStop, err = client.NodeWatcher[client.Variable](r.nc, r.vout.ID, r.vout.Parent) 199 200 if err != nil { 201 return r, fmt.Errorf("Error setting up watcher: %w", err) 202 } 203 204 // wait for rule to get set up 205 time.Sleep(250 * time.Millisecond) 206 207 return r, nil 208 } 209 210 // TestRules populates a rule in the system that watches 211 // a variable and when set, sets another variable. This 212 // tests out the basic rule logic. 213 func TestRule(t *testing.T) { 214 r, err := setupRuleTest(t, 1) 215 if err != nil { 216 t.Fatal("Rule test setup failed: ", err) 217 } 218 219 defer r.stop() 220 defer r.voutStop() 221 222 r.checkVout(0, "initial value", "0") 223 224 // set vin and look for vout to change 225 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 226 r.checkVout(1, "look for vout to change after set vin", "0") 227 228 // clear vin and look for vout to change 229 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 0}) 230 r.checkVout(0, "look for vout to clear", "0") 231 } 232 233 /* 234 leave everything enabled and toggle vin and watch vout toggle -- same as the TestRules() function. This ensures that your test is setup correctly. 235 disable rule, set vin and verify vout does not get set. Then clear vin. 236 - enable rule, and disable condition. set vin and verify vout does not get set. Clear vin. 237 - enable condition, and disable action. set vin and verify vout does not get set. Clear vin. 238 - enable action, set vin, then disable rule. verify vout gets cleared. 239 - enable rule, and verify vout gets set. 240 - disable condition, and verify vout gets cleared. 241 - enable condition, and verify vout gets set. 242 */ 243 func TestRuleDisabled(t *testing.T) { 244 r, err := setupRuleTest(t, 1) 245 if err != nil { 246 t.Fatal("Rule test setup failed: ", err) 247 } 248 249 defer r.stop() 250 defer r.voutStop() 251 252 r.checkVout(0, "check initial state", "0") 253 254 // everything enabled, set vin and look for vout to change 255 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 256 r.checkVout(1, "set vin and look for vout to change", "0") 257 258 // everything enabled, clear vin and look for vout to change 259 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 0}) 260 r.checkVout(0, "clear vin and look for vout to change", "0") 261 262 // disable rule, set vin and verify vout does not get set. Then clear vin. 263 r.sendPoint(r.r.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 264 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 265 r.checkVout(0, "disable rule, set vin and verify vout does not get set", "0") 266 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 0}) 267 268 // enable rule, and disable condition. set vin and verify vout does not get set. 269 r.sendPoint(r.r.ID, data.Point{Type: data.PointTypeDisabled, Value: 0}) 270 r.sendPoint(r.c.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 271 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 272 r.checkVout(0, "disable condition and verify vout does not get set", "0") 273 274 // enable condition and verify vout gets set again. Clear vin. 275 r.sendPoint(r.c.ID, data.Point{Type: data.PointTypeDisabled, Value: 0}) 276 r.checkVout(1, "re-enable condition and verify vout is set.", "0") 277 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 0}) 278 279 // enable condition, and disable action. set vin and verify vout does not get set. Clear vin. 280 r.sendPoint(r.a.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 281 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 282 r.checkVout(0, "disable action and verify vout does not get set.", "0") 283 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 0}) 284 285 // enable action, set vin, then disable rule. verify vout gets cleared. 286 r.sendPoint(r.a.ID, data.Point{Type: data.PointTypeDisabled, Value: 0}) 287 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 288 r.checkVout(1, "disable rule, initial state", "0") 289 r.sendPoint(r.r.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 290 r.checkVout(0, "disable rule, vout cleared.", "0") 291 292 // enable rule, and verify vout gets set. 293 r.sendPoint(r.r.ID, data.Point{Type: data.PointTypeDisabled, Value: 0}) 294 r.checkVout(1, "enable rule, and verify vout gets set.", "0") 295 296 // disable condition, and verify vout gets cleared. 297 r.sendPoint(r.c.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 298 r.checkVout(0, "disable condition, and verify vout gets cleared.", "0") 299 300 // enable condition, and verify vout gets set. 301 r.sendPoint(r.c.ID, data.Point{Type: data.PointTypeDisabled, Value: 0}) 302 r.checkVout(1, "enable condition, and verify vout gets set.", "0") 303 } 304 305 /* 306 if one condition is active and the 2nd condition is disabled, the rule fires 307 if both conditions are disabled, the rule is inactive. 308 */ 309 func TestRuleMultipleConditions(t *testing.T) { 310 r, err := setupRuleTest(t, 2) 311 if err != nil { 312 t.Fatal("Rule test setup failed: ", err) 313 } 314 315 defer r.stop() 316 defer r.voutStop() 317 318 r.checkVout(0, "initial condition", "0") 319 320 // both conditions enabled 321 // if one condition is active and the 2nd condition is inactive, the rule should not fire 322 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 323 r.checkVout(0, "1st active, 2nd inactive", "0") 324 325 // if both conditions are active the rule should fire 326 r.sendPoint(r.vin2.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 327 r.checkVout(1, "both active", "0") 328 329 // if one condition is disabled, the rule should still fire because 330 // the enabled condition is still active 331 r.sendPoint(r.c.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 332 r.checkVout(1, "one condition enabled and action", "0") 333 334 // if both conditions are active but disabled, the rule is inactive. 335 r.sendPoint(r.c.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 336 r.sendPoint(r.c2.ID, data.Point{Type: data.PointTypeDisabled, Value: 1}) 337 r.checkVout(0, "both active and disabled", "0") 338 } 339 340 /* 341 Test PointKey of Action Node. 342 */ 343 func TestRuleActionPointKey(t *testing.T) { 344 r, err := setupRuleTest(t, 1) 345 if err != nil { 346 t.Fatal("Rule test setup failed: ", err) 347 } 348 349 // we are setting the an action with key set to "1", so modify the rule 350 r.sendPoint(r.a.ID, data.Point{Type: data.PointTypePointKey, Text: "1"}) 351 r.sendPoint(r.a2.ID, data.Point{Type: data.PointTypePointKey, Text: "1"}) 352 353 defer r.stop() 354 defer r.voutStop() 355 356 r.checkVout(0, "inital value", "1") 357 358 // check if point is set correctly. 359 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 1}) 360 r.checkVout(1, "should be high", "1") 361 362 // check if point is cleared correctly 363 r.sendPoint(r.vin.ID, data.Point{Type: data.PointTypeValue, Value: 0}) 364 365 r.checkVout(0, "should be low", "1") 366 }