Ctrl K
ComunidadGuías…

tariff/fixed_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package tariff

import (
	"testing"
	"time"

	"github.com/benbjohnson/clock"
	"github.com/evcc-io/evcc/api"
	"github.com/evcc-io/evcc/tariff/fixed"
	"github.com/jinzhu/now"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestFixed(t *testing.T) {
	tf := &Fixed{
		embed: &embed{},
		clock: clock.NewMock(),
		zones: []fixed.Zone{
			{Price: 0.3},
		},
	}

	var expect api.Rates
	for dow := range 7 {
		dayStart := now.With(tf.clock.Now()).BeginningOfDay().AddDate(0, 0, dow)

		for hour := range 24 {
			expect = append(expect, api.Rate{
				Value: 0.3,
				Start: dayStart.Add(time.Hour * time.Duration(hour)),
				End:   dayStart.Add(time.Hour * time.Duration(hour+1)),
			})
		}
	}

	rates, err := tf.Rates()
	require.NoError(t, err)
	assert.Equal(t, expect, rates)
}

func TestFixedSplitZones(t *testing.T) {
	at, err := NewFixedFromConfig(map[string]any{
		"price": 0.5,
		"zones": []struct {
			Price float64
			Hours string
		}{
			{0.1, "0-5:30,20-21,21-0"},
		},
	})
	require.NoError(t, err)

	tf := at.(*Fixed)
	tf.clock = clock.NewMock()

	var expect api.Rates
	for i := range 7 {
		dayStart := now.With(tf.clock.Now()).BeginningOfDay().AddDate(0, 0, i)

		// 00:00-05:00 0.1
		for hour := range 5 {
			expect = append(expect, api.Rate{
				Value: 0.1,
				Start: dayStart.Add(time.Hour * time.Duration(hour)),
				End:   dayStart.Add(time.Hour * time.Duration(hour+1)),
			})
		}

		// 05:00-05:30 0.1
		expect = append(expect, api.Rate{
			Value: 0.1,
			Start: dayStart.Add(5 * time.Hour),
			End:   dayStart.Add(5*time.Hour + 30*time.Minute),
		})

		// 05:30-06:00 0.5
		expect = append(expect, api.Rate{
			Value: 0.5,
			Start: dayStart.Add(5*time.Hour + 30*time.Minute),
			End:   dayStart.Add(6 * time.Hour),
		})

		// 06:00-20:00 0.5
		for hour := 6; hour < 20; hour++ {
			expect = append(expect, api.Rate{
				Value: 0.5,
				Start: dayStart.Add(time.Hour * time.Duration(hour)),
				End:   dayStart.Add(time.Hour * time.Duration(hour+1)),
			})
		}

		// 20:00-21:00,21:00-00:00 0.1
		for hour := 20; hour < 24; hour++ {
			expect = append(expect, api.Rate{
				Value: 0.1,
				Start: dayStart.Add(time.Hour * time.Duration(hour)),
				End:   dayStart.Add(time.Hour * time.Duration(hour+1)),
			})
		}
	}

	rates, err := tf.Rates()
	require.NoError(t, err)
	assert.Equal(t, expect, rates)
}

func TestFixedChargesZonesMarkers(t *testing.T) {
	at, err := NewFixedFromConfig(map[string]any{
		"price": 0.5,
		"chargesZones": []struct {
			Charges float64
			Hours   string
		}{
			{0.1, "0-5:30"},
		},
	})
	require.NoError(t, err)

	tf := at.(*Fixed)
	tf.clock = clock.NewMock()

	rates, err := tf.Rates()
	require.NoError(t, err)

	dayStart := now.With(tf.clock.Now()).BeginningOfDay()

	// charges zone ends mid-hour at 05:30 - the price change must take effect there, not at 06:00
	require.GreaterOrEqual(t, len(rates), 2)
	assert.Equal(t, dayStart.Add(5*time.Hour), rates[5].Start)
	assert.Equal(t, dayStart.Add(5*time.Hour+30*time.Minute), rates[5].End)
	assert.Equal(t, 0.6, rates[5].Value)
	assert.Equal(t, dayStart.Add(5*time.Hour+30*time.Minute), rates[6].Start)
	assert.Equal(t, 0.5, rates[6].Value)
}

func TestFixedChargesZonesOrder(t *testing.T) {
	at, err := NewFixedFromConfig(map[string]any{
		"price": 0.5,
		"zones": []struct {
			Price float64
			Hours string
		}{
			{0.2, "12-14"},
		},
		"chargesZones": []struct {
			Charges float64
			Hours   string
		}{
			{0.1, "0-5:30"},
		},
	})
	require.NoError(t, err)

	tf := at.(*Fixed)
	tf.clock = clock.NewMock()

	rates, err := tf.Rates()
	require.NoError(t, err)

	// rates must stay ordered and contiguous even if charges zone markers don't align with price zones
	for i, r := range rates {
		require.True(t, r.End.After(r.Start), "invalid rate %d: %v", i, r)
		if i > 0 {
			require.Equal(t, rates[i-1].End, r.Start, "non-contiguous rate %d: %v", i, r)
		}
	}
}

func TestFixedDstSpringForward(t *testing.T) {
	loc, err := time.LoadLocation("Europe/Berlin")
	require.NoError(t, err)

	tf := &Fixed{
		embed: &embed{},
		clock: clock.NewMock(),
		zones: []fixed.Zone{{Price: 0.3}},
	}

	// 2026-03-29 02:00 -> 03:00
	tf.clock.(*clock.Mock).Set(time.Date(2026, 3, 27, 12, 0, 0, 0, loc))

	rates, err := tf.Rates()
	require.NoError(t, err)

	// one hourly slot dropped on the 23h day
	start := now.With(tf.clock.Now()).BeginningOfDay()
	require.Len(t, rates, 7*24-1)
	assert.Equal(t, start, rates[0].Start)
	assert.Equal(t, start.AddDate(0, 0, 7), rates[len(rates)-1].End)

	for i, r := range rates {
		require.True(t, r.End.After(r.Start), "invalid rate %d: %v", i, r)
		if i > 0 {
			require.Equal(t, rates[i-1].End, r.Start, "non-contiguous rate %d: %v", i, r)
		}
	}

	_, short := shortSlot(rates)
	assert.False(t, short)
}

func TestFixedDstSpringForwardSkippedMarker(t *testing.T) {
	loc, err := time.LoadLocation("Europe/Berlin")
	require.NoError(t, err)

	zones, err := fixed.ParseZones([]fixed.ZoneSpec{{Price: 0.1, Hours: "00:00-02:30"}})
	require.NoError(t, err)

	tf := &Fixed{
		embed: &embed{},
		clock: clock.NewMock(),
		zones: append([]fixed.Zone{{Price: 0.3}}, zones...),
	}

	// 2026-03-29 02:00 -> 03:00, 02:30 does not exist
	tf.clock.(*clock.Mock).Set(time.Date(2026, 3, 29, 0, 30, 0, 0, loc))

	rates, err := tf.Rates()
	require.NoError(t, err)

	for i, r := range rates {
		require.True(t, r.End.After(r.Start), "invalid rate %d: %v", i, r)
		if i > 0 {
			require.Equal(t, rates[i-1].End, r.Start, "non-contiguous rate %d: %v", i, r)
		}
	}

	r, err := rates.At(time.Date(2026, 3, 29, 1, 30, 0, 0, loc))
	require.NoError(t, err)
	assert.Equal(t, 0.1, r.Value)

	r, err = rates.At(time.Date(2026, 3, 29, 3, 0, 0, 0, loc))
	require.NoError(t, err)
	assert.Equal(t, 0.3, r.Value)
}

func TestFixedDstFallBack(t *testing.T) {
	loc, err := time.LoadLocation("Europe/Berlin")
	require.NoError(t, err)

	zones, err := fixed.ParseZones([]fixed.ZoneSpec{{Price: 0.1, Hours: "19:00-20:00"}})
	require.NoError(t, err)

	tf := &Fixed{
		embed: &embed{},
		clock: clock.NewMock(),
		zones: append([]fixed.Zone{{Price: 0.3}}, zones...),
	}

	// 2026-10-25 03:00 -> 02:00
	tf.clock.(*clock.Mock).Set(time.Date(2026, 10, 25, 12, 0, 0, 0, loc))

	rates, err := tf.Rates()
	require.NoError(t, err)

	var cheap int
	for _, r := range rates {
		if r.Value != 0.1 {
			continue
		}
		cheap++
		assert.Equal(t, 19, r.Start.In(loc).Hour(), "start %v", r.Start)
		assert.Equal(t, time.Hour, r.End.Sub(r.Start), "duration %v", r)
	}
	assert.Equal(t, 7, cheap)
}