mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Add action type *dynamic_action_plan_accounts and tests for it
This commit is contained in:
committed by
Dan Christian Bogos
parent
2d94ca89bb
commit
801631196d
100
engine/action.go
100
engine/action.go
@@ -128,8 +128,9 @@ func newActionConnCfg(source, action string, cfg *config.CGRConfig) ActionConnCf
|
||||
dynamicActions := []string{
|
||||
utils.MetaDynamicThreshold, utils.MetaDynamicStats,
|
||||
utils.MetaDynamicAttribute, utils.MetaDynamicActionPlan,
|
||||
utils.MetaDynamicAction, utils.MetaDynamicDestination,
|
||||
utils.MetaDynamicFilter, utils.MetaDynamicRoute,
|
||||
utils.MetaDynamicActionPlanAccounts, utils.MetaDynamicAction,
|
||||
utils.MetaDynamicDestination, utils.MetaDynamicFilter,
|
||||
utils.MetaDynamicRoute,
|
||||
}
|
||||
act := ActionConnCfg{}
|
||||
switch source {
|
||||
@@ -193,6 +194,7 @@ func init() {
|
||||
actionFuncMap[utils.MetaDynamicStats] = dynamicStats
|
||||
actionFuncMap[utils.MetaDynamicAttribute] = dynamicAttribute
|
||||
actionFuncMap[utils.MetaDynamicActionPlan] = dynamicActionPlan
|
||||
actionFuncMap[utils.MetaDynamicActionPlanAccounts] = dynamicActionPlanAccount
|
||||
actionFuncMap[utils.MetaDynamicAction] = dynamicAction
|
||||
actionFuncMap[utils.MetaDynamicDestination] = dynamicDestination
|
||||
actionFuncMap[utils.MetaDynamicFilter] = dynamicFilter
|
||||
@@ -1462,7 +1464,7 @@ func remoteSetAccount(ub *Account, a *Action, _ Actions, _ *FilterS, _ any, _ Sh
|
||||
// 9 ActionIDs: strings separated by "&".
|
||||
// 10 Async: bool
|
||||
// 11 EeIDs: strings separated by "&".
|
||||
// 11 APIOpts: set of key-value pairs (separated by "&").
|
||||
// 12 APIOpts: set of key-value pairs (separated by "&").
|
||||
//
|
||||
// Parameters are separated by ";" and must be provided in the specified order.
|
||||
func dynamicThreshold(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
@@ -1925,6 +1927,98 @@ func dynamicActionPlan(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetActionPlan, ap, &reply)
|
||||
}
|
||||
|
||||
// dynamicActionPlanAccount processes the `ExtraParameters` field from the action to construct an ActionPlan with account ids
|
||||
//
|
||||
// The ExtraParameters field format is expected as follows:
|
||||
//
|
||||
// 0 Id: string
|
||||
// 1 ActionsId: string
|
||||
// 2 TimingId: string
|
||||
// 3 Weight: float
|
||||
// 4 Overwrite: bool
|
||||
// 5 Tenant:AccountIDs: strings separated by "&".
|
||||
//
|
||||
// Parameters are separated by ";" and must be provided in the specified order.
|
||||
func dynamicActionPlanAccount(_ *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) != 6 {
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 6", 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.
|
||||
ap := &AttrSetActionPlanAccounts{
|
||||
Id: params[0],
|
||||
ReloadScheduler: true,
|
||||
}
|
||||
// populate ActionPlan's ActionsId
|
||||
if params[1] == utils.EmptyString {
|
||||
return fmt.Errorf("empty ActionsId for <%s> dynamic_action_plan", params[0])
|
||||
}
|
||||
// Make sure ActionsId exists in DataDB
|
||||
var actsRply []*utils.TPAction
|
||||
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{})
|
||||
ap.ActionPlan[0].ActionsId = params[1]
|
||||
if params[2] != utils.EmptyString {
|
||||
// Make sure TimingID exists in DataDB and use it for the action plan
|
||||
var tpTiming utils.TPTiming
|
||||
if err := connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1GetTiming, &utils.ArgsGetTimingID{ID: params[2]}, &tpTiming); err != nil {
|
||||
return err
|
||||
}
|
||||
ap.ActionPlan[0].TimingID = tpTiming.ID
|
||||
ap.ActionPlan[0].Years = tpTiming.Years.Serialize(";")
|
||||
ap.ActionPlan[0].Months = tpTiming.Months.Serialize(";")
|
||||
ap.ActionPlan[0].MonthDays = tpTiming.MonthDays.Serialize(";")
|
||||
ap.ActionPlan[0].WeekDays = tpTiming.WeekDays.Serialize(";")
|
||||
if tpTiming.EndTime != utils.EmptyString {
|
||||
ap.ActionPlan[0].Time = utils.InfieldJoin(tpTiming.StartTime, tpTiming.EndTime)
|
||||
} else {
|
||||
ap.ActionPlan[0].Time = tpTiming.StartTime
|
||||
}
|
||||
}
|
||||
// populate ActionPlan's Weight
|
||||
if params[3] != utils.EmptyString {
|
||||
ap.ActionPlan[0].Weight, err = strconv.ParseFloat(params[3], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate ActionPlan's Overwrite
|
||||
if params[4] != utils.EmptyString {
|
||||
ap.Overwrite, err = strconv.ParseBool(params[4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate ActionPlan's AccountIDs
|
||||
if params[5] != utils.EmptyString {
|
||||
ap.AccountIDs = strings.Split(params[5], utils.ANDSep)
|
||||
}
|
||||
|
||||
// create the ActionPlan based on the populated parameters
|
||||
var reply string
|
||||
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetActionPlanAccounts, ap, &reply)
|
||||
}
|
||||
|
||||
// dynamicAction processes the `ExtraParameters` field from the action to construct a new Action
|
||||
//
|
||||
// The ExtraParameters field format is expected as follows:
|
||||
|
||||
@@ -390,6 +390,14 @@ func (atpl ActionTimingWeightOnlyPriorityList) Sort() {
|
||||
sort.Sort(atpl)
|
||||
}
|
||||
|
||||
type AttrSetActionPlanAccounts struct {
|
||||
Id string // Profile id
|
||||
ActionPlan []*AttrActionPlan // Set of actions this Actions profile will perform
|
||||
AccountIDs []string // Set of accounts which the actions can be performed on
|
||||
Overwrite bool // If previously defined, will be overwritten
|
||||
ReloadScheduler bool // Enables automatic reload of the scheduler (eg: useful when adding a single action timing)
|
||||
}
|
||||
|
||||
type AttrSetActionPlan struct {
|
||||
Id string // Profile id
|
||||
ActionPlan []*AttrActionPlan // Set of actions this Actions profile will perform
|
||||
|
||||
@@ -5791,6 +5791,195 @@ func TestDynamicActionPlan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicActionPlanAccount(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 ap *AttrSetActionPlanAccounts
|
||||
ccMock := &ccMock{
|
||||
calls: map[string]func(ctx *context.Context, args any, reply any) error{
|
||||
utils.APIerSv1SetActionPlanAccounts: func(ctx *context.Context, args, reply any) error {
|
||||
var canCast bool
|
||||
if ap, canCast = args.(*AttrSetActionPlanAccounts); !canCast {
|
||||
return fmt.Errorf("couldnt cast AttrSetActionPlanAccounts")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
utils.APIerSv1GetActions: func(ctx *context.Context, args2, reply any) error {
|
||||
return nil
|
||||
},
|
||||
utils.APIerSv1GetTiming: func(ctx *context.Context, args3, reply any) error {
|
||||
var canCast bool
|
||||
if args3, canCast = args3.(*utils.ArgsGetTimingID); !canCast {
|
||||
return fmt.Errorf("couldnt cast ArgsGetTimingID")
|
||||
}
|
||||
var exp *utils.TPTiming
|
||||
if utils.ToJSON(args3) == utils.ToJSON(&utils.ArgsGetTimingID{ID: "Timing_1"}) {
|
||||
exp = &utils.TPTiming{
|
||||
ID: "Timing_1",
|
||||
Years: utils.Years{2025},
|
||||
Months: utils.Months{2},
|
||||
MonthDays: utils.MonthDays{3},
|
||||
WeekDays: utils.WeekDays{1, 2, 3},
|
||||
StartTime: "00:12:12",
|
||||
EndTime: "12:12:12",
|
||||
}
|
||||
}
|
||||
if utils.ToJSON(args3) == utils.ToJSON(&utils.ArgsGetTimingID{ID: "*asap"}) {
|
||||
exp = &utils.TPTiming{
|
||||
ID: "*asap",
|
||||
Years: utils.Years{2025},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: "*asap",
|
||||
}
|
||||
}
|
||||
if exp == nil {
|
||||
return utils.ErrNotFound
|
||||
}
|
||||
*reply.(*utils.TPTiming) = *exp
|
||||
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
|
||||
expAp *AttrSetActionPlanAccounts
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "SuccessfulRequest",
|
||||
connIDs: []string{connID},
|
||||
expAp: &AttrSetActionPlanAccounts{
|
||||
Id: "ActPl_1",
|
||||
ActionPlan: []*AttrActionPlan{
|
||||
{
|
||||
ActionsId: "Action_1",
|
||||
TimingID: "Timing_1",
|
||||
Years: "2025",
|
||||
Months: "2",
|
||||
MonthDays: "3",
|
||||
WeekDays: "1;2;3",
|
||||
Time: "00:12:12;12:12:12",
|
||||
Weight: 10,
|
||||
},
|
||||
},
|
||||
AccountIDs: []string{"cgrates.org:1001"},
|
||||
Overwrite: true,
|
||||
ReloadScheduler: true,
|
||||
},
|
||||
extraParams: "ActPl_1;Action_1;Timing_1;10;true;cgrates.org:1001",
|
||||
},
|
||||
{
|
||||
name: "SuccessfulRequestWithDynamicPaths",
|
||||
connIDs: []string{connID},
|
||||
expAp: &AttrSetActionPlanAccounts{
|
||||
Id: "ActPl_1",
|
||||
ActionPlan: []*AttrActionPlan{
|
||||
{
|
||||
ActionsId: "Action_1001",
|
||||
TimingID: "*asap",
|
||||
Years: "2025",
|
||||
Months: "*any",
|
||||
MonthDays: "*any",
|
||||
WeekDays: "*any",
|
||||
Time: "*asap",
|
||||
Weight: 10,
|
||||
},
|
||||
},
|
||||
AccountIDs: []string{"cgrates.org:1001"},
|
||||
Overwrite: false,
|
||||
ReloadScheduler: true,
|
||||
},
|
||||
extraParams: "ActPl_1;Action_<~*req.Account>;*asap;10;;<*tenant+:+~*req.Account>",
|
||||
},
|
||||
{
|
||||
name: "SuccessfulRequestEmptyFields",
|
||||
connIDs: []string{connID},
|
||||
expAp: &AttrSetActionPlanAccounts{
|
||||
Id: "ActPl_1",
|
||||
ActionPlan: []*AttrActionPlan{
|
||||
{
|
||||
ActionsId: "Action_1",
|
||||
TimingID: "",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
Overwrite: false,
|
||||
ReloadScheduler: true,
|
||||
},
|
||||
extraParams: "ActPl_1;Action_1;;;;",
|
||||
},
|
||||
{
|
||||
name: "MissingConns",
|
||||
extraParams: "ActPl_1;Action_1;*asap;10;;",
|
||||
expectedErr: "MANDATORY_IE_MISSING: [connIDs]",
|
||||
},
|
||||
{
|
||||
name: "WrongNumberOfParams",
|
||||
extraParams: "ActPl_1;Action_1;*asap;10;;;",
|
||||
expectedErr: "invalid number of parameters <7> expected 6",
|
||||
},
|
||||
{
|
||||
name: "ActionIdEmptyFail",
|
||||
extraParams: "ActPl_1;;*asap;10;;",
|
||||
expectedErr: `empty ActionsId for <ActPl_1> dynamic_action_plan`,
|
||||
},
|
||||
{
|
||||
name: "WeightFail",
|
||||
connIDs: []string{connID},
|
||||
extraParams: "ActPl_1;Action_1;*asap;BadString;;",
|
||||
expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, 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() {
|
||||
ap = nil
|
||||
})
|
||||
err := dynamicActionPlanAccount(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 utils.ToJSON(ap) != utils.ToJSON(tc.expAp) {
|
||||
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAp), utils.ToJSON(ap))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicActionAll(t *testing.T) {
|
||||
tempConn := connMgr
|
||||
tmpDm := dm
|
||||
|
||||
Reference in New Issue
Block a user