diff --git a/config/configsanity.go b/config/configsanity.go index 12d78d2f2..1c3715373 100644 --- a/config/configsanity.go +++ b/config/configsanity.go @@ -711,7 +711,8 @@ func (cfg *CGRConfig) checkConfigSanity() error { field.Type == utils.MetaMultiply || field.Type == utils.MetaDivide || field.Type == utils.MetaValueExponent || - field.Type == utils.MetaUnixTimestamp { + field.Type == utils.MetaUnixTimestamp || + field.Type == utils.MetaSIPCID { for _, val := range field.Value { if err := utils.IsPathValidForExporters(val.path); err != nil { return fmt.Errorf("<%s> %s for %s at %s of %s", utils.ERs, err, val.path, utils.Values, utils.CacheDumpFieldsCfg) @@ -739,7 +740,8 @@ func (cfg *CGRConfig) checkConfigSanity() error { field.Type == utils.MetaMultiply || field.Type == utils.MetaDivide || field.Type == utils.MetaValueExponent || - field.Type == utils.MetaUnixTimestamp { + field.Type == utils.MetaUnixTimestamp || + field.Type == utils.MetaSIPCID { for _, val := range field.Value { if err := utils.IsPathValidForExporters(val.path); err != nil { return fmt.Errorf("<%s> %s for %s at %s of %s", utils.ERs, err, val.path, utils.Values, utils.FieldsCfg) @@ -809,7 +811,8 @@ func (cfg *CGRConfig) checkConfigSanity() error { field.Type == utils.MetaMultiply || field.Type == utils.MetaDivide || field.Type == utils.MetaValueExponent || - field.Type == utils.MetaUnixTimestamp { + field.Type == utils.MetaUnixTimestamp || + field.Type == utils.MetaSIPCID { for _, val := range field.Value { if err := utils.IsPathValidForExporters(val.path); err != nil { return fmt.Errorf("<%s> %s for %s at %s of %s", utils.EEs, err, val.path, utils.Values, utils.FieldsCfg) diff --git a/engine/attributes.go b/engine/attributes.go index a6a6c4383..08fcba5b6 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -21,6 +21,7 @@ package engine import ( "fmt" "math" + "sort" "strconv" "strings" "time" @@ -505,6 +506,15 @@ func ParseAttribute(dp utils.DataProvider, attrType, path string, value config.R reqNr = 0 } return usedCCTime + time.Duration(debitItvl.Nanoseconds()*reqNr), nil + case utils.MetaSIPCID: + values := make([]string, len(value)) + for i, val := range value { + if values[i], err = val.ParseDataProvider(dp); err != nil { + return + } + } + sort.Strings(values[1:]) + out = strings.Join(values, utils.InfieldSep) default: return utils.EmptyString, fmt.Errorf("unsupported type: <%s>", attrType) } diff --git a/engine/attributes_test.go b/engine/attributes_test.go index 3ac16ab0f..5dda6dd54 100644 --- a/engine/attributes_test.go +++ b/engine/attributes_test.go @@ -122,3 +122,481 @@ func TestAttributesV1ProcessEvent(t *testing.T) { t.Errorf("\nExpected <%+v>, \nReceived <%+v>", utils.ToJSON(expected), utils.ToJSON(rply)) } } + +func TestAttributesV1ProcessEventErrorMetaSum(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + cfg.FilterSCfg().ResourceSConns = []string{} + conMng := &ConnManager{} + db := NewInternalDB(nil, nil, true) + dm := NewDataManager(db, nil, conMng) + filterS := NewFilterS(cfg, conMng, dm) + attr := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_CHANGE_TENANT_FROM_USER", + FilterIDs: []string{"*string:~*req.Account:dan@itsyscom.com|adrian@itsyscom.com"}, + Attributes: []*Attribute{ + { + FilterIDs: nil, + Path: "*tenant", + Type: "*variable", + Value: config.NewRSRParsersMustCompile("~*req.Account:s/(.*)@(.*)/${1}.${2}/", utils.InfieldSep), + }, + { + FilterIDs: nil, + Path: "*req.Account", + Type: "*variable", + Value: config.NewRSRParsersMustCompile("~*req.Account:s/(dan)@(.*)/${1}.${2}/:s/(adrian)@(.*)/andrei.${2}/", utils.InfieldSep), + }, + { + FilterIDs: nil, + Path: "*tenant", + Type: "*composed", + Value: config.NewRSRParsersMustCompile(".co.uk", utils.InfieldSep), + }, + }, + Blocker: false, + Weight: 20, + } + err := dm.SetAttributeProfile(context.Background(), attr, true) + if err != nil { + t.Error(err) + } + + attr2 := &AttributeProfile{ + Tenant: "adrian.itsyscom.com.co.uk", + ID: "ATTR_MATCH_TENANT", + Attributes: []*Attribute{ + { + FilterIDs: nil, + Path: "*req.Password", + Type: utils.MetaSum, + Value: config.NewRSRParsersMustCompile("CGRATES.ORG", utils.InfieldSep), + }, + }, + Blocker: false, + Weight: 20, + } + + err = dm.SetAttributeProfile(context.Background(), attr2, true) + if err != nil { + t.Error(err) + } + + alS := NewAttributeService(dm, filterS, cfg) + args := &AttrArgsProcessEvent{ + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "123", + Event: map[string]interface{}{ + utils.AccountField: "adrian@itsyscom.com", + }, + }, + ProcessRuns: utils.IntPointer(2), + } + rply := &AttrSProcessEventReply{} + err = alS.V1ProcessEvent(context.Background(), args, rply) + sort.Strings(rply.AlteredFields) + expErr := "SERVER_ERROR: NotEnoughParameters" + if err == nil || err.Error() != expErr { + t.Errorf("\nExpected <%+v>, \nReceived <%+v>", nil, err) + } + +} + +func TestAttributesV1ProcessEventErrorMetaDifference(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + cfg.FilterSCfg().ResourceSConns = []string{} + conMng := &ConnManager{} + db := NewInternalDB(nil, nil, true) + dm := NewDataManager(db, nil, conMng) + filterS := NewFilterS(cfg, conMng, dm) + attr := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_CHANGE_TENANT_FROM_USER", + FilterIDs: []string{"*string:~*req.Account:dan@itsyscom.com|adrian@itsyscom.com"}, + Attributes: []*Attribute{ + { + FilterIDs: nil, + Path: "*tenant", + Type: "*variable", + Value: config.NewRSRParsersMustCompile("~*req.Account:s/(.*)@(.*)/${1}.${2}/", utils.InfieldSep), + }, + { + FilterIDs: nil, + Path: "*req.Account", + Type: "*variable", + Value: config.NewRSRParsersMustCompile("~*req.Account:s/(dan)@(.*)/${1}.${2}/:s/(adrian)@(.*)/andrei.${2}/", utils.InfieldSep), + }, + { + FilterIDs: nil, + Path: "*tenant", + Type: "*composed", + Value: config.NewRSRParsersMustCompile(".co.uk", utils.InfieldSep), + }, + }, + Blocker: false, + Weight: 20, + } + err := dm.SetAttributeProfile(context.Background(), attr, true) + if err != nil { + t.Error(err) + } + + attr2 := &AttributeProfile{ + Tenant: "adrian.itsyscom.com.co.uk", + ID: "ATTR_MATCH_TENANT", + Attributes: []*Attribute{ + { + FilterIDs: nil, + Path: "*req.Password", + Type: utils.MetaDifference, + Value: config.NewRSRParsersMustCompile("CGRATES.ORG", utils.InfieldSep), + }, + }, + Blocker: false, + Weight: 20, + } + + err = dm.SetAttributeProfile(context.Background(), attr2, true) + if err != nil { + t.Error(err) + } + + alS := NewAttributeService(dm, filterS, cfg) + args := &AttrArgsProcessEvent{ + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "123", + Event: map[string]interface{}{ + utils.AccountField: "adrian@itsyscom.com", + }, + }, + ProcessRuns: utils.IntPointer(2), + } + rply := &AttrSProcessEventReply{} + err = alS.V1ProcessEvent(context.Background(), args, rply) + sort.Strings(rply.AlteredFields) + expErr := "SERVER_ERROR: NotEnoughParameters" + if err == nil || err.Error() != expErr { + t.Errorf("\nExpected <%+v>, \nReceived <%+v>", nil, err) + } + +} + +func TestAttributesV1ProcessEventErrorMetaValueExponent(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + cfg.FilterSCfg().ResourceSConns = []string{} + conMng := &ConnManager{} + db := NewInternalDB(nil, nil, true) + dm := NewDataManager(db, nil, conMng) + filterS := NewFilterS(cfg, conMng, dm) + attr := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_CHANGE_TENANT_FROM_USER", + FilterIDs: []string{"*string:~*req.Account:dan@itsyscom.com|adrian@itsyscom.com"}, + Attributes: []*Attribute{ + { + FilterIDs: nil, + Path: "*tenant", + Type: "*variable", + Value: config.NewRSRParsersMustCompile("~*req.Account:s/(.*)@(.*)/${1}.${2}/", utils.InfieldSep), + }, + { + FilterIDs: nil, + Path: "*req.Account", + Type: "*variable", + Value: config.NewRSRParsersMustCompile("~*req.Account:s/(dan)@(.*)/${1}.${2}/:s/(adrian)@(.*)/andrei.${2}/", utils.InfieldSep), + }, + { + FilterIDs: nil, + Path: "*tenant", + Type: "*composed", + Value: config.NewRSRParsersMustCompile(".co.uk", utils.InfieldSep), + }, + }, + Blocker: false, + Weight: 20, + } + err := dm.SetAttributeProfile(context.Background(), attr, true) + if err != nil { + t.Error(err) + } + + attr2 := &AttributeProfile{ + Tenant: "adrian.itsyscom.com.co.uk", + ID: "ATTR_MATCH_TENANT", + Attributes: []*Attribute{ + { + FilterIDs: nil, + Path: "*req.Password", + Type: utils.MetaValueExponent, + Value: config.NewRSRParsersMustCompile("CGRATES.ORG", utils.InfieldSep), + }, + }, + Blocker: false, + Weight: 20, + } + + err = dm.SetAttributeProfile(context.Background(), attr2, true) + if err != nil { + t.Error(err) + } + + alS := NewAttributeService(dm, filterS, cfg) + args := &AttrArgsProcessEvent{ + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "123", + Event: map[string]interface{}{ + utils.AccountField: "adrian@itsyscom.com", + }, + }, + ProcessRuns: utils.IntPointer(2), + } + rply := &AttrSProcessEventReply{} + err = alS.V1ProcessEvent(context.Background(), args, rply) + sort.Strings(rply.AlteredFields) + expErr := "SERVER_ERROR: invalid arguments <[{\"Rules\":\"CGRATES.ORG\"}]> to *value_exponent" + if err == nil || err.Error() != expErr { + t.Errorf("\nExpected <%+v>, \nReceived <%+v>", nil, err) + } + +} + +func TestAttributesattributeProfileForEventNoDBConn(t *testing.T) { + tmp := Cache + defer func() { + Cache = tmp + }() + + cfg := config.NewDefaultCGRConfig() + dataDB := NewInternalDB(nil, nil, true) + dm := NewDataManager(dataDB, cfg.CacheCfg(), nil) + Cache = NewCacheS(cfg, dm, nil) + alS := &AttributeService{ + cgrcfg: cfg, + dm: dm, + filterS: NewFilterS(cfg, nil, dm), + } + + postpaid, err := config.NewRSRParsers(utils.MetaPostpaid, utils.InfieldSep) + if err != nil { + t.Error(err) + } + ap1 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Attributes: []*Attribute{ + { + Path: "*req.RequestType", + Type: utils.MetaConstant, + Value: postpaid, + }, + }, + Weight: 20, + } + err = alS.dm.SetAttributeProfile(context.Background(), ap1, true) + if err != nil { + t.Error(err) + } + + ap2 := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_2", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Attributes: []*Attribute{ + { + Path: "*req.RequestType", + Type: utils.MetaConstant, + Value: postpaid, + }, + }, + Weight: 10, + } + err = alS.dm.SetAttributeProfile(context.Background(), ap2, true) + if err != nil { + t.Error(err) + } + + tnt := "cgrates.org" + evNm := utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + utils.AccountField: "1001", + }, + } + lastID := "" + alS.dm = nil + + if rcv, err := alS.attributeProfileForEvent(context.Background(), tnt, []string{"ATTR_3"}, evNm, lastID); err == nil || err != utils.ErrNoDatabaseConn { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ErrNoDatabaseConn, err) + } else if rcv != nil { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, rcv) + } +} + +func TestAttributesattributeProfileForEventErrNotFound(t *testing.T) { + tmp := Cache + defer func() { + Cache = tmp + }() + + cfg := config.NewDefaultCGRConfig() + dataDB := NewInternalDB(nil, nil, true) + dm := NewDataManager(dataDB, cfg.CacheCfg(), nil) + Cache = NewCacheS(cfg, dm, nil) + alS := &AttributeService{ + cgrcfg: cfg, + dm: dm, + filterS: NewFilterS(cfg, nil, dm), + } + + apNil := &AttributeProfile{} + err = alS.dm.SetAttributeProfile(context.Background(), apNil, true) + if err != nil { + t.Error(err) + } + + tnt := "" + evNm := utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + utils.AccountField: "1001", + }, + } + lastID := "" + + if rcv, err := alS.attributeProfileForEvent(context.Background(), tnt, []string{"ATTR_3"}, evNm, lastID); err == nil || err != utils.ErrNotFound { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err) + } else if rcv != nil { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, rcv) + } +} + +func TestAttributesattributeProfileForEventErrPass(t *testing.T) { + tmp := Cache + defer func() { + Cache = tmp + }() + + cfg := config.NewDefaultCGRConfig() + dataDB := NewInternalDB(nil, nil, true) + dm := NewDataManager(dataDB, cfg.CacheCfg(), nil) + Cache = NewCacheS(cfg, dm, nil) + alS := &AttributeService{ + cgrcfg: cfg, + dm: dm, + filterS: NewFilterS(cfg, nil, dm), + } + + postpaid, err := config.NewRSRParsers(utils.MetaPostpaid, utils.InfieldSep) + if err != nil { + t.Error(err) + } + ap := &AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + Attributes: []*Attribute{ + { + Path: "*req.RequestType", + Type: utils.MetaConstant, + Value: postpaid, + }, + }, + Weight: 20, + } + err = alS.dm.SetAttributeProfile(context.Background(), ap, true) + if err != nil { + t.Error(err) + } + + tnt := "cgrates.org" + evNm := utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + utils.AccountField: "1001", + }, + } + lastID := "" + + evNm = utils.MapStorage{ + utils.MetaReq: 1, + } + + if rcv, err := alS.attributeProfileForEvent(context.Background(), tnt, []string{"ATTR_1"}, evNm, lastID); err == nil || err != utils.ErrWrongPath { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ErrWrongPath, err) + } else if rcv != nil { + t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, rcv) + } +} + +func TestAttributesParseAttributeSIPCID(t *testing.T) { + exp := "12345;1001;1002" + dp := utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + "cid": "12345", + "to": "1001", + "from": "1002", + }, + } + if out, err := ParseAttribute(dp, utils.MetaSIPCID, utils.EmptyString, config.NewRSRParsersMustCompile("~*req.cid;~*req.to;~*req.from", utils.InfieldSep), + 0, utils.EmptyString, utils.EmptyString, utils.InfieldSep); err != nil { + t.Fatal(err) + } else if exp != out { + t.Errorf("Expected %q, Received %q", exp, out) + } + + dp = utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + "cid": "12345", + "to": "1002", + "from": "1001", + }, + } + if out, err := ParseAttribute(dp, utils.MetaSIPCID, utils.EmptyString, config.NewRSRParsersMustCompile("~*req.cid;~*req.to;~*req.from", utils.InfieldSep), + 0, utils.EmptyString, utils.EmptyString, utils.InfieldSep); err != nil { + t.Fatal(err) + } else if exp != out { + t.Errorf("Expected %q, Received %q", exp, out) + } + + exp = "12345;1001;1002;1003" + dp = utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + "cid": "12345", + "to": "1001", + "from": "1002", + "extra": "1003", + }, + } + if out, err := ParseAttribute(dp, utils.MetaSIPCID, utils.EmptyString, config.NewRSRParsersMustCompile("~*req.cid;~*req.to;~*req.extra;~*req.from", + utils.InfieldSep), 0, utils.EmptyString, utils.EmptyString, utils.InfieldSep); err != nil { + t.Fatal(err) + } else if exp != out { + t.Errorf("Expected %q, Received %q", exp, out) + } + + dp = utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + "cid": "12345", + "to": "1002", + "from": "1001", + "extra": "1003", + }, + } + if out, err := ParseAttribute(dp, utils.MetaSIPCID, utils.EmptyString, config.NewRSRParsersMustCompile("~*req.cid;~*req.extra;~*req.to;~*req.from", + utils.InfieldSep), 0, utils.EmptyString, utils.EmptyString, utils.InfieldSep); err != nil { + t.Fatal(err) + } else if exp != out { + t.Errorf("Expected %q, Received %q", exp, out) + } + + dp = utils.MapStorage{ + utils.MetaReq: utils.MapStorage{ + "cid": "12345", + }, + } + if _, err := ParseAttribute(dp, utils.MetaSIPCID, utils.EmptyString, config.NewRSRParsersMustCompile("~*req.cid;~*req.extra;~*req.to;~*req.from", utils. + InfieldSep), 0, utils.EmptyString, utils.EmptyString, utils.InfieldSep); err != utils.ErrNotFound { + t.Errorf("Expected <%+v>, received <%+v>", utils.ErrNotFound, err) + } +} diff --git a/utils/consts.go b/utils/consts.go index f758bacef..9ce53277b 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -391,6 +391,7 @@ const ( MetaDifference = "*difference" MetaVariable = "*variable" MetaCCUsage = "*ccUsage" + MetaSIPCID = "*sipcid" MetaValueExponent = "*valueExponent" //rsrparser consts NegativePrefix = "!" @@ -2455,6 +2456,7 @@ var StringTmplType = StringSet{ MetaUsageDifference: struct{}{}, MetaPrefix: struct{}{}, MetaSuffix: struct{}{}, + MetaSIPCID: struct{}{}, } // Time duration suffix