Removed cdr struct

This commit is contained in:
andronache98
2022-02-17 13:28:56 +02:00
committed by Dan Christian Bogos
parent df412e55fb
commit 7e80fe008f
10 changed files with 0 additions and 3588 deletions

View File

@@ -1,67 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package console
import (
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func init() {
c := &CmdGetCDRs{
name: "cdrs",
rpcMethod: utils.CDRsV1GetCDRs,
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetCDRs struct {
name string
rpcMethod string
rpcParams *utils.RPCCDRsFilterWithAPIOpts
*CommandExecuter
}
func (self *CmdGetCDRs) Name() string {
return self.name
}
func (self *CmdGetCDRs) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCDRs) RpcParams(reset bool) interface{} {
if reset || self.rpcParams == nil {
self.rpcParams = &utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: new(utils.RPCCDRsFilter),
}
}
return self.rpcParams
}
func (self *CmdGetCDRs) PostprocessRpcParams() error {
return nil
}
func (self *CmdGetCDRs) RpcResult() interface{} {
a := make([]*engine.CDR, 0)
return &a
}

View File

@@ -1,542 +0,0 @@
//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 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 <http://www.gnu.org/licenses/>
*/
package dispatchers
import (
"testing"
"time"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
var (
sTestsDspCDRs = []func(t *testing.T){
testDspCDRsPing,
/*
testDspCDRsProcessEvent,
testDspCDRsCountCDR,
testDspCDRsGetCDR,
testDspCDRsGetCDRWithoutTenant,
testDspCDRsProcessCDR,
testDspCDRsGetCDR2,
testDspCDRsProcessExternalCDR,
testDspCDRsGetCDR3,
testDspCDRsV2ProcessEvent,
*/
// testDspCDRsV2StoreSessionCost,
}
sTestsDspCDRsWithoutAuth = []func(t *testing.T){
testDspCDRsPingNoAuth,
/*
testDspCDRsProcessEventNoAuth,
testDspCDRsCountCDRNoAuth,
testDspCDRsGetCDRNoAuth,
testDspCDRsGetCDRNoAuthWithoutTenant,
testDspCDRsProcessCDRNoAuth,
testDspCDRsGetCDR2NoAuth,
testDspCDRsProcessExternalCDRNoAuth,
testDspCDRsGetCDR3NoAuth,
testDspCDRsV2ProcessEventNoAuth,
// testDspCDRsV2StoreSessionCostNoAuth,
*/
}
)
//Test start here
func TestDspCDRsIT(t *testing.T) {
var config1, config2, config3 string
switch *dbType {
case utils.MetaInternal:
t.SkipNow()
case utils.MetaMySQL:
config1 = "all_mysql"
config2 = "all2_mysql"
config3 = "dispatchers_mysql"
case utils.MetaMongo:
config1 = "all_mongo"
config2 = "all2_mongo"
config3 = "dispatchers_mongo"
case utils.MetaPostgres:
t.SkipNow()
default:
t.Fatal("Unknown Database type")
}
dispDIR := "dispatchers"
if *encoding == utils.MetaGOB {
dispDIR += "_gob"
}
testDsp(t, sTestsDspCDRs, "TestDspCDRs", config1, config2, config3, "tutorial", "oldtutorial", dispDIR)
}
func TestDspCDRsITMySQLWithoutAuth(t *testing.T) {
if *dbType != utils.MetaMySQL {
t.SkipNow()
}
if *encoding == utils.MetaGOB {
testDsp(t, sTestsDspCDRsWithoutAuth, "TestDspCDRsWithoutAuth", "all_mysql", "all2_mysql", "dispatchers_no_attributes", "tutorial", "oldtutorial", "dispatchers_gob")
} else {
testDsp(t, sTestsDspCDRsWithoutAuth, "TestDspCDRsWithoutAuth", "all_mysql", "all2_mysql", "dispatchers_no_attributes", "tutorial", "oldtutorial", "dispatchers")
}
}
func testDspCDRsPing(t *testing.T) {
var reply string
if err := allEngine.RPC.Call(utils.CDRsV1Ping, new(utils.CGREvent), &reply); err != nil {
t.Error(err)
} else if reply != utils.Pong {
t.Errorf("Received: %s", reply)
}
if err := dispEngine.RPC.Call(utils.CDRsV1Ping, &utils.CGREvent{
Tenant: "cgrates.org",
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.Pong {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsProcessEvent(t *testing.T) {
var reply string
args := &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.OriginID: "testDspCDRsProcessEvent",
utils.OriginHost: "192.168.1.1",
utils.Source: "testDspCDRsProcessEvent",
utils.RequestType: utils.MetaRated,
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC),
utils.Usage: time.Minute,
"field_extr1": "val_extr1",
"fieldextr2": "valextr2",
},
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
utils.OptsCDRsStore: true,
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1ProcessEvent, args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsCountCDR(t *testing.T) {
var reply int64
args := &utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
},
Tenant: "cgrates.org",
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRsCount, args, &reply); err != nil {
t.Error(err)
} else if reply != 1 {
t.Errorf("Received: %+v", reply)
}
}
func testDspCDRsGetCDR(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
},
Tenant: "cgrates.org",
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "9ee4c71fcd67eef5fb25a4bb3f190487de3073f5" {
// t.Errorf("Expected: 9ee4c71fcd67eef5fb25a4bb3f190487de3073f5 , received:%v", reply[0].CGRID)
}
}
func testDspCDRsGetCDRWithoutTenant(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
},
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "9ee4c71fcd67eef5fb25a4bb3f190487de3073f5" {
// t.Errorf("Expected: 9ee4c71fcd67eef5fb25a4bb3f190487de3073f5 , received:%v", reply[0].CGRID)
}
}
func testDspCDRsProcessCDR(t *testing.T) {
var reply string
args := &engine.CDRWithAPIOpts{
CDR: &engine.CDR{
Tenant: "cgrates.org",
OriginID: "testDspCDRsProcessCDR",
OriginHost: "192.168.1.1",
Source: "testDspCDRsProcessCDR",
RequestType: utils.MetaRated,
Account: "1001",
Subject: "1001",
Destination: "1002",
AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC),
Usage: 2 * time.Minute,
},
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1ProcessCDR, args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsGetCDR2(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
OriginIDs: []string{"testDspCDRsProcessCDR"},
},
Tenant: "cgrates.org",
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "f08dfd32930b6bea326bb8ec4e38ab03d781c0bf" {
// t.Errorf("Expected: f08dfd32930b6bea326bb8ec4e38ab03d781c0bf , received:%v", reply[0].CGRID)
}
}
func testDspCDRsProcessExternalCDR(t *testing.T) {
var reply string
args := &engine.ExternalCDRWithAPIOpts{
ExternalCDR: &engine.ExternalCDR{
ToR: utils.MetaVoice,
OriginID: "testDspCDRsProcessExternalCDR",
OriginHost: "127.0.0.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1003",
Subject: "1003",
Destination: "1001",
SetupTime: "2014-08-04T13:00:00Z",
AnswerTime: "2014-08-04T13:00:07Z",
Usage: "1s",
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
},
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsGetCDR3(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1003"},
RunIDs: []string{utils.MetaDefault},
OriginIDs: []string{"testDspCDRsProcessExternalCDR"},
},
Tenant: "cgrates.org",
APIOpts: map[string]interface{}{
utils.OptsAPIKey: "cdrs12345",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "8ae63781b39f3265d014d2ba6a70437172fba46d" {
// t.Errorf("Expected: 8ae63781b39f3265d014d2ba6a70437172fba46d , received:%v", reply[0].CGRID)
}
}
// func testDspCDRsV2StoreSessionCost(t *testing.T) {
// var reply string
// cc := &engine.CallCost{
// Category: "generic",
// Tenant: "cgrates.org",
// Subject: "1001",
// Account: "1001",
// Destination: "data",
// ToR: "*data",
// Cost: 0,
// }
// args := &engine.ArgsV2CDRSStoreSMCost{
// CheckDuplicate: true,
// Cost: &engine.V2SMCost{
// CGRID: "testDspCDRsV2StoreSessionCost",
// RunID: utils.MetaDefault,
// OriginHost: "",
// OriginID: "testdatagrp_grp1",
// CostSource: "SMR",
// Usage: 1536,
// CostDetails: engine.NewEventCostFromCallCost(cc, "testDspCDRsV2StoreSessionCost", utils.MetaDefault),
// },
// APIOpts: map[string]interface{}{
// utils.OptsAPIKey: "cdrsv212345",
// },
// }
// if err := dispEngine.RPC.Call(utils.CDRsV2StoreSessionCost, args, &reply); err != nil {
// t.Error("Unexpected error: ", err.Error())
// } else if reply != utils.OK {
// t.Error("Unexpected reply received: ", reply)
// }
// time.Sleep(150 * time.Millisecond)
// if err := dispEngine.RPC.Call(utils.CDRsV2StoreSessionCost, args,
// &reply); err == nil || err.Error() != "SERVER_ERROR: EXISTS" {
// t.Error("Unexpected error: ", err)
// }
// }
func testDspCDRsPingNoAuth(t *testing.T) {
var reply string
if err := allEngine.RPC.Call(utils.CDRsV1Ping, new(utils.CGREvent), &reply); err != nil {
t.Error(err)
} else if reply != utils.Pong {
t.Errorf("Received: %s", reply)
}
if err := dispEngine.RPC.Call(utils.CDRsV1Ping, &utils.CGREvent{
Tenant: "cgrates.org",
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.Pong {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsProcessEventNoAuth(t *testing.T) {
var reply string
args := &utils.CGREvent{
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.OriginID: "testDspCDRsProcessEvent",
utils.OriginHost: "192.168.1.1",
utils.Source: "testDspCDRsProcessEvent",
utils.RequestType: utils.MetaRated,
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC),
utils.Usage: time.Minute,
"field_extr1": "val_extr1",
"fieldextr2": "valextr2",
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1ProcessEvent, args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsCountCDRNoAuth(t *testing.T) {
var reply int64
args := &utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
},
Tenant: "cgrates.org",
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRsCount, args, &reply); err != nil {
t.Error(err)
} else if reply != 1 {
t.Errorf("Received: %+v", reply)
}
}
func testDspCDRsGetCDRNoAuth(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
},
Tenant: "cgrates.org",
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "9ee4c71fcd67eef5fb25a4bb3f190487de3073f5" {
// t.Errorf("Expected: 9ee4c71fcd67eef5fb25a4bb3f190487de3073f5 , received:%v", reply[0].CGRID)
}
}
func testDspCDRsGetCDRNoAuthWithoutTenant(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "9ee4c71fcd67eef5fb25a4bb3f190487de3073f5" {
// t.Errorf("Expected: 9ee4c71fcd67eef5fb25a4bb3f190487de3073f5 , received:%v", reply[0].CGRID)
}
}
func testDspCDRsProcessCDRNoAuth(t *testing.T) {
var reply string
args := &engine.CDRWithAPIOpts{
CDR: &engine.CDR{
Tenant: "cgrates.org",
OriginID: "testDspCDRsProcessCDR",
OriginHost: "192.168.1.1",
Source: "testDspCDRsProcessCDR",
RequestType: utils.MetaRated,
Account: "1001",
Subject: "1001",
Destination: "1002",
AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC),
Usage: 2 * time.Minute,
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1ProcessCDR, args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsGetCDR2NoAuth(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1001"},
RunIDs: []string{utils.MetaDefault},
OriginIDs: []string{"testDspCDRsProcessCDR"},
},
Tenant: "cgrates.org",
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "f08dfd32930b6bea326bb8ec4e38ab03d781c0bf" {
// t.Errorf("Expected: f08dfd32930b6bea326bb8ec4e38ab03d781c0bf , received:%v", reply[0].CGRID)
}
}
func testDspCDRsProcessExternalCDRNoAuth(t *testing.T) {
var reply string
args := &engine.ExternalCDRWithAPIOpts{
ExternalCDR: &engine.ExternalCDR{
ToR: utils.MetaVoice,
OriginID: "testDspCDRsProcessExternalCDR",
OriginHost: "127.0.0.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1003",
Subject: "1003",
Destination: "1001",
SetupTime: "2014-08-04T13:00:00Z",
AnswerTime: "2014-08-04T13:00:07Z",
Usage: "1s",
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
},
}
if err := dispEngine.RPC.Call(utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Received: %s", reply)
}
}
func testDspCDRsGetCDR3NoAuth(t *testing.T) {
var reply []*engine.CDR
args := utils.RPCCDRsFilterWithAPIOpts{
RPCCDRsFilter: &utils.RPCCDRsFilter{
Accounts: []string{"1003"},
RunIDs: []string{utils.MetaDefault},
OriginIDs: []string{"testDspCDRsProcessExternalCDR"},
},
Tenant: "cgrates.org",
}
if err := dispEngine.RPC.Call(utils.CDRsV1GetCDRs, &args, &reply); err != nil {
t.Error(err)
} else if len(reply) != 1 {
t.Errorf("Received: %+v", reply)
// } else if reply[0].CGRID != "8ae63781b39f3265d014d2ba6a70437172fba46d" {
// t.Errorf("Expected: 8ae63781b39f3265d014d2ba6a70437172fba46d , received:%v", reply[0].CGRID)
}
}

View File

@@ -1,370 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"encoding/json"
"math"
"strconv"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func NewCDRFromExternalCDR(extCdr *ExternalCDR, timezone string) (*CDR, error) {
var err error
cdr := &CDR{RunID: extCdr.RunID, OrderID: extCdr.OrderID, ToR: extCdr.ToR,
OriginID: extCdr.OriginID, OriginHost: extCdr.OriginHost,
Source: extCdr.Source, RequestType: extCdr.RequestType, Tenant: extCdr.Tenant, Category: extCdr.Category,
Account: extCdr.Account, Subject: extCdr.Subject, Destination: extCdr.Destination,
CostSource: extCdr.CostSource, Cost: extCdr.Cost, PreRated: extCdr.PreRated}
if extCdr.SetupTime != "" {
if cdr.SetupTime, err = utils.ParseTimeDetectLayout(extCdr.SetupTime, timezone); err != nil {
return nil, err
}
}
if extCdr.AnswerTime != "" {
if cdr.AnswerTime, err = utils.ParseTimeDetectLayout(extCdr.AnswerTime, timezone); err != nil {
return nil, err
}
}
if extCdr.Usage != "" {
if cdr.Usage, err = utils.ParseDurationWithNanosecs(extCdr.Usage); err != nil {
return nil, err
}
}
if extCdr.ExtraFields != nil {
cdr.ExtraFields = make(map[string]string)
}
for k, v := range extCdr.ExtraFields {
cdr.ExtraFields[k] = v
}
return cdr, nil
}
type CDR struct {
RunID string
OrderID int64 // Stor order id used as export order id
OriginHost string // represents the IP address of the host generating the CDR (automatically populated by the server)
Source string // formally identifies the source of the CDR (free form field)
OriginID string // represents the unique accounting id given by the telecom switch generating the CDR
ToR string // type of record, meta-field, should map to one of the TORs hardcoded inside the server <*voice|*data|*sms|*generic>
RequestType string // matching the supported request types by the **CGRateS**, accepted values are hardcoded in the server <prepaid|postpaid|pseudoprepaid|rated>.
Tenant string // tenant whom this record belongs
Category string // free-form filter for this record, matching the category defined in rating profiles.
Account string // account id (accounting subsystem) the record should be attached to
Subject string // rating subject (rating subsystem) this record should be attached to
Destination string // destination to be charged
SetupTime time.Time // set-up time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp.
AnswerTime time.Time // answer time of the event. Supported formats: datetime RFC3339 compatible, SQL datetime (eg: MySQL), unix timestamp.
Usage time.Duration // event usage information (eg: in case of tor=*voice this will represent the total duration of a call)
ExtraFields map[string]string // Extra fields to be stored in CDR
ExtraInfo string // Container for extra information related to this CDR, eg: populated with error reason in case of error on calculation
Partial bool // Used for partial record processing by ERs
PreRated bool // Mark the CDR as rated so we do not process it during rating
CostSource string // The source of this cost
Cost float64 //
}
// AddDefaults will add missing information based on other fields
func (cdr *CDR) AddDefaults(cfg *config.CGRConfig) {
if cdr.RunID == utils.EmptyString {
cdr.RunID = utils.MetaDefault
}
if cdr.ToR == utils.EmptyString {
cdr.ToR = utils.MetaVoice
}
if cdr.RequestType == utils.EmptyString {
cdr.RequestType = cfg.GeneralCfg().DefaultReqType
}
if cdr.Tenant == utils.EmptyString {
cdr.Tenant = cfg.GeneralCfg().DefaultTenant
}
if cdr.Category == utils.EmptyString {
cdr.Category = cfg.GeneralCfg().DefaultCategory
}
if cdr.Subject == utils.EmptyString {
cdr.Subject = cdr.Account
}
}
// FormatCost formats the cost as string on export
func (cdr *CDR) FormatCost(shiftDecimals, roundDecimals int) string {
cost := cdr.Cost
if shiftDecimals != 0 {
cost = cost * math.Pow10(shiftDecimals)
}
return strconv.FormatFloat(cost, 'f', roundDecimals, 64)
}
// FieldAsString is used to retrieve fields as string, primary fields are const labeled
func (cdr *CDR) FieldAsString(rsrPrs *config.RSRParser) (parsed string, err error) {
parsed, err = rsrPrs.ParseDataProviderWithInterfaces(
cdr.AsMapStorage())
if err != nil {
return
}
return
}
// FieldsAsString concatenates values of multiple fields defined in template, used eg in CDR templates
func (cdr *CDR) FieldsAsString(rsrFlds config.RSRParsers) string {
outVal, err := rsrFlds.ParseDataProvider(
cdr.AsMapStorage())
if err != nil {
return ""
}
return outVal
}
func (cdr *CDR) Clone() *CDR {
if cdr == nil {
return nil
}
cln := &CDR{
RunID: cdr.RunID,
OrderID: cdr.OrderID,
OriginHost: cdr.OriginHost,
Source: cdr.Source,
OriginID: cdr.OriginID,
ToR: cdr.ToR,
RequestType: cdr.RequestType,
Tenant: cdr.Tenant,
Category: cdr.Category,
Account: cdr.Account,
Subject: cdr.Subject,
Destination: cdr.Destination,
SetupTime: cdr.SetupTime,
AnswerTime: cdr.AnswerTime,
Usage: cdr.Usage,
ExtraFields: cdr.ExtraFields,
ExtraInfo: cdr.ExtraInfo,
Partial: cdr.Partial,
PreRated: cdr.PreRated,
CostSource: cdr.CostSource,
Cost: cdr.Cost,
}
if cdr.ExtraFields != nil {
cln.ExtraFields = make(map[string]string, len(cdr.ExtraFields))
for key, val := range cdr.ExtraFields {
cln.ExtraFields[key] = val
}
}
return cln
}
func (cdr *CDR) AsMapStorage() (mp utils.MapStorage) {
mp = utils.MapStorage{
utils.MetaReq: cdr.AsMapStringIface(),
}
return
}
func (cdr *CDR) AsMapStringIface() (mp map[string]interface{}) {
mp = make(map[string]interface{})
for fld, val := range cdr.ExtraFields {
mp[fld] = val
}
mp[utils.RunID] = cdr.RunID
mp[utils.OrderID] = cdr.OrderID
mp[utils.OriginHost] = cdr.OriginHost
mp[utils.Source] = cdr.Source
mp[utils.OriginID] = cdr.OriginID
mp[utils.ToR] = cdr.ToR
mp[utils.RequestType] = cdr.RequestType
mp[utils.Tenant] = cdr.Tenant
mp[utils.Category] = cdr.Category
mp[utils.AccountField] = cdr.Account
mp[utils.Subject] = cdr.Subject
mp[utils.Destination] = cdr.Destination
mp[utils.SetupTime] = cdr.SetupTime
mp[utils.AnswerTime] = cdr.AnswerTime
mp[utils.Usage] = cdr.Usage
mp[utils.ExtraInfo] = cdr.ExtraInfo
mp[utils.Partial] = cdr.Partial
mp[utils.PreRated] = cdr.PreRated
mp[utils.CostSource] = cdr.CostSource
mp[utils.Cost] = cdr.Cost
return
}
func (cdr *CDR) AsExternalCDR() *ExternalCDR {
var usageStr string
switch cdr.ToR {
case utils.MetaVoice: // usage as time
usageStr = cdr.Usage.String()
default: // usage as units
usageStr = strconv.FormatInt(cdr.Usage.Nanoseconds(), 10)
}
return &ExternalCDR{
RunID: cdr.RunID,
OrderID: cdr.OrderID,
OriginHost: cdr.OriginHost,
Source: cdr.Source,
OriginID: cdr.OriginID,
ToR: cdr.ToR,
RequestType: cdr.RequestType,
Tenant: cdr.Tenant,
Category: cdr.Category,
Account: cdr.Account,
Subject: cdr.Subject,
Destination: cdr.Destination,
SetupTime: cdr.SetupTime.Format(time.RFC3339),
AnswerTime: cdr.AnswerTime.Format(time.RFC3339),
Usage: usageStr,
ExtraFields: cdr.ExtraFields,
CostSource: cdr.CostSource,
Cost: cdr.Cost,
ExtraInfo: cdr.ExtraInfo,
PreRated: cdr.PreRated,
}
}
func (cdr *CDR) String() string {
mrsh, _ := json.Marshal(cdr)
return string(mrsh)
}
// AsCDRsql converts the CDR into the format used for SQL storage
func (cdr *CDR) AsCDRsql() (cdrSQL *CDRsql) {
cdrSQL = new(CDRsql)
cdrSQL.RunID = cdr.RunID
cdrSQL.OriginHost = cdr.OriginHost
cdrSQL.Source = cdr.Source
cdrSQL.OriginID = cdr.OriginID
cdrSQL.TOR = cdr.ToR
cdrSQL.RequestType = cdr.RequestType
cdrSQL.Tenant = cdr.Tenant
cdrSQL.Category = cdr.Category
cdrSQL.Account = cdr.Account
cdrSQL.Subject = cdr.Subject
cdrSQL.Destination = cdr.Destination
cdrSQL.SetupTime = cdr.SetupTime
if !cdr.AnswerTime.IsZero() {
cdrSQL.AnswerTime = utils.TimePointer(cdr.AnswerTime)
}
cdrSQL.Usage = cdr.Usage.Nanoseconds()
cdrSQL.ExtraFields = utils.ToJSON(cdr.ExtraFields)
cdrSQL.CostSource = cdr.CostSource
cdrSQL.Cost = cdr.Cost
cdrSQL.ExtraInfo = cdr.ExtraInfo
cdrSQL.CreatedAt = time.Now()
return
}
func (cdr *CDR) AsCGREvent() *utils.CGREvent {
return &utils.CGREvent{
Tenant: cdr.Tenant,
ID: utils.UUIDSha1Prefix(),
Event: cdr.AsMapStringIface(),
APIOpts: map[string]interface{}{},
}
}
// NewCDRFromSQL converts the CDRsql into CDR
func NewCDRFromSQL(cdrSQL *CDRsql) (cdr *CDR, err error) {
cdr = new(CDR)
cdr.RunID = cdrSQL.RunID
cdr.OriginHost = cdrSQL.OriginHost
cdr.Source = cdrSQL.Source
cdr.OriginID = cdrSQL.OriginID
cdr.OrderID = cdrSQL.ID
cdr.ToR = cdrSQL.TOR
cdr.RequestType = cdrSQL.RequestType
cdr.Tenant = cdrSQL.Tenant
cdr.Category = cdrSQL.Category
cdr.Account = cdrSQL.Account
cdr.Subject = cdrSQL.Subject
cdr.Destination = cdrSQL.Destination
cdr.SetupTime = cdrSQL.SetupTime
if cdrSQL.AnswerTime != nil {
cdr.AnswerTime = *cdrSQL.AnswerTime
}
cdr.Usage = time.Duration(cdrSQL.Usage)
cdr.CostSource = cdrSQL.CostSource
cdr.Cost = cdrSQL.Cost
cdr.ExtraInfo = cdrSQL.ExtraInfo
if cdrSQL.ExtraFields != "" {
if err = json.Unmarshal([]byte(cdrSQL.ExtraFields), &cdr.ExtraFields); err != nil {
return nil, err
}
}
return
}
type ExternalCDR struct {
RunID string
OrderID int64
OriginHost string
Source string
OriginID string
ToR string
RequestType string
Tenant string
Category string
Account string
Subject string
Destination string
SetupTime string
AnswerTime string
Usage string
ExtraFields map[string]string
CostSource string
Cost float64
CostDetails string
ExtraInfo string
PreRated bool // Mark the CDR as rated so we do not process it during mediation
}
// UsageRecord is used when authorizing requests from outside, eg APIerSv1.GetMaxUsage
type UsageRecord struct {
ToR string
RequestType string
Tenant string
Category string
Account string
Subject string
Destination string
SetupTime string
AnswerTime string
Usage string
ExtraFields map[string]string
}
func (uR *UsageRecord) GetID() string {
return utils.Sha1(uR.ToR, uR.RequestType, uR.Tenant, uR.Category, uR.Account, uR.Subject, uR.Destination, uR.SetupTime, uR.AnswerTime, uR.Usage)
}
type ExternalCDRWithAPIOpts struct {
*ExternalCDR
APIOpts map[string]interface{}
}
type UsageRecordWithAPIOpts struct {
*UsageRecord
APIOpts map[string]interface{}
}
type CDRWithAPIOpts struct {
*CDR
APIOpts map[string]interface{}
}

View File

@@ -1,475 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"encoding/json"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func TestNewCDRFromExternalCDR(t *testing.T) {
extCdr := &ExternalCDR{
// CGRID: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: "2013-11-07T08:42:20Z", AnswerTime: "2013-11-07T08:42:26Z", RunID: utils.MetaDefault,
Usage: "10", Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
eStorCdr := &CDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated, RunID: utils.MetaDefault,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Usage: 10, Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
if CDR, err := NewCDRFromExternalCDR(extCdr, ""); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eStorCdr, CDR) {
t.Errorf("Expected: %+v, received: %+v", eStorCdr, CDR)
}
}
func TestCDRClone(t *testing.T) {
storCdr := &CDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated, Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault, Usage: 10,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, PreRated: true,
}
if clnStorCdr := storCdr.Clone(); !reflect.DeepEqual(storCdr, clnStorCdr) {
t.Errorf("Expecting: %+v, received: %+v", storCdr, clnStorCdr)
}
}
func TestFieldsAsString(t *testing.T) {
cdr := CDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1", Source: "test",
RequestType: utils.MetaRated, Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault, Usage: 10 * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01,
}
eVal := "call_from_1001"
if val := cdr.FieldsAsString(
config.NewRSRParsersMustCompile("~*req.Category;_from_;~*req.Account", utils.InfieldSep)); val != eVal {
t.Errorf("Expecting : %s, received: %q", eVal, val)
}
}
func TestFormatCost(t *testing.T) {
cdr := CDR{Cost: 1.01}
if cdr.FormatCost(0, 4) != "1.0100" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(0, 4))
}
cdr = CDR{Cost: 1.01001}
if cdr.FormatCost(0, 4) != "1.0100" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(0, 4))
}
if cdr.FormatCost(2, 0) != "101" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(2, 0))
}
if cdr.FormatCost(1, 0) != "10" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(1, 0))
}
if cdr.FormatCost(2, 3) != "101.001" {
t.Error("Unexpected format of the cost: ", cdr.FormatCost(2, 3))
}
}
func TestCDRAsMapStringIface(t *testing.T) {
cdr := &CDR{
OrderID: 123,
ToR: utils.MetaVoice,
OriginID: "dsafdsaf",
OriginHost: "192.168.1.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1002",
Subject: "1001",
Destination: "+4986517174963",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 10 * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01,
}
mp := map[string]interface{}{
"field_extr1": "val_extr1",
"fieldextr2": "valextr2",
utils.RunID: utils.MetaDefault,
utils.OrderID: cdr.OrderID,
utils.OriginHost: "192.168.1.1",
utils.Source: utils.UnitTest,
utils.OriginID: "dsafdsaf",
utils.ToR: utils.MetaVoice,
utils.RequestType: utils.MetaRated,
utils.Tenant: "cgrates.org",
utils.Category: "call",
utils.AccountField: "1002",
utils.Subject: "1001",
utils.Destination: "+4986517174963",
utils.SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
utils.AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
utils.Usage: 10 * time.Second,
utils.CostSource: cdr.CostSource,
utils.Cost: 1.01,
utils.PreRated: false,
utils.Partial: false,
utils.ExtraInfo: cdr.ExtraInfo,
}
if cdrMp := cdr.AsMapStringIface(); !reflect.DeepEqual(mp, cdrMp) {
t.Errorf("Expecting: %+v, received: %+v", mp, cdrMp)
}
}
func TestCDRNewCDRFromSQL(t *testing.T) {
extraFields := map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}
cdrSQL := &CDRsql{
ID: 123,
// Cgrid: "abecd993d06672714c4218a6dcf8278e0589a171",
RunID: utils.MetaDefault,
OriginID: "dsafdsaf",
TOR: utils.MetaVoice,
Source: utils.UnitTest,
Tenant: "cgrates.org",
Category: "call",
Account: "1001",
Subject: "1001",
Destination: "+4986517174963",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: utils.TimePointer(time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC)),
Usage: 10000000000,
Cost: 1.01,
RequestType: utils.MetaRated,
OriginHost: "192.168.1.1",
ExtraFields: utils.ToJSON(extraFields),
}
cdr := &CDR{
OrderID: 123,
ToR: utils.MetaVoice,
OriginID: "dsafdsaf",
OriginHost: "192.168.1.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1001",
Subject: "1001",
Destination: "+4986517174963",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 10 * time.Second,
Cost: 1.01,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
if eCDR, err := NewCDRFromSQL(cdrSQL); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(cdr, eCDR) {
t.Errorf("Expecting: %+v, received: %+v", cdr, eCDR)
}
}
func TestCDRAsCGREvent(t *testing.T) {
cdr := &CDR{
OrderID: 123,
ToR: utils.MetaVoice,
OriginID: "dsafdsaf",
OriginHost: "192.168.1.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1001",
Subject: "1001",
Destination: "+4986517174963",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 10 * time.Second,
Cost: 1.01,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
eCGREvent := utils.CGREvent{
Tenant: "cgrates.org",
ID: "GenePreRated",
Event: map[string]interface{}{
"Account": "1001",
"AnswerTime": time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
"Category": "call",
"Cost": 1.01,
"CostSource": "",
"Destination": "+4986517174963",
"ExtraInfo": "",
"OrderID": int64(123),
"OriginHost": "192.168.1.1",
"OriginID": "dsafdsaf",
"Partial": false,
"RequestType": utils.MetaRated,
"RunID": utils.MetaDefault,
"SetupTime": time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
"Source": "UNIT_TEST",
"Subject": "1001",
"Tenant": "cgrates.org",
"ToR": "*voice",
"Usage": 10 * time.Second,
"field_extr1": "val_extr1",
"fieldextr2": "valextr2",
"PreRated": false,
},
}
cgrEvent := cdr.AsCGREvent()
if !reflect.DeepEqual(eCGREvent.Tenant, cgrEvent.Tenant) {
t.Errorf("Expecting: %+v, received: %+v", eCGREvent.Tenant, cgrEvent.Tenant)
}
for fldName, fldVal := range eCGREvent.Event {
if _, has := cgrEvent.Event[fldName]; !has {
t.Errorf("Expecting: %+v, received: %+v", fldName, nil)
} else if fldVal != cgrEvent.Event[fldName] {
t.Errorf("Expecting: %s:%+v, received: %s:%+v",
fldName, eCGREvent.Event[fldName], fldName, cgrEvent.Event[fldName])
}
}
}
func TestCDRAddDefaults(t *testing.T) {
cdr := &CDR{
OriginID: "dsafdsaf",
OriginHost: "192.168.1.2",
Account: "1001",
}
cfg := config.NewDefaultCGRConfig()
eCDR := &CDR{
ToR: utils.MetaVoice,
RunID: utils.MetaDefault,
Subject: "1001",
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: utils.Call,
OriginID: "dsafdsaf",
OriginHost: "192.168.1.2",
Account: "1001",
}
cdr.AddDefaults(cfg)
if !reflect.DeepEqual(cdr, eCDR) {
t.Errorf("Expecting: %+v, received: %+v", eCDR, cdr)
}
}
func TestNewCDRFromExternalCDRSetupTimeError(t *testing.T) {
extCdr := &ExternalCDR{
// CGRID: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: "*testTime", AnswerTime: "2013-11-07T08:42:26Z", RunID: utils.MetaDefault,
Usage: "10", Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
_, err := NewCDRFromExternalCDR(extCdr, "")
if err == nil || err.Error() != "Unsupported time format" {
t.Error(err)
}
}
func TestNewCDRFromExternalCDRAnswerTimeError(t *testing.T) {
extCdr := &ExternalCDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", AnswerTime: "*testTime", RunID: utils.MetaDefault,
Usage: "10", Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
_, err := NewCDRFromExternalCDR(extCdr, "")
if err == nil || err.Error() != "Unsupported time format" {
t.Error(err)
}
}
func TestNewCDRFromExternalCDRUsageError(t *testing.T) {
extCdr := &ExternalCDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", RunID: utils.MetaDefault,
Usage: "testUsage", Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
_, err := NewCDRFromExternalCDR(extCdr, "")
if err == nil || err.Error() != `time: invalid duration "testUsage"` {
t.Error(err)
}
}
func TestCDRCloneNilCDR(t *testing.T) {
var storCdr *CDR
if clnStorCdr := storCdr.Clone(); !reflect.DeepEqual(storCdr, clnStorCdr) {
t.Errorf("\nExpecting: %+v, \nreceived: %+v", storCdr, clnStorCdr)
}
}
func TestAsExternalCDR(t *testing.T) {
extCdr := &ExternalCDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: "2013-11-07T08:42:20Z", AnswerTime: "2013-11-07T08:42:26Z", RunID: utils.MetaDefault,
Usage: "10ns", Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
eStorCdr := &CDR{
OrderID: 123, ToR: utils.MetaVoice, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated, RunID: utils.MetaDefault,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Usage: 10, Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
if eCDR := eStorCdr.AsExternalCDR(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(extCdr, eCDR) {
t.Errorf("\nExpected: %+v, \nreceived: %+v", utils.ToJSON(extCdr), utils.ToJSON(eCDR))
}
}
func TestAsExternalCDRDefaultTOR(t *testing.T) {
extCdr := &ExternalCDR{
OrderID: 123, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: "2013-11-07T08:42:20Z", AnswerTime: "2013-11-07T08:42:26Z", RunID: utils.MetaDefault,
Usage: "10", Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
eStorCdr := &CDR{
OrderID: 123, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated, RunID: utils.MetaDefault,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Usage: 10, Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
if eCDR := eStorCdr.AsExternalCDR(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(extCdr, eCDR) {
t.Errorf("\nExpected: %+v, \nreceived: %+v", utils.ToJSON(extCdr), utils.ToJSON(eCDR))
}
}
func TestCDRString(t *testing.T) {
testCdr := &CDR{
OrderID: 123, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated, RunID: utils.MetaDefault,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Usage: 10, Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
mrsh, _ := json.Marshal(testCdr)
expected := string(mrsh)
result := testCdr.String()
if !reflect.DeepEqual(result, expected) {
t.Errorf("\nExpected <%+v>, \nreceived <%+v>", expected, result)
}
}
func TestCDRAsCDRsql(t *testing.T) {
cdr := &CDR{
OrderID: 123, OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated, RunID: utils.MetaDefault,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Usage: 10, Cost: 1.01, PreRated: true,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
cdrSQL := new(CDRsql)
cdrSQL.RunID = cdr.RunID
cdrSQL.OriginHost = cdr.OriginHost
cdrSQL.Source = cdr.Source
cdrSQL.OriginID = cdr.OriginID
cdrSQL.TOR = cdr.ToR
cdrSQL.RequestType = cdr.RequestType
cdrSQL.Tenant = cdr.Tenant
cdrSQL.Category = cdr.Category
cdrSQL.Account = cdr.Account
cdrSQL.Subject = cdr.Subject
cdrSQL.Destination = cdr.Destination
cdrSQL.SetupTime = cdr.SetupTime
if !cdr.AnswerTime.IsZero() {
cdrSQL.AnswerTime = utils.TimePointer(cdr.AnswerTime)
}
cdrSQL.Usage = cdr.Usage.Nanoseconds()
cdrSQL.ExtraFields = utils.ToJSON(cdr.ExtraFields)
cdrSQL.CostSource = cdr.CostSource
cdrSQL.Cost = cdr.Cost
cdrSQL.ExtraInfo = cdr.ExtraInfo
result := cdr.AsCDRsql()
cdrSQL.CreatedAt = result.CreatedAt
if !reflect.DeepEqual(result, cdrSQL) {
t.Errorf("\nExpected <%+v>, \nreceived <%+v>", utils.ToJSON(cdrSQL), utils.ToJSON(result))
}
}
func TestCDRGetID(t *testing.T) {
uR := &UsageRecord{
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1001",
Subject: "1001",
Destination: "1002",
Usage: "10",
}
result := uR.GetID()
expected := utils.Sha1(uR.ToR, uR.RequestType, uR.Tenant, uR.Category, uR.Account, uR.Subject, uR.Destination, uR.SetupTime, uR.AnswerTime, uR.Usage)
if !reflect.DeepEqual(result, expected) {
t.Errorf("\nExpected <%+v>, \nreceived <%+v>", utils.ToJSON(expected), utils.ToJSON(result))
}
}

View File

@@ -1,179 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"encoding/json"
"io"
"strconv"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
const (
// Freswitch event property names
fsCDRMap = "variables"
fsUUID = "uuid" // -Unique ID for this call leg
fsCallDestNR = "dialed_extension"
fsParkTime = "start_epoch"
fsSetupTime = "start_epoch"
fsAnswerTime = "answer_epoch"
fsHangupTime = "end_epoch"
fsDuration = "billsec"
fsUsernameVar = "user_name"
fsCDRSource = "freeswitch_json"
fsSIPReqUser = "sip_req_user" // Apps like FusionPBX do not set dialed_extension, alternative being destination_number but that comes in customer profile, not in vars
fsProgressMediamsec = "progress_mediamsec"
fsProgressMS = "progressmsec"
fsUsername = "username"
fsIPv4 = "FreeSWITCH-IPv4"
)
func NewFSCdr(body io.Reader, cgrCfg *config.CGRConfig) (*FSCdr, error) {
fsCdr := &FSCdr{cgrCfg: cgrCfg, vars: make(map[string]string)}
var err error
if err = json.NewDecoder(body).Decode(&fsCdr.body); err != nil {
return nil, err
}
if variables, ok := fsCdr.body[fsCDRMap]; ok {
if variables, ok := variables.(map[string]interface{}); ok {
for k, v := range variables {
fsCdr.vars[k] = v.(string)
}
}
}
return fsCdr, nil
}
type FSCdr struct {
cgrCfg *config.CGRConfig
vars map[string]string
body map[string]interface{} // keeps the loaded body for extra field search
}
func (fsCdr FSCdr) getOriginID() string {
return utils.Sha1(fsCdr.vars[fsUUID],
utils.FirstNonEmpty(fsCdr.vars[utils.CGROriginHost], fsCdr.vars[fsIPv4]))
}
func (fsCdr FSCdr) getExtraFields() map[string]string {
extraFields := make(map[string]string, len(fsCdr.cgrCfg.CdrsCfg().ExtraFields))
const dynprefix string = utils.MetaDynReq + utils.NestingSep
for _, field := range fsCdr.cgrCfg.CdrsCfg().ExtraFields {
if !strings.HasPrefix(field.Rules, dynprefix) {
continue
}
attrName := field.AttrName()[5:]
origFieldVal, foundInVars := fsCdr.vars[attrName]
if !foundInVars {
origFieldVal = fsCdr.searchExtraField(attrName, fsCdr.body)
}
if parsed, err := field.ParseValue(origFieldVal); err == nil {
extraFields[attrName] = parsed
}
}
return extraFields
}
func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) (result string) {
for key, value := range body {
if key == field {
return utils.IfaceAsString(value)
}
switch v := value.(type) {
case map[string]interface{}:
if result = fsCdr.searchExtraField(field, v); len(result) != 0 {
return
}
case []interface{}:
for _, item := range v {
if otherMap, ok := item.(map[string]interface{}); ok {
if result = fsCdr.searchExtraField(field, otherMap); len(result) != 0 {
return
}
}
}
}
}
return
}
// firstDefined will return first defined or search for dfltFld
func (fsCdr FSCdr) firstDefined(fldNames []string, dfltFld string) (val string) {
var has bool
for _, fldName := range fldNames {
if val, has = fsCdr.vars[fldName]; has {
return
}
}
return fsCdr.searchExtraField(dfltFld, fsCdr.body)
}
func (fsCdr FSCdr) AsCDR(timezone string) (storCdr *CDR, err error) {
storCdr = &CDR{
RunID: fsCdr.vars["cgr_runid"],
OriginHost: utils.FirstNonEmpty(fsCdr.vars[utils.CGROriginHost], fsCdr.vars[fsIPv4]),
Source: fsCDRSource,
OriginID: fsCdr.vars[fsUUID],
ToR: utils.MetaVoice,
RequestType: utils.FirstNonEmpty(fsCdr.vars[utils.CGRReqType], fsCdr.cgrCfg.GeneralCfg().DefaultReqType),
Tenant: utils.FirstNonEmpty(fsCdr.vars[utils.CGRTenant], fsCdr.cgrCfg.GeneralCfg().DefaultTenant),
Category: utils.FirstNonEmpty(fsCdr.vars[utils.CGRCategory], fsCdr.cgrCfg.GeneralCfg().DefaultCategory),
Account: fsCdr.firstDefined([]string{utils.CGRAccount, fsUsernameVar}, fsUsername),
Subject: fsCdr.firstDefined([]string{utils.CGRSubject, utils.CGRAccount, fsUsernameVar}, fsUsername),
Destination: utils.FirstNonEmpty(fsCdr.vars[utils.CGRDestination], fsCdr.vars[fsCallDestNR], fsCdr.vars[fsSIPReqUser]),
ExtraFields: fsCdr.getExtraFields(),
ExtraInfo: fsCdr.vars["cgr_extrainfo"],
CostSource: fsCdr.vars["cgr_costsource"],
Cost: -1,
}
if orderID, hasIt := fsCdr.vars["cgr_orderid"]; hasIt {
if storCdr.OrderID, err = strconv.ParseInt(orderID, 10, 64); err != nil {
return nil, err
}
}
if setupTime, hasIt := fsCdr.vars[fsSetupTime]; hasIt {
if storCdr.SetupTime, err = utils.ParseTimeDetectLayout(setupTime, timezone); err != nil {
return nil, err
} // Not interested to process errors, should do them if necessary in a previous step
}
if answerTime, hasIt := fsCdr.vars[fsAnswerTime]; hasIt {
if storCdr.AnswerTime, err = utils.ParseTimeDetectLayout(answerTime, timezone); err != nil {
return nil, err
}
}
if usage, hasIt := fsCdr.vars[fsDuration]; hasIt {
if storCdr.Usage, err = utils.ParseDurationWithSecs(usage); err != nil {
return nil, err
}
}
if partial, hasIt := fsCdr.vars["cgr_partial"]; hasIt {
if storCdr.Partial, err = strconv.ParseBool(partial); err != nil {
return nil, err
}
}
if preRated, hasIt := fsCdr.vars["cgr_prerated"]; hasIt {
if storCdr.PreRated, err = strconv.ParseBool(preRated); err != nil {
return nil, err
}
}
return
}

View File

@@ -1,787 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"bytes"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
var body = []byte(`{
"core-uuid": "eb8bcdd2-d9eb-4f8f-80c3-1a4042aabe31",
"switchname": "FSDev1",
"channel_data": {
"state": "CS_REPORTING",
"state_number": "11",
"flags": "0=1;1=1;3=1;20=1;37=1;38=1;40=1;43=1;48=1;53=1;75=1;98=1;112=1;113=1;122=1;134=1",
"caps": "1=1;2=1;3=1;4=1;5=1;6=1"
},
"callStats": {
"audio": {
"inbound": {
"raw_bytes": 572588,
"media_bytes": 572588,
"packet_count": 3329,
"media_packet_count": 3329,
"skip_packet_count": 10,
"jitter_packet_count": 0,
"dtmf_packet_count": 0,
"cng_packet_count": 0,
"flush_packet_count": 0,
"largest_jb_size": 0,
"jitter_min_variance": 0,
"jitter_max_variance": 0,
"jitter_loss_rate": 0,
"jitter_burst_rate": 0,
"mean_interval": 0,
"flaw_total": 0,
"quality_percentage": 100,
"mos": 4.500000
},
"outbound": {
"raw_bytes": 0,
"media_bytes": 0,
"packet_count": 0,
"media_packet_count": 0,
"skip_packet_count": 0,
"dtmf_packet_count": 0,
"cng_packet_count": 0,
"rtcp_packet_count": 0,
"rtcp_octet_count": 0
}
}
},
"variables": {
"uuid": "3da8bf84-c133-4959-9e24-e72875cb33a1",
"session_id": "7",
"sip_from_user": "1001",
"sip_from_uri": "1001@10.10.10.204",
"sip_from_host": "10.10.10.204",
"channel_name": "sofia/internal/1001@10.10.10.204",
"ep_codec_string": "CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b",
"sip_local_network_addr": "10.10.10.204",
"sip_network_ip": "10.10.10.100",
"sip_network_port": "5060",
"sip_invite_stamp": "1515666344534355",
"sip_received_ip": "10.10.10.100",
"sip_received_port": "5060",
"sip_via_protocol": "udp",
"sip_from_user_stripped": "1001",
"sofia_profile_name": "internal",
"recovery_profile_name": "internal",
"sip_req_user": "1002",
"sip_req_uri": "1002@10.10.10.204",
"sip_req_host": "10.10.10.204",
"sip_to_user": "1002",
"sip_to_uri": "1002@10.10.10.204",
"sip_to_host": "10.10.10.204",
"sip_contact_params": "transport=udp;registering_acc=10_10_10_204",
"sip_contact_user": "1001",
"sip_contact_port": "5060",
"sip_contact_uri": "1001@10.10.10.100:5060",
"sip_contact_host": "10.10.10.100",
"sip_user_agent": "Jitsi2.10.5550Linux",
"sip_via_host": "10.10.10.100",
"sip_via_port": "5060",
"presence_id": "1001@10.10.10.204",
"cgr_notify": "AUTH_OK",
"max_forwards": "69",
"transfer_history": "1515666344:b4300942-e809-4393-99cb-d39a1bc3c219:bl_xfer:1002/default/XML",
"transfer_source": "1515666344:b4300942-e809-4393-99cb-d39a1bc3c219:bl_xfer:1002/default/XML",
"DP_MATCH": "ARRAY::1002|:1002",
"call_uuid": "3da8bf84-c133-4959-9e24-e72875cb33a1",
"call_timeout": "30",
"current_application_data": "user/1002@10.10.10.204",
"current_application": "bridge",
"dialed_user": "1002",
"dialed_domain": "10.10.10.204",
"originated_legs": "ARRAY::f52c26f1-b018-4963-bf6d-a3111d1a0320;Outbound Call;1002|:f52c26f1-b018-4963-bf6d-a3111d1a0320;Outbound Call;1002",
"switch_m_sdp": "v=0\r\no=1002-jitsi.org 0 0 IN IP4 10.10.10.100\r\ns=-\r\nc=IN IP4 10.10.10.100\r\nt=0 0\r\nm=audio 5022 RTP/AVP 0 8 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:101 telephone-event/8000\r\n",
"rtp_use_codec_name": "PCMU",
"rtp_use_codec_rate": "8000",
"rtp_use_codec_ptime": "20",
"rtp_use_codec_channels": "1",
"rtp_last_audio_codec_string": "PCMU@8000h@20i@1c",
"read_codec": "PCMU",
"original_read_codec": "PCMU",
"read_rate": "8000",
"original_read_rate": "8000",
"write_codec": "PCMU",
"write_rate": "8000",
"video_possible": "true",
"video_media_flow": "sendonly",
"local_media_ip": "10.10.10.204",
"local_media_port": "21566",
"advertised_media_ip": "10.10.10.204",
"rtp_use_timer_name": "soft",
"rtp_use_pt": "0",
"rtp_use_ssrc": "1448966920",
"endpoint_disposition": "ANSWER",
"originate_causes": "ARRAY::f52c26f1-b018-4963-bf6d-a3111d1a0320;NONE|:f52c26f1-b018-4963-bf6d-a3111d1a0320;NONE",
"originate_disposition": "SUCCESS",
"DIALSTATUS": "SUCCESS",
"last_bridge_to": "f52c26f1-b018-4963-bf6d-a3111d1a0320",
"bridge_channel": "sofia/internal/1002@10.10.10.100:5060",
"bridge_uuid": "f52c26f1-b018-4963-bf6d-a3111d1a0320",
"signal_bond": "f52c26f1-b018-4963-bf6d-a3111d1a0320",
"last_sent_callee_id_name": "Outbound Call",
"last_sent_callee_id_number": "1002",
"switch_r_sdp": "v=0\r\no=1001-jitsi.org 0 1 IN IP4 10.10.10.100\r\ns=-\r\nc=IN IP4 10.10.10.100\r\nt=0 0\r\nm=audio 5018 RTP/AVP 96 97 98 9 100 102 0 8 103 3 104 101\r\na=rtpmap:96 opus/48000/2\r\na=fmtp:96 usedtx=1\r\na=rtpmap:97 SILK/24000\r\na=rtpmap:98 SILK/16000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:100 speex/32000\r\na=rtpmap:102 speex/16000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:103 iLBC/8000\r\na=rtpmap:3 GSM/8000\r\na=rtpmap:104 speex/8000\r\na=rtpmap:101 telephone-event/8000\r\na=sendonly\r\na=ptime:20\r\na=extmap:1 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=rtcp-xr:voip-metrics\r\nm=video 0 RTP/AVP 105 99\r\n",
"rtp_use_codec_string": "PCMU,PCMA",
"audio_media_flow": "recvonly",
"remote_media_ip": "10.10.10.100",
"remote_media_port": "5018",
"rtp_audio_recv_pt": "0",
"dtmf_type": "rfc2833",
"rtp_2833_send_payload": "101",
"rtp_2833_recv_payload": "101",
"rtp_local_sdp_str": "v=0\r\no=FreeSWITCH 1515644781 1515644783 IN IP4 10.10.10.204\r\ns=FreeSWITCH\r\nc=IN IP4 10.10.10.204\r\nt=0 0\r\nm=audio 21566 RTP/AVP 0 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-16\r\na=ptime:20\r\na=recvonly\r\n",
"sip_to_tag": "m3g4NZ4rXFX3p",
"sip_from_tag": "f25afe20",
"sip_cseq": "2",
"sip_call_id": "818e26f805701988c1a330175d7d2629@0:0:0:0:0:0:0:0",
"sip_full_via": "SIP/2.0/UDP 10.10.10.100:5060;branch=z9hG4bK-313838-2ee350643dea826b4a74f8049852f307",
"sip_from_display": "1001",
"sip_full_from": "\"1001\" <sip:1001@10.10.10.204>;tag=f25afe20",
"sip_full_to": "<sip:1002@10.10.10.204>;tag=m3g4NZ4rXFX3p",
"sip_hangup_phrase": "OK",
"last_bridge_hangup_cause": "NORMAL_CLEARING",
"last_bridge_proto_specific_hangup_cause": "sip:200",
"bridge_hangup_cause": "NORMAL_CLEARING",
"hangup_cause": "NORMAL_CLEARING",
"hangup_cause_q850": "16",
"digits_dialed": "none",
"start_stamp": "2018-01-11 11:25:44",
"profile_start_stamp": "2018-01-11 11:25:44",
"answer_stamp": "2018-01-11 11:25:47",
"bridge_stamp": "2018-01-11 11:25:47",
"hold_stamp": "2018-01-11 11:25:48",
"progress_stamp": "2018-01-11 11:25:44",
"progress_media_stamp": "2018-01-11 11:25:47",
"hold_events": "{{1515666348363496,1515666415502648}}",
"end_stamp": "2018-01-11 11:26:55",
"start_epoch": "1515666344",
"start_uepoch": "1515666344534355",
"profile_start_epoch": "1515666344",
"profile_start_uepoch": "1515666344534355",
"answer_epoch": "1515666347",
"answer_uepoch": "1515666347954373",
"bridge_epoch": "1515666347",
"bridge_uepoch": "1515666347954373",
"last_hold_epoch": "1515666348",
"last_hold_uepoch": "1515666348363496",
"hold_accum_seconds": "67",
"hold_accum_usec": "67139151",
"hold_accum_ms": "67139",
"resurrect_epoch": "0",
"resurrect_uepoch": "0",
"progress_epoch": "1515666344",
"progress_uepoch": "1515666344594267",
"progress_media_epoch": "1515666347",
"progress_media_uepoch": "1515666347954373",
"end_epoch": "1515666415",
"end_uepoch": "1515666415494269",
"last_app": "bridge",
"last_arg": "user/1002@10.10.10.204",
"caller_id": "\"1001\" <1001>",
"duration": "71",
"billsec": "68",
"progresssec": "0",
"answersec": "3",
"waitsec": "3",
"progress_mediasec": "3",
"flow_billsec": "71",
"mduration": "70960",
"billmsec": "67540",
"progressmsec": "60",
"answermsec": "3420",
"waitmsec": "3420",
"progress_mediamsec": "3420",
"flow_billmsec": "70960",
"uduration": "70959914",
"billusec": "67539896",
"progressusec": "59912",
"answerusec": "3420018",
"waitusec": "3420018",
"progress_mediausec": "3420018",
"flow_billusec": "70959914",
"sip_hangup_disposition": "send_bye",
"rtp_audio_in_raw_bytes": "572588",
"rtp_audio_in_media_bytes": "572588",
"rtp_audio_in_packet_count": "3329",
"rtp_audio_in_media_packet_count": "3329",
"rtp_audio_in_skip_packet_count": "10",
"rtp_audio_in_jitter_packet_count": "0",
"rtp_audio_in_dtmf_packet_count": "0",
"rtp_audio_in_cng_packet_count": "0",
"rtp_audio_in_flush_packet_count": "0",
"rtp_audio_in_largest_jb_size": "0",
"rtp_audio_in_jitter_min_variance": "0.00",
"rtp_audio_in_jitter_max_variance": "0.00",
"rtp_audio_in_jitter_loss_rate": "0.00",
"rtp_audio_in_jitter_burst_rate": "0.00",
"rtp_audio_in_mean_interval": "0.00",
"rtp_audio_in_flaw_total": "0",
"rtp_audio_in_quality_percentage": "100.00",
"rtp_audio_in_mos": "4.50",
"rtp_audio_out_raw_bytes": "0",
"rtp_audio_out_media_bytes": "0",
"rtp_audio_out_packet_count": "0",
"rtp_audio_out_media_packet_count": "0",
"rtp_audio_out_skip_packet_count": "0",
"rtp_audio_out_dtmf_packet_count": "0",
"rtp_audio_out_cng_packet_count": "0",
"rtp_audio_rtcp_packet_count": "0",
"rtp_audio_rtcp_octet_count": "0"
},
"app_log": {
"applications": [{
"app_name": "park",
"app_data": "",
"app_stamp": "1515666344548466"
}, {
"app_name": "set",
"app_data": "ringback=",
"app_stamp": "1515666344575066"
}, {
"app_name": "set",
"app_data": "call_timeout=30",
"app_stamp": "1515666344576009"
}, {
"app_name": "bridge",
"app_data": "user/1002@10.10.10.204",
"app_stamp": "1515666344576703"
}]
},
"callflow": [{
"dialplan": "XML",
"profile_index": "2",
"extension": {
"name": "Local_Extension",
"number": "1002",
"applications": [{
"app_name": "set",
"app_data": "ringback=${us-ring}"
}, {
"app_name": "set",
"app_data": "call_timeout=30"
}, {
"app_name": "bridge",
"app_data": "user/${destination_number}@${domain_name}"
}]
},
"caller_profile": {
"username": "1001",
"dialplan": "XML",
"caller_id_name": "1001",
"ani": "1001",
"aniii": "",
"caller_id_number": "1001",
"network_addr": "10.10.10.100",
"rdnis": "1002",
"destination_number": "1002",
"uuid": "3da8bf84-c133-4959-9e24-e72875cb33a1",
"source": "mod_sofia",
"context": "default",
"chan_name": "sofia/internal/1001@10.10.10.204",
"originatee": {
"originatee_caller_profiles": [{
"username": "1001",
"dialplan": "XML",
"caller_id_name": "1001",
"ani": "1001",
"aniii": "",
"caller_id_number": "1001",
"network_addr": "10.10.10.100",
"rdnis": "1002",
"destination_number": "1002",
"uuid": "f52c26f1-b018-4963-bf6d-a3111d1a0320",
"source": "mod_sofia",
"context": "default",
"chan_name": "sofia/internal/1002@10.10.10.100:5060"
}, {
"username": "1001",
"dialplan": "XML",
"caller_id_name": "1001",
"ani": "1001",
"aniii": "",
"caller_id_number": "1001",
"network_addr": "10.10.10.100",
"rdnis": "1002",
"destination_number": "1002",
"uuid": "f52c26f1-b018-4963-bf6d-a3111d1a0320",
"source": "mod_sofia",
"context": "default",
"chan_name": "sofia/internal/1002@10.10.10.100:5060"
}]
}
},
"times": {
"created_time": "1515666344534355",
"profile_created_time": "1515666344534355",
"progress_time": "1515666344594267",
"progress_media_time": "1515666347954373",
"answered_time": "1515666347954373",
"bridged_time": "1515666347954373",
"last_hold_time": "1515666348363496",
"hold_accum_time": "67139151",
"hangup_time": "1515666415494269",
"resurrect_time": "0",
"transfer_time": "0"
}
}, {
"dialplan": "XML",
"profile_index": "1",
"extension": {
"name": "CGRateS_Auth",
"number": "1002",
"applications": [{
"app_name": "park",
"app_data": ""
}]
},
"caller_profile": {
"username": "1001",
"dialplan": "XML",
"caller_id_name": "1001",
"ani": "1001",
"aniii": "",
"caller_id_number": "1001",
"network_addr": "10.10.10.100",
"rdnis": "",
"destination_number": "1002",
"uuid": "3da8bf84-c133-4959-9e24-e72875cb33a1",
"source": "mod_sofia",
"context": "default",
"chan_name": "sofia/internal/1001@10.10.10.204"
},
"times": {
"created_time": "1515666344534355",
"profile_created_time": "1515666344534355",
"progress_time": "0",
"progress_media_time": "0",
"answered_time": "0",
"bridged_time": "0",
"last_hold_time": "0",
"hold_accum_time": "0",
"hangup_time": "0",
"resurrect_time": "0",
"transfer_time": "1515666344534355"
}
}]
}`)
var fsCdrCfg *config.CGRConfig
func TestFsCdrFirstNonEmpty(t *testing.T) {
fsCdrCfg = config.NewDefaultCGRConfig()
reader := bytes.NewReader(body)
fsCdr, err := NewFSCdr(reader, fsCdrCfg)
if err != nil {
t.Errorf("Error loading cdr: %v", err)
}
//fsc := fsCdr.(FSCdr)
if _, ok := fsCdr.vars["cgr_notify"]; !ok {
t.Error("Error parsing cdr: ", fsCdr)
}
}
func TestFsCdrCDRFields(t *testing.T) {
fsCdrCfg.CdrsCfg().ExtraFields = config.NewRSRParsersMustCompile("~*req.sip_user_agent", utils.FieldsSep)
reader := bytes.NewReader(body)
fsCdr, err := NewFSCdr(reader, fsCdrCfg)
if err != nil {
t.Errorf("Error loading cdr: %v", err)
}
setupTime, _ := utils.ParseTimeDetectLayout("1515666344", "")
answerTime, _ := utils.ParseTimeDetectLayout("1515666347", "")
expctCDR := &CDR{
ToR: utils.MetaVoice,
OriginID: "3da8bf84-c133-4959-9e24-e72875cb33a1",
OriginHost: "",
Source: "freeswitch_json",
Category: "call",
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Account: "1001",
Subject: "1001",
Destination: "1002",
SetupTime: setupTime,
AnswerTime: answerTime,
Usage: 68 * time.Second,
Cost: -1,
ExtraFields: map[string]string{"sip_user_agent": "Jitsi2.10.5550Linux"},
}
if CDR, err := fsCdr.AsCDR(""); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expctCDR, CDR) {
t.Errorf("Expecting: %+v, received: %+v", expctCDR, CDR)
}
}
func TestFsCdrSearchExtraFieldLast(t *testing.T) {
newReader := bytes.NewReader(body)
fsCdr, err := NewFSCdr(newReader, fsCdrCfg)
if err != nil {
t.Error(err)
}
value := fsCdr.searchExtraField("progress_media_time", fsCdr.body)
if value != "1515666347954373" {
t.Error("Error finding extra field: ", value)
}
}
func TestFsCdrSearchExtraField(t *testing.T) {
newReader := bytes.NewReader(body)
fsCdr, err := NewFSCdr(newReader, fsCdrCfg)
if err != nil {
t.Error(err)
}
fsCdrCfg.CdrsCfg().ExtraFields, err = config.NewRSRParsersFromSlice([]string{"~*req.caller_id_name"})
if err != nil {
t.Fatal(err)
}
extraFields := fsCdr.getExtraFields()
if len(extraFields) != 1 || extraFields["caller_id_name"] != "1001" {
t.Error("Error parsing extra fields: ", utils.ToJSON(extraFields))
}
}
func TestFsCdrSearchExtraFieldInSlice(t *testing.T) {
newReader := bytes.NewReader(body)
if fsCdr, err := NewFSCdr(newReader, fsCdrCfg); err != nil {
t.Error(err)
} else if value := fsCdr.searchExtraField("floatfld1", map[string]interface{}{"floatfld1": 6.4}); value != "6.4" {
t.Errorf("Expecting: 6.4, received: %s", value)
}
}
func TestFsCdrSearchReplaceInExtraFields(t *testing.T) {
fsCdrCfg.CdrsCfg().ExtraFields = config.NewRSRParsersMustCompile(`~*req.read_codec;~*req.sip_user_agent:s/([A-Za-z]*).+/$1/;~*req.write_codec`, utils.InfieldSep)
newReader := bytes.NewReader(body)
fsCdr, err := NewFSCdr(newReader, fsCdrCfg)
if err != nil {
t.Error(err)
}
extraFields := fsCdr.getExtraFields()
if len(extraFields) != 3 {
t.Error("Error parsing extra fields: ", extraFields)
}
if extraFields["sip_user_agent"] != "Jitsi" {
t.Error("Error parsing extra fields: ", utils.ToJSON(extraFields))
}
}
func TestFsCdrDDazRSRExtraFields(t *testing.T) {
eFieldsCfg := `{"cdrs": {
"extra_fields": ["~*req.effective_caller_id_number:s/(\\d+)/+$1/"],
},}`
simpleJSONCdr := []byte(`{
"core-uuid": "feef0b51-7fdf-4c4a-878e-aff233752de2",
"channel_data": {
"state": "CS_REPORTING",
"state_number": "11",
"flags": "0=1;1=1;3=1;36=1;37=1;39=1;42=1;47=1;52=1;73=1;75=1;94=1",
"caps": "1=1;2=1;3=1;4=1;5=1;6=1"
},
"variables": {
"uuid": "86cfd6e2-dbda-45a3-b59d-f683ec368e8b",
"session_id": "5",
"accountcode": "1001",
"user_context": "default",
"effective_caller_id_name": "Extension 1001",
"effective_caller_id_number": "4986517174963",
"outbound_caller_id_name": "FreeSWITCH",
"outbound_caller_id_number": "0000000000"
},
"times": {
"created_time": "1396984221278217",
"profile_created_time": "1396984221278217",
"progress_time": "0",
"progress_media_time": "0",
"answered_time": "0",
"hangup_time": "0",
"resurrect_time": "0",
"transfer_time": "1396984221377035"
}
}`)
var err error
fsCdrCfg, err = config.NewCGRConfigFromJSONStringWithDefaults(eFieldsCfg)
expCdrExtra := config.NewRSRParsersMustCompile(`~*req.effective_caller_id_number:s/(\d+)/+$1/`, utils.InfieldSep)
if err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(expCdrExtra[0], fsCdrCfg.CdrsCfg().ExtraFields[0]) { // Kinda deepEqual bug since without index does not match
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(expCdrExtra), utils.ToJSON(fsCdrCfg.CdrsCfg().ExtraFields))
}
newReader := bytes.NewReader(simpleJSONCdr)
fsCdr, err := NewFSCdr(newReader, fsCdrCfg)
if err != nil {
t.Error("Could not parse cdr", err.Error())
}
extraFields := fsCdr.getExtraFields()
if extraFields["effective_caller_id_number"] != "+4986517174963" {
t.Errorf("Unexpected effective_caller_id_number received: %+v", extraFields["effective_caller_id_number"])
}
}
func TestFsCdrFirstDefined(t *testing.T) {
newReader := bytes.NewReader(body)
fsCdr, _ := NewFSCdr(newReader, fsCdrCfg)
value := fsCdr.firstDefined([]string{utils.CGRSubject, utils.CGRAccount, fsUsernameVar}, fsUsername)
if value != "1001" {
t.Errorf("Expecting: 1001, received: %s", value)
}
value = fsCdr.firstDefined([]string{utils.CGRAccount, fsUsernameVar}, fsUsername)
if value != "1001" {
t.Errorf("Expecting: 1001, received: %s", value)
}
}
func TestFscdrAsCDR(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
cgrCfg.CdrsCfg().ExtraFields, err = config.NewRSRParsersFromSlice([]string{"~*req.PayPalAccount"})
if err != nil {
t.Error(err)
}
fsCdrByte := []byte(` {
"variables": {
"cgr_orderid": "123",
"cgr_partial": "true",
"cgr_prerated": "false"
}
}`)
expectedCdr := &CDR{
OrderID: 123,
ToR: utils.MetaVoice,
Source: fsCDRSource, Category: cgrCfg.GeneralCfg().DefaultCategory,
Tenant: cgrCfg.GeneralCfg().DefaultTenant,
RequestType: cgrCfg.GeneralCfg().DefaultReqType,
Partial: true,
PreRated: false,
ExtraFields: map[string]string{
"PayPalAccount": "",
},
Cost: -1,
}
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else {
if cdr, err := fsCdr.AsCDR(""); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedCdr, cdr) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedCdr), utils.ToJSON(cdr))
}
}
}
func TestFscdrAsCdrOrderId(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"cgr_orderid": "123s"
}
}`)
expectedErr := "strconv.ParseInt: parsing \"123s\": invalid syntax"
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else if _, err := fsCdr.AsCDR(""); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func TestFscdrAsCdrSetupTime(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"start_epoch": "123ss"
}
}`)
expectedErr := "Unsupported time format"
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else if _, err := fsCdr.AsCDR(""); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func TestFscdrAsCdrAnswerTime(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"answer_epoch": "123ss"
}
}`)
expectedErr := "Unsupported time format"
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else if _, err := fsCdr.AsCDR(""); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func TestFscdrAsCdrUsage(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"billsec": "1ss"
}
}`)
expectedErr := "time: unknown unit \"ss\" in duration \"1ss\""
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else if _, err := fsCdr.AsCDR(""); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func TestFscdrAsCdrPartial(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"cgr_partial": "InvalidBoolFormat"
}
}`)
expectedErr := "strconv.ParseBool: parsing \"InvalidBoolFormat\": invalid syntax"
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else if _, err := fsCdr.AsCDR(""); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func TestFscdrAsCdrPreRated(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"cgr_prerated": "InvalidBoolFormat"
}
}`)
expectedErr := "strconv.ParseBool: parsing \"InvalidBoolFormat\": invalid syntax"
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else if _, err := fsCdr.AsCDR(""); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
}
}
func TestFscdrAsCdrFirstDefined(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
fsCdrByte := []byte(` {
"variables": {
"cgr_account": "randomAccount"
}
}`)
expectedCdr := &CDR{
ToR: utils.MetaVoice,
Source: fsCDRSource, Category: cgrCfg.GeneralCfg().DefaultCategory,
Tenant: cgrCfg.GeneralCfg().DefaultTenant,
RequestType: cgrCfg.GeneralCfg().DefaultReqType,
Account: "randomAccount",
Subject: "randomAccount",
ExtraFields: map[string]string{},
Cost: -1,
}
newReader := bytes.NewReader(fsCdrByte)
if fsCdr, err := NewFSCdr(newReader, cgrCfg); err != nil {
t.Error(err)
} else {
if cdr, err := fsCdr.AsCDR(""); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedCdr, cdr) {
t.Errorf("Expected %+v \n, redceived %+v", utils.ToJSON(expectedCdr), utils.ToJSON(cdr))
}
}
}
func TestNewFSCdrDecodeError(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
expectedErr := "EOF"
newReader := bytes.NewReader(nil)
if _, err := NewFSCdr(newReader, cgrCfg); err == nil || err.Error() != expectedErr {
t.Errorf("Expected %+v, received %+v", expectedErr, err)
}
}
func TestSearchExtraFieldDefaultType(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
newMap := map[string]interface{}{
"variables": map[string]string{
"cgr_orderid": "123",
},
}
fsCdr := FSCdr{
cgrCfg: cgrCfg,
body: newMap,
}
fsCdr.searchExtraField(utils.EmptyString, newMap)
}
func TestSearchExtraFieldInterface(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
newMap := map[string]interface{}{ //There is a slice with no maps
"variables": []interface{}{
2,
"randomValue",
true,
},
}
fsCdr := FSCdr{
cgrCfg: cgrCfg,
body: newMap,
}
fsCdr.searchExtraField(utils.EmptyString, newMap)
}
func TestGetExtraFields(t *testing.T) {
cgrCfg := config.NewDefaultCGRConfig()
cgrCfg.CdrsCfg().ExtraFields, err = config.NewRSRParsersFromSlice([]string{"PayPalAccount"})
if err != nil {
t.Error(err)
}
fsCdr := FSCdr{
cgrCfg: cgrCfg,
}
expected := map[string]string{}
if reply := fsCdr.getExtraFields(); !reflect.DeepEqual(reply, expected) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expected), utils.ToJSON(reply))
}
}

View File

@@ -19,10 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"fmt"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
@@ -195,80 +193,6 @@ func (me MapEvent) AsMapString(ignoredFlds utils.StringSet) (mp map[string]strin
return
}
// AsCDR exports the MapEvent as CDR
func (me MapEvent) AsCDR(cfg *config.CGRConfig, tnt, tmz string) (cdr *CDR, err error) {
cdr = &CDR{Tenant: tnt, Cost: -1.0, ExtraFields: make(map[string]string)}
for k, v := range me {
if !utils.MainCDRFields.Has(k) { // not primary field, populate extra ones
cdr.ExtraFields[k] = utils.IfaceAsString(v)
continue
}
switch k {
default:
// for the momment this return can not be reached because we implemented a case for every MainCDRField
return nil, fmt.Errorf("unimplemented CDR field: <%s>", k)
case utils.RunID:
cdr.RunID = utils.IfaceAsString(v)
case utils.OriginHost:
cdr.OriginHost = utils.IfaceAsString(v)
case utils.Source:
cdr.Source = utils.IfaceAsString(v)
case utils.OriginID:
cdr.OriginID = utils.IfaceAsString(v)
case utils.ToR:
cdr.ToR = utils.IfaceAsString(v)
case utils.RequestType:
cdr.RequestType = utils.IfaceAsString(v)
case utils.Tenant:
cdr.Tenant = utils.IfaceAsString(v)
case utils.Category:
cdr.Category = utils.IfaceAsString(v)
case utils.AccountField:
cdr.Account = utils.IfaceAsString(v)
case utils.Subject:
cdr.Subject = utils.IfaceAsString(v)
case utils.Destination:
cdr.Destination = utils.IfaceAsString(v)
case utils.SetupTime:
if cdr.SetupTime, err = utils.IfaceAsTime(v, tmz); err != nil {
return nil, err
}
case utils.AnswerTime:
if cdr.AnswerTime, err = utils.IfaceAsTime(v, tmz); err != nil {
return nil, err
}
case utils.Usage:
if cdr.Usage, err = utils.IfaceAsDuration(v); err != nil {
return nil, err
}
case utils.Partial:
if cdr.Partial, err = utils.IfaceAsBool(v); err != nil {
return nil, err
}
case utils.PreRated:
if cdr.PreRated, err = utils.IfaceAsBool(v); err != nil {
return nil, err
}
case utils.CostSource:
cdr.CostSource = utils.IfaceAsString(v)
case utils.Cost:
if cdr.Cost, err = utils.IfaceAsFloat64(v); err != nil {
return nil, err
}
case utils.ExtraInfo:
cdr.ExtraInfo = utils.IfaceAsString(v)
case utils.OrderID:
if cdr.OrderID, err = utils.IfaceAsTInt64(v); err != nil {
return nil, err
}
}
}
if cfg != nil {
cdr.AddDefaults(cfg)
}
return
}
// Data returns the MapEvent as a map[string]interface{}
func (me MapEvent) Data() map[string]interface{} {
return me

View File

@@ -1,237 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
var sureTaxClient *http.Client // Cache the client here if in use
// Init a new request to be sent out to SureTax
func NewSureTaxRequest(cdr *CDR, stCfg *config.SureTaxCfg) (*SureTaxRequest, error) {
if stCfg == nil {
return nil, errors.New("invalid SureTax config")
}
aTimeLoc := cdr.AnswerTime.In(stCfg.Timezone)
revenue := utils.Round(cdr.Cost, 4, utils.MetaRoundingMiddle)
unts, err := strconv.ParseInt(cdr.FieldsAsString(stCfg.Units), 10, 64)
if err != nil {
return nil, err
}
taxExempt := []string{}
definedTaxExtempt := cdr.FieldsAsString(stCfg.TaxExemptionCodeList)
if len(definedTaxExtempt) != 0 {
taxExempt = strings.Split(cdr.FieldsAsString(stCfg.TaxExemptionCodeList), ",")
}
stReq := new(STRequest)
stReq.ClientNumber = stCfg.ClientNumber
stReq.BusinessUnit = stCfg.BusinessUnit
stReq.ValidationKey = stCfg.ValidationKey
stReq.DataYear = strconv.Itoa(aTimeLoc.Year())
stReq.DataMonth = strconv.Itoa(int(aTimeLoc.Month()))
stReq.TotalRevenue = revenue
stReq.ReturnFileCode = stCfg.ReturnFileCode
stReq.ClientTracking = cdr.FieldsAsString(stCfg.ClientTracking)
stReq.ResponseGroup = stCfg.ResponseGroup
stReq.ResponseType = stCfg.ResponseType
stReq.ItemList = []*STRequestItem{
{
CustomerNumber: cdr.FieldsAsString(stCfg.CustomerNumber),
OrigNumber: cdr.FieldsAsString(stCfg.OrigNumber),
TermNumber: cdr.FieldsAsString(stCfg.TermNumber),
BillToNumber: cdr.FieldsAsString(stCfg.BillToNumber),
Zipcode: cdr.FieldsAsString(stCfg.Zipcode),
Plus4: cdr.FieldsAsString(stCfg.Plus4),
P2PZipcode: cdr.FieldsAsString(stCfg.P2PZipcode),
P2PPlus4: cdr.FieldsAsString(stCfg.P2PPlus4),
TransDate: aTimeLoc.Format("2006-01-02T15:04:05"),
Revenue: revenue,
Units: unts,
UnitType: cdr.FieldsAsString(stCfg.UnitType),
Seconds: int64(cdr.Usage.Seconds()),
TaxIncludedCode: cdr.FieldsAsString(stCfg.TaxIncluded),
TaxSitusRule: cdr.FieldsAsString(stCfg.TaxSitusRule),
TransTypeCode: cdr.FieldsAsString(stCfg.TransTypeCode),
SalesTypeCode: cdr.FieldsAsString(stCfg.SalesTypeCode),
RegulatoryCode: stCfg.RegulatoryCode,
TaxExemptionCodeList: taxExempt,
},
}
jsnContent, err := json.Marshal(stReq)
if err != nil {
return nil, err
}
return &SureTaxRequest{Request: string(jsnContent)}, nil
}
// SureTax JSON Request
type SureTaxRequest struct {
Request string `json:"request"` // SureTax Requires us to encapsulate the content into a request element
}
// SureTax JSON Response
type SureTaxResponse struct {
D string // SureTax requires encapsulating reply into a D object
}
// SureTax Request type
type STRequest struct {
ClientNumber string // Client ID Number provided by SureTax. Required. Max Len: 10
BusinessUnit string // Clients Business Unit. Value for this field is not required. Max Len: 20
ValidationKey string // Validation Key provided by SureTax. Required for client access to API function. Max Len: 36
DataYear string // Required. YYYY Year to use for tax calculation purposes
DataMonth string // Required. MM Month to use for tax calculation purposes. Leading zero is preferred.
TotalRevenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus - indicator.
ReturnFileCode string // Required. 0 Default.Q Quote purposes taxes are computed and returned in the response message for generating quotes.
ClientTracking string // Field for client transaction tracking. This value will be provided in the response data. Value for this field is not required, but preferred. Max Len: 100
IndustryExemption string // Reserved for future use.
ResponseGroup string // Required. Determines how taxes are grouped for the response.
ResponseType string // Required. Determines the granularity of taxes and (optionally) the decimal precision for the tax calculations and amounts in the response.
ItemList []*STRequestItem // List of Item records
}
// Part of SureTax Request
type STRequestItem struct {
LineNumber string // Used to identify an item within the request. If no value is provided, requests are numbered sequentially. Max Len: 40
InvoiceNumber string // Used for tax aggregation by Invoice. Must be alphanumeric. Max Len: 40
CustomerNumber string // Used for tax aggregation by Customer. Must be alphanumeric. Max Len: 40
OrigNumber string // Required when using Tax Situs Rule 01 or 03. Format: NPANXXNNNN
TermNumber string // Required when using Tax Situs Rule 01. Format: NPANXXNNNN
BillToNumber string // Required when using Tax Situs Rule 01 or 02. Format: NPANXXNNNN
Zipcode string // Required when using Tax Situs Rule 04, 05, or 14.
Plus4 string // Zip code extension in format: 9999 (not applicable for Tax Situs Rule 14)
P2PZipcode string // Secondary zip code in format: 99999 (US or US territory) or X9X9X9 (Canadian)
P2PPlus4 string // Secondary zip code extension in format: 99999 (US or US territory) or X9X9X9 (Canadian)
TransDate string // Required. Date of transaction. Valid date formats include: MM/DD/YYYY, MM-DD-YYYY, YYYY-MM-DDTHH:MM:SS
Revenue float64 // Required. Format: $$$$$$$$$.CCCC. For Negative charges, the first position should have a minus -indicator.
Units int64 // Required. Units representing number of “lines” or unique charges contained within the revenue. This value is essentially a multiplier on unit-based fees (e.g. E911 fees). Format: 99999. Default should be 1 (one unit).
UnitType string // Required. 00 Default / Number of unique access lines.
Seconds int64 // Required. Duration of call in seconds. Format 99999. Default should be 1.
TaxIncludedCode string // Required. Values: 0 Default (No Tax Included) 1 Tax Included in Revenue
TaxSitusRule string // Required.
TransTypeCode string // Required. Transaction Type Indicator.
SalesTypeCode string // Required. Values: R Residential customer (default) B Business customer I Industrial customer L Lifeline customer
RegulatoryCode string // Required. Provider Type.
TaxExemptionCodeList []string // Required. Tax Exemption to be applied to this item only.
}
type STResponse struct {
Successful string // Response will be either Y' or N' : Y = Success / Success with Item error N = Failure
ResponseCode string // ResponseCode: 9999 Request was successful. 1101-1400 Range of values for a failed request (no processing occurred) 9001 Request was successful, but items within the request have errors. The specific items with errors are provided in the ItemMessages field.
HeaderMessage string // Response message: For ResponseCode 9999 “Success”For ResponseCode 9001 “Success with Item errors”. For ResponseCode 1100-1400 Unsuccessful / declined web request.
ItemMessages []*STItemMessage // This field contains a list of items that were not able to be processed due to bad or invalid data (see Response Code of “9001”).
ClientTracking string // Client transaction tracking provided in web request.
TotalTax string // Total Tax a total of all taxes included in the TaxList
TransId int // Transaction ID provided by SureTax
GroupList []*STGroup // contains one-to-many Groups
}
// Part of the SureTax Response
type STItemMessage struct {
LineNumber string // value corresponding to the line number in the web request
ResponseCode string // a value in the range 9100-9400
Message string // the error message corresponding to the ResponseCode
}
// Part of the SureTax Response
type STGroup struct {
StateCode string // Tax State
InvoiceNumber string // Invoice Number
CustomerNumber string // Customer number
TaxList []*STTaxItem // contains one-to-many Tax Items
}
// Part of the SureTax Response
type STTaxItem struct {
TaxTypeCode string // Tax Type Code
TaxTypeDesc string // Tax Type Description
TaxAmount string // Tax Amount
}
func SureTaxProcessCdr(cdr *CDR) error {
stCfg := config.CgrConfig().SureTaxCfg()
if stCfg == nil {
return errors.New("Invalid SureTax configuration")
}
if sureTaxClient == nil { // First time used, init the client here
sureTaxClient = &http.Client{
Transport: httpPstrTransport,
}
}
req, err := NewSureTaxRequest(cdr, stCfg)
if err != nil {
return err
}
jsnContent, err := json.Marshal(req)
if err != nil {
return err
}
resp, err := sureTaxClient.Post(stCfg.URL, "application/json", bytes.NewBuffer(jsnContent))
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode > 299 {
return fmt.Errorf("Unexpected status code received: %d", resp.StatusCode)
}
var respFull SureTaxResponse
if err := json.Unmarshal(respBody, &respFull); err != nil {
return err
}
var stResp STResponse
if err := json.Unmarshal([]byte(respFull.D), &stResp); err != nil {
return err
}
if stResp.ResponseCode != "9999" {
cdr.ExtraInfo = stResp.HeaderMessage
return nil // No error because the request was processed by SureTax, error will be in the ExtraInfo
}
// Write cost to CDR
totalTax, err := strconv.ParseFloat(stResp.TotalTax, 64)
if err != nil {
cdr.ExtraInfo = err.Error()
}
if !stCfg.IncludeLocalCost {
cdr.Cost = utils.Round(totalTax,
config.CgrConfig().GeneralCfg().RoundingDecimals,
utils.MetaRoundingMiddle)
} else {
cdr.Cost = utils.Round(cdr.Cost+totalTax,
config.CgrConfig().GeneralCfg().RoundingDecimals,
utils.MetaRoundingMiddle)
}
// Add response into extra fields to be available for later review
cdr.ExtraFields[utils.MetaSureTax] = respFull.D
return nil
}

View File

@@ -1,182 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>
*/
package engine
import (
"encoding/json"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
)
func TestNewSureTaxRequest(t *testing.T) {
cdr := &CDR{OrderID: 123, ToR: utils.MetaVoice,
OriginID: "dsafdsaf", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 12 * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, PreRated: true,
}
cfg := config.NewDefaultCGRConfig()
stCfg := cfg.SureTaxCfg()
stCfg.ClientNumber = "000000000"
stCfg.ValidationKey = "19491161-F004-4F44-BDB3-E976D6739A64"
stCfg.Timezone = time.UTC
eSTRequest := &STRequest{
ClientNumber: "000000000",
ValidationKey: "19491161-F004-4F44-BDB3-E976D6739A64",
DataYear: "2013",
DataMonth: "11",
TotalRevenue: 1.01,
ReturnFileCode: "0",
ResponseGroup: "03",
ResponseType: "D4",
ItemList: []*STRequestItem{
{
CustomerNumber: "1001",
OrigNumber: "1001",
TermNumber: "1002",
BillToNumber: "",
TransDate: "2013-11-07T08:42:26",
Revenue: 1.01,
Units: 1,
UnitType: "00",
Seconds: 12,
TaxIncludedCode: "0",
TaxSitusRule: "04",
TransTypeCode: "010101",
SalesTypeCode: "R",
RegulatoryCode: "03",
TaxExemptionCodeList: []string{},
},
},
}
jsnReq, _ := json.Marshal(eSTRequest)
eSureTaxRequest := &SureTaxRequest{Request: string(jsnReq)}
if stReq, err := NewSureTaxRequest(cdr, stCfg); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eSureTaxRequest, stReq) {
t.Errorf("Expecting:\n%s\nReceived:\n%s", string(eSureTaxRequest.Request), string(stReq.Request))
}
}
func TestSuretaxNewSureTaxRequestNilCfg(t *testing.T) {
cdr := &CDR{
OrderID: 123,
ToR: utils.MetaVoice,
OriginID: "testOriginID",
OriginHost: "192.168.1.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1001",
Subject: "1001",
Destination: "1002",
SetupTime: time.Date(2021, 1, 1, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2021, 1, 1, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 12 * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, PreRated: true,
}
var stCfg *config.SureTaxCfg
experr := "invalid SureTax config"
stReq, err := NewSureTaxRequest(cdr, stCfg)
if err == nil || err.Error() != experr {
t.Fatalf("\nExpected: %q, \nReceived: %q", experr, err)
}
if stReq != nil {
t.Errorf("\nExpected: <%+v>, \nReceived: <%+v>",
nil, stReq)
}
}
func TestSuretaxNewSureTaxRequestInvalidUnits(t *testing.T) {
cdr := &CDR{OrderID: 123, ToR: utils.MetaVoice,
OriginID: "testOriginID", OriginHost: "192.168.1.1",
Source: utils.UnitTest, RequestType: utils.MetaRated,
Tenant: "cgrates.org", Category: "call", Account: "1001",
Subject: "1001", Destination: "1002",
SetupTime: time.Date(2021, 1, 1, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2021, 1, 1, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 12 * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, PreRated: true,
}
cfg := config.NewDefaultCGRConfig()
stCfg := cfg.SureTaxCfg()
stCfg.Units = nil
stCfg.ClientNumber = "000000000"
stCfg.ValidationKey = "19491161-F004-4F44-BDB3-E976D6739A64"
stCfg.Timezone = time.UTC
experr := "strconv.ParseInt: parsing \"\": invalid syntax"
stReq, err := NewSureTaxRequest(cdr, stCfg)
if err == nil || err.Error() != experr {
t.Fatalf("\nExpected: %q, \nReceived: %q", experr, err)
}
if stReq != nil {
t.Errorf("\nExpected: <%+v>, \nReceived: <%+v>",
nil, stReq)
}
}
func TestSuretaxSureTaxProcessCdrPostErr(t *testing.T) {
cdr := &CDR{
OrderID: 123,
ToR: utils.MetaVoice,
OriginID: "testOriginID",
OriginHost: "192.168.1.1",
Source: utils.UnitTest,
RequestType: utils.MetaRated,
Tenant: "cgrates.org",
Category: "call",
Account: "1001",
Subject: "1001",
Destination: "1002",
SetupTime: time.Date(2021, 1, 1, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2021, 1, 1, 8, 42, 26, 0, time.UTC),
RunID: utils.MetaDefault,
Usage: 12 * time.Second,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, PreRated: true,
}
experr := `Post "": unsupported protocol scheme ""`
err := SureTaxProcessCdr(cdr)
if err == nil || err.Error() != experr {
t.Errorf("\nExpected: %q, \nReceived: %q", experr, err)
}
}

View File

@@ -1,673 +0,0 @@
//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 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 <http://www.gnu.org/licenses/>
*/
package ers
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var (
sqlCfgPath string
sqlCfg *config.CGRConfig
sqlTests = []func(t *testing.T){
testSQLInitConfig,
testSQLInitDBs,
testSQLInitCdrDb,
testSQLInitDB,
testSQLReader,
testSQLEmptyTable,
testSQLPoster,
testSQLAddData,
testSQLReader2,
testSQLStop,
}
sqlTests2 = []func(t *testing.T){
testSQLInitConfig2,
testSQLInitDBs2,
testSQLInitCdrDb2,
testSQLInitDB2,
testSQLReader3,
testSQLEmptyTable2,
testSQLPoster2,
testSQLAddData2,
testSQLReader4,
testSQLStop2,
}
cdr = &engine.CDR{
RunID: "RunID",
}
db *gorm.DB
dbConnString = "cgrates:CGRateS.org@tcp(127.0.0.1:3306)/%s?charset=utf8&loc=Local&parseTime=true&sql_mode='ALLOW_INVALID_DATES'"
)
func TestSQL(t *testing.T) {
// sqlCfgPath = path.Join(*dataDir, "conf", "samples", "ers_reload", "disabled")
for _, test := range sqlTests {
t.Run("TestSQL", test)
}
}
func testSQLInitConfig(t *testing.T) {
var err error
if sqlCfg, err = config.NewCGRConfigFromJSONStringWithDefaults(`{
"stor_db": {
"db_password": "CGRateS.org",
},
"ers": { // EventReaderService
"enabled": true, // starts the EventReader service: <true|false>
"sessions_conns":["*localhost"],
"readers": [
{
"id": "mysql", // identifier of the EventReader profile
"type": "*sql", // reader type <*fileCSV>
"run_delay": "1", // sleep interval in seconds between consecutive runs, -1 to use automation via inotify or 0 to disable running all together
"concurrent_requests": 1024, // maximum simultaneous requests/files to process, 0 for unlimited
"source_path": "*mysql://cgrates:CGRateS.org@127.0.0.1:3306", // read data from this path
"opts": {
"sqlDBName":"cgrates2",
"sqlDBNameProcessed":"cgrates2",
"sqlTableNameProcessed":"cdrs2",
},
"processed_path": "", // move processed data here
"tenant": "cgrates.org", // tenant used by import
"filters": [], // limit parsing based on the filters
"flags": [], // flags to influence the event processing
"fields":[ // import fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value
{"tag": "OriginID", "type": "*composed", "value": "~*req.OriginID", "path": "*cgreq.OriginID"},
],
},
],
},
}`); err != nil {
t.Fatal(err)
}
if err := sqlCfg.CheckConfigSanity(); err != nil {
t.Fatal(err)
}
}
func testSQLInitCdrDb(t *testing.T) {
if err := engine.InitStorDB(sqlCfg); err != nil {
t.Fatal(err)
}
}
type testModelSql struct {
ID int64
RunID string
OriginHost string
Source string
OriginID string
ToR string
RequestType string
Tenant string
Category string
Account string
Subject string
Destination string
SetupTime time.Time
AnswerTime time.Time
Usage int64
ExtraFields string
CostSource string
Cost float64
CostDetails string
ExtraInfo string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
func (_ *testModelSql) TableName() string {
return "cdrs2"
}
func testSQLInitDBs(t *testing.T) {
var err error
var db2 *gorm.DB
if db2, err = gorm.Open(mysql.Open(fmt.Sprintf(dbConnString, "cgrates")),
&gorm.Config{
AllowGlobalUpdate: true,
Logger: logger.Default.LogMode(logger.Silent),
}); err != nil {
t.Fatal(err)
}
if err = db2.Exec(`CREATE DATABASE IF NOT EXISTS cgrates2;`).Error; err != nil {
t.Fatal(err)
}
}
func testSQLInitDB(t *testing.T) {
var err error
if db, err = gorm.Open(mysql.Open(fmt.Sprintf(dbConnString, "cgrates2")),
&gorm.Config{
AllowGlobalUpdate: true,
Logger: logger.Default.LogMode(logger.Silent),
}); err != nil {
t.Fatal(err)
}
tx := db.Begin()
if !tx.Migrator().HasTable("cdrs") {
if err = tx.Migrator().CreateTable(new(engine.CDRsql)); err != nil {
tx.Rollback()
t.Fatal(err)
}
}
if !tx.Migrator().HasTable("cdrs2") {
if err = tx.Migrator().CreateTable(new(testModelSql)); err != nil {
tx.Rollback()
t.Fatal(err)
}
}
tx.Commit()
tx = db.Begin()
tx = tx.Table(utils.CDRsTBL)
cdrSql := cdr.AsCDRsql()
cdrSql.CreatedAt = time.Now()
saved := tx.Save(cdrSql)
if saved.Error != nil {
tx.Rollback()
t.Fatal(err)
}
tx.Commit()
time.Sleep(10 * time.Millisecond)
var result int64
db.Table(utils.CDRsTBL).Count(&result)
if result != 1 {
t.Fatal("Expected table to have only one result ", result)
}
}
func testSQLAddData(t *testing.T) {
tx := db.Begin()
tx = tx.Table(utils.CDRsTBL)
cdrSql := cdr.AsCDRsql()
cdrSql.CreatedAt = time.Now()
saved := tx.Save(cdrSql)
if saved.Error != nil {
tx.Rollback()
t.Fatal(saved.Error)
}
tx.Commit()
time.Sleep(10 * time.Millisecond)
}
func testSQLReader(t *testing.T) {
rdrEvents = make(chan *erEvent, 1)
rdrErr = make(chan error, 1)
rdrExit = make(chan struct{}, 1)
sqlER, err := NewEventReader(sqlCfg, 1, rdrEvents, make(chan *erEvent, 1), rdrErr, new(engine.FilterS), rdrExit)
if err != nil {
t.Fatal(err)
}
sqlER.Serve()
select {
case err = <-rdrErr:
t.Error(err)
case ev := <-rdrEvents:
if ev.rdrCfg.ID != "mysql" {
t.Errorf("Expected 'mysql' received `%s`", ev.rdrCfg.ID)
}
expected := &utils.CGREvent{
Tenant: "cgrates.org",
ID: ev.cgrEvent.ID,
Event: map[string]interface{}{},
APIOpts: map[string]interface{}{},
}
if !reflect.DeepEqual(ev.cgrEvent, expected) {
t.Errorf("Expected %s ,received %s", utils.ToJSON(expected), utils.ToJSON(ev.cgrEvent))
}
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func testSQLEmptyTable(t *testing.T) {
time.Sleep(10 * time.Millisecond)
var result int64
db.Table(utils.CDRsTBL).Count(&result)
if result != 0 {
t.Fatal("Expected empty table ", result)
}
}
func testSQLReader2(t *testing.T) {
select {
case err := <-rdrErr:
t.Error(err)
case ev := <-rdrEvents:
if ev.rdrCfg.ID != "mysql" {
t.Errorf("Expected 'mysql' received `%s`", ev.rdrCfg.ID)
}
expected := &utils.CGREvent{
Tenant: "cgrates.org",
ID: ev.cgrEvent.ID,
Event: map[string]interface{}{},
APIOpts: map[string]interface{}{},
}
if !reflect.DeepEqual(ev.cgrEvent, expected) {
t.Errorf("Expected %s ,received %s", utils.ToJSON(expected), utils.ToJSON(ev.cgrEvent))
}
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func testSQLPoster(t *testing.T) {
rows, err := db.Table("cdrs2").Select("*").Rows()
if err != nil {
t.Fatal(err)
}
colNames, err := rows.Columns()
if err != nil {
t.Fatal(err)
}
for rows.Next() {
columns := make([]interface{}, len(colNames))
columnPointers := make([]interface{}, len(colNames))
for i := range columns {
columnPointers[i] = &columns[i]
}
if err = rows.Scan(columnPointers...); err != nil {
t.Fatal(err)
}
msg := make(map[string]interface{})
for i, colName := range colNames {
msg[colName] = columns[i]
}
db.Table("cdrs2").Delete(msg)
}
}
func testSQLStop(t *testing.T) {
close(rdrExit)
if err := db.Migrator().DropTable("cdrs2"); err != nil {
t.Fatal(err)
}
if err := db.Migrator().DropTable("cdrs"); err != nil {
t.Fatal(err)
}
if err := db.Exec(`DROP DATABASE cgrates2;`).Error; err != nil {
t.Fatal(err)
}
if db2, err := db.DB(); err != nil {
t.Fatal(err)
} else if err = db2.Close(); err != nil {
t.Fatal(err)
}
}
func TestSQLReaderServeBadTypeErr(t *testing.T) {
rdr := &SQLEventReader{
connType: "badType",
}
expected := "db type <badType> not supported"
err := rdr.Serve()
if err == nil || err.Error() != expected {
t.Errorf("\nExpected: <%+v>, \nreceived: <%+v>", expected, err)
}
}
func TestSQL2(t *testing.T) {
// sqlCfgPath = path.Join(*dataDir, "conf", "samples", "ers_reload", "disabled")
for _, test := range sqlTests2 {
t.Run("TestSQL", test)
}
}
func testSQLInitConfig2(t *testing.T) {
var err error
if sqlCfg, err = config.NewCGRConfigFromJSONStringWithDefaults(`{
"stor_db": {
"db_password": "CGRateS.org",
},
"ers": { // EventReaderService
"enabled": true, // starts the EventReader service: <true|false>
"readers": [
{
"id": "mysql", // identifier of the EventReader profile
"type": "*sql", // reader type <*fileCSV>
"run_delay": "1", // sleep interval in seconds between consecutive runs, -1 to use automation via inotify or 0 to disable running all together
"concurrent_requests": 1024, // maximum simultaneous requests/files to process, 0 for unlimited
"source_path": "*mysql://cgrates:CGRateS.org@127.0.0.1:3306", // read data from this path
"opts": {
"sqlDBName":"cgrates2",
"sqlDBNameProcessed":"cgrates2",
"sqlTableNameProcessed":"cdrs2",
},
"processed_path": "", // move processed data here
"tenant": "cgrates.org", // tenant used by import
"filters": [], // limit parsing based on the filters
"flags": [], // flags to influence the event processing
"fields":[ // import fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value
{"tag": "OriginID", "type": "*composed", "value": "~*req.OriginID", "path": "*cgreq.OriginID"},
],
},
],
},
}`); err != nil {
t.Fatal(err)
}
}
func testSQLInitCdrDb2(t *testing.T) {
if err := engine.InitStorDB(sqlCfg); err != nil {
t.Fatal(err)
}
}
func testSQLInitDBs2(t *testing.T) {
var err error
var db2 *gorm.DB
if db2, err = gorm.Open(mysql.Open(fmt.Sprintf(dbConnString, "cgrates")),
&gorm.Config{
AllowGlobalUpdate: true,
Logger: logger.Default.LogMode(logger.Silent),
}); err != nil {
t.Fatal(err)
}
if err = db2.Exec(`CREATE DATABASE IF NOT EXISTS cgrates2;`).Error; err != nil {
t.Fatal(err)
}
}
func testSQLInitDB2(t *testing.T) {
var err error
if db, err = gorm.Open(mysql.Open(fmt.Sprintf(dbConnString, "cgrates2")),
&gorm.Config{
AllowGlobalUpdate: true,
Logger: logger.Default.LogMode(logger.Silent),
}); err != nil {
t.Fatal(err)
}
tx := db.Begin()
if !tx.Migrator().HasTable("cdrs") {
if err = tx.Migrator().CreateTable(new(engine.CDRsql)); err != nil {
tx.Rollback()
t.Fatal(err)
}
}
if !tx.Migrator().HasTable("cdrs2") {
if err = tx.Migrator().CreateTable(new(testModelSql)); err != nil {
tx.Rollback()
t.Fatal(err)
}
}
tx.Commit()
tx = db.Begin()
tx = tx.Table(utils.CDRsTBL)
cdrSql := cdr.AsCDRsql()
cdrSql.CreatedAt = time.Now()
saved := tx.Save(cdrSql)
if saved.Error != nil {
tx.Rollback()
t.Fatal(err)
}
tx.Commit()
time.Sleep(10 * time.Millisecond)
var result int64
db.Table(utils.CDRsTBL).Count(&result)
if result != 1 {
t.Fatal("Expected table to have only one result ", result)
}
}
func testSQLAddData2(t *testing.T) {
tx := db.Begin()
tx = tx.Table(utils.CDRsTBL)
cdrSql := cdr.AsCDRsql()
cdrSql.CreatedAt = time.Now()
saved := tx.Save(cdrSql)
if saved.Error != nil {
tx.Rollback()
t.Fatal(saved.Error)
}
tx.Commit()
time.Sleep(10 * time.Millisecond)
}
func testSQLReader3(t *testing.T) {
rdrEvents = make(chan *erEvent, 1)
rdrErr = make(chan error, 1)
rdrExit = make(chan struct{}, 1)
sqlER, err := NewEventReader(sqlCfg, 1, rdrEvents, make(chan *erEvent, 1), rdrErr, new(engine.FilterS), rdrExit)
if err != nil {
t.Fatal(err)
}
sqlER.Serve()
select {
case err = <-rdrErr:
t.Error(err)
case ev := <-rdrEvents:
if ev.rdrCfg.ID != "mysql" {
t.Errorf("Expected 'mysql' received `%s`", ev.rdrCfg.ID)
}
expected := &utils.CGREvent{
Tenant: "cgrates.org",
ID: ev.cgrEvent.ID,
Event: map[string]interface{}{},
APIOpts: map[string]interface{}{},
}
if !reflect.DeepEqual(ev.cgrEvent, expected) {
t.Errorf("Expected %s ,received %s", utils.ToJSON(expected), utils.ToJSON(ev.cgrEvent))
}
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func testSQLEmptyTable2(t *testing.T) {
time.Sleep(10 * time.Millisecond)
var result int64
db.Table(utils.CDRsTBL).Count(&result)
if result != 0 {
t.Fatal("Expected empty table ", result)
}
}
func testSQLReader4(t *testing.T) {
select {
case err := <-rdrErr:
t.Error(err)
case ev := <-rdrEvents:
if ev.rdrCfg.ID != "mysql" {
t.Errorf("Expected 'mysql' received `%s`", ev.rdrCfg.ID)
}
expected := &utils.CGREvent{
Tenant: "cgrates.org",
ID: ev.cgrEvent.ID,
Event: map[string]interface{}{},
APIOpts: map[string]interface{}{},
}
if !reflect.DeepEqual(ev.cgrEvent, expected) {
t.Errorf("Expected %s ,received %s", utils.ToJSON(expected), utils.ToJSON(ev.cgrEvent))
}
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func testSQLPoster2(t *testing.T) {
rows, err := db.Table("cdrs2").Select("*").Rows()
if err != nil {
t.Fatal(err)
}
colNames, err := rows.Columns()
if err != nil {
t.Fatal(err)
}
for rows.Next() {
columns := make([]interface{}, len(colNames))
columnPointers := make([]interface{}, len(colNames))
for i := range columns {
columnPointers[i] = &columns[i]
}
if err = rows.Scan(columnPointers...); err != nil {
t.Fatal(err)
}
msg := make(map[string]interface{})
for i, colName := range colNames {
msg[colName] = columns[i]
}
db.Table("cdrs2").Delete(msg)
}
}
func testSQLStop2(t *testing.T) {
close(rdrExit)
if err := db.Migrator().DropTable("cdrs2"); err != nil {
t.Fatal(err)
}
if err := db.Migrator().DropTable("cdrs"); err != nil {
t.Fatal(err)
}
if err := db.Exec(`DROP DATABASE cgrates2;`).Error; err != nil {
t.Fatal(err)
}
if db2, err := db.DB(); err != nil {
t.Fatal(err)
} else if err = db2.Close(); err != nil {
t.Fatal(err)
}
}
func TestSQLProcessMessageError(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
testSQLEventReader := &SQLEventReader{
cgrCfg: cfg,
cfgIdx: 0,
fltrS: &engine.FilterS{},
connString: "",
connType: "",
tableName: "testName",
expConnString: "",
expConnType: utils.Postgres,
expTableName: "",
rdrEvents: nil,
rdrExit: nil,
rdrErr: nil,
cap: nil,
}
msgTest := map[string]interface{}{}
err := testSQLEventReader.processMessage(msgTest)
expected := "NOT_FOUND:ToR"
if err == nil || err.Error() != expected {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", expected, err)
}
}
func TestSQLSetURLError(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
testSQLEventReader := &SQLEventReader{
cgrCfg: cfg,
cfgIdx: 0,
fltrS: &engine.FilterS{},
connString: "",
connType: "",
tableName: "testName",
expConnString: "",
expConnType: utils.Postgres,
expTableName: "",
rdrEvents: nil,
rdrExit: nil,
rdrErr: nil,
cap: nil,
}
err := testSQLEventReader.setURL("http://user^:passwo^rd@foo.com/", "", nil)
expected := `parse "http://user^:passwo^rd@foo.com/": net/url: invalid userinfo`
if err == nil || err.Error() != expected {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", expected, err)
}
}
func TestSQLSetURLError2(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
testSQLEventReader := &SQLEventReader{
cgrCfg: cfg,
cfgIdx: 0,
fltrS: &engine.FilterS{},
connString: "",
connType: "",
tableName: "testName",
expConnString: "",
expConnType: utils.Postgres,
expTableName: "",
rdrEvents: nil,
rdrExit: nil,
rdrErr: nil,
cap: nil,
}
err := testSQLEventReader.setURL("*mysql://cgrates:CGRateS.org@127.0.0.1:3306", "http://user^:passwo^rd@foo.com/", nil)
expected := `parse "http://user^:passwo^rd@foo.com/": net/url: invalid userinfo`
if err == nil || err.Error() != expected {
t.Errorf("\nExpected <%+v>, \nReceived <%+v>", expected, err)
}
}
func TestErsSqlPostCDRS(t *testing.T) {
tmp := logger.Default
logger.Default = logger.Default.LogMode(logger.Silent)
cfg := config.NewDefaultCGRConfig()
fltr := &engine.FilterS{}
reader := cfg.ERsCfg().Readers[0].Clone()
reader.Type = utils.MetaSQL
reader.ID = "file_reader"
reader.ConcurrentReqs = -1
reader.Opts = &config.EventReaderOpts{
SQLDBName: utils.StringPointer("cgrates2"),
}
reader.SourcePath = "*mysql://cgrates:CGRateS.org@127.0.0.1:3306"
reader.ProcessedPath = ""
cfg.ERsCfg().Readers = append(cfg.ERsCfg().Readers, reader)
if len(cfg.ERsCfg().Readers) != 2 {
t.Errorf("Expecting: <2>, received: <%+v>", len(cfg.ERsCfg().Readers))
}
sqlEvReader, err := NewSQLEventReader(cfg, 1, nil, nil, nil, fltr, nil)
if err != nil {
t.Errorf("Expecting: <nil>, received: <%+v>", err)
}
sqlEvReader.(*SQLEventReader).expConnType = utils.MySQL
result := sqlEvReader.(*SQLEventReader).postCDR([]interface{}{})
expected := "Error 1045: Access denied for user ''@'localhost' (using password: NO)"
if result == nil {
t.Errorf("\nExpected: <%+v>, \nreceived: <%+v>", expected, result)
}
logger.Default = tmp
}