openmeter / ledger /impact_test.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 5)
cee2387 verified
Raw
History Blame Contribute Delete
12.1 kB
package ledger_test
import (
"testing"
"time"
"github.com/alpacahq/alpacadecimal"
"github.com/samber/mo"
"github.com/stretchr/testify/require"
"github.com/openmeterio/openmeter/openmeter/ledger"
"github.com/openmeterio/openmeter/pkg/currencyx"
"github.com/openmeterio/openmeter/pkg/models"
)
func TestEntryMatchesImpactFilter(t *testing.T) {
t.Parallel()
taxCode := "tax-standard"
otherTaxCode := "tax-reduced"
taxBehavior := ledger.TaxBehaviorInclusive
otherTaxBehavior := ledger.TaxBehaviorExclusive
priority := 10
otherPriority := 20
costBasis := alpacadecimal.NewFromInt(1)
otherCostBasis := alpacadecimal.NewFromInt(2)
authStatus := ledger.TransactionAuthorizationStatusOpen
otherAuthStatus := ledger.TransactionAuthorizationStatusAuthorized
entry := mustImpactTestEntry(t, ledger.AccountTypeCustomerFBO, ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &taxCode,
TaxBehavior: &taxBehavior,
Features: []string{"feature-a", "feature-b"},
CostBasis: &costBasis,
CreditPriority: &priority,
TransactionAuthorizationStatus: &authStatus,
})
tests := []struct {
name string
filter ledger.ImpactFilter
want bool
}{
{
name: "empty filter matches",
want: true,
},
{
name: "account type matches",
filter: ledger.ImpactFilter{
AccountType: ledger.AccountTypeCustomerFBO,
},
want: true,
},
{
name: "account type mismatch",
filter: ledger.ImpactFilter{
AccountType: ledger.AccountTypeCustomerAccrued,
},
},
{
name: "currency matches",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{Currency: currencyx.Code("USD")},
},
want: true,
},
{
name: "currency mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{Currency: currencyx.Code("EUR")},
},
},
{
name: "tax code matches",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxCode: mo.Some(&taxCode)},
},
want: true,
},
{
name: "tax code mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxCode: mo.Some(&otherTaxCode)},
},
},
{
name: "nil tax code required rejects non-nil route tax code",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxCode: mo.Some[*string](nil)},
},
},
{
name: "tax behavior matches",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxBehavior: mo.Some(&taxBehavior)},
},
want: true,
},
{
name: "tax behavior mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxBehavior: mo.Some(&otherTaxBehavior)},
},
},
{
name: "nil tax behavior required rejects non-nil route tax behavior",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxBehavior: mo.Some[*ledger.TaxBehavior](nil)},
},
},
{
name: "features match regardless of filter order",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{Features: mo.Some([]string{"feature-b", "feature-a"})},
},
want: true,
},
{
name: "features mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{Features: mo.Some([]string{"feature-c"})},
},
},
{
name: "cost basis absent filter ignores route cost basis",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{},
},
want: true,
},
{
name: "cost basis matches",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CostBasis: mo.Some(&costBasis)},
},
want: true,
},
{
name: "cost basis mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CostBasis: mo.Some(&otherCostBasis)},
},
},
{
name: "nil cost basis filter rejects non-nil route cost basis",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CostBasis: mo.Some[*alpacadecimal.Decimal](nil)},
},
},
{
name: "credit priority matches",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CreditPriority: &priority},
},
want: true,
},
{
name: "credit priority mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CreditPriority: &otherPriority},
},
},
{
name: "authorization status matches",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TransactionAuthorizationStatus: &authStatus},
},
want: true,
},
{
name: "authorization status mismatch",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TransactionAuthorizationStatus: &otherAuthStatus},
},
},
{
name: "multiple fields match together",
filter: ledger.ImpactFilter{
AccountType: ledger.AccountTypeCustomerFBO,
Route: ledger.RouteFilter{
Currency: currencyx.Code("USD"),
TaxCode: mo.Some(&taxCode),
TaxBehavior: mo.Some(&taxBehavior),
Features: mo.Some([]string{"feature-b", "feature-a"}),
CostBasis: mo.Some(&costBasis),
CreditPriority: &priority,
},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, ledger.EntryMatchesImpactFilter(entry, tt.filter))
})
}
}
func TestEntryMatchesImpactFilter_NilRouteFields(t *testing.T) {
t.Parallel()
taxCode := "tax-standard"
taxBehavior := ledger.TaxBehaviorInclusive
priority := 10
authStatus := ledger.TransactionAuthorizationStatusOpen
entry := mustImpactTestEntry(t, ledger.AccountTypeCustomerReceivable, ledger.Route{
Currency: currencyx.Code("USD"),
})
tests := []struct {
name string
filter ledger.ImpactFilter
want bool
}{
{
name: "tax code required but route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxCode: mo.Some(&taxCode)},
},
},
{
name: "nil tax code required and route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxCode: mo.Some[*string](nil)},
},
want: true,
},
{
name: "tax behavior required but route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxBehavior: mo.Some(&taxBehavior)},
},
},
{
name: "nil tax behavior required and route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TaxBehavior: mo.Some[*ledger.TaxBehavior](nil)},
},
want: true,
},
{
name: "nil cost basis required and route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CostBasis: mo.Some[*alpacadecimal.Decimal](nil)},
},
want: true,
},
{
name: "credit priority required but route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{CreditPriority: &priority},
},
},
{
name: "authorization status required but route has nil",
filter: ledger.ImpactFilter{
Route: ledger.RouteFilter{TransactionAuthorizationStatus: &authStatus},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, ledger.EntryMatchesImpactFilter(entry, tt.filter))
})
}
}
func TestTransactionImpact(t *testing.T) {
t.Parallel()
priorityOne := 1
priorityTwo := 2
tx := impactTestTransaction{
entries: []ledger.Entry{
mustImpactTestEntry(t, ledger.AccountTypeCustomerFBO, ledger.Route{
Currency: currencyx.Code("USD"),
CreditPriority: &priorityOne,
}, alpacadecimal.NewFromInt(10)),
mustImpactTestEntry(t, ledger.AccountTypeCustomerFBO, ledger.Route{
Currency: currencyx.Code("USD"),
CreditPriority: &priorityTwo,
}, alpacadecimal.NewFromInt(-3)),
mustImpactTestEntry(t, ledger.AccountTypeCustomerFBO, ledger.Route{
Currency: currencyx.Code("EUR"),
}, alpacadecimal.NewFromInt(7)),
mustImpactTestEntry(t, ledger.AccountTypeCustomerAccrued, ledger.Route{
Currency: currencyx.Code("USD"),
}, alpacadecimal.NewFromInt(20)),
},
}
tests := []struct {
name string
filter ledger.ImpactFilter
want alpacadecimal.Decimal
}{
{
name: "empty filter sums all entries",
want: alpacadecimal.NewFromInt(34),
},
{
name: "account type filter sums matching account type",
filter: ledger.ImpactFilter{
AccountType: ledger.AccountTypeCustomerFBO,
},
want: alpacadecimal.NewFromInt(14),
},
{
name: "account type and currency filter sum matching entries",
filter: ledger.ImpactFilter{
AccountType: ledger.AccountTypeCustomerFBO,
Route: ledger.RouteFilter{
Currency: currencyx.Code("USD"),
},
},
want: alpacadecimal.NewFromInt(7),
},
{
name: "route priority filter sum matching entries",
filter: ledger.ImpactFilter{
AccountType: ledger.AccountTypeCustomerFBO,
Route: ledger.RouteFilter{
CreditPriority: &priorityOne,
},
},
want: alpacadecimal.NewFromInt(10),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.True(t, tt.want.Equal(ledger.TransactionImpact(tx, tt.filter)))
})
}
}
type impactTestTransaction struct {
entries []ledger.Entry
}
func (t impactTestTransaction) Cursor() ledger.TransactionCursor {
return ledger.TransactionCursor{
BookedAt: t.BookedAt(),
CreatedAt: t.BookedAt(),
ID: t.ID(),
}
}
func (t impactTestTransaction) BookedAt() time.Time {
return time.Date(2026, 5, 14, 12, 0, 0, 0, time.UTC)
}
func (t impactTestTransaction) Entries() []ledger.Entry {
return t.entries
}
func (t impactTestTransaction) ID() models.NamespacedID {
return models.NamespacedID{Namespace: "ns", ID: "tx-id"}
}
func (t impactTestTransaction) Annotations() models.Annotations {
return nil
}
var _ ledger.Transaction = impactTestTransaction{}
type impactTestEntry struct {
id models.NamespacedID
txID models.NamespacedID
address ledger.PostingAddress
amount alpacadecimal.Decimal
identity string
metadata models.Annotations
}
func (e impactTestEntry) ID() models.NamespacedID {
return e.id
}
func (e impactTestEntry) TransactionID() models.NamespacedID {
return e.txID
}
func (e impactTestEntry) PostingAddress() ledger.PostingAddress {
return e.address
}
func (e impactTestEntry) Amount() alpacadecimal.Decimal {
return e.amount
}
func (e impactTestEntry) IdentityKey() string {
return e.identity
}
func (e impactTestEntry) SchemaVersion() ledger.EntrySchemaVersion {
return ledger.EntrySchemaVersionCurrent
}
func (e impactTestEntry) SourceChargeID() *string {
return nil
}
func (e impactTestEntry) SpendChargeID() *string {
return nil
}
func (e impactTestEntry) Annotations() models.Annotations {
return e.metadata
}
var _ ledger.Entry = impactTestEntry{}
type impactTestAddress struct {
subAccountID string
accountType ledger.AccountType
route ledger.SubAccountRoute
}
func (a impactTestAddress) SubAccountID() string {
return a.subAccountID
}
func (a impactTestAddress) AccountType() ledger.AccountType {
return a.accountType
}
func (a impactTestAddress) Route() ledger.SubAccountRoute {
return a.route
}
func (a impactTestAddress) Equal(other ledger.PostingAddress) bool {
return a.SubAccountID() == other.SubAccountID() &&
a.AccountType() == other.AccountType() &&
a.Route().ID() == other.Route().ID()
}
var _ ledger.PostingAddress = impactTestAddress{}
func mustImpactTestEntry(t *testing.T, accountType ledger.AccountType, route ledger.Route, amount ...alpacadecimal.Decimal) ledger.Entry {
t.Helper()
normalizedRoute, err := route.Normalize()
require.NoError(t, err)
routingKey, err := ledger.BuildRoutingKey(normalizedRoute)
require.NoError(t, err)
subAccountRoute, err := ledger.NewSubAccountRouteFromData("route-id", routingKey, normalizedRoute)
require.NoError(t, err)
entryAmount := alpacadecimal.NewFromInt(1)
if len(amount) > 0 {
entryAmount = amount[0]
}
return impactTestEntry{
id: models.NamespacedID{Namespace: "ns", ID: "entry-id"},
txID: models.NamespacedID{Namespace: "ns", ID: "tx-id"},
address: impactTestAddress{
subAccountID: "sub-account-id",
accountType: accountType,
route: subAccountRoute,
},
amount: entryAmount,
}
}