diff --git a/engine/action.go b/engine/action.go index ff085e574..e8546b942 100644 --- a/engine/action.go +++ b/engine/action.go @@ -1801,8 +1801,12 @@ func dynamicAttribute(_ *Account, act *Action, _ Actions, _ *FilterS, ev any, if err != nil { return err } + var attrFltrIDs []string + if params[5] != utils.EmptyString { + attrFltrIDs = strings.Split(params[5], utils.ANDSep) + } attrP.Attributes = append(attrP.Attributes, &Attribute{ - FilterIDs: strings.Split(params[5], utils.ANDSep), + FilterIDs: attrFltrIDs, Path: params[6], Type: params[7], Value: value, @@ -1879,7 +1883,7 @@ func dynamicActionPlan(_ *Account, act *Action, _ Actions, _ *FilterS, ev any, } // Make sure ActionsId exists in DataDB var actsRply []*utils.TPAction - if err := connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1GetActions, params[1], &actsRply); err != nil { + if err := connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1GetActions, utils.StringPointer(params[1]), &actsRply); err != nil { return err } ap.ActionPlan = append(ap.ActionPlan, &AttrActionPlan{}) diff --git a/general_tests/dynamic_thresholds_it_test.go b/general_tests/dynamic_thresholds_it_test.go index 07afe3443..9b7615341 100644 --- a/general_tests/dynamic_thresholds_it_test.go +++ b/general_tests/dynamic_thresholds_it_test.go @@ -21,6 +21,7 @@ along with this program. If not, see package general_tests import ( + "os" "path" "reflect" "testing" @@ -28,6 +29,8 @@ import ( "github.com/cgrates/birpc" "github.com/cgrates/birpc/context" + v1 "github.com/cgrates/cgrates/apier/v1" + v2 "github.com/cgrates/cgrates/apier/v2" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" @@ -43,10 +46,17 @@ var ( sTestsDynThd = []func(t *testing.T){ testDynThdLoadConfig, testDynThdInitDataDb, - testDynThdResetStorDb, + testDynThdInitStorDb, testDynThdStartEngine, testDynThdRpcConn, + testDynThdLoadDefaultTimings, testDynThdCheckForThresholdProfile, + testDynThdCheckForStatsProfile, + testDynThdCheckForAttributeProfile, + testDynThdCheckForActionPlan, + testDynThdCheckForAction, + testDynThdCheckForDestination, + testDynThdSetLogAction, testDynThdSetAction, testDynThdSetThresholdProfile, testDynThdGetThresholdBeforeDebit, @@ -58,6 +68,11 @@ var ( testDynThdGetAccountAfterDebit, testDynThdGetThresholdAfterDebit, testDynThdCheckForDynCreatedThresholdProfile, + testDynThdCheckForDynCreatedStatQueueProfile, + testDynThdCheckForDynCreatedAttributeProfile, + testDynThdCheckForDynCreatedActionPlan, + testDynThdCheckForDynCreatedAction, + testDynThdCheckForDynCreatedDestination, testDynThdStopEngine, } ) @@ -97,7 +112,7 @@ func testDynThdInitDataDb(t *testing.T) { } } -func testDynThdResetStorDb(t *testing.T) { +func testDynThdInitStorDb(t *testing.T) { if err := engine.InitStorDb(dynThdCfg); err != nil { t.Fatal(err) } @@ -113,22 +128,114 @@ func testDynThdRpcConn(t *testing.T) { dynThdRpc = engine.NewRPCClient(t, dynThdCfg.ListenCfg()) } +func testDynThdLoadDefaultTimings(t *testing.T) { + emptyFolderPath := "/tmp/tmpFldr" + if err := os.MkdirAll(emptyFolderPath, 0777); err != nil { + t.Fatal(err) + } + var reply string + attrs := &utils.AttrLoadTpFromFolder{FolderPath: emptyFolderPath} + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1LoadTariffPlanFromFolder, attrs, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error(reply) + } +} + func testDynThdCheckForThresholdProfile(t *testing.T) { var rply *engine.ThresholdProfile - if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetThresholdProfile, &utils.TenantID{Tenant: "cgrates.org", ID: "DYNAMICLY_CREATED_THRESHOLD"}, &rply); err == nil || err.Error() != utils.ErrNotFound.Error() { + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetThresholdProfile, &utils.TenantID{Tenant: "cgrates.org", ID: "DYNAMICLY_THR_1002"}, &rply); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Error(err) } } +func testDynThdCheckForStatsProfile(t *testing.T) { + var rply *engine.StatQueueProfile + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetStatQueueProfile, &utils.TenantID{Tenant: "cgrates.org", ID: "DYNAMICLY_STAT_1002"}, &rply); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } +} + +func testDynThdCheckForAttributeProfile(t *testing.T) { + var rply *engine.StatQueueProfile + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetAttributeProfile, &utils.TenantID{Tenant: "cgrates.org", ID: "DYNAMICLY_ATTR_1002"}, &rply); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } +} + +func testDynThdCheckForActionPlan(t *testing.T) { + var rply *engine.ActionPlan + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetActionPlan, &v1.AttrGetActionPlan{ID: "DYNAMICLY_ACT_PLN_1002"}, &rply); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } +} + +func testDynThdCheckForAction(t *testing.T) { + var rply map[string]engine.Actions + if err := dynThdRpc.Call(context.Background(), utils.APIerSv2GetActions, &v2.AttrGetActions{ActionIDs: []string{"DYNAMICLY_ACT_1002"}}, &rply); err == nil || err.Error() != utils.NewErrServerError(utils.ErrNotFound).Error() { + t.Error(err) + } +} + +func testDynThdCheckForDestination(t *testing.T) { + var rply *engine.Destination + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetDestination, "1005", &rply); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } +} + +func testDynThdSetLogAction(t *testing.T) { + var reply string + + act := &utils.AttrSetActions{ + ActionsId: "LOG_WARNING_1002", + Actions: []*utils.TPAction{ + { + Identifier: utils.MetaLog, + BalanceBlocker: "false", + BalanceDisabled: "false", + Weight: 10, + }, + }} + if err := dynThdRpc.Call(context.Background(), utils.APIerSv2SetActions, + act, &reply); err != nil { + t.Error("Got error on APIerSv2.SetActions: ", err.Error()) + } else if reply != utils.OK { + t.Errorf("Calling APIerSv2.SetActions received: %s", reply) + } +} + func testDynThdSetAction(t *testing.T) { var reply string act := &utils.AttrSetActions{ ActionsId: "DYNAMIC_THRESHOLD_ACTION", - Actions: []*utils.TPAction{{ - Identifier: utils.MetaDynamicThreshold, - ExtraParameters: "cgrates.org;DYNAMICLY_THR_<~*req.ID>;*string:~*opts.*eventType:AccountUpdate;;1;;;true;10;;true;;~*opts", - }}} + Actions: []*utils.TPAction{ + { + Identifier: utils.MetaDynamicThreshold, + ExtraParameters: "cgrates.org;DYNAMICLY_THR_<~*req.ID>;*string:~*opts.*eventType:AccountUpdate;;1;;;true;10;;true;;~*opts", + }, + { + Identifier: utils.MetaDynamicStats, + ExtraParameters: "*tenant;DYNAMICLY_STAT_<~*req.ID>;*string:~*opts.*eventType:AccountUpdate;*now;100;10s;0;*acd&*tcd&*asr;;false;true;30;*none;~*opts", + }, + { + Identifier: utils.MetaDynamicAttribute, + ExtraParameters: "*tenant;DYNAMICLY_ATTR_<~*req.ID>;*any;*string:~*opts.*eventType:AccountUpdate;*now;;*req.Subject;*constant;SUPPLIER1;true;10;~*opts", + }, + { + Identifier: utils.MetaDynamicActionPlan, + ExtraParameters: "DYNAMICLY_ACT_PLN_<~*req.ID>;LOG_WARNING_<~*req.ID>;*asap;10;", + }, + { + Identifier: utils.MetaDynamicAction, + ExtraParameters: "DYNAMICLY_ACT_<~*req.ID>;*cdrlog;\f{\"Account\":\"<~*req.ID>\",\"RequestType\":\"*pseudoprepaid\",\"Subject\":\"DifferentThanAccount\", \"ToR\":\"~ActionType:s/^\\*(.*)$/did_$1/\"}\f;*string:~*req.Account:<~*req.ID>&filter2;balID;*monetary;call&data;1002&1003;SPECIAL_1002;SHARED_A&SHARED_B;*unlimited;*daily;10;10;true;false;10", + }, + { + Identifier: utils.MetaDynamicDestination, + ExtraParameters: "DST_1005;1005", + }, + }} if err := dynThdRpc.Call(context.Background(), utils.APIerSv2SetActions, act, &reply); err != nil { t.Error("Got error on APIerSv2.SetActions: ", err.Error()) @@ -317,6 +424,223 @@ func testDynThdCheckForDynCreatedThresholdProfile(t *testing.T) { } } +func testDynThdCheckForDynCreatedStatQueueProfile(t *testing.T) { + time.Sleep(50 * time.Millisecond) + args := &utils.TenantID{ + Tenant: "cgrates.org", + ID: "DYNAMICLY_STAT_1002", + } + var result1 *engine.StatQueueProfile + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetStatQueueProfile, args, &result1); err != nil { + t.Fatal(err) + } + exp := &engine.StatQueueProfile{ + Tenant: "cgrates.org", + ID: "DYNAMICLY_STAT_1002", + FilterIDs: []string{"*string:~*opts.*eventType:AccountUpdate"}, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: result1.ActivationInterval.ActivationTime, + }, + QueueLength: 100, + TTL: 10000000000, + MinItems: 0, + Metrics: []*engine.MetricWithFilters{ + { + MetricID: "*acd", + }, + { + MetricID: "*tcd", + }, + { + MetricID: "*asr", + }, + }, + Stored: false, + Blocker: true, + Weight: 30, + ThresholdIDs: []string{utils.MetaNone}, + } + if !reflect.DeepEqual(result1, exp) { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(result1)) + } +} + +func testDynThdCheckForDynCreatedAttributeProfile(t *testing.T) { + time.Sleep(50 * time.Millisecond) + args := &utils.TenantID{ + Tenant: "cgrates.org", + ID: "DYNAMICLY_ATTR_1002", + } + var result1 *engine.AttributeProfile + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetAttributeProfile, args, &result1); err != nil { + t.Fatal(err) + } + exp := &engine.AttributeProfile{ + Tenant: "cgrates.org", + ID: "DYNAMICLY_ATTR_1002", + FilterIDs: []string{"*string:~*opts.*eventType:AccountUpdate"}, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: result1.ActivationInterval.ActivationTime, + }, + Contexts: []string{utils.MetaAny}, + Attributes: []*engine.Attribute{ + { + Path: "*req.Subject", + Type: utils.MetaConstant, + Value: config.NewRSRParsersMustCompile("SUPPLIER1", "&"), + }, + }, + Blocker: true, + Weight: 10, + } + if !reflect.DeepEqual(utils.ToJSON(result1), utils.ToJSON(exp)) { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(result1)) + } +} + +func testDynThdCheckForDynCreatedActionPlan(t *testing.T) { + time.Sleep(50 * time.Millisecond) + args := &v1.AttrGetActionPlan{ID: "DYNAMICLY_ACT_PLN_1002"} + var result1 []*engine.ActionPlan + if err := dynThdRpc.Call(context.Background(), utils.APIerSv1GetActionPlan, args, &result1); err != nil { + t.Fatal(err) + } + exp := &[]*engine.ActionPlan{ + { + Id: "DYNAMICLY_ACT_PLN_1002", + AccountIDs: nil, + ActionTimings: []*engine.ActionTiming{ + { + Uuid: result1[0].ActionTimings[0].Uuid, + Timing: &engine.RateInterval{ + Timing: &engine.RITiming{ + ID: utils.MetaASAP, + Years: utils.Years{}, + Months: utils.Months{}, + MonthDays: utils.MonthDays{}, + WeekDays: utils.WeekDays{}, + StartTime: utils.MetaASAP, + EndTime: utils.EmptyString, + }, + Rating: nil, + Weight: 0, + }, + ActionsID: "LOG_WARNING_1002", + ExtraData: nil, + Weight: 10, + }, + }, + }, + } + if !reflect.DeepEqual(utils.ToJSON(exp), utils.ToJSON(result1)) { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(result1)) + } +} + +func testDynThdCheckForDynCreatedAction(t *testing.T) { + time.Sleep(50 * time.Millisecond) + args := &v2.AttrGetActions{ActionIDs: []string{"DYNAMICLY_ACT_1002"}} + result1 := make(map[string]engine.Actions) + if err := dynThdRpc.Call(context.Background(), utils.APIerSv2GetActions, args, &result1); err != nil { + t.Fatal(err) + } + exp := map[string]engine.Actions{ + "DYNAMICLY_ACT_1002": { + { + Id: "DYNAMICLY_ACT_1002", + ActionType: utils.CDRLog, + ExtraParameters: "{\"Account\":\"1002\",\"RequestType\":\"*pseudoprepaid\",\"Subject\":\"DifferentThanAccount\", \"ToR\":\"~ActionType:s/^\\*(.*)$/did_$1/\"}", + Filters: []string{"*string:~*req.Account:1002", "filter2"}, + ExpirationString: utils.MetaUnlimited, + Weight: 10, + Balance: &engine.BalanceFilter{ + Uuid: result1["DYNAMICLY_ACT_1002"][0].Balance.Uuid, + ID: utils.StringPointer("balID"), + Type: utils.StringPointer(utils.MetaMonetary), + Value: &utils.ValueFormula{ + Method: utils.EmptyString, + Params: nil, + Static: 10, + }, + ExpirationDate: nil, + Weight: utils.Float64Pointer(10), + DestinationIDs: &utils.StringMap{"1002": true, "1003": true}, + RatingSubject: utils.StringPointer("SPECIAL_1002"), + Categories: &utils.StringMap{"call": true, "data": true}, + SharedGroups: &utils.StringMap{"SHARED_A": true, "SHARED_B": true}, + TimingIDs: &utils.StringMap{utils.MetaDaily: true}, + Timings: []*engine.RITiming{{ + ID: utils.MetaDaily, + Years: utils.Years{}, + Months: utils.Months{}, + MonthDays: utils.MonthDays{}, + WeekDays: utils.WeekDays{}, + StartTime: result1["DYNAMICLY_ACT_1002"][0].Balance.Timings[0].StartTime, //depends on time it ran + }}, + Disabled: utils.BoolPointer(false), + Factors: nil, + Blocker: utils.BoolPointer(true), + }, + }, + }, + } + if !reflect.DeepEqual(exp, result1) { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(result1)) + } +} + +func testDynThdCheckForDynCreatedDestination(t *testing.T) { + time.Sleep(50 * time.Millisecond) + args := &v2.AttrGetActions{ActionIDs: []string{"DYNAMICLY_DST_1005"}} + result1 := make(map[string]engine.Actions) + if err := dynThdRpc.Call(context.Background(), utils.APIerSv2GetActions, args, &result1); err != nil { + t.Fatal(err) + } + exp := map[string]engine.Actions{ + "DYNAMICLY_ACT_1002": { + { + Id: "DYNAMICLY_ACT_1002", + ActionType: utils.CDRLog, + ExtraParameters: "{\"Account\":\"1002\",\"RequestType\":\"*pseudoprepaid\",\"Subject\":\"DifferentThanAccount\", \"ToR\":\"~ActionType:s/^\\*(.*)$/did_$1/\"}", + Filters: []string{"*string:~*req.Account:1002", "filter2"}, + ExpirationString: utils.MetaUnlimited, + Weight: 10, + Balance: &engine.BalanceFilter{ + Uuid: result1["DYNAMICLY_ACT_1002"][0].Balance.Uuid, + ID: utils.StringPointer("balID"), + Type: utils.StringPointer(utils.MetaMonetary), + Value: &utils.ValueFormula{ + Method: utils.EmptyString, + Params: nil, + Static: 10, + }, + ExpirationDate: nil, + Weight: utils.Float64Pointer(10), + DestinationIDs: &utils.StringMap{"1002": true, "1003": true}, + RatingSubject: utils.StringPointer("SPECIAL_1002"), + Categories: &utils.StringMap{"call": true, "data": true}, + SharedGroups: &utils.StringMap{"SHARED_A": true, "SHARED_B": true}, + TimingIDs: &utils.StringMap{utils.MetaDaily: true}, + Timings: []*engine.RITiming{{ + ID: utils.MetaDaily, + Years: utils.Years{}, + Months: utils.Months{}, + MonthDays: utils.MonthDays{}, + WeekDays: utils.WeekDays{}, + StartTime: result1["DYNAMICLY_ACT_1002"][0].Balance.Timings[0].StartTime, //depends on time it ran + }}, + Disabled: utils.BoolPointer(false), + Factors: nil, + Blocker: utils.BoolPointer(true), + }, + }, + }, + } + if !reflect.DeepEqual(exp, result1) { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(result1)) + } +} + func testDynThdStopEngine(t *testing.T) { if err := engine.KillEngine(dynThdDelay); err != nil { t.Error(err)