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, Program_name: "default", }, } func TestStateEquivalent(t *testing.T) { var tests = []struct { state1 DeviceState state2 DeviceState want bool }{ { DeviceState{ Mode: "program", Setpoint: 14, Time: test_time, Program_name: "default", }, DeviceState{ Mode: "always", Setpoint: 13, Time: test_time, }, false, }, { DeviceState{ Mode: "program", Setpoint: 14, Time: test_time, Program_name: "default", }, DeviceState{ Mode: "program", Setpoint: 14, Time: test_time.Add(1 * time.Minute), Program_name: "default", }, false, }, { DeviceState{ Mode: "program", Setpoint: 14, Time: test_time, Program_name: "default", }, DeviceState{ Mode: "program", Setpoint: 13, Time: test_time, Program_name: "default", }, true, }, { DeviceState{ Mode: "program", Setpoint: 14, Time: test_time, Program_name: "default", }, DeviceState{ Mode: "program", Setpoint: 13, Time: test_time, Program_name: "other", }, false, }, { DeviceState{ Mode: "always", Setpoint: 14, Time: test_time, }, DeviceState{ Mode: "always", Setpoint: 14, Time: test_time, Program_name: "other", Until_time: test_time, }, true, }, { DeviceState{ Mode: "always", Setpoint: 14, Time: test_time, }, DeviceState{ Mode: "always", Setpoint: 15, Time: test_time, Program_name: "other", Until_time: test_time, }, false, }, { DeviceState{ Mode: "until_next", Setpoint: 14, Time: test_time, }, DeviceState{ Mode: "until_next", Setpoint: 14, Time: test_time, Program_name: "other", Until_time: test_time, }, true, }, { DeviceState{ Mode: "until_next", Setpoint: 14, Time: test_time, }, DeviceState{ Mode: "until_next", Setpoint: 13, Time: test_time, Program_name: "other", Until_time: test_time, }, false, }, { DeviceState{ Mode: "until_time", Setpoint: 14, Time: test_time, Until_time: test_time.Add(1 * time.Hour), }, DeviceState{ Mode: "until_time", Setpoint: 14, Time: test_time, Program_name: "other", Until_time: test_time.Add(1 * time.Hour), }, true, }, { DeviceState{ Mode: "until_time", Setpoint: 14, Time: test_time, Until_time: test_time.Add(1 * time.Hour), }, DeviceState{ Mode: "until_time", Setpoint: 13, Time: test_time, Program_name: "other", Until_time: test_time.Add(1 * time.Hour), }, false, }, { DeviceState{ Mode: "until_time", Setpoint: 14, Time: test_time, Until_time: test_time.Add(1 * time.Hour), }, DeviceState{ Mode: "until_time", Setpoint: 14, Time: test_time, Program_name: "other", Until_time: 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.Program_name = "" //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.Program_name = "confort" //case 3: program set "confort" but not exist case3_device := test_device case3_device.State.Program_name = "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, Program_name: "", } device2 := test_device device2.Name = "2" device2.State.Program_name = "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(), Until_time: 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), Until_time: 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) } }) } }