- Added ClimateApp to handle climate entity operations including turning on/off, adjusting temperature, and setting HVAC modes. - Implemented caching mechanism for climate entities to optimize state retrieval. - Created domain model for Climate with attributes such as current temperature, target temperature, and HVAC modes. - Developed unit tests for ClimateApp to ensure functionality and correctness. feat: add RemoteApp for SwitchBot commands - Introduced RemoteApp to facilitate sending commands to SwitchBot devices. - Implemented SendCommand method for executing device commands without state management. chore: update configuration for SwitchBot integration - Added SwitchBotToken and SwitchBotSecret to configuration for enabling SwitchBot Cloud commands. feat: define gRPC services for Climate and Remote operations - Created climate.proto and remote.proto files to define gRPC services for climate management and remote command execution. - Implemented corresponding request and response message structures for gRPC interactions.
101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
package grpc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
|
|
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/grpc/test/bufconn"
|
|
)
|
|
|
|
type mockRemoteService struct {
|
|
sendCommandFunc func(ctx context.Context, deviceID, command, commandType string) error
|
|
}
|
|
|
|
func (m *mockRemoteService) SendCommand(ctx context.Context, deviceID, command, commandType string) error {
|
|
if m.sendCommandFunc == nil {
|
|
return nil
|
|
}
|
|
return m.sendCommandFunc(ctx, deviceID, command, commandType)
|
|
}
|
|
|
|
func TestRemoteGRPCSendCommand(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
wantCode codes.Code
|
|
}{
|
|
{name: "happy path", wantCode: codes.OK},
|
|
{name: "generic error maps to codes.Internal", err: errors.New("boom"), wantCode: codes.Internal},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var gotDeviceID, gotCommand, gotCommandType string
|
|
conn := newRemoteTestClientConn(t, &mockRemoteService{
|
|
sendCommandFunc: func(ctx context.Context, deviceID, command, commandType string) error {
|
|
gotDeviceID = deviceID
|
|
gotCommand = command
|
|
gotCommandType = commandType
|
|
return tt.err
|
|
},
|
|
})
|
|
client := hav1.NewRemoteServiceClient(conn)
|
|
|
|
_, err := client.SendCommand(context.Background(), &hav1.SendCommandRequest{
|
|
DeviceId: "dyson-id",
|
|
Command: "turnOn",
|
|
CommandType: "command",
|
|
})
|
|
if status.Code(err) != tt.wantCode {
|
|
t.Fatalf("status code = %v, want %v", status.Code(err), tt.wantCode)
|
|
}
|
|
if tt.wantCode != codes.OK {
|
|
return
|
|
}
|
|
|
|
if gotDeviceID != "dyson-id" || gotCommand != "turnOn" || gotCommandType != "command" {
|
|
t.Fatalf("SendCommand args = (%q, %q, %q), want (%q, %q, %q)",
|
|
gotDeviceID, gotCommand, gotCommandType, "dyson-id", "turnOn", "command")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func newRemoteTestClientConn(t *testing.T, svc *mockRemoteService) *grpc.ClientConn {
|
|
t.Helper()
|
|
|
|
lis := bufconn.Listen(testBufSize)
|
|
server := grpc.NewServer()
|
|
hav1.RegisterRemoteServiceServer(server, NewRemoteGRPC(svc))
|
|
go func() {
|
|
_ = server.Serve(lis)
|
|
}()
|
|
t.Cleanup(func() {
|
|
server.Stop()
|
|
_ = lis.Close()
|
|
})
|
|
|
|
conn, err := grpc.DialContext(
|
|
context.Background(),
|
|
"bufnet",
|
|
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
|
|
return lis.Dial()
|
|
}),
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("grpc.DialContext() error = %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = conn.Close()
|
|
})
|
|
return conn
|
|
}
|