Files
cgrates/actions/dynamic_test.go
2025-10-19 13:15:57 +02:00

4405 lines
109 KiB
Go

/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
*/
package actions
import (
"cmp"
"fmt"
"reflect"
"slices"
"strings"
"testing"
"time"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/rpcclient"
)
type ccMock struct {
calls map[string]func(ctx *context.Context, args any, reply any) error
}
func (ccM *ccMock) Call(ctx *context.Context, serviceMethod string, args any, reply any) (err error) {
if call, has := ccM.calls[serviceMethod]; !has {
return rpcclient.ErrUnsupporteServiceMethod
} else {
return call(ctx, args, reply)
}
}
func TestParseParamStringToMap(t *testing.T) {
testcases := []struct {
name string
paramStr string
wantMap map[string]any
expectErr bool
errMsg string
}{
{
name: "TenantAndAccount",
paramStr: "*tenant:cgrates.org&*account:1001",
wantMap: map[string]any{
"*tenant": "cgrates.org",
"*account": "1001",
},
expectErr: false,
},
{
name: "TenantAccountDestination",
paramStr: "*tenant:cgrates.org&*account:1001&*destination:1002",
wantMap: map[string]any{
"*tenant": "cgrates.org",
"*account": "1001",
"*destination": "1002",
},
expectErr: false,
},
{
name: "InvalidPairMissingColon",
paramStr: "*tenantcgrates.org&*profileID:ID1001",
wantMap: nil,
expectErr: true,
errMsg: "invalid key-value pair: *tenantcgrates.org",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got := make(map[string]any)
err := parseParamStringToMap(tc.paramStr, got)
if tc.expectErr {
if err == nil {
t.Errorf("expected error but got nil")
} else if err.Error() != tc.errMsg {
t.Errorf("expected error <%v>, got <%v>", tc.errMsg, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, tc.wantMap) {
t.Errorf("expected %+v, got %+v", tc.wantMap, got)
}
})
}
}
func TestActDynamicThresholdID(t *testing.T) {
aL1 := &actDynamicThreshold{
aCfg: &utils.APAction{
ID: "TH1",
},
}
if aL1.id() != "TH1" {
t.Errorf("expected ID <TH1>, got <%s>", aL1.id())
}
aL2 := &actDynamicThreshold{
aCfg: &utils.APAction{
ID: "",
},
}
if aL2.id() != "" {
t.Errorf("expected empty ID, got <%s>", aL2.id())
}
}
func TestActDynamicThresholdCfg(t *testing.T) {
aCfg1 := &utils.APAction{ID: "TH1"}
aL1 := &actDynamicThreshold{aCfg: aCfg1}
if got := aL1.cfg(); got != aCfg1 {
t.Errorf("Scenario 1: expected cfg <%v>, got <%v>", aCfg1, got)
}
aCfg2 := &utils.APAction{}
aL2 := &actDynamicThreshold{aCfg: aCfg2}
if got := aL2.cfg(); got != aCfg2 {
t.Errorf("Scenario 2: expected cfg <%v>, got <%v>", aCfg2, got)
}
aL3 := &actDynamicThreshold{aCfg: nil}
if got := aL3.cfg(); got != nil {
t.Errorf("Scenario 3: expected cfg <nil>, got <%v>", got)
}
}
func TestActDynamicThresholdExecuteSort(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.GeneralCfg().DefaultCaching = utils.MetaNone
cfg.ActionSCfg().AdminSConns = []string{"admins"}
connMgr := engine.NewConnManager(cfg)
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, connMgr)
fltrS := engine.NewFilterS(cfg, connMgr, dm)
data := utils.MapStorage{
utils.AccountField: "1001",
}
diktats := []*utils.APDiktat{
{
ID: "d1",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{{Weight: 10}},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD1;;1;1;1s;true;AP1;true;ee1;key1:val1",
},
},
{
ID: "d2",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{{Weight: 20}},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD2;;1;1;1s;true;AP2;true;ee2;key2:val2",
},
},
}
act := &actDynamicThreshold{
config: cfg,
connMgr: connMgr,
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",
Diktats: diktats,
},
}
weights := map[string]float64{
"d1": 10,
"d2": 20,
}
sorted := make([]*utils.APDiktat, len(diktats))
copy(sorted, diktats)
slices.SortFunc(sorted, func(a, b *utils.APDiktat) int {
return cmp.Compare(weights[b.ID], weights[a.ID])
})
if sorted[0].ID != "d2" || sorted[1].ID != "d1" {
t.Errorf("expected d2 to be first and d1 second after sort, got %v", []string{sorted[0].ID, sorted[1].ID})
}
ctx := context.Background()
trgID := "trigger1"
err := act.execute(ctx, data, trgID)
if err != nil {
t.Fatalf("execute failed: %v", err)
}
}
func TestActDynamicThresholdExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var tpwo *engine.ThresholdProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetThresholdProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if tpwo, canCast = args.(*engine.ThresholdProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldnt cast")
}
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expTpwo *engine.ThresholdProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expTpwo: &engine.ThresholdProfileWithAPIOpts{
ThresholdProfile: &engine.ThresholdProfile{
Tenant: "cgrates.org",
ID: "THD_ACNT_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
MaxHits: 1,
MinHits: 1,
MinSleep: 1 * time.Second,
Blocker: true,
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
ActionProfileIDs: []string{"ACT_LOG_WARNING"},
Async: true,
EeIDs: []string{"eeID1", "eeID2"},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;THD_ACNT_1001;*string:~*req.Account:1001;&20;1;1;1s;true;ACT_LOG_WARNING;true;eeID1&eeID2;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDyanmicPaths",
expTpwo: &engine.ThresholdProfileWithAPIOpts{
ThresholdProfile: &engine.ThresholdProfile{
Tenant: "cgrates.org",
ID: "THD_ACNT_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
MaxHits: 1,
MinHits: 1,
MinSleep: 1 * time.Second,
Blocker: true,
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
ActionProfileIDs: []string{"ACT_LOG_WARNING"},
Async: true,
EeIDs: []string{"eeID1", "eeID2"},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;THD_ACNT_<~*req.Account>;*string:~*req.Account:<~*req.Account>;&20;1;1;1s;true;ACT_LOG_WARNING;true;eeID1&eeID2;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expTpwo: &engine.ThresholdProfileWithAPIOpts{
ThresholdProfile: &engine.ThresholdProfile{
Tenant: "cgrates.org",
ID: "THD_ACNT_1001",
FilterIDs: nil,
MaxHits: 0,
MinHits: 0,
MinSleep: 0,
Blocker: false,
Weights: nil,
ActionProfileIDs: nil,
Async: false,
EeIDs: nil,
},
APIOpts: map[string]any{},
},
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;THD_ACNT_1001;;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "EmptyDiktats",
diktats: []*utils.APDiktat{},
wantErr: true,
},
{
name: "InvalidNumberOfParams",
diktats: []*utils.APDiktat{
{
ID: "d4",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1004;1;2",
},
},
},
wantErr: true,
},
{
name: "InvalidMaxHitsParam",
diktats: []*utils.APDiktat{
{
ID: "d5",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1005;*string:~*req.Account:1005;;invalid;1;1s;true;ACT_LOG_WARNING;true;eeID5;key:value",
},
},
},
wantErr: true,
},
{
name: "InvalidMinSleepParam",
diktats: []*utils.APDiktat{
{
ID: "d6",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1006;*string:~*req.Account:1006;&20;1;1;invalid;true;ACT_LOG_WARNING;true;eeID6;key:value",
},
},
},
wantErr: true,
},
{
name: "WeightParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d10",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1010;;w1&w2&w3;1;1;1s;true;ACT_LOG_WARNING;true;eeID10;key:value",
},
},
},
wantErr: true,
},
{
name: "WeightParamInvalidFloat",
diktats: []*utils.APDiktat{
{
ID: "d11",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1011;;w1&invalid;1;1;1s;true;ACT_LOG_WARNING;true;eeID11;key:value",
},
},
},
wantErr: true,
},
{
name: "APIOptsInvalidFormat",
diktats: []*utils.APDiktat{
{
ID: "d12",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1012;;;;1;1;1s;true;ACT_LOG_WARNING;true;eeID12;keyvalue",
},
},
},
wantErr: true,
},
{
name: "BlockerFail",
diktats: []*utils.APDiktat{
{
ID: "d14",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;THD_ACNT_1014;*string:~*req.Account:1014;;1;1;1s;notbool;ACT_LOG_WARNING;true;eeID14;key:value",
},
},
},
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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicThreshold{
config: cfg,
connMgr: connMgr,
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",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
tpwo = 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 !reflect.DeepEqual(tpwo, tc.expTpwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expTpwo), utils.ToJSON(tpwo))
}
})
}
}
func TestActDynamicStatsID(t *testing.T) {
actionCfg := &utils.APAction{
ID: "StatsID",
}
aL := &actDynamicStats{
aCfg: actionCfg,
}
got := aL.id()
if got != "StatsID" {
t.Errorf("expected %q, got %q", "StatsID", got)
}
}
func TestActDynamicStatsCfg(t *testing.T) {
testcases := []struct {
name string
actCfg *utils.APAction
expIsNil bool
}{
{
name: "ValidAPAction",
actCfg: &utils.APAction{
ID: "StatsProfile!",
},
expIsNil: false,
},
{
name: "NilAPAction",
actCfg: nil,
expIsNil: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
aL := &actDynamicStats{
config: config.NewDefaultCGRConfig(),
connMgr: &engine.ConnManager{},
aCfg: tc.actCfg,
tnt: "cgrates.org",
cgrEv: &utils.CGREvent{},
}
got := aL.cfg()
if tc.expIsNil && got != nil {
t.Errorf("expected nil, got %+v", got)
}
if !tc.expIsNil && got != tc.actCfg {
t.Errorf("expected pointer %p, got %p", tc.actCfg, got)
}
})
}
}
func TestActDynamicStatsExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var sqpwo *engine.StatQueueProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetStatQueueProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if sqpwo, canCast = args.(*engine.StatQueueProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldnt cast")
}
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expSqpwo *engine.StatQueueProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expSqpwo: &engine.StatQueueProfileWithAPIOpts{
StatQueueProfile: &engine.StatQueueProfile{
Tenant: "cgrates.org",
ID: "STATS_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
QueueLength: 100,
TTL: 1 * time.Hour,
MinItems: 1,
Stored: true,
ThresholdIDs: []string{"THD_1", "THD_2"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 20.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: true,
},
},
Metrics: []*engine.MetricWithFilters{
{
MetricID: "ASR",
FilterIDs: []string{"*string:~*req.Account:1001"},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: false,
},
},
},
{
MetricID: "PDD",
FilterIDs: []string{"*string:~*req.Account:1001"},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: false,
},
},
},
},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;STATS_1001;*string:~*req.Account:1001;*string:~*req.Account:1001&20;*string:~*req.Account:1001&true;100;1h;1;true;THD_1&THD_2;ASR&PDD;*string:~*req.Account:1001;*string:~*req.Account:1001&false;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expSqpwo: &engine.StatQueueProfileWithAPIOpts{
StatQueueProfile: &engine.StatQueueProfile{
Tenant: "cgrates.org",
ID: "STATS_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
QueueLength: 100,
TTL: 1 * time.Hour,
MinItems: 1,
Stored: true,
ThresholdIDs: []string{"THD_1", "THD_2"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 20.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: true,
},
},
Metrics: []*engine.MetricWithFilters{
{
MetricID: "ASR",
FilterIDs: []string{"*string:~*req.Account:1001"},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: false,
},
},
},
{
MetricID: "PDD",
FilterIDs: []string{"*string:~*req.Account:1001"},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: false,
},
},
},
},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;STATS_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&20;*string:~*req.Account:<~*req.Account>&true;100;1h;1;true;THD_1&THD_2;ASR&PDD;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&false;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expSqpwo: &engine.StatQueueProfileWithAPIOpts{
StatQueueProfile: &engine.StatQueueProfile{
Tenant: "cgrates.org",
ID: "STATS_1001",
FilterIDs: nil,
QueueLength: 0,
TTL: 0,
MinItems: 0,
Stored: false,
ThresholdIDs: nil,
Weights: nil,
Blockers: nil,
Metrics: nil,
},
APIOpts: map[string]any{},
},
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;STATS_1001;;;;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "EmptyDiktats",
diktats: []*utils.APDiktat{},
wantErr: true,
},
{
name: "InvalidNumberOfParams",
diktats: []*utils.APDiktat{
{
ID: "d4",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1004;param1;param2",
},
},
},
wantErr: true,
},
{
name: "InvalidQueueLengthParam",
diktats: []*utils.APDiktat{
{
ID: "d5",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1005;*string:~*req.Account:1005;;;invalid;1h;1;true;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidTTLParam",
diktats: []*utils.APDiktat{
{
ID: "d6",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1006;*string:~*req.Account:1006;;;100;invalid;1;true;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidMinItemsParam",
diktats: []*utils.APDiktat{
{
ID: "d7",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1007;*string:~*req.Account:1007;;;100;1h;invalid;true;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidStoredParam",
diktats: []*utils.APDiktat{
{
ID: "d8",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1008;*string:~*req.Account:1008;;;100;1h;1;invalid;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "WeightParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d9",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1009;;w1&w2&w3;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "WeightParamInvalidFloat",
diktats: []*utils.APDiktat{
{
ID: "d10",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1010;;w1&invalid;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "BlockerParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d11",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1011;;;b1&b2&b3;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "BlockerParamInvalidBool",
diktats: []*utils.APDiktat{
{
ID: "d12",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1012;;;b1&invalid;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "MetricBlockerParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d13",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1013;;;;;;;;;;ASR;;mb1&mb2&mb3;",
},
},
},
wantErr: true,
},
{
name: "MetricBlockerParamInvalidBool",
diktats: []*utils.APDiktat{
{
ID: "d14",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1014;;;;;;;;;;ASR;;mb1&invalid;",
},
},
},
wantErr: true,
},
{
name: "APIOptsInvalidFormat",
diktats: []*utils.APDiktat{
{
ID: "d15",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1015;;;;;;;;;;;;;keyvalue",
},
},
},
wantErr: true,
},
{
name: "NoAdminSConns",
diktats: []*utils.APDiktat{
{
ID: "d16",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;STATS_1016;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.GeneralCfg().DefaultCaching = utils.MetaNone
if tc.name != "NoAdminSConns" {
cfg.ActionSCfg().AdminSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS)}
}
connMgr := engine.NewConnManager(cfg)
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicStats{
config: cfg,
connMgr: connMgr,
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_STATS",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
sqpwo = 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 !reflect.DeepEqual(sqpwo, tc.expSqpwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expSqpwo), utils.ToJSON(sqpwo))
}
})
}
}
func TestActDynamicAttributeId(t *testing.T) {
testcases := []struct {
name string
aCfg *utils.APAction
expect string
}{
{
name: "WithValidID",
aCfg: &utils.APAction{
ID: "AttrA1",
FilterIDs: []string{"f1", "f2"},
TTL: time.Second,
Type: "attribute",
Opts: map[string]any{
"key1": "val1",
},
},
expect: "AttrA1",
},
{
name: "WithEmptyID",
aCfg: &utils.APAction{
ID: "",
FilterIDs: nil,
TTL: 0,
Type: "attribute",
Opts: nil,
},
expect: "",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
al := &actDynamicAttribute{
aCfg: tc.aCfg,
}
got := al.id()
if got != tc.expect {
t.Errorf("expected %q, got %q", tc.expect, got)
}
})
}
}
func TestActDynamicAttributeCfg(t *testing.T) {
aCfg := &utils.APAction{ID: "AttrA1"}
al := &actDynamicAttribute{aCfg: aCfg}
if got := al.cfg(); got != aCfg {
t.Errorf("expected cfg() to return %+v, got %+v", aCfg, got)
}
}
func TestActDynamicAttributeExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var apwo *utils.APIAttributeProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetAttributeProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if apwo, canCast = args.(*utils.APIAttributeProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldn't cast")
}
*reply.(*string) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expApwo *utils.APIAttributeProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expApwo: &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 20.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: true,
},
},
Attributes: []*utils.ExternalAttribute{
{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: false,
},
},
Path: "*req.Category",
Type: "*constant",
Value: "call",
},
},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;ATTR_1001;*string:~*req.Account:1001;*string:~*req.Account:1001&20;*string:~*req.Account:1001&true;*string:~*req.Account:1001;*string:~*req.Account:1001&false;*req.Category;*constant;call;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expApwo: &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 20.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: true,
},
},
Attributes: []*utils.ExternalAttribute{
{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*string:~*req.Account:1001"},
Blocker: false,
},
},
Path: "*req.Category",
Type: "*constant",
Value: "call",
},
},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;ATTR_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&20;*string:~*req.Account:<~*req.Account>&true;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&false;*req.Category;*constant;call;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expApwo: &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_1001",
FilterIDs: nil,
Weights: nil,
Blockers: nil,
Attributes: nil,
},
APIOpts: map[string]any{},
},
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;ATTR_1001;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "WeightParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ATTR_1001;;w1&w2&w3;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "WeightParamInvalidFloat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ATTR_1001;;w1&invalid;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "BlockerParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ATTR_1001;;;b1&b2&b3;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "BlockerParamInvalidBool",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ATTR_1001;;;b1&invalid;;;;;;;",
},
},
},
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;ATTR_1001;;;;;;;;;keyvalue",
},
},
},
wantErr: true,
},
{
name: "NoAdminSConns",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ATTR_1001;;;;;;;;;",
},
},
},
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.GeneralCfg().DefaultCaching = utils.MetaNone
if tc.name != "NoAdminSConns" {
cfg.ActionSCfg().AdminSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS)}
}
connMgr := engine.NewConnManager(cfg)
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicAttribute{
config: cfg,
connMgr: connMgr,
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_ATTR",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
apwo = 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 !reflect.DeepEqual(apwo, tc.expApwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expApwo), utils.ToJSON(apwo))
}
})
}
}
func TestActDynamicResourceId(t *testing.T) {
testcases := []struct {
name string
aCfg *utils.APAction
expect string
}{
{
name: "WithValidID",
aCfg: &utils.APAction{
ID: "ResA1",
FilterIDs: []string{"f1", "f2"},
TTL: time.Second,
Type: "resource",
Opts: map[string]any{
"Opts": "Opts1",
},
},
expect: "ResA1",
},
{
name: "WithEmptyID",
aCfg: &utils.APAction{
ID: "",
FilterIDs: nil,
TTL: 0,
Type: "resource",
Opts: nil,
},
expect: "",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
al := &actDynamicResource{
aCfg: tc.aCfg,
}
got := al.id()
if got != tc.expect {
t.Errorf("expected %q, got %q", tc.expect, got)
}
})
}
}
func TestActDynamicResourceCfg(t *testing.T) {
testcases := []struct {
name string
aCfg *utils.APAction
expect *utils.APAction
}{
{
name: "WithValidCfg",
aCfg: &utils.APAction{
ID: "ResCfg1",
FilterIDs: []string{"f1"},
TTL: time.Second,
Type: "resource",
Opts: map[string]any{
"Opts1": "val1",
},
},
expect: &utils.APAction{
ID: "ResCfg1",
FilterIDs: []string{"f1"},
TTL: time.Second,
Type: "resource",
Opts: map[string]any{
"Opts1": "val1",
},
},
},
{
name: "WithNilCfg",
aCfg: nil,
expect: nil,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
al := &actDynamicResource{
aCfg: tc.aCfg,
}
got := al.cfg()
if !reflect.DeepEqual(got, tc.expect) {
t.Errorf("expected %+v, got %+v", tc.expect, got)
}
})
}
}
func TestActDynamicResourceExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var rpwo *utils.ResourceProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetResourceProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if rpwo, canCast = args.(*utils.ResourceProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldn't cast")
}
*reply.(*string) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expRpwo *utils.ResourceProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expRpwo: &utils.ResourceProfileWithAPIOpts{
ResourceProfile: &utils.ResourceProfile{
Tenant: "cgrates.org",
ID: "RES_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
UsageTTL: time.Duration(60 * time.Second),
Limit: 100.0,
AllocationMessage: "Resource allocated",
Blocker: true,
Stored: true,
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 20.0,
},
},
ThresholdIDs: []string{"THD_1001"},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;RES_1001;*string:~*req.Account:1001;*string:~*req.Account:1001&20;60s;100;Resource allocated;true;true;THD_1001;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expRpwo: &utils.ResourceProfileWithAPIOpts{
ResourceProfile: &utils.ResourceProfile{
Tenant: "cgrates.org",
ID: "RES_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
UsageTTL: time.Duration(60 * time.Second),
Limit: 100.0,
AllocationMessage: "Resource allocated",
Blocker: true,
Stored: true,
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 20.0,
},
},
ThresholdIDs: []string{"THD_1001"},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;RES_<~*req.Account>;*string:~*req.Account:<~*req.Account>;*string:~*req.Account:<~*req.Account>&20;60s;100;Resource allocated;true;true;THD_<~*req.Account>;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expRpwo: &utils.ResourceProfileWithAPIOpts{
ResourceProfile: &utils.ResourceProfile{
Tenant: "cgrates.org",
ID: "RES_1001",
FilterIDs: nil,
UsageTTL: 0,
Limit: 0.0,
AllocationMessage: "",
Blocker: false,
Stored: false,
Weights: nil,
ThresholdIDs: nil,
},
APIOpts: map[string]any{},
},
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;RES_1001;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "WeightParamUnsupportedFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;w1&w2&w3;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "WeightParamInvalidFloat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;w1&invalid;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "UsageTTLInvalidDuration",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;;invalid;;;;;;",
},
},
},
wantErr: true,
},
{
name: "LimitInvalidFloat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;;;invalid;;;;;",
},
},
},
wantErr: true,
},
{
name: "BlockerInvalidBool",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;;;;;invalid;;;",
},
},
},
wantErr: true,
},
{
name: "StoredInvalidBool",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;;;;;;invalid;;",
},
},
},
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;RES_1001;;;;;;;;;keyvalue",
},
},
},
wantErr: true,
},
{
name: "InvalidNumberOfParameters",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;not_enough_params",
},
},
},
wantErr: true,
},
{
name: "NoDiktatsSpecified",
diktats: []*utils.APDiktat{},
wantErr: true,
},
{
name: "NoAdminSConns",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RES_1001;;;;;;;;;",
},
},
},
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.GeneralCfg().DefaultCaching = utils.MetaNone
if tc.name != "NoAdminSConns" {
cfg.ActionSCfg().AdminSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS)}
}
connMgr := engine.NewConnManager(cfg)
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicResource{
config: cfg,
connMgr: connMgr,
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_RESOURCE",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
rpwo = 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 !reflect.DeepEqual(rpwo, tc.expRpwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRpwo), utils.ToJSON(rpwo))
}
})
}
}
func TestActDynamicTrendId(t *testing.T) {
tests := []struct {
name string
actionID string
expected string
}{
{
name: "With ID",
actionID: "DynamicTrendID",
expected: "DynamicTrendID",
},
{
name: "Empty ID",
actionID: "",
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actDT := &actDynamicTrend{
aCfg: &utils.APAction{
ID: tt.actionID,
},
}
result := actDT.id()
if result != tt.expected {
t.Errorf("Expected %s, got %s", tt.expected, result)
}
})
}
}
func TestActDynamicTrendCfg(t *testing.T) {
apAction := &utils.APAction{ID: "TrendProfile1"}
a := &actDynamicTrend{
aCfg: apAction,
}
if got := a.cfg(); !reflect.DeepEqual(got, apAction) {
t.Errorf("cfg() = %v, want %v", got, apAction)
}
}
func TestActDynamicTrendExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var tpwo *utils.TrendProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetTrendProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if tpwo, canCast = args.(*utils.TrendProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldn't cast")
}
*reply.(*string) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expTpwo *utils.TrendProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expTpwo: &utils.TrendProfileWithAPIOpts{
TrendProfile: &utils.TrendProfile{
Tenant: "cgrates.org",
ID: "TREND_1001",
Schedule: "* * * * *",
StatID: "STAT_1001",
Metrics: []string{"*acd", "*tcd"},
TTL: 60 * time.Second,
QueueLength: 100,
MinItems: 10,
CorrelationType: "*last",
Tolerance: 0.05,
Stored: true,
ThresholdIDs: []string{"THD_1001"},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;TREND_1001;* * * * *;STAT_1001;*acd&*tcd;60s;100;10;*last;0.05;true;THD_1001;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expTpwo: &utils.TrendProfileWithAPIOpts{
TrendProfile: &utils.TrendProfile{
Tenant: "cgrates.org",
ID: "TREND_1001",
Schedule: "* * * * *",
StatID: "STAT_1001",
Metrics: []string{"*acd", "*tcd"},
TTL: 60 * time.Second,
QueueLength: 100,
MinItems: 10,
CorrelationType: "*last",
Tolerance: 0.05,
Stored: true,
ThresholdIDs: []string{"THD_1001"},
},
APIOpts: map[string]any{
"key": "value",
},
},
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;TREND_<~*req.Account>;* * * * *;STAT_<~*req.Account>;*acd&*tcd;60s;100;10;*last;0.05;true;THD_<~*req.Account>;key:value",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expTpwo: &utils.TrendProfileWithAPIOpts{
TrendProfile: &utils.TrendProfile{
Tenant: "cgrates.org",
ID: "TREND_1001",
Schedule: "",
StatID: "",
Metrics: nil,
TTL: 0,
QueueLength: 0,
MinItems: 0,
CorrelationType: "",
Tolerance: 0.0,
Stored: false,
ThresholdIDs: nil,
},
APIOpts: map[string]any{},
},
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;TREND_1001;;;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "TTLInvalidDuration",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;;;invalid;;;;;;",
},
},
},
wantErr: true,
},
{
name: "QueueLengthInvalidInt",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;;;;;invalid;;;;;",
},
},
},
wantErr: true,
},
{
name: "MinItemsInvalidInt",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;;;;;;invalid;;;;",
},
},
},
wantErr: true,
},
{
name: "ToleranceInvalidFloat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;;;;;;;;invalid;;",
},
},
},
wantErr: true,
},
{
name: "StoredInvalidBool",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;;;;;;;;;invalid;",
},
},
},
wantErr: true,
},
{
name: "InvalidNumberOfParameters",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;not_enough_params",
},
},
},
wantErr: true,
},
{
name: "NoDiktatsSpecified",
diktats: []*utils.APDiktat{},
wantErr: true,
},
{
name: "NoAdminSConns",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;TREND_1001;;;;;;;;;;;",
},
},
},
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.GeneralCfg().DefaultCaching = utils.MetaNone
if tc.name != "NoAdminSConns" {
cfg.ActionSCfg().AdminSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS)}
}
connMgr := engine.NewConnManager(cfg)
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicTrend{
config: cfg,
connMgr: connMgr,
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_TREND",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
tpwo = 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 !reflect.DeepEqual(tpwo, tc.expTpwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expTpwo), utils.ToJSON(tpwo))
}
})
}
}
func TestActDynamicRankingId(t *testing.T) {
tests := []struct {
name string
dr actDynamicRanking
want string
}{
{
name: "WithValidID",
dr: actDynamicRanking{aCfg: &utils.APAction{ID: "RankingProfile1"}},
want: "RankingProfile1",
},
{
name: "WithEmptyID",
dr: actDynamicRanking{aCfg: &utils.APAction{ID: ""}},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.dr.id()
if got != tt.want {
t.Errorf("id() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicRankingCfg(t *testing.T) {
tests := []struct {
name string
al actDynamicRanking
want *utils.APAction
}{
{
name: "WithValidCfg",
al: actDynamicRanking{aCfg: &utils.APAction{ID: "RankingProfile1"}},
want: &utils.APAction{ID: "RankingProfile1"},
},
{
name: "WithNilCfg",
al: actDynamicRanking{aCfg: nil},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.al.cfg()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("cfg() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicRankingExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var rpwo *utils.RankingProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetRankingProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if rpwo, canCast = args.(*utils.RankingProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldnt cast")
}
*(reply.(*string)) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expRpwo *utils.RankingProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expRpwo: &utils.RankingProfileWithAPIOpts{
RankingProfile: &utils.RankingProfile{
Tenant: "cgrates.org",
ID: "RANK_ACNT_1001",
Schedule: "* * * * *",
Sorting: "*desc",
SortingParameters: []string{"*tcc", "*tcd"},
Stored: true,
StatIDs: []string{"STAT_1", "STAT_2"},
MetricIDs: []string{"*tcc", "*tcd"},
ThresholdIDs: []string{"THD_1", "THD_2"},
},
APIOpts: map[string]any{
"key1": "value1",
"key2": "value2",
},
},
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;RANK_ACNT_1001;* * * * *;STAT_1&STAT_2;*tcc&*tcd;*desc;*tcc&*tcd;true;THD_1&THD_2;key1:value1&key2:value2",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expRpwo: &utils.RankingProfileWithAPIOpts{
RankingProfile: &utils.RankingProfile{
Tenant: "cgrates.org",
ID: "RANK_ACNT_1001",
Schedule: "0 0 * * *",
Sorting: "*asc",
SortingParameters: []string{"*acc"},
Stored: false,
StatIDs: []string{"STAT_1001"},
MetricIDs: []string{"*acc"},
ThresholdIDs: []string{"THD_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;RANK_ACNT_<~*req.Account>;0 0 * * *;STAT_<~*req.Account>;*acc;*asc;*acc;false;THD_<~*req.Account>;account:<~*req.Account>",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expRpwo: &utils.RankingProfileWithAPIOpts{
RankingProfile: &utils.RankingProfile{
Tenant: "cgrates.org",
ID: "RANK_EMPTY",
Schedule: "",
Sorting: "",
SortingParameters: nil,
Stored: false,
StatIDs: nil,
MetricIDs: nil,
ThresholdIDs: nil,
},
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;RANK_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;RANK_INVALID;only;four;params",
},
},
},
wantErr: true,
},
{
name: "InvalidStoredParam",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RANK_INVALID;* * * * *;STAT_1;*tcc;*desc;*tcc;invalid_bool;THD_1;key:value",
},
},
},
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;RANK_INVALID;* * * * *;STAT_1;*tcc;*desc;*tcc;true;THD_1;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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicRanking{
config: cfg,
connMgr: connMgr,
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_RANKING",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
rpwo = 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.expRpwo != nil && !reflect.DeepEqual(rpwo, tc.expRpwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRpwo), utils.ToJSON(rpwo))
}
})
}
}
func TestActDynamicFilterId(t *testing.T) {
tests := []struct {
name string
df actDynamicFilter
want string
}{
{
name: "WithValidID",
df: actDynamicFilter{aCfg: &utils.APAction{ID: "FilterProfile1"}},
want: "FilterProfile1",
},
{
name: "WithEmptyID",
df: actDynamicFilter{aCfg: &utils.APAction{ID: ""}},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.df.id()
if got != tt.want {
t.Errorf("id() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicFilterCfg(t *testing.T) {
tests := []struct {
name string
df actDynamicFilter
want *utils.APAction
}{
{
name: "WithValidCfg",
df: actDynamicFilter{aCfg: &utils.APAction{ID: "FilterProfile1"}},
want: &utils.APAction{ID: "FilterProfile1"},
},
{
name: "WithNilCfg",
df: actDynamicFilter{aCfg: nil},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.df.cfg()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("cfg() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicFilterExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var fwo *engine.FilterWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetFilter: func(ctx *context.Context, args, reply any) error {
var canCast bool
if fwo, canCast = args.(*engine.FilterWithAPIOpts); !canCast {
return fmt.Errorf("couldnt cast")
}
*(reply.(*string)) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expFwo *engine.FilterWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expFwo: &engine.FilterWithAPIOpts{
Filter: &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_ACNT_1001",
Rules: []*engine.FilterRule{{
Type: "*string",
Element: "~*req.Account",
Values: []string{"1001", "1002"},
}},
},
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;FLTR_ACNT_1001;*string;~*req.Account;1001&1002;*subsys:*sessions",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expFwo: &engine.FilterWithAPIOpts{
Filter: &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_ACNT_1001",
Rules: []*engine.FilterRule{{
Type: "*string",
Element: "~*req.Account",
Values: []string{"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;FLTR_ACNT_<~*req.Account>;*string;~*req.Account;<~*req.Account>;account:<~*req.Account>",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expFwo: &engine.FilterWithAPIOpts{
Filter: &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_EMPTY",
Rules: []*engine.FilterRule{{
Type: "*string",
Element: "~*req.Field",
Values: nil,
}},
},
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;FLTR_EMPTY;*string;~*req.Field;;",
},
},
},
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;FLTR_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;FLTR_INVALID;*string;~*req.Field;value;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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicFilter{
config: cfg,
connMgr: connMgr,
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_FILTER",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
fwo = 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.expFwo != nil && !reflect.DeepEqual(fwo, tc.expFwo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expFwo), utils.ToJSON(fwo))
}
})
}
}
func TestActDynamicRouteId(t *testing.T) {
tests := []struct {
name string
aCfg *utils.APAction
want string
}{
{
name: "WithValidID",
aCfg: &utils.APAction{ID: "RouteID1"},
want: "RouteID1",
},
{
name: "WithEmptyID",
aCfg: &utils.APAction{ID: ""},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aL := &actDynamicRoute{aCfg: tt.aCfg}
if got := aL.id(); got != tt.want {
t.Errorf("id() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicRouteCfg(t *testing.T) {
tests := []struct {
name string
dr actDynamicRoute
want *utils.APAction
}{
{
name: "WithValidCfg",
dr: actDynamicRoute{aCfg: &utils.APAction{ID: "RouteProfile1"}},
want: &utils.APAction{ID: "RouteProfile1"},
},
{
name: "WithNilCfg",
dr: actDynamicRoute{aCfg: nil},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.dr.cfg()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("cfg() = %v, want %v", got, tt.want)
}
})
}
}
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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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)
}
})
}
}
func TestActDynamicIPExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var ipo *utils.IPProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetIPProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if ipo, canCast = args.(*utils.IPProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldnt cast")
}
*(reply.(*string)) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expIpo *utils.IPProfileWithAPIOpts
wantErr bool
}{
{
name: "SuccessfulRequest",
expIpo: &utils.IPProfileWithAPIOpts{
IPProfile: &utils.IPProfile{
Tenant: "cgrates.org",
ID: "IP_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*weight"},
Weight: 10.0,
},
},
TTL: time.Duration(60) * time.Second,
Stored: true,
Pools: []*utils.IPPool{
{
ID: "POOL_1001",
FilterIDs: []string{"*string:~*req.Pool:1001"},
Type: "ipv4",
Range: "192.168.1.0/24",
Strategy: "*ascending",
Message: "IP allocated",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*pool_weight"},
Weight: 20.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*pool_blocker"},
Blocker: false,
},
},
},
},
},
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;IP_1001;*string:~*req.Account:1001;*weight&10.0;60s;true;POOL_1001;*string:~*req.Pool:1001;ipv4;192.168.1.0/24;*ascending;IP allocated;*pool_weight&20.0;*pool_blocker&false;*subsys:*sessions",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expIpo: &utils.IPProfileWithAPIOpts{
IPProfile: &utils.IPProfile{
Tenant: "cgrates.org",
ID: "IP_1001",
Pools: []*utils.IPPool{
{
ID: "POOL_1001",
Type: "ipv4",
Range: "192.168.1.0/24",
Strategy: "*ascending",
Message: "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;IP_<~*req.Account>;;;;false;POOL_<~*req.Account>;;ipv4;192.168.1.0/24;*ascending;<~*req.Account>;;;account:<~*req.Account>",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expIpo: &utils.IPProfileWithAPIOpts{
IPProfile: &utils.IPProfile{
Tenant: "cgrates.org",
ID: "IP_EMPTY",
Pools: []*utils.IPPool{
{
ID: "",
Type: "",
Range: "",
Strategy: "",
Message: "",
},
},
},
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;IP_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;IP_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;IP_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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicIP{
config: cfg,
connMgr: connMgr,
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_IP",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
ipo = 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.expIpo != nil && !reflect.DeepEqual(ipo, tc.expIpo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expIpo), utils.ToJSON(ipo))
}
})
}
}
func TestActDynamicIPId(t *testing.T) {
tests := []struct {
name string
aCfg *utils.APAction
want string
}{
{
name: "WithValidID",
aCfg: &utils.APAction{ID: "ActDynamicIP"},
want: "ActDynamicIP",
},
{
name: "WithEmptyID",
aCfg: &utils.APAction{ID: ""},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
act := &actDynamicIP{
aCfg: tt.aCfg,
}
if got := act.id(); got != tt.want {
t.Errorf("id() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicIPCfg(t *testing.T) {
tests := []struct {
name string
aCfg *utils.APAction
want *utils.APAction
}{
{
name: "WithValidCfg",
aCfg: &utils.APAction{ID: "ActDynamicIP"},
want: &utils.APAction{ID: "ActDynamicIP"},
},
{
name: "WithNilCfg",
aCfg: nil,
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
act := &actDynamicIP{
aCfg: tt.aCfg,
}
if got := act.cfg(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("cfg() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicRateExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var rateProfile *utils.APIRateProfile
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetRateProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if rateProfile, canCast = args.(*utils.APIRateProfile); !canCast {
return fmt.Errorf("couldn't cast")
}
*(reply.(*string)) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expRateProfile *utils.APIRateProfile
wantErr bool
}{
{
name: "SuccessfulRequest",
expRateProfile: &utils.APIRateProfile{
RateProfile: &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*weight"},
Weight: 10.0,
},
},
MinCost: utils.NewDecimal(5, 2),
MaxCost: utils.NewDecimal(100, 2),
MaxCostStrategy: "*free",
Rates: map[string]*utils.Rate{
"RT_1001": {
ID: "RT_1001",
FilterIDs: []string{"*string:~*req.Destination:+49"},
ActivationTimes: "* * * * *",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"fltr1"},
Weight: 20.0,
},
},
Blocker: false,
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
FixedFee: utils.NewDecimal(10, 2),
RecurrentFee: utils.NewDecimal(1, 2),
Unit: utils.NewDecimal(int64(time.Second), 0),
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
},
},
},
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;RATE_1001;*string:~*req.Account:1001;*weight&10.0;0.05;1.00;*free;RT_1001;*string:~*req.Destination:+49;* * * * *;fltr1&20.0;false;0;0.10;0.01;1s;1s;*subsys:*sessions",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestWithDynamicPaths",
expRateProfile: &utils.APIRateProfile{
RateProfile: &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_1001",
MaxCostStrategy: "*free",
Rates: map[string]*utils.Rate{
"RT_1001": {
ID: "RT_1001",
ActivationTimes: "* * * * *",
IntervalRates: []*utils.IntervalRate{
{
RecurrentFee: utils.NewDecimal(2, 2),
Unit: utils.NewDecimal(int64(time.Minute), 0),
Increment: utils.NewDecimal(int64(time.Minute), 0),
},
},
},
},
},
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;RATE_<~*req.Account>;;;;;*free;RT_<~*req.Account>;;* * * * *;;;;;<~*req.RecurrentFee>;1m;1m;account:<~*req.Account>",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expRateProfile: &utils.APIRateProfile{
RateProfile: &utils.RateProfile{
Tenant: "cgrates.org",
ID: "RATE_EMPTY",
MaxCostStrategy: "",
Rates: map[string]*utils.Rate{
"": {
ID: "",
ActivationTimes: "",
IntervalRates: []*utils.IntervalRate{{}},
},
},
},
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;RATE_EMPTY;;;;;;;;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "EmptyDiktats",
diktats: []*utils.APDiktat{},
wantErr: true,
},
{
name: "InvalidNumberOfParams17",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;only;seventeen;params;here;not;eighteen;params;total;missing;one;more;param;here;now;still",
},
},
},
wantErr: true,
},
{
name: "InvalidNumberOfParams19",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;too;many;params;here;now;we;have;nineteen;params;total;which;is;one;too;many;for;this",
},
},
},
wantErr: true,
},
{
name: "InvalidWeightFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;*weight&invalid_weight;;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidMinCostFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;invalid_decimal;;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidMaxCostFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;invalid_decimal;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidRateWeightFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;*weight&invalid_weight;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidBlockerFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;;invalid_bool;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidIntervalStartFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;;false;invalid_decimal;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidFixedFeeFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;;false;;invalid_decimal;;",
},
},
},
wantErr: true,
},
{
name: "InvalidRecurrentFeeFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;;false;;;invalid_decimal;",
},
},
},
wantErr: true,
},
{
name: "InvalidUnitFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;;false;;;;invalid_decimal",
},
},
},
wantErr: true,
},
{
name: "InvalidIncrementFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;RATE_INVALID;;;;;;RT_1;;;;;;;;;invalid_decimal;",
},
},
},
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;RATE_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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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 := &actDynamicRate{
config: cfg,
connMgr: connMgr,
fltrS: fltrS,
tnt: "cgrates.org",
cgrEv: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "evID",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "+49123456789",
utils.RecurrentFee: "0.02",
},
},
aCfg: &utils.APAction{
ID: "ACT_DYNAMIC_RATE",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
rateProfile = nil
})
ctx := context.Background()
data := utils.MapStorage{
"*req": map[string]any{
utils.AccountField: "1001",
utils.Destination: "+49123456789",
utils.RecurrentFee: "0.02",
},
}
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.expRateProfile != nil && !reflect.DeepEqual(rateProfile, tc.expRateProfile) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expRateProfile), utils.ToJSON(rateProfile))
}
})
}
}
func TestActDynamicActionExecute(t *testing.T) {
engine.Cache.Clear(nil)
defer engine.Cache.Clear(nil)
var actionProfile *utils.ActionProfileWithAPIOpts
ccM := &ccMock{
calls: map[string]func(ctx *context.Context, args any, reply any) error{
utils.AdminSv1SetActionProfile: func(ctx *context.Context, args, reply any) error {
var canCast bool
if actionProfile, canCast = args.(*utils.ActionProfileWithAPIOpts); !canCast {
return fmt.Errorf("couldn't cast")
}
*(reply.(*string)) = utils.OK
return nil
},
},
}
testcases := []struct {
name string
diktats []*utils.APDiktat
expActionProfile *utils.ActionProfileWithAPIOpts
wantErr bool
expectErrContains string
}{
{
name: "SuccessfulRequest",
expActionProfile: &utils.ActionProfileWithAPIOpts{
ActionProfile: &utils.ActionProfile{
Tenant: "cgrates.org",
ID: "ACT_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,
},
},
Schedule: "* * * * *",
Targets: map[string]utils.StringSet{
"*voice": utils.NewStringSet([]string{"account1", "account2"}),
},
Actions: []*utils.APAction{
{
ID: "SUB_ACT_1001",
FilterIDs: []string{"*string:~*req.Destination:+49"},
TTL: 30 * time.Second,
Type: "*log",
Opts: map[string]any{
"level": "info",
"msg": "test message",
},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"act_weight"},
Weight: 20.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"act_blocker"},
Blocker: true,
},
},
Diktats: []*utils.APDiktat{
{
ID: "DIKTAT_1001",
FilterIDs: []string{"*string:~*req.Type:test"},
Opts: map[string]any{
"path": "*req.Balance",
"value": "100",
},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"diktat_weight"},
Weight: 30.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"diktat_blocker"},
Blocker: false,
},
},
},
},
},
},
},
APIOpts: map[string]any{
utils.MetaSubsys: "*sessions",
"timeout": "60s",
},
},
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;ACT_1001;*string:~*req.Account:1001;*weight&10.0;*blocker&false;* * * * *;*voice;account1&account2;SUB_ACT_1001;*string:~*req.Destination:+49;30s;*log;level:info&msg:test message;act_weight&20.0;act_blocker&true;DIKTAT_1001;*string:~*req.Type:test;path:*req.Balance&value:100;diktat_weight&30.0;diktat_blocker&false;*subsys:*sessions&timeout:60s",
},
},
},
wantErr: false,
},
{
name: "SuccessfulRequestEmptyFields",
expActionProfile: &utils.ActionProfileWithAPIOpts{
ActionProfile: &utils.ActionProfile{
Tenant: "cgrates.org",
ID: "ACT_EMPTY",
Targets: map[string]utils.StringSet{},
Actions: []*utils.APAction{
{
ID: "",
Type: "",
Opts: map[string]any{},
Diktats: []*utils.APDiktat{
{
ID: "",
Opts: map[string]any{},
},
},
},
},
},
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;ACT_EMPTY;;;;;;;;;;;;;;;;;;;",
},
},
},
wantErr: false,
},
{
name: "NoAdminSConns",
diktats: []*utils.APDiktat{
{
ID: "d1",
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_1001;;;;;;;;;;;;;;;;;;;;",
},
},
},
wantErr: true,
expectErrContains: "no connection with AdminS",
},
{
name: "EmptyDiktats",
diktats: []*utils.APDiktat{},
wantErr: true,
expectErrContains: "No diktats were specified for action",
},
{
name: "InvalidNumberOfParams20",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;only;twenty;params;here;not;twenty;one;params;total;missing;one;more;param;here;now;still;incomplete;test",
},
},
},
wantErr: true,
expectErrContains: "invalid number of parameters <20> expected 21",
},
{
name: "InvalidNumberOfParams22",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;too;many;params;here;now;we;have;twenty;two;params;total;which;is;one;too;many;for;this;function;call",
},
},
},
wantErr: true,
expectErrContains: "invalid number of parameters <22> expected 21",
},
{
name: "InvalidWeightFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;*weight&invalid_weight;;;;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidBlockerFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;*blocker&invalid_bool;;;;;;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidTTLFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;invalid_ttl;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidActionWeightFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;;*log;;*weight&invalid_weight;;;;;;;",
},
},
},
wantErr: true,
expectErrContains: "strconv.ParseFloat",
},
{
name: "InvalidActionBlockerFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;;*log;;;*blocker&invalid_bool;;;;;;",
},
},
},
wantErr: true,
expectErrContains: "strconv.ParseBool",
},
{
name: "InvalidDiktatWeightFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;;*log;;;;DIKTAT_1;;opt:val;*weight&invalid_weight;;",
},
},
},
wantErr: true,
expectErrContains: "strconv.ParseFloat",
},
{
name: "InvalidDiktatBlockerFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;;*log;;;;DIKTAT_1;;opt:val;;*blocker&invalid_bool;",
},
},
},
wantErr: true,
expectErrContains: "strconv.ParseBool",
},
{
name: "InvalidOptsFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;;*log;invalid_opts_format;;;;;;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidDiktatOptsFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;SUB_ACT;;;*log;;;;DIKTAT_1;;invalid_opts_format;;;;;",
},
},
},
wantErr: true,
},
{
name: "InvalidAPIOptsFormat",
diktats: []*utils.APDiktat{
{
ID: "d1",
Weights: utils.DynamicWeights{
&utils.DynamicWeight{Weight: 20.0},
},
Opts: map[string]any{
utils.MetaTemplate: "cgrates.org;ACT_INVALID;;;;;;;;;;;;;;;;;;;invalid_format",
},
},
},
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.GeneralCfg().DefaultCaching = utils.MetaNone
if !strings.Contains(tc.expectErrContains, "no connection") {
cfg.ActionSCfg().AdminSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS)}
}
connMgr := engine.NewConnManager(cfg)
dataDB, _ := engine.NewInternalDB(nil, nil, nil, cfg.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, cfg, connMgr)
fltrS := engine.NewFilterS(cfg, connMgr, dm)
if len(cfg.ActionSCfg().AdminSConns) > 0 {
rpcInternal := make(chan birpc.ClientConnector, 1)
rpcInternal <- ccM
connMgr.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAdminS), utils.AdminSv1, rpcInternal)
}
act := &actDynamicAction{
config: cfg,
connMgr: connMgr,
fltrS: fltrS,
tnt: "cgrates.org",
cgrEv: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "evID",
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "+49123456789",
},
},
aCfg: &utils.APAction{
ID: "ACT_DYNAMIC_ACTION",
Diktats: tc.diktats,
},
}
t.Cleanup(func() {
actionProfile = nil
})
ctx := context.Background()
data := utils.MapStorage{
"*req": map[string]any{
utils.AccountField: "1001",
utils.Destination: "+49123456789",
},
}
trgID := "trigger1"
err := act.execute(ctx, data, trgID)
if tc.wantErr {
if err == nil {
t.Errorf("expected an error, but got nil")
} else if tc.expectErrContains != "" && !strings.Contains(err.Error(), tc.expectErrContains) {
t.Errorf("expected error to contain '%s', but got: %v", tc.expectErrContains, err)
}
} else if err != nil {
t.Error(err)
} else if tc.expActionProfile != nil && !reflect.DeepEqual(actionProfile, tc.expActionProfile) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(tc.expActionProfile), utils.ToJSON(actionProfile))
}
})
}
}
func TestActDynamicActionId(t *testing.T) {
tests := []struct {
name string
aL actDynamicAction
want string
}{
{
name: "WithValidID",
aL: actDynamicAction{
aCfg: &utils.APAction{ID: "ActionProfile1"},
},
want: "ActionProfile1",
},
{
name: "WithEmptyID",
aL: actDynamicAction{
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("actDynamicAction.id() = %v, want %v", got, tt.want)
}
})
}
}
func TestActDynamicActionCfg(t *testing.T) {
tests := []struct {
name string
aL actDynamicAction
want *utils.APAction
}{
{
name: "WithValidCfg",
aL: actDynamicAction{
aCfg: &utils.APAction{ID: "ActionProfile1"},
},
want: &utils.APAction{ID: "ActionProfile1"},
},
{
name: "WithEmptyCfg",
aL: actDynamicAction{
aCfg: &utils.APAction{},
},
want: &utils.APAction{},
},
{
name: "WithNilCfg",
aL: actDynamicAction{aCfg: nil},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.aL.cfg()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("actDynamicAction.cfg() = %#v, want %#v", got, tt.want)
}
})
}
}
func TestActDynamicRouteExecuteModifyExistingRoute(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
},
},
}
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.DbCfg().Items)
dbCM := engine.NewDBConnManager(map[string]engine.DataDB{utils.MetaDefault: dataDB}, cfg.DbCfg())
dm := engine.NewDataManager(dbCM, 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)
existingRP := &utils.RouteProfile{
Tenant: "cgrates.org",
ID: "existingroute",
Sorting: "*ascending",
Routes: []*utils.Route{
{
ID: "rtexisting",
FilterIDs: []string{"*string:~*req.OldFilter:original"},
AccountIDs: []string{"1001"},
RateProfileIDs: []string{"rporiginal"},
ResourceIDs: []string{"resoriginal"},
StatIDs: []string{"statoriginal"},
RouteParameters: "originalparam=value",
},
},
}
ctx := context.Background()
dm.SetRouteProfile(ctx, existingRP, true)
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: []*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;routefromswitch@existingroute;;;;*ascending;;rtexisting;*string:~*req.NewFilter:modified;2001&2002;rpmodified;resmodified;statmodified;*modifiedweight&30.0;*modifiedblocker&true;modifiedparam=value;",
},
},
},
},
}
data := utils.MapStorage{
"*req": map[string]any{
utils.AccountField: "1001",
},
}
trgID := "trigger1"
err := act.execute(ctx, data, trgID)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expRpo := &utils.RouteProfileWithAPIOpts{
RouteProfile: &utils.RouteProfile{
Tenant: "cgrates.org",
ID: "existingroute",
Sorting: "*ascending",
Routes: []*utils.Route{
{
ID: "rtexisting",
FilterIDs: []string{"*string:~*req.NewFilter:modified"},
AccountIDs: []string{"2001", "2002"},
RateProfileIDs: []string{"rpmodified"},
ResourceIDs: []string{"resmodified"},
StatIDs: []string{"statmodified"},
Weights: utils.DynamicWeights{
&utils.DynamicWeight{
FilterIDs: []string{"*modifiedweight"},
Weight: 30.0,
},
},
Blockers: utils.DynamicBlockers{
&utils.DynamicBlocker{
FilterIDs: []string{"*modifiedblocker"},
Blocker: true,
},
},
RouteParameters: "modifiedparam=value",
},
},
},
APIOpts: map[string]any{},
}
if !reflect.DeepEqual(rpo, expRpo) {
t.Errorf("Expected <%v>\nReceived\n<%v>", utils.ToJSON(expRpo), utils.ToJSON(rpo))
}
}