openmeter / billing /charges /usagebased /realizationrun_test.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 2)
5a22efd verified
Raw
History Blame Contribute Delete
9.8 kB
package usagebased
import (
"testing"
"time"
"github.com/alpacahq/alpacadecimal"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
"github.com/openmeterio/openmeter/openmeter/billing/models/totals"
"github.com/openmeterio/openmeter/pkg/models"
"github.com/openmeterio/openmeter/pkg/timeutil"
)
func TestRealizationRuns_MapToBillingMeteredQuantity(t *testing.T) {
periodStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
tests := []struct {
name string
runs RealizationRuns
currentRun RealizationRun
wantLine float64
wantPreLine float64
wantErr bool
}{
{
name: "first run has no pre-line period quantity",
currentRun: newRealizationRunForBillingMeteredQuantityTest(
"current",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
5,
),
wantLine: 5,
wantPreLine: 0,
},
{
name: "uses latest prior persisted cumulative quantity",
runs: RealizationRuns{
newRealizationRunForBillingMeteredQuantityTest(
"run-1",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
5,
),
newRealizationRunForBillingMeteredQuantityTest(
"run-2",
RealizationRunTypePartialInvoice,
periodStart.Add(48*time.Hour),
8,
),
},
currentRun: newRealizationRunForBillingMeteredQuantityTest(
"current",
RealizationRunTypeFinalRealization,
periodStart.Add(72*time.Hour),
20,
),
wantLine: 12,
wantPreLine: 8,
},
{
name: "errors when current cumulative quantity is below prior billed quantity",
runs: RealizationRuns{
newRealizationRunForBillingMeteredQuantityTest(
"run-1",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
10,
),
},
currentRun: newRealizationRunForBillingMeteredQuantityTest(
"current",
RealizationRunTypeFinalRealization,
periodStart.Add(48*time.Hour),
5,
),
wantErr: true,
},
{
name: "ignores deleted prior runs",
runs: RealizationRuns{
func() RealizationRun {
run := newRealizationRunForBillingMeteredQuantityTest(
"deleted-run",
RealizationRunTypePartialInvoice,
periodStart.Add(48*time.Hour),
18,
)
run.DeletedAt = &periodStart
return run
}(),
newRealizationRunForBillingMeteredQuantityTest(
"run-1",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
5,
),
},
currentRun: newRealizationRunForBillingMeteredQuantityTest(
"current",
RealizationRunTypeFinalRealization,
periodStart.Add(72*time.Hour),
20,
),
wantLine: 15,
wantPreLine: 5,
},
{
name: "ignores invalid unsupported credit note prior runs",
runs: RealizationRuns{
newRealizationRunForBillingMeteredQuantityTest(
"run-1",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
5,
),
newRealizationRunForBillingMeteredQuantityTest(
"invalid-run",
RealizationRunTypeInvalidDueToUnsupportedCreditNote,
periodStart.Add(48*time.Hour),
18,
),
},
currentRun: newRealizationRunForBillingMeteredQuantityTest(
"current",
RealizationRunTypeFinalRealization,
periodStart.Add(72*time.Hour),
20,
),
wantLine: 15,
wantPreLine: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
billingMeteredQuantity, err := tt.runs.MapToBillingMeteredQuantity(tt.currentRun)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tt.wantLine, billingMeteredQuantity.LinePeriod.InexactFloat64())
require.Equal(t, tt.wantPreLine, billingMeteredQuantity.PreLinePeriod.InexactFloat64())
})
}
}
func TestRealizationRunType_IsVoidedBillingHistory(t *testing.T) {
require.False(t, RealizationRunTypeFinalRealization.IsVoidedBillingHistory())
require.False(t, RealizationRunTypePartialInvoice.IsVoidedBillingHistory())
require.True(t, RealizationRunTypeInvalidDueToUnsupportedCreditNote.IsVoidedBillingHistory())
}
func TestRealizationRun_InvalidUnsupportedCreditNoteKeepsInitialType(t *testing.T) {
run := newRealizationRunForBillingMeteredQuantityTest(
"invalid-run",
RealizationRunTypeFinalRealization,
time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC),
10,
)
run.Type = RealizationRunTypeInvalidDueToUnsupportedCreditNote
require.NoError(t, run.Validate())
require.Equal(t, RealizationRunTypeFinalRealization, run.InitialType)
require.True(t, run.IsVoidedBillingHistory())
}
func TestRealizationRuns_SumSkipsVoidedBillingHistory(t *testing.T) {
periodStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
deletedAt := periodStart.Add(time.Hour)
deletedRun := newRealizationRunForBillingMeteredQuantityTest(
"deleted-run",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
100,
)
deletedRun.DeletedAt = &deletedAt
invalidRun := newRealizationRunForBillingMeteredQuantityTest(
"invalid-run",
RealizationRunTypeInvalidDueToUnsupportedCreditNote,
periodStart.Add(48*time.Hour),
100,
)
effectiveRun := newRealizationRunForBillingMeteredQuantityTest(
"effective-run",
RealizationRunTypeFinalRealization,
periodStart.Add(72*time.Hour),
7,
)
require.Equal(t, float64(7), RealizationRuns{deletedRun, invalidRun, effectiveRun}.Sum().Total.InexactFloat64())
}
func TestRealizationRuns_Latest(t *testing.T) {
periodStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
t.Run("empty", func(t *testing.T) {
_, ok := RealizationRuns{}.Latest()
require.False(t, ok)
})
t.Run("latest service period end", func(t *testing.T) {
run, ok := RealizationRuns{
newRealizationRunForBillingMeteredQuantityTest(
"run-1",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
1,
),
newRealizationRunForBillingMeteredQuantityTest(
"run-2",
RealizationRunTypePartialInvoice,
periodStart.Add(48*time.Hour),
1,
),
}.Latest()
require.True(t, ok)
require.Equal(t, "run-2", run.ID.ID)
})
t.Run("latest created at wins same service period end", func(t *testing.T) {
periodEnd := periodStart.Add(24 * time.Hour)
older := newRealizationRunForBillingMeteredQuantityTest(
"older",
RealizationRunTypePartialInvoice,
periodEnd,
1,
)
newer := newRealizationRunForBillingMeteredQuantityTest(
"newer",
RealizationRunTypePartialInvoice,
periodEnd,
1,
)
newer.CreatedAt = older.CreatedAt.Add(time.Hour)
run, ok := RealizationRuns{newer, older}.Latest()
require.True(t, ok)
require.Equal(t, "newer", run.ID.ID)
})
}
func TestRealizationRuns_GetByLineID(t *testing.T) {
lineID := "line-1"
otherLineID := "line-2"
runs := RealizationRuns{
{
RealizationRunBase: RealizationRunBase{
ID: RealizationRunID{Namespace: "namespace", ID: "run-1"},
LineID: &otherLineID,
},
},
{
RealizationRunBase: RealizationRunBase{
ID: RealizationRunID{Namespace: "namespace", ID: "run-2"},
LineID: &lineID,
},
},
}
run, err := runs.GetByLineID(lineID)
require.NoError(t, err)
require.Equal(t, "run-2", run.ID.ID)
_, err = runs.GetByLineID("missing-line")
require.ErrorContains(t, err, "realization run not found")
}
func TestRealizationRuns_BisectByTimestamp(t *testing.T) {
periodStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
servicePeriod := timeutil.ClosedPeriod{
From: periodStart,
To: periodStart.Add(96 * time.Hour),
}
at := periodStart.Add(36 * time.Hour)
deletedAt := periodStart.Add(time.Hour)
runs := RealizationRuns{
func() RealizationRun {
run := newRealizationRunForBillingMeteredQuantityTest(
"deleted-run",
RealizationRunTypePartialInvoice,
periodStart.Add(96*time.Hour),
1,
)
run.DeletedAt = &deletedAt
return run
}(),
newRealizationRunForBillingMeteredQuantityTest(
"after-run",
RealizationRunTypePartialInvoice,
periodStart.Add(72*time.Hour),
1,
),
newRealizationRunForBillingMeteredQuantityTest(
"before-run",
RealizationRunTypePartialInvoice,
periodStart.Add(24*time.Hour),
1,
),
newRealizationRunForBillingMeteredQuantityTest(
"containing-run",
RealizationRunTypePartialInvoice,
periodStart.Add(48*time.Hour),
1,
),
}
before, containingOrAfter := runs.BisectByTimestamp(servicePeriod, at)
require.Equal(t, []string{"before-run"}, lo.Map(before, func(run RealizationRun, _ int) string {
return run.ID.ID
}))
require.Equal(t, []string{"containing-run", "after-run"}, lo.Map(containingOrAfter, func(run RealizationRun, _ int) string {
return run.ID.ID
}))
before, containingOrAfter = runs.BisectByTimestamp(servicePeriod, periodStart.Add(48*time.Hour))
require.Equal(t, []string{"before-run", "containing-run"}, lo.Map(before, func(run RealizationRun, _ int) string {
return run.ID.ID
}))
require.Equal(t, []string{"after-run"}, lo.Map(containingOrAfter, func(run RealizationRun, _ int) string {
return run.ID.ID
}))
}
func newRealizationRunForBillingMeteredQuantityTest(id string, typ RealizationRunType, servicePeriodTo time.Time, meteredQuantity int64) RealizationRun {
return RealizationRun{
RealizationRunBase: RealizationRunBase{
ID: RealizationRunID{
Namespace: "namespace",
ID: id,
},
ManagedModel: models.ManagedModel{
CreatedAt: servicePeriodTo.Add(-time.Hour),
UpdatedAt: servicePeriodTo.Add(-time.Hour),
},
FeatureID: "feature-1",
Type: typ,
InitialType: typ,
StoredAtLT: servicePeriodTo,
ServicePeriodTo: servicePeriodTo,
MeteredQuantity: alpacadecimal.NewFromInt(meteredQuantity),
Totals: totals.Totals{
Amount: alpacadecimal.NewFromInt(meteredQuantity),
Total: alpacadecimal.NewFromInt(meteredQuantity),
},
},
}
}