package server import ( "context" "encoding/json" "errors" "io" "log/slog" "testing" devicecontrol "git.cactoz.su/cacto/home_automatization/proto/device_control" ) type fakeShadow struct { err error patches []map[string]any } func (f *fakeShadow) PatchDesiredState(_ context.Context, _ string, patch map[string]any) error { if f.err != nil { return f.err } f.patches = append(f.patches, patch) return nil } type fakeMQTT struct { err error topic string payload []byte } func (f *fakeMQTT) Publish(topic string, _ byte, _ bool, payload []byte) error { if f.err != nil { return f.err } f.topic = topic f.payload = payload return nil } func discardLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } func TestTurnOn_Success(t *testing.T) { sh := &fakeShadow{} mq := &fakeMQTT{} s := New(sh, mq, discardLogger()) res, err := s.TurnOn(context.Background(), &devicecontrol.TurnOnRequest{DeviceId: "d1"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !res.Success { t.Fatalf("expected success, got error %q", res.Error) } if len(sh.patches) != 1 || sh.patches[0]["power"] != "on" { t.Fatalf("got desired patches %+v, want [{power: on}]", sh.patches) } if mq.topic != "devices/d1/commands" { t.Fatalf("got topic %q, want devices/d1/commands", mq.topic) } var payload map[string]any if err := json.Unmarshal(mq.payload, &payload); err != nil { t.Fatalf("unmarshal published payload: %v", err) } if payload["action"] != "turn_on" { t.Fatalf("got payload %+v, want action=turn_on", payload) } } func TestSetLevel_Success(t *testing.T) { sh := &fakeShadow{} mq := &fakeMQTT{} s := New(sh, mq, discardLogger()) res, err := s.SetLevel(context.Background(), &devicecontrol.SetLevelRequest{DeviceId: "d1", Level: 42.5}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !res.Success { t.Fatalf("expected success, got error %q", res.Error) } if sh.patches[0]["level"] != 42.5 { t.Fatalf("got desired patch %+v, want level=42.5", sh.patches[0]) } } func TestDispatch_MissingDeviceID(t *testing.T) { s := New(&fakeShadow{}, &fakeMQTT{}, discardLogger()) res, err := s.TurnOff(context.Background(), &devicecontrol.TurnOffRequest{DeviceId: ""}) if err != nil { t.Fatalf("unexpected transport error: %v", err) } if res.Success { t.Fatal("expected failure for missing device_id") } } func TestDispatch_ShadowFailure(t *testing.T) { s := New(&fakeShadow{err: errors.New("redis down")}, &fakeMQTT{}, discardLogger()) res, err := s.TurnOn(context.Background(), &devicecontrol.TurnOnRequest{DeviceId: "d1"}) if err != nil { t.Fatalf("unexpected transport error: %v", err) } if res.Success { t.Fatal("expected failure when shadow patch errors") } } func TestDispatch_PublishFailure(t *testing.T) { s := New(&fakeShadow{}, &fakeMQTT{err: errors.New("broker unreachable")}, discardLogger()) res, err := s.TurnOn(context.Background(), &devicecontrol.TurnOnRequest{DeviceId: "d1"}) if err != nil { t.Fatalf("unexpected transport error: %v", err) } if res.Success { t.Fatal("expected failure when mqtt publish errors") } }