openmeter / ledger /routingrules /routingrules_test.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 5)
cee2387 verified
Raw
History Blame Contribute Delete
25.8 kB
package routingrules_test
import (
"testing"
"time"
"github.com/alpacahq/alpacadecimal"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
"github.com/openmeterio/openmeter/openmeter/ledger"
ledgeraccount "github.com/openmeterio/openmeter/openmeter/ledger/account"
"github.com/openmeterio/openmeter/openmeter/ledger/routingrules"
transactionstestutils "github.com/openmeterio/openmeter/openmeter/ledger/transactions/testutils"
"github.com/openmeterio/openmeter/pkg/currencyx"
)
func TestDefaultValidator_AllowsFBOToAccrued(t *testing.T) {
validator := routingrules.DefaultValidator
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsAccruedToFBO(t *testing.T) {
validator := routingrules.DefaultValidator
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsFBOToReceivableReverse(t *testing.T) {
validator := routingrules.DefaultValidator
openStatus := ledger.TransactionAuthorizationStatusOpen
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-open", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_RejectsForbiddenAccountCombination(t *testing.T) {
validator := routingrules.DefaultValidator
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earnings", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsDuplicateSubAccountEntries(t *testing.T) {
validator := routingrules.DefaultValidator
costBasis := alpacadecimal.NewFromInt(1)
accruedAddress := addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-dup", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
})
earningsAddress := addressForRoute(t, ledger.AccountTypeEarnings, "sub-dup", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
})
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: accruedAddress,
AmountValue: alpacadecimal.NewFromInt(-20),
},
&transactionstestutils.AnyEntryInput{
Address: earningsAddress,
AmountValue: alpacadecimal.NewFromInt(20),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_AllowsDuplicateSubAccountEntriesWithUniqueIdentities(t *testing.T) {
validator := routingrules.DefaultValidator
costBasis := alpacadecimal.NewFromInt(1)
accruedAddress := addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-source", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
})
earningsAddress := addressForRoute(t, ledger.AccountTypeEarnings, "sub-earnings", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
})
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: accruedAddress,
AmountValue: alpacadecimal.NewFromInt(-20),
IdentityKeyValue: "source:0",
},
&transactionstestutils.AnyEntryInput{
Address: accruedAddress,
AmountValue: alpacadecimal.NewFromInt(-10),
IdentityKeyValue: "source:1",
},
&transactionstestutils.AnyEntryInput{
Address: earningsAddress,
AmountValue: alpacadecimal.NewFromInt(30),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsDuplicateSubAccountEntriesWithUniqueProvenanceIdentities(t *testing.T) {
validator := routingrules.DefaultValidator
sourceChargeID1 := "01JABCDEF0123456789ABCDEFG"
sourceChargeID2 := "01JBCDEFG0123456789ABCDEFG"
spendChargeID := "01JCDEFGH0123456789ABCDEFG"
collectionSource := "collection-source"
identityKey1, _ := ledger.EntryIdentityParts{
CollectionSource: &collectionSource,
SourceChargeID: &sourceChargeID1,
SpendChargeID: &spendChargeID,
}.Text()
identityKey2, _ := ledger.EntryIdentityParts{
CollectionSource: &collectionSource,
SourceChargeID: &sourceChargeID2,
SpendChargeID: &spendChargeID,
}.Text()
costBasis := alpacadecimal.NewFromInt(1)
accruedAddress := addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-source", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
})
earningsAddress := addressForRoute(t, ledger.AccountTypeEarnings, "sub-earnings", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
})
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: accruedAddress,
AmountValue: alpacadecimal.NewFromInt(-20),
IdentityKeyValue: string(identityKey1),
SourceChargeIDValue: &sourceChargeID1,
SpendChargeIDValue: &spendChargeID,
},
&transactionstestutils.AnyEntryInput{
Address: accruedAddress,
AmountValue: alpacadecimal.NewFromInt(-10),
IdentityKeyValue: string(identityKey2),
SourceChargeIDValue: &sourceChargeID2,
SpendChargeIDValue: &spendChargeID,
},
&transactionstestutils.AnyEntryInput{
Address: earningsAddress,
AmountValue: alpacadecimal.NewFromInt(30),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_RejectsTaxBehaviorOutsideAccruedOrEarnings(t *testing.T) {
validator := routingrules.DefaultValidator
taxBehavior := ledger.TaxBehaviorExclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-tax-behavior", ledger.Route{
Currency: currencyx.Code("USD"),
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsTaxCodeOnFBO(t *testing.T) {
validator := routingrules.DefaultValidator
taxCode := "tax_A"
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-tax-code", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &taxCode,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsMismatchedReceivableAndFBORoute(t *testing.T) {
validator := routingrules.DefaultValidator
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo", ledger.Route{
Currency: currencyx.Code("EUR"),
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_AllowsFBOCostBasisTranslationBothDirections(t *testing.T) {
validator := routingrules.DefaultValidator
priority := ledger.DefaultCustomerFBOPriority
costBasis := alpacadecimal.NewFromInt(1)
unknownFBO := addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-unknown", ledger.Route{
Currency: currencyx.Code("USD"),
CreditPriority: &priority,
})
knownFBO := addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-known", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
CreditPriority: &priority,
})
t.Run("attribute", func(t *testing.T) {
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: unknownFBO,
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: knownFBO,
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
})
t.Run("correction", func(t *testing.T) {
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: knownFBO,
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: unknownFBO,
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
})
}
func TestDefaultValidator_RejectsFBOCostBasisTranslationWithMismatchedPriority(t *testing.T) {
validator := routingrules.DefaultValidator
unknownPriority := ledger.DefaultCustomerFBOPriority
knownPriority := ledger.DefaultCustomerFBOPriority + 1
costBasis := alpacadecimal.NewFromInt(1)
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-unknown", ledger.Route{
Currency: currencyx.Code("USD"),
CreditPriority: &unknownPriority,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-known", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
CreditPriority: &knownPriority,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsFBOCostBasisTranslationWithMismatchedTaxBehavior(t *testing.T) {
validator := routingrules.DefaultValidator
priority := ledger.DefaultCustomerFBOPriority
costBasis := alpacadecimal.NewFromInt(1)
inclusive := ledger.TaxBehaviorInclusive
exclusive := ledger.TaxBehaviorExclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-unknown", ledger.Route{
Currency: currencyx.Code("USD"),
CreditPriority: &priority,
TaxBehavior: &inclusive,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo-known", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
CreditPriority: &priority,
TaxBehavior: &exclusive,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_AllowsReceivableCostBasisAttributionAcrossFeatures(t *testing.T) {
validator := routingrules.DefaultValidator
openStatus := ledger.TransactionAuthorizationStatusOpen
costBasis := alpacadecimal.NewFromFloat(0.5)
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-advance", ledger.Route{
Currency: currencyx.Code("USD"),
Features: []string{"api-calls"},
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(20),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-attributed", ledger.Route{
Currency: currencyx.Code("USD"),
CostBasis: &costBasis,
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(-20),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsReceivableAuthorizationTransition(t *testing.T) {
validator := routingrules.DefaultValidator
openStatus := ledger.TransactionAuthorizationStatusOpen
status := ledger.TransactionAuthorizationStatusAuthorized
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-authorized", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &status,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-open", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_RejectsReceivableAuthorizationTransitionWithWrongDirection(t *testing.T) {
validator := routingrules.DefaultValidator
openStatus := ledger.TransactionAuthorizationStatusOpen
status := ledger.TransactionAuthorizationStatusAuthorized
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-open", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-authorized", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &status,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_AllowsWashToAuthorizedReceivable(t *testing.T) {
validator := routingrules.DefaultValidator
status := ledger.TransactionAuthorizationStatusAuthorized
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeWash, "sub-wash", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-authorized", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &status,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsFBOToTaxedAccrued(t *testing.T) {
validator := routingrules.DefaultValidator
tax := "tax_A"
taxBehavior := ledger.TaxBehaviorInclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerFBO, "sub-fbo", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-tax", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &tax,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_RejectsTaxCodeOnReceivable(t *testing.T) {
validator := routingrules.DefaultValidator
openStatus := ledger.TransactionAuthorizationStatusOpen
taxA := "tax_A"
taxBehavior := ledger.TaxBehaviorInclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-taxA", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &taxA,
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-taxA", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &taxA,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsFeaturesOnAccrued(t *testing.T) {
validator := routingrules.DefaultValidator
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-feature", ledger.Route{
Currency: currencyx.Code("USD"),
Features: []string{"api-calls"},
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsAccruedToEarningsTaxCodeMismatch(t *testing.T) {
validator := routingrules.DefaultValidator
taxA := "tax_A"
taxB := "tax_B"
taxBehavior := ledger.TaxBehaviorInclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-taxA", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &taxA,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earn-taxB", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &taxB,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_RejectsAccruedToEarningsTaxBehaviorMismatch(t *testing.T) {
validator := routingrules.DefaultValidator
inclusive := ledger.TaxBehaviorInclusive
exclusive := ledger.TaxBehaviorExclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-inclusive", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: lo.ToPtr("tax_A"),
TaxBehavior: &inclusive,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earn-exclusive", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: lo.ToPtr("tax_A"),
TaxBehavior: &exclusive,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_AllowsReceivableToAccruedMatchingTaxCode(t *testing.T) {
validator := routingrules.DefaultValidator
openStatus := ledger.TransactionAuthorizationStatusOpen
tax := "tax_A"
taxBehavior := ledger.TaxBehaviorInclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &openStatus,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-tax", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &tax,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsAccruedToEarningsMatchingTaxCode(t *testing.T) {
validator := routingrules.DefaultValidator
tax := "tax_A"
taxBehavior := ledger.TaxBehaviorInclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-tax", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &tax,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earn-tax", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &tax,
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_AllowsAccruedToEarningsTaxCodeWithoutTaxBehavior(t *testing.T) {
validator := routingrules.DefaultValidator
tax := "tax_A"
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-tax-only", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &tax,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earn-tax-only", ledger.Route{
Currency: currencyx.Code("USD"),
TaxCode: &tax,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_RejectsTaxBehaviorWithoutTaxCode(t *testing.T) {
validator := routingrules.DefaultValidator
taxBehavior := ledger.TaxBehaviorInclusive
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-behavior-only", ledger.Route{
Currency: currencyx.Code("USD"),
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earn-behavior-only", ledger.Route{
Currency: currencyx.Code("USD"),
TaxBehavior: &taxBehavior,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func TestDefaultValidator_AllowsAccruedToEarningsNilTaxCodeBothSides(t *testing.T) {
validator := routingrules.DefaultValidator
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerAccrued, "sub-accrued-nil", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeEarnings, "sub-earn-nil", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.NoError(t, err)
}
func TestDefaultValidator_RejectsWashToOpenReceivable(t *testing.T) {
validator := routingrules.DefaultValidator
status := ledger.TransactionAuthorizationStatusOpen
err := validator.ValidateEntries([]ledger.EntryInput{
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeWash, "sub-wash", ledger.Route{
Currency: currencyx.Code("USD"),
}),
AmountValue: alpacadecimal.NewFromInt(-50),
},
&transactionstestutils.AnyEntryInput{
Address: addressForRoute(t, ledger.AccountTypeCustomerReceivable, "sub-rec-open", ledger.Route{
Currency: currencyx.Code("USD"),
TransactionAuthorizationStatus: &status,
}),
AmountValue: alpacadecimal.NewFromInt(50),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "ledger routing rule violated")
}
func addressForRoute(t *testing.T, accountType ledger.AccountType, subAccountID string, route ledger.Route) ledger.PostingAddress {
t.Helper()
key, err := ledger.BuildRoutingKey(route)
require.NoError(t, err)
addr, err := ledgeraccount.NewAddressFromData(ledgeraccount.AddressData{
SubAccountID: subAccountID,
AccountType: accountType,
Route: route,
RouteID: "route-" + subAccountID + "-" + time.Now().UTC().Format("150405.000000000"),
RoutingKey: key,
})
require.NoError(t, err)
return addr
}