evcc-io/meter/powerwall_test.go

165 lines
3.8 KiB
Go

package meter
import (
"testing"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewPowerWallFromConfigValidation(t *testing.T) {
tests := []struct {
name string
config map[string]any
want string
}{
{
name: "missing usage",
config: map[string]any{"password": "secret"},
want: "missing usage",
},
{
name: "missing password",
config: map[string]any{"usage": "battery"},
want: "missing password",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := NewPowerWallFromConfig(tc.config)
assert.ErrorContains(t, err, tc.want)
})
}
}
// decodePowerWallConfig mirrors the decoding done by the meter constructors
func decodePowerWallConfig(t *testing.T, other map[string]any) powerWallConfig {
t.Helper()
cc := defaultPowerWallConfig()
require.NoError(t, util.DecodeOther(other, &cc))
require.NoError(t, cc.validate())
return cc
}
func TestPowerWallConfigLegacyUsage(t *testing.T) {
tests := []struct {
usage string
want string
}{
{usage: "grid", want: "site"},
{usage: "pv", want: "solar"},
}
for _, tc := range tests {
t.Run(tc.usage, func(t *testing.T) {
cc := decodePowerWallConfig(t, map[string]any{
"usage": tc.usage,
"password": "secret",
})
assert.Equal(t, tc.want, cc.Usage)
})
}
}
func TestPowerWallConfigDeprecatedParams(t *testing.T) {
decodePowerWallConfig(t, map[string]any{
"usage": "battery", "password": "secret",
"refreshToken": "token", "siteId": 123,
"maxsoc": 95, "maxchargepower": 4600, "maxdischargepower": 4600,
})
}
// battery control must fall back to the default reserve, not to zero
func TestPowerWallFleetReserve(t *testing.T) {
cc := decodePowerWallConfig(t, map[string]any{"usage": "battery", "password": "secret"})
limits := batterySocLimits{MinSoc: cc.MinSoc, MaxSoc: 100}
var reserve float64
ctrl := limits.LimitController(
func() (float64, error) { return 50, nil },
func(limit float64) error { reserve = limit; return nil },
)
tests := []struct {
mode api.BatteryMode
want float64
}{
{mode: api.BatteryNormal, want: 20},
{mode: api.BatteryHold, want: 50},
{mode: api.BatteryCharge, want: 100},
}
for _, tc := range tests {
t.Run(tc.mode.String(), func(t *testing.T) {
require.NoError(t, ctrl(tc.mode))
assert.Equal(t, tc.want, reserve)
})
}
}
func TestNewPowerWallFleetFromConfigValidation(t *testing.T) {
tests := []struct {
name string
config map[string]any
want string
wantSentinel error
}{
{
name: "missing client id",
config: map[string]any{
"usage": "battery",
"password": "secret",
},
want: "missing client id",
},
{
name: "missing tokens",
config: map[string]any{
"usage": "battery",
"password": "secret",
"credentials": map[string]any{
"id": "client",
},
},
wantSentinel: api.ErrMissingToken,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := NewPowerWallFleetFromConfig(tc.config)
if tc.wantSentinel != nil {
assert.ErrorIs(t, err, tc.wantSentinel)
return
}
assert.ErrorContains(t, err, tc.want)
})
}
}
func TestTeslaReserveLimit(t *testing.T) {
tests := []struct {
name string
limit float64
want uint64
}{
{name: "negative", limit: -1, want: 0},
{name: "below cap", limit: 79.9, want: 79},
{name: "at cap", limit: 80, want: 80},
{name: "unsupported range start", limit: 81, want: 80},
{name: "unsupported range end", limit: 99.9, want: 80},
{name: "full reserve", limit: 100, want: 100},
{name: "above full reserve", limit: 101, want: 100},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, teslaReserveLimit(tc.limit))
})
}
}