/* 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 */ 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) } } }