456 lines
9.7 KiB
Go
456 lines
9.7 KiB
Go
package device
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// monday
|
|
var test_time = time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local)
|
|
|
|
var test_presets = []Preset{
|
|
{Label: "default", Value: 17, Color: "#012a36"},
|
|
{Label: "normal", Value: 19, Color: "#b6244f"},
|
|
}
|
|
|
|
var test_setpoints = []Setpoint{
|
|
{Start: 7 * 60, Preset_id: 1},
|
|
{Start: 8 * 60, Preset_id: 0},
|
|
{Start: 16 * 60, Preset_id: 1},
|
|
{Start: 22 * 60, Preset_id: 0},
|
|
}
|
|
|
|
var test_weekprogram = WeekProgram{
|
|
Monday: test_setpoints,
|
|
Thuesday: test_setpoints,
|
|
Wednesday: test_setpoints,
|
|
Thursday: test_setpoints,
|
|
Friday: test_setpoints,
|
|
Saturday: test_setpoints,
|
|
Sunday: test_setpoints,
|
|
}
|
|
|
|
var test_programs = Programs{
|
|
"default": test_weekprogram,
|
|
}
|
|
|
|
var test_device = Device{
|
|
Name: "valid",
|
|
Settings: DeviceSettings{
|
|
Programs: test_programs,
|
|
Presets: test_presets,
|
|
TVR: DefaultTVRSettings,
|
|
},
|
|
CurrentSetpoint: 0,
|
|
State: DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "default",
|
|
},
|
|
}
|
|
|
|
func TestStateEquivalent(t *testing.T) {
|
|
|
|
var tests = []struct {
|
|
state1 DeviceState
|
|
state2 DeviceState
|
|
want bool
|
|
}{
|
|
{
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "default",
|
|
},
|
|
DeviceState{
|
|
Mode: "always",
|
|
Setpoint: 13,
|
|
Time: test_time,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "default",
|
|
},
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 14,
|
|
Time: test_time.Add(1 * time.Minute),
|
|
ProgramName: "default",
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "default",
|
|
},
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 13,
|
|
Time: test_time,
|
|
ProgramName: "default",
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "default",
|
|
},
|
|
DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 13,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "always",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
},
|
|
DeviceState{
|
|
Mode: "always",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "always",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
},
|
|
DeviceState{
|
|
Mode: "always",
|
|
Setpoint: 15,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "until_next",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
},
|
|
DeviceState{
|
|
Mode: "until_next",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "until_next",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
},
|
|
DeviceState{
|
|
Mode: "until_next",
|
|
Setpoint: 13,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
UntilTime: test_time.Add(1 * time.Hour),
|
|
},
|
|
DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time.Add(1 * time.Hour),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
UntilTime: test_time.Add(1 * time.Hour),
|
|
},
|
|
DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 13,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time.Add(1 * time.Hour),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
UntilTime: test_time.Add(1 * time.Hour),
|
|
},
|
|
DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 14,
|
|
Time: test_time,
|
|
ProgramName: "other",
|
|
UntilTime: test_time.Add(2 * time.Hour),
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
testname := fmt.Sprintf("%d", i)
|
|
t.Run(testname, func(t *testing.T) {
|
|
r := tt.state1.Equivalent(tt.state2)
|
|
if r != tt.want {
|
|
t.Errorf("got %t, want %t", r, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStateTopic(t *testing.T) {
|
|
topic := test_device.StateTopic()
|
|
if topic != "heater/valid/state" {
|
|
t.Errorf("Got %s; want heater/valid/state", topic)
|
|
}
|
|
}
|
|
|
|
func TestListenTopic(t *testing.T) {
|
|
topic, err := test_device.ListenTopic()
|
|
want := "zigbee2mqtt/TVR/valid"
|
|
if err != nil {
|
|
t.Errorf("Got %s", err.Error())
|
|
}
|
|
if topic != want {
|
|
t.Errorf("Got %s; want %s", topic, want)
|
|
}
|
|
}
|
|
|
|
func TestProgram(t *testing.T) {
|
|
//case 1: no program set in state return default
|
|
case1_device := test_device
|
|
case1_device.State.ProgramName = ""
|
|
|
|
//case 2: program set "confort" must return it
|
|
var test_confort_weekprogram = WeekProgram{
|
|
Monday: test_setpoints,
|
|
Thuesday: test_setpoints,
|
|
Wednesday: test_setpoints,
|
|
Thursday: test_setpoints,
|
|
Friday: test_setpoints,
|
|
Saturday: test_setpoints,
|
|
Sunday: test_setpoints,
|
|
}
|
|
case2_device := test_device
|
|
case2_device.Settings.Programs = Programs{
|
|
"default": test_weekprogram,
|
|
"confort": test_confort_weekprogram,
|
|
}
|
|
case2_device.State.ProgramName = "confort"
|
|
|
|
//case 3: program set "confort" but not exist
|
|
case3_device := test_device
|
|
case3_device.State.ProgramName = "confort"
|
|
|
|
var tests = []struct {
|
|
name string
|
|
device Device
|
|
result WeekProgram
|
|
err error
|
|
}{
|
|
{"case 1 no program set use default", case1_device, DefaultWeekProgram, nil},
|
|
{"case 2 program confort", case2_device, test_confort_weekprogram, nil},
|
|
{"case 3 program confort no defined", case3_device, WeekProgram{}, Error("device valid don't have confort program")},
|
|
}
|
|
for _, tt := range tests {
|
|
testname := fmt.Sprintf("%s", tt.name)
|
|
t.Run(testname, func(t *testing.T) {
|
|
prog, err := tt.device.Program()
|
|
if err != tt.err {
|
|
t.Errorf("got %s, want %s", err, tt.err)
|
|
}
|
|
if !reflect.DeepEqual(prog, tt.result) {
|
|
t.Errorf("got %v, want %v", prog, tt.result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
// device 1: currentSetpoint 0 program, not specified expect default
|
|
// device 2: currentSetpoint 0 program, unknown one, expect error
|
|
// device 3: currentSetpoint 0 always, 22
|
|
// device 4: currentSetpoint 22 until_time, fixed at timeNow, for 2h 22
|
|
// device 5: currentSetpoint 17 indem 4 but fixed timeNow-2h
|
|
// device 6: currentSetpoint 17, until_next set a timeNow, test time time now +1h : no change
|
|
|
|
timeNow = func() time.Time {
|
|
return test_time
|
|
}
|
|
|
|
device1 := test_device
|
|
device1.Name = "1"
|
|
device1.State = DeviceState{
|
|
Mode: "program",
|
|
Setpoint: 0,
|
|
Time: test_time,
|
|
ProgramName: "",
|
|
}
|
|
|
|
device2 := test_device
|
|
device2.Name = "2"
|
|
device2.State.ProgramName = "unknown"
|
|
|
|
device3 := test_device
|
|
device3.Name = "3"
|
|
device3.State = DeviceState{
|
|
Mode: "always",
|
|
Setpoint: 22,
|
|
}
|
|
|
|
device4 := test_device
|
|
device4.Name = "4"
|
|
device4.CurrentSetpoint = 22
|
|
device4.State = DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 22,
|
|
Time: timeNow(),
|
|
UntilTime: timeNow().Add(2 * time.Hour),
|
|
}
|
|
|
|
device5 := test_device
|
|
device5.Name = "5"
|
|
device5.CurrentSetpoint = 17
|
|
device5.State = DeviceState{
|
|
Mode: "until_time",
|
|
Setpoint: 22,
|
|
Time: timeNow().Add(-2 * time.Hour),
|
|
UntilTime: timeNow().Add(-1 * time.Minute),
|
|
}
|
|
|
|
device6 := test_device
|
|
device6.Name = "6"
|
|
device6.CurrentSetpoint = 22
|
|
device6.State = DeviceState{
|
|
Mode: "until_next",
|
|
Setpoint: 22,
|
|
Time: timeNow(),
|
|
}
|
|
|
|
var tests = []struct {
|
|
getTime func() time.Time
|
|
device Device
|
|
want []Message
|
|
change bool
|
|
err error
|
|
}{
|
|
{timeNow, device1, []Message{
|
|
{
|
|
Payload: []byte("{\"current_heating_setpoint\": 17}"),
|
|
Topic: "zigbee2mqtt/TVR/1/set",
|
|
Retain: false,
|
|
},
|
|
}, true, nil},
|
|
{timeNow, device2, []Message{}, false, Error("device 2 don't have unknown program")},
|
|
{timeNow, device3, []Message{
|
|
{
|
|
Payload: []byte("{\"current_heating_setpoint\": 22}"),
|
|
Topic: "zigbee2mqtt/TVR/3/set",
|
|
Retain: false,
|
|
},
|
|
}, true, nil},
|
|
{timeNow, device4, []Message{}, false, nil},
|
|
{timeNow, device5, []Message{}, true, nil},
|
|
{timeNow, device6, []Message{}, false, nil},
|
|
{func() time.Time { return test_time.Add(7*time.Hour + 30*time.Minute) }, device6, []Message{
|
|
{
|
|
Payload: []byte("{\"current_heating_setpoint\": 19}"),
|
|
Topic: "zigbee2mqtt/TVR/6/set",
|
|
Retain: false,
|
|
},
|
|
}, true, nil},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
testname := fmt.Sprintf("%s", tt.device.Name)
|
|
t.Run(testname, func(t *testing.T) {
|
|
|
|
timeNow = tt.getTime
|
|
result := []Message{}
|
|
|
|
rchan := make(chan Message, 10)
|
|
errchan := make(chan error, 1)
|
|
changechan := make(chan bool, 1)
|
|
|
|
go func(result chan Message, changechan chan bool, errchan chan error) {
|
|
logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger()
|
|
change, err := tt.device.update(logger, result)
|
|
errchan <- err
|
|
changechan <- change
|
|
close(rchan)
|
|
close(changechan)
|
|
close(errchan)
|
|
}(rchan, changechan, errchan)
|
|
|
|
for msg := range rchan {
|
|
result = append(result, msg)
|
|
}
|
|
|
|
err, ok := <-errchan
|
|
if !ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err != tt.err {
|
|
t.Errorf("got %s, want %s", err, tt.err)
|
|
}
|
|
|
|
change, ok := <-changechan
|
|
if !ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if change != tt.change {
|
|
t.Errorf("got %s, want %s", err, tt.err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(result, tt.want) {
|
|
t.Errorf("got %v, want %v", result, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|