Files
cgrates/rates/rates_test.go
2021-10-13 20:32:43 +03:00

1184 lines
33 KiB
Go

/*
Real-time Online/Offline Charging System (OerS) 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 rates
import (
"reflect"
"strings"
"testing"
"time"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func TestListenAndServe(t *testing.T) {
newRates := &RateS{}
cfgRld := make(chan struct{}, 1)
stopChan := make(chan struct{}, 1)
cfgRld <- struct{}{}
go func() {
time.Sleep(10 * time.Nanosecond)
stopChan <- struct{}{}
}()
newRates.ListenAndServe(stopChan, cfgRld)
}
func TestNewRateS(t *testing.T) {
config := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dataManager := engine.NewDataManager(data, config.CacheCfg(), nil)
filters := engine.NewFilterS(config, nil, dataManager)
expected := &RateS{
cfg: config,
filterS: filters,
dm: dataManager,
}
if newRateS := NewRateS(config, filters, dataManager); !reflect.DeepEqual(newRateS, expected) {
t.Errorf("Expected %+v, received %+v", expected, newRateS)
}
}
func TestRateProfileCostForEvent(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
},
}
//MatchItmID before setting
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
expectedRPCost := &utils.RateProfileCost{
ID: "RATE_1",
Cost: utils.NewDecimal(2, 1),
CostIntervals: []*utils.RateSIntervalCost{
{
Increments: []*utils.RateSIncrementCost{
{
RateIntervalIndex: 0,
RateID: "RATE1",
CompressFactor: 1,
Usage: utils.NewDecimal(int64(time.Minute), 0),
},
},
CompressFactor: 1,
},
},
Rates: map[string]*utils.IntervalRate{
"RATE1": {
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
}
if rcv, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err != nil {
t.Error(err)
} else {
rtsIntrvl := []*utils.RateSInterval{
{
Increments: []*utils.RateSIncrement{
{
RateIntervalIndex: 0,
RateID: "RATE1",
CompressFactor: 1,
Usage: utils.NewDecimal(int64(time.Minute), 0),
},
},
CompressFactor: 1,
},
}
rtsIntrvl[0].Cost(expectedRPCost.Rates)
expectedRPCost.CostIntervals[0] = rtsIntrvl[0].AsRatesIntervalsCost()
if !rcv.Equals(expectedRPCost) {
t.Errorf("Expected %+v\n, received %+v", utils.ToJSON(expectedRPCost), utils.ToJSON(rcv))
}
}
expRpCostAfterV1 := expectedRPCost
if err := rateS.V1CostForEvent(context.Background(), &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"}}, expectedRPCost); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedRPCost, expRpCostAfterV1) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expRpCostAfterV1), utils.ToJSON(expectedRPCost))
}
//fmt.Printf("received costV1: \n%s\n", utils.ToIJSON(expectedRPCost))
if err := dm.RemoveRateProfile(context.Background(), rPrf.Tenant, rPrf.ID, true); err != nil {
t.Error(err)
}
}
func TestRateProfileCostForEventUnmatchEvent(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_PRF1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
FilterIDs: []string{"*string:~*req.Destination:10"},
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
"RATE2": {
ID: "RATE2",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
FilterIDs: []string{"*string:~*req.Destination:10"},
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
},
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
expectedErr := "invalid converter terminator in rule: <~*req.Cost{*>"
rPrf.Rates["RATE2"].FilterIDs = []string{"*gt:~*req.Cost{*:10"}
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Destination: "10",
utils.Cost: 1002,
}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v, receied %+v", expectedErr, err)
}
rPrf.Rates["RATE2"].FilterIDs = []string{"*prefix:~*req.CustomValue:randomValue"}
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Destination: "10",
utils.Cost: 1002,
}}, rateS.cfg.RateSCfg().Verbosity); err != nil {
t.Error(err)
}
if err := rateS.dm.RemoveRateProfile(context.Background(), rPrf.Tenant, rPrf.ID, true); err != nil {
t.Error(err)
}
}
func TestMatchingRateProfileEvent(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rate := RateS{
cfg: defaultCfg,
filterS: filters,
dm: dm,
}
t1 := time.Date(2020, 7, 21, 10, 0, 0, 0, time.UTC)
rpp := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RP1",
Weights: utils.DynamicWeights{
{
Weight: 7,
},
},
FilterIDs: []string{"*string:~*req.Account:1001|1002|1003", "*prefix:~*req.Destination:10"},
}
err := dm.SetRateProfile(context.Background(), rpp, true)
if err != nil {
t.Error(err)
}
if rtPRf, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rtPRf, rpp) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(rpp), utils.ToJSON(rtPRf))
}
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Destination: 2002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false); err != utils.ErrNotFound {
t.Error(err)
}
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Destination: 2002,
utils.AnswerTime: t1.Add(10 * time.Second),
},
}, false); err != utils.ErrNotFound {
t.Error(err)
}
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1007",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false); err != utils.ErrNotFound {
t.Error(err)
}
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{"rp2"},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1007",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false); err != utils.ErrNotFound {
t.Error(err)
}
rpp.FilterIDs = []string{"*string:~*req.Account:1001|1002|1003", "*gt:~*req.Cost{*:10"}
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Cost: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false); err.Error() != "invalid converter terminator in rule: <~*req.Cost{*>" {
t.Error(err)
}
rpp.FilterIDs = []string{"*string:~*req.Account:1001|1002|1003"}
rate.dm = nil
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{"rp3"},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1007",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false); err != utils.ErrNoDatabaseConn {
t.Error(err)
}
err = dm.RemoveRateProfile(context.Background(), rpp.Tenant, rpp.ID, true)
if err != nil {
t.Error(err)
}
}
func TestV1CostForEventError(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
},
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
rcv, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity)
if err != nil {
t.Error(err)
}
expectedErr := "SERVER_ERROR: NOT_IMPLEMENTED:*notAType"
rPrf.FilterIDs = []string{"*notAType:~*req.Account:1001"}
if err := rateS.V1CostForEvent(context.Background(), &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"},
APIOpts: map[string]interface{}{
utils.OptsRatesRateProfileIDs: []string{"RATE_1"},
},
}, rcv); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v, received %+v", expectedErr, err)
}
rPrf.FilterIDs = []string{"*string:~*req.Destination:10"}
expectedErr = "SERVER_ERROR: zero increment to be charged within rate: <cgrates.org:RATE_1:RATE1>"
rPrf.Rates["RATE1"].IntervalRates[0].Increment = utils.NewDecimal(0, 0)
if err := rateS.V1CostForEvent(context.Background(), &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.Destination: "10"},
APIOpts: map[string]interface{}{
utils.OptsRatesRateProfileIDs: []string{"RATE_1"},
}}, rcv); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v, received %+v", expectedErr, err)
}
if err := rateS.dm.RemoveRateProfile(context.Background(), rPrf.Tenant, rPrf.ID, true); err != nil {
t.Error(err)
}
}
// go test -run=^$ -v -bench=BenchmarkRateS_V1CostForEvent -benchtime=5s
func BenchmarkRateS_V1CostForEvent(b *testing.B) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := RateS{
cfg: defaultCfg,
filterS: filters,
dm: dm,
}
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
b.Error(err)
}
secDecimal, err := utils.NewDecimalFromUsage("1s")
if err != nil {
b.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RateChristmas",
FilterIDs: []string{"*string:~*req.Subject:1010"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
{
IntervalStart: utils.NewDecimal(int64(time.Minute), 0),
RecurrentFee: utils.NewDecimal(1, 1),
Unit: minDecimal,
Increment: secDecimal,
},
},
},
"RATE_CHRISTMAS": {
ID: "RT_CHRISTMAS",
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
ActivationTimes: "* * 24 12 *",
IntervalRates: []*utils.IntervalRate{{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(6, 3),
Unit: minDecimal,
Increment: secDecimal,
}},
},
},
}
if err := dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
b.Error(err)
}
if err := rPrf.Compile(); err != nil {
b.Fatal(err)
}
if rcv, err := dm.GetRateProfile(context.TODO(), "cgrates.org", "RateChristmas",
true, true, utils.NonTransactional); err != nil {
b.Error(err)
} else if !reflect.DeepEqual(rPrf, rcv) {
b.Errorf("Expecting: %v, received: %v", rPrf, rcv)
}
rply := new(utils.RateProfileCost)
argsRt := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Event: map[string]interface{}{
utils.Subject: "1010",
},
APIOpts: map[string]interface{}{
utils.OptsRatesStartTime: time.Date(2020, 12, 23, 59, 0, 0, 0, time.UTC),
utils.OptsRatesUsage: "2h",
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := rateS.V1CostForEvent(context.Background(), argsRt, rply); err != nil {
b.Error(err)
}
}
b.StopTimer()
}
// go test -run=^$ -v -bench=BenchmarkRateS_V1CostForEventSingleRate -benchtime=5s
func BenchmarkRateS_V1CostForEventSingleRate(b *testing.B) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := RateS{
cfg: defaultCfg,
filterS: filters,
dm: dm,
}
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
b.Error(err)
}
secDecimal, err := utils.NewDecimalFromUsage("1s")
if err != nil {
b.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RateAlways",
FilterIDs: []string{"*string:~*req.Subject:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
{
IntervalStart: utils.NewDecimal(int64(time.Minute), 0),
RecurrentFee: utils.NewDecimal(1, 1),
Unit: minDecimal,
Increment: secDecimal,
},
},
},
},
}
if err := dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
b.Error(err)
}
if err := rPrf.Compile(); err != nil {
b.Fatal(err)
}
if rcv, err := dm.GetRateProfile(context.TODO(), "cgrates.org", "RateAlways",
true, true, utils.NonTransactional); err != nil {
b.Error(err)
} else if !reflect.DeepEqual(rPrf, rcv) {
b.Errorf("Expecting: %v, received: %v", rPrf, rcv)
}
rply := new(utils.RateProfileCost)
argsRt := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Event: map[string]interface{}{
utils.Subject: "1001",
},
APIOpts: map[string]interface{}{
utils.OptsRatesStartTime: time.Date(2020, 12, 23, 59, 0, 0, 0, time.UTC),
utils.OptsRatesUsage: "2h",
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := rateS.V1CostForEvent(context.Background(), argsRt, rply); err != nil {
b.Error(err)
}
}
b.StopTimer()
}
func TestRatesShutDown(t *testing.T) {
rateS := new(RateS)
if err := rateS.Shutdown(); err != nil {
t.Error(err)
}
}
func TestRateProfileCostForEventInvalidUsage(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
},
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
expected := "can't convert <invalidUsageFormat> to decimal"
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"},
APIOpts: map[string]interface{}{
utils.OptsRatesUsage: "invalidUsageFormat",
}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
expected = "Unsupported time format"
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"},
APIOpts: map[string]interface{}{
utils.OptsRatesStartTime: "invalidStartTimeFormat",
}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
if err := dm.RemoveRateProfile(context.Background(), rPrf.Tenant, rPrf.ID, true); err != nil {
t.Error(err)
}
}
func TestRateProfileCostForEventZeroIncrement(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "1 * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: utils.NewDecimal(0, 0),
},
},
},
},
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
expected := "zero increment to be charged within rate: <cgrates.org:RATE_1:RATE1>"
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"},
APIOpts: map[string]interface{}{
utils.OptsRatesUsage: "100m",
}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
if err := dm.RemoveRateProfile(context.Background(), rPrf.Tenant, rPrf.ID, true); err != nil {
t.Error(err)
}
}
func TestRateProfileCostForEventMaximumIterations(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "1 * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: utils.NewDecimal(0, 0),
},
},
},
},
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
rateS.cfg.RateSCfg().Verbosity = 10
expected := "maximum iterations reached"
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"},
APIOpts: map[string]interface{}{
utils.OptsRatesUsage: "10000m",
}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
if err := dm.RemoveRateProfile(context.Background(), rPrf.Tenant, rPrf.ID, true); err != nil {
t.Error(err)
}
}
func TestRateSMatchingRateProfileForEventErrFltr(t *testing.T) {
dftCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, dftCfg.CacheCfg(), nil)
filterS := engine.NewFilterS(dftCfg, nil, dm)
rateS := RateS{
cfg: dftCfg,
filterS: filterS,
dm: dm,
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RP1",
Weights: utils.DynamicWeights{
{
Weight: 10,
FilterIDs: []string{"fi"},
},
},
FilterIDs: []string{"*ai:~*req.AnswerTime:2020-07-21T00:00:00Z|9999-07-21T10:00:00Z"},
}
err := dm.SetRateProfile(context.Background(), rPrf, true)
if err != nil {
t.Error(err)
}
_, err = rateS.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: time.Date(9999, 7, 21, 10, 0, 0, 0, time.UTC).Add(-10 * time.Second),
},
}, false)
expectedErr := "NOT_FOUND"
if err == nil || err.Error() != expectedErr {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", expectedErr, err)
}
}
func TestRateSRateProfileCostForEventErrFltr(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
FilterIDs: []string{"fi"},
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
},
}
//MatchItmID before setting
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
expected := "NOT_FOUND:fi"
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]interface{}{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expected {
t.Errorf("Expected %+v\n, received %+v", expected, err)
}
}
func TestRateSRateProfileCostForEventErrInterval(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
filters := engine.NewFilterS(defaultCfg, nil, dm)
rateS := NewRateS(defaultCfg, filters, dm)
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
t.Error(err)
}
rPrf := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
{
Weight: 50,
},
},
Rates: map[string]*utils.Rate{
"RATE1": {
ID: "RATE1",
Weights: utils.DynamicWeights{
{
Weight: 0,
},
},
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(2, 1),
Unit: minDecimal,
Increment: minDecimal,
},
},
},
},
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, true); err != nil {
t.Error(err)
}
expected := "can't convert <wrongValue> to decimal"
if _, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
APIOpts: map[string]interface{}{
utils.OptsRatesIntervalStart: "wrongValue",
},
Event: map[string]interface{}{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err.Error() != expected {
t.Error(err)
}
}
func TestCDRProcessRatesCostForEvent(t *testing.T) {
cache := engine.Cache
engine.Cache.Clear(nil)
jsonCfg := `{
"cdrs": {
"enabled": true,
"rates_conns": ["*internal"],
"opts": {
"*rateS": [
{
"Tenant": "cgrates.org",
"FilterIDs": [],
"Value": true,
},
],
},
},
"rates": {
"enabled": true,
},
}
`
cfg, err := config.NewCGRConfigFromJSONStringWithDefaults(jsonCfg)
if err != nil {
t.Error(err)
}
connMgr := engine.NewConnManager(cfg)
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), connMgr)
filters := engine.NewFilterS(cfg, connMgr, dm)
storDBChan := make(chan engine.StorDB, 1)
storDBChan <- new(engine.InternalDB)
cdrs := engine.NewCDRServer(cfg, storDBChan, dm, filters, connMgr)
ratesConns := make(chan birpc.ClientConnector, 1)
rateSrv, err := birpc.NewServiceWithMethodsRename(NewRateS(cfg, filters, dm), utils.RateSv1, true, func(key string) (newKey string) {
return strings.TrimPrefix(key, utils.V1Prfx)
})
if err != nil {
t.Error(err)
}
ratesConns <- rateSrv
connMgr.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS), utils.RateSv1, ratesConns)
// set a RateProfile for usage
ratePrf := &utils.RateProfile{
Tenant: utils.CGRateSorg,
ID: "TEST_RATE_PROCESS_CDR",
FilterIDs: []string{"*string:~*req.Account:1001"},
MaxCostStrategy: "*free",
Rates: map[string]*utils.Rate{
"RT_WEEK": {
ID: "RT_WEEK",
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(1, 1),
Unit: utils.NewDecimal(1, 0),
Increment: utils.NewDecimal(int64(time.Second), 1),
},
},
},
},
}
err = dm.SetRateProfile(context.Background(), ratePrf, true)
if err != nil {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", nil, err)
}
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestCDRProcessRatesCostForEvent",
Event: map[string]interface{}{
utils.AccountField: "1001",
},
APIOpts: map[string]interface{}{
utils.MetaUsage: 15 * time.Second,
},
}
var reply string
// here we will test that cdrs can communicate with rates correctly
if err := cdrs.V1ProcessEvent(context.Background(), cgrEv, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+q, received %+q", utils.OK, reply)
}
//compare the new event that we got from RateSv1.CostForEvent called from cdrs
expCgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestCDRProcessRatesCostForEvent",
Event: map[string]interface{}{
utils.AccountField: "1001",
utils.MetaRateSCost: utils.RateProfileCost{
ID: "TEST_RATE_PROCESS_CDR",
Cost: utils.NewDecimal(int64(15*time.Second)/10, 0),
CostIntervals: []*utils.RateSIntervalCost{
{
Increments: []*utils.RateSIncrementCost{
{
Usage: utils.NewDecimal(int64(15*time.Second), 0),
RateID: cgrEv.Event[utils.MetaRateSCost].(utils.RateProfileCost).CostIntervals[0].Increments[0].RateID,
RateIntervalIndex: 0,
CompressFactor: 150,
},
},
CompressFactor: 1,
},
},
Rates: map[string]*utils.IntervalRate{
cgrEv.Event[utils.MetaRateSCost].(utils.RateProfileCost).CostIntervals[0].Increments[0].RateID: {
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(1, 1),
Unit: utils.NewDecimal(1, 0),
Increment: utils.NewDecimal(int64(time.Second), 1),
},
},
},
},
APIOpts: map[string]interface{}{
utils.MetaUsage: 15 * time.Second,
},
}
if !reflect.DeepEqual(expCgrEv, cgrEv) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expCgrEv), utils.ToJSON(cgrEv))
}
engine.Cache = cache
}