diff --git a/apier/v1/filter_indexes_health_it_test.go b/apier/v1/filter_indexes_health_it_test.go index a718c0aad..3187e8b0e 100644 --- a/apier/v1/filter_indexes_health_it_test.go +++ b/apier/v1/filter_indexes_health_it_test.go @@ -501,7 +501,7 @@ func testV1FIdxGetStatsIndexesHealth(t *testing.T) { func testV1FIdxGetRoutesIndexesHealth(t *testing.T) { // set another routes profile different than the one from tariffplan - rPrf := &RouteWithAPIOpts{ + rPrf := &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: tenant, ID: "TEST_PROFILE1", diff --git a/apier/v1/filter_indexes_it_test.go b/apier/v1/filter_indexes_it_test.go index 5e921755f..8806e6d86 100644 --- a/apier/v1/filter_indexes_it_test.go +++ b/apier/v1/filter_indexes_it_test.go @@ -1060,7 +1060,7 @@ func testV1FIdxSetRouteProfileIndexes(t *testing.T) { err.Error() != utils.ErrNotFound.Error() { t.Error(err) } - rPrf := &RouteWithAPIOpts{ + rPrf := &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: tenant, ID: "TEST_PROFILE1", @@ -1209,7 +1209,7 @@ func testV1FIdxSetSecondRouteProfileIndexes(t *testing.T) { err.Error() != utils.ErrNotFound.Error() { t.Error(err) } - rPrf := &RouteWithAPIOpts{ + rPrf := &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: tenant, ID: "TEST_PROFILE2", diff --git a/apier/v1/replicate_it_test.go b/apier/v1/replicate_it_test.go index 2483d9d24..702e19e13 100644 --- a/apier/v1/replicate_it_test.go +++ b/apier/v1/replicate_it_test.go @@ -348,7 +348,7 @@ func testInternalReplicateITRouteProfile(t *testing.T) { err.Error() != utils.ErrNotFound.Error() { t.Error(err) } - rPrf := &RouteWithAPIOpts{ + rPrf := &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "TEST_PROFILE1", diff --git a/apier/v1/routes.go b/apier/v1/routes.go index 03750f499..4efd3d0d0 100644 --- a/apier/v1/routes.go +++ b/apier/v1/routes.go @@ -66,13 +66,8 @@ func (apierSv1 *APIerSv1) GetRouteProfileIDs(ctx *context.Context, args *utils.P return nil } -type RouteWithAPIOpts struct { - *engine.RouteProfile - APIOpts map[string]any -} - // SetRouteProfile add a new Route configuration -func (apierSv1 *APIerSv1) SetRouteProfile(ctx *context.Context, args *RouteWithAPIOpts, reply *string) error { +func (apierSv1 *APIerSv1) SetRouteProfile(ctx *context.Context, args *engine.RouteWithAPIOpts, reply *string) error { if missing := utils.MissingStructFields(args.RouteProfile, []string{utils.ID}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } diff --git a/apier/v1/routes_it_test.go b/apier/v1/routes_it_test.go index 4c13ae81b..80a5089d5 100644 --- a/apier/v1/routes_it_test.go +++ b/apier/v1/routes_it_test.go @@ -38,7 +38,7 @@ var ( routeSv1CfgPath string routeSv1Cfg *config.CGRConfig routeSv1Rpc *birpc.Client - routePrf *RouteWithAPIOpts + routePrf *engine.RouteWithAPIOpts routeSv1ConfDIR string //run tests for specific configuration sTestsRouteSV1 = []func(t *testing.T){ @@ -843,7 +843,7 @@ func testV1RouteGetRouteWithoutFilter(t *testing.T) { } func testV1RouteSetRouteProfiles(t *testing.T) { - routePrf = &RouteWithAPIOpts{ + routePrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "TEST_PROFILE1", @@ -1105,7 +1105,7 @@ func testV1RoutesOneRouteWithoutDestination(t *testing.T) { err.Error() != utils.ErrNotFound.Error() { t.Error(err) } - routePrf = &RouteWithAPIOpts{ + routePrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "ROUTE_DESTINATION", @@ -1177,7 +1177,7 @@ func testV1RouteMultipleRouteSameID(t *testing.T) { err.Error() != utils.ErrNotFound.Error() { t.Error(err) } - routePrf = &RouteWithAPIOpts{ + routePrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "MULTIPLE_ROUTES", @@ -1281,7 +1281,7 @@ func testV1RouteMultipleRouteSameID(t *testing.T) { } func testV1RouteAccountWithRatingPlan(t *testing.T) { - routePrf = &RouteWithAPIOpts{ + routePrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "RouteWithAccAndRP", @@ -1555,7 +1555,7 @@ func testV1RouteStopEngine(t *testing.T) { } func testV1RouteSetRouteProfilesWithoutTenant(t *testing.T) { - routePrf = &RouteWithAPIOpts{ + routePrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "TEST_PROFILE10", @@ -1633,7 +1633,7 @@ func testRouteSCacheTestGetFound(t *testing.T) { } func testRouteSCacheTestSet(t *testing.T) { - routePrf = &RouteWithAPIOpts{ + routePrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "ROUTE_CACHE", diff --git a/console/routes_profile_set.go b/console/routes_profile_set.go index 160a28a46..3ecb7011e 100644 --- a/console/routes_profile_set.go +++ b/console/routes_profile_set.go @@ -19,7 +19,6 @@ along with this program. If not, see package console import ( - v1 "github.com/cgrates/cgrates/apier/v1" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) @@ -28,7 +27,7 @@ func init() { c := &CmdSetRoute{ name: "routes_profile_set", rpcMethod: utils.APIerSv1SetRouteProfile, - rpcParams: &v1.RouteWithAPIOpts{}, + rpcParams: &engine.RouteWithAPIOpts{}, } commands[c.Name()] = c c.CommandExecuter = &CommandExecuter{c} @@ -37,7 +36,7 @@ func init() { type CmdSetRoute struct { name string rpcMethod string - rpcParams *v1.RouteWithAPIOpts + rpcParams *engine.RouteWithAPIOpts *CommandExecuter } @@ -51,7 +50,7 @@ func (self *CmdSetRoute) RpcMethod() string { func (self *CmdSetRoute) RpcParams(reset bool) any { if reset || self.rpcParams == nil { - self.rpcParams = &v1.RouteWithAPIOpts{ + self.rpcParams = &engine.RouteWithAPIOpts{ RouteProfile: new(engine.RouteProfile), APIOpts: map[string]any{}, } diff --git a/engine/action.go b/engine/action.go index 7621eaf1c..ba234bac0 100644 --- a/engine/action.go +++ b/engine/action.go @@ -129,7 +129,7 @@ func newActionConnCfg(source, action string, cfg *config.CGRConfig) ActionConnCf utils.MetaDynamicThreshold, utils.MetaDynamicStats, utils.MetaDynamicAttribute, utils.MetaDynamicActionPlan, utils.MetaDynamicAction, utils.MetaDynamicDestination, - utils.MetaDynamicFilter, + utils.MetaDynamicFilter, utils.MetaDynamicRoute, } act := ActionConnCfg{} switch source { @@ -196,6 +196,7 @@ func init() { actionFuncMap[utils.MetaDynamicAction] = dynamicAction actionFuncMap[utils.MetaDynamicDestination] = dynamicDestination actionFuncMap[utils.MetaDynamicFilter] = dynamicFilter + actionFuncMap[utils.MetaDynamicRoute] = dynamicRoute } func getActionFunc(typ string) (f actionTypeFunc, exists bool) { @@ -2155,3 +2156,141 @@ func dynamicFilter(_ *Account, act *Action, _ Actions, _ *FilterS, ev any, var reply string return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetFilter, fltr, &reply) } + +// dynamicRoute processes the `ExtraParameters` field from the action to +// construct a RouteProfile +// +// The ExtraParameters field format is expected as follows: +// +// 0 Tenant: string +// 1 ID: string +// 2 FilterIDs: strings separated by "&". +// 3 ActivationInterval: strings separated by "&". +// 4 Sorting: string +// 5 SortingParameters: strings separated by "&". +// 6 RouteID: string +// 7 RouteFilterIDs: strings separated by "&". +// 8 RouteAccountIDs: strings separated by "&". +// 9 RouteRatingPlanIDs: strings separated by "&". +// 10 RouteResourceIDs: strings separated by "&". +// 11 RouteStatIDs: strings separated by "&". +// 12 RouteWeight: string +// 13 RouteBlocker: string +// 14 RouteParameters: string +// 15 Weight: string +// 16 APIOpts: set of key-value pairs (separated by "&"). +// +// Parameters are separated by ";" and must be provided in the specified order. +func dynamicRoute(_ *Account, act *Action, _ Actions, _ *FilterS, ev any, + _ SharedActionsData, connCfg ActionConnCfg) (err error) { + cgrEv, canCast := ev.(*utils.CGREvent) + if !canCast { + return errors.New("Couldn't cast event to CGREvent") + } + dP := utils.MapStorage{ // create DataProvider from event + utils.MetaReq: cgrEv.Event, + utils.MetaTenant: cgrEv.Tenant, + utils.MetaNow: time.Now(), + utils.MetaOpts: cgrEv.APIOpts, + } + // Parse action parameters based on the predefined format. + params := strings.Split(act.ExtraParameters, utils.InfieldSep) + if len(params) != 17 { + return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 17", len(params))) + } + // parse dynamic parameters + for i := range params { + if params[i], err = utils.ParseParamForDataProvider(params[i], dP, false); err != nil { + return err + } + } + // Prepare request arguments based on provided parameters. + route := &RouteWithAPIOpts{ + RouteProfile: &RouteProfile{ + Tenant: params[0], + ID: params[1], + ActivationInterval: &utils.ActivationInterval{}, // avoid reaching inside a nil pointer + Sorting: params[4], + Routes: []*Route{ + { + ID: params[6], + RouteParameters: params[14], + }, + }, + }, + APIOpts: make(map[string]any), + } + // populate Route's FilterIDs + if params[2] != utils.EmptyString { + route.FilterIDs = strings.Split(params[2], utils.ANDSep) + } + // populate Route's ActivationInterval + aISplit := strings.Split(params[3], utils.ANDSep) + if len(aISplit) > 2 { + return utils.ErrUnsupportedFormat + } + if len(aISplit) > 0 && aISplit[0] != utils.EmptyString { + if err := route.ActivationInterval.ActivationTime.UnmarshalText([]byte(aISplit[0])); err != nil { + return err + } + if len(aISplit) == 2 { + if err := route.ActivationInterval.ExpiryTime.UnmarshalText([]byte(aISplit[1])); err != nil { + return err + } + } + } + // populate Route's SortingParameters + if params[5] != utils.EmptyString { + route.SortingParameters = strings.Split(params[5], utils.ANDSep) + } + // populate Route's RouteFilterIDs + if params[7] != utils.EmptyString { + route.Routes[0].FilterIDs = strings.Split(params[7], utils.ANDSep) + } + // populate Route's RouteAccountIDs + if params[8] != utils.EmptyString { + route.Routes[0].AccountIDs = strings.Split(params[8], utils.ANDSep) + } + // populate Route's RouteRatingPlanIDs + if params[9] != utils.EmptyString { + route.Routes[0].RatingPlanIDs = strings.Split(params[9], utils.ANDSep) + } + // populate Route's RouteResourceIDs + if params[10] != utils.EmptyString { + route.Routes[0].ResourceIDs = strings.Split(params[10], utils.ANDSep) + } + // populate Route's RouteStatIDs + if params[11] != utils.EmptyString { + route.Routes[0].StatIDs = strings.Split(params[11], utils.ANDSep) + } + // populate Route's RouteWeight + if params[12] != utils.EmptyString { + route.Routes[0].Weight, err = strconv.ParseFloat(params[12], 64) + if err != nil { + return err + } + } + // populate Route's RouteBlocker + if params[13] != utils.EmptyString { + route.Routes[0].Blocker, err = strconv.ParseBool(params[13]) + if err != nil { + return err + } + } + // populate Route's Weight + if params[15] != utils.EmptyString { + route.Weight, err = strconv.ParseFloat(params[15], 64) + if err != nil { + return err + } + } + // populate Route's APIOpts + if params[16] != utils.EmptyString { + if err := parseParamStringToMap(params[16], route.APIOpts); err != nil { + return err + } + } + // create the RouteProfile based on the populated parameters + var reply string + return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetRouteProfile, route, &reply) +} diff --git a/engine/actions_test.go b/engine/actions_test.go index 5f09a3014..b339a1177 100644 --- a/engine/actions_test.go +++ b/engine/actions_test.go @@ -6246,3 +6246,233 @@ func TestDynamicFilter(t *testing.T) { }) } } + +func TestDynamicRoute(t *testing.T) { + tempConn := connMgr + tmpDm := dm + tmpCache := Cache + defer func() { + config.SetCgrConfig(config.NewDefaultCGRConfig()) + SetConnManager(tempConn) + dm = tmpDm + Cache = tmpCache + }() + Cache.Clear(nil) + var rwo *RouteWithAPIOpts + ccMock := &ccMock{ + calls: map[string]func(ctx *context.Context, args any, reply any) error{ + utils.APIerSv1SetRouteProfile: func(ctx *context.Context, args, reply any) error { + var canCast bool + if rwo, canCast = args.(*RouteWithAPIOpts); !canCast { + return fmt.Errorf("couldnt cast") + } + return nil + }, + }, + } + connID := utils.ConcatenatedKey(utils.MetaInternal, utils.MetaApier) + clientconn := make(chan birpc.ClientConnector, 1) + clientconn <- ccMock + NewConnManager(config.NewDefaultCGRConfig(), map[string]chan birpc.ClientConnector{ + connID: clientconn, + }) + testcases := []struct { + name string + extraParams string + connIDs []string + expRwo *RouteWithAPIOpts + expectedErr string + }{ + { + name: "SuccessfulRequest", + connIDs: []string{connID}, + expRwo: &RouteWithAPIOpts{ + RouteProfile: &RouteProfile{ + Tenant: "cgrates.org", + ID: "RTP_ACNT_1001", + FilterIDs: []string{"*string:~*req.Account:1001", "*string:~*req.Account:1002"}, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: time.Date(2014, 7, 29, 15, 0, 0, 0, time.UTC), + }, + SortingParameters: []string{"*acd", "*tcc"}, + Routes: []*Route{ + { + ID: "route1", + FilterIDs: []string{"*string:~*req.Account:1001", "*string:~*req.Account:1002"}, + AccountIDs: []string{"1001", "1002"}, + RatingPlanIDs: []string{"RP1", "RP2"}, + ResourceIDs: []string{"RS1", "RS2"}, + StatIDs: []string{"Stat_1", "Stat_1_1"}, + Weight: 10, + Blocker: true, + RouteParameters: "param", + }, + }, + Sorting: utils.MetaWeight, + Weight: 10, + }, + APIOpts: map[string]any{ + "key": "value", + }, + }, + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;key:value", + }, + { + name: "SuccessfulRequestWithDynamicPaths", + connIDs: []string{connID}, + expRwo: &RouteWithAPIOpts{ + RouteProfile: &RouteProfile{ + Tenant: "cgrates.org", + ID: "RTP_ACNT_1001", + FilterIDs: []string{"*string:~*req.Account:1001", "*string:~*req.Account:1002"}, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: time.Now(), + ExpiryTime: time.Date(3000, 7, 29, 15, 0, 0, 0, time.UTC), + }, + SortingParameters: []string{"*acd", "*tcc"}, + Routes: []*Route{ + { + ID: "route1", + FilterIDs: []string{"*string:~*req.Account:1001", "*string:~*req.Account:1002"}, + AccountIDs: []string{"1001", "1002"}, + RatingPlanIDs: []string{"RP1", "RP2"}, + ResourceIDs: []string{"RS1", "RS2"}, + StatIDs: []string{"Stat_1", "Stat_1_1"}, + Weight: 10, + Blocker: true, + RouteParameters: "param", + }, + }, + Sorting: utils.MetaWeight, + Weight: 10, + }, + APIOpts: map[string]any{ + "key": "value", + }, + }, + extraParams: "*tenant;RTP_ACNT_<~*req.Account>;*string:~*req.Account:<~*req.Account>&*string:~*req.Account:1002;*now&3000-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:<~*req.Account>&*string:~*req.Account:1002;<~*req.Account>&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;key:value", + }, + { + name: "SuccessfulRequestEmptyFields", + connIDs: []string{connID}, + expRwo: &RouteWithAPIOpts{ + RouteProfile: &RouteProfile{ + Tenant: "cgrates.org", + ID: "RTP_ACNT_1001", + FilterIDs: nil, + ActivationInterval: &utils.ActivationInterval{}, + SortingParameters: nil, + Routes: []*Route{ + { + ID: "route1", + FilterIDs: nil, + AccountIDs: nil, + RatingPlanIDs: nil, + ResourceIDs: nil, + StatIDs: nil, + Weight: 0, + Blocker: false, + RouteParameters: "", + }, + }, + Sorting: "", + Weight: 0, + }, + APIOpts: map[string]any{}, + }, + extraParams: "cgrates.org;RTP_ACNT_1001;;;;;route1;;;;;;;;;;", + }, + { + name: "MissingConns", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;key:value", + expectedErr: "MANDATORY_IE_MISSING: [connIDs]", + }, + { + name: "WrongNumberOfParams", + extraParams: "tenant;RTP1;;", + expectedErr: "invalid number of parameters <4> expected 17", + }, + { + name: "ActivationIntervalLengthFail", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z&&;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;key:value", + expectedErr: utils.ErrUnsupportedFormat.Error(), + }, + { + name: "ActivationIntervalBadStringFail", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;bad String;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;key:value", + expectedErr: `parsing time "bad String" as "2006-01-02T15:04:05Z07:00": cannot parse "bad String" as "2006"`, + }, + { + name: "ActivationIntervalBadStringFail2", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z&bad String;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;key:value", + expectedErr: `parsing time "bad String" as "2006-01-02T15:04:05Z07:00": cannot parse "bad String" as "2006"`, + }, + { + name: "RouteWeightFail", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;BadString;true;param;10;key:value", + expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`, + }, + { + name: "RouteBlockerFail", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;BadString;param;10;key:value", + expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`, + }, + { + name: "WeightFail", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;BadString;key:value", + expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`, + }, + { + name: "InvalidOptsMap", + extraParams: "cgrates.org;RTP_ACNT_1001;*string:~*req.Account:1001&*string:~*req.Account:1002;2014-07-29T15:00:00Z;*weight;*acd&*tcc;route1;*string:~*req.Account:1001&*string:~*req.Account:1002;1001&1002;RP1&RP2;RS1&RS2;Stat_1&Stat_1_1;10;true;param;10;opt", + expectedErr: "invalid key-value pair: opt", + }, + } + + for i, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + action := &Action{ExtraParameters: tc.extraParams} + ev := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "evID", + Time: &time.Time{}, + Event: map[string]any{ + utils.AccountField: "1001", + }, + } + t.Cleanup(func() { + rwo = nil + }) + err := dynamicRoute(nil, action, nil, nil, ev, + SharedActionsData{}, ActionConnCfg{ + ConnIDs: tc.connIDs, + }) + if tc.expectedErr != "" { + if err == nil || err.Error() != tc.expectedErr { + t.Errorf("expected error <%v>, received <%v>", tc.expectedErr, err) + } + } else if err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rwo, tc.expRwo) { + if i != 1 { + t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRwo), utils.ToJSON(rwo)) + } else { + // Get the absolute difference between the times + diff := rwo.ActivationInterval.ActivationTime.Sub(tc.expRwo.ActivationInterval.ActivationTime) + if diff < 0 { + diff = -diff // Make sure it's positive + } + // Check if difference is less than or equal to 1 second + if diff <= time.Second { + tc.expRwo.ActivationInterval.ActivationTime = rwo.ActivationInterval.ActivationTime + if !reflect.DeepEqual(rwo, tc.expRwo) { + t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRwo), utils.ToJSON(rwo)) + } + } else { + t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRwo), utils.ToJSON(rwo)) + } + } + } + }) + } +} diff --git a/engine/routes.go b/engine/routes.go index f9d250693..6cf9065d6 100644 --- a/engine/routes.go +++ b/engine/routes.go @@ -90,6 +90,11 @@ func (r *Route) Clone() *Route { return clone } +type RouteWithAPIOpts struct { + *RouteProfile + APIOpts map[string]any +} + // RouteProfile represents the configuration of a Route profile type RouteProfile struct { Tenant string diff --git a/general_tests/filtered_replication_it_test.go b/general_tests/filtered_replication_it_test.go index b13930b8c..24dc6a246 100644 --- a/general_tests/filtered_replication_it_test.go +++ b/general_tests/filtered_replication_it_test.go @@ -912,7 +912,7 @@ func testFltrRplResourceProfile(t *testing.T) { func testFltrRplRouteProfile(t *testing.T) { rpID := "RT1" - rpPrf := &v1.RouteWithAPIOpts{ + rpPrf := &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: rpID, diff --git a/general_tests/route_it_test.go b/general_tests/route_it_test.go index 39137728b..48edcb42c 100644 --- a/general_tests/route_it_test.go +++ b/general_tests/route_it_test.go @@ -29,7 +29,6 @@ import ( "github.com/cgrates/birpc" "github.com/cgrates/birpc/context" - v1 "github.com/cgrates/cgrates/apier/v1" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" @@ -39,7 +38,7 @@ var ( splSv1CfgPath string splSv1Cfg *config.CGRConfig splSv1Rpc *birpc.Client - splPrf *v1.RouteWithAPIOpts + splPrf *engine.RouteWithAPIOpts splSv1ConfDIR string //run tests for specific configuration sTestsSupplierSV1 = []func(t *testing.T){ @@ -131,7 +130,7 @@ func testV1SplSSetRouteProfilesWithoutRatingPlanIDs(t *testing.T) { err.Error() != utils.ErrNotFound.Error() { t.Error(err) } - splPrf = &v1.RouteWithAPIOpts{ + splPrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "TEST_PROFILE2", @@ -193,7 +192,7 @@ func testV1SplSAddNewRoutePrf(t *testing.T) { t.Error(err) } //create a new Supplier Profile to test *reas and *reds sorting strategy - splPrf = &v1.RouteWithAPIOpts{ + splPrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "ROUTE_ResourceTest", @@ -463,7 +462,7 @@ func testV1SplSAddNewRoutePrf2(t *testing.T) { t.Error(err) } //create a new Supplier Profile to test *reas and *reds sorting strategy - splPrf = &v1.RouteWithAPIOpts{ + splPrf = &engine.RouteWithAPIOpts{ RouteProfile: &engine.RouteProfile{ Tenant: "cgrates.org", ID: "ROUTE_ResourceDescendent", diff --git a/general_tests/routes_cases_it_test.go b/general_tests/routes_cases_it_test.go index 76738c679..fc3931ca2 100644 --- a/general_tests/routes_cases_it_test.go +++ b/general_tests/routes_cases_it_test.go @@ -30,7 +30,6 @@ import ( "github.com/cgrates/birpc" "github.com/cgrates/birpc/context" - v1 "github.com/cgrates/cgrates/apier/v1" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" @@ -40,7 +39,7 @@ var ( rtsCaseSv1CfgPath string rtsCaseSv1Cfg *config.CGRConfig rtsCaseSv1Rpc *birpc.Client - rtsCasePrf *v1.RouteWithAPIOpts + rtsCasePrf *engine.RouteWithAPIOpts rtsCaseSv1ConfDIR string //run tests for specific configuration sTestsRtsCaseSV1 = []func(t *testing.T){ diff --git a/utils/consts.go b/utils/consts.go index da985eb7a..d1d44dac7 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1213,6 +1213,7 @@ const ( MetaDynamicAction = "*dynamic_action" MetaDynamicDestination = "*dynamic_destination" MetaDynamicFilter = "*dynamic_filter" + MetaDynamicRoute = "*dynamic_route" ActionID = "ActionID" ActionType = "ActionType" ActionValue = "ActionValue"