Files
cgrates/utils/routes_test.go
2025-10-13 09:57:41 +02:00

688 lines
21 KiB
Go

/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
*/
package utils
import (
"reflect"
"testing"
"time"
)
var (
testRoutesPrfs = []*RouteProfile{
{
Tenant: "cgrates.org",
ID: "RouteProfile1",
FilterIDs: []string{"FLTR_RPP_1"},
Sorting: MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weights: DynamicWeights{{Weight: 10}},
RouteParameters: "param1",
},
},
Weights: DynamicWeights{{Weight: 10}},
Blockers: DynamicBlockers{{Blocker: true}},
},
{
Tenant: "cgrates.org",
ID: "RouteProfile2",
FilterIDs: []string{"FLTR_SUPP_2"},
Sorting: MetaWeight,
Routes: []*Route{
{
ID: "route2",
Weights: DynamicWeights{{Weight: 20}},
RouteParameters: "param2",
},
{
ID: "route3",
Weights: DynamicWeights{{Weight: 10}},
RouteParameters: "param3",
},
{
ID: "route1",
Weights: DynamicWeights{{Weight: 30}},
RouteParameters: "param1",
},
},
Weights: DynamicWeights{{Weight: 20}},
},
{
Tenant: "cgrates.org",
ID: "RouteProfilePrefix",
FilterIDs: []string{"FLTR_SUPP_3"},
Sorting: MetaWeight,
Routes: []*Route{
{
ID: "route1",
Weights: DynamicWeights{{Weight: 10}},
RouteParameters: "param1",
},
},
Weights: DynamicWeights{{Weight: 10}},
},
}
testRoutesArgs = []*CGREvent{
{ //matching RouteProfile1
Tenant: "cgrates.org",
ID: "CGREvent1",
Event: map[string]any{
"Route": "RouteProfile1",
AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"UsageInterval": "1s",
"PddInterval": "1s",
Weight: "20.0",
},
APIOpts: map[string]any{
OptsRoutesProfilesCount: 1,
},
},
{ //matching RouteProfile2
Tenant: "cgrates.org",
ID: "CGREvent1",
Event: map[string]any{
"Route": "RouteProfile2",
AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"UsageInterval": "1s",
"PddInterval": "1s",
Weight: "20.0",
},
APIOpts: map[string]any{
OptsRoutesProfilesCount: 1,
},
},
{ //matching RouteProfilePrefix
Tenant: "cgrates.org",
ID: "CGREvent1",
Event: map[string]any{
"Route": "RouteProfilePrefix",
},
APIOpts: map[string]any{
OptsRoutesProfilesCount: 1,
},
},
{ //matching
Tenant: "cgrates.org",
ID: "CGR",
Event: map[string]any{
"UsageInterval": "1s",
"PddInterval": "1s",
},
APIOpts: map[string]any{
OptsRoutesProfilesCount: 1,
},
},
}
)
func TestRouteProfileSet(t *testing.T) {
rp := RouteProfile{}
exp := RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{Blocker: false},
},
Sorting: MetaQOS,
SortingParameters: []string{"param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
if err := rp.Set([]string{}, "", false); err != ErrWrongPath {
t.Error(err)
}
if err := rp.Set([]string{"", ""}, "", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{"NotAField"}, "", false); err != ErrWrongPath {
t.Error(err)
}
if err := rp.Set([]string{"NotAField", "1"}, ":", false); err != ErrWrongPath {
t.Error(err)
}
if err := rp.Set([]string{Tenant}, "cgrates.org", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{ID}, "ID", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{FilterIDs}, "fltr1;*string:~*req.Account:1001", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Weights}, ";0", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Blockers}, ";false", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Sorting}, MetaQOS, false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{SortingParameters}, "param", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, ID}, "RT1", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, FilterIDs}, "fltr1", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, AccountIDs}, "acc1", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, RateProfileIDs}, "rp1", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, ResourceIDs}, "res1", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, StatIDs}, "stat1", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, Weights}, ";0", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, Blockers}, ";true", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{Routes, RouteParameters}, "params", false); err != nil {
t.Error(err)
}
if err := rp.Set([]string{SortingParameters, "wrong"}, "param", false); err != ErrWrongPath {
t.Error(err)
}
if err := rp.Set([]string{Routes, "wrong"}, "param", false); err != ErrWrongPath {
t.Error(err)
}
if !reflect.DeepEqual(exp, rp) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(rp))
}
}
func TestRouteProfileAsInterface(t *testing.T) {
rp := RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{{Blocker: false}},
Sorting: MetaQOS,
SortingParameters: []string{"param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
if _, err := rp.FieldAsInterface(nil); err != ErrNotFound {
t.Fatal(err)
}
if _, err := rp.FieldAsInterface([]string{"field"}); err != ErrNotFound {
t.Fatal(err)
}
if _, err := rp.FieldAsInterface([]string{"field", ""}); err != ErrNotFound {
t.Fatal(err)
}
if val, err := rp.FieldAsInterface([]string{Tenant}); err != nil {
t.Fatal(err)
} else if exp := "cgrates.org"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{ID}); err != nil {
t.Fatal(err)
} else if exp := ID; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Weights}); err != nil {
t.Fatal(err)
} else if exp := ";0"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Blockers}); err != nil {
t.Fatal(err)
} else if exp := ";false"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{FilterIDs}); err != nil {
t.Fatal(err)
} else if exp := rp.FilterIDs; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{FilterIDs + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.FilterIDs[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{SortingParameters}); err != nil {
t.Fatal(err)
} else if exp := rp.SortingParameters; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{SortingParameters + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.SortingParameters[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Sorting}); err != nil {
t.Fatal(err)
} else if exp := rp.Sorting; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if _, err := rp.FieldAsInterface([]string{Routes + "[4]", ""}); err != ErrNotFound {
t.Fatal(err)
}
if _, err := rp.FieldAsInterface([]string{Routes + "[0]", ""}); err != ErrNotFound {
t.Fatal(err)
}
if _, err := rp.FieldAsInterface([]string{Routes + "[0]", "", ""}); err != ErrNotFound {
t.Fatal(err)
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", ID}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].ID; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", Weights}); err != nil {
t.Fatal(err)
} else if exp := ";0"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", Blockers}); err != nil {
t.Fatal(err)
} else if exp := ";true"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", RouteParameters}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].RouteParameters; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", FilterIDs}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].FilterIDs; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", FilterIDs + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].FilterIDs[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", AccountIDs}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].AccountIDs; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", AccountIDs + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].AccountIDs[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", RateProfileIDs}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].RateProfileIDs; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", RateProfileIDs + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].RateProfileIDs[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", ResourceIDs}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].ResourceIDs; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", ResourceIDs + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].ResourceIDs[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", StatIDs}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].StatIDs; !reflect.DeepEqual(exp, val) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, err := rp.FieldAsInterface([]string{Routes + "[0]", StatIDs + "[0]"}); err != nil {
t.Fatal(err)
} else if exp := rp.Routes[0].StatIDs[0]; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if _, err := rp.FieldAsString([]string{""}); err != ErrNotFound {
t.Fatal(err)
}
if val, err := rp.FieldAsString([]string{ID}); err != nil {
t.Fatal(err)
} else if exp := "ID"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, exp := rp.String(), ToJSON(rp); exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if _, err := rp.Routes[0].FieldAsString([]string{""}); err != ErrNotFound {
t.Fatal(err)
}
if val, err := rp.Routes[0].FieldAsString([]string{ID}); err != nil {
t.Fatal(err)
} else if exp := "RT1"; exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
if val, exp := rp.Routes[0].String(), ToJSON(rp.Routes[0]); exp != val {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(val))
}
}
func TestRouteProfileMerge(t *testing.T) {
dp := &RouteProfile{}
exp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaQOS,
SortingParameters: []string{"param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
if dp.Merge(&RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaQOS,
SortingParameters: []string{"param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}); !reflect.DeepEqual(exp, dp) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(dp))
}
}
func TestRouteMerge(t *testing.T) {
route := &Route{}
routeV2 := &Route{
ID: "RouteId",
RouteParameters: "RouteParam",
Weights: DynamicWeights{{Weight: 10}},
Blockers: DynamicBlockers{{Blocker: false}},
FilterIDs: []string{"FltrId"},
AccountIDs: []string{"AccId"},
RateProfileIDs: []string{"RateProfileId"},
ResourceIDs: []string{"ResourceId"},
StatIDs: []string{"StatId"},
}
exp := routeV2
route.Merge(routeV2)
if !reflect.DeepEqual(route, exp) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(route))
}
}
func TestRouteProfileCompileCacheParametersErrParse(t *testing.T) {
rp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaLoad,
SortingParameters: []string{"sort:param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
expErr := `strconv.Atoi: parsing "param": invalid syntax`
if err := rp.compileCacheParameters(); err.Error() != expErr || err == nil {
t.Errorf("Expected error <%v>, Received error <%v>", expErr, err)
}
}
func TestRouteProfileCompileCacheParametersConfigRatio(t *testing.T) {
rp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaLoad,
SortingParameters: []string{"param:1"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
expErr := `strconv.Atoi: parsing "param": invalid syntax`
if err := rp.compileCacheParameters(); err != nil {
t.Errorf("Expected error <%v>, Received error <%v>", expErr, err)
}
}
func TestRouteProfileCompileCacheParametersDefaultRatio(t *testing.T) {
rp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaLoad,
SortingParameters: []string{"*default:1"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
expErr := `strconv.Atoi: parsing "param": invalid syntax`
if err := rp.compileCacheParameters(); err != nil {
t.Errorf("Expected error <%v>, Received error <%v>", expErr, err)
}
}
func TestRouteProfileCompileCacheParametersRouteRatio(t *testing.T) {
rp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaLoad,
SortingParameters: []string{"RT1:1"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
expErr := `strconv.Atoi: parsing "param": invalid syntax`
if err := rp.compileCacheParameters(); err != nil {
t.Errorf("Expected error <%v>, Received error <%v>", expErr, err)
}
}
func TestRouteProfileMergeWithRPRoutes(t *testing.T) {
dp := &RouteProfile{
Routes: []*Route{
{
ID: "RT1",
},
},
}
exp := &RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaQOS,
SortingParameters: []string{"param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}
if dp.Merge(&RouteProfile{
Tenant: "cgrates.org",
ID: "ID",
FilterIDs: []string{"fltr1", "*string:~*req.Account:1001"},
Weights: DynamicWeights{{}},
Sorting: MetaQOS,
SortingParameters: []string{"param"},
Routes: []*Route{{
ID: "RT1",
FilterIDs: []string{"fltr1"},
AccountIDs: []string{"acc1"},
RateProfileIDs: []string{"rp1"},
ResourceIDs: []string{"res1"},
StatIDs: []string{"stat1"},
Weights: DynamicWeights{{}},
Blockers: DynamicBlockers{
{
Blocker: true,
},
},
RouteParameters: "params",
}},
}); !reflect.DeepEqual(exp, dp) {
t.Errorf("Expected %v \n but received \n %v", ToJSON(exp), ToJSON(dp))
}
}