/* 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 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ package cdrc import ( "bytes" "path" "reflect" "strings" "testing" "time" "github.com/antchfx/xmlquery" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) var cdrXmlBroadsoft = ` 0002183384 CGRateSaabb 20160419210000.104 1+020000 Start 0002183385 CGRateSaabb 20160419210005.247 1+020000 MBC Normal 1001 2001 Terminating Network 1001 Public +4986517174963 20160419210005.247 1+020000 25160047719:0 Yes 20160419210006.813 20160419210020.296 016 y local 1001@cgrates.org Yes Yes CGR_GROUP CGR_GROUP/CGR_GROUP_TRUNK30 Normal 1001@cgrates.org Primary Device 31.882 gw04.cgrates.org 74122796919420162305@172.16.1.2 PCMA/8000 172.16.1.4 BW2300052501904161738474465@172.16.1.10 31.882 OmniPCX Enterprise R11.0.1 k1.520.22.b 0002183386 CGRateSaabb 20160419210006.909 1+020000 MBC Normal 1002 2001 Terminating Network +4986517174964 Public 1002 20160419210006.909 1+020000 27280048121:0 Yes 20160419210007.037 20160419210030.322 016 y local 314028947650@cgrates.org Yes Yes CGR_GROUP CGR_GROUP/CGR_GROUP_TRUNK65 Normal 31403456100@cgrates.org Primary Device 26.244 gw01.cgrates.org 108352493719420162306@172.31.250.150 PCMA/8000 172.16.1.4 2345300069121904161716512907@172.16.1.10 26.244 Altitude vBox 0002183486 CGRateSaabb 20160419211500.104 1+020000 End ` func TestXMLElementText(t *testing.T) { doc, err := xmlquery.Parse(strings.NewReader(cdrXmlBroadsoft)) if err != nil { t.Error(err) } cdrs := xmlquery.Find(doc, path.Join("/broadWorksCDR/cdrData/")) cdrWithoutUserNr := cdrs[0] if _, err := elementText(cdrWithoutUserNr, "basicModule/userNumber"); err != utils.ErrNotFound { t.Error(err) } cdrWithUser := cdrs[1] if val, err := elementText(cdrWithUser, "basicModule/userNumber"); err != nil { t.Error(err) } else if val != "1001" { t.Errorf("Expecting: 1001, received: %s", val) } if val, err := elementText(cdrWithUser, "centrexModule/locationList/locationInformation/locationType"); err != nil { t.Error(err) } else if val != "Primary Device" { t.Errorf("Expecting: , received: <%s>", val) } } func TestXMLHandlerSubstractUsage(t *testing.T) { doc, err := xmlquery.Parse(strings.NewReader(cdrXmlBroadsoft)) if err != nil { t.Error(err) } cdrs := xmlquery.Find(doc, path.Join("/broadWorksCDR/cdrData/")) cdrWithUsage := cdrs[1] if usage, err := handlerSubstractUsage(cdrWithUsage, config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.releaseTime;|;~broadWorksCDR.cdrData.basicModule.answerTime", true, utils.INFIELD_SEP), utils.HierarchyPath([]string{"broadWorksCDR", "cdrData"}), "UTC"); err != nil { t.Error(err) } else if usage != time.Duration(13483000000) { t.Errorf("Expected: 13.483s, received: %v", usage) } } func TestXMLRPProcess(t *testing.T) { cdrcCfgs := []*config.CdrcCfg{ { ID: "TestXML", Enabled: true, CdrFormat: "xml", DataUsageMultiplyFactor: 1024, CDRPath: utils.HierarchyPath([]string{"broadWorksCDR", "cdrData"}), CdrSourceId: "TestXML", ContentFields: []*config.FCTemplate{ {Tag: "TOR", Type: utils.META_COMPOSED, FieldId: utils.ToR, Value: config.NewRSRParsersMustCompile("*voice", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.OriginID, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.localCallId", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.RequestType, Value: config.NewRSRParsersMustCompile("*rated", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.Tenant, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.userId:s/.*@(.*)/${1}/", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Category", Type: utils.META_COMPOSED, FieldId: utils.Category, Value: config.NewRSRParsersMustCompile("call", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Account", Type: utils.META_COMPOSED, FieldId: utils.Account, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.userNumber", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Destination", Type: utils.META_COMPOSED, FieldId: utils.Destination, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.calledNumber", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SetupTime, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.startTime", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.AnswerTime, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.answerTime", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Usage", Type: utils.META_HANDLER, FieldId: utils.Usage, HandlerId: utils.HandlerSubstractUsage, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.releaseTime;|;~broadWorksCDR.cdrData.basicModule.answerTime", true, utils.INFIELD_SEP), Mandatory: true}, }, }, } xmlRP, err := NewXMLRecordsProcessor(bytes.NewBufferString(cdrXmlBroadsoft), utils.HierarchyPath([]string{"broadWorksCDR", "cdrData"}), "UTC", true, cdrcCfgs, nil) if err != nil { t.Error(err) } var cdrs []*engine.CDR for i := 0; i < 4; i++ { cdrs, err = xmlRP.ProcessNextRecord() if i == 1 { // Take second CDR since the first one cannot be processed break } } if err != nil { t.Error(err) } expectedCDRs := []*engine.CDR{ {CGRID: "1f045359a0784d15e051d7e41ae30132b139d714", OriginHost: "0.0.0.0", Source: "TestXML", OriginID: "25160047719:0", ToR: "*voice", RequestType: "*rated", Tenant: "cgrates.org", Category: "call", Account: "1001", Destination: "+4986517174963", SetupTime: time.Date(2016, 4, 19, 21, 0, 5, 247000000, time.UTC), AnswerTime: time.Date(2016, 4, 19, 21, 0, 6, 813000000, time.UTC), Usage: time.Duration(13483000000), ExtraFields: map[string]string{}, Cost: -1}, } if !reflect.DeepEqual(expectedCDRs, cdrs) { t.Errorf("Expecting: %+v\n, received: %+v\n", expectedCDRs, cdrs) } } func TestXMLRPProcessWithNewFilters(t *testing.T) { cdrcCfgs := []*config.CdrcCfg{ { ID: "XMLWithFilters", Enabled: true, CdrFormat: "xml", DataUsageMultiplyFactor: 1024, CDRPath: utils.HierarchyPath([]string{"broadWorksCDR", "cdrData"}), CdrSourceId: "XMLWithFilters", Filters: []string{"*string:~broadWorksCDR.cdrData.headerModule.type:Normal"}, ContentFields: []*config.FCTemplate{ {Tag: "TOR", Type: utils.META_COMPOSED, FieldId: utils.ToR, Value: config.NewRSRParsersMustCompile("*voice", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.OriginID, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.localCallId", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.RequestType, Value: config.NewRSRParsersMustCompile("*rated", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.Tenant, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.userId:s/.*@(.*)/${1}/", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Category", Type: utils.META_COMPOSED, FieldId: utils.Category, Value: config.NewRSRParsersMustCompile("call", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Account", Type: utils.META_COMPOSED, FieldId: utils.Account, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.userNumber", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Destination", Type: utils.META_COMPOSED, FieldId: utils.Destination, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.calledNumber", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SetupTime, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.startTime", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.AnswerTime, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.answerTime", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Usage", Type: utils.META_HANDLER, FieldId: utils.Usage, HandlerId: utils.HandlerSubstractUsage, Value: config.NewRSRParsersMustCompile("~broadWorksCDR.cdrData.basicModule.releaseTime;|;~broadWorksCDR.cdrData.basicModule.answerTime", true, utils.INFIELD_SEP), Mandatory: true}, }, }, } data, _ := engine.NewMapStorage() defaultCfg, err := config.NewDefaultCGRConfig() if err != nil { t.Errorf("Error: %+v", err) } xmlRP, err := NewXMLRecordsProcessor(bytes.NewBufferString(cdrXmlBroadsoft), utils.HierarchyPath([]string{"broadWorksCDR", "cdrData"}), "UTC", true, cdrcCfgs, engine.NewFilterS(defaultCfg, nil, nil, engine.NewDataManager(data))) if err != nil { t.Error(err) } var cdrs []*engine.CDR for i := 0; i < 4; i++ { cdrs, err = xmlRP.ProcessNextRecord() if i == 1 { // Take second CDR since the first one cannot be processed break } } if err != nil { t.Error(err) } expectedCDRs := []*engine.CDR{ {CGRID: "1f045359a0784d15e051d7e41ae30132b139d714", OriginHost: "0.0.0.0", Source: "XMLWithFilters", OriginID: "25160047719:0", ToR: "*voice", RequestType: "*rated", Tenant: "cgrates.org", Category: "call", Account: "1001", Destination: "+4986517174963", SetupTime: time.Date(2016, 4, 19, 21, 0, 5, 247000000, time.UTC), AnswerTime: time.Date(2016, 4, 19, 21, 0, 6, 813000000, time.UTC), Usage: time.Duration(13483000000), ExtraFields: map[string]string{}, Cost: -1}, } if !reflect.DeepEqual(expectedCDRs, cdrs) { t.Errorf("Expecting: %+v\n, received: %+v\n", expectedCDRs, cdrs) } } var xmlContent = ` Metaswitch CFS 1510225200002 National Orig 19 480 No answer 223007622 +27110493421 +27110493421 +27110493421 Ro_test 0gQAAC8WAAACBAAALxYAAD57SAEV7ekv/OSKkO7qmD82OmbfHO+Z7wIZJkXdCv8z@10.170.248.200 1 0 8824071D@10.170.248.140 19 480 0763371551 +270763371551 0763371551 110493421 110493421 False NetworkDefault 0 Speech 13442698e525ad2c21251f76479ab2b4 voice.lab.liquidtelecom.net 1510225513055 1510225513304 1510225514836 1510225516981 1510225516981 1510225516981 Premium Orig 16 No error 223007623 +27110493421 +27110493421 +27110493421 Ro_test 0gQAAC8WAAACBAAALxYAAPkyWDO29Do1SyxNi5UV71mJYEIEkfNa9wCFCCjY2asU@10.170.248.200 1 0 8E450FA1@10.170.248.140 0843073451 +270843073451 0843073451 110493421 110493421 False NetworkDefault 0 Speech 46d7974398c2671016afccc3f2c428c7 voice.lab.liquidtelecom.net 1510225531933 1510225532183 1510225534973 1510225539364 1510225593101 1510225593101 1510225593101 International Orig 16 No error 223007624 +27110493421 +27110493421 +27110493421 Ro_test 0gQAAC8WAAACBAAALxYAAJrUscTicyU5GtjPyQnpAeuNmz9p/bdOoR/Mk9RXciOI@10.170.248.200 1 0 BC8B2801@10.170.248.140 263772822306 +263772822306 263772822306 110493421 110493421 False NetworkDefault 0 Speech 750b8b73e41ba7b59b21240758522268 voice.lab.liquidtelecom.net 1510225865894 1510225866144 1510225866756 1510225876243 1510225916144 1510225916144 1510225916144 1510227591467 3 0 0 ` func TestXMLElementText3(t *testing.T) { doc, err := xmlquery.Parse(strings.NewReader(xmlContent)) if err != nil { t.Error(err) } hPath2 := utils.ParseHierarchyPath("File.CDRs.Call", "") cdrs := xmlquery.Find(doc, hPath2.AsString("/", true)) if len(cdrs) != 3 { t.Errorf("Expecting: 3, received: %+v", len(cdrs)) } if _, err := elementText(cdrs[0], "SignalingInfo/PChargingVector/test"); err != utils.ErrNotFound { t.Error(err) } if val, err := elementText(cdrs[1], "SignalingInfo/PChargingVector/icidvalue"); err != nil { t.Error(err) } else if val != "46d7974398c2671016afccc3f2c428c7" { t.Errorf("Expecting: 46d7974398c2671016afccc3f2c428c7, received: %s", val) } } func TestXMLRPNestingSeparator(t *testing.T) { cdrcCfgs := []*config.CdrcCfg{ { ID: "msw_xml", Enabled: true, CdrFormat: "xml", DataUsageMultiplyFactor: 1024, CDRPath: utils.HierarchyPath([]string{"File", "CDRs", "Call"}), CdrSourceId: "zw_cfs1", Filters: []string{}, ContentFields: []*config.FCTemplate{ {Tag: "TOR", Type: utils.META_COMPOSED, FieldId: utils.ToR, Value: config.NewRSRParsersMustCompile("*voice", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "OriginID", Type: utils.META_COMPOSED, FieldId: utils.OriginID, Value: config.NewRSRParsersMustCompile("~File.CDRs.Call.SignalingInfo.PChargingVector.icidvalue", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "RequestType", Type: utils.META_COMPOSED, FieldId: utils.RequestType, Value: config.NewRSRParsersMustCompile("*rated", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Tenant", Type: utils.META_COMPOSED, FieldId: utils.Tenant, Value: config.NewRSRParsersMustCompile("XX.liquid.tel", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Category", Type: utils.META_COMPOSED, FieldId: utils.Category, Value: config.NewRSRParsersMustCompile("call", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Account", Type: utils.META_COMPOSED, FieldId: utils.Account, Value: config.NewRSRParsersMustCompile("~File.CDRs.Call.OrigParty.SubscriberAddr", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Destination", Type: utils.META_COMPOSED, FieldId: utils.Destination, Value: config.NewRSRParsersMustCompile("~File.CDRs.Call.RoutingInfo.DestAddr", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "SetupTime", Type: utils.META_COMPOSED, FieldId: utils.SetupTime, Value: config.NewRSRParsersMustCompile("~File.CDRs.Call.RingingTime", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "AnswerTime", Type: utils.META_COMPOSED, FieldId: utils.AnswerTime, Value: config.NewRSRParsersMustCompile("~File.CDRs.Call.ConnectTime", true, utils.INFIELD_SEP), Mandatory: true}, {Tag: "Usage", Type: utils.META_HANDLER, FieldId: utils.Usage, HandlerId: utils.HandlerSubstractUsage, Value: config.NewRSRParsersMustCompile("~File.CDRs.Call.ReleaseTime;|;~File.CDRs.Call.ConnectTime", true, utils.INFIELD_SEP), Mandatory: true}, }, }, } data, _ := engine.NewMapStorage() defaultCfg, err := config.NewDefaultCGRConfig() if err != nil { t.Errorf("Error: %+v", err) } xmlRP, err := NewXMLRecordsProcessor(bytes.NewBufferString(xmlContent), utils.HierarchyPath([]string{"File", "CDRs", "Call"}), "UTC", true, cdrcCfgs, engine.NewFilterS(defaultCfg, nil, nil, engine.NewDataManager(data))) if err != nil { t.Error(err) } var cdrs []*engine.CDR for i := 0; i < 4; i++ { cdrs, err = xmlRP.ProcessNextRecord() if i == 1 { // Take second CDR since the first one cannot be processed break } } if err != nil { t.Error(err) } expectedCDRs := []*engine.CDR{ {CGRID: "0ad7f9554ff8fc5b3a7cebbe7431bbf809bc5144", OriginHost: "0.0.0.0", Source: "zw_cfs1", OriginID: "46d7974398c2671016afccc3f2c428c7", ToR: "*voice", RequestType: "*rated", Tenant: "XX.liquid.tel", Category: "call", Account: "+27110493421", Destination: "+270843073451", SetupTime: time.Date(2017, 11, 9, 11, 5, 34, 973000000, time.UTC), AnswerTime: time.Date(2017, 11, 9, 11, 5, 39, 364000000, time.UTC), Usage: time.Duration(53737000000), ExtraFields: map[string]string{}, Cost: -1}, } if !reflect.DeepEqual(expectedCDRs, cdrs) { t.Errorf("Expecting: %+v\n, received: %+v\n", expectedCDRs, cdrs) } } var xmlMultipleIndex = ` 2005-08-26T14:16:42 2005-08-26T14:16:56 2005-08-26T14:17:34 My Call Reference 386 sampleusername 1 Conecto LLC US$0.21 yes no US$0.13 44 +441624828505 Isle of Man 38 US$0.0200 US$0.0140 US$0.0130 US$0.0082 +44 7624 494075 Isle of Man 37 US$0.2700 US$0.1890 US$0.1880 US$0.1159 ` func TestXMLIndexes(t *testing.T) { doc, err := xmlquery.Parse(strings.NewReader(xmlMultipleIndex)) if err != nil { t.Error(err) } dP := newXmlProvider(doc, utils.HierarchyPath([]string{})) if data, err := dP.FieldAsString([]string{"complete-success-notification", "userid"}); err != nil { t.Error(err) } else if data != "386" { t.Errorf("expecting: 386, received: <%s>", data) } if data, err := dP.FieldAsString([]string{"complete-success-notification", "username"}); err != nil { t.Error(err) } else if data != "sampleusername" { t.Errorf("expecting: sampleusername, received: <%s>", data) } if data, err := dP.FieldAsString([]string{"complete-success-notification", "callleg", "seconds"}); err != nil { t.Error(err) } else if data != "38" { t.Errorf("expecting: 38, received: <%s>", data) } if data, err := dP.FieldAsString([]string{"complete-success-notification", "callleg[1]", "seconds"}); err != nil { t.Error(err) } else if data != "37" { t.Errorf("expecting: 37, received: <%s>", data) } if data, err := dP.FieldAsString([]string{"complete-success-notification", "callleg[@calllegid='222147']", "seconds"}); err != nil { t.Error(err) } else if data != "37" { t.Errorf("expecting: 37, received: <%s>", data) } }