Files
cgrates/attributes/attributes_it_test.go
2025-11-12 12:54:46 +01:00

2403 lines
68 KiB
Go

//go:build integration
// +build integration
/*
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 attributes
import (
"path"
"reflect"
"slices"
"sort"
"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"
)
var (
attrCfgPath string
attrCfg *config.CGRConfig
attrRPC *birpc.Client
attrConfigDIR string //run tests for specific configuration
sTestsAttr = []func(t *testing.T){
testAttributesInitCfg,
testAttributesInitDataDb,
testAttributesStartEngine,
testAttributesRPCConn,
// tests for AdminSv1 APIs
testAttributesGetAttributeProfileBeforeSet,
testAttributesGetAttributeProfileIDsBeforeSet,
testAttributesGetAttributeProfileCountBeforeSet,
testAttributesGetAttributeProfilesBeforeSet,
testAttributesSetAttributeProfiles,
testAttributesGetAttributeProfileAfterSet,
testAttributesGetAttributeProfileIDsAfterSet,
testAttributesGetAttributeProfileCountAfterSet,
testAttributesGetAttributeProfilesAfterSet,
testAttributesRemoveAttributeProfile,
testAttributesGetAttributeProfileAfterRemove,
testAttributesGetAttributeProfileIDsAfterRemove,
testAttributesGetAttributeProfileCountAfterRemove,
testAttributesGetAttributeProfilesAfterRemove,
//tests for AttributeSv1 APIs
testAttributeSetAttributeProfileBrokenReference,
testAttributeSGetAttributeForEventMissingEvent,
testAttributeSGetAttributeForEventAnyContext,
testAttributeSGetAttributeForEventSameAnyContext,
testAttributeSGetAttributeForEventNotFound,
testAttributeSGetAttributeForEvent,
testAttributeSetAttributeProfile,
testAttributeProcessEvent,
testAttributeProcessEventWithSearchAndReplace,
testAttributeSProcessWithMultipleRuns,
testAttributeSProcessWithMultipleRuns2,
testAttributeGetAttributeProfileAllIDs,
testAttributeGetAttributeProfileAllCount,
testAttributeRemoveRemainAttributeProfiles,
testAttributeGetAttributeProfileAfterRemove,
// tests for attribute blocker behaviour
testAttributeSetAttributeProfileWithAttrBlockers,
testAttributeSetAttributeProfileWithAttrBlockers2,
testAttributeSetAttributeProfileBlockersBothProfilesProcessRuns,
// Testing index behaviour
testAttributeSSetNonIndexedTypeFilter,
testAttributeSSetIndexedTypeFilter,
testAttributeSClearIndexes,
testAttributeSCheckIndexesSetAttributeProfileWithoutFilters,
// testAttributeSCheckIndexesAddNonIndexedFilter,
testAttributeSCheckIndexesAddIndexedFilters,
testAttributeSCheckIndexesModifyIndexedFilter,
testAttributeSCheckIndexesRemoveAnIndexedFilter,
testAttributeSCheckIndexesRemoveAttributeProfile,
testAttributesKillEngine,
}
)
func TestAttributesIT(t *testing.T) {
switch *utils.DBType {
case utils.MetaInternal:
attrConfigDIR = "attributes_internal"
case utils.MetaMongo:
attrConfigDIR = "attributes_mongo"
case utils.MetaRedis:
attrConfigDIR = "attributes_redis"
case utils.MetaMySQL:
attrConfigDIR = "attributes_mysql"
case utils.MetaPostgres:
attrConfigDIR = "attributes_postgres"
default:
t.Fatal("Unknown Database type")
}
for _, stest := range sTestsAttr {
t.Run(attrConfigDIR, stest)
}
}
func testAttributesInitCfg(t *testing.T) {
var err error
attrCfgPath = path.Join(*utils.DataDir, "conf", "samples", attrConfigDIR)
attrCfg, err = config.NewCGRConfigFromPath(context.Background(), attrCfgPath)
if err != nil {
t.Error(err)
}
}
func testAttributesInitDataDb(t *testing.T) {
if err := engine.InitDB(attrCfg); err != nil {
t.Fatal(err)
}
}
// Start CGR Engine
func testAttributesStartEngine(t *testing.T) {
if _, err := engine.StopStartEngine(attrCfgPath, *utils.WaitRater); err != nil {
t.Fatal(err)
}
}
func testAttributesRPCConn(t *testing.T) {
attrRPC = engine.NewRPCClient(t, attrCfg.ListenCfg(), *utils.Encoding)
}
func testAttributesGetAttributeProfileBeforeSet(t *testing.T) {
var replyAttributeProfile utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: "cgrates.org",
ID: "TestA_ATTRIBUTE1",
}}, &replyAttributeProfile); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
}
}
func testAttributesGetAttributeProfilesBeforeSet(t *testing.T) {
var replyAttributeProfiles *[]*utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfiles,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyAttributeProfiles); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
}
}
func testAttributesGetAttributeProfileIDsBeforeSet(t *testing.T) {
var replyAttributeProfileIDs []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyAttributeProfileIDs); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
}
}
func testAttributesGetAttributeProfileCountBeforeSet(t *testing.T) {
var replyCount int
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyCount); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
} else if replyCount != 0 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
}
func testAttributesSetAttributeProfiles(t *testing.T) {
attributeProfiles := []*utils.APIAttributeProfileWithAPIOpts{
{
APIAttributeProfile: &utils.APIAttributeProfile{
ID: "TestA_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: false,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1001",
FilterIDs: []string{"fltr1"},
},
},
},
},
{
APIAttributeProfile: &utils.APIAttributeProfile{
ID: "TestA_ATTRIBUTE2",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: true,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1002",
FilterIDs: []string{"fltr2"},
},
},
},
},
{
APIAttributeProfile: &utils.APIAttributeProfile{
ID: "TestA_ATTRIBUTE3",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1003",
FilterIDs: []string{"fltr3"},
},
},
},
},
{
APIAttributeProfile: &utils.APIAttributeProfile{
ID: "TestB_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 5,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "2001",
FilterIDs: []string{"fltr4"},
},
},
},
},
{
APIAttributeProfile: &utils.APIAttributeProfile{
ID: "TestB_ATTRIBUTE2",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "2002",
FilterIDs: []string{"fltr5"},
},
},
},
},
}
var reply string
for _, attributeProfile := range attributeProfiles {
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attributeProfile, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
}
}
func testAttributesGetAttributeProfileAfterSet(t *testing.T) {
expectedAttributeProfile := utils.APIAttributeProfile{
ID: "TestA_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: false,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1001",
FilterIDs: []string{"fltr1"},
},
},
}
var replyAttributeProfile utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: "cgrates.org",
ID: "TestA_ATTRIBUTE1",
}}, &replyAttributeProfile); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(replyAttributeProfile, expectedAttributeProfile) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>",
utils.ToJSON(expectedAttributeProfile), utils.ToJSON(replyAttributeProfile))
}
}
func testAttributesGetAttributeProfileIDsAfterSet(t *testing.T) {
expectedIDs := []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE2", "TestA_ATTRIBUTE3", "TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
var replyAttributeProfileIDs []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyAttributeProfileIDs); err != nil {
t.Error(err)
} else {
sort.Strings(replyAttributeProfileIDs)
if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
}
}
expectedIDs = []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE2", "TestA_ATTRIBUTE3"}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestA",
}, &replyAttributeProfileIDs); err != nil {
t.Error(err)
} else {
sort.Strings(replyAttributeProfileIDs)
if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
}
}
expectedIDs = []string{"TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestB",
}, &replyAttributeProfileIDs); err != nil {
t.Error(err)
} else {
sort.Strings(replyAttributeProfileIDs)
if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
}
}
}
func testAttributesGetAttributeProfileCountAfterSet(t *testing.T) {
var replyCount int
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyCount); err != nil {
t.Error(err)
} else if replyCount != 5 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestA",
}, &replyCount); err != nil {
t.Error(err)
} else if replyCount != 3 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestB",
}, &replyCount); err != nil {
t.Error(err)
} else if replyCount != 2 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
}
func testAttributesGetAttributeProfilesAfterSet(t *testing.T) {
expectedAttributeProfiles := []*utils.APIAttributeProfile{
{
ID: "TestA_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: false,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1001",
FilterIDs: []string{"fltr1"},
},
},
},
{
ID: "TestA_ATTRIBUTE2",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: true,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1002",
FilterIDs: []string{"fltr2"},
},
},
},
{
ID: "TestA_ATTRIBUTE3",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1003",
FilterIDs: []string{"fltr3"},
},
},
},
{
ID: "TestB_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 5,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "2001",
FilterIDs: []string{"fltr4"},
},
},
},
{
ID: "TestB_ATTRIBUTE2",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "2002",
FilterIDs: []string{"fltr5"},
},
},
},
}
var replyAttributeProfiles []*utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfiles,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyAttributeProfiles); err != nil {
t.Error(err)
} else {
sort.Slice(replyAttributeProfiles, func(i, j int) bool {
return replyAttributeProfiles[i].ID < replyAttributeProfiles[j].ID
})
if !reflect.DeepEqual(replyAttributeProfiles, expectedAttributeProfiles) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>",
utils.ToJSON(expectedAttributeProfiles), utils.ToJSON(replyAttributeProfiles))
}
}
}
func testAttributesRemoveAttributeProfile(t *testing.T) {
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: "cgrates.org",
ID: "TestA_ATTRIBUTE2",
}}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply returned:", reply)
}
}
func testAttributesGetAttributeProfileAfterRemove(t *testing.T) {
var replyAttributeProfile utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: "cgrates.org",
ID: "TestA_Attribute2",
}}, &replyAttributeProfile); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
}
}
func testAttributesGetAttributeProfileIDsAfterRemove(t *testing.T) {
expectedIDs := []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE3", "TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
var replyAttributeProfileIDs []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyAttributeProfileIDs); err != nil {
t.Error(err)
} else {
sort.Strings(replyAttributeProfileIDs)
if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
}
}
expectedIDs = []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE3"}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestA",
}, &replyAttributeProfileIDs); err != nil {
t.Error(err)
} else {
sort.Strings(replyAttributeProfileIDs)
if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
}
}
expectedIDs = []string{"TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestB",
}, &replyAttributeProfileIDs); err != nil {
t.Error(err)
} else {
sort.Strings(replyAttributeProfileIDs)
if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
}
}
}
func testAttributesGetAttributeProfileCountAfterRemove(t *testing.T) {
var replyCount int
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyCount); err != nil {
t.Error(err)
} else if replyCount != 4 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestA",
}, &replyCount); err != nil {
t.Error(err)
} else if replyCount != 2 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
ItemsPrefix: "TestB",
}, &replyCount); err != nil {
t.Error(err)
} else if replyCount != 2 {
t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
}
}
func testAttributesGetAttributeProfilesAfterRemove(t *testing.T) {
expectedAttributeProfiles := []*utils.APIAttributeProfile{
{
ID: "TestA_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: false,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1001",
FilterIDs: []string{"fltr1"},
},
},
},
{
ID: "TestA_ATTRIBUTE3",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "1003",
FilterIDs: []string{"fltr3"},
},
},
},
{
ID: "TestB_ATTRIBUTE1",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 5,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "2001",
FilterIDs: []string{"fltr4"},
},
},
},
{
ID: "TestB_ATTRIBUTE2",
Tenant: "cgrates.org",
FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.Account",
Type: utils.MetaConstant,
Value: "2002",
FilterIDs: []string{"fltr5"},
},
},
},
}
var replyAttributeProfiles []*utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfiles,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &replyAttributeProfiles); err != nil {
t.Error(err)
} else {
sort.Slice(replyAttributeProfiles, func(i, j int) bool {
return replyAttributeProfiles[i].ID < replyAttributeProfiles[j].ID
})
if !reflect.DeepEqual(replyAttributeProfiles, expectedAttributeProfiles) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>",
utils.ToJSON(expectedAttributeProfiles), utils.ToJSON(replyAttributeProfiles))
}
}
}
func testAttributeSetAttributeProfileBrokenReference(t *testing.T) {
attrPrf := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: utils.CGRateSorg,
ID: "TEST_ATTRIBUTES_IT_TEST_SECOND",
FilterIDs: []string{"invalid_filter_format", "*string:~*opts.*context:*sessions"},
Attributes: []*utils.ExternalAttribute{
{
Path: "*tenant",
Type: utils.MetaConstant,
Value: "cgrates.itsyscom",
},
},
},
}
var reply string
expectedErr := "SERVER_ERROR: broken reference to filter: <invalid_filter_format> for item with ID: cgrates.org:TEST_ATTRIBUTES_IT_TEST_SECOND"
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf, &reply); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func testAttributeSGetAttributeForEventMissingEvent(t *testing.T) {
var rplyEv AttrSProcessEventReply
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
nil, &rplyEv); err == nil ||
err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
func testAttributeSGetAttributeForEventAnyContext(t *testing.T) {
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "testAttributeSGetAttributeForEventWihMetaAnyContext",
Event: map[string]any{
utils.AccountField: "dan",
utils.Destination: "+491511231234",
utils.ToR: "*voice",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaCDRs,
},
}
eAttrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: ev.Tenant,
ID: "ATTR_2",
FilterIDs: []string{"*string:~*req.Account:dan", "*prefix:~*req.Destination:+4915", "*exists:~*opts.*usage:", "*notexists:~*req.RequestType:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + utils.AccountField,
Type: utils.MetaConstant,
Value: "1001",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
var result string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
eAttrPrf2, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
var reply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_2"}}, &reply); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eAttrPrf2.APIAttributeProfile, reply) {
t.Errorf("Expecting : %+v, received: %+v", eAttrPrf2.APIAttributeProfile, reply)
}
var attrReply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
ev, &attrReply); err != nil {
t.Fatal(err)
}
expAttrFromEv := &utils.APIAttributeProfile{
Tenant: ev.Tenant,
ID: "ATTR_2",
FilterIDs: []string{"*string:~*req.Account:dan", "*prefix:~*req.Destination:+4915", "*exists:~*opts.*usage:", "*notexists:~*req.RequestType:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + utils.AccountField,
Type: utils.MetaConstant,
Value: "1001",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
}
if !reflect.DeepEqual(expAttrFromEv, attrReply) {
t.Errorf("Expecting: %s, received: %s", utils.ToJSON(expAttrFromEv), utils.ToJSON(attrReply))
}
}
func testAttributeSGetAttributeForEventSameAnyContext(t *testing.T) {
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "testAttributeSGetAttributeForEventWihMetaAnyContext",
Event: map[string]any{
utils.AccountField: "dan",
utils.Destination: "+491511231234",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
},
}
var attrReply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
ev, &attrReply); err != nil {
t.Fatal(err)
}
expAttrFromEv := &utils.APIAttributeProfile{
Tenant: ev.Tenant,
ID: "ATTR_2",
FilterIDs: []string{"*string:~*req.Account:dan", "*prefix:~*req.Destination:+4915", "*exists:~*opts.*usage:", "*notexists:~*req.RequestType:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + utils.AccountField,
Type: utils.MetaConstant,
Value: "1001",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
}
if !reflect.DeepEqual(expAttrFromEv, attrReply) {
t.Errorf("Expecting: %s, received: %s", utils.ToJSON(expAttrFromEv), utils.ToJSON(attrReply))
}
}
func testAttributeSGetAttributeForEventNotFound(t *testing.T) {
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "testAttributeSGetAttributeForEventWihMetaAnyContext",
Event: map[string]any{
utils.AccountField: "dann",
utils.Destination: "+491511231234",
},
APIOpts: map[string]any{
utils.OptsContext: utils.MetaCDRs,
},
}
var attrReply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
ev, &attrReply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
func testAttributeSGetAttributeForEvent(t *testing.T) {
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "testAttributeSGetAttributeForEvent",
Event: map[string]any{
utils.AccountField: "1007",
utils.Destination: "+491511231234",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaSessionS,
},
}
eAttrPrf := &utils.APIAttributeProfile{
Tenant: ev.Tenant,
ID: "ATTR_1",
FilterIDs: []string{"*string:~*req.Account:1007", "*string:~*opts.*context:*cdrs|*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + utils.AccountField,
Value: "1001",
Type: utils.MetaConstant,
FilterIDs: []string{},
},
{
Path: utils.MetaReq + utils.NestingSep + utils.Subject,
Value: "1001",
Type: utils.MetaConstant,
FilterIDs: []string{},
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
}
if *utils.Encoding == utils.MetaGOB {
eAttrPrf.Attributes[0].FilterIDs = nil
eAttrPrf.Attributes[1].FilterIDs = nil
}
var result string
eAttrPrfApi := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: ev.Tenant,
ID: "ATTR_1",
FilterIDs: []string{"*string:~*req.Account:1007", "*string:~*opts.*context:*cdrs|*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + utils.AccountField,
Value: "1001",
Type: utils.MetaConstant,
FilterIDs: []string{},
},
{
Path: utils.MetaReq + utils.NestingSep + utils.Subject,
Value: "1001",
Type: utils.MetaConstant,
FilterIDs: []string{},
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
eAttrPrfApi, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
var attrReply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
ev, &attrReply); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eAttrPrf, attrReply) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eAttrPrf), utils.ToJSON(attrReply))
}
ev.Tenant = utils.EmptyString
ev.ID = "randomID"
var attrPrf *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
ev, &attrPrf); err != nil {
t.Fatal(err)
}
// Populate private variables in RSRParsers
if !reflect.DeepEqual(eAttrPrf, attrPrf) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eAttrPrf), utils.ToJSON(attrPrf))
}
}
func testAttributeSetAttributeProfile(t *testing.T) {
attrPrf := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: utils.CGRateSorg,
ID: "TEST_ATTRIBUTES_IT_TEST",
FilterIDs: []string{"*string:~*req.Account:1002", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.AccountField,
Type: utils.MetaConstant,
Value: "1002",
},
{
Path: "*tenant",
Type: utils.MetaConstant,
Value: "cgrates.itsyscom",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
expectedAttr := utils.APIAttributeProfile{
Tenant: utils.CGRateSorg,
ID: "TEST_ATTRIBUTES_IT_TEST",
FilterIDs: []string{"*string:~*req.Account:1002", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.AccountField,
Type: utils.MetaConstant,
Value: "1002",
},
{
Path: "*tenant",
Type: utils.MetaConstant,
Value: "cgrates.itsyscom",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
}
var result utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: utils.CGRateSorg,
ID: "TEST_ATTRIBUTES_IT_TEST",
},
}, &result); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(result, expectedAttr) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedAttr), utils.ToJSON(result))
}
}
func testAttributeProcessEvent(t *testing.T) {
var reply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_1"}}, &reply); err != nil {
t.Fatal(err)
}
args := &utils.CGREvent{
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "1002",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaCDRs,
},
}
expEvReply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:TEST_ATTRIBUTES_IT_TEST",
Fields: []string{"*tenant", utils.AccountField},
},
},
CGREvent: &utils.CGREvent{
Tenant: "cgrates.itsyscom",
Event: map[string]any{
utils.AccountField: "1002",
utils.ToR: utils.MetaVoice,
},
APIOpts: map[string]any{
utils.MetaUsage: float64(10 * time.Second),
utils.OptsContext: utils.MetaCDRs,
},
},
}
evRply := &AttrSProcessEventReply{}
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
args, &evRply); err != nil {
t.Error(err)
} else {
sort.Strings(expEvReply.AlteredFields[0].Fields)
sort.Strings(evRply.AlteredFields[0].Fields)
if !reflect.DeepEqual(evRply, expEvReply) {
t.Errorf("Expected %+v, received %+v", expEvReply, evRply)
}
}
}
func testAttributeProcessEventWithSearchAndReplace(t *testing.T) {
attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_Search_and_replace",
FilterIDs: []string{"*string:~*req.Category:call", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Category",
Value: "~*req.Category:s/(.*)/${1}_suffix/",
},
},
Blockers: utils.DynamicBlockers{
{
Blocker: true,
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
var result string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf1, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
attrArgs := &utils.CGREvent{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "HeaderEventForAttribute",
Event: map[string]any{
"Category": "call",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaSessionS,
},
}
eRply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_Search_and_replace",
Fields: []string{"*req.Category"},
},
},
CGREvent: &utils.CGREvent{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "HeaderEventForAttribute",
Event: map[string]any{
"Category": "call_suffix",
},
APIOpts: map[string]any{
utils.MetaUsage: float64(10 * time.Second),
utils.OptsContext: utils.MetaSessionS,
},
},
}
var rplyEv *AttrSProcessEventReply
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
attrArgs, &rplyEv); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eRply, rplyEv) {
t.Errorf("Expecting: %s, received: %s",
utils.ToJSON(eRply), utils.ToJSON(rplyEv))
}
}
func testAttributeSProcessWithMultipleRuns(t *testing.T) {
attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_1",
FilterIDs: []string{"*string:~*req.InitialField:InitialValue", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Field1",
Value: "Value1",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
attrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_2",
FilterIDs: []string{"*string:~*req.Field1:Value1", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Field2",
Value: "Value2",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
attrPrf3 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_3",
FilterIDs: []string{"*string:~*req.NotFound:NotFound", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Field3",
Value: "Value3",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
// Add attribute in DM
var result string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf1, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf2, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf3, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
attrArgs := &utils.CGREvent{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: utils.GenUUID(),
Event: map[string]any{
"InitialField": "InitialValue",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaSessionS,
utils.OptsAttributesProcessRuns: 4,
},
}
eRply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_1",
Fields: []string{"*req.Field1"},
},
{
MatchedProfileID: "cgrates.org:ATTR_2",
Fields: []string{"*req.Field2"},
},
{
MatchedProfileID: "cgrates.org:ATTR_1",
Fields: []string{"*req.Field1"},
},
{
MatchedProfileID: "cgrates.org:ATTR_2",
Fields: []string{"*req.Field2"},
},
},
CGREvent: &utils.CGREvent{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: utils.GenUUID(),
Event: map[string]any{
"InitialField": "InitialValue",
"Field1": "Value1",
"Field2": "Value2",
},
},
}
var rplyEv AttrSProcessEventReply
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
attrArgs, &rplyEv); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(eRply.AlteredFields, rplyEv.AlteredFields) {
t.Errorf("Expecting %+v, received: %+v", eRply.AlteredFields, rplyEv.AlteredFields)
} else if !reflect.DeepEqual(eRply.CGREvent.Event, rplyEv.CGREvent.Event) {
t.Errorf("Expecting %+v, received: %+v", eRply.CGREvent.Event, rplyEv.CGREvent.Event)
}
}
func testAttributeSProcessWithMultipleRuns2(t *testing.T) {
attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_1",
FilterIDs: []string{"*string:~*req.InitialField:InitialValue", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Field1",
Value: "Value1",
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
},
}
attrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_2",
FilterIDs: []string{"*string:~*req.Field1:Value1", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Field2",
Value: "Value2",
},
},
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
},
}
attrPrf3 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_3",
FilterIDs: []string{"*string:~*req.Field2:Value2", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "Field3",
Value: "Value3",
},
},
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
},
}
// Add attributeProfiles
var result string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf1, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf2, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf3, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
attrArgs := &utils.CGREvent{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: utils.GenUUID(),
Event: map[string]any{
"InitialField": "InitialValue",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaSessionS,
utils.OptsAttributesProcessRuns: 4,
},
}
eRply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_1",
Fields: []string{"*req.Field1"},
},
{
MatchedProfileID: "cgrates.org:ATTR_2",
Fields: []string{"*req.Field2"},
},
{
MatchedProfileID: "cgrates.org:ATTR_2",
Fields: []string{"*req.Field2"},
},
{
MatchedProfileID: "cgrates.org:ATTR_3",
Fields: []string{"*req.Field3"},
},
},
CGREvent: &utils.CGREvent{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: utils.GenUUID(),
Event: map[string]any{
"InitialField": "InitialValue",
"Field1": "Value1",
"Field2": "Value2",
"Field3": "Value3",
},
APIOpts: map[string]any{
utils.MetaUsage: 10 * time.Second,
utils.OptsContext: utils.MetaSessionS,
utils.OptsAttributesProcessRuns: 4,
},
},
}
var rplyEv AttrSProcessEventReply
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
attrArgs, &rplyEv); err != nil {
t.Fatal(err)
}
sort.Slice(rplyEv.AlteredFields, func(i, j int) bool {
return rplyEv.AlteredFields[i].MatchedProfileID < rplyEv.AlteredFields[j].MatchedProfileID
})
if !reflect.DeepEqual(eRply.AlteredFields, rplyEv.AlteredFields) {
t.Errorf("Expecting %+v, received: %+v", utils.ToJSON(eRply.AlteredFields), utils.ToJSON(rplyEv.AlteredFields))
} else if !reflect.DeepEqual(eRply.CGREvent.Event, rplyEv.CGREvent.Event) {
t.Errorf("Expecting %+v, received: %+v", eRply.CGREvent.Event, rplyEv.CGREvent.Event)
}
}
func testAttributeGetAttributeProfileAllIDs(t *testing.T) {
var rply []string
expectedIds := []string{"ATTR_1", "ATTR_2", "ATTR_3", "ATTR_Search_and_replace", "TEST_ATTRIBUTES_IT_TEST", "TestA_ATTRIBUTE1",
"TestA_ATTRIBUTE3", "TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"} //"TEST_ATTRIBUTES_IT_TEST_SECOND"}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &rply); err != nil {
t.Error(err)
} else {
sort.Strings(rply)
sort.Strings(expectedIds)
if !reflect.DeepEqual(expectedIds, rply) {
t.Errorf("Expected %+v, received %+v", expectedIds, rply)
}
}
}
func testAttributeGetAttributeProfileAllCount(t *testing.T) {
var rply int
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: "cgrates.org",
},
}, &rply); err != nil {
t.Error(err)
} else if rply != 9 {
t.Errorf("Expected %+v, received %+v", 9, rply)
}
}
func testAttributeRemoveRemainAttributeProfiles(t *testing.T) {
var reply string
args := &utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
ID: "ATTR_1",
Tenant: utils.CGRateSorg,
},
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
}
args = &utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
ID: "ATTR_2",
Tenant: utils.CGRateSorg,
},
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
}
args = &utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
ID: "ATTR_3",
Tenant: utils.CGRateSorg,
},
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
}
args = &utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
ID: "ATTR_Search_and_replace",
Tenant: utils.CGRateSorg,
},
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
}
}
func testAttributeGetAttributeProfileAfterRemove(t *testing.T) {
var reply *utils.APIAttributeProfile
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: utils.CGRateSorg,
ID: "ATTR_Search_and_replace",
},
}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: utils.CGRateSorg,
ID: "ATTR_1",
},
}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: utils.CGRateSorg,
ID: "ATTR_2",
},
}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
&utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: utils.CGRateSorg,
ID: "ATTR_3",
},
}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
}
func testAttributeSetAttributeProfileWithAttrBlockers(t *testing.T) {
// the blocker on the profile is false
attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_WITH_BLOCKER_TRUE",
FilterIDs: []string{"*string:~*req.Blockers:*exists", "*eq:~*opts.*attrProcessRuns:2"},
Blockers: utils.DynamicBlockers{
{
Blocker: false,
},
},
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
Attributes: []*utils.ExternalAttribute{
{
Path: utils.MetaReq + utils.NestingSep + "ToR",
Value: "*sms",
},
{
Path: utils.MetaOpts + utils.NestingSep + "*chargers",
Value: "true",
},
{
Blockers: utils.DynamicBlockers{
{
FilterIDs: []string{"*prefix:~*req.Destination:4433"},
Blocker: true,
},
},
Path: utils.MetaReq + utils.NestingSep + "RequestType",
Value: "*rated",
},
{
Path: utils.MetaOpts + utils.NestingSep + "*usage",
Value: "1m",
},
},
},
}
// here the the blocker on the profile is true
attrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
ID: "ATTR_WITH_BLOCKER",
FilterIDs: []string{"*string:~*req.Blockers:*exists",
"*notexists:~*opts.*usage:"},
Blockers: utils.DynamicBlockers{
{
Blocker: true,
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Attributes: []*utils.ExternalAttribute{
{
Blockers: utils.DynamicBlockers{
{
FilterIDs: []string{"*prefix:~*req.Destination:4433"},
Blocker: true,
},
},
Path: utils.MetaReq + utils.NestingSep + "Account",
Value: "10093",
},
{
Path: utils.MetaOpts + utils.NestingSep + "*rates",
Value: "true",
},
},
},
}
// Add attributeProfiles
var result string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf1, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf2, &result); err != nil {
t.Error(err)
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
// first we will process the second attribute with true BLOCKER on profile, but true on Attributes if the Destination prefix is 4433(FOR NOW THE DESTINATION WILL BE EMPTY)
args := &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "1002",
"Blockers": "*exists",
},
APIOpts: map[string]any{},
}
expEvReply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
Fields: []string{"*opts.*rates", "*req.Account"},
},
},
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "10093",
"Blockers": "*exists",
},
APIOpts: map[string]any{
utils.MetaRates: "true",
},
},
}
evRply := &AttrSProcessEventReply{}
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
args, &evRply); err != nil {
t.Error(err)
} else {
sort.Strings(expEvReply.AlteredFields[0].Fields)
sort.Strings(evRply.AlteredFields[0].Fields)
if !reflect.DeepEqual(evRply, expEvReply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
}
}
}
func testAttributeSetAttributeProfileWithAttrBlockers2(t *testing.T) {
// first we will process the second attribute with true BLOCKER on profile, but true on Attributes if the Destination prefix is 4433(NOW WE WILL POPULATE THE DESTINATION, AND THE BLOCKER WILL STOP THE NEXT ATTRIBUTES)
args := &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "1002",
"Blockers": "*exists",
utils.Destination: "4433254",
},
APIOpts: map[string]any{},
}
expEvReply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
Fields: []string{"*req.Account"},
},
},
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "10093",
"Blockers": "*exists",
utils.Destination: "4433254",
},
// now *rates was not processde ebcause the blocker amtched the filter of Destination
APIOpts: map[string]any{},
},
}
evRply := &AttrSProcessEventReply{}
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
args, &evRply); err != nil {
t.Error(err)
} else {
sort.Strings(expEvReply.AlteredFields[0].Fields)
sort.Strings(evRply.AlteredFields[0].Fields)
if !reflect.DeepEqual(evRply, expEvReply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
}
}
}
func testAttributeSetAttributeProfileBlockersBothProfilesProcessRuns(t *testing.T) {
// now we will process both attributes that matched, but blokcers on the attributes will be felt and not all attributes from the list were processed
args := &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "1002",
"Blockers": "*exists",
utils.Destination: "4433254",
},
APIOpts: map[string]any{
utils.OptsAttributesProcessRuns: 2,
},
}
expEvReply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER_TRUE",
Fields: []string{"*opts.*chargers", "*req.RequestType", "*req.ToR"},
},
{
MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
Fields: []string{"*req.Account"},
},
},
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: "*sms",
utils.AccountField: "10093",
"Blockers": "*exists",
utils.Destination: "4433254",
utils.RequestType: "*rated",
},
// now *rates was not processde ebcause the blocker amtched the filter of Destination
APIOpts: map[string]any{
utils.OptsAttributesProcessRuns: 2.,
utils.MetaChargers: "true",
},
},
}
evRply := &AttrSProcessEventReply{}
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
args, &evRply); err != nil {
t.Error(err)
} else {
sort.Strings(expEvReply.AlteredFields[0].Fields)
sort.Strings(evRply.AlteredFields[0].Fields)
if !reflect.DeepEqual(evRply, expEvReply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
}
}
}
func testAttributeSSetNonIndexedTypeFilter(t *testing.T) {
filter := &engine.FilterWithAPIOpts{
Filter: &engine.Filter{
Tenant: "cgrates.org",
ID: "NONINDEXED_FLTR_TYPE",
Rules: []*engine.FilterRule{
{
Type: utils.MetaGreaterThan,
Element: utils.Cost,
Values: []string{"10"},
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetFilter, filter,
&reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply returned")
}
args := &utils.CGREvent{
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "1002",
"Blockers": "*exists",
utils.Destination: "44322",
},
APIOpts: map[string]any{},
}
expEvReply := &AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
Fields: []string{"*opts.*rates", "*req.Account"},
},
},
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.AccountField: "10093",
"Blockers": "*exists",
utils.Destination: "44322",
},
APIOpts: map[string]any{
utils.MetaRates: "true",
},
},
}
evRply := &AttrSProcessEventReply{}
if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
args, &evRply); err != nil {
t.Error(err)
} else {
sort.Strings(expEvReply.AlteredFields[0].Fields)
sort.Strings(evRply.AlteredFields[0].Fields)
if !reflect.DeepEqual(evRply, expEvReply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
}
}
}
func testAttributeSSetIndexedTypeFilter(t *testing.T) {
filter := &engine.FilterWithAPIOpts{
Filter: &engine.Filter{
Tenant: "cgrates.org",
ID: "INDEXED_FLTR_TYPE",
Rules: []*engine.FilterRule{
{
Type: utils.MetaPrefix,
Element: "~*req.Account",
Values: []string{"10"},
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetFilter, filter,
&reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply: ", reply)
}
}
func testAttributeSClearIndexes(t *testing.T) {
// args := &apis.AttrRemFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// using a map instead because the object causes import cycle.
args := map[string]string{
"Tenant": "cgrates.org",
"ItemType": utils.MetaAttributes,
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveFilterIndexes,
args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply: ", reply)
}
}
func testAttributeSCheckIndexesSetAttributeProfileWithoutFilters(t *testing.T) {
attrPrf := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_TEST",
Attributes: []*utils.ExternalAttribute{
{
Type: utils.MetaConstant,
Path: "~*req.Account",
Value: "1002",
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
expIdx := []string{"*none:*any:*any:ATTR_TEST"}
// args := &apis.AttrGetFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// using a map instead because the object causes import cycle.
args := map[string]string{
"Tenant": "cgrates.org",
"ItemType": utils.MetaAttributes,
}
var replyIdx []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
args, &replyIdx); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(replyIdx, expIdx) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
}
}
// func testAttributeSCheckIndexesAddNonIndexedFilter(t *testing.T) {
// attrPrf := &utils.APIAttributeProfileWithAPIOpts{
// APIAttributeProfile: &utils.APIAttributeProfile{
// Tenant: "cgrates.org",
// ID: "ATTR_TEST",
// FilterIDs: []string{"NONINDEXED_FLTR_TYPE"},
// Attributes: []*utils.ExternalAttribute{
// {
// Type: utils.MetaConstant,
// Path: "~*req.Account",
// Value: "1002",
// },
// },
// },
// }
// var reply string
// if err := attrSRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
// attrPrf, &reply); err != nil {
// t.Error(err)
// } else if reply != utils.OK {
// t.Error(err)
// }
// expIdx := []string{"*none:*any:*any:ATTR_TEST"}
// args := &AttrGetFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// var replyIdx []string
// if err := attrSRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
// args, &replyIdx); err != nil {
// t.Error(err)
// } else if !reflect.DeepEqual(replyIdx, expIdx) {
// t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
// }
// }
func testAttributeSCheckIndexesAddIndexedFilters(t *testing.T) {
attrPrf := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_TEST",
FilterIDs: []string{"NONINDEXED_FLTR_TYPE", "INDEXED_FLTR_TYPE"},
Attributes: []*utils.ExternalAttribute{
{
Type: utils.MetaConstant,
Path: "~*req.Account",
Value: "1002",
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
expIdx := []string{"*prefix:*req.Account:10:ATTR_TEST"}
// args := &apis.AttrGetFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// using a map instead because the object causes import cycle.
args := map[string]string{
"Tenant": "cgrates.org",
"ItemType": utils.MetaAttributes,
}
var replyIdx []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
args, &replyIdx); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(replyIdx, expIdx) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
}
attrPrf = &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_TEST",
FilterIDs: []string{"NONINDEXED_FLTR_TYPE", "INDEXED_FLTR_TYPE", "*string:~*req.Category:call"},
Attributes: []*utils.ExternalAttribute{
{
Type: utils.MetaConstant,
Path: "~*req.Account",
Value: "1002",
},
},
},
}
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
expIdx = []string{"*prefix:*req.Account:10:ATTR_TEST", "*string:*req.Category:call:ATTR_TEST"}
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
args, &replyIdx); err != nil {
t.Error(err)
} else {
sort.Strings(replyIdx)
if !reflect.DeepEqual(replyIdx, expIdx) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
}
}
}
func testAttributeSCheckIndexesModifyIndexedFilter(t *testing.T) {
filter := &engine.FilterWithAPIOpts{
Filter: &engine.Filter{
Tenant: "cgrates.org",
ID: "INDEXED_FLTR_TYPE",
Rules: []*engine.FilterRule{
{
Type: utils.MetaSuffix,
Element: "~*req.Subject",
Values: []string{"01"},
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetFilter, filter,
&reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply: ", reply)
}
expIdx := []string{"*string:*req.Category:call:ATTR_TEST", "*suffix:*req.Subject:01:ATTR_TEST"}
// args := &apis.AttrGetFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// using a map instead because the object causes import cycle.
args := map[string]string{
"Tenant": "cgrates.org",
"ItemType": utils.MetaAttributes,
}
var replyIdx []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
args, &replyIdx); err != nil {
t.Error(err)
} else {
sort.Strings(replyIdx)
if !reflect.DeepEqual(replyIdx, expIdx) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
}
}
}
func testAttributeSCheckIndexesRemoveAnIndexedFilter(t *testing.T) {
attrPrf := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_TEST",
FilterIDs: []string{"NONINDEXED_FLTR_TYPE", "INDEXED_FLTR_TYPE"},
Attributes: []*utils.ExternalAttribute{
{
Type: utils.MetaConstant,
Path: "~*req.Account",
Value: "1002",
},
},
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
attrPrf, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
expIdx := []string{"*suffix:*req.Subject:01:ATTR_TEST"}
// args := &apis.AttrGetFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// using a map instead because the object causes import cycle.
args := map[string]string{
"Tenant": "cgrates.org",
"ItemType": utils.MetaAttributes,
}
var replyIdx []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
args, &replyIdx); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(replyIdx, expIdx) {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
}
}
func testAttributeSCheckIndexesRemoveAttributeProfile(t *testing.T) {
argsRem := &utils.TenantIDWithAPIOpts{
TenantID: &utils.TenantID{
Tenant: "cgrates.org",
ID: "ATTR_TEST",
},
}
var reply string
if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
argsRem, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error(err)
}
// args := &apis.AttrGetFilterIndexes{
// Tenant: "cgrates.org",
// ItemType: utils.MetaAttributes,
// }
// using a map instead because the object causes import cycle.
args := map[string]string{
"Tenant": "cgrates.org",
"ItemType": utils.MetaAttributes,
}
var replyIdx []string
if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
args, &replyIdx); err == nil || err.Error() != utils.ErrNotFound.Error() {
t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
}
}
// Kill the engine when it is about to be finished
func testAttributesKillEngine(t *testing.T) {
if err := engine.KillEngine(100); err != nil {
t.Error(err)
}
}
func TestAttributesArith(t *testing.T) {
var dbCfg engine.DBCfg
switch *utils.DBType {
case utils.MetaInternal:
dbCfg = engine.InternalDBCfg
case utils.MetaMySQL, utils.MetaRedis, utils.MetaMongo, utils.MetaPostgres:
t.SkipNow()
default:
t.Fatal("unsupported dbtype value")
}
content := `{
"attributes": {
"enabled": true
},
"db": {
"db_conns": {
"*default": {
"db_type": "*internal",
"opts":{
"internalDBRewriteInterval": "0s",
"internalDBDumpInterval": "0s"
}
}
},
},
"admins": {
"enabled": true
}
}`
tpFiles := map[string]string{
utils.AttributesCsv: `#Tenant,ID,FilterIDs,Weights,Blockers,AttributeFilterIDs,AttributeBlockers,Path,Type,Value
cgrates.org,ATTR_ARITH,*string:~*req.AttrSource:csv,,,,,,,
cgrates.org,ATTR_ARITH,,,,,,*req.3*4,*multiply,3;4
cgrates.org,ATTR_ARITH,,,,,,*req.12/4,*divide,12;4
cgrates.org,ATTR_ARITH,,,,,,*req.3+4,*sum,3;4
cgrates.org,ATTR_ARITH,,,,,,*req.3-4,*difference,3;4
cgrates.org,ATTR_ARITH,,,,,,*req.MultiplyBetweenVariables,*multiply,~*req.Elem1;~*req.Elem2`,
}
testEnv := engine.TestEngine{
ConfigJSON: content,
TpFiles: tpFiles,
DBCfg: dbCfg,
Encoding: *utils.Encoding,
}
client, _ := testEnv.Run(t)
t.Run("SetAttributesThroughAPI", func(t *testing.T) {
var reply string
attrPrf := &utils.APIAttributeProfileWithAPIOpts{
APIAttributeProfile: &utils.APIAttributeProfile{
Tenant: "cgrates.org",
ID: "ATTR_API",
FilterIDs: []string{"*string:~*req.AttrSource:api"},
Attributes: []*utils.ExternalAttribute{
{
Path: "*req.3*4",
Type: utils.MetaMultiply,
Value: "3;4",
},
{
Path: "*req.12/4",
Type: utils.MetaDivide,
Value: "12;4",
},
{
Path: "*req.3+4",
Type: utils.MetaSum,
Value: "3;4",
},
{
Path: "*req.3-4",
Type: utils.MetaDifference,
Value: "3;4",
},
{
Path: "*req.MultiplyBetweenVariables",
Type: utils.MetaMultiply,
Value: "~*req.Elem1;~*req.Elem2",
},
},
},
}
if err := client.Call(context.Background(), utils.AdminSv1SetAttributeProfile, attrPrf, &reply); err != nil {
t.Error(err)
}
})
t.Run("CheckFieldsAlteredByAttributeS", func(t *testing.T) {
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "event_test",
Event: map[string]any{
"AttrSource": "api",
"Elem1": "3",
"Elem2": "4",
},
}
expected := AttrSProcessEventReply{
AlteredFields: []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_API",
Fields: []string{"*req.12/4", "*req.3*4", "*req.3+4", "*req.3-4", "*req.MultiplyBetweenVariables"},
},
},
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "event_test",
Event: map[string]any{
"AttrSource": "api",
"Elem1": "3",
"Elem2": "4",
"12/4": "3",
"3*4": "12",
"3+4": "7",
"3-4": "-1",
"MultiplyBetweenVariables": "12",
},
APIOpts: make(map[string]any),
},
}
var reply AttrSProcessEventReply
if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
ev, &reply); err != nil {
t.Error(err)
} else {
sort.Strings(expected.AlteredFields[0].Fields)
sort.Strings(reply.AlteredFields[0].Fields)
if !reflect.DeepEqual(expected, reply) {
t.Errorf("\nexpected: %s, \nreceived: %s",
utils.ToJSON(expected), utils.ToJSON(reply))
}
}
ev.Event["AttrSource"] = "csv"
expected.AlteredFields = []*FieldsAltered{
{
MatchedProfileID: "cgrates.org:ATTR_ARITH",
Fields: []string{"*req.12/4", "*req.3*4", "*req.3+4", "*req.3-4", "*req.MultiplyBetweenVariables"},
},
}
expected.CGREvent.Event = map[string]any{
"AttrSource": "csv",
"Elem1": "3",
"Elem2": "4",
"12/4": "3",
"3*4": "12",
"3+4": "7",
"3-4": "-1",
"MultiplyBetweenVariables": "12",
}
reply = AttrSProcessEventReply{}
if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
ev, &reply); err != nil {
t.Error(err)
} else {
sort.Strings(expected.AlteredFields[0].Fields)
sort.Strings(reply.AlteredFields[0].Fields)
if !reflect.DeepEqual(expected, reply) {
t.Errorf("\nexpected: %s, \nreceived: %s",
utils.ToJSON(expected), utils.ToJSON(reply))
}
}
})
}