diff --git a/actions/dynamic_test.go b/actions/dynamic_test.go index f6be8ff95..0c47f92d5 100644 --- a/actions/dynamic_test.go +++ b/actions/dynamic_test.go @@ -2758,3 +2758,299 @@ func TestActDynamicRouteCfg(t *testing.T) { }) } } + +func TestActDynamicRouteExecute(t *testing.T) { + engine.Cache.Clear(nil) + defer engine.Cache.Clear(nil) + + var rpo *utils.RouteProfileWithAPIOpts + ccM := &ccMock{ + calls: map[string]func(ctx *context.Context, args any, reply any) error{ + utils.AdminSv1SetRouteProfile: func(ctx *context.Context, args, reply any) error { + var canCast bool + if rpo, canCast = args.(*utils.RouteProfileWithAPIOpts); !canCast { + return fmt.Errorf("couldnt cast") + } + *(reply.(*string)) = utils.OK + return nil + }, + }, + } + + testcases := []struct { + name string + diktats []*utils.APDiktat + expRpo *utils.RouteProfileWithAPIOpts + wantErr bool + }{ + { + name: "SuccessfulRequest", + expRpo: &utils.RouteProfileWithAPIOpts{ + RouteProfile: &utils.RouteProfile{ + Tenant: "cgrates.org", + ID: "ROUTE_1001", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{ + FilterIDs: []string{"*weight"}, + Weight: 10.0, + }, + }, + Blockers: utils.DynamicBlockers{ + &utils.DynamicBlocker{ + FilterIDs: []string{"*blocker"}, + Blocker: false, + }, + }, + Sorting: "*weight", + SortingParameters: []string{"*weight", "*cost"}, + Routes: []*utils.Route{ + { + ID: "RT_1001", + FilterIDs: []string{"*string:~*req.Route:1001"}, + AccountIDs: []string{"1001", "1002"}, + RateProfileIDs: []string{"RP_1001"}, + ResourceIDs: []string{"RES_1001"}, + StatIDs: []string{"STAT_1001"}, + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{ + FilterIDs: []string{"*route_weight"}, + Weight: 20.0, + }, + }, + Blockers: utils.DynamicBlockers{ + &utils.DynamicBlocker{ + FilterIDs: []string{"*route_blocker"}, + Blocker: false, + }, + }, + RouteParameters: "param1=value1", + }, + }, + }, + APIOpts: map[string]any{ + utils.MetaSubsys: "*sessions", + }, + }, + diktats: []*utils.APDiktat{ + { + ID: "d1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{Weight: 20.0}, + }, + Opts: map[string]any{ + utils.MetaTemplate: "cgrates.org;ROUTE_1001;*string:~*req.Account:1001;*weight&10.0;*blocker&false;*weight;*weight&*cost;RT_1001;*string:~*req.Route:1001;1001&1002;RP_1001;RES_1001;STAT_1001;*route_weight&20.0;*route_blocker&false;param1=value1;*subsys:*sessions", + }, + }, + }, + wantErr: false, + }, + { + name: "SuccessfulRequestWithDynamicPaths", + expRpo: &utils.RouteProfileWithAPIOpts{ + RouteProfile: &utils.RouteProfile{ + Tenant: "cgrates.org", + ID: "ROUTE_1001", + Sorting: "*weight", + Routes: []*utils.Route{ + { + ID: "RT_1001", + AccountIDs: []string{"1001"}, + RouteParameters: "1001", + }, + }, + }, + APIOpts: map[string]any{ + "account": "1001", + }, + }, + diktats: []*utils.APDiktat{ + { + ID: "d1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{Weight: 15.0}, + }, + Opts: map[string]any{ + utils.MetaTemplate: "cgrates.org;ROUTE_<~*req.Account>;;;;*weight;;RT_<~*req.Account>;;1001;;;;;;<~*req.Account>;account:<~*req.Account>", + }, + }, + }, + wantErr: false, + }, + { + name: "SuccessfulRequestEmptyFields", + expRpo: &utils.RouteProfileWithAPIOpts{ + RouteProfile: &utils.RouteProfile{ + Tenant: "cgrates.org", + ID: "ROUTE_EMPTY", + Sorting: "", + }, + APIOpts: map[string]any{}, + }, + diktats: []*utils.APDiktat{ + { + ID: "d1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{Weight: 10.0}, + }, + Opts: map[string]any{ + utils.MetaTemplate: "cgrates.org;ROUTE_EMPTY;;;;;;;;;;;;;;;", + }, + }, + }, + wantErr: false, + }, + { + name: "EmptyDiktats", + diktats: []*utils.APDiktat{}, + wantErr: true, + }, + { + name: "InvalidNumberOfParams", + diktats: []*utils.APDiktat{ + { + ID: "d1", + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{Weight: 20.0}, + }, + Opts: map[string]any{ + utils.MetaTemplate: "cgrates.org;ROUTE_INVALID;only;four;params", + }, + }, + }, + wantErr: true, + }, + { + name: "APIOptsInvalidFormat", + diktats: []*utils.APDiktat{ + { + ID: "d1", + Weights: utils.DynamicWeights{ + &utils.DynamicWeight{Weight: 20.0}, + }, + Opts: map[string]any{ + utils.MetaTemplate: "cgrates.org;ROUTE_INVALID;;;;;;;;;;;;;;;;invalidformat", + }, + }, + }, + wantErr: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + cfg.GeneralCfg().DefaultCaching = utils.MetaNone + cfg.ActionSCfg().AdminSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS)} + connMgr := engine.NewConnManager(cfg) + dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DataDbCfg().Items) + dm := engine.NewDataManager(dataDB, cfg, connMgr) + fltrS := engine.NewFilterS(cfg, connMgr, dm) + rpcInternal := make(chan birpc.ClientConnector, 1) + rpcInternal <- ccM + connMgr.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS), utils.AdminSv1, rpcInternal) + + act := &actDynamicRoute{ + config: cfg, + connMgr: connMgr, + dm: dm, + fltrS: fltrS, + tnt: "cgrates.org", + cgrEv: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "evID", + Event: map[string]any{ + utils.AccountField: "1001", + }, + }, + aCfg: &utils.APAction{ + ID: "ACT_DYNAMIC_ROUTE", + Diktats: tc.diktats, + }, + } + + t.Cleanup(func() { + rpo = nil + }) + + ctx := context.Background() + data := utils.MapStorage{ + "*req": map[string]any{ + utils.AccountField: "1001", + }, + } + trgID := "trigger1" + err := act.execute(ctx, data, trgID) + + if tc.wantErr { + if err == nil { + t.Errorf("expected an error, but got nil") + } + } else if err != nil { + t.Error(err) + } else if tc.expRpo != nil && !reflect.DeepEqual(rpo, tc.expRpo) { + t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRpo), utils.ToJSON(rpo)) + } + }) + } +} + +func TestActDynamicRateId(t *testing.T) { + tests := []struct { + name string + aL actDynamicRate + want string + }{ + { + name: "WithValidID", + aL: actDynamicRate{aCfg: &utils.APAction{ID: "RateProfile1"}}, + want: "RateProfile1", + }, + { + name: "WithEmptyID", + aL: actDynamicRate{aCfg: &utils.APAction{ID: ""}}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.aL.id(); got != tt.want { + t.Errorf("actDynamicRate.id() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestActDynamicRateCfg(t *testing.T) { + tests := []struct { + name string + aL actDynamicRate + want *utils.APAction + }{ + { + name: "WithValidCfg", + aL: actDynamicRate{aCfg: &utils.APAction{ID: "RateProfile1"}}, + want: &utils.APAction{ID: "RateProfile1"}, + }, + { + name: "WithEmptyCfg", + aL: actDynamicRate{aCfg: &utils.APAction{}}, + want: &utils.APAction{}, + }, + { + name: "WithNilCfg", + aL: actDynamicRate{aCfg: nil}, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.aL.cfg(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("actDynamicRate.cfg() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/actions/libactions_test.go b/actions/libactions_test.go index 74fd8e1a4..caf544d27 100644 --- a/actions/libactions_test.go +++ b/actions/libactions_test.go @@ -144,3 +144,230 @@ func TestACActionTarget(t *testing.T) { t.Errorf("Expected %+v, received %+v", utils.MetaNone, rcv) } } + +func TestActionTarget(t *testing.T) { + tests := []struct { + name string + act string + want string + }{ + { + name: "ResetStatQueue", + act: utils.MetaResetStatQueue, + want: utils.MetaStats, + }, + { + name: "DynamicStats", + act: utils.MetaDynamicStats, + want: utils.MetaStats, + }, + { + name: "ResetThreshold", + act: utils.MetaResetThreshold, + want: utils.MetaThresholds, + }, + { + name: "DynamicThreshold", + act: utils.MetaDynamicThreshold, + want: utils.MetaThresholds, + }, + { + name: "AddBalance", + act: utils.MetaAddBalance, + want: utils.MetaAccounts, + }, + { + name: "SetBalance", + act: utils.MetaSetBalance, + want: utils.MetaAccounts, + }, + { + name: "RemBalance", + act: utils.MetaRemBalance, + want: utils.MetaAccounts, + }, + { + name: "DynamicAttribute", + act: utils.MetaDynamicAttribute, + want: utils.MetaAttributes, + }, + { + name: "DynamicResource", + act: utils.MetaDynamicResource, + want: utils.MetaResources, + }, + { + name: "DynamicTrend", + act: utils.MetaDynamicTrend, + want: utils.MetaTrends, + }, + { + name: "DynamicRanking", + act: utils.MetaDynamicRanking, + want: utils.MetaRankings, + }, + { + name: "DynamicFilter", + act: utils.MetaDynamicFilter, + want: utils.MetaFilters, + }, + { + name: "DynamicRoute", + act: utils.MetaDynamicRoute, + want: utils.MetaRoutes, + }, + { + name: "DynamicRate", + act: utils.MetaDynamicRate, + want: utils.MetaRates, + }, + { + name: "DynamicIP", + act: utils.MetaDynamicIP, + want: utils.MetaIPs, + }, + { + name: "DynamicAction", + act: utils.MetaDynamicAction, + want: utils.MetaActions, + }, + { + name: "UnknownAction", + act: "unknown", + want: utils.MetaNone, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := actionTarget(tt.act); got != tt.want { + t.Errorf("actionTarget(%q) = %q, want %q", tt.act, got, tt.want) + } + }) + } +} + +func TestNewActioner(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + data, _ := engine.NewInternalDB(nil, nil, nil, cfg.DataDbCfg().Items) + dm := engine.NewDataManager(data, cfg, nil) + fltr := engine.NewFilterS(cfg, nil, nil) + + ctx := context.Background() + cgrEv := new(utils.CGREvent) + connMgr := new(engine.ConnManager) + tnt := utils.CGRateSorg + + tests := []struct { + name string + aCfg *utils.APAction + wantErr string + }{ + { + name: "UnsupportedAction", + aCfg: &utils.APAction{Type: "not_a_type"}, + wantErr: "unsupported action type: ", + }, + { + name: "MetaLog", + aCfg: &utils.APAction{Type: utils.MetaLog}, + }, + { + name: "CDRLog", + aCfg: &utils.APAction{Type: utils.CDRLog}, + }, + { + name: "MetaHTTPPost", + aCfg: &utils.APAction{Type: utils.MetaHTTPPost}, + }, + { + name: "MetaExport", + aCfg: &utils.APAction{Type: utils.MetaExport}, + }, + { + name: "MetaResetStatQueue", + aCfg: &utils.APAction{Type: utils.MetaResetStatQueue}, + }, + { + name: "MetaResetThreshold", + aCfg: &utils.APAction{Type: utils.MetaResetThreshold}, + }, + { + name: "MetaAddBalance", + aCfg: &utils.APAction{Type: utils.MetaAddBalance}, + }, + { + name: "MetaSetBalance", + aCfg: &utils.APAction{Type: utils.MetaSetBalance}, + }, + { + name: "MetaRemBalance", + aCfg: &utils.APAction{Type: utils.MetaRemBalance}, + }, + { + name: "MetaDynamicThreshold", + aCfg: &utils.APAction{Type: utils.MetaDynamicThreshold}, + }, + { + name: "MetaDynamicStats", + aCfg: &utils.APAction{Type: utils.MetaDynamicStats}, + }, + { + name: "MetaDynamicAttribute", + aCfg: &utils.APAction{Type: utils.MetaDynamicAttribute}, + }, + { + name: "MetaDynamicResource", + aCfg: &utils.APAction{Type: utils.MetaDynamicResource}, + }, + { + name: "MetaDynamicTrend", + aCfg: &utils.APAction{Type: utils.MetaDynamicTrend}, + }, + { + name: "MetaDynamicRanking", + aCfg: &utils.APAction{Type: utils.MetaDynamicRanking}, + }, + { + name: "MetaDynamicFilter", + aCfg: &utils.APAction{Type: utils.MetaDynamicFilter}, + }, + { + name: "MetaDynamicRoute", + aCfg: &utils.APAction{Type: utils.MetaDynamicRoute}, + }, + { + name: "MetaDynamicRate", + aCfg: &utils.APAction{Type: utils.MetaDynamicRate}, + }, + { + name: "MetaDynamicIP", + aCfg: &utils.APAction{Type: utils.MetaDynamicIP}, + }, + { + name: "MetaDynamicAction", + aCfg: &utils.APAction{Type: utils.MetaDynamicAction}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + act, err := newActioner(ctx, cgrEv, cfg, fltr, dm, connMgr, tt.aCfg, tnt) + if tt.wantErr != "" { + if err == nil || err.Error() != tt.wantErr { + t.Errorf("Expected error: %q, got: %v", tt.wantErr, err) + } + return + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if act == nil { + t.Errorf("Expected non-nil action, got nil") + } + }) + } +}