openmeter / credit /engine /reset_test.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 3)
1c4c66b verified
Raw
History Blame Contribute Delete
9.97 kB
package engine_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/openmeterio/openmeter/openmeter/credit/balance"
"github.com/openmeterio/openmeter/openmeter/credit/engine"
"github.com/openmeterio/openmeter/openmeter/credit/grant"
meterpkg "github.com/openmeterio/openmeter/openmeter/meter"
"github.com/openmeterio/openmeter/openmeter/streaming"
streamingtestutils "github.com/openmeterio/openmeter/openmeter/streaming/testutils"
"github.com/openmeterio/openmeter/openmeter/testutils"
"github.com/openmeterio/openmeter/pkg/timeutil"
)
func TestReset(t *testing.T) {
t1 := testutils.GetRFC3339Time(t, "2024-01-01T00:00:00Z")
meterSlug := "meter-1"
meter := meterpkg.Meter{
Key: meterSlug,
Aggregation: meterpkg.MeterAggregationSum,
}
grant1 := makeGrant(grant.Grant{
ID: "grant-1",
Amount: 100.0,
Priority: 1,
EffectiveAt: t1,
Expiration: &grant.ExpirationPeriod{
Duration: grant.ExpirationPeriodDurationDay,
Count: 30,
},
})
setup := func(t *testing.T) (engine.Engine, addUsageFunc) {
streamingConnector := streamingtestutils.NewMockStreamingConnector(t)
queryFeatureUsage := func(ctx context.Context, from, to time.Time) (float64, error) {
rows, err := streamingConnector.QueryMeter(ctx, "default", meter, streaming.QueryParams{
From: &from,
To: &to,
})
if err != nil {
return 0.0, err
}
if len(rows) > 1 {
return 0.0, fmt.Errorf("expected 1 row, got %d", len(rows))
}
if len(rows) == 0 {
return 0.0, nil
}
return rows[0].Value, nil
}
return engine.NewEngine(engine.EngineConfig{
QueryUsage: queryFeatureUsage,
}), func(usage float64, at time.Time) {
streamingConnector.AddSimpleEvent(meterSlug, usage, at)
}
}
t.Run("Should reset roll over grant balance after one reset", func(t *testing.T) {
eng, use := setup(t)
use(10.0, t1.Add(time.Hour))
g1 := grant1
g1.ResetMaxRollover = 50.0
// grants can roll over their own balance so we don't check the details of that
res, err := eng.Run(
context.Background(),
engine.RunParams{
Meter: meter,
Grants: []grant.Grant{g1},
StartingSnapshot: balance.Snapshot{
Usage: balance.SnapshottedUsage{
Since: t1.AddDate(0, 0, -1), // Last "reset time", outside this period, arbitrary
Usage: 0.0,
},
Balances: balance.Map{
g1.ID: 100.0,
},
Overage: 0,
At: t1,
},
Until: t1.AddDate(0, 0, 1),
ResetBehavior: grant.ResetBehavior{
PreserveOverage: false,
},
Resets: timeutil.NewSimpleTimeline([]time.Time{t1.Add(time.Hour * 5)}),
},
)
assert.NoError(t, err)
// The grant should be rolled over:
// 100 - 10 = 90;
// Min(50, max(0, 90)) = 50
assert.Equal(t, 50.0, res.Snapshot.Balances[grant1.ID])
// Usage since last reset should be captured in snapshot
assert.Equal(t, 0.0, res.Snapshot.Usage.Usage) // 0 usage after 5h mark
assert.Equal(t, t1.Add(time.Hour*5), res.Snapshot.Usage.Since) // should mark since the last reset time
// History should have 2 segments, one before and one after the reset
assert.Equal(t, 2, len(res.History.Segments()))
// The first segment should have a balance of 100 with 10 usage
assert.Equal(t, 100.0, res.History.Segments()[0].BalanceAtStart.Balance())
assert.Equal(t, 0.0, res.History.Segments()[0].OverageAtStart)
assert.Equal(t, 10.0, res.History.Segments()[0].TotalUsage)
// It should end with a reset
assert.True(t, res.History.Segments()[0].TerminationReasons.UsageReset)
// The second segment should have a balance of 50 with no usage
assert.Equal(t, 50.0, res.History.Segments()[1].BalanceAtStart.Balance())
assert.Equal(t, 0.0, res.History.Segments()[1].OverageAtStart)
assert.Equal(t, 0.0, res.History.Segments()[1].TotalUsage)
})
t.Run("Should carry over overage to next period", func(t *testing.T) {
eng, use := setup(t)
use(10.0, t1.Add(time.Hour))
use(100.0, t1.Add(time.Hour*2))
g1 := grant1
g1.ResetMaxRollover = 50.0
g1.ResetMinRollover = 50.0
// grants can roll over their own balance so we don't check the details of that
res, err := eng.Run(
context.Background(),
engine.RunParams{
Meter: meter,
Grants: []grant.Grant{g1},
StartingSnapshot: balance.Snapshot{
Balances: balance.Map{
g1.ID: 100.0,
},
Overage: 0,
At: t1,
},
Until: t1.AddDate(0, 0, 1),
ResetBehavior: grant.ResetBehavior{
PreserveOverage: true,
},
Resets: timeutil.NewSimpleTimeline([]time.Time{t1.Add(time.Hour * 5)}),
},
)
assert.NoError(t, err)
// The grant should be rolled over:
assert.Equal(t, 40.0, res.Snapshot.Balances[grant1.ID])
// History should have 2 segments, one before and one after the reset
assert.Equal(t, 2, len(res.History.Segments()))
// The first segment should have a balance of 0 with 10 overage
assert.Equal(t, 100.0, res.History.Segments()[0].BalanceAtStart.Balance())
assert.Equal(t, 0.0, res.History.Segments()[0].OverageAtStart)
assert.Equal(t, 10.0, res.History.Segments()[0].Overage)
assert.Equal(t, 110.0, res.History.Segments()[0].TotalUsage)
// It should end with a reset
assert.True(t, res.History.Segments()[0].TerminationReasons.UsageReset)
// The second segment should have a balance of 50 - 10 with no usage (minRolloverAmount + overage)
assert.Equal(t, 40.0, res.History.Segments()[1].BalanceAtStart.Balance())
assert.Equal(t, 0.0, res.History.Segments()[1].OverageAtStart)
assert.Equal(t, 0.0, res.History.Segments()[1].TotalUsage)
})
t.Run("No reset", func(t *testing.T) {
eng, use := setup(t)
use(10.0, t1.Add(time.Hour))
g2 := grant1
g2.EffectiveAt = t1.Add(time.Hour * 2)
u := balance.SnapshottedUsage{
Since: t1.AddDate(0, 0, -1),
Usage: 10.0,
}
res, err := eng.Run(
context.Background(),
engine.RunParams{
Meter: meter,
Grants: []grant.Grant{grant1, g2},
StartingSnapshot: balance.Snapshot{
Usage: u,
Balances: balance.Map{
grant1.ID: 100.0,
g2.ID: 100.0,
},
Overage: 0,
At: t1,
},
Until: t1.AddDate(0, 0, 1),
},
)
assert.NoError(t, err)
// If there was no reset, should extend the starting snapshot with the current usage data
assert.Equal(t, 20.0, res.Snapshot.Usage.Usage) // 10 + 10
assert.Equal(t, u.Since, res.Snapshot.Usage.Since)
// Should have 2 periods, start - g2, g2 - end
assert.Equal(t, 2, len(res.History.Segments()))
assert.False(t, res.History.Segments()[0].TerminationReasons.UsageReset)
assert.False(t, res.History.Segments()[1].TerminationReasons.UsageReset)
})
t.Run("Should return starting balance if the end of the queried period is a reset", func(t *testing.T) {
eng, use := setup(t)
use(10.0, t1.Add(time.Hour))
g1 := grant1
g1.ResetMaxRollover = 50.0
resetTime := t1.AddDate(0, 0, 1)
res, err := eng.Run(
context.Background(),
engine.RunParams{
Meter: meter,
Grants: []grant.Grant{g1},
StartingSnapshot: balance.Snapshot{
Balances: balance.Map{
g1.ID: 100.0,
},
Overage: 0,
At: t1,
},
Until: resetTime,
Resets: timeutil.NewSimpleTimeline([]time.Time{resetTime}),
},
)
assert.NoError(t, err)
// There cannot be any usage right at reset
assert.Equal(t, 0.0, res.Snapshot.Usage.Usage)
assert.Equal(t, resetTime, res.Snapshot.Usage.Since)
// Should have 2 periods, start - reset, reset - end where reset = end, 2nd period is 0 length
assert.Equal(t, 2, len(res.History.Segments()), "expected: %+v, got %+v, history: %+v", 2, len(res.History.Segments()), res.History.Segments())
assert.True(t, res.History.Segments()[0].TerminationReasons.UsageReset)
assert.False(t, res.History.Segments()[1].TerminationReasons.UsageReset)
})
t.Run("Should include grant recurrence in starting balance", func(t *testing.T) {
eng, use := setup(t)
use(10.0, t1.Add(time.Hour))
g1 := grant1
g1.Recurrence = &timeutil.Recurrence{
Interval: timeutil.RecurrencePeriodDaily,
Anchor: t1,
}
resetTime := t1.AddDate(0, 0, 1)
res, err := eng.Run(
context.Background(),
engine.RunParams{
Meter: meter,
Grants: []grant.Grant{g1},
StartingSnapshot: balance.Snapshot{
Balances: balance.Map{
g1.ID: 100.0,
},
Overage: 0,
At: t1,
},
Until: resetTime,
Resets: timeutil.NewSimpleTimeline([]time.Time{resetTime}),
},
)
assert.NoError(t, err)
// There cannot be any usage right at reset
assert.Equal(t, 0.0, res.Snapshot.Usage.Usage)
assert.Equal(t, resetTime, res.Snapshot.Usage.Since)
// Should have 2 periods, start - reset, reset - end where reset = end, 2nd period is 0 length
assert.Equal(t, 2, len(res.History.Segments()), "expected: %+v, got %+v, history: %+v", 2, len(res.History.Segments()), res.History.Segments())
assert.True(t, res.History.Segments()[0].TerminationReasons.UsageReset)
assert.False(t, res.History.Segments()[1].TerminationReasons.UsageReset)
assert.Equal(t, 100.0, res.History.Segments()[1].BalanceAtStart[g1.ID])
// The starting balance should be the amount of the grant
assert.Equal(t, 100.0, res.Snapshot.Balances[g1.ID])
})
t.Run("Should error if a reset is provided for the starting snapshot", func(t *testing.T) {
eng, use := setup(t)
use(10.0, t1.Add(time.Hour))
g1 := grant1
g1.ResetMaxRollover = 50.0
_, err := eng.Run(
context.Background(),
engine.RunParams{
Meter: meter,
Grants: []grant.Grant{g1},
StartingSnapshot: balance.Snapshot{
Balances: balance.Map{g1.ID: 100.0},
Overage: 0,
At: t1,
},
Until: t1.AddDate(0, 0, 1),
Resets: timeutil.NewSimpleTimeline([]time.Time{t1}),
},
)
assert.EqualError(t, err, "provided reset times must occur after the starting snapshot, got 2024-01-01 00:00:00 +0000 UTC")
})
}