141 lines
4.5 KiB
Go
141 lines
4.5 KiB
Go
package collector
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
dto "github.com/prometheus/client_model/go"
|
|
"lostak.dev/shelly-exporter/configuration"
|
|
)
|
|
|
|
const plugSStatusFixture = `{
|
|
"wifi_sta":{"connected":true,"ssid":"lostak","ip":"192.168.0.30","rssi":-77},
|
|
"cloud":{"enabled":false,"connected":false},
|
|
"mqtt":{"connected":false},
|
|
"time":"23:52","serial":1,"has_update":false,"mac":"E8DB84BC666A",
|
|
"relays":[{"ison":true,"has_timer":false,"overpower":false}],
|
|
"meters":[{"power":166.00,"is_valid":true,"timestamp":1787010749,"counters":[168.129,167.929,167.771],"total":4188430}],
|
|
"temperature":49.40,"overtemperature":false,
|
|
"update":{"status":"unknown","has_update":false,"new_version":"","old_version":"20190516-073020/master@ea1b23db"},
|
|
"ram_total":50832,"ram_free":40188,"fs_size":233681,"fs_free":171182,"uptime":1512798
|
|
}`
|
|
|
|
type statusClientFunc func(context.Context, any) error
|
|
|
|
func (function statusClientFunc) GetStatus(ctx context.Context, destination any) error {
|
|
return function(ctx, destination)
|
|
}
|
|
|
|
func fixtureClient(_ context.Context, destination any) error {
|
|
return json.Unmarshal([]byte(plugSStatusFixture), destination)
|
|
}
|
|
|
|
func TestPlugSCollectorExposesStatusAndEnergyCounter(t *testing.T) {
|
|
deviceCollector, err := NewDeviceCollector(configuration.DeviceConfiguration{
|
|
Name: "office", Product: "plug_s",
|
|
}, statusClientFunc(fixtureClient))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
registry := prometheus.NewPedanticRegistry()
|
|
if err := registry.Register(deviceCollector); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
families, err := registry.Gather()
|
|
if err != nil {
|
|
t.Fatalf("Gather() returned an unexpected error: %v", err)
|
|
}
|
|
energy := findMetricFamily(t, families, "shelly_meter_energy_watt_hours_total")
|
|
if energy.GetType() != dto.MetricType_COUNTER {
|
|
t.Fatalf("energy metric type = %s, want COUNTER", energy.GetType())
|
|
}
|
|
value := energy.Metric[0].GetCounter().GetValue()
|
|
want := 4188430.0 / 60.0
|
|
if math.Abs(value-want) > 0.000001 {
|
|
t.Fatalf("energy counter = %f, want %f", value, want)
|
|
}
|
|
|
|
assertGauge(t, families, "shelly_up", 1)
|
|
assertGauge(t, families, "shelly_meter_power_watts", 166)
|
|
assertGauge(t, families, "shelly_temperature_celsius", 49.4)
|
|
assertGauge(t, families, "shelly_relay_on", 1)
|
|
}
|
|
|
|
func TestPlugSCollectorReportsFailedScrapes(t *testing.T) {
|
|
client := statusClientFunc(func(context.Context, any) error { return errors.New("unreachable") })
|
|
deviceCollector, err := NewDeviceCollector(configuration.DeviceConfiguration{
|
|
Name: "office", Product: "plug-s",
|
|
}, client)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
registry := prometheus.NewRegistry()
|
|
if err := registry.Register(deviceCollector); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for expectedErrors := 1.0; expectedErrors <= 2; expectedErrors++ {
|
|
families, err := registry.Gather()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertGauge(t, families, "shelly_up", 0)
|
|
errorsFamily := findMetricFamily(t, families, "shelly_scrape_errors_total")
|
|
if value := errorsFamily.Metric[0].GetCounter().GetValue(); value != expectedErrors {
|
|
t.Fatalf("scrape errors = %f, want %f", value, expectedErrors)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGroupAllowsMultipleDevicesWithTheSameMetrics(t *testing.T) {
|
|
group := make(Group, 0, 2)
|
|
for _, name := range []string{"office", "kitchen"} {
|
|
deviceCollector, err := NewDeviceCollector(configuration.DeviceConfiguration{
|
|
Name: name, Product: "plug_s",
|
|
}, statusClientFunc(fixtureClient))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
group = append(group, deviceCollector)
|
|
}
|
|
registry := prometheus.NewPedanticRegistry()
|
|
if err := registry.Register(group); err != nil {
|
|
t.Fatalf("Register() returned an unexpected error: %v", err)
|
|
}
|
|
families, err := registry.Gather()
|
|
if err != nil {
|
|
t.Fatalf("Gather() returned an unexpected error: %v", err)
|
|
}
|
|
up := findMetricFamily(t, families, "shelly_up")
|
|
if len(up.Metric) != 2 {
|
|
t.Fatalf("shelly_up has %d metrics, want 2", len(up.Metric))
|
|
}
|
|
}
|
|
|
|
func findMetricFamily(t *testing.T, families []*dto.MetricFamily, name string) *dto.MetricFamily {
|
|
t.Helper()
|
|
for _, family := range families {
|
|
if family.GetName() == name {
|
|
return family
|
|
}
|
|
}
|
|
t.Fatalf("metric family %q not found", name)
|
|
return nil
|
|
}
|
|
|
|
func assertGauge(t *testing.T, families []*dto.MetricFamily, name string, want float64) {
|
|
t.Helper()
|
|
family := findMetricFamily(t, families, name)
|
|
if family.GetType() != dto.MetricType_GAUGE {
|
|
t.Fatalf("%s type = %s, want GAUGE", name, family.GetType())
|
|
}
|
|
if value := family.Metric[0].GetGauge().GetValue(); value != want {
|
|
t.Fatalf("%s = %f, want %f", name, value, want)
|
|
}
|
|
}
|