Add 2 new actions (*topup_zero_negative and *set_expiry) and test for them

This commit is contained in:
TeoV
2018-02-14 11:59:51 +02:00
committed by Dan Christian Bogos
parent 0a94cd76f0
commit bea5803f76
9 changed files with 169 additions and 39 deletions

View File

@@ -140,7 +140,6 @@ func (acc *Account) setBalanceAction(a *Action) error {
acc.BalanceMap[*a.Balance.Type] = append(acc.BalanceMap[*a.Balance.Type], balance)
}
}
if a.Balance.ID != nil && *a.Balance.ID == utils.META_DEFAULT { // treat it separately since modifyBalance sets expiry and others parameters, not specific for *default
if a.Balance.Value != nil {
balance.ID = *a.Balance.ID
@@ -185,7 +184,7 @@ func (acc *Account) setBalanceAction(a *Action) error {
// Debits some amount of user's specified balance adding the balance if it does not exists.
// Returns the remaining credit in user's balance.
func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
func (ub *Account) debitBalanceAction(a *Action, reset, resetIfNegative, resetExpiry bool) error {
if a == nil {
return errors.New("nil action")
}
@@ -204,14 +203,23 @@ func (ub *Account) debitBalanceAction(a *Action, reset bool) error {
continue // just to be safe (cleaned expired balances above)
}
b.account = ub
if b.MatchFilter(a.Balance, false) {
if reset {
b.SetValue(0)
//if resetExpiry if false we do normal match otherwise modify
if resetExpiry {
if b.MatchFilter(a.Balance, false, resetExpiry) {
b.ExpirationDate = a.Balance.GetExpirationDate()
b.dirty = true
found = true
}
} else {
if b.MatchFilter(a.Balance, false, resetExpiry) {
if reset || (resetIfNegative && b.Value < 0) {
b.SetValue(0)
}
b.SubstractValue(bClone.GetValue())
b.dirty = true
found = true
a.balanceValue = b.GetValue()
}
b.SubstractValue(bClone.GetValue())
b.dirty = true
found = true
a.balanceValue = b.GetValue()
}
}
// if it is not found then we add it to the list

View File

@@ -969,7 +969,7 @@ func TestAccountdebitBalance(t *testing.T) {
Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)),
}
a := &Action{Balance: newMb}
ub.debitBalanceAction(a, false)
ub.debitBalanceAction(a, false, false, false)
if len(ub.BalanceMap[utils.VOICE]) != 3 || !ub.BalanceMap[utils.VOICE][2].DestinationIDs.Equal(*newMb.DestinationIDs) {
t.Errorf("Error adding minute bucket! %d %+v %+v", len(ub.BalanceMap[utils.VOICE]), ub.BalanceMap[utils.VOICE][2], newMb)
}
@@ -1012,7 +1012,7 @@ func TestAccountdebitBalanceExists(t *testing.T) {
Directions: utils.StringMapPointer(utils.NewStringMap(utils.OUT)),
}
a := &Action{Balance: newMb}
ub.debitBalanceAction(a, false)
ub.debitBalanceAction(a, false, false, false)
if len(ub.BalanceMap[utils.VOICE]) != 2 || ub.BalanceMap[utils.VOICE][0].GetValue() != 25 {
t.Error("Error adding minute bucket!")
}
@@ -1024,7 +1024,7 @@ func TestAccountAddMinuteNil(t *testing.T) {
AllowNegative: true,
BalanceMap: map[string]Balances{utils.SMS: Balances{&Balance{Value: 14}}, utils.DATA: Balances{&Balance{Value: 1024}}, utils.VOICE: Balances{&Balance{Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}},
}
ub.debitBalanceAction(nil, false)
ub.debitBalanceAction(nil, false, false, false)
if len(ub.BalanceMap[utils.VOICE]) != 2 {
t.Error("Error adding minute bucket!")
}
@@ -1051,17 +1051,17 @@ func TestAccountAddMinutBucketEmpty(t *testing.T) {
}
ub := &Account{}
a := &Action{Balance: mb1}
ub.debitBalanceAction(a, false)
ub.debitBalanceAction(a, false, false, false)
if len(ub.BalanceMap[utils.VOICE]) != 1 {
t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE])
}
a = &Action{Balance: mb2}
ub.debitBalanceAction(a, false)
ub.debitBalanceAction(a, false, false, false)
if len(ub.BalanceMap[utils.VOICE]) != 1 || ub.BalanceMap[utils.VOICE][0].GetValue() != 20 {
t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE])
}
a = &Action{Balance: mb3}
ub.debitBalanceAction(a, false)
ub.debitBalanceAction(a, false, false, false)
if len(ub.BalanceMap[utils.VOICE]) != 2 {
t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE])
}

View File

@@ -79,6 +79,8 @@ const (
SET_DDESTINATIONS = "*set_ddestinations"
TRANSFER_MONETARY_DEFAULT = "*transfer_monetary_default"
CGR_RPC = "*cgr_rpc"
TopUpZeroNegative = "*topup_zero_negative"
SetExpiry = "*set_expiry"
)
func (a *Action) Clone() *Action {
@@ -115,6 +117,8 @@ func getActionFunc(typ string) (actionTypeFunc, bool) {
SET_BALANCE: setBalanceAction,
TRANSFER_MONETARY_DEFAULT: transferMonetaryDefaultAction,
CGR_RPC: cgrRPCAction,
TopUpZeroNegative: topupZeroNegativeAction,
SetExpiry: setExpiryAction,
}
f, exists := actionFuncMap[typ]
return f, exists
@@ -310,7 +314,7 @@ func topupResetAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Ac
}
c := a.Clone()
genericMakeNegative(c)
err = genericDebit(ub, c, true)
err = genericDebit(ub, c, true, false)
a.balanceValue = c.balanceValue
return
}
@@ -321,7 +325,7 @@ func topupAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions
}
c := a.Clone()
genericMakeNegative(c)
err = genericDebit(ub, c, false)
err = genericDebit(ub, c, false, false)
a.balanceValue = c.balanceValue
return
}
@@ -333,14 +337,14 @@ func debitResetAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Ac
if ub.BalanceMap == nil { // Init the map since otherwise will get error if nil
ub.BalanceMap = make(map[string]Balances, 0)
}
return genericDebit(ub, a, true)
return genericDebit(ub, a, true, false)
}
func debitAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) (err error) {
if ub == nil {
return errors.New("nil account")
}
err = genericDebit(ub, a, false)
err = genericDebit(ub, a, false, false)
return
}
@@ -360,14 +364,14 @@ func genericMakeNegative(a *Action) {
}
}
func genericDebit(ub *Account, a *Action, reset bool) (err error) {
func genericDebit(ub *Account, a *Action, reset, resetExpiry bool) (err error) {
if ub == nil {
return errors.New("nil account")
}
if ub.BalanceMap == nil {
ub.BalanceMap = make(map[string]Balances)
}
return ub.debitBalanceAction(a, reset)
return ub.debitBalanceAction(a, reset, false, resetExpiry)
}
func enableAccountAction(acc *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) (err error) {
@@ -544,7 +548,6 @@ func setddestinations(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Ac
}
func removeAccountAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) error {
var accID string
if ub != nil {
accID = ub.ID
@@ -615,7 +618,7 @@ func removeBalanceAction(ub *Account, sq *CDRStatsQueueTriggered, a *Action, acs
bChain := ub.BalanceMap[a.Balance.GetType()]
found := false
for i := 0; i < len(bChain); i++ {
if bChain[i].MatchFilter(a.Balance, false) {
if bChain[i].MatchFilter(a.Balance, false, false) {
// delete without preserving order
bChain[i] = bChain[len(bChain)-1]
bChain = bChain[:len(bChain)-1]
@@ -650,7 +653,7 @@ func transferMonetaryDefaultAction(acc *Account, sq *CDRStatsQueueTriggered, a *
for _, balance := range bChain {
if balance.Uuid != defaultBalance.Uuid &&
balance.ID != defaultBalance.ID && // extra caution
balance.MatchFilter(a.Balance, false) {
balance.MatchFilter(a.Balance, false, false) {
if balance.Value > 0 {
defaultBalance.Value += balance.Value
balance.Value = 0
@@ -747,6 +750,26 @@ func cgrRPCAction(account *Account, sq *CDRStatsQueueTriggered, a *Action, acs A
return nil
}
func topupZeroNegativeAction(account *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) error {
if account == nil {
return errors.New("nil account")
}
if account.BalanceMap == nil {
account.BalanceMap = make(map[string]Balances)
}
return account.debitBalanceAction(a, false, true, false)
}
func setExpiryAction(account *Account, sq *CDRStatsQueueTriggered, a *Action, acs Actions) error {
if account == nil {
return errors.New("nil account")
}
if account.BalanceMap == nil {
account.BalanceMap = make(map[string]Balances)
}
return account.debitBalanceAction(a, false, false, true)
}
// Structure to store actions according to weight
type Actions []*Action

View File

@@ -145,7 +145,7 @@ func (at *ActionTrigger) Match(a *Action) bool {
thresholdType = t.ThresholdType == "" || at.ThresholdType == t.ThresholdType
}
return thresholdType && at.Balance.CreateBalance().MatchFilter(a.Balance, false)
return thresholdType && at.Balance.CreateBalance().MatchFilter(a.Balance, false, false)
}
func (at *ActionTrigger) CreateBalance() *Balance {

View File

@@ -39,7 +39,6 @@ var actsLclCfgPath = path.Join(*dataDir, "conf", "samples", "actions")
var waitRater = flag.Int("wait_rater", 100, "Number of miliseconds to wait for rater to start and cache")
func TestActionsitInitCfg(t *testing.T) {
// Init config first
var err error
actsLclCfg, err = config.NewCGRConfigFromFolder(actsLclCfgPath)
@@ -51,7 +50,6 @@ func TestActionsitInitCfg(t *testing.T) {
}
func TestActionsitInitCdrDb(t *testing.T) {
if err := InitStorDb(actsLclCfg); err != nil {
t.Fatal(err)
}
@@ -59,7 +57,6 @@ func TestActionsitInitCdrDb(t *testing.T) {
// Finds cgr-engine executable and starts it with default configuration
func TestActionsitStartEngine(t *testing.T) {
if _, err := StartEngine(actsLclCfgPath, *waitRater); err != nil {
t.Fatal(err)
}
@@ -67,7 +64,6 @@ func TestActionsitStartEngine(t *testing.T) {
// Connect rpc client to rater
func TestActionsitRpcConn(t *testing.T) {
var err error
time.Sleep(500 * time.Millisecond)
actsLclRpc, err = jsonrpc.Dial("tcp", actsLclCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
@@ -121,7 +117,6 @@ func TestActionsitSetCdrlogDebit(t *testing.T) {
}
func TestActionsitSetCdrlogTopup(t *testing.T) {
var reply string
attrsSetAccount := &utils.AttrSetAccount{Tenant: "cgrates.org", Account: "dan2905"}
if err := actsLclRpc.Call("ApierV1.SetAccount", attrsSetAccount, &reply); err != nil {

View File

@@ -2449,7 +2449,107 @@ func TestActionCdrlogBalanceValue(t *testing.T) {
if len(cdrs) != 2 ||
cdrs[0].ExtraFields["BalanceValue"] != "11.1" ||
cdrs[1].ExtraFields["BalanceValue"] != "9" {
t.Errorf("Wrong cdrlogs: %", utils.ToIJSON(cdrs))
t.Errorf("Wrong cdrlogs: %s", utils.ToIJSON(cdrs))
}
}
func TestActionTopUpZeroNegative(t *testing.T) {
account := &Account{
ID: "cgrates.org:zeroNegative",
BalanceMap: map[string]Balances{
utils.MONETARY: Balances{
&Balance{
ID: "Bal1",
Value: -10,
},
&Balance{
ID: "Bal2",
Value: 5,
},
},
},
}
err := dm.DataDB().SetAccount(account)
if err != nil {
t.Error("Error setting account: ", err)
}
at := &ActionTiming{
accountIDs: utils.StringMap{"cgrates.org:zeroNegative": true},
Timing: &RateInterval{},
actions: []*Action{
&Action{
Id: "ZeroMonetary",
ActionType: TopUpZeroNegative,
Balance: &BalanceFilter{
Type: utils.StringPointer(utils.MONETARY),
},
},
},
}
err = at.Execute(nil, nil)
acc, err := dm.DataDB().GetAccount("cgrates.org:zeroNegative")
if err != nil || acc == nil {
t.Error("Error getting account: ", acc, err)
}
//Verify value for first balance(Bal1) should be 0 after execute action TopUpZeroNegative
if acc.BalanceMap[utils.MONETARY][0].Value != 0 {
t.Errorf("Expecting 0, received: %+v", acc.BalanceMap[utils.MONETARY][0].Value)
}
//Verify value for secound balance(Bal2) should be the same
if acc.BalanceMap[utils.MONETARY][1].Value != 5 {
t.Errorf("Expecting 5, received: %+v", acc.BalanceMap[utils.MONETARY][1].Value)
}
}
func TestActionSetExpiry(t *testing.T) {
var cloneTimeNowPlus24h time.Time
timeNowPlus24h := time.Now().Add(time.Duration(24 * time.Hour))
//Need clone because time.Now adds extra information that DeepEqual doesn't like
if err := utils.Clone(timeNowPlus24h, &cloneTimeNowPlus24h); err != nil {
t.Error(err)
}
account := &Account{
ID: "cgrates.org:zeroNegative",
BalanceMap: map[string]Balances{
utils.MONETARY: Balances{
&Balance{
ID: "Bal1",
Value: -10,
},
&Balance{
ID: "Bal2",
Value: 5,
},
},
},
}
err := dm.DataDB().SetAccount(account)
if err != nil {
t.Error("Error setting account: ", err)
}
at := &ActionTiming{
accountIDs: utils.StringMap{"cgrates.org:zeroNegative": true},
Timing: &RateInterval{},
actions: []*Action{
&Action{
Id: "SetExpiry",
ActionType: SetExpiry,
Balance: &BalanceFilter{
ID: utils.StringPointer("Bal1"),
Type: utils.StringPointer(utils.MONETARY),
ExpirationDate: utils.TimePointer(cloneTimeNowPlus24h),
},
},
},
}
err = at.Execute(nil, nil)
acc, err := dm.DataDB().GetAccount("cgrates.org:zeroNegative")
if err != nil || acc == nil {
t.Error("Error getting account: ", acc, err)
}
//Verify ExpirationDate for first balance(Bal1)
if acc.BalanceMap[utils.MONETARY][0].ExpirationDate != cloneTimeNowPlus24h {
t.Errorf("Expecting: %+v, received: %+v", cloneTimeNowPlus24h, acc.BalanceMap[utils.MONETARY][0].ExpirationDate)
}
}

View File

@@ -71,7 +71,7 @@ func (b *Balance) Equal(o *Balance) bool {
b.Blocker == o.Blocker
}
func (b *Balance) MatchFilter(o *BalanceFilter, skipIds bool) bool {
func (b *Balance) MatchFilter(o *BalanceFilter, skipIds, skipExpiry bool) bool {
if o == nil {
return true
}
@@ -81,8 +81,12 @@ func (b *Balance) MatchFilter(o *BalanceFilter, skipIds bool) bool {
if !skipIds && o.ID != nil && *o.ID != "" {
return b.ID == *o.ID
}
return (o.ExpirationDate == nil || b.ExpirationDate.Equal(*o.ExpirationDate)) &&
(o.Weight == nil || b.Weight == *o.Weight) &&
if !skipExpiry {
if o.ExpirationDate != nil && !b.ExpirationDate.Equal(*o.ExpirationDate) {
return false
}
}
return (o.Weight == nil || b.Weight == *o.Weight) &&
(o.Blocker == nil || b.Blocker == *o.Blocker) &&
(o.Disabled == nil || b.Disabled == *o.Disabled) &&
(o.DestinationIDs == nil || b.DestinationIDs.Includes(*o.DestinationIDs)) &&

View File

@@ -90,7 +90,7 @@ func TestBalanceEqual(t *testing.T) {
func TestBalanceMatchFilter(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &BalanceFilter{Weight: utils.Float64Pointer(1), RatingSubject: nil, DestinationIDs: nil}
if !mb1.MatchFilter(mb2, false) {
if !mb1.MatchFilter(mb2, false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
}
@@ -98,7 +98,7 @@ func TestBalanceMatchFilter(t *testing.T) {
func TestBalanceMatchFilterEmpty(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &BalanceFilter{}
if !mb1.MatchFilter(mb2, false) {
if !mb1.MatchFilter(mb2, false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
}
@@ -106,7 +106,7 @@ func TestBalanceMatchFilterEmpty(t *testing.T) {
func TestBalanceMatchFilterId(t *testing.T) {
mb1 := &Balance{ID: "T1", Weight: 2, precision: 2, RatingSubject: "2", DestinationIDs: utils.NewStringMap("NAT")}
mb2 := &BalanceFilter{ID: utils.StringPointer("T1"), Weight: utils.Float64Pointer(1), RatingSubject: utils.StringPointer("1"), DestinationIDs: nil}
if !mb1.MatchFilter(mb2, false) {
if !mb1.MatchFilter(mb2, false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
}
@@ -114,7 +114,7 @@ func TestBalanceMatchFilterId(t *testing.T) {
func TestBalanceMatchFilterDiffId(t *testing.T) {
mb1 := &Balance{ID: "T1", Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &BalanceFilter{ID: utils.StringPointer("T2"), Weight: utils.Float64Pointer(1), RatingSubject: utils.StringPointer("1"), DestinationIDs: nil}
if mb1.MatchFilter(mb2, false) {
if mb1.MatchFilter(mb2, false, false) {
t.Errorf("Match filter failure: %+v != %+v", mb1, mb2)
}
}

View File

@@ -79,7 +79,7 @@ func (ucs UnitCounters) addUnits(amount float64, kind string, cc *CallCost, b *B
continue
}
if uc.CounterType == utils.COUNTER_BALANCE && b != nil && b.MatchFilter(c.Filter, true) {
if uc.CounterType == utils.COUNTER_BALANCE && b != nil && b.MatchFilter(c.Filter, true, false) {
c.Value += amount
continue
}