Files
beszel-ipv6/agent/battery/battery_linux_test.go
henrygd 87405c5f10 feat: add multi-battery monitoring
- Report battery data for individual devices
- Select a representative battery for legacy fields and alerts
- Average named battery data independently
- Display multiple batteries in system charts
- Add cross-platform coverage and transport tests
2026-08-16 13:52:29 -04:00

110 lines
3.6 KiB
Go

//go:build testing && linux
package battery
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type fakeBattery struct{ id, name, capacity, status, full, scope string }
func setupFakeSysfs(t *testing.T) (string, func(fakeBattery)) {
t.Helper()
root := t.TempDir()
previousRoot := batteryRoot
batteryRoot = root
t.Cleanup(func() { batteryRoot = previousRoot })
write := func(path, value string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
require.NoError(t, os.WriteFile(path, []byte(value), 0o644))
}
add := func(b fakeBattery) {
t.Helper()
dir := filepath.Join(root, b.id)
write(filepath.Join(dir, "type"), "Battery")
if b.capacity != "" {
write(filepath.Join(dir, "capacity"), b.capacity)
}
write(filepath.Join(dir, "status"), b.status)
if b.name != "" {
write(filepath.Join(dir, "model_name"), b.name)
}
if b.full != "" {
write(filepath.Join(dir, "energy_full"), b.full)
}
if b.scope != "" {
write(filepath.Join(dir, "scope"), b.scope)
}
}
return root, add
}
func TestParseSysfsState(t *testing.T) {
assert.Equal(t, stateEmpty, parseSysfsState("Empty"))
assert.Equal(t, stateFull, parseSysfsState("Full"))
assert.Equal(t, stateCharging, parseSysfsState("Charging"))
assert.Equal(t, stateDischarging, parseSysfsState("Discharging"))
assert.Equal(t, stateIdle, parseSysfsState("Not charging"))
assert.Equal(t, stateUnknown, parseSysfsState("SomethingElse"))
}
func TestGetBatteryStatsMultipleNamedAndPrimary(t *testing.T) {
_, add := setupFakeSysfs(t)
add(fakeBattery{id: "BAT0", name: "Primary", capacity: "105", status: "Charging", full: "5000", scope: "System"})
add(fakeBattery{id: "hidpp_battery_0", name: "MX Keys S", capacity: "55", status: "Unknown", full: "900", scope: "Device"})
batteries, err := GetBatteryStats()
require.NoError(t, err)
require.Len(t, batteries, 2)
assert.Equal(t, "Primary", batteries[0].Name)
assert.Equal(t, uint8(100), batteries[0].Percent)
assert.Equal(t, stateUnknown, batteries[1].State)
primary, ok := Primary(batteries)
require.True(t, ok)
assert.Equal(t, "Primary", primary.Name)
}
func TestGetBatteryStatsFallbackDuplicatesAndUnreadable(t *testing.T) {
root, add := setupFakeSysfs(t)
add(fakeBattery{id: "BAT0", name: "Keyboard", capacity: "80", status: "Discharging"})
add(fakeBattery{id: "BAT1", name: "Keyboard", capacity: "-4", status: "SomethingWeird"})
add(fakeBattery{id: "BAT2", capacity: "not-a-number", status: "Charging"})
add(fakeBattery{id: "BAT3", capacity: "42", status: "Full"})
ac := filepath.Join(root, "AC0")
require.NoError(t, os.MkdirAll(ac, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(ac, "type"), []byte("Mains"), 0o644))
batteries, err := GetBatteryStats()
require.NoError(t, err)
require.Len(t, batteries, 3)
assert.Equal(t, "Keyboard", batteries[0].Name)
assert.Equal(t, "Keyboard (2)", batteries[1].Name)
assert.Equal(t, uint8(0), batteries[1].Percent)
assert.Equal(t, "BAT3", batteries[2].Name)
}
func TestGetBatteryStatsHotPlugReenumerates(t *testing.T) {
_, add := setupFakeSysfs(t)
_, err := GetBatteryStats()
assert.Error(t, err)
assert.False(t, HasReadableBattery())
add(fakeBattery{id: "BAT0", capacity: "64", status: "Discharging"})
batteries, err := GetBatteryStats()
require.NoError(t, err)
assert.True(t, HasReadableBattery())
require.Len(t, batteries, 1)
assert.Equal(t, uint8(64), batteries[0].Percent)
}
func TestGetBatteryStatsNoReadableCapacity(t *testing.T) {
_, add := setupFakeSysfs(t)
add(fakeBattery{id: "BAT0", status: "Charging"})
_, err := GetBatteryStats()
assert.Error(t, err)
assert.False(t, HasReadableBattery())
}