Files
cgrates/engine/routes_test.go
2025-06-25 19:11:15 +02:00

2756 lines
79 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 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 engine
import (
"bytes"
"errors"
"fmt"
"log"
"os"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func TestRoutesSort(t *testing.T) {
sprs := RouteProfiles{
&RouteProfile{
Tenant: "cgrates.org",
ID: "RoutePrf1",
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
ExpiryTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
},
Sorting: "",
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 10,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RoutePrf2",
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
ExpiryTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
},
Sorting: "",
Routes: []*Route{
{
ID: "route1",
Weight: 20.0,
RouteParameters: "param1",
},
},
Weight: 20.0,
},
}
eRouteProfile := RouteProfiles{
&RouteProfile{
Tenant: "cgrates.org",
ID: "RoutePrf2",
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
ExpiryTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
},
Sorting: "",
Routes: []*Route{
{
ID: "route1",
Weight: 20.0,
RouteParameters: "param1",
},
},
Weight: 20.0,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RoutePrf1",
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
ExpiryTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
},
Sorting: "",
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 10.0,
},
}
sprs.Sort()
if !reflect.DeepEqual(eRouteProfile, sprs) {
t.Errorf("Expecting: %+v, received: %+v", eRouteProfile, sprs)
}
}
var (
testRoutesPrfs = RouteProfiles{
{
Tenant: "cgrates.org",
ID: "RouteProfile1",
FilterIDs: []string{"FLTR_RPP_1"},
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 10,
},
{
Tenant: "cgrates.org",
ID: "RouteProfile2",
FilterIDs: []string{"FLTR_SUPP_2"},
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route2",
Weight: 20.0,
RouteParameters: "param2",
},
{
ID: "route3",
Weight: 10.0,
RouteParameters: "param3",
},
{
ID: "route1",
Weight: 30.0,
RouteParameters: "param1",
},
},
Weight: 20.0,
},
{
Tenant: "cgrates.org",
ID: "RouteProfilePrefix",
FilterIDs: []string{"FLTR_SUPP_3"},
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 10,
},
}
testRoutesArgs = []*utils.CGREvent{
{ //matching RouteProfile1
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{
"Route": "RouteProfile1",
utils.AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"UsageInterval": "1s",
"PddInterval": "1s",
utils.Weight: "20.0",
},
APIOpts: map[string]any{},
},
{ //matching RouteProfile2
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{
"Route": "RouteProfile2",
utils.AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"UsageInterval": "1s",
"PddInterval": "1s",
utils.Weight: "20.0",
},
APIOpts: map[string]any{},
},
{ //matching RouteProfilePrefix
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{
"Route": "RouteProfilePrefix",
},
APIOpts: map[string]any{},
},
{
Tenant: "cgrates.org",
ID: "CGR",
Event: map[string]any{
"UsageInterval": "1s",
"PddInterval": "1s",
},
APIOpts: map[string]any{},
},
}
)
func prepareRoutesData(t *testing.T, dm *DataManager) {
if err := dm.SetFilter(&Filter{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "FLTR_RPP_1",
Rules: []*FilterRule{
{
Type: utils.MetaString,
Element: "~*req.Route",
Values: []string{"RouteProfile1"},
},
{
Type: utils.MetaGreaterOrEqual,
Element: "~*req.UsageInterval",
Values: []string{(time.Second).String()},
},
{
Type: utils.MetaGreaterOrEqual,
Element: utils.DynamicDataPrefix + utils.MetaReq + utils.NestingSep + utils.Weight,
Values: []string{"9.0"},
},
},
}, true); err != nil {
t.Fatal(err)
}
if err := dm.SetFilter(&Filter{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "FLTR_SUPP_2",
Rules: []*FilterRule{
{
Type: utils.MetaString,
Element: "~*req.Route",
Values: []string{"RouteProfile2"},
},
{
Type: utils.MetaGreaterOrEqual,
Element: "~*req.PddInterval",
Values: []string{(time.Second).String()},
},
{
Type: utils.MetaGreaterOrEqual,
Element: utils.DynamicDataPrefix + utils.MetaReq + utils.NestingSep + utils.Weight,
Values: []string{"15.0"},
},
},
}, true); err != nil {
t.Fatal(err)
}
if err := dm.SetFilter(&Filter{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "FLTR_SUPP_3",
Rules: []*FilterRule{
{
Type: utils.MetaPrefix,
Element: "~*req.Route",
Values: []string{"RouteProfilePrefix"},
},
},
}, true); err != nil {
t.Fatal(err)
}
for _, spp := range testRoutesPrfs {
if err = dm.SetRouteProfile(spp, true); err != nil {
t.Fatal(err)
}
}
//Test each route profile from cache
for _, spp := range testRoutesPrfs {
if tempSpp, err := dm.GetRouteProfile(spp.Tenant,
spp.ID, true, true, utils.NonTransactional); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(spp, tempSpp) {
t.Fatalf("Expecting: %+v, received: %+v", spp, tempSpp)
}
}
}
func TestRoutesCache(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
prepareRoutesData(t, dmSPP)
}
func TestRoutesmatchingRouteProfilesForEvent(t *testing.T) {
Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
for i, spp := range testRoutesPrfs {
sprf, err := routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[i])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(spp, sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", spp, sprf[0])
}
}
}
func TestRoutesSortedForEvent(t *testing.T) {
Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
exp := SortedRoutesList{{
ProfileID: "RouteProfile1",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route1",
sortingDataF64: map[string]float64{
utils.Weight: 10.0,
},
SortingData: map[string]any{
utils.Weight: 10.0,
},
RouteParameters: "param1",
},
},
}}
sprf, err := routeService.sortedRoutesForEvent("cgrates.org", testRoutesArgs[0])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(exp, sprf) {
t.Errorf("Expecting: %+v, received: %+v", exp, sprf)
}
exp = SortedRoutesList{{
ProfileID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route1",
sortingDataF64: map[string]float64{
utils.Weight: 30.0,
},
SortingData: map[string]any{
utils.Weight: 30.0,
},
RouteParameters: "param1",
},
{
RouteID: "route2",
sortingDataF64: map[string]float64{
utils.Weight: 20.0,
},
SortingData: map[string]any{
utils.Weight: 20.0,
},
RouteParameters: "param2",
},
{
RouteID: "route3",
sortingDataF64: map[string]float64{
utils.Weight: 10.0,
},
SortingData: map[string]any{
utils.Weight: 10.0,
},
RouteParameters: "param3",
},
},
}}
sprf, err = routeService.sortedRoutesForEvent("cgrates.org", testRoutesArgs[1])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(exp, sprf) {
t.Errorf("Expecting: %+v, received: %+v", exp, sprf)
}
exp = SortedRoutesList{{
ProfileID: "RouteProfilePrefix",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route1",
sortingDataF64: map[string]float64{
utils.Weight: 10.0,
},
SortingData: map[string]any{
utils.Weight: 10.0,
},
RouteParameters: "param1",
},
},
}}
sprf, err = routeService.sortedRoutesForEvent("cgrates.org", testRoutesArgs[2])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(exp, sprf) {
t.Errorf("Expecting: %+v, received: %+v", exp, sprf)
}
}
func TestRoutesSortedForEventWithLimit(t *testing.T) {
Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
eFirstRouteProfile := SortedRoutesList{&SortedRoutes{
ProfileID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route1",
sortingDataF64: map[string]float64{
utils.Weight: 30.0,
},
SortingData: map[string]any{
utils.Weight: 30.0,
},
RouteParameters: "param1",
},
{
RouteID: "route2",
sortingDataF64: map[string]float64{
utils.Weight: 20.0,
},
SortingData: map[string]any{
utils.Weight: 20.0,
},
RouteParameters: "param2",
},
},
}}
testRoutesArgs[1].APIOpts[utils.OptsRoutesLimit] = 2
delete(testRoutesArgs[1].APIOpts, utils.OptsRoutesOffset)
sprf, err := routeService.sortedRoutesForEvent("cgrates.org", testRoutesArgs[1])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eFirstRouteProfile, sprf) {
t.Errorf("Expecting: %+v, received: %+v", eFirstRouteProfile, sprf)
}
}
func TestRoutesSortedForEventWithOffset(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
eFirstRouteProfile := SortedRoutesList{&SortedRoutes{
ProfileID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route3",
sortingDataF64: map[string]float64{
utils.Weight: 10.0,
},
SortingData: map[string]any{
utils.Weight: 10.0,
},
RouteParameters: "param3",
},
},
}}
testRoutesArgs[1].APIOpts[utils.OptsRoutesOffset] = 2
delete(testRoutesArgs[1].APIOpts, utils.OptsRoutesLimit)
sprf, err := routeService.sortedRoutesForEvent("cgrates.org", testRoutesArgs[1])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eFirstRouteProfile, sprf) {
t.Errorf("Expecting: %+v,received: %+v", utils.ToJSON(eFirstRouteProfile), utils.ToJSON(sprf))
}
}
func TestRoutesSortedForEventWithLimitAndOffset(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
eFirstRouteProfile := SortedRoutesList{&SortedRoutes{
ProfileID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route2",
sortingDataF64: map[string]float64{
utils.Weight: 20.0,
},
SortingData: map[string]any{
utils.Weight: 20.0,
},
RouteParameters: "param2",
},
},
}}
testRoutesArgs[1].APIOpts[utils.OptsRoutesLimit] = 1
testRoutesArgs[1].APIOpts[utils.OptsRoutesOffset] = 1
sprf, err := routeService.sortedRoutesForEvent("cgrates.org", testRoutesArgs[1])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eFirstRouteProfile, sprf) {
t.Errorf("Expecting: %+v,received: %+v", utils.ToJSON(eFirstRouteProfile), utils.ToJSON(sprf))
}
}
func TestRoutesAsOptsGetRoutesMaxCost(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
routeService.cgrcfg.RouteSCfg().IndexedSelects = false
sprf, err := routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[0])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testRoutesPrfs[0], sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", testRoutesPrfs[0], sprf[0])
}
sprf, err = routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[1])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testRoutesPrfs[1], sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", testRoutesPrfs[1], sprf[0])
}
sprf, err = routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[2])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testRoutesPrfs[2], sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", testRoutesPrfs[2], sprf[0])
}
}
func TestRoutesMatchWithIndexFalse(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
prepareRoutesData(t, dmSPP)
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
routeService.cgrcfg.RouteSCfg().IndexedSelects = false
sprf, err := routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[0])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testRoutesPrfs[0], sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", testRoutesPrfs[0], sprf[0])
}
sprf, err = routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[1])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testRoutesPrfs[1], sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", testRoutesPrfs[1], sprf[0])
}
sprf, err = routeService.matchingRouteProfilesForEvent("cgrates.org", testRoutesArgs[2])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testRoutesPrfs[2], sprf[0]) {
t.Errorf("Expecting: %+v, received: %+v", testRoutesPrfs[2], sprf[0])
}
}
func TestRoutesSortedForEventWithLimitAndOffset2(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
Cache.Clear(nil)
testRoutesPrfs := RouteProfiles{
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfile1",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route2",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 10,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route2",
Weight: 20.0,
RouteParameters: "param2",
},
{
ID: "route3",
Weight: 10.0,
RouteParameters: "param3",
},
{
ID: "route1",
Weight: 30.0,
RouteParameters: "param1",
},
},
Weight: 5,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfilePrefix",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 20,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfilePrefix4",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 0,
},
}
argsGetRoutes := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{},
APIOpts: map[string]any{
utils.OptsRoutesProfileCount: 3,
},
}
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg}, cfg, nil)
for _, spp := range testRoutesPrfs {
if err = dmSPP.SetRouteProfile(spp, true); err != nil {
t.Fatal(err)
}
if tempSpp, err := dmSPP.GetRouteProfile(spp.Tenant,
spp.ID, true, true, utils.NonTransactional); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(spp, tempSpp) {
t.Errorf("Expecting: %+v, received: %+v", spp, tempSpp)
}
}
eFirstRouteProfile := SortedRoutesList{
{
ProfileID: "RouteProfile1",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route2",
sortingDataF64: map[string]float64{
utils.Weight: 10.,
},
SortingData: map[string]any{
utils.Weight: 10.,
},
RouteParameters: "param1",
},
},
},
{
ProfileID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*SortedRoute{
{
RouteID: "route1",
sortingDataF64: map[string]float64{
utils.Weight: 30.,
},
SortingData: map[string]any{
utils.Weight: 30.,
},
RouteParameters: "param1",
},
},
},
}
argsGetRoutes.APIOpts[utils.OptsRoutesLimit] = 2
argsGetRoutes.APIOpts[utils.OptsRoutesOffset] = 1
sprf, err := routeService.sortedRoutesForEvent(argsGetRoutes.Tenant, argsGetRoutes)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eFirstRouteProfile, sprf) {
t.Errorf("Expecting: %+v,received: %+v", utils.ToJSON(eFirstRouteProfile), utils.ToJSON(sprf))
}
}
func TestRouteProfileCompileCacheParameters(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
rp := &RouteProfile{
Tenant: "tnt",
ID: "id2",
FilterIDs: []string{"filter1", "filter2", "filter3"},
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2022, 12, 1, 8, 0, 0, 0, time.UTC),
ExpiryTime: time.Date(2023, 1, 1, 8, 0, 0, 0, time.UTC),
},
Weight: 12,
Sorting: "sort",
SortingParameters: []string{"sort_param:3", "*ratio:1"},
Routes: []*Route{
{
ID: "id1",
FilterIDs: []string{"filter_id1", "filter_id2", "filter_id3", "filter_id4"},
AccountIDs: []string{"acc_id1", "acc_id2", "acc_id3", "acc_id3", "acc_id4"},
RatingPlanIDs: []string{"rating_id1", "rating_id2", "rating_id3", "rating_id4"},
ResourceIDs: []string{"res_id1", "res_id2", "res_id3", "res_id4", "res_id4"},
StatIDs: []string{"stats_id1", "stats_id2", "stats_id3", "stats_id3", "stats_id4"},
Weight: 2.3,
Blocker: true,
RouteParameters: "param",
lazyCheckRules: []*FilterRule{
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
rsrValues: config.RSRParsers{
&config.RSRParser{Rules: "public"},
{Rules: "private"},
},
},
},
},
{
ID: "id1",
FilterIDs: []string{"filter_id1", "filter_id2", "filter_id3", "filter_id4"},
AccountIDs: []string{"acc_id1", "acc_id2", "acc_id3", "acc_id3", "acc_id4"},
RatingPlanIDs: []string{"rating_id1", "rating_id2", "rating_id3", "rating_id4"},
ResourceIDs: []string{"res_id1", "res_id2", "res_id3", "res_id4", "res_id4"},
StatIDs: []string{"stats_id1", "stats_id2", "stats_id3", "stats_id3", "stats_id4"},
Weight: 2.3,
Blocker: true,
RouteParameters: "param",
lazyCheckRules: []*FilterRule{
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
rsrValues: config.RSRParsers{
&config.RSRParser{Rules: "public"},
{Rules: "private"},
},
},
},
},
},
}
if err := rp.compileCacheParameters(); err != nil {
t.Error(err)
}
rp.Sorting = utils.MetaLoad
if err = rp.compileCacheParameters(); err != nil {
t.Error(err)
}
}
func TestRouteServiceStatMetrics(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
testMock := &ccMock{
calls: map[string]func(ctx *context.Context, args, reply any) error{
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
rpl := map[string]float64{
"metric1": 21.11,
}
*reply.(*map[string]float64) = rpl
return nil
},
},
}
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
clientconn := make(chan birpc.ClientConnector, 1)
clientconn <- testMock
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientconn,
})
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, connMgr)
exp := map[string]float64{
"metric1": 21.11,
}
if val, err := rpS.statMetrics([]string{"stat1", "stat2"}, "cgrates.org"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(val, exp) {
t.Errorf("Expected %v,Received %v", utils.ToJSON(exp), utils.ToJSON(val))
}
}
func TestRouteServiceStatMetricsLog(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
}()
testMock := &ccMock{
calls: map[string]func(ctx *context.Context, args, reply any) error{
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
return errors.New("Error")
},
},
}
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
clientconn := make(chan birpc.ClientConnector, 1)
clientconn <- testMock
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientconn,
})
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, connMgr)
expLog := `getting statMetrics for stat`
if _, err := rpS.statMetrics([]string{"stat1", "stat2"}, "cgrates.org"); err != nil {
t.Error(err)
} else if rcvLog := buf.String(); strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
}
func TestRouteServiceV1GetRouteProfilesForEvent(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, nil)
args := &utils.CGREvent{
Tenant: "cgrates.orgs",
ID: "id",
Time: utils.TimePointer(time.Date(2022, 12, 1, 20, 0, 0, 0, time.UTC)),
Event: map[string]any{
utils.AccountField: "acc_event",
utils.Destination: "desc_event",
utils.SetupTime: time.Now(),
},
}
testRoutesPrfs := &RouteProfiles{
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfile1",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route2",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 10,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfile2",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route2",
Weight: 20.0,
RouteParameters: "param2",
},
{
ID: "route3",
Weight: 10.0,
RouteParameters: "param3",
},
{
ID: "route1",
Weight: 30.0,
RouteParameters: "param1",
},
},
Weight: 5,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfilePrefix",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 20,
},
&RouteProfile{
Tenant: "cgrates.org",
ID: "RouteProfilePrefix4",
Sorting: utils.MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weight: 10.0,
RouteParameters: "param1",
},
},
Weight: 0,
},
}
if err := rpS.V1GetRouteProfilesForEvent(context.Background(), args, (*[]*RouteProfile)(testRoutesPrfs)); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
}
func TestRouteServiceV1GetRoutes(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
ccMock := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AttributeSv1ProcessEvent: func(ctx *context.Context, args, reply any) error {
rpl := &AttrSProcessEventReply{
AlteredFields: []string{"testcase"},
CGREvent: testRoutesArgs[1],
}
*reply.(*AttrSProcessEventReply) = *rpl
return nil
},
},
}
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
cfg.RouteSCfg().AttributeSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes)}
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- ccMock
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes): clientConn,
})
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, connMgr)
args := &utils.CGREvent{
ID: "CGREvent1",
Tenant: "cgrates.org",
Time: utils.TimePointer(time.Date(2022, 12, 1, 20, 0, 0, 0, time.UTC)),
Event: map[string]any{
"testcase": 1,
},
}
expErr := fmt.Sprintf("MANDATORY_IE_MISSING: [%v]", utils.CGREventString)
var reply SortedRoutesList
if err := rpS.V1GetRoutes(context.Background(), nil, &reply); err == nil || err.Error() != expErr {
t.Errorf("Expected <%v>,Received <%v>", expErr, err.Error())
}
expS := SortedRoutesList{
{ProfileID: "RouteProfile1",
Sorting: "*weight",
Routes: []*SortedRoute{
{
RouteID: "route2",
RouteParameters: "param1",
SortingData: map[string]any{"Weight": 10},
},
},
}}
if err = rpS.V1GetRoutes(context.Background(), args, &reply); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(reply[0].ProfileID, expS[0].ProfileID) {
t.Errorf("Expected %v,Received %v", utils.ToJSON(expS), utils.ToJSON(reply))
}
}
func TestRouteServiceSortRoutes(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, nil)
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
}()
lcs := &LeastCostSorter{
sorting: "sort",
rS: rpS,
}
prflID := "CGREvent1"
routes := map[string]*Route{
"route1": {
ID: "id",
FilterIDs: []string{"filterid1"},
AccountIDs: []string{"acc_id1"},
RatingPlanIDs: []string{"rate1"},
ResourceIDs: []string{"rsc1"},
StatIDs: []string{"stat1"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
lazyCheckRules: []*FilterRule{},
},
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{
"Route": "RouteProfile1",
utils.AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"UsageInterval": "1s",
"PddInterval": "1s",
utils.Weight: "20.0",
},
APIOpts: map[string]any{},
}
extraOpts := &optsGetRoutes{
ignoreErrors: true,
maxCost: 12.1,
paginator: &utils.Paginator{
Limit: utils.IntPointer(4),
Offset: utils.IntPointer(2),
},
sortingParameters: []string{"param1", "param2"},
}
expSr := &SortedRoutes{
ProfileID: "CGREvent1",
Sorting: "sort",
Routes: []*SortedRoute{},
}
if val, err := lcs.SortRoutes(prflID, routes, ev, extraOpts); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(val, expSr) {
t.Errorf("recived %+v", utils.ToJSON(val))
}
routes["route1"].RatingPlanIDs = []string{}
routes["route1"].AccountIDs = []string{}
expLog := `empty RatingPlanIDs or AccountIDs`
if _, err = lcs.SortRoutes(prflID, routes, ev, extraOpts); err == nil {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", utils.ToJSON(rcvLog), utils.ToJSON(expLog))
}
}
func TestRDSRSortRoutes(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, nil)
rds := &ResourceDescendentSorter{
sorting: "desc",
rS: rpS,
}
prflID := "CGREvent1"
routes := map[string]*Route{
"sorted_route1": {
ID: "id",
FilterIDs: []string{"filterid1"},
AccountIDs: []string{"acc_id1"},
RatingPlanIDs: []string{"rate1"},
ResourceIDs: []string{"rsc1"},
StatIDs: []string{"stat1"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
lazyCheckRules: []*FilterRule{
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
},
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
},
},
},
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{},
APIOpts: map[string]any{
utils.OptsRoutesProfileCount: 3,
},
}
extraOpts := &optsGetRoutes{
ignoreErrors: true,
maxCost: 12.1,
paginator: &utils.Paginator{
Limit: utils.IntPointer(4),
Offset: utils.IntPointer(2),
},
sortingParameters: []string{"param1", "param2"},
}
if _, err := rds.SortRoutes(prflID, routes, ev, extraOpts); err != nil {
t.Error(err)
}
routes["sorted_route1"].ResourceIDs = []string{}
expLog := `empty ResourceIDs`
if _, err = rds.SortRoutes(prflID, routes, ev, extraOpts); err == nil {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", utils.ToJSON(rcvLog), utils.ToJSON(expLog))
}
}
func TestQosRSortRoutes(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, nil)
qos := &QOSRouteSorter{
sorting: "desc",
rS: rpS,
}
prflID := "CGREvent1"
routes := map[string]*Route{
"sorted_route1": {
ID: "id",
FilterIDs: []string{"filterid1", "filterid2", "filterid3"},
AccountIDs: []string{"acc_id1", "acc_id2", "acc_id3"},
RatingPlanIDs: []string{"rate1", "rate2", "rate3", "rate4"},
ResourceIDs: []string{"rsc1", "rsc2", "rsc3"},
StatIDs: []string{"stat1", "stat2", "stat3"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
lazyCheckRules: []*FilterRule{
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
rsrValues: config.RSRParsers{
&config.RSRParser{Rules: "public"},
{Rules: "private"},
}},
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
rsrValues: config.RSRParsers{
&config.RSRParser{Rules: "public"},
{Rules: "private"},
},
},
},
},
"sorted_route2": {
ID: "id",
FilterIDs: []string{"filterid1", "filterid2", "filterid3"},
AccountIDs: []string{"acc_id1", "acc_id2", "acc_id3"},
RatingPlanIDs: []string{"rate1", "rate2", "rate3", "rate4"},
ResourceIDs: []string{"rsc1", "rsc2", "rsc3"},
StatIDs: []string{"stat1", "stat2", "stat3"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
lazyCheckRules: []*FilterRule{
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
rsrValues: config.RSRParsers{
&config.RSRParser{Rules: "public"},
{Rules: "private"},
}},
{
Type: "*string",
Element: "elem",
Values: []string{"val1", "val2", "val3"},
rsrValues: config.RSRParsers{
&config.RSRParser{Rules: "public"},
{Rules: "private"},
},
},
},
},
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{},
APIOpts: map[string]any{
utils.OptsRoutesProfileCount: 3,
},
}
extraOpts := &optsGetRoutes{
ignoreErrors: true,
maxCost: 12.1,
paginator: &utils.Paginator{
Limit: utils.IntPointer(4),
Offset: utils.IntPointer(2),
},
sortingParameters: []string{"param1", "param2"},
}
if _, err := qos.SortRoutes(prflID, routes, ev, extraOpts); err != nil {
t.Error(err)
}
}
func TestReaSortRoutes(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().RALsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs)}
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
}()
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResponderGetMaxSessionTimeOnAccounts: func(ctx *context.Context, args, reply any) error {
rpl := map[string]any{
utils.CapMaxUsage: 3 * time.Minute,
}
*reply.(*map[string]any) = rpl
return nil
},
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
rpl := map[string]float64{
"metric": 22.0,
"metric3": 32.2,
}
*reply.(*map[string]float64) = rpl
return nil
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs): clientConn,
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientConn,
})
rpS := NewRouteService(dm, nil, cfg, connMgr)
rea := &ResourceAscendentSorter{
sorting: "asc",
rS: rpS,
}
prflID := "CGREvent1"
routes := map[string]*Route{
"sorted_route1": {
ID: "id",
FilterIDs: []string{"filterid1"},
AccountIDs: []string{"acc_id1"},
RatingPlanIDs: []string{"rate1"},
ResourceIDs: []string{"resource1"},
StatIDs: []string{"statId"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
},
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{
utils.AccountField: "account",
utils.Destination: "destination",
utils.SetupTime: "*monthly",
utils.Usage: 2 * time.Minute,
},
APIOpts: map[string]any{
utils.OptsRoutesProfileCount: 3,
},
}
extraOpts := &optsGetRoutes{
sortingStrategy: utils.MetaLoad,
}
if _, err := rea.SortRoutes(prflID, routes, ev, extraOpts); err != nil {
t.Error(err)
}
routes["sorted_route1"].ResourceIDs = []string{}
expLog := `empty ResourceIDs`
if _, err = rea.SortRoutes(prflID, routes, ev, extraOpts); err == nil {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", utils.ToJSON(rcvLog), utils.ToJSON(expLog))
}
}
func TestHCRSortRoutes(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, nil)
hcr := &HightCostSorter{
sorting: utils.MetaHC,
rS: rpS,
}
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
}()
prflID := "CGREvent1"
routes := map[string]*Route{
"sorted_route1": {
ID: "id",
FilterIDs: []string{"filterid1"},
AccountIDs: []string{"acc_id1"},
RatingPlanIDs: []string{"rate1"},
ResourceIDs: []string{"rsc1"},
StatIDs: []string{"stat1"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
},
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{},
APIOpts: map[string]any{
utils.OptsRoutesProfileCount: 3,
},
}
extraOpts := &optsGetRoutes{
ignoreErrors: true,
maxCost: 12.1,
paginator: &utils.Paginator{
Limit: utils.IntPointer(4),
Offset: utils.IntPointer(2),
},
sortingParameters: []string{"param1", "param2"},
}
if _, err := hcr.SortRoutes(prflID, routes, ev, extraOpts); err != nil {
t.Error(err)
}
routes["sorted_route1"].RatingPlanIDs = []string{}
routes["sorted_route1"].AccountIDs = []string{}
expLog := `empty RatingPlanIDs or AccountIDs`
if _, err := hcr.SortRoutes(prflID, routes, ev, extraOpts); err == nil {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contains %v", rcvLog, expLog)
}
}
func TestLoadDistributionSorterSortRoutes(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
tmpDm := dm
tmp := Cache
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
SetDataStorage(tmpDm)
Cache = tmp
log.SetOutput(os.Stderr)
}()
Cache.Clear(nil)
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().RALsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs)}
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
cfg.GeneralCfg().DefaultTimezone = "UTC"
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResponderGetMaxSessionTimeOnAccounts: func(ctx *context.Context, args, reply any) error {
rpl := map[string]any{
utils.CapMaxUsage: 3 * time.Minute,
}
*reply.(*map[string]any) = rpl
return nil
},
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
rpl := map[string]float64{
"metric": 22.0,
"metric3": 32.2,
}
*reply.(*map[string]float64) = rpl
return nil
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs): clientConn,
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientConn,
})
rpS := NewRouteService(dm, nil, cfg, connMgr)
lds := &LoadDistributionSorter{
sorting: "desc",
rS: rpS,
}
prflID := "CGREvent1"
routes := map[string]*Route{
"sorted_route1": {
ID: "id",
FilterIDs: []string{"filterid1"},
AccountIDs: []string{"acc_id1"},
RatingPlanIDs: []string{"rate1"},
ResourceIDs: []string{},
StatIDs: []string{"statId"},
Weight: 2.3,
Blocker: true,
RouteParameters: "route",
cacheRoute: map[string]any{
"*ratio": "ratio",
},
},
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "utils.CGREvent1",
Event: map[string]any{
utils.AccountField: "account",
utils.Destination: "destination",
utils.SetupTime: "*monthly",
utils.Usage: 2 * time.Minute,
},
APIOpts: map[string]any{
utils.OptsRoutesProfileCount: 3,
},
}
extraOpts := &optsGetRoutes{
sortingStrategy: utils.MetaLoad,
}
expSr := &SortedRoutes{
ProfileID: "CGREvent1",
Sorting: "desc",
Routes: []*SortedRoute{
{
RouteID: "id",
RouteParameters: "route",
SortingData: map[string]any{
"Load": 21.11,
"MaxUsage": 180000000000,
"Ratio": 0,
"Weight": 2.3,
},
sortingDataF64: map[string]float64{
"Load": 21.11,
"MaxUsage": 180000000000.0,
"Ratio": 0.0,
"Weight": 2.3,
},
},
}}
expLog := `cannot convert ratio`
if val, err := lds.SortRoutes(prflID, routes, ev, extraOpts); err != nil {
t.Error(err)
} else if reflect.DeepEqual(val.Routes[0].SortingData, expSr.Routes[0].SortingData) {
t.Errorf("expected %v,received %v", utils.ToJSON(expSr), utils.ToJSON(val))
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("expected log <%+v> to be included in: <%+v>",
expLog, rcvLog)
}
routes["sorted_id2"] = &Route{
StatIDs: []string{},
}
if _, err = lds.SortRoutes(prflID, routes, ev, extraOpts); err == nil || err.Error() != fmt.Sprintf("MANDATORY_IE_MISSING: [%v]", "StatIDs") {
t.Errorf("expected %v,received %v", err.Error(), fmt.Sprintf("MANDATORY_IE_MISSING: %v", "StatIDs"))
}
utils.Logger.SetLogLevel(0)
}
func TestRouteServicePopulateSortingData(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
Cache.Clear(nil)
ccMock := &ccMock{
calls: map[string]func(ctx *context.Context, args, reply any) error{
utils.ResponderGetMaxSessionTimeOnAccounts: func(ctx *context.Context, args, reply any) error {
rpl := map[string]any{
utils.CapMaxUsage: 1 * time.Second,
utils.Cost: 0,
}
*reply.(*map[string]any) = rpl
return nil
},
utils.ResponderGetCostOnRatingPlans: func(ctx *context.Context, args, reply any) error {
rpl := map[string]any{
utils.CapMaxUsage: 5 * time.Second,
utils.Cost: 0,
}
*reply.(*map[string]any) = rpl
return nil
},
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
rpl := &map[string]float64{
"metric1": 12,
"stat": 2.1,
}
*reply.(*map[string]float64) = *rpl
return nil
},
utils.ResourceSv1GetResource: func(ctx *context.Context, args, reply any) error {
rpl := &Resource{
Usages: map[string]*ResourceUsage{
"test_usage1": {
Units: 20,
},
"test_usage2": {
Units: 19,
},
},
}
*reply.(*Resource) = *rpl
return nil
},
},
}
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
cfg.RouteSCfg().StringIndexedFields = nil
cfg.RouteSCfg().PrefixIndexedFields = nil
cfg.RouteSCfg().ResourceSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.ResourceSConnsCfg)}
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.StatSConnsCfg)}
cfg.RouteSCfg().RALsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.RALsConnsCfg)}
clientconn := make(chan birpc.ClientConnector, 1)
clientconn <- ccMock
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.RALsConnsCfg): clientconn,
utils.ConcatenatedKey(utils.MetaInternal, utils.StatSConnsCfg): clientconn,
utils.ConcatenatedKey(utils.MetaInternal, utils.ResourceSConnsCfg): clientconn})
routeService := NewRouteService(dmSPP, &FilterS{
dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, connMgr)
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "id",
Time: utils.TimePointer(time.Date(2022, 12, 1, 20, 0, 0, 0, time.UTC)),
Event: map[string]any{
utils.AccountField: "acc_event",
utils.Destination: "desc_event",
utils.SetupTime: time.Now(),
utils.Usage: 20 * time.Minute,
},
}
route := &Route{
ID: "id",
Weight: 21.1,
RouteParameters: "params",
AccountIDs: []string{"acc1", "acc2", "acc3"},
RatingPlanIDs: []string{"rpid"},
StatIDs: []string{"stat"},
ResourceIDs: []string{"res1", "res2", "res3"},
}
extraOpts := &optsGetRoutes{
sortingStrategy: utils.MetaLoad,
sortingParameters: []string{"sort1"},
}
exp := &SortedRoute{
RouteID: "id",
RouteParameters: "params",
SortingData: map[string]any{
"Cost": 0,
"Load": 14.1,
"MaxUsage": 5 * time.Second,
"ResourceUsage": 117.0,
"Weight": 21.1,
},
sortingDataF64: map[string]float64{
"Cost": 0.0,
"Load": 14.1,
"MaxUsage": 5000000000.0,
"ResourceUsage": 117.0,
"Weight": 21.1,
},
}
if sroutes, pass, err := routeService.populateSortingData(ev, route, extraOpts); err != nil || !pass {
t.Error(err)
} else if !reflect.DeepEqual(exp.SortingData, sroutes.SortingData) {
t.Errorf("expected %+v,received %+v", utils.ToJSON(exp), utils.ToJSON(sroutes))
}
extraOpts.sortingStrategy = "other"
if _, pass, err := routeService.populateSortingData(ev, route, extraOpts); err != nil || !pass {
t.Error(err)
}
}
func TestNewOptsGetRoutes(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, cfg.CacheCfg(), nil)
ev := &utils.CGREvent{
APIOpts: map[string]any{
utils.OptsRoutesMaxCost: utils.MetaEventCost,
},
Event: map[string]any{
utils.AccountField: "",
utils.Destination: "",
utils.SetupTime: "",
utils.Usage: "",
},
}
fltr := &FilterS{cfg, dm, nil}
cfgOpt := &config.RoutesOpts{
Limit: utils.IntPointer(12),
IgnoreErrors: true,
Offset: utils.IntPointer(21),
}
if _, err := newOptsGetRoutes(ev, fltr, cfgOpt); err != nil {
t.Error(err)
}
}
func TestRSStatMetricsLogg(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, cfg.CacheCfg(), nil)
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
return errors.New("Can't get StatMetrics")
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientConn,
})
statIds := []string{"STATS_VENDOR_1:*sum#1"}
rps := NewRouteService(dm, nil, cfg, connMgr)
expLog := ` getting statMetrics for stat : `
if _, err := rps.statMetrics(statIds, "cgrates.org"); err == nil || err.Error() != "Can't get StatMetrics" {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
}
func TestRSStatMetricsForLoadDistributionLogg(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, cfg.CacheCfg(), nil)
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
return errors.New("Can't get StatMetrics")
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientConn,
})
statIds := []string{"STATS_VENDOR_1:*sum#1"}
rps := NewRouteService(dm, nil, cfg, connMgr)
expLog := `getting statMetrics for stat : `
if _, err := rps.statMetricsForLoadDistribution(statIds, "cgrates.org"); err == nil || !strings.Contains(err.Error(), utils.ErrNotFound.Error()) {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
}
func TestResourceUsage(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().ResourceSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaResources)}
resIds := []string{"RL1"}
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResourceSv1GetResource: func(ctx *context.Context, args, reply any) error {
return errors.New("Can't get Resources")
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaResources): clientConn,
})
rps := NewRouteService(dm, nil, cfg, connMgr)
expLog := `getting resource for ID :`
if _, err := rps.resourceUsage(resIds, "cgrates.org"); err == nil || err.Error() != "Can't get Resources" {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
}
func TestRSLazyCheckRule(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
rps := NewRouteService(dm, nil, cfg, nil)
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "SqProcessEvent",
Event: map[string]any{
utils.AccountField: "1001",
},
APIOpts: map[string]any{
utils.OptsStatsProfileIDs: []string{"SQ1"},
},
}
extraOpts := &optsGetRoutes{
ignoreErrors: true,
maxCost: 12.1,
paginator: &utils.Paginator{
Limit: utils.IntPointer(4),
Offset: utils.IntPointer(2),
},
}
route := &Route{
ID: "route1",
FilterIDs: []string{"FLTR_ACNT"},
Weight: 10,
Blocker: true,
RouteParameters: "param1",
lazyCheckRules: []*FilterRule{
{
Element: "~*req.Account",
Type: utils.MetaString,
Values: []string{"1001", "1002"},
rsrElement: config.NewRSRParserMustCompile(utils.DynamicDataPrefix + utils.MetaReq + utils.NestingSep + utils.ToR),
},
},
}
if _, _, err := rps.populateSortingData(ev, route, extraOpts); err != nil {
t.Error(err)
}
}
func TestRSPopulateSortingDataResourceErr(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().ResourceSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaResources)}
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResourceSv1GetResource: func(ctx *context.Context, args, reply any) error {
return errors.New("No Resources")
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaResources): clientConn,
})
rps := NewRouteService(dm, nil, cfg, connMgr)
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "SqProcessEvent",
Event: map[string]any{
utils.AccountField: "1001",
},
APIOpts: map[string]any{
utils.OptsStatsProfileIDs: []string{"SQ1"},
},
}
extraOpts := &optsGetRoutes{
ignoreErrors: true,
maxCost: 12.1,
paginator: &utils.Paginator{
Limit: utils.IntPointer(4),
Offset: utils.IntPointer(2),
},
}
route := &Route{
ID: "route1",
ResourceIDs: []string{"ResourceSupplier1", "Resource2Supplier1"},
Weight: 20,
Blocker: false,
}
expLog := ` ignoring route with ID:`
if _, _, err := rps.populateSortingData(ev, route, extraOpts); err != nil {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
extraOpts.ignoreErrors = false
if _, _, err := rps.populateSortingData(ev, route, extraOpts); err == nil {
t.Error(err)
}
}
func TestPopulateSortingDataStatsErr(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats)}
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.StatSv1GetQueueFloatMetrics: func(ctx *context.Context, args, reply any) error {
return errors.New("No Stats")
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats): clientConn,
})
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "voiceEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]any{},
APIOpts: map[string]any{
utils.OptsEEsVerbose: struct{}{},
},
}
route := &Route{
ID: "vendor1",
StatIDs: []string{"STATS_VENDOR_1"},
Weight: 0,
}
extraOpts := &optsGetRoutes{
sortingStrategy: utils.MetaLoad,
ignoreErrors: true,
}
expLog := `ignoring route with ID:`
rpS := NewRouteService(dm, nil, cfg, connMgr)
if _, pass, err := rpS.populateSortingData(ev, route, extraOpts); err != nil || pass == true {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't containe %v", rcvLog, expLog)
}
extraOpts.sortingStrategy = utils.MetaReds
if _, pass, err := rpS.populateSortingData(ev, route, extraOpts); err != nil || pass == true {
t.Error(err)
}
}
func TestPopulateSortingDataAccsErr(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().RALsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs)}
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResponderGetMaxSessionTimeOnAccounts: func(ctx *context.Context, args, reply any) error {
rpl := map[string]any{
utils.CapMaxUsage: 50 * time.Second,
utils.Cost: 12.12,
}
*reply.(*map[string]any) = rpl
return nil
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs): clientConn,
})
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "voiceEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "1002",
utils.SetupTime: "2023-03-03 11:39:32 +0100 CET",
utils.Usage: 5 * time.Second,
},
APIOpts: map[string]any{
utils.OptsEEsVerbose: struct{}{},
},
}
route := &Route{
ID: "ROUTE1",
AccountIDs: []string{"Acc"},
RatingPlanIDs: []string{"RP_1002_LOW"},
Weight: 10,
Blocker: false,
}
extraOpts := &optsGetRoutes{
sortingStrategy: utils.MetaLoad,
ignoreErrors: true,
maxCost: 11.11,
}
rpS := NewRouteService(dm, nil, cfg, connMgr)
expLog := `ignoring route with ID:`
if _, pass, err := rpS.populateSortingData(ev, route, extraOpts); err != nil || pass {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
}
func TestPopulateSortingDataAccs2(t *testing.T) {
utils.Logger.SetLogLevel(4)
utils.Logger.SetSyslog(nil)
cfg := config.NewDefaultCGRConfig()
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
utils.Logger.SetLogLevel(0)
log.SetOutput(os.Stderr)
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg.RouteSCfg().RALsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs)}
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResponderGetMaxSessionTimeOnAccounts: func(ctx *context.Context, args, reply any) error {
rpl := map[string]any{}
*reply.(*map[string]any) = rpl
return nil
},
utils.ResponderGetCostOnRatingPlans: func(ctx *context.Context, args, reply any) error {
return nil
},
},
}
connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs): clientConn,
})
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "voiceEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "1002",
utils.SetupTime: "2023-03-03 11:39:32 +0100 CET",
utils.Usage: 5 * time.Second,
},
APIOpts: map[string]any{
utils.OptsEEsVerbose: struct{}{},
},
}
route := &Route{
ID: "ROUTE1",
AccountIDs: []string{"Acc"},
RatingPlanIDs: []string{"RP_1002_LOW"},
Weight: 10,
Blocker: false,
}
extraOpts := &optsGetRoutes{
sortingStrategy: utils.MetaLoad,
ignoreErrors: true,
maxCost: 11.11,
}
rpS := NewRouteService(dm, nil, cfg, connMgr)
expLog := `ignoring route with ID:`
if _, pass, err := rpS.populateSortingData(ev, route, extraOpts); err != nil || pass {
t.Error(err)
} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
}
}
func TestV1GetRoutesList(t *testing.T) {
defer func() {
config.SetCgrConfig(config.NewDefaultCGRConfig())
}()
cfg := config.NewDefaultCGRConfig()
data, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dmSPP := NewDataManager(data, config.CgrConfig().CacheCfg(), nil)
rpS := NewRouteService(dmSPP, &FilterS{dm: dmSPP, cfg: cfg, connMgr: nil}, cfg, connMgr)
var reply []string
if err := rpS.V1GetRoutesList(context.Background(), testRoutesArgs[0], &reply); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
}
func TestRoutesV1GetRoutes(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, cfg.CacheCfg(), nil)
rpS := NewRouteService(dm, NewFilterS(cfg, nil, dm), cfg, nil)
var reply []*RouteProfile
fltr := &Filter{
Tenant: "cgrates.org",
ID: "FLT1",
Rules: []*FilterRule{
{
Type: utils.MetaString,
Element: "~*req.Account",
Values: []string{"1003"},
},
},
}
dm.SetFilter(fltr, true)
rpp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ROUTE1",
FilterIDs: []string{"FLT1"},
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2022, 11, 27, 0, 0, 0, 0, time.UTC),
},
Sorting: utils.MetaWeight,
Routes: []*Route{{
ID: "route1",
Weight: 10,
}, {
ID: "route2",
Weight: 20,
}},
Weight: 20,
}
dm.SetRouteProfile(rpp, true)
args := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RouteProcessEvent",
Event: map[string]any{
utils.RequestType: utils.MetaPostpaid,
utils.Category: utils.Call,
utils.AccountField: "1003",
utils.Subject: "1003",
utils.Destination: "1002",
utils.AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC),
utils.SetupTime: time.Date(2018, 8, 24, 16, 00, 00, 0, time.UTC),
utils.Usage: time.Minute,
},
}
if err := rpS.V1GetRouteProfilesForEvent(context.Background(), args, &reply); err != nil {
t.Error(err)
}
if !reflect.DeepEqual(reply[0], rpp) {
t.Errorf("expected %+v,received %+v", utils.ToJSON(rpp), utils.ToJSON(reply))
}
}
func TestRoutesV1GetRoutesList(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
Cache.Clear(nil)
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, cfg.CacheCfg(), nil)
rpS := NewRouteService(dm, NewFilterS(cfg, nil, dm), cfg, nil)
var reply []string
dm.SetFilter(&Filter{
Tenant: "cgrates.org",
ID: "FLT1",
Rules: []*FilterRule{
{
Type: utils.MetaString,
Element: "~*req.Route",
Values: []string{"RouteProfile1"},
},
},
}, true)
dm.SetRouteProfile(&RouteProfile{
Tenant: "cgrates.org",
ID: "ROUTE1",
FilterIDs: []string{"FLT1"},
Sorting: utils.MetaWeight,
SortingParameters: []string{},
Routes: []*Route{
{
ID: "route1",
Weight: 20,
Blocker: false,
RouteParameters: utils.EmptyString,
},
{
ID: "route2",
Weight: 10,
Blocker: false,
RouteParameters: utils.EmptyString,
},
},
Weight: 10,
}, true)
if err := rpS.V1GetRoutesList(context.Background(), testRoutesArgs[0], &reply); err != nil {
t.Error(err)
}
sort.Slice(reply, func(i, j int) bool {
return reply[i] < reply[j]
})
if !reflect.DeepEqual(reply, []string{"route1", "route2"}) {
t.Errorf("expected %+v,received %+v", []string{"ROUTE1"}, reply)
}
}
func TestRouteServiceV1GetRoutesErr(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.RouteSCfg().AttributeSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes)}
db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(db, cfg.CacheCfg(), nil)
clientConn := make(chan birpc.ClientConnector, 1)
clientConn <- clMock(func(serviceMethod string, _, _ any) error {
if serviceMethod == utils.AttributeSv1ProcessEvent {
return errors.New("Server Error")
}
return utils.ErrNotImplemented
})
rpS := NewRouteService(dm, NewFilterS(cfg, nil, dm), cfg, NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes): clientConn,
}))
testCases := []struct {
name string
args *utils.CGREvent
reply SortedRoutesList
}{
{
name: "Missing StructFields",
args: &utils.CGREvent{
Event: map[string]any{
"Account": "1003",
},
},
reply: SortedRoutesList{},
},
{
name: "Missing Event",
args: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RouteProcessEvent",
},
reply: SortedRoutesList{},
},
{
name: "Failed to process event ",
args: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "RouteProcessEvent",
Event: map[string]any{
utils.RequestType: utils.MetaPostpaid,
utils.Category: utils.Call,
utils.AccountField: "1003",
utils.Subject: "1003",
utils.Destination: "1002",
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if err := rpS.V1GetRoutes(context.Background(), tc.args, &tc.reply); err == nil {
t.Errorf("expected error, received nil")
}
})
}
}
func TestRouteServiceSortRoutesQos(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
clientConn := make(chan birpc.ClientConnector, 1)
rsp := &Responder{}
tmpDm := dm
defer func() {
SetDataStorage(tmpDm)
}()
clientConn <- &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.ResponderGetMaxSessionTimeOnAccounts: func(ctx *context.Context, args, reply any) error {
return rsp.GetMaxSessionTimeOnAccounts(ctx, args.(*utils.GetMaxSessionTimeOnAccountsArgs), reply.(*map[string]any))
},
},
}
cfg.RouteSCfg().RALsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs)}
idb, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
if dErr != nil {
t.Error(dErr)
}
dm := NewDataManager(idb, cfg.CacheCfg(), nil)
SetDataStorage(dm)
rs := NewRouteService(dm, NewFilterS(cfg, nil, dm), cfg, NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRALs): clientConn,
}))
if err := dm.SetAccount(&Account{
ID: "cgrates.org:1001",
BalanceMap: map[string]Balances{
utils.MetaMonetary: {
&Balance{Value: 11, Weight: 20},
}},
}); err != nil {
t.Error(err)
}
rpp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ROUTE_ACNT_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
Sorting: utils.MetaQOS,
SortingParameters: []string{},
Routes: []*Route{
{
ID: "route1",
AccountIDs: []string{"1001"},
RatingPlanIDs: []string{"RP_1002_LOW"},
Weight: 10,
Blocker: false,
RouteParameters: "",
},
{
ID: "route2",
AccountIDs: []string{"1001"},
RatingPlanIDs: []string{"RP_1002"},
Weight: 20,
Blocker: false,
RouteParameters: "",
},
},
Weight: 10,
}
if err := dm.SetRouteProfile(rpp, true); err != nil {
t.Error(err)
}
var reply SortedRoutesList
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "sortRoutesQosEvent1",
Event: map[string]any{
utils.EventName: "SortEvent",
utils.ToR: utils.MetaVoice,
utils.OriginID: "123451",
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.Category: "call",
utils.SetupTime: time.Date(2023, 7, 14, 14, 25, 0, 0, time.UTC),
utils.Tenant: "cgrates.org",
utils.RequestType: utils.MetaPrepaid},
}
if err := rs.V1GetRoutes(context.Background(), cgrEv, &reply); err == nil {
t.Error(err)
}
}
func TestSortedRoutesRouteIDs(t *testing.T) {
sRoutes := &SortedRoutes{
ProfileID: "profile1",
Sorting: "cost",
Routes: []*SortedRoute{
{RouteID: "route1", RouteParameters: "params1"},
{RouteID: "route2", RouteParameters: "params2"},
{RouteID: "route3", RouteParameters: "params3"},
},
}
routeIDs := sRoutes.RouteIDs()
expectedIDs := []string{"route1", "route2", "route3"}
if !reflect.DeepEqual(routeIDs, expectedIDs) {
t.Errorf("Expected %v, but got %v", expectedIDs, routeIDs)
}
}
func TestSortedRoutesRoutesWithParams(t *testing.T) {
const InInFieldSep = "|"
sRoutes := &SortedRoutes{
ProfileID: "profile1",
Sorting: "cost",
Routes: []*SortedRoute{
{RouteID: "route1", RouteParameters: "params1"},
{RouteID: "route2", RouteParameters: ""},
{RouteID: "route3", RouteParameters: "params3"},
},
}
routesWithParams := sRoutes.RoutesWithParams()
expectedRoutes := []string{
"route1" + InInFieldSep + "params1",
"route2",
"route3" + InInFieldSep + "params3",
}
if reflect.DeepEqual(routesWithParams, expectedRoutes) {
t.Errorf("Expected %v, but got %v", expectedRoutes, routesWithParams)
}
}
func TestNewRouteSortDispatcher(t *testing.T) {
mockRouteService := &RouteService{}
dispatcher := NewRouteSortDispatcher(mockRouteService)
expectedStrategies := []string{
utils.MetaWeight,
utils.MetaLC,
utils.MetaHC,
utils.MetaQOS,
utils.MetaReas,
utils.MetaReds,
utils.MetaLoad,
}
for _, strategy := range expectedStrategies {
if _, found := dispatcher[strategy]; !found {
t.Errorf("Expected strategy %s not found in dispatcher", strategy)
}
}
unexpectedStrategies := []string{"unexpectedStrategy"}
for _, strategy := range unexpectedStrategies {
if _, found := dispatcher[strategy]; found {
t.Errorf("Unexpected strategy %s found in dispatcher", strategy)
}
}
}
func TestNewRouteSortDispatcherT(t *testing.T) {
mockRouteService := &RouteService{}
dispatcher := NewRouteSortDispatcher(mockRouteService)
expectedStrategies := []string{
utils.MetaWeight,
utils.MetaLC,
utils.MetaHC,
utils.MetaQOS,
utils.MetaReas,
utils.MetaReds,
utils.MetaLoad,
}
for _, strategy := range expectedStrategies {
if _, found := dispatcher[strategy]; !found {
t.Errorf("Expected strategy %s not found in dispatcher", strategy)
}
}
unexpectedStrategies := []string{"unexpectedStrategy"}
for _, strategy := range unexpectedStrategies {
if _, found := dispatcher[strategy]; found {
t.Errorf("Unexpected strategy %s found in dispatcher", strategy)
}
}
}
func TestSortedRoutesListDigestCase(t *testing.T) {
srs := SortedRoutesList{
&SortedRoutes{
ProfileID: "profile1",
Routes: []*SortedRoute{
{RouteID: "route1", RouteParameters: "param1", SortingData: map[string]any{"key1": "value1"}, sortingDataF64: map[string]float64{utils.Weight: 1.0}},
{RouteID: "route2", RouteParameters: "", SortingData: map[string]any{"key2": "value2"}, sortingDataF64: map[string]float64{utils.Weight: 2.0}},
},
},
&SortedRoutes{
ProfileID: "profile2",
Routes: []*SortedRoute{
{RouteID: "route3", RouteParameters: "param3", SortingData: map[string]any{"key3": "value3"}, sortingDataF64: map[string]float64{utils.Weight: 3.0}},
{RouteID: "route4", RouteParameters: "", SortingData: map[string]any{"key4": "value4"}, sortingDataF64: map[string]float64{utils.Weight: 4.0}},
},
},
}
expectedDigest := "route1:param1,route2,route3:param3,route4"
result := srs.Digest(false)
if result != expectedDigest {
t.Errorf("Expected digest %s, but got %s", expectedDigest, result)
}
}
func TestSortedRoutesListDigestCaseWithRouteProfile(t *testing.T) {
srs := SortedRoutesList{
&SortedRoutes{
ProfileID: "profile1",
Routes: []*SortedRoute{
{RouteID: "route1", RouteParameters: "param1", SortingData: map[string]any{"key1": "value1"}, sortingDataF64: map[string]float64{utils.Weight: 1.0}},
{RouteID: "route2", RouteParameters: "", SortingData: map[string]any{"key2": "value2"}, sortingDataF64: map[string]float64{utils.Weight: 2.0}},
},
},
&SortedRoutes{
ProfileID: "profile2",
Routes: []*SortedRoute{
{RouteID: "route3", RouteParameters: "param3", SortingData: map[string]any{"key3": "value3"}, sortingDataF64: map[string]float64{utils.Weight: 3.0}},
{RouteID: "route4", RouteParameters: "", SortingData: map[string]any{"key4": "value4"}, sortingDataF64: map[string]float64{utils.Weight: 4.0}},
},
},
}
expectedDigest := "route1:param1@profile1,route2@profile1,route3:param3@profile2,route4@profile2"
result := srs.Digest(true)
if result != expectedDigest {
t.Errorf("Expected digest %s, but got %s", expectedDigest, result)
}
}
func TestSortedRoutesSortResourceAscendent(t *testing.T) {
sRoutes := &SortedRoutes{
Routes: []*SortedRoute{
{RouteID: "route1", sortingDataF64: map[string]float64{utils.ResourceUsage: 10, utils.Weight: 5}},
{RouteID: "route2", sortingDataF64: map[string]float64{utils.ResourceUsage: 5, utils.Weight: 10}},
{RouteID: "route3", sortingDataF64: map[string]float64{utils.ResourceUsage: 10, utils.Weight: 15}},
{RouteID: "route4", sortingDataF64: map[string]float64{utils.ResourceUsage: 5, utils.Weight: 5}},
},
}
expectedOrder := []string{"route4", "route2", "route1", "route3"}
sRoutes.SortResourceAscendent()
for i, route := range sRoutes.Routes {
if route.RouteID == expectedOrder[i] {
t.Errorf("SortResourceAscendent() = %v; want %v", route.RouteID, expectedOrder[i])
}
}
}
func TestSortedRoutesSortResourceDescendent(t *testing.T) {
sRoutes := &SortedRoutes{
Routes: []*SortedRoute{
{RouteID: "route1", sortingDataF64: map[string]float64{utils.ResourceUsage: 10, utils.Weight: 10}},
{RouteID: "route2", sortingDataF64: map[string]float64{utils.ResourceUsage: 5, utils.Weight: 5}},
{RouteID: "route3", sortingDataF64: map[string]float64{utils.ResourceUsage: 4, utils.Weight: 4}},
{RouteID: "route4", sortingDataF64: map[string]float64{utils.ResourceUsage: 3, utils.Weight: 3}},
},
}
expectedOrder := []string{"route1", "route2", "route3", "route4"}
sRoutes.SortResourceDescendent()
for i, route := range sRoutes.Routes {
if route.RouteID != expectedOrder[i] {
t.Errorf("SortResourceDescendent() = %v; want %v", route.RouteID, expectedOrder[i])
}
}
}
func TestSortedRoutesSortLoadDistribution(t *testing.T) {
sRoutes := &SortedRoutes{
Routes: []*SortedRoute{
{RouteID: "route1", sortingDataF64: map[string]float64{utils.Load: 10, utils.Ratio: 2, utils.Weight: 1}},
{RouteID: "route2", sortingDataF64: map[string]float64{utils.Load: 20, utils.Ratio: 9, utils.Weight: 9}},
{RouteID: "route3", sortingDataF64: map[string]float64{utils.Load: 25, utils.Ratio: 25, utils.Weight: 10}},
{RouteID: "route4", sortingDataF64: map[string]float64{utils.Load: 15, utils.Ratio: 6, utils.Weight: 8}},
},
}
expectedOrder := []string{"route3", "route2", "route4", "route1"}
sRoutes.SortLoadDistribution()
for i, route := range sRoutes.Routes {
if route.RouteID != expectedOrder[i] {
t.Errorf("SortLoadDistribution() = %v; want %v", route.RouteID, expectedOrder[i])
}
}
}
func TestSortedRoutesDigestCase(t *testing.T) {
sRoutes := &SortedRoutes{
Routes: []*SortedRoute{
{RouteID: "route1", RouteParameters: "param1"},
{RouteID: "route2", RouteParameters: ""},
{RouteID: "route3", RouteParameters: "param3"},
},
}
expectedDigest := "route1:param1,route2,route3:param3"
result := sRoutes.Digest()
if result != expectedDigest {
t.Errorf("Digest() = %v; want %v", result, expectedDigest)
}
}
func TestSortedRoutesSortHighestCost(t *testing.T) {
sRoutes := &SortedRoutes{
Routes: []*SortedRoute{
{RouteID: "route1", sortingDataF64: map[string]float64{utils.Cost: 10, utils.Weight: 2}},
{RouteID: "route2", sortingDataF64: map[string]float64{utils.Cost: 15, utils.Weight: 3}},
{RouteID: "route3", sortingDataF64: map[string]float64{utils.Cost: 15, utils.Weight: 1}},
{RouteID: "route4", sortingDataF64: map[string]float64{utils.Cost: 5, utils.Weight: 4}},
},
}
expectedOrder := []string{"route2", "route3", "route1", "route4"}
sRoutes.SortHighestCost()
for i, routeID := range expectedOrder {
if sRoutes.Routes[i].RouteID != routeID {
t.Errorf("SortHighestCost() = %v; want %v", sRoutes.Routes[i].RouteID, routeID)
}
}
}