Files
cgrates/rates/rates_test.go
2025-10-19 13:15:57 +02:00

1441 lines
41 KiB
Go

/*
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 Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
*/
package rates
import (
"reflect"
"strings"
"testing"
"time"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/cdrs"
"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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dataManager := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dataManager)
expected := &RateS{
cfg: cfg,
fltrS: filters,
dm: dataManager,
}
if newRateS := NewRateS(cfg, filters, dataManager); !reflect.DeepEqual(newRateS, expected) {
t.Errorf("Expected %+v, received %+v", expected, newRateS)
}
}
func TestRateProfileCostForEvent(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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]any{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, false, 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]any{
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]any{
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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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, false, 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]any{
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]any{
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) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rate := RateS{
cfg: cfg,
fltrS: 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, false, true)
if err != nil {
t.Error(err)
}
var ignoredRPIDs utils.StringSet
if rtPRf, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); 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]any{
utils.AccountField: "1001",
utils.Destination: 2002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); 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]any{
utils.AccountField: "1001",
utils.Destination: 2002,
utils.AnswerTime: t1.Add(10 * time.Second),
},
}, false, ignoredRPIDs); 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]any{
utils.AccountField: "1007",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); 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]any{
utils.AccountField: "1007",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); err != utils.ErrNotFound {
t.Error(err)
}
rpp.FilterIDs = []string{"*string:~*req.Account:1001|1002|1003", "*gt:~*req.Cost{*:10"}
engine.Cache.Clear(nil)
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Cost: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); err.Error() != "invalid converter terminator in rule: <~*req.Cost{*>" {
t.Error(err)
}
rpp.FilterIDs = []string{"*string:~*req.Account:1001|1002|1003"}
engine.Cache.Clear(nil)
rate.dm = nil
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{"rp3"},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]any{
utils.AccountField: "1007",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); 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) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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, false, true); err != nil {
t.Error(err)
}
rcv, err := rateS.rateProfileCostForEvent(context.Background(), rPrf, &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]any{
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]any{
utils.AccountField: "1001"},
APIOpts: map[string]any{
utils.OptsRatesProfileIDs: []string{"RATE_1"},
},
}, rcv); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v, received %+v", expectedErr, err)
}
rPrf.FilterIDs = []string{"*string:~*req.Destination:10"}
engine.Cache.Clear(nil)
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]any{
utils.Destination: "10"},
APIOpts: map[string]any{
utils.OptsRatesProfileIDs: []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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := RateS{
cfg: cfg,
fltrS: 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, false, 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]any{
utils.Subject: "1010",
},
APIOpts: map[string]any{
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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := RateS{
cfg: cfg,
fltrS: 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, false, 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]any{
utils.Subject: "1001",
},
APIOpts: map[string]any{
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 TestRateProfileCostForEventInvalidUsage(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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, false, 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]any{
utils.AccountField: "1001"},
APIOpts: map[string]any{
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]any{
utils.AccountField: "1001"},
APIOpts: map[string]any{
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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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, false, 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]any{
utils.AccountField: "1001"},
APIOpts: map[string]any{
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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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, false, 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]any{
utils.AccountField: "1001"},
APIOpts: map[string]any{
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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filterS := engine.NewFilterS(cfg, nil, dm)
rateS := RateS{
cfg: cfg,
fltrS: 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, false, true)
if err != nil {
t.Error(err)
}
var ignoredRPfIDs utils.StringSet
_, err = rateS.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "CACHE1",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: time.Date(9999, 7, 21, 10, 0, 0, 0, time.UTC).Add(-10 * time.Second),
},
}, false, ignoredRPfIDs)
expectedErr := "NOT_FOUND"
if err == nil || err.Error() != expectedErr {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", expectedErr, err)
}
}
func TestRateSRateProfileCostForEventErrFltr(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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]any{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, false, 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]any{
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) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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, false, 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]any{
utils.OptsRatesIntervalStart: "wrongValue",
},
Event: map[string]any{
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, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, connMgr)
filters := engine.NewFilterS(cfg, connMgr, dm)
cdrs := cdrs.NewCDRServer(cfg, 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, false, true)
if err != nil {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", nil, err)
}
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestCDRProcessRatesCostForEvent",
Event: map[string]any{
utils.AccountField: "1001",
},
APIOpts: map[string]any{
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]any{
utils.AccountField: "1001",
},
APIOpts: map[string]any{
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.APIOpts[utils.MetaRateSCost].(utils.RateProfileCost).CostIntervals[0].Increments[0].RateID,
RateIntervalIndex: 0,
CompressFactor: 150,
},
},
CompressFactor: 1,
},
},
Rates: map[string]*utils.IntervalRate{
cgrEv.APIOpts[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),
},
},
},
utils.MetaCost: utils.NewDecimal(int64(15*time.Second)/10, 0),
utils.MetaUsage: 15 * time.Second,
},
}
delete(cgrEv.APIOpts, utils.MetaCDRID) // ignore autogenerated *cdr field when comparing
if !reflect.DeepEqual(expCgrEv, cgrEv) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expCgrEv), utils.ToJSON(cgrEv))
}
engine.Cache = cache
}
func TestRateProfileCostForEventProfileIgnoreFilters(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.RateSCfg().Opts.ProfileIgnoreFilters = []*config.DynamicBoolOpt{
config.NewDynamicBoolOpt(nil, "", true, nil),
}
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rateS := NewRateS(cfg, 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",
"*string:~*req.TestField:testValue"},
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]any{
utils.AccountField: "1001"}}, rateS.cfg.RateSCfg().Verbosity); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
if err := rateS.dm.SetRateProfile(context.Background(), rPrf, false, 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]any{
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))
}
}
event := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RATE_1",
Event: map[string]any{
utils.AccountField: "1001",
"TestField": "testValue1",
},
APIOpts: map[string]any{
utils.OptsRatesProfileIDs: []string{"RATE_1"},
utils.MetaProfileIgnoreFilters: true,
},
}
expRpCostAfterV1 := expectedRPCost
if err := rateS.V1CostForEvent(context.Background(), event, 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 TestMatchingRateProfileFallbacks(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: data}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, nil)
filters := engine.NewFilterS(cfg, nil, dm)
rate := RateS{
cfg: cfg,
fltrS: filters,
dm: dm,
}
t1 := time.Date(2020, 7, 21, 10, 0, 0, 0, time.UTC)
rpp1 := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RP1",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
FilterIDs: []string{"*string:~*req.Account:1001|1002|1003", "*prefix:~*req.Destination:10"},
}
rpp2 := &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RP2",
Weights: utils.DynamicWeights{
{
Weight: 5,
},
},
FilterIDs: []string{"*string:~*req.Account:1001|1002|1003", "*prefix:~*req.Destination:10"},
}
// Set both rate profiles
err := dm.SetRateProfile(context.Background(), rpp1, false, true)
if err != nil {
t.Error(err)
}
err = dm.SetRateProfile(context.Background(), rpp2, false, true)
if err != nil {
t.Error(err)
}
// Initialize ignoredRPIDs as a new empty map
ignoredRPIDs := make(utils.StringSet)
// 1. Test without any ignored profiles (should match RP1 with higher weight)
if rtPRf, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "ID",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rtPRf, rpp1) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(rpp1), utils.ToJSON(rtPRf))
}
// 2. Test with ignoredRPIDs containing RP1 (should match RP2 )
ignoredRPIDs.Add(rpp1.ID)
if rtPRf, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "ID",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rtPRf, rpp2) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(rpp2), utils.ToJSON(rtPRf))
}
// 3. Test with both RP1 and RP2 in ignoredRPIDs (should return ErrNotFound)
ignoredRPIDs.Add(rpp2.ID)
if _, err := rate.matchingRateProfileForEvent(context.TODO(), "cgrates.org", []string{},
&utils.CGREvent{
Tenant: "cgrates.org",
ID: "ID",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: 1002,
utils.AnswerTime: t1.Add(-10 * time.Second),
},
}, false, ignoredRPIDs); err != utils.ErrNotFound {
t.Errorf("Expected error %v, got %v", utils.ErrNotFound, err)
}
err = dm.RemoveRateProfile(context.Background(), rpp1.Tenant, rpp1.ID, true)
if err != nil {
t.Error(err)
}
err = dm.RemoveRateProfile(context.Background(), rpp2.Tenant, rpp2.ID, true)
if err != nil {
t.Error(err)
}
}