/* 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/birpc" "github.com/cgrates/birpc/context" "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.MetaDefault, 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, utils.CSV_SEP, cfg.GeneralCfg().HttpSkipTlsVerify, nil, nil) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } if err = cdre.composeHeader(); err != nil { t.Error(err) } if err = cdre.composeTrailer(); 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.MetaDefault, 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) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } if err = cdre.composeHeader(); err != nil { t.Error(err) } if err = cdre.composeTrailer(); 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.Fields = []*config.FCTemplate{ { Tag: "*exp.ToR", Path: "*exp.ToR", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"ToR", true, utils.INFIELD_SEP)}, { Tag: "*exp.OriginID", Path: "*exp.OriginID", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"OriginID", true, utils.INFIELD_SEP)}, { Tag: "*exp.RequestType", Path: "*exp.RequestType", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"RequestType", true, utils.INFIELD_SEP)}, { Tag: "*exp.Tenant", Path: "*exp.Tenant", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Tenant", true, utils.INFIELD_SEP)}, { Tag: "*exp.Category", Path: "*exp.Category", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Category", true, utils.INFIELD_SEP)}, { Tag: "*exp.Account", Path: "*exp.Account", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Account", true, utils.INFIELD_SEP)}, { Tag: "*exp.Destination", Path: "*exp.Destination", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Destination", true, utils.INFIELD_SEP)}, { Tag: "*exp.AnswerTime", Path: "*exp.AnswerTime", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"AnswerTime", true, utils.INFIELD_SEP), Layout: "2006-01-02T15:04:05Z07:00"}, { Tag: "*exp.UsageVoice", Path: "*exp.UsageVoice", Type: "*composed", Filters: []string{"*string:~*req.ToR:*voice"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_seconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.UsageData", Path: "*exp.UsageData", Type: "*composed", Filters: []string{"*string:~*req.ToR:*data"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.UsageSMS", Path: "*exp.UsageSMS", Type: "*composed", Filters: []string{"*string:~*req.ToR:*sms"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.Cost", Path: "*exp.Cost", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Cost", true, utils.INFIELD_SEP), RoundingDecimals: utils.IntPointer(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.MetaDefault, 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.MetaDefault, 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.MetaDefault, 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, &FilterS{cfg: cfg}) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } if err = cdre.composeHeader(); err != nil { t.Error(err) } if err = cdre.composeTrailer(); 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:~*req.Tenant:cgrates.org"} cdreCfg.Fields = []*config.FCTemplate{ { Tag: "*exp.ToR", Path: "*exp.ToR", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"ToR", true, utils.INFIELD_SEP)}, { Tag: "*exp.OriginID", Path: "*exp.OriginID", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"OriginID", true, utils.INFIELD_SEP)}, { Tag: "*exp.RequestType", Path: "*exp.RequestType", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"RequestType", true, utils.INFIELD_SEP)}, { Tag: "*exp.Tenant", Path: "*exp.Tenant", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Tenant", true, utils.INFIELD_SEP)}, { Tag: "*exp.Category", Path: "*exp.Category", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Category", true, utils.INFIELD_SEP)}, { Tag: "*exp.Account", Path: "*exp.Account", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Account", true, utils.INFIELD_SEP)}, { Tag: "*exp.Destination", Path: "*exp.Destination", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Destination", true, utils.INFIELD_SEP)}, { Tag: "*exp.AnswerTime", Path: "*exp.AnswerTime", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"AnswerTime", true, utils.INFIELD_SEP), Layout: "2006-01-02T15:04:05Z07:00"}, { Tag: "*exp.UsageVoice", Path: "*exp.UsageVoice", Type: "*composed", Filters: []string{"*string:~*req.ToR:*voice"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_seconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.UsageData", Path: "*exp.UsageData", Type: "*composed", Filters: []string{"*string:~*req.ToR:*data"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.UsageSMS", Path: "*exp.UsageSMS", Type: "*composed", Filters: []string{"*string:~*req.ToR:*sms"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.Cost", Path: "*exp.Cost", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Cost", true, utils.INFIELD_SEP), RoundingDecimals: utils.IntPointer(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.MetaDefault, 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.MetaDefault, 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.MetaDefault, 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, &FilterS{cfg: cfg}) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } if err = cdre.composeHeader(); err != nil { t.Error(err) } if err = cdre.composeTrailer(); 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:~*req.Tenant:cgrates.org", "*lte:~*req.Cost:0.5"} cdreCfg.Fields = []*config.FCTemplate{ { Tag: "*exp.ToR", Path: "*exp.ToR", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"ToR", true, utils.INFIELD_SEP)}, { Tag: "*exp.OriginID", Path: "*exp.OriginID", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"OriginID", true, utils.INFIELD_SEP)}, { Tag: "*exp.RequestType", Path: "*exp.RequestType", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"RequestType", true, utils.INFIELD_SEP)}, { Tag: "*exp.Tenant", Path: "*exp.Tenant", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Tenant", true, utils.INFIELD_SEP)}, { Tag: "*exp.Category", Path: "*exp.Category", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Category", true, utils.INFIELD_SEP)}, { Tag: "*exp.Account", Path: "*exp.Account", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Account", true, utils.INFIELD_SEP)}, { Tag: "*exp.Destination", Path: "*exp.Destination", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Destination", true, utils.INFIELD_SEP)}, { Tag: "*exp.AnswerTime", Path: "*exp.AnswerTime", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"AnswerTime", true, utils.INFIELD_SEP), Layout: "2006-01-02T15:04:05Z07:00"}, { Tag: "*exp.UsageVoice", Path: "*exp.UsageVoice", Type: "*composed", Filters: []string{"*string:~*req.ToR:*voice"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_seconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.UsageData", Path: "*exp.UsageData", Type: "*composed", Filters: []string{"*string:~*req.ToR:*data"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.UsageSMS", Path: "*exp.UsageSMS", Type: "*composed", Filters: []string{"*string:~*req.ToR:*sms"}, Value: config.NewRSRParsersMustCompile("~*req.Usage{*duration_nanoseconds}", true, utils.INFIELD_SEP)}, { Tag: "*exp.Cost", Path: "*exp.Cost", Type: "*composed", Value: config.NewRSRParsersMustCompile(utils.DynamicDataPrefix+utils.MetaReq+utils.NestingSep+"Cost", true, utils.INFIELD_SEP), RoundingDecimals: utils.IntPointer(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.MetaDefault, 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.MetaDefault, 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.MetaDefault, 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, &FilterS{cfg: cfg}) if err != nil { t.Error("Unexpected error received: ", err) } if err = cdre.processCDRs(); err != nil { t.Error(err) } if err = cdre.composeHeader(); err != nil { t.Error(err) } if err = cdre.composeTrailer(); 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()) } } func TestCsvCdrWriterWithAttributeContext(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() defer func() { SetConnManager(connMgr) }() 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.MetaDefault, Cost: 1.01, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, } clientConn := make(chan birpc.ClientConnector, 1) cdrEv := storedCdr1.AsCGREvent() cdrEv.Event[utils.Account] = "1002" cdrEv.Event[utils.Subject] = "1002" cdrEv.Event[utils.Destination] = "1004" clientConn <- &ccMock{ calls: map[string]func(ctx *context.Context, args any, reply any) error{ utils.AttributeSv1ProcessEvent: func(ctx *context.Context, args, reply any) error { rpl := AttrSProcessEventReply{ CGREvent: cdrEv, AlteredFields: []string{utils.Account, utils.Subject, utils.Destination}, } *reply.(*AttrSProcessEventReply) = rpl return nil }, }, } connMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{ "con1": clientConn, }, ) cfg.CdreProfiles[utils.MetaDefault].AttributeSContext = "context" cdre, err := NewCDRExporter([]*CDR{storedCdr1}, cfg.CdreProfiles[utils.MetaDefault], utils.MetaFileCSV, "", "", "firstexport", true, 1, utils.CSV_SEP, cfg.GeneralCfg().HttpSkipTlsVerify, []string{"con1"}, nil) if err != nil { t.Error("Unexpected error received: ", err) } SetConnManager(connMgr) if err = cdre.processCDRs(); err != nil { t.Error(err) } }