diff --git a/console/cdrs.go b/console/cdrs.go deleted file mode 100644 index 09dc56709..000000000 --- a/console/cdrs.go +++ /dev/null @@ -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 -*/ - -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 -} diff --git a/dispatchers/cdrs_it_test.go b/dispatchers/cdrs_it_test.go deleted file mode 100644 index 71e93d47b..000000000 --- a/dispatchers/cdrs_it_test.go +++ /dev/null @@ -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 -*/ - -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) - } -} diff --git a/engine/cdr.go b/engine/cdr.go deleted file mode 100644 index 879d5b6d3..000000000 --- a/engine/cdr.go +++ /dev/null @@ -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 -*/ - -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 . - 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{} -} diff --git a/engine/cdr_test.go b/engine/cdr_test.go deleted file mode 100644 index 3612c8c46..000000000 --- a/engine/cdr_test.go +++ /dev/null @@ -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 -*/ -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)) - } -} diff --git a/engine/fscdr.go b/engine/fscdr.go deleted file mode 100644 index 43a03d5ac..000000000 --- a/engine/fscdr.go +++ /dev/null @@ -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 -*/ - -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 -} diff --git a/engine/fscdr_test.go b/engine/fscdr_test.go deleted file mode 100644 index d13aa0629..000000000 --- a/engine/fscdr_test.go +++ /dev/null @@ -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 -*/ -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\" ;tag=f25afe20", - "sip_full_to": ";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)) - } -} diff --git a/engine/mapevent.go b/engine/mapevent.go index b9ab97f80..97629fad2 100644 --- a/engine/mapevent.go +++ b/engine/mapevent.go @@ -19,10 +19,8 @@ along with this program. If not, see 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 diff --git a/engine/suretax.go b/engine/suretax.go deleted file mode 100644 index f86a95183..000000000 --- a/engine/suretax.go +++ /dev/null @@ -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 -*/ - -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 // Client’s 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 -} diff --git a/engine/suretax_test.go b/engine/suretax_test.go deleted file mode 100644 index 1b12499e6..000000000 --- a/engine/suretax_test.go +++ /dev/null @@ -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 -*/ -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) - } -} diff --git a/ers/sql_it_test.go b/ers/sql_it_test.go deleted file mode 100644 index 4a0c6832f..000000000 --- a/ers/sql_it_test.go +++ /dev/null @@ -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 -*/ - -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: - "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 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: - "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: , 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 -}