generated from rmcguire/go-server-with-otel
149 lines
4.1 KiB
Go
149 lines
4.1 KiB
Go
package econetui
|
|
|
|
import (
|
|
"context"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
pb "gitea.libretechconsulting.com/rmcguire/econet-exporter/api/econet/v1alpha1"
|
|
"gitea.libretechconsulting.com/rmcguire/econet-exporter/pkg/config"
|
|
)
|
|
|
|
func newTestServer(t *testing.T) *EconetUIServer {
|
|
t.Helper()
|
|
// grpc is nil: the render/view helpers under test never touch it.
|
|
return NewEconetUIServer(context.Background(), &config.ServiceConfig{}, nil)
|
|
}
|
|
|
|
func sampleDevice() *pb.Device {
|
|
return &pb.Device{
|
|
SerialNumber: "SN123",
|
|
FriendlyName: "Garage Water Heater",
|
|
GenericType: "heatpumpWaterHeater",
|
|
Connected: true,
|
|
Mode: "energy-saving",
|
|
Running: true,
|
|
RunningState: "compressor-running",
|
|
Setpoint: 120,
|
|
HotWaterAvailability: 66,
|
|
AlertCount: 1,
|
|
SupportedModes: []pb.Mode{
|
|
pb.Mode_MODE_HEAT_PUMP,
|
|
pb.Mode_MODE_ENERGY_SAVING,
|
|
pb.Mode_MODE_HIGH_DEMAND,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestCardRendersWithPending(t *testing.T) {
|
|
s := newTestServer(t)
|
|
d := sampleDevice()
|
|
|
|
// Simulate an in-flight change to a mode different from the reported one.
|
|
s.pending.set(d.SerialNumber, "heat-pump")
|
|
|
|
cv := s.toCardView(d, true)
|
|
if cv.Pending != "Heat Pump" {
|
|
t.Fatalf("pending = %q, want %q", cv.Pending, "Heat Pump")
|
|
}
|
|
if cv.Icon != "/static/icons/heatpump.svg" {
|
|
t.Fatalf("icon = %q", cv.Icon)
|
|
}
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.render(rec, "card", cv)
|
|
body := rec.Body.String()
|
|
|
|
for _, want := range []string{
|
|
`id="card-SN123"`,
|
|
"Garage Water Heater",
|
|
`hx-post="/ui/devices/SN123/mode"`,
|
|
"badge-pending",
|
|
"Updating", // "Updating → Heat Pump…"
|
|
"Energy Saving",
|
|
"Heat Pump",
|
|
"High Demand",
|
|
"120°F",
|
|
"66%",
|
|
"fill-meter", // fill-level graph
|
|
"fill-high", // 66% → high bucket (>=66)
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("card body missing %q\n---\n%s", want, body)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCardConvergenceClearsPending(t *testing.T) {
|
|
s := newTestServer(t)
|
|
d := sampleDevice()
|
|
d.Mode = "heat-pump" // reported now matches the desired
|
|
|
|
s.pending.set(d.SerialNumber, "heat-pump")
|
|
cv := s.toCardView(d, true)
|
|
|
|
if cv.Pending != "" {
|
|
t.Fatalf("pending should clear once reported catches up, got %q", cv.Pending)
|
|
}
|
|
if _, ok := s.pending.get(d.SerialNumber); ok {
|
|
t.Fatalf("pending entry should have been removed after convergence")
|
|
}
|
|
}
|
|
|
|
func TestDevicesEmptyState(t *testing.T) {
|
|
s := newTestServer(t)
|
|
rec := httptest.NewRecorder()
|
|
s.render(rec, "devices", pageView{})
|
|
if !strings.Contains(rec.Body.String(), "No EcoNet devices loaded yet") {
|
|
t.Errorf("empty state not rendered:\n%s", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestIconForLoose(t *testing.T) {
|
|
cases := map[string]string{
|
|
"heatpumpWaterHeater": "/static/icons/heatpump.svg",
|
|
"heatpumpWaterHeaterGen5": "/static/icons/heatpump.svg",
|
|
"HeatPump": "/static/icons/heatpump.svg",
|
|
"Hybrid Electric": "/static/icons/heatpump.svg",
|
|
"heat_pump_water_heater": "/static/icons/heatpump.svg",
|
|
"gasWaterHeater": "/static/icons/gas.svg",
|
|
"tanklessWaterHeater": "/static/icons/tankless.svg",
|
|
"electricWaterHeater": "/static/icons/electric.svg",
|
|
"somethingElse": "/static/icons/unknown.svg",
|
|
"": "/static/icons/unknown.svg",
|
|
}
|
|
for in, want := range cases {
|
|
if got := iconFor(in); got != want {
|
|
t.Errorf("iconFor(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDebugTemplateEscapes(t *testing.T) {
|
|
s := newTestServer(t)
|
|
rec := httptest.NewRecorder()
|
|
s.render(rec, "debug", `{"@NAME":"<b>x</b>"}`)
|
|
body := rec.Body.String()
|
|
if strings.Contains(body, "<b>") {
|
|
t.Errorf("debug output not escaped: %s", body)
|
|
}
|
|
if !strings.Contains(body, "<b>") {
|
|
t.Errorf("expected escaped JSON, got: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestPrettyMode(t *testing.T) {
|
|
cases := map[string]string{
|
|
"heat-pump": "Heat Pump",
|
|
"energy-saving": "Energy Saving",
|
|
"off": "Off",
|
|
"": "Unknown",
|
|
}
|
|
for in, want := range cases {
|
|
if got := prettyMode(in); got != want {
|
|
t.Errorf("prettyMode(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|