410 lines
9.2 KiB
Go
410 lines
9.2 KiB
Go
// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
|
|
//
|
|
// This software is released under the MIT License.
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
//nolint:paralleltest
|
|
//revive:disable:unused-receiver
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/adrg/xdg"
|
|
"github.com/eclipse/paho.golang/paho"
|
|
"github.com/go-test/deep"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
mqtthass "github.com/joshuar/go-hass-anything/v12/pkg/hass"
|
|
mqttapi "github.com/joshuar/go-hass-anything/v12/pkg/mqtt"
|
|
)
|
|
|
|
var mockCommandCallback = func(_ *paho.Publish) {}
|
|
|
|
var mockButton = mqtthass.NewButtonEntity().
|
|
WithDetails(
|
|
mqtthass.App("test"),
|
|
mqtthass.Name("test button"),
|
|
mqtthass.ID("test_button"),
|
|
mqtthass.OriginInfo(&mqtthass.Origin{}),
|
|
mqtthass.DeviceInfo(&mqtthass.Device{}),
|
|
mqtthass.Icon("mdi:test"),
|
|
).
|
|
WithCommand(
|
|
mqtthass.CommandCallback(mockCommandCallback),
|
|
)
|
|
|
|
var mockSwitch = mqtthass.NewSwitchEntity().
|
|
WithDetails(
|
|
mqtthass.App("test"),
|
|
mqtthass.Name("test switch"),
|
|
mqtthass.ID("test_switch"),
|
|
mqtthass.OriginInfo(&mqtthass.Origin{}),
|
|
mqtthass.DeviceInfo(&mqtthass.Device{}),
|
|
mqtthass.Icon("mdi:test"),
|
|
).
|
|
WithCommand(
|
|
mqtthass.CommandCallback(mockCommandCallback),
|
|
).
|
|
OptimisticMode()
|
|
|
|
var mockNumber = mqtthass.NewNumberEntity[int]().
|
|
WithDetails(
|
|
mqtthass.App("test"),
|
|
mqtthass.Name("test number"),
|
|
mqtthass.ID("test_number"),
|
|
mqtthass.OriginInfo(&mqtthass.Origin{}),
|
|
mqtthass.DeviceInfo(&mqtthass.Device{}),
|
|
mqtthass.Icon("mdi:test"),
|
|
).
|
|
WithCommand(
|
|
mqtthass.CommandCallback(mockCommandCallback),
|
|
).
|
|
WithStep(1).
|
|
WithMin(0).
|
|
WithMax(100).
|
|
WithMode(mqtthass.NumberAuto)
|
|
|
|
func TestController_Subscriptions(t *testing.T) {
|
|
var mockButtonSubscription, mockSwitchSubscription, mockNumberSubscription *mqttapi.Subscription
|
|
var err error
|
|
|
|
mockButtonSubscription, err = mockButton.MarshalSubscription()
|
|
require.NoError(t, err)
|
|
mockSwitchSubscription, err = mockSwitch.MarshalSubscription()
|
|
require.NoError(t, err)
|
|
mockNumberSubscription, err = mockNumber.MarshalSubscription()
|
|
require.NoError(t, err)
|
|
|
|
type fields struct {
|
|
buttons []*mqtthass.ButtonEntity
|
|
switches []*mqtthass.SwitchEntity
|
|
intNumbers []*mqtthass.NumberEntity[int]
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want []*mqttapi.Subscription
|
|
}{
|
|
{
|
|
name: "with subscriptions",
|
|
fields: fields{
|
|
buttons: []*mqtthass.ButtonEntity{mockButton},
|
|
switches: []*mqtthass.SwitchEntity{mockSwitch},
|
|
intNumbers: []*mqtthass.NumberEntity[int]{mockNumber},
|
|
},
|
|
want: []*mqttapi.Subscription{
|
|
mockButtonSubscription,
|
|
mockSwitchSubscription,
|
|
mockNumberSubscription,
|
|
},
|
|
},
|
|
{
|
|
name: "without subscriptions",
|
|
want: []*mqttapi.Subscription{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
d := &Worker{
|
|
buttons: tt.fields.buttons,
|
|
switches: tt.fields.switches,
|
|
intNumbers: tt.fields.intNumbers,
|
|
}
|
|
got := d.Subscriptions()
|
|
if diff := deep.Equal(got, tt.want); diff != nil {
|
|
t.Error(diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestController_Configs(t *testing.T) {
|
|
var mockButtonConfig, mockSwitchConfig, mockNumberConfig *mqttapi.Msg
|
|
var err error
|
|
|
|
mockButtonConfig, err = mockButton.MarshalConfig()
|
|
require.NoError(t, err)
|
|
mockSwitchConfig, err = mockSwitch.MarshalConfig()
|
|
require.NoError(t, err)
|
|
mockNumberConfig, err = mockNumber.MarshalConfig()
|
|
require.NoError(t, err)
|
|
|
|
type fields struct {
|
|
buttons []*mqtthass.ButtonEntity
|
|
switches []*mqtthass.SwitchEntity
|
|
intNumbers []*mqtthass.NumberEntity[int]
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want []*mqttapi.Msg
|
|
}{
|
|
{
|
|
name: "with configs",
|
|
fields: fields{
|
|
buttons: []*mqtthass.ButtonEntity{mockButton},
|
|
switches: []*mqtthass.SwitchEntity{mockSwitch},
|
|
intNumbers: []*mqtthass.NumberEntity[int]{mockNumber},
|
|
},
|
|
want: []*mqttapi.Msg{
|
|
mockButtonConfig,
|
|
mockSwitchConfig,
|
|
mockNumberConfig,
|
|
},
|
|
},
|
|
{
|
|
name: "without configs",
|
|
want: []*mqttapi.Msg{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
d := &Worker{
|
|
buttons: tt.fields.buttons,
|
|
switches: tt.fields.switches,
|
|
intNumbers: tt.fields.intNumbers,
|
|
}
|
|
got := d.Configs()
|
|
if diff := deep.Equal(got, tt.want); diff != nil {
|
|
t.Error(diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewCommandsController(t *testing.T) {
|
|
mockDevice := &mqtthass.Device{}
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
device *mqtthass.Device
|
|
configDir string
|
|
}
|
|
tests := []struct {
|
|
want *Worker
|
|
args args
|
|
name string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "unreadable commands file",
|
|
wantErr: true,
|
|
args: args{ctx: context.TODO(), configDir: "testdata/unreadable", device: mockDevice},
|
|
},
|
|
{
|
|
name: "invalid commands file",
|
|
wantErr: true,
|
|
args: args{ctx: context.TODO(), configDir: "testdata/invalid", device: mockDevice},
|
|
},
|
|
{
|
|
name: "valid commands file",
|
|
args: args{ctx: context.TODO(), configDir: "testdata/valid", device: mockDevice},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
xdg.ConfigHome = tt.args.configDir
|
|
_, err := NewCommandsWorker(tt.args.ctx, tt.args.device)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("NewCommandsController() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_switchCmd(t *testing.T) {
|
|
type args struct {
|
|
command string
|
|
state string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful command",
|
|
args: args{command: "true", state: "someState"},
|
|
},
|
|
{
|
|
name: "unsuccessful command",
|
|
args: args{command: "false", state: "someState"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "no state",
|
|
args: args{command: "true"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := cmdWithState(tt.args.command, tt.args.state); (err != nil) != tt.wantErr {
|
|
t.Errorf("switchCmd() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_cmdWithoutState(t *testing.T) {
|
|
type args struct {
|
|
command string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful command",
|
|
args: args{command: "true"},
|
|
},
|
|
{
|
|
name: "unsuccessful command",
|
|
args: args{command: "false"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := cmdWithoutState(tt.args.command); (err != nil) != tt.wantErr {
|
|
t.Errorf("cmdWithoutState() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_cmdWithState(t *testing.T) {
|
|
type args struct {
|
|
command string
|
|
state string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful command",
|
|
args: args{command: "true", state: "foo"},
|
|
},
|
|
{
|
|
name: "unsuccessful command",
|
|
args: args{command: "false", state: "foo"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := cmdWithState(tt.args.command, tt.args.state); (err != nil) != tt.wantErr {
|
|
t.Errorf("cmdWithState() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_switchState(t *testing.T) {
|
|
type args struct {
|
|
command string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want json.RawMessage
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "switch ON",
|
|
args: args{command: "echo ON"},
|
|
want: json.RawMessage(`ON`),
|
|
},
|
|
{
|
|
name: "switch OFF",
|
|
args: args{command: "echo OFF"},
|
|
want: json.RawMessage(`OFF`),
|
|
},
|
|
{
|
|
name: "unsuccessful",
|
|
args: args{command: "false"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "unknown output",
|
|
args: args{command: "echo SOMETHING"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := switchState(tt.args.command)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("switchState() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("switchState() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_numberState(t *testing.T) {
|
|
type args struct {
|
|
command string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want json.RawMessage
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid int",
|
|
args: args{command: "echo 1"},
|
|
want: json.RawMessage(`{ "value": 1 }`),
|
|
},
|
|
{
|
|
name: "valid float",
|
|
args: args{command: "echo 0.567"},
|
|
want: json.RawMessage(`{ "value": 0.567 }`),
|
|
},
|
|
{
|
|
name: "invalid (quoted)",
|
|
args: args{command: `echo "1"`},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid (string)",
|
|
args: args{command: `echo some string`},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid (multiple outputs)",
|
|
args: args{command: `echo 0.6788 1.3453`},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "unsuccessful",
|
|
args: args{command: "false"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := numberState(tt.args.command)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("numberState() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("numberState() = %v, want %v", string(got), string(tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|