Nik Afiq 8f7024edfa
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 24s
CI / build-ai-gateway (push) Successful in 1m6s
CI / build-ha-gateway (push) Successful in 1m3s
CI / build-discord-bot (push) Successful in 1m4s
CI / build-alexa-bridge (push) Successful in 1m15s
CI / build-tts-gateway (push) Successful in 1m5s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat(climate): add SetTemperature method to ClimateService
- Implemented SetTemperature in ClimateService for setting an absolute target temperature.
- Updated ClimateServiceClient and ClimateServiceServer interfaces to include SetTemperature.
- Added corresponding handler and tests for SetTemperature in ClimateGRPC.
- Modified ClimateApp to handle SetTemperature requests without clamping.
- Updated climate.proto to define SetTemperatureRequest message.
- Adjusted Dockerfiles to include alexa-bridge dependencies.
2026-07-25 12:08:24 +09:00

110 lines
3.6 KiB
Go

package entities
import (
"context"
"errors"
"sort"
"testing"
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
"google.golang.org/grpc"
)
type fakeEntityServiceClient struct {
listStatesFunc func(ctx context.Context, in *hav1.ListStatesRequest) (*hav1.ListStatesResponse, error)
}
func (f *fakeEntityServiceClient) GetState(ctx context.Context, in *hav1.GetStateRequest, _ ...grpc.CallOption) (*hav1.GetStateResponse, error) {
return &hav1.GetStateResponse{}, nil
}
func (f *fakeEntityServiceClient) ListStates(ctx context.Context, in *hav1.ListStatesRequest, _ ...grpc.CallOption) (*hav1.ListStatesResponse, error) {
if f.listStatesFunc == nil {
return &hav1.ListStatesResponse{}, nil
}
return f.listStatesFunc(ctx, in)
}
func TestClientFetchAll(t *testing.T) {
t.Run("fetches per domain and extracts friendly_name", func(t *testing.T) {
fake := &fakeEntityServiceClient{
listStatesFunc: func(ctx context.Context, in *hav1.ListStatesRequest) (*hav1.ListStatesResponse, error) {
switch in.GetDomain() {
case "light":
return &hav1.ListStatesResponse{States: []*hav1.EntityState{
{EntityId: "light.living_room", Attributes: map[string]string{"friendly_name": "Living Room Lamp"}},
}}, nil
case "switch":
return &hav1.ListStatesResponse{States: []*hav1.EntityState{
{EntityId: "switch.fan", Attributes: map[string]string{"friendly_name": "Fan"}},
}}, nil
case "climate":
return &hav1.ListStatesResponse{States: []*hav1.EntityState{
{EntityId: "climate.air_conditioner", Attributes: map[string]string{}},
}}, nil
}
t.Fatalf("unexpected domain %q", in.GetDomain())
return nil, nil
},
}
c := NewClient(fake, []string{"light", "switch", "climate"})
got, err := c.FetchAll(context.Background())
if err != nil {
t.Fatalf("FetchAll() error = %v", err)
}
sort.Slice(got, func(i, j int) bool { return got[i].EntityID < got[j].EntityID })
want := []struct {
entityID, friendlyName, domain string
}{
{"climate.air_conditioner", "climate.air_conditioner", "climate"}, // no friendly_name attr -> falls back to entity_id
{"light.living_room", "Living Room Lamp", "light"},
{"switch.fan", "Fan", "switch"},
}
if len(got) != len(want) {
t.Fatalf("len(got) = %d, want %d (got=%#v)", len(got), len(want), got)
}
for i, w := range want {
if got[i].EntityID != w.entityID || got[i].FriendlyName != w.friendlyName || got[i].Domain != w.domain {
t.Fatalf("got[%d] = %#v, want {%s %s %s}", i, got[i], w.entityID, w.friendlyName, w.domain)
}
}
})
t.Run("one domain failing fails the whole fetch", func(t *testing.T) {
wantErr := errors.New("boom")
fake := &fakeEntityServiceClient{
listStatesFunc: func(ctx context.Context, in *hav1.ListStatesRequest) (*hav1.ListStatesResponse, error) {
if in.GetDomain() == "switch" {
return nil, wantErr
}
return &hav1.ListStatesResponse{}, nil
},
}
c := NewClient(fake, []string{"light", "switch", "climate"})
got, err := c.FetchAll(context.Background())
if err == nil {
t.Fatal("FetchAll() error = nil, want error")
}
if !errors.Is(err, wantErr) {
t.Fatalf("FetchAll() error = %v, want it to wrap %v", err, wantErr)
}
if got != nil {
t.Fatalf("FetchAll() entities = %#v, want nil on partial failure", got)
}
})
t.Run("empty domain list returns empty result", func(t *testing.T) {
c := NewClient(&fakeEntityServiceClient{}, nil)
got, err := c.FetchAll(context.Background())
if err != nil {
t.Fatalf("FetchAll() error = %v", err)
}
if len(got) != 0 {
t.Fatalf("FetchAll() = %#v, want empty", got)
}
})
}