Add action type *dynamic_action_trigger

This commit is contained in:
arberkatellari
2025-06-12 17:31:30 +02:00
committed by Dan Christian Bogos
parent 0e15a7826f
commit 990f001465
12 changed files with 601 additions and 162 deletions

View File

@@ -131,7 +131,7 @@ func newActionConnCfg(source, action string, cfg *config.CGRConfig) ActionConnCf
utils.MetaDynamicActionPlanAccounts, utils.MetaDynamicAction,
utils.MetaDynamicDestination, utils.MetaDynamicFilter,
utils.MetaDynamicRoute, utils.MetaDynamicRatingProfile,
utils.MetaDynamicResource,
utils.MetaDynamicResource, utils.MetaDynamicActionTrigger,
}
act := ActionConnCfg{}
switch source {
@@ -204,6 +204,7 @@ func init() {
actionFuncMap[utils.MetaDynamicRatingProfile] = dynamicRatingProfile
actionFuncMap[utils.MetaDynamicRanking] = dynamicTrend
actionFuncMap[utils.MetaDynamicResource] = dynamicResource
actionFuncMap[utils.MetaDynamicActionTrigger] = dynamicActionTrigger
}
func getActionFunc(typ string) (f actionTypeFunc, exists bool) {
@@ -2781,3 +2782,172 @@ func dynamicResource(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
var reply string
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetResourceProfile, rsc, &reply)
}
// dynamicActionTrigger processes the `ExtraParameters` field from the action to
// construct a ActionTrigger
//
// The ExtraParameters field format is expected as follows:
//
// 0 Tag: string
// 1 UniqueId: string
// 2 ThresholdType: string
// 3 ThresholdValue: float
// 4 Recurrent: bool
// 5 MinSleep: duration
// 6 ExpiryTime: time
// 7 ActivationTime: time
// 8 BalanceTag: string
// 9 BalanceType: string
// 10 BalanceCategories: strings separated by "&".
// 11 BalanceDestinationIds: strings separated by "&".
// 12 BalanceRatingSubject: string
// 13 BalanceSharedGroup: strings separated by "&".
// 14 BalanceExpiryTime: time
// 15 BalanceTimingIds: strings separated by "&".
// 16 BalanceWeight: float
// 17 BalanceBlocker: bool
// 18 BalanceDisabled: bool
// 19 ActionsId: string
// 20 Weight: float
//
// Parameters are separated by ";" and must be provided in the specified order.
func dynamicActionTrigger(_ *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) != 21 {
return fmt.Errorf("invalid number of parameters <%d> expected 21", 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.
at := &AttrSetActionTrigger{
GroupID: params[0],
UniqueID: utils.FirstNonEmpty(params[1], utils.GenUUID()),
ActionTrigger: make(map[string]any),
}
// populate ActionTrigger's ThresholdType
if params[2] != utils.EmptyString {
at.ActionTrigger[utils.ThresholdType] = params[2]
}
// populate ActionTrigger's ThresholdValue
if params[3] != utils.EmptyString {
at.ActionTrigger[utils.ThresholdValue], err = strconv.ParseFloat(params[3], 64)
if err != nil {
return err
}
}
// populate ActionTrigger's Recurrent
if params[4] != utils.EmptyString {
at.ActionTrigger[utils.Recurrent], err = strconv.ParseBool(params[4])
if err != nil {
return err
}
}
// populate ActionTrigger's MinSleep
if params[5] != utils.EmptyString {
at.ActionTrigger[utils.MinSleep], err = utils.ParseDurationWithNanosecs(params[5])
if err != nil {
return err
}
}
// populate ActionTrigger's ExpirationDate
if params[6] != utils.EmptyString {
at.ActionTrigger[utils.ExpirationDate], err = utils.ParseTimeDetectLayout(params[6], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
return err
}
}
// populate ActionTrigger's ActivationDate
if params[7] != utils.EmptyString {
at.ActionTrigger[utils.ActivationDate], err = utils.ParseTimeDetectLayout(params[7], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
return err
}
}
// populate ActionTrigger's BalanceID
if params[8] != utils.EmptyString {
at.ActionTrigger[utils.BalanceID] = params[8]
}
// populate ActionTrigger's BalanceType
if params[9] != utils.EmptyString {
at.ActionTrigger[utils.BalanceType] = params[9]
}
// populate ActionTrigger's BalanceCategories
if params[10] != utils.EmptyString {
at.ActionTrigger[utils.BalanceCategories] = strings.Split(params[10], utils.ANDSep)
}
// populate ActionTrigger's BalanceDestinationIds
if params[11] != utils.EmptyString {
at.ActionTrigger[utils.BalanceDestinationIds] = strings.Split(params[11], utils.ANDSep)
}
// populate ActionTrigger's BalanceRatingSubject
if params[12] != utils.EmptyString {
at.ActionTrigger[utils.BalanceRatingSubject] = params[12]
}
// populate ActionTrigger's BalanceSharedGroups
if params[13] != utils.EmptyString {
at.ActionTrigger[utils.BalanceSharedGroups] = strings.Split(params[13], utils.ANDSep)
}
// populate ActionTrigger's BalanceExpirationDate
if params[14] != utils.EmptyString {
at.ActionTrigger[utils.BalanceExpirationDate], err = utils.ParseTimeDetectLayout(params[14], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
return err
}
}
// populate ActionTrigger's BalanceTimingTags
if params[15] != utils.EmptyString {
at.ActionTrigger[utils.BalanceTimingTags] = strings.Split(params[15], utils.ANDSep)
}
// populate ActionTrigger's BalanceWeight
if params[16] != utils.EmptyString {
at.ActionTrigger[utils.BalanceWeight], err = strconv.ParseFloat(params[16], 64)
if err != nil {
return err
}
}
// populate ActionTrigger's BalanceBlocker
if params[17] != utils.EmptyString {
at.ActionTrigger[utils.BalanceBlocker], err = strconv.ParseBool(params[17])
if err != nil {
return err
}
}
// populate ActionTrigger's BalanceDisabled
if params[18] != utils.EmptyString {
at.ActionTrigger[utils.BalanceDisabled], err = strconv.ParseBool(params[18])
if err != nil {
return err
}
}
// populate ActionTrigger's ActionsID
if params[19] != utils.EmptyString {
at.ActionTrigger[utils.ActionsID] = params[19]
}
// populate ActionTrigger's Weight
if params[20] != utils.EmptyString {
at.ActionTrigger[utils.Weight], err = strconv.ParseFloat(params[20], 64)
if err != nil {
return err
}
}
// create the ActionTrigger based on the populated parameters
var reply string
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetActionTrigger, at, &reply)
}

View File

@@ -20,6 +20,7 @@ package engine
import (
"encoding/json"
"errors"
"fmt"
"sort"
"time"
@@ -314,3 +315,154 @@ func (at *ActionTrigger) FieldAsString(fldPath []string) (val string, err error)
}
return utils.IfaceAsString(iface), nil
}
type AttrSetActionTrigger struct {
GroupID string
UniqueID string
ActionTrigger map[string]any
}
// UpdateActionTrigger updates the ActionTrigger if is matching
func (attr *AttrSetActionTrigger) UpdateActionTrigger(at *ActionTrigger, timezone string) (updated bool, err error) {
if at == nil {
return false, errors.New("Empty ActionTrigger")
}
if at.ID == utils.EmptyString { // New AT, update it's data
if attr.GroupID == utils.EmptyString {
return false, utils.NewErrMandatoryIeMissing(utils.GroupID)
}
if missing := utils.MissingMapFields(attr.ActionTrigger, []string{"ThresholdType", "ThresholdValue"}); len(missing) != 0 {
return false, utils.NewErrMandatoryIeMissing(missing...)
}
at.ID = attr.GroupID
if attr.UniqueID != utils.EmptyString {
at.UniqueID = attr.UniqueID
}
}
if attr.GroupID != utils.EmptyString && attr.GroupID != at.ID {
return
}
if attr.UniqueID != utils.EmptyString && attr.UniqueID != at.UniqueID {
return
}
// at matches
updated = true
if thr, has := attr.ActionTrigger[utils.ThresholdType]; has {
at.ThresholdType = utils.IfaceAsString(thr)
}
if thr, has := attr.ActionTrigger[utils.ThresholdValue]; has {
if at.ThresholdValue, err = utils.IfaceAsFloat64(thr); err != nil {
return
}
}
if rec, has := attr.ActionTrigger[utils.Recurrent]; has {
if at.Recurrent, err = utils.IfaceAsBool(rec); err != nil {
return
}
}
if exec, has := attr.ActionTrigger[utils.Executed]; has {
if at.Executed, err = utils.IfaceAsBool(exec); err != nil {
return
}
}
if minS, has := attr.ActionTrigger[utils.MinSleep]; has {
if at.MinSleep, err = utils.IfaceAsDuration(minS); err != nil {
return
}
}
if exp, has := attr.ActionTrigger[utils.ExpirationDate]; has {
if at.ExpirationDate, err = utils.IfaceAsTime(exp, timezone); err != nil {
return
}
}
if act, has := attr.ActionTrigger[utils.ActivationDate]; has {
if at.ActivationDate, err = utils.IfaceAsTime(act, timezone); err != nil {
return
}
}
if at.Balance == nil {
at.Balance = &BalanceFilter{}
}
if bid, has := attr.ActionTrigger[utils.BalanceID]; has {
at.Balance.ID = utils.StringPointer(utils.IfaceAsString(bid))
}
if btype, has := attr.ActionTrigger[utils.BalanceType]; has {
at.Balance.Type = utils.StringPointer(utils.IfaceAsString(btype))
}
if bdest, has := attr.ActionTrigger[utils.BalanceDestinationIds]; has {
var bdIds []string
if bdIds, err = utils.IfaceAsSliceString(bdest); err != nil {
return
}
at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(bdIds...))
}
if bweight, has := attr.ActionTrigger[utils.BalanceWeight]; has {
var bw float64
if bw, err = utils.IfaceAsFloat64(bweight); err != nil {
return
}
at.Balance.Weight = utils.Float64Pointer(bw)
}
if exp, has := attr.ActionTrigger[utils.BalanceExpirationDate]; has {
var balanceExpTime time.Time
if balanceExpTime, err = utils.IfaceAsTime(exp, timezone); err != nil {
return
}
at.Balance.ExpirationDate = utils.TimePointer(balanceExpTime)
}
if bTimeTag, has := attr.ActionTrigger[utils.BalanceTimingTags]; has {
var timeTag []string
if timeTag, err = utils.IfaceAsSliceString(bTimeTag); err != nil {
return
}
at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(timeTag...))
}
if brs, has := attr.ActionTrigger[utils.BalanceRatingSubject]; has {
at.Balance.RatingSubject = utils.StringPointer(utils.IfaceAsString(brs))
}
if bcat, has := attr.ActionTrigger[utils.BalanceCategories]; has {
var cat []string
if cat, err = utils.IfaceAsSliceString(bcat); err != nil {
return
}
at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(cat...))
}
if bsg, has := attr.ActionTrigger[utils.BalanceSharedGroups]; has {
var shrgrps []string
if shrgrps, err = utils.IfaceAsSliceString(bsg); err != nil {
return
}
at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(shrgrps...))
}
if bb, has := attr.ActionTrigger[utils.BalanceBlocker]; has {
var bBlocker bool
if bBlocker, err = utils.IfaceAsBool(bb); err != nil {
return
}
at.Balance.Blocker = utils.BoolPointer(bBlocker)
}
if bd, has := attr.ActionTrigger[utils.BalanceDisabled]; has {
var bDis bool
if bDis, err = utils.IfaceAsBool(bd); err != nil {
return
}
at.Balance.Disabled = utils.BoolPointer(bDis)
}
if minQ, has := attr.ActionTrigger[utils.MinQueuedItems]; has {
var mQ int64
if mQ, err = utils.IfaceAsTInt64(minQ); err != nil {
return
}
at.MinQueuedItems = int(mQ)
}
if accID, has := attr.ActionTrigger[utils.ActionsID]; has {
at.ActionsID = utils.IfaceAsString(accID)
}
if weight, has := attr.ActionTrigger[utils.Weight]; has {
if at.Weight, err = utils.IfaceAsFloat64(weight); err != nil {
return
}
}
return
}

View File

@@ -7334,3 +7334,264 @@ func TestDynamicResource(t *testing.T) {
})
}
}
func TestDynamicActionTrigger(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 at *AttrSetActionTrigger
ccMock := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.APIerSv1SetActionTrigger: func(ctx *context.Context, args, reply any) error {
var canCast bool
if at, canCast = args.(*AttrSetActionTrigger); !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
expAt *AttrSetActionTrigger
expectedErr string
}{
{
name: "SuccessfulRequest",
connIDs: []string{connID},
expAt: &AttrSetActionTrigger{
GroupID: "STANDARD_TRIGGERS",
UniqueID: "uid",
ActionTrigger: map[string]any{
utils.ThresholdType: "*max_balance",
utils.ThresholdValue: float64(20),
utils.Recurrent: true,
utils.MinSleep: time.Second,
utils.ActivationDate: time.Date(2014, 7, 29, 15, 0, 0, 0, time.UTC),
utils.BalanceID: "*default",
utils.BalanceType: "*monetary",
utils.BalanceCategories: []string{utils.Call, "data"},
utils.BalanceDestinationIds: []string{"DST1", "DST2"},
utils.BalanceRatingSubject: "SPECIAL_1002",
utils.BalanceSharedGroups: []string{"SHRGroup1", "SHRGroup2"},
utils.BalanceExpirationDate: time.Date(2030, 7, 29, 15, 0, 0, 0, time.UTC),
utils.BalanceTimingTags: []string{"*asap"},
utils.BalanceWeight: float64(10),
utils.BalanceBlocker: true,
utils.BalanceDisabled: true,
utils.ActionsID: "ACT_1001",
utils.Weight: float64(20),
},
},
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
},
{
name: "SuccessfulRequestWithDynamicPaths",
connIDs: []string{connID},
expAt: &AttrSetActionTrigger{
GroupID: "STANDARD_TRIGGERS_1001",
UniqueID: "uid_1001",
ActionTrigger: map[string]any{
utils.ThresholdType: "*max_balance",
utils.ThresholdValue: float64(20),
utils.Recurrent: true,
utils.MinSleep: time.Second,
utils.ExpirationDate: time.Now(),
utils.ActivationDate: time.Now(),
utils.BalanceID: "*default",
utils.BalanceType: "*monetary",
utils.BalanceCategories: []string{utils.Call, "data"},
utils.BalanceDestinationIds: []string{"DST_1001", "DST2"},
utils.BalanceRatingSubject: "SPECIAL_1001",
utils.BalanceSharedGroups: []string{"SHRGroup_1001", "SHRGroup2"},
utils.BalanceExpirationDate: time.Now(),
utils.BalanceTimingTags: []string{"*asap"},
utils.BalanceWeight: float64(10),
utils.BalanceBlocker: true,
utils.BalanceDisabled: true,
utils.ActionsID: "ACT_1001",
utils.Weight: float64(20),
},
},
extraParams: "STANDARD_TRIGGERS_<~*req.Account>;uid_<~*req.Account>;*max_balance;20;true;1s;*now;*now;*default;*monetary;call&data;DST_<~*req.Account>&DST2;SPECIAL_<~*req.Account>;SHRGroup_<~*req.Account>&SHRGroup2;*now;*asap;10;true;true;ACT_<~*req.Account>;20",
},
{
name: "SuccessfulRequestEmptyFields",
connIDs: []string{connID},
expAt: &AttrSetActionTrigger{
GroupID: "STANDARD_TRIGGERS",
UniqueID: "uid",
ActionTrigger: map[string]any{
utils.ThresholdType: "*max_balance",
utils.ThresholdValue: float64(20),
},
},
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;;;;;;;;;;;;;;;;;",
},
{
name: "MissingConns",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
expectedErr: "MANDATORY_IE_MISSING: [connIDs]",
},
{
name: "WrongNumberOfParams",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20",
expectedErr: "invalid number of parameters <4> expected 21",
},
{
name: "ThresholdValueFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;BadString;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
},
{
name: "RecurrentFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;BadString;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
},
{
name: "MinSleepFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;BadString;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
expectedErr: `time: invalid duration "BadString"`,
},
{
name: "ExpirationDateBadStringFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;bad String;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
expectedErr: `Unsupported time format`,
},
{
name: "ActivationDateBadStringFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;bad String;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;20",
expectedErr: `Unsupported time format`,
},
{
name: "BalanceExpirationDateBadStringFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;bad String;*asap;10;true;true;ACT_1001;20",
expectedErr: `Unsupported time format`,
},
{
name: "BalanceWeightFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;BadString;true;true;ACT_1001;20",
expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
},
{
name: "BalanceBlockerFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;BadString;true;ACT_1001;20",
expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
},
{
name: "BalanceDisabledFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;BadString;ACT_1001;20",
expectedErr: `strconv.ParseBool: parsing "BadString": invalid syntax`,
},
{
name: "WeightFail",
extraParams: "STANDARD_TRIGGERS;uid;*max_balance;20;true;1s;;2014-07-29T15:00:00Z;*default;*monetary;call&data;DST1&DST2;SPECIAL_1002;SHRGroup1&SHRGroup2;2030-07-29T15:00:00Z;*asap;10;true;true;ACT_1001;BadString",
expectedErr: `strconv.ParseFloat: parsing "BadString": invalid syntax`,
},
}
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() {
at = nil
})
err := dynamicActionTrigger(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(at, tc.expAt) {
if i != 1 {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
} else {
// Get the absolute difference between the times
rcvExpDate, err := utils.IfaceAsTime(at.ActionTrigger[utils.ExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
t.Fatal(err)
}
expExpDate, err := utils.IfaceAsTime(tc.expAt.ActionTrigger[utils.ExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
t.Fatal(err)
}
diff := rcvExpDate.Sub(expExpDate)
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 {
t.Fatalf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
}
tc.expAt.ActionTrigger[utils.ExpirationDate] = rcvExpDate
// Get the absolute difference between the times
rcvExpDate, err = utils.IfaceAsTime(at.ActionTrigger[utils.ActivationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
t.Fatal(err)
}
expExpDate, err = utils.IfaceAsTime(tc.expAt.ActionTrigger[utils.ActivationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
t.Fatal(err)
}
diff = rcvExpDate.Sub(expExpDate)
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 {
t.Fatalf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
}
tc.expAt.ActionTrigger[utils.ActivationDate] = rcvExpDate
// Get the absolute difference between the times
rcvExpDate, err = utils.IfaceAsTime(at.ActionTrigger[utils.BalanceExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
t.Fatal(err)
}
expExpDate, err = utils.IfaceAsTime(tc.expAt.ActionTrigger[utils.BalanceExpirationDate], config.CgrConfig().GeneralCfg().DefaultTimezone)
if err != nil {
t.Fatal(err)
}
diff = rcvExpDate.Sub(expExpDate)
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 {
t.Fatalf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
}
tc.expAt.ActionTrigger[utils.BalanceExpirationDate] = rcvExpDate
if !reflect.DeepEqual(at, tc.expAt) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expAt), utils.ToJSON(at))
}
}
}
})
}
}