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) } }) }