- sensors:read tokens open the sensor endpoints (here and on managed machines through the hub) and nothing else; full tokens are never accepted from a URL - /api/sensors/stream: a shaped query that keeps answering, over SSE - /metrics: Prometheus text format - services, updates, ZFS and SMART as background status sensors - widget library over the sensor API; custom widgets on the dashboard - /embed/widget: one live widget in its own bundle, the only frameable path - Dashboard tab is now Monitors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
78 lines
2.6 KiB
Go
78 lines
2.6 KiB
Go
package sensors
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type fake []Sensor
|
|
|
|
func (f fake) Sensors() []Sensor { return f }
|
|
|
|
func TestSeg(t *testing.T) {
|
|
for in, want := range map[string]string{"/": "root", "/mnt/tank": "mnt_tank", "Package id 0": "package_id_0", "enp3s0": "enp3s0", " ": "unnamed", "Tctl+": "tctl", "nvme0n1": "nvme0n1"} {
|
|
if got := Seg(in); got != want {
|
|
t.Errorf("Seg(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegistryDedupes(t *testing.T) {
|
|
r := NewRegistry(fake{{ID: "temp.nvme.composite", Value: 40.0}}, fake{{ID: "temp.nvme.composite", Value: 50.0}})
|
|
all := r.All()
|
|
if len(all) != 2 || all[0].ID != "temp.nvme.composite" || all[1].ID != "temp.nvme.composite_2" {
|
|
t.Fatalf("got %+v", all)
|
|
}
|
|
}
|
|
|
|
func TestShape(t *testing.T) {
|
|
all := []Sensor{{ID: "cpu.usage", Value: 12.5, Unit: "%"}, {ID: "fan.a.fan1", Value: 800.0}, {ID: "fan.a.fan2", Value: 900.0}, {ID: "net.eth0.up", Value: true}}
|
|
got, err := Shape(all, json.RawMessage(`{"cpu":"cpu.usage","fans":"fan.*","box":{"link":"net.eth0.up","gone":"ups.battery"},"list":["cpu.usage"]}`), false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := map[string]any{
|
|
"cpu": 12.5,
|
|
"fans": map[string]any{"fan.a.fan1": 800.0, "fan.a.fan2": 900.0},
|
|
"box": map[string]any{"link": true, "gone": nil},
|
|
"list": []any{12.5},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("got %#v\nwant %#v", got, want)
|
|
}
|
|
full, _ := Shape(all, json.RawMessage(`"cpu.usage"`), true)
|
|
if s, ok := full.(Sensor); !ok || s.Unit != "%" {
|
|
t.Fatalf("full: got %#v", full)
|
|
}
|
|
for _, bad := range []string{`{"x": 5}`, `{"x": "fan.["}`, `not json`} {
|
|
if _, err := Shape(all, json.RawMessage(bad), false); err == nil {
|
|
t.Errorf("Shape(%s) should fail", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteProm(t *testing.T) {
|
|
var b strings.Builder
|
|
WriteProm(&b, []Sensor{
|
|
{ID: "cpu.usage", Kind: "usage", Unit: "%", Value: 12.5},
|
|
{ID: "ups.runtime", Kind: "duration", Unit: "min", Value: 2.0},
|
|
{ID: "net.eth0.up", Kind: "state", Value: true, Labels: map[string]string{"iface": "eth0"}},
|
|
{ID: "ups.state", Kind: "state", Value: `Nor"mal`},
|
|
{ID: "updates.pending", Kind: "count", Value: 3.0},
|
|
})
|
|
got := b.String()
|
|
for _, want := range []string{
|
|
"# TYPE hamlab_usage_percent gauge\n" + `hamlab_usage_percent{id="cpu.usage"} 12.5`,
|
|
`hamlab_duration_seconds{id="ups.runtime"} 120`,
|
|
`hamlab_state{id="net.eth0.up",iface="eth0"} 1`,
|
|
`hamlab_state{id="ups.state",value="Nor\"mal"} 1`,
|
|
`hamlab_updates_pending{id="updates.pending"} 3`,
|
|
} {
|
|
if !strings.Contains(got, want) {
|
|
t.Errorf("missing %q in:\n%s", want, got)
|
|
}
|
|
}
|
|
}
|