Nik Afiq b327150d45
All checks were successful
CI / test (push) Successful in 5s
CI / build-ai-gateway (push) Successful in 39s
CI / build-ha-gateway (push) Successful in 39s
CI / build-discord-bot (push) Successful in 40s
feat: implement ClimateApp for managing HVAC entities
- 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.
2026-07-23 13:49:54 +09:00

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
}