Implement new *transfer_balance action

Added possibility to mock datamanager account functions.

Fixed typo in SubtractValue function name.

Added unit & integration tests.
This commit is contained in:
ionutboangiu
2024-01-09 08:08:16 -05:00
committed by Dan Christian Bogos
parent 57413b1346
commit b76d61281c
7 changed files with 674 additions and 16 deletions

View File

@@ -214,7 +214,7 @@ func (acc *Account) debitBalanceAction(a *Action, reset, resetIfNegative bool, f
if reset || (resetIfNegative && b.Value < 0) {
b.SetValue(0)
}
b.SubstractValue(bClone.GetValue())
b.SubtractValue(bClone.GetValue())
b.dirty = true
found = true
a.balanceValue = b.GetValue()
@@ -527,7 +527,7 @@ func (acc *Account) debitCreditBalance(cd *CallDescriptor, count bool, dryRun bo
}
cost := increment.Cost
defaultBalance := acc.GetDefaultMoneyBalance()
defaultBalance.SubstractValue(cost)
defaultBalance.SubtractValue(cost)
increment.BalanceInfo.Monetary = &MonetaryInfo{
UUID: defaultBalance.Uuid,
@@ -868,7 +868,7 @@ func (acc *Account) DebitConnectionFee(cc *CallCost, ufMoneyBalances Balances, c
var connectFeePaid bool
for _, b := range ufMoneyBalances {
if b.GetValue() >= connectFee {
b.SubstractValue(connectFee)
b.SubtractValue(connectFee)
// the conect fee is not refundable!
if count {
acc.countUnits(connectFee, utils.MetaMonetary, cc, b, fltrS)
@@ -886,7 +886,7 @@ func (acc *Account) DebitConnectionFee(cc *CallCost, ufMoneyBalances Balances, c
cc.negativeConnectFee = true
// there are no money for the connect fee; go negative
b := acc.GetDefaultMoneyBalance()
b.SubstractValue(connectFee)
b.SubtractValue(connectFee)
debitedBalance = *b
// the conect fee is not refundable!
if count {

View File

@@ -91,6 +91,7 @@ func init() {
actionFuncMap[utils.MetaTopUp] = topupAction
actionFuncMap[utils.MetaDebitReset] = debitResetAction
actionFuncMap[utils.MetaDebit] = debitAction
actionFuncMap[utils.MetaTransferBalance] = transferBalanceAction
actionFuncMap[utils.MetaResetCounters] = resetCountersAction
actionFuncMap[utils.MetaEnableAccount] = enableAccountAction
actionFuncMap[utils.MetaDisableAccount] = disableAccountAction
@@ -122,6 +123,84 @@ func RegisterActionFunc(action string, f actionTypeFunc) {
actionFuncMap[action] = f
}
// transferBalanceAction transfers units between accounts' balances.
// It ensures both source and destination balances are of the same type and non-expired.
// Destination account and balance IDs are obtained from Action's ExtraParameters.
// ExtraParameters should be a JSON string containing keys 'DestAccountID' and 'DestBalanceID',
// which identify the destination account and balance for the transfer.
func transferBalanceAction(srcAcc *Account, act *Action, _ Actions, fltrS *FilterS, _ any) error {
if srcAcc == nil {
return errors.New("source account is nil")
}
if act.Balance.Type == nil {
return errors.New("balance type is missing")
}
if act.Balance.ID == nil {
return errors.New("source balance ID is missing")
}
if act.ExtraParameters == "" {
return errors.New("ExtraParameters used to identify the destination balance are missing")
}
if len(srcAcc.BalanceMap) == 0 {
return fmt.Errorf("account %s has no balances to transfer from", srcAcc.ID)
}
srcBalance := srcAcc.GetBalanceWithID(*act.Balance.Type, *act.Balance.ID)
if srcBalance == nil || srcBalance.IsExpiredAt(time.Now()) {
return errors.New("source balance not found or expired")
}
transferUnits := act.Balance.GetValue()
if transferUnits == 0 {
return errors.New("balance value is missing or 0")
}
if transferUnits > srcBalance.Value {
return utils.ErrInsufficientCredit
}
accDestInfo := struct {
DestAccountID string
DestBalanceID string
}{}
if err := json.Unmarshal([]byte(act.ExtraParameters), &accDestInfo); err != nil {
return err
}
// This guard is meant to lock the destination account as we are making changes to it. It was not needed
// for the source account due to it being locked from outside this function.
guardErr := guardian.Guardian.Guard(func() error {
destAcc, err := dm.GetAccount(accDestInfo.DestAccountID)
if err != nil {
return fmt.Errorf("retrieving destination account failed: %w", err)
}
destBalance := destAcc.GetBalanceWithID(*act.Balance.Type, accDestInfo.DestBalanceID)
if destBalance == nil || destBalance.IsExpiredAt(time.Now()) {
return errors.New("destination balance not found or expired")
}
srcBalance.SubtractValue(transferUnits)
srcBalance.dirty = true
destBalance.AddValue(transferUnits)
destBalance.dirty = true
destAcc.InitCounters()
destAcc.ExecuteActionTriggers(act, fltrS)
if err := dm.SetAccount(destAcc); err != nil {
return fmt.Errorf("updating destination account failed: %w", err)
}
return nil
}, config.CgrConfig().GeneralCfg().LockingTimeout, utils.AccountPrefix+accDestInfo.DestAccountID)
if guardErr != nil {
return guardErr
}
// Execute action triggers for the source account. This account will be updated in the parent function.
srcAcc.InitCounters()
srcAcc.ExecuteActionTriggers(act, fltrS)
return nil
}
func logAction(ub *Account, a *Action, acs Actions, _ *FilterS, extraData any) (err error) {
switch {
case ub != nil:

View File

@@ -4197,3 +4197,403 @@ func TestRemoveAccountActionLogg(t *testing.T) {
}
}
func TestActionsTransferBalance(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
var srcAcc *Account
var destAcc *Account
db := &DataDBMock{
SetAccountDrvF: func(acc *Account) error {
switch {
case strings.HasSuffix(acc.ID, "_FAIL"):
return utils.ErrServerError
case srcAcc.ID == acc.ID:
srcAcc = acc
case destAcc.ID == acc.ID:
destAcc = acc
default:
return utils.ErrAccountNotFound
}
return nil
},
GetAccountDrvF: func(id string) (*Account, error) {
if destAcc == nil || destAcc.ID != id {
return nil, utils.ErrNotFound
}
return destAcc, nil
},
}
mockedDM := NewDataManager(db, cfg.CacheCfg(), nil)
originalDM := dm
defer func() { dm = originalDM }()
dm = mockedDM
testcases := []struct {
name string
srcAcc *Account
destAcc *Account
act *Action
expectedSrcBalance float64
expectedDestBalance float64
expectedErr string
}{
{
name: "SuccessfulTransfer",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 10,
},
},
},
},
destAcc: &Account{
ID: "cgrates.org:ACC_DEST",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_DEST",
Value: 5,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ActionType: utils.MetaTransferBalance,
ExtraParameters: `{
"DestAccountID": "cgrates.org:ACC_DEST",
"DestBalanceID": "BALANCE_DEST"
}`,
Balance: &BalanceFilter{
ID: utils.StringPointer("BALANCE_SRC"),
Type: utils.StringPointer(utils.MetaMonetary),
Value: &utils.ValueFormula{
Static: 3,
},
},
},
expectedSrcBalance: 7,
expectedDestBalance: 8,
},
{
name: "NilAccount",
expectedErr: "source account is nil",
},
{
name: "UnspecifiedBalanceType",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
Balance: &BalanceFilter{},
},
expectedErr: "balance type is missing",
},
{
name: "UnspecifiedBalanceID",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
},
},
expectedErr: "source balance ID is missing",
},
{
name: "MissingDestinationParameters",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
},
},
expectedErr: "ExtraParameters used to identify the destination balance are missing",
},
{
name: "MissingSourceAccountBalances",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: "invalid_params",
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
},
},
expectedErr: "account cgrates.org:ACC_SRC has no balances to transfer from",
},
{
name: "SourceAccountBalanceNotFound",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "*default",
Value: 10,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: "invalid_params",
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
},
},
expectedErr: "source balance not found or expired",
},
{
name: "InvalidExtraParameters",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 10,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: "invalid_params",
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
Value: &utils.ValueFormula{
Static: 3,
},
},
},
expectedErr: "invalid character 'i' looking for beginning of value",
},
{
name: "DestinationAccountNotFound",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 10,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: `{
"DestAccountID": "cgrates.org:ACC_DEST",
"DestBalanceID": "BALANCE_DEST"
}`,
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
Value: &utils.ValueFormula{
Static: 3,
},
},
},
expectedErr: "retrieving destination account failed: NOT_FOUND",
},
{
name: "DestinationBalanceNotFound",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 10,
},
},
},
},
destAcc: &Account{
ID: "cgrates.org:ACC_DEST",
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: `{
"DestAccountID": "cgrates.org:ACC_DEST",
"DestBalanceID": "BALANCE_DEST"
}`,
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
Value: &utils.ValueFormula{
Static: 3,
},
},
},
expectedErr: "destination balance not found or expired",
},
{
name: "TransferUnitsNotSpecifiedOr0",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 1,
},
},
},
},
destAcc: &Account{
ID: "cgrates.org:ACC_DEST",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_DEST",
Value: 5,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: `{
"DestAccountID": "cgrates.org:ACC_DEST",
"DestBalanceID": "BALANCE_DEST"
}`,
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
Value: &utils.ValueFormula{
Static: 0,
},
},
},
expectedErr: "balance value is missing or 0",
},
{
name: "NotEnoughFundsInSourceBalance",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 1,
},
},
},
},
destAcc: &Account{
ID: "cgrates.org:ACC_DEST",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_DEST",
Value: 5,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: `{
"DestAccountID": "cgrates.org:ACC_DEST",
"DestBalanceID": "BALANCE_DEST"
}`,
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
Value: &utils.ValueFormula{
Static: 3,
},
},
},
expectedErr: "INSUFFICIENT_CREDIT",
},
{
name: "DestinationBalanceFailedUpdate",
srcAcc: &Account{
ID: "cgrates.org:ACC_SRC",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_SRC",
Value: 10,
},
},
},
},
destAcc: &Account{
ID: "cgrates.org:ACC_DEST_FAIL",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
{
ID: "BALANCE_DEST",
Value: 5,
},
},
},
},
act: &Action{
Id: "ACT_TRANSFER_BALANCE",
ExtraParameters: `{
"DestAccountID": "cgrates.org:ACC_DEST_FAIL",
"DestBalanceID": "BALANCE_DEST"
}`,
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MetaMonetary),
ID: utils.StringPointer("BALANCE_SRC"),
Value: &utils.ValueFormula{
Static: 3,
},
},
},
expectedErr: "updating destination account failed: SERVER_ERROR",
},
}
verifyBalance := func(t *testing.T, acc *Account, expected float64, accType, balanceType string, balanceIdx int) {
t.Helper()
balance := acc.BalanceMap[balanceType]
balanceValue := balance[balanceIdx].Value
if balanceValue != expected {
t.Errorf("received wrong %s balance value: expected %v, received %v",
accType, expected, balanceValue)
}
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
srcAcc = tc.srcAcc
destAcc = tc.destAcc
err := transferBalanceAction(srcAcc, tc.act, nil, nil, nil)
if tc.expectedErr != "" {
if err == nil || err.Error() != tc.expectedErr {
t.Errorf("expected error %v, received %v", tc.expectedErr, err)
}
return
}
if err != nil {
t.Fatal(err)
}
verifyBalance(t, srcAcc, tc.expectedSrcBalance, "source", utils.MetaMonetary, 0)
verifyBalance(t, destAcc, tc.expectedDestBalance, "destination", utils.MetaMonetary, 0)
})
}
}

View File

@@ -282,7 +282,7 @@ func (b *Balance) AddValue(amount float64) {
b.SetValue(b.GetValue() + amount)
}
func (b *Balance) SubstractValue(amount float64) {
func (b *Balance) SubtractValue(amount float64) {
b.SetValue(b.GetValue() - amount)
}
@@ -347,7 +347,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
globalRoundingDecimals, utils.MetaRoundingUp)
}
if b.GetValue() >= amount {
b.SubstractValue(amount)
b.SubtractValue(amount)
inc.BalanceInfo.Unit = &UnitInfo{
UUID: b.Uuid,
ID: b.ID,
@@ -477,7 +477,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
moneyBal = ub.GetDefaultMoneyBalance()
}
if b.GetValue() >= amount && (moneyBal != nil || cost == 0) {
b.SubstractValue(amount)
b.SubtractValue(amount)
inc.BalanceInfo.Unit = &UnitInfo{
UUID: b.Uuid,
ID: b.ID,
@@ -489,7 +489,7 @@ func (b *Balance) debitUnits(cd *CallDescriptor, ub *Account, moneyBalances Bala
}
inc.BalanceInfo.AccountID = ub.ID
if cost != 0 {
moneyBal.SubstractValue(cost)
moneyBal.SubtractValue(cost)
inc.BalanceInfo.Monetary = &MonetaryInfo{
UUID: moneyBal.Uuid,
ID: moneyBal.ID,
@@ -635,7 +635,7 @@ func (b *Balance) debitMoney(cd *CallDescriptor, ub *Account, moneyBalances Bala
}
if b.GetValue() >= amount {
b.SubstractValue(amount)
b.SubtractValue(amount)
cd.MaxCostSoFar += amount
inc.BalanceInfo.Monetary = &MonetaryInfo{
UUID: b.Uuid,
@@ -880,7 +880,7 @@ func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances,
globalRoundingDecimals, utils.MetaRoundingUp)
}
if b.GetValue() >= amount {
b.SubstractValue(amount)
b.SubtractValue(amount)
inc.BalanceInfo.Unit = &UnitInfo{
UUID: b.Uuid,
ID: b.ID,
@@ -1049,7 +1049,7 @@ func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances,
}
if isUnitBal { // unit balance
b.SubstractValue(amount)
b.SubtractValue(amount)
inc.BalanceInfo.Unit = &UnitInfo{
UUID: b.Uuid,
ID: b.ID,
@@ -1061,7 +1061,7 @@ func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances,
}
inc.BalanceInfo.AccountID = ub.ID
if cost != 0 {
moneyBal.SubstractValue(cost)
moneyBal.SubtractValue(cost)
inc.BalanceInfo.Monetary = &MonetaryInfo{
UUID: moneyBal.Uuid,
ID: moneyBal.ID,
@@ -1076,7 +1076,7 @@ func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances,
}
}
} else { // monetary balance
b.SubstractValue(cost)
b.SubtractValue(cost)
cd.MaxCostSoFar += cost
inc.BalanceInfo.Monetary = &MonetaryInfo{
UUID: b.Uuid,

View File

@@ -43,6 +43,9 @@ type DataDBMock struct {
RemoveActionPlanDrvF func(key string) (err error)
GetRouteProfileDrvF func(tenant, id string) (rp *RouteProfile, err error)
RemoveRouteProfileDrvF func(tenant, id string) error
GetAccountDrvF func(id string) (*Account, error)
SetAccountDrvF func(acc *Account) error
RemoveAccountDrvF func(id string) error
}
// Storage methods
@@ -213,15 +216,24 @@ func (dbM *DataDBMock) PopTask() (*Task, error) {
return nil, utils.ErrNotImplemented
}
func (dbM *DataDBMock) GetAccountDrv(string) (*Account, error) {
func (dbM *DataDBMock) GetAccountDrv(id string) (*Account, error) {
if dbM.GetAccountDrvF != nil {
return dbM.GetAccountDrvF(id)
}
return nil, utils.ErrNotImplemented
}
func (dbM *DataDBMock) SetAccountDrv(*Account) error {
func (dbM *DataDBMock) SetAccountDrv(acc *Account) error {
if dbM.SetAccountDrvF != nil {
return dbM.SetAccountDrvF(acc)
}
return utils.ErrNotImplemented
}
func (dbM *DataDBMock) RemoveAccountDrv(string) error {
func (dbM *DataDBMock) RemoveAccountDrv(id string) error {
if dbM.RemoveAccountDrvF != nil {
return dbM.RemoveAccountDrvF(id)
}
return utils.ErrNotImplemented
}

View File

@@ -0,0 +1,166 @@
//go:build integration
// +build integration
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package general_tests
import (
"bytes"
"sort"
"testing"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// TestTransferBalance tests the implementation of the "*transfer_balance" action.
//
// The test steps are as follows:
// 1. Create two accounts with a single *monetary balance of 10 units.
// 2. Set a "*transfer_balance" action that takes 4 units from the source balance and adds them to the destination balance.
// 3. Execute that action using the method "APIerSv1.ExecuteAction".
// 4. Check the balances; the source balance should now have 6 units while the destination balance should have 14.
func TestTransferBalance(t *testing.T) {
switch *dbType {
case utils.MetaInternal:
case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres:
t.SkipNow()
default:
t.Fatal("unsupported dbtype value")
}
content := `{
"data_db": {
"db_type": "*internal"
},
"stor_db": {
"db_type": "*internal"
},
"schedulers": {
"enabled": true
},
"apiers": {
"enabled": true,
"scheduler_conns": ["*internal"]
}
}`
tpFiles := map[string]string{
utils.AccountActionsCsv: `#Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled
cgrates.org,ACC_SRC,PACKAGE_ACC_SRC,,,
cgrates.org,ACC_DEST,PACKAGE_ACC_DEST,,,`,
utils.ActionPlansCsv: `#Id,ActionsId,TimingId,Weight
PACKAGE_ACC_SRC,ACT_TOPUP_SRC,*asap,10
PACKAGE_ACC_DEST,ACT_TOPUP_DEST,*asap,10`,
utils.ActionsCsv: `#ActionsId[0],Action[1],ExtraParameters[2],Filter[3],BalanceId[4],BalanceType[5],Categories[6],DestinationIds[7],RatingSubject[8],SharedGroup[9],ExpiryTime[10],TimingIds[11],Units[12],BalanceWeight[13],BalanceBlocker[14],BalanceDisabled[15],Weight[16]
ACT_TOPUP_SRC,*topup_reset,,,balance_src,*monetary,,*any,,,*unlimited,,10,20,false,false,20
ACT_TOPUP_DEST,*topup_reset,,,balance_dest,*monetary,,*any,,,*unlimited,,10,10,false,false,10
ACT_TRANSFER,*transfer_balance,"{""DestAccountID"":""cgrates.org:ACC_DEST"",""DestBalanceID"":""balance_dest""}",,balance_src,*monetary,,,,,*unlimited,,4,,,,`,
}
buf := &bytes.Buffer{}
testEnv := TestEnvironment{
Name: "TestTransferBalance",
// Encoding: *encoding,
ConfigJSON: content,
TpFiles: tpFiles,
LogBuffer: buf,
}
client, _, shutdown, err := testEnv.Setup(t, *waitRater)
if err != nil {
t.Fatal(err)
}
defer shutdown()
t.Run("CheckInitialBalances", func(t *testing.T) {
var acnts []*engine.Account
if err := client.Call(context.Background(), utils.APIerSv2GetAccounts,
&utils.AttrGetAccounts{
Tenant: "cgrates.org",
}, &acnts); err != nil {
t.Error(err)
}
if len(acnts) != 2 {
t.Fatal("expecting 2 accounts to be retrieved")
}
sort.Slice(acnts, func(i, j int) bool {
return acnts[i].ID > acnts[j].ID
})
if len(acnts[0].BalanceMap) != 1 || len(acnts[0].BalanceMap[utils.MetaMonetary]) != 1 {
t.Fatalf("expected account to have only one balance of type *monetary, received %v", acnts[0])
}
balance := acnts[0].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_src" || balance.Value != 10 {
t.Fatalf("received account with unexpected balance: %v", balance)
}
if len(acnts[1].BalanceMap) != 1 || len(acnts[1].BalanceMap[utils.MetaMonetary]) != 1 {
t.Fatalf("expected account to have only one balance of type *monetary, received %v", acnts[1])
}
balance = acnts[1].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_dest" || balance.Value != 10 {
t.Fatalf("received account with unexpected balance: %v", balance)
}
})
t.Run("TransferBalance", func(t *testing.T) {
var reply string
attrsEA := &utils.AttrExecuteAction{Tenant: "cgrates.org", Account: "ACC_SRC", ActionsId: "ACT_TRANSFER"}
if err := client.Call(context.Background(), utils.APIerSv1ExecuteAction, attrsEA, &reply); err != nil {
t.Error(err)
}
})
t.Run("CheckFinalBalances", func(t *testing.T) {
var acnts []*engine.Account
if err := client.Call(context.Background(), utils.APIerSv2GetAccounts,
&utils.AttrGetAccounts{
Tenant: "cgrates.org",
}, &acnts); err != nil {
t.Error(err)
}
if len(acnts) != 2 {
t.Fatal("expecting 2 accounts to be retrieved")
}
sort.Slice(acnts, func(i, j int) bool {
return acnts[i].ID > acnts[j].ID
})
if len(acnts[0].BalanceMap) != 1 || len(acnts[0].BalanceMap[utils.MetaMonetary]) != 1 {
t.Errorf("expected account to have only one balance of type *monetary, received %v", acnts[0])
}
balance := acnts[0].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_src" || balance.Value != 6 {
t.Errorf("received account with unexpected balance: %v", balance)
}
if len(acnts[1].BalanceMap) != 1 || len(acnts[1].BalanceMap[utils.MetaMonetary]) != 1 {
t.Errorf("expected account to have only one balance of type *monetary, received %v", acnts[1])
}
balance = acnts[1].BalanceMap[utils.MetaMonetary][0]
if balance.ID != "balance_dest" || balance.Value != 14 {
t.Errorf("received account with unexpected balance: %v", balance)
}
})
}

View File

@@ -1034,6 +1034,7 @@ const (
MetaTopUp = "*topup"
MetaDebitReset = "*debit_reset"
MetaDebit = "*debit"
MetaTransferBalance = "*transfer_balance"
MetaResetCounters = "*reset_counters"
MetaEnableAccount = "*enable_account"
MetaDisableAccount = "*disable_account"