- Implement tests for the Entity gRPC service, covering GetState and ListStates methods. - Create tests for the Light gRPC service, including TurnOn, TurnOff, Toggle, and ListLights methods. - Introduce mock service implementations to simulate behavior and validate interactions. - Add logging interceptor tests to ensure proper logging levels based on handler errors. - Develop application layer tests for entity and light functionalities, ensuring correct state management and error propagation.
173 lines
4.2 KiB
Go
173 lines
4.2 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|