/*
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)
}
}