tariff/proxy_cache_test.go
Rama master · réplica de lectura del repositorio original.
package tariff
import (
"context"
"errors"
"testing"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/db"
"github.com/evcc-io/evcc/db/cache"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCachedFallback(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
other := map[string]any{"interval": 50 * time.Millisecond}
key := "test-retry-" + cacheKey("test-retry", other)
// no cache: startup fails
retryable.setErr(errors.New("unavailable"))
_, err := NewCachedFromConfig(context.TODO(), "test-retry", other)
require.Error(t, err)
// outdated cache: startup succeeds, cached rates served
// utc for comparing cached rates after json round trip
now := time.Now().UTC().Truncate(SlotDuration)
stale := makeRates(now.Add(-time.Hour), SlotDuration, 8, 0)
require.NoError(t, cache.Put(key, &cached{
Type: api.TariffTypePriceForecast,
Rates: stale,
Updated: now.Add(-time.Hour),
}))
res, err := NewCachedFromConfig(context.TODO(), "test-retry", other)
require.NoError(t, err)
rr, err := res.Rates()
require.NoError(t, err)
assert.Equal(t, stale, rr)
assert.Equal(t, api.TariffTypePriceForecast, res.Type())
// tariff becomes available: wrapper retry creates it, live rates served and cached
retryable.setErr(nil)
w := res.(*cachingProxy).tariff.(*Wrapper)
w.mu.Lock()
w.retriedAt = time.Time{}
w.mu.Unlock()
live, err := res.Rates()
require.NoError(t, err)
assert.NotEqual(t, stale, live)
// tariff becomes unavailable at runtime: last rates served
retryable.setErr(api.ErrOutdated)
rr, err = res.Rates()
require.NoError(t, err)
assert.Equal(t, live, rr)
}
func TestCachedFallbackElapsed(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
other := map[string]any{"interval": 60 * time.Millisecond}
key := "test-retry-" + cacheKey("test-retry", other)
// cached rates have entirely elapsed
now := time.Now().UTC().Truncate(SlotDuration)
elapsed := makeRates(now.Add(-2*time.Hour), SlotDuration, 4, 0)
require.NoError(t, cache.Put(key, &cached{
Type: api.TariffTypePriceForecast,
Rates: elapsed,
Updated: now.Add(-2 * time.Hour),
}))
// startup fails rather than reporting an available tariff without usable rates
retryable.setErr(errors.New("unavailable"))
_, err := NewCachedFromConfig(context.TODO(), "test-retry", other)
require.Error(t, err)
// tariff available at startup, then unavailable at runtime with elapsed cache only
retryable.setErr(nil)
res, err := NewCachedFromConfig(context.TODO(), "test-retry", other)
require.NoError(t, err)
retryable.setErr(api.ErrOutdated)
_, err = res.Rates()
require.Error(t, err)
}
func TestCachedFreshElapsed(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
other := map[string]any{"interval": time.Hour}
key := "test-retry-" + cacheKey("test-retry", other)
// cache updated within the interval, but its rates have entirely elapsed
now := time.Now().UTC().Truncate(SlotDuration)
putElapsed := func() {
require.NoError(t, cache.Put(key, &cached{
Type: api.TariffTypePriceForecast,
Rates: makeRates(now.Add(-2*time.Hour), SlotDuration, 4, 0),
Updated: now,
}))
}
// the tariff is created instead of serving the elapsed rates
putElapsed()
retryable.setErr(nil)
res, err := NewCachedFromConfig(context.TODO(), "test-retry", other)
require.NoError(t, err)
rr, err := res.Rates()
require.NoError(t, err)
require.NotEmpty(t, rr)
assert.True(t, rr[len(rr)-1].End.After(time.Now()))
// without a tariff to create, the elapsed rates are not served either
putElapsed()
retryable.setErr(errors.New("unavailable"))
_, err = NewCachedFromConfig(context.TODO(), "test-retry", other)
require.Error(t, err)
}
func TestCacheKeepsLeadingSlots(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
now := time.Now().Truncate(SlotDuration)
// the fresh tariff's first fetch starts an hour out, the cache covers the hour before
p := &cachingProxy{
key: "test-merge",
interval: time.Hour,
tariff: &testTariff{typ: api.TariffTypeCo2, rates: makeRates(now.Add(time.Hour), SlotDuration, 4, 10)},
cached: &cached{Type: api.TariffTypeCo2, Rates: makeRates(now.Add(-time.Hour), SlotDuration, 12, 0), Updated: now.Add(-2 * time.Hour)},
}
rr, err := p.Rates()
require.NoError(t, err)
// elapsed cached slots dropped, the current hour kept, fresh rates appended
assert.Equal(t, append(makeRates(now, SlotDuration, 4, 4), makeRates(now.Add(time.Hour), SlotDuration, 4, 10)...), rr)
}
func TestCacheInterval(t *testing.T) {
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
p := &cachingProxy{key: "test", interval: time.Hour}
require.NoError(t, p.cachePut(api.TariffTypeSolar, api.Rates{
{Start: time.Now(), End: time.Now().Add(time.Hour), Value: 1},
}))
// within interval
res, err := p.cacheGet()
require.NoError(t, err)
require.Equal(t, api.TariffTypeSolar, res.Type)
// interval elapsed
p.cached.Updated = time.Now().Add(-2 * time.Hour)
_, err = p.cacheGet()
require.Error(t, err)
}