Skip to content

Commit

Permalink
Implement new *transfer_balance action
Browse files Browse the repository at this point in the history
Added possibility to mock datamanager account functions.

Fixed typo in SubtractValue function name.

Added unit & integration tests.
  • Loading branch information
ionutboangiu authored and danbogos committed Jan 15, 2024
1 parent 57413b1 commit b76d612
Show file tree
Hide file tree
Showing 7 changed files with 674 additions and 16 deletions.
8 changes: 4 additions & 4 deletions engine/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
79 changes: 79 additions & 0 deletions engine/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit b76d612

Please sign in to comment.