generated from rmcguire/go-server-with-otel
add ui, new config opts
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user