112 lines
2.5 KiB
Go
112 lines
2.5 KiB
Go
package core
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/evcc-io/evcc/api"
|
||
"github.com/evcc-io/evcc/core/loadpoint"
|
||
optimizer "github.com/evcc-io/optimizer/client"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
"go.uber.org/mock/gomock"
|
||
)
|
||
|
||
func TestLoadpointProfile(t *testing.T) {
|
||
ctrl := gomock.NewController(t)
|
||
|
||
lp := loadpoint.NewMockAPI(ctrl)
|
||
lp.EXPECT().GetMode().Return(api.ModeMinPV).AnyTimes()
|
||
lp.EXPECT().GetStatus().Return(api.StatusC).AnyTimes()
|
||
lp.EXPECT().GetChargePower().Return(10000.0).AnyTimes() // 10 kW
|
||
lp.EXPECT().EffectiveMinPower().Return(1000.0).AnyTimes() // 1 kW
|
||
lp.EXPECT().GetRemainingEnergy().Return(1.8).AnyTimes() // 1.8 kWh
|
||
|
||
// expected slots: 0.25 kWh...
|
||
require.Equal(t, []float64{250, 250, 250, 250, 250, 250, 250, 50}, loadpointProfile(lp, 8))
|
||
}
|
||
|
||
func TestAsTimestamps(t *testing.T) {
|
||
// now is 10 minutes into a 15-minute slot
|
||
now := time.Date(2025, 1, 1, 12, 10, 0, 0, time.UTC)
|
||
|
||
// dt[0]=300 means first event is 300s (5min) before end of current slot
|
||
// dt[1..] just mark subsequent slot boundaries
|
||
dt := []int{60 * 5, 60 * 15, 60 * 15}
|
||
|
||
got := asTimestamps(dt, now)
|
||
|
||
// current slot: 12:00–12:15
|
||
// first timestamp: 12:15 - 5min = 12:10
|
||
// subsequent: 12:15, 12:30
|
||
assert.Equal(t, []time.Time{
|
||
time.Date(2025, 1, 1, 12, 10, 0, 0, time.UTC),
|
||
time.Date(2025, 1, 1, 12, 15, 0, 0, time.UTC),
|
||
time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC),
|
||
}, got)
|
||
}
|
||
|
||
func TestBatteryForecastTotals(t *testing.T) {
|
||
site := new(Site)
|
||
|
||
req := []optimizer.BatteryConfig{
|
||
{SMax: 80},
|
||
{SMax: 80},
|
||
}
|
||
|
||
const zero = -1
|
||
|
||
for _, tc := range []struct {
|
||
name string
|
||
bat1, bat2 []float32
|
||
full, empty int
|
||
}{
|
||
{
|
||
"never full",
|
||
[]float32{0, 0},
|
||
[]float32{0, 0},
|
||
zero, 0,
|
||
},
|
||
{
|
||
"never empty",
|
||
[]float32{100, 100},
|
||
[]float32{100, 100},
|
||
0, zero,
|
||
},
|
||
{
|
||
"first full then empty",
|
||
[]float32{100, 0},
|
||
[]float32{100, 0},
|
||
0, 1,
|
||
},
|
||
{
|
||
"first full finally empty",
|
||
[]float32{100, 100, 0},
|
||
[]float32{100, 0, 0},
|
||
0, 2,
|
||
},
|
||
{
|
||
"first empty then full",
|
||
[]float32{0, 100},
|
||
[]float32{0, 100},
|
||
1, 0,
|
||
},
|
||
{
|
||
"first empty finally full",
|
||
[]float32{0, 100, 100},
|
||
[]float32{0, 0, 100},
|
||
2, 0,
|
||
},
|
||
} {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
resp := []optimizer.BatteryResult{
|
||
{StateOfCharge: tc.bat1},
|
||
{StateOfCharge: tc.bat2},
|
||
}
|
||
|
||
full, empty := site.batteryForecastFullAndEmptySlots(req, resp)
|
||
assert.Equal(t, tc.full, full, "full")
|
||
assert.Equal(t, tc.empty, empty, "empty")
|
||
})
|
||
}
|
||
}
|