/* 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/csv" "strings" "testing" "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) func TestCsvCdrWriter(t *testing.T) { writer := &bytes.Buffer{} cfg, _ := config.NewDefaultCGRConfig() storedCdr1 := &CDR{ CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), ToR: utils.VOICE, OriginID: "dsafdsaf", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, Cost: 1.01, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdre, err := NewCDRExporter([]*CDR{storedCdr1}, cfg.CdreProfiles[utils.MetaDefault], utils.MetaFileCSV, "", "", "firstexport", true, 1, ',', cfg.GeneralCfg().HttpSkipTlsVerify, nil, nil, nil) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } csvWriter := csv.NewWriter(writer) if err := cdre.writeCsv(csvWriter); err != nil { t.Error("Unexpected error: ", err) } expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6,*default,*voice,dsafdsaf,*rated,cgrates.org,call,1001,1001,1002,2013-11-07T08:42:25Z,2013-11-07T08:42:26Z,10s,1.0100` result := strings.TrimSpace(writer.String()) if result != expected { t.Errorf("Expected: \n%s \n received: \n%s.", expected, result) } if cdre.TotalCost() != 1.01 { t.Error("Unexpected TotalCost: ", cdre.TotalCost()) } } func TestAlternativeFieldSeparator(t *testing.T) { writer := &bytes.Buffer{} cfg, _ := config.NewDefaultCGRConfig() storedCdr1 := &CDR{ CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), ToR: utils.VOICE, OriginID: "dsafdsaf", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, Cost: 1.01, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdre, err := NewCDRExporter([]*CDR{storedCdr1}, cfg.CdreProfiles[utils.MetaDefault], utils.MetaFileCSV, "", "", "firstexport", true, 1, '|', cfg.GeneralCfg().HttpSkipTlsVerify, nil, nil, nil) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } csvWriter := csv.NewWriter(writer) if err := cdre.writeCsv(csvWriter); err != nil { t.Error("Unexpected error: ", err) } expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6|*default|*voice|dsafdsaf|*rated|cgrates.org|call|1001|1001|1002|2013-11-07T08:42:25Z|2013-11-07T08:42:26Z|10s|1.0100` result := strings.TrimSpace(writer.String()) if result != expected { t.Errorf("Expected: \n%s received: \n%s.", expected, result) } if cdre.TotalCost() != 1.01 { t.Error("Unexpected TotalCost: ", cdre.TotalCost()) } } func TestExportVoiceWithConvert(t *testing.T) { writer := &bytes.Buffer{} cfg, _ := config.NewDefaultCGRConfig() cdreCfg := cfg.CdreProfiles[utils.MetaDefault] cdreCfg.ContentFields = []*config.FCTemplate{ {Tag: "ToR", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"ToR", true, utils.INFIELD_SEP)}, {Tag: "OriginID", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"OriginID", true, utils.INFIELD_SEP)}, {Tag: "RequestType", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"RequestType", true, utils.INFIELD_SEP)}, {Tag: "Tenant", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Tenant", true, utils.INFIELD_SEP)}, {Tag: "Category", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Category", true, utils.INFIELD_SEP)}, {Tag: "Account", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Account", true, utils.INFIELD_SEP)}, {Tag: "Destination", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Destination", true, utils.INFIELD_SEP)}, {Tag: "AnswerTime", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"AnswerTime", true, utils.INFIELD_SEP), Layout: "2006-01-02T15:04:05Z07:00"}, {Tag: "UsageVoice", Type: "*composed", Filters: []string{"*string:~ToR:*voice"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_seconds}", true, utils.INFIELD_SEP)}, {Tag: "UsageData", Type: "*composed", Filters: []string{"*string:~ToR:*data"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, {Tag: "UsageSMS", Type: "*composed", Filters: []string{"*string:~ToR:*sms"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, {Tag: "Cost", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Cost", true, utils.INFIELD_SEP), RoundingDecimals: 5}, } cdrVoice := &CDR{ CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), ToR: utils.VOICE, OriginID: "dsafdsaf", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, Cost: 1.01, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdrData := &CDR{ CGRID: utils.Sha1("abcdef", time.Unix(1383813745, 0).UTC().String()), ToR: utils.DATA, OriginID: "abcdef", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Nanosecond, RunID: utils.DEFAULT_RUNID, Cost: 0.012, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdrSMS := &CDR{ CGRID: utils.Sha1("sdfwer", time.Unix(1383813745, 0).UTC().String()), ToR: utils.SMS, OriginID: "sdfwer", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(1), RunID: utils.DEFAULT_RUNID, Cost: 0.15, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdre, err := NewCDRExporter([]*CDR{cdrVoice, cdrData, cdrSMS}, cdreCfg, utils.MetaFileCSV, "", "", "firstexport", true, 1, '|', true, nil, nil, &FilterS{cfg: cfg}) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } csvWriter := csv.NewWriter(writer) if err := cdre.writeCsv(csvWriter); err != nil { t.Error("Unexpected error: ", err) } expected := `*sms|sdfwer|*rated|cgrates.org|call|1001|1002|2013-11-07T08:42:26Z|1|0.15000 *voice|dsafdsaf|*rated|cgrates.org|call|1001|1002|2013-11-07T08:42:26Z|10|1.01000 *data|abcdef|*rated|cgrates.org|call|1001|1002|2013-11-07T08:42:26Z|10|0.01200` result := strings.TrimSpace(writer.String()) if len(result) != len(expected) { // export is async, cannot check order t.Errorf("expected: \n%s received: \n%s.", expected, result) } if cdre.TotalCost() != 1.172 { t.Error("unexpected TotalCost: ", cdre.TotalCost()) } } func TestExportWithFilter(t *testing.T) { writer := &bytes.Buffer{} cfg, _ := config.NewDefaultCGRConfig() cdreCfg := cfg.CdreProfiles[utils.MetaDefault] cdreCfg.Filters = []string{"*string:~Tenant:cgrates.org"} cdreCfg.ContentFields = []*config.FCTemplate{ {Tag: "ToR", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"ToR", true, utils.INFIELD_SEP)}, {Tag: "OriginID", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"OriginID", true, utils.INFIELD_SEP)}, {Tag: "RequestType", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"RequestType", true, utils.INFIELD_SEP)}, {Tag: "Tenant", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Tenant", true, utils.INFIELD_SEP)}, {Tag: "Category", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Category", true, utils.INFIELD_SEP)}, {Tag: "Account", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Account", true, utils.INFIELD_SEP)}, {Tag: "Destination", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Destination", true, utils.INFIELD_SEP)}, {Tag: "AnswerTime", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"AnswerTime", true, utils.INFIELD_SEP), Layout: "2006-01-02T15:04:05Z07:00"}, {Tag: "UsageVoice", Type: "*composed", Filters: []string{"*string:~ToR:*voice"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_seconds}", true, utils.INFIELD_SEP)}, {Tag: "UsageData", Type: "*composed", Filters: []string{"*string:~ToR:*data"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, {Tag: "UsageSMS", Type: "*composed", Filters: []string{"*string:~ToR:*sms"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, {Tag: "Cost", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Cost", true, utils.INFIELD_SEP), RoundingDecimals: 5}, } cdrVoice := &CDR{ CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), ToR: utils.VOICE, OriginID: "dsafdsaf", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, Cost: 1.01, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdrData := &CDR{ CGRID: utils.Sha1("abcdef", time.Unix(1383813745, 0).UTC().String()), ToR: utils.DATA, OriginID: "abcdef", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "AnotherTenant", Category: "call", //for data CDR use different Tenant Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Nanosecond, RunID: utils.DEFAULT_RUNID, Cost: 0.012, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdrSMS := &CDR{ CGRID: utils.Sha1("sdfwer", time.Unix(1383813745, 0).UTC().String()), ToR: utils.SMS, OriginID: "sdfwer", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(1), RunID: utils.DEFAULT_RUNID, Cost: 0.15, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdre, err := NewCDRExporter([]*CDR{cdrVoice, cdrData, cdrSMS}, cdreCfg, utils.MetaFileCSV, "", "", "firstexport", true, 1, '|', true, nil, nil, &FilterS{cfg: cfg}) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } csvWriter := csv.NewWriter(writer) if err := cdre.writeCsv(csvWriter); err != nil { t.Error("Unexpected error: ", err) } expected := `*sms|sdfwer|*rated|cgrates.org|call|1001|1002|2013-11-07T08:42:26Z|1|0.15000 *voice|dsafdsaf|*rated|cgrates.org|call|1001|1002|2013-11-07T08:42:26Z|10|1.01000` result := strings.TrimSpace(writer.String()) if len(result) != len(expected) { // export is async, cannot check order t.Errorf("expected: \n%s received: \n%s.", expected, result) } if cdre.TotalCost() != 1.16 { t.Error("unexpected TotalCost: ", cdre.TotalCost()) } } func TestExportWithFilter2(t *testing.T) { writer := &bytes.Buffer{} cfg, _ := config.NewDefaultCGRConfig() cdreCfg := cfg.CdreProfiles[utils.MetaDefault] cdreCfg.Filters = []string{"*string:~Tenant:cgrates.org", "*lte:~Cost:0.5"} cdreCfg.ContentFields = []*config.FCTemplate{ {Tag: "ToR", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"ToR", true, utils.INFIELD_SEP)}, {Tag: "OriginID", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"OriginID", true, utils.INFIELD_SEP)}, {Tag: "RequestType", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"RequestType", true, utils.INFIELD_SEP)}, {Tag: "Tenant", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Tenant", true, utils.INFIELD_SEP)}, {Tag: "Category", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Category", true, utils.INFIELD_SEP)}, {Tag: "Account", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Account", true, utils.INFIELD_SEP)}, {Tag: "Destination", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Destination", true, utils.INFIELD_SEP)}, {Tag: "AnswerTime", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"AnswerTime", true, utils.INFIELD_SEP), Layout: "2006-01-02T15:04:05Z07:00"}, {Tag: "UsageVoice", Type: "*composed", Filters: []string{"*string:~ToR:*voice"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_seconds}", true, utils.INFIELD_SEP)}, {Tag: "UsageData", Type: "*composed", Filters: []string{"*string:~ToR:*data"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, {Tag: "UsageSMS", Type: "*composed", Filters: []string{"*string:~ToR:*sms"}, Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, {Tag: "Cost", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+"Cost", true, utils.INFIELD_SEP), RoundingDecimals: 5}, } cdrVoice := &CDR{ CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), ToR: utils.VOICE, OriginID: "dsafdsaf", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Second, RunID: utils.DEFAULT_RUNID, Cost: 1.01, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdrData := &CDR{ CGRID: utils.Sha1("abcdef", time.Unix(1383813745, 0).UTC().String()), ToR: utils.DATA, OriginID: "abcdef", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "AnotherTenant", Category: "call", //for data CDR use different Tenant Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(10) * time.Nanosecond, RunID: utils.DEFAULT_RUNID, Cost: 0.012, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdrSMS := &CDR{ CGRID: utils.Sha1("sdfwer", time.Unix(1383813745, 0).UTC().String()), ToR: utils.SMS, OriginID: "sdfwer", OriginHost: "192.168.1.1", RequestType: utils.META_RATED, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(), Usage: time.Duration(1), RunID: utils.DEFAULT_RUNID, Cost: 0.15, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } cdre, err := NewCDRExporter([]*CDR{cdrVoice, cdrData, cdrSMS}, cdreCfg, utils.MetaFileCSV, "", "", "firstexport", true, 1, '|', true, nil, nil, &FilterS{cfg: cfg}) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } csvWriter := csv.NewWriter(writer) if err := cdre.writeCsv(csvWriter); err != nil { t.Error("Unexpected error: ", err) } expected := `*sms|sdfwer|*rated|cgrates.org|call|1001|1002|2013-11-07T08:42:26Z|1|0.15000` result := strings.TrimSpace(writer.String()) if len(result) != len(expected) { // export is async, cannot check order t.Errorf("expected: \n%s received: \n%s.", expected, result) } if cdre.TotalCost() != 0.15 { t.Error("unexpected TotalCost: ", cdre.TotalCost()) } }