diff --git a/engine/account.go b/engine/account.go index d733af1c8..16493a6cb 100644 --- a/engine/account.go +++ b/engine/account.go @@ -699,7 +699,9 @@ func (acc *Account) DebitConnectionFee(cc *CallCost, usefulMoneyBalances Balance func (acc *Account) matchConditions(condition string) (bool, error) { cl := &utils.CondLoader{} - cl.Parse(condition) + if err := cl.Parse(condition); err != nil { + return false, err + } for balanceType, balanceChain := range acc.BalanceMap { for _, b := range balanceChain { check, err := cl.Check(&struct { diff --git a/engine/actions_test.go b/engine/actions_test.go index 12adecf9d..4eaaec183 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -1568,6 +1568,197 @@ func TestActionTransferMonetaryDefaultFilter(t *testing.T) { } } +func TestActionConditionalTopup(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:cond", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Id: utils.META_DEFAULT, + Value: 10, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 3, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 1, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + Weight: 20, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: CONDITIONAL_TOPUP, + BalanceType: utils.MONETARY, + ExtraParameters: `{"Type":"*monetary","Value":1,"Weight":10}`, + Balance: &Balance{ + Value: 11, + Weight: 30, + }, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:cond": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:cond") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if len(afterUb.BalanceMap[utils.MONETARY]) != 5 || + afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 31 || + afterUb.BalanceMap[utils.MONETARY][4].Value != 11 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + +func TestActionConditionalTopupNoMatch(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:cond", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Id: utils.META_DEFAULT, + Value: 10, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 3, + Weight: 20, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 1, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + Weight: 20, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: CONDITIONAL_TOPUP, + BalanceType: utils.MONETARY, + ExtraParameters: `{"Type":"*monetary","Value":2,"Weight":10}`, + Balance: &Balance{ + Value: 11, + Weight: 30, + }, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:cond": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:cond") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if len(afterUb.BalanceMap[utils.MONETARY]) != 4 || + afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 20 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + +func TestActionConditionalTopupExistingBalance(t *testing.T) { + err := accountingStorage.SetAccount( + &Account{ + Id: "cgrates.org:cond", + BalanceMap: map[string]BalanceChain{ + utils.MONETARY: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Value: 1, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 6, + Weight: 20, + }, + }, + utils.VOICE: BalanceChain{ + &Balance{ + Uuid: utils.GenUUID(), + Value: 10, + Weight: 10, + }, + &Balance{ + Uuid: utils.GenUUID(), + Value: 100, + Weight: 20, + }, + }, + }, + }) + if err != nil { + t.Errorf("error setting account: %v", err) + } + + a := &Action{ + ActionType: CONDITIONAL_TOPUP, + BalanceType: utils.MONETARY, + ExtraParameters: `{"Type":"*voice","Value":{"*gte":100}}`, + Balance: &Balance{ + Value: 11, + Weight: 10, + }, + } + + at := &ActionTiming{ + accountIDs: map[string]struct{}{"cgrates.org:cond": struct{}{}}, + actions: Actions{a}, + } + at.Execute() + + afterUb, err := accountingStorage.GetAccount("cgrates.org:cond") + if err != nil { + t.Error("account not found: ", err, afterUb) + } + if len(afterUb.BalanceMap[utils.MONETARY]) != 2 || + afterUb.BalanceMap[utils.MONETARY].GetTotalValue() != 18 { + for _, b := range afterUb.BalanceMap[utils.MONETARY] { + t.Logf("B: %+v", b) + } + t.Error("ransfer balance value: ", afterUb.BalanceMap[utils.MONETARY].GetTotalValue()) + } +} + /**************** Benchmarks ********************************/ func BenchmarkUUID(b *testing.B) { diff --git a/utils/cond_loader.go b/utils/cond_loader.go index 8fdfca867..6af762673 100644 --- a/utils/cond_loader.go +++ b/utils/cond_loader.go @@ -148,6 +148,13 @@ func (kv *keyValue) checkStruct(o interface{}) (bool, error) { return value.Interface() == kv.value, nil } +type trueElement struct{} + +func (te *trueElement) addChild(condElement) error { return ErrNotImplemented } +func (te *trueElement) checkStruct(o interface{}) (bool, error) { + return true, nil +} + func isOperator(s string) bool { return strings.HasPrefix(s, "*") } @@ -186,18 +193,27 @@ func (cp *CondLoader) load(a map[string]interface{}, parentElement condElement) if parentElement != nil { parentElement.addChild(currentElement) } else { - return currentElement, nil + if len(a) > 1 { + parentElement = &operatorSlice{operator: CondAND} + parentElement.addChild(currentElement) + } else { + return currentElement, nil + } } } - return nil, nil + return parentElement, nil } func (cp *CondLoader) Parse(s string) (err error) { a := make(map[string]interface{}) - if err := json.Unmarshal([]byte([]byte(s)), &a); err != nil { - return err + if len(s) != 0 { + if err := json.Unmarshal([]byte([]byte(s)), &a); err != nil { + return err + } + cp.rootElement, err = cp.load(a, nil) + } else { + cp.rootElement = &trueElement{} } - cp.rootElement, err = cp.load(a, nil) return } diff --git a/utils/cond_loader_test.go b/utils/cond_loader_test.go index c0e9930ff..908c29b19 100644 --- a/utils/cond_loader_test.go +++ b/utils/cond_loader_test.go @@ -20,6 +20,10 @@ func TestCondLoader(t *testing.T) { if err != nil { t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) } + err = cl.Parse(``) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } } func TestCondKeyValue(t *testing.T) { @@ -68,6 +72,27 @@ func TestCondKeyValue(t *testing.T) { if check, err := cl.Check(o); check || err != nil { t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) } + err = cl.Parse(`{"Field":6, "Other":false}`) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } + err = cl.Parse(`{"Other":true, "Field":{"*gt":5}}`) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); !check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } + err = cl.Parse(``) + if err != nil { + t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err) + } + if check, err := cl.Check(o); !check || err != nil { + t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement)) + } } func TestCondKeyValuePointer(t *testing.T) {