package app import ( "context" "errors" "reflect" "testing" "time" "gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain" "gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/ports/driven" ) func TestEntityAppGetState(t *testing.T) { now := time.Date(2026, 4, 9, 10, 0, 0, 0, time.UTC) t.Run("happy path maps attributes to strings", func(t *testing.T) { app := NewEntityApp(&mockHAClient{ getStateFunc: func(ctx context.Context, entityID string) (*driven.HAState, error) { if entityID != "light.kitchen" { t.Fatalf("GetState() entityID = %q, want %q", entityID, "light.kitchen") } return &driven.HAState{ EntityID: "light.kitchen", State: "on", Attributes: map[string]any{ "friendly_name": "Kitchen", "brightness": 42, "available": true, }, LastChanged: now, LastUpdated: now, }, nil }, }) got, err := app.GetState(context.Background(), "light.kitchen") if err != nil { t.Fatalf("GetState() error = %v", err) } want := &domain.EntityState{ EntityID: "light.kitchen", State: "on", Attributes: map[string]string{ "friendly_name": "Kitchen", "brightness": "42", "available": "true", }, LastChanged: now, LastUpdated: now, } if !reflect.DeepEqual(got, want) { t.Fatalf("GetState() = %#v, want %#v", got, want) } }) t.Run("propagates ha.GetState error", func(t *testing.T) { wantErr := errors.New("missing") app := NewEntityApp(&mockHAClient{ getStateFunc: func(ctx context.Context, entityID string) (*driven.HAState, error) { return nil, wantErr }, }) _, err := app.GetState(context.Background(), "light.kitchen") if !errors.Is(err, wantErr) { t.Fatalf("GetState() error = %v, want %v", err, wantErr) } }) } func TestEntityAppListStates(t *testing.T) { now := time.Date(2026, 4, 9, 10, 0, 0, 0, time.UTC) allStates := []*driven.HAState{ { EntityID: "light.kitchen", State: "on", Attributes: map[string]any{"friendly_name": "Kitchen"}, LastChanged: now, LastUpdated: now, }, { EntityID: "switch.fan", State: "off", Attributes: map[string]any{"friendly_name": "Fan"}, LastChanged: now, LastUpdated: now, }, { EntityID: "sensor.temp", State: "21", Attributes: map[string]any{"unit": "C"}, LastChanged: now, LastUpdated: now, }, } tests := []struct { name string ids []domain.EntityID domainFilter string want []*domain.EntityState }{ { name: "no filters", want: []*domain.EntityState{ haStateToDomain(allStates[0]), haStateToDomain(allStates[1]), haStateToDomain(allStates[2]), }, }, { name: "id filter only", ids: []domain.EntityID{"switch.fan"}, want: []*domain.EntityState{ haStateToDomain(allStates[1]), }, }, { name: "domain filter only", domainFilter: "light", want: []*domain.EntityState{ haStateToDomain(allStates[0]), }, }, { name: "both filters", ids: []domain.EntityID{"light.kitchen", "sensor.temp"}, domainFilter: "light", want: []*domain.EntityState{ haStateToDomain(allStates[0]), }, }, { name: "id not found returns empty", ids: []domain.EntityID{"light.missing"}, want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { app := NewEntityApp(&mockHAClient{ listStatesFunc: func(ctx context.Context) ([]*driven.HAState, error) { return allStates, nil }, }) got, err := app.ListStates(context.Background(), tt.ids, tt.domainFilter) if err != nil { t.Fatalf("ListStates() error = %v", err) } if !reflect.DeepEqual(got, tt.want) { t.Fatalf("ListStates() = %#v, want %#v", got, tt.want) } }) } t.Run("propagates ha.ListStates error", func(t *testing.T) { wantErr := errors.New("list failed") app := NewEntityApp(&mockHAClient{ listStatesFunc: func(ctx context.Context) ([]*driven.HAState, error) { return nil, wantErr }, }) _, err := app.ListStates(context.Background(), nil, "") if !errors.Is(err, wantErr) { t.Fatalf("ListStates() error = %v, want %v", err, wantErr) } }) }