func TestPSQLRemStoredCdrs(t *testing.T) { if !*testLocal { return } cgrIdB1 := utils.Sha1("bbb1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()) if err := psqlDb.RemStoredCdrs([]string{cgrIdB1}); err != nil { t.Error(err.Error()) } if storedCdrs, _, err := psqlDb.GetStoredCdrs(new(utils.CdrsFilter)); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 7 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } tm, _ := utils.ParseTimeDetectLayout("2013-11-08T08:42:20Z", "") cgrIdA1 := utils.Sha1("aaa1", tm.String()) tm, _ = utils.ParseTimeDetectLayout("2013-11-08T08:42:22Z", "") cgrIdA2 := utils.Sha1("aaa2", tm.String()) tm, _ = utils.ParseTimeDetectLayout("2013-11-07T08:42:24Z", "") cgrIdA3 := utils.Sha1("aaa3", tm.String()) tm, _ = utils.ParseTimeDetectLayout("2013-11-07T08:42:21Z", "") cgrIdA4 := utils.Sha1("aaa4", tm.String()) tm, _ = utils.ParseTimeDetectLayout("2013-11-07T08:42:25Z", "") cgrIdA5 := utils.Sha1("aaa5", tm.String()) cgrIdB2 := utils.Sha1("bbb2", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()) cgrIdB3 := utils.Sha1("bbb3", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()) if err := psqlDb.RemStoredCdrs([]string{cgrIdA1, cgrIdA2, cgrIdA3, cgrIdA4, cgrIdA5, cgrIdB2, cgrIdB3}); err != nil { t.Error(err.Error()) } if storedCdrs, _, err := psqlDb.GetStoredCdrs(new(utils.CdrsFilter)); err != nil { t.Error(err.Error()) } else if len(storedCdrs) != 0 { t.Error("Unexpected number of StoredCdrs returned: ", storedCdrs) } }
func NewCDRFromExternalCDR(extCdr *ExternalCDR, timezone string) (*CDR, error) { var err error cdr := &CDR{CGRID: extCdr.CGRID, RunID: extCdr.RunID, OrderID: extCdr.OrderID, ToR: extCdr.ToR, OriginID: extCdr.OriginID, OriginHost: extCdr.OriginHost, Source: extCdr.Source, RequestType: extCdr.RequestType, Direction: extCdr.Direction, Tenant: extCdr.Tenant, Category: extCdr.Category, Account: extCdr.Account, Subject: extCdr.Subject, Destination: extCdr.Destination, Supplier: extCdr.Supplier, DisconnectCause: extCdr.DisconnectCause, CostSource: extCdr.CostSource, Cost: extCdr.Cost, Rated: extCdr.Rated} if cdr.SetupTime, err = utils.ParseTimeDetectLayout(extCdr.SetupTime, timezone); err != nil { return nil, err } if len(cdr.CGRID) == 0 { // Populate CGRID if not present cdr.CGRID = utils.Sha1(cdr.OriginID, cdr.SetupTime.UTC().String()) } if cdr.AnswerTime, err = utils.ParseTimeDetectLayout(extCdr.AnswerTime, timezone); err != nil { return nil, err } if cdr.Usage, err = utils.ParseDurationWithSecs(extCdr.Usage); err != nil { return nil, err } if cdr.PDD, err = utils.ParseDurationWithSecs(extCdr.PDD); err != nil { return nil, err } if len(extCdr.CostDetails) != 0 { if err = json.Unmarshal([]byte(extCdr.CostDetails), cdr.CostDetails); 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 }
// Populates the field with id from value; strings are appended to original one func (cdr *CDR) ParseFieldValue(fieldId, fieldVal, timezone string) error { var err error switch fieldId { case utils.ORDERID: if cdr.OrderID, err = strconv.ParseInt(fieldVal, 10, 64); err != nil { return err } case utils.TOR: cdr.ToR += fieldVal case utils.MEDI_RUNID: cdr.RunID += fieldVal case utils.ACCID: cdr.OriginID += fieldVal case utils.REQTYPE: cdr.RequestType += fieldVal case utils.DIRECTION: cdr.Direction += fieldVal case utils.TENANT: cdr.Tenant += fieldVal case utils.CATEGORY: cdr.Category += fieldVal case utils.ACCOUNT: cdr.Account += fieldVal case utils.SUBJECT: cdr.Subject += fieldVal case utils.DESTINATION: cdr.Destination += fieldVal case utils.RATED_FLD: cdr.Rated, _ = strconv.ParseBool(fieldVal) case utils.SETUP_TIME: if cdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal, timezone); err != nil { return fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.PDD: if cdr.PDD, err = utils.ParseDurationWithSecs(fieldVal); err != nil { return fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.ANSWER_TIME: if cdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal, timezone); err != nil { return fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.USAGE: if cdr.Usage, err = utils.ParseDurationWithSecs(fieldVal); err != nil { return fmt.Errorf("Cannot parse duration field with value: %s, err: %s", fieldVal, err.Error()) } case utils.SUPPLIER: cdr.Supplier += fieldVal case utils.DISCONNECT_CAUSE: cdr.DisconnectCause += fieldVal case utils.COST: if cdr.Cost, err = strconv.ParseFloat(fieldVal, 64); err != nil { return fmt.Errorf("Cannot parse cost field with value: %s, err: %s", fieldVal, err.Error()) } case utils.PartialField: cdr.Partial, _ = strconv.ParseBool(fieldVal) default: // Extra fields will not match predefined so they all show up here cdr.ExtraFields[fieldId] += fieldVal } return nil }
// Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog func (self *MediatorV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error { if self.Medi == nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "MediatorNotRunning") } var tStart, tEnd time.Time var err error if len(attrs.TimeStart) != 0 { if tStart, err = utils.ParseTimeDetectLayout(attrs.TimeStart); err != nil { return err } } if len(attrs.TimeEnd) != 0 { if tEnd, err = utils.ParseTimeDetectLayout(attrs.TimeEnd); err != nil { return err } } //RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string, //orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool) if err := self.Medi.RateCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions, attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects, attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } *reply = utils.OK return nil }
// handlerUsageDiff will calculate the usage as difference between timeEnd and timeStart // Expects the 2 arguments in template separated by | func handlerSubstractUsage(xmlElmnt tree.Res, argsTpl utils.RSRFields, cdrPath utils.HierarchyPath, timezone string) (time.Duration, error) { var argsStr string for _, rsrArg := range argsTpl { if rsrArg.Id == utils.HandlerArgSep { argsStr += rsrArg.Id continue } absolutePath := utils.ParseHierarchyPath(rsrArg.Id, "") relPath := utils.HierarchyPath(absolutePath[len(cdrPath)-1:]) // Need relative path to the xmlElmnt argStr, _ := elementText(xmlElmnt, relPath.AsString("/", true)) argsStr += argStr } handlerArgs := strings.Split(argsStr, utils.HandlerArgSep) if len(handlerArgs) != 2 { return time.Duration(0), errors.New("Unexpected number of arguments") } tEnd, err := utils.ParseTimeDetectLayout(handlerArgs[0], timezone) if err != nil { return time.Duration(0), err } tStart, err := utils.ParseTimeDetectLayout(handlerArgs[1], timezone) if err != nil { return time.Duration(0), err } return tEnd.Sub(tStart), nil }
func (cgrCdr CgrCdr) AsStoredCdr(timezone string) *CDR { storCdr := new(CDR) storCdr.CGRID = cgrCdr.getCGRID(timezone) storCdr.ToR = cgrCdr[utils.TOR] storCdr.OriginID = cgrCdr[utils.ACCID] storCdr.OriginHost = cgrCdr[utils.CDRHOST] storCdr.Source = cgrCdr[utils.CDRSOURCE] storCdr.RequestType = cgrCdr[utils.REQTYPE] storCdr.Direction = utils.OUT storCdr.Tenant = cgrCdr[utils.TENANT] storCdr.Category = cgrCdr[utils.CATEGORY] storCdr.Account = cgrCdr[utils.ACCOUNT] storCdr.Subject = cgrCdr[utils.SUBJECT] storCdr.Destination = cgrCdr[utils.DESTINATION] storCdr.SetupTime, _ = utils.ParseTimeDetectLayout(cgrCdr[utils.SETUP_TIME], timezone) // Not interested to process errors, should do them if necessary in a previous step storCdr.PDD, _ = utils.ParseDurationWithSecs(cgrCdr[utils.PDD]) storCdr.AnswerTime, _ = utils.ParseTimeDetectLayout(cgrCdr[utils.ANSWER_TIME], timezone) storCdr.Usage, _ = utils.ParseDurationWithSecs(cgrCdr[utils.USAGE]) storCdr.Supplier = cgrCdr[utils.SUPPLIER] storCdr.DisconnectCause = cgrCdr[utils.DISCONNECT_CAUSE] storCdr.ExtraFields = cgrCdr.getExtraFields() storCdr.Cost = -1 if costStr, hasIt := cgrCdr[utils.COST]; hasIt { storCdr.Cost, _ = strconv.ParseFloat(costStr, 64) } if ratedStr, hasIt := cgrCdr[utils.RATED]; hasIt { storCdr.Rated, _ = strconv.ParseBool(ratedStr) } return storCdr }
func NewStoredCdrFromExternalCdr(extCdr *ExternalCdr, timezone string) (*StoredCdr, error) { var err error storedCdr := &StoredCdr{CgrId: extCdr.CgrId, OrderId: extCdr.OrderId, TOR: extCdr.TOR, AccId: extCdr.AccId, CdrHost: extCdr.CdrHost, CdrSource: extCdr.CdrSource, ReqType: extCdr.ReqType, Direction: extCdr.Direction, Tenant: extCdr.Tenant, Category: extCdr.Category, Account: extCdr.Account, Subject: extCdr.Subject, Destination: extCdr.Destination, Supplier: extCdr.Supplier, DisconnectCause: extCdr.DisconnectCause, MediationRunId: extCdr.MediationRunId, RatedAccount: extCdr.RatedAccount, RatedSubject: extCdr.RatedSubject, Cost: extCdr.Cost, Rated: extCdr.Rated} if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(extCdr.SetupTime, timezone); err != nil { return nil, err } if len(storedCdr.CgrId) == 0 { // Populate CgrId if not present storedCdr.CgrId = utils.Sha1(storedCdr.AccId, storedCdr.SetupTime.UTC().String()) } if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(extCdr.AnswerTime, timezone); err != nil { return nil, err } if storedCdr.Usage, err = utils.ParseDurationWithSecs(extCdr.Usage); err != nil { return nil, err } if storedCdr.Pdd, err = utils.ParseDurationWithSecs(extCdr.Pdd); err != nil { return nil, err } if len(extCdr.CostDetails) != 0 { if err = json.Unmarshal([]byte(extCdr.CostDetails), storedCdr.CostDetails); err != nil { return nil, err } } if extCdr.ExtraFields != nil { storedCdr.ExtraFields = make(map[string]string) } for k, v := range extCdr.ExtraFields { storedCdr.ExtraFields[k] = v } return storedCdr, nil }
func (fsCdr FSCdr) AsStoredCdr(timezone string) *CDR { storCdr := new(CDR) storCdr.CGRID = fsCdr.getCGRID(timezone) storCdr.ToR = utils.VOICE storCdr.OriginID = fsCdr.vars[FS_UUID] storCdr.OriginHost = fsCdr.vars[FS_IP] storCdr.Source = FS_CDR_SOURCE storCdr.RequestType = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_REQTYPE], fsCdr.cgrCfg.DefaultReqType) storCdr.Direction = utils.OUT storCdr.Tenant = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_TENANT], fsCdr.cgrCfg.DefaultTenant) storCdr.Category = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_CATEGORY], fsCdr.cgrCfg.DefaultCategory) storCdr.Account = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_ACCOUNT], fsCdr.vars[FS_USERNAME]) storCdr.Subject = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_SUBJECT], fsCdr.vars[utils.CGR_ACCOUNT], fsCdr.vars[FS_USERNAME]) storCdr.Destination = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_DESTINATION], fsCdr.vars[FS_CALL_DEST_NR], fsCdr.vars[FS_SIP_REQUSER]) storCdr.SetupTime, _ = utils.ParseTimeDetectLayout(fsCdr.vars[FS_SETUP_TIME], timezone) // Not interested to process errors, should do them if necessary in a previous step pddStr := utils.FirstNonEmpty(fsCdr.vars[FS_PROGRESS_MEDIAMSEC], fsCdr.vars[FS_PROGRESSMS]) pddStr += "ms" storCdr.PDD, _ = time.ParseDuration(pddStr) storCdr.AnswerTime, _ = utils.ParseTimeDetectLayout(fsCdr.vars[FS_ANSWER_TIME], timezone) storCdr.Usage, _ = utils.ParseDurationWithSecs(fsCdr.vars[FS_DURATION]) storCdr.Supplier = fsCdr.vars[utils.CGR_SUPPLIER] storCdr.DisconnectCause = utils.FirstNonEmpty(fsCdr.vars[utils.CGR_DISCONNECT_CAUSE], fsCdr.vars["hangup_cause"]) storCdr.ExtraFields = fsCdr.getExtraFields() storCdr.Cost = -1 return storCdr }
// Takes the record out of csv and turns it into http form which can be posted func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) { storedCdr := &utils.StoredCdr{CdrHost: "0.0.0.0", CdrSource: self.cdrSourceId, ExtraFields: make(map[string]string), Cost: -1} var err error for cfgFieldName, cfgFieldRSRs := range self.cdrFields { var fieldVal string if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) { for _, cfgFieldRSR := range cfgFieldRSRs { if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) { fieldVal += cfgFieldRSR.ParseValue("PLACEHOLDER") } else { // Dynamic value extracted using index if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx { return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName) } else { fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx]) } } } } else { // Modify here when we add more supported cdr formats fieldVal = "UNKNOWN" } switch cfgFieldName { case utils.TOR: storedCdr.TOR = fieldVal case utils.ACCID: storedCdr.AccId = fieldVal case utils.REQTYPE: storedCdr.ReqType = fieldVal case utils.DIRECTION: storedCdr.Direction = fieldVal case utils.TENANT: storedCdr.Tenant = fieldVal case utils.CATEGORY: storedCdr.Category = fieldVal case utils.ACCOUNT: storedCdr.Account = fieldVal case utils.SUBJECT: storedCdr.Subject = fieldVal case utils.DESTINATION: storedCdr.Destination = fieldVal case utils.SETUP_TIME: if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil { return nil, fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.ANSWER_TIME: if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil { return nil, fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.USAGE: if storedCdr.Usage, err = utils.ParseDurationWithNanosecs(fieldVal); err != nil { return nil, fmt.Errorf("Cannot parse duration field with value: %s, err: %s", fieldVal, err.Error()) } default: // Extra fields will not match predefined so they all show up here storedCdr.ExtraFields[cfgFieldName] = fieldVal } } storedCdr.CgrId = utils.Sha1(storedCdr.AccId, storedCdr.SetupTime.String()) return storedCdr, nil }
// Connect rpc client to rater func TestCdrsHttpCdrReplication(t *testing.T) { if !*testIntegration { return } cdrsMasterRpc, err := rpcclient.NewRpcClient("tcp", cdrsMasterCfg.RPCJSONListen, 1, 1, "json", nil) if err != nil { t.Fatal("Could not connect to rater: ", err.Error()) } testCdr1 := &engine.CDR{CGRID: utils.Sha1("httpjsonrpc1", time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC).String()), ToR: utils.VOICE, OriginID: "httpjsonrpc1", OriginHost: "192.168.1.1", Source: "UNKNOWN", RequestType: utils.META_PSEUDOPREPAID, Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Date(2013, 12, 7, 8, 42, 24, 0, time.UTC), AnswerTime: time.Date(2013, 12, 7, 8, 42, 26, 0, time.UTC), Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, RunID: utils.DEFAULT_RUNID, Cost: 1.201, Rated: true} var reply string if err := cdrsMasterRpc.Call("CdrsV2.ProcessCdr", testCdr1, &reply); err != nil { t.Error("Unexpected error: ", err.Error()) } else if reply != utils.OK { t.Error("Unexpected reply received: ", reply) } time.Sleep(time.Duration(*waitRater) * time.Millisecond) cdrsSlaveRpc, err := rpcclient.NewRpcClient("tcp", "127.0.0.1:12012", 1, 1, "json", nil) if err != nil { t.Fatal("Could not connect to rater: ", err.Error()) } // ToDo: Fix cdr_http to be compatible with rest of processCdr methods var rcvedCdrs []*engine.ExternalCDR if err := cdrsSlaveRpc.Call("ApierV2.GetCdrs", utils.RPCCDRsFilter{CGRIDs: []string{testCdr1.CGRID}, RunIDs: []string{utils.META_DEFAULT}}, &rcvedCdrs); err != nil { t.Error("Unexpected error: ", err.Error()) } else if len(rcvedCdrs) != 1 { t.Error("Unexpected number of CDRs returned: ", len(rcvedCdrs)) } else { rcvSetupTime, _ := utils.ParseTimeDetectLayout(rcvedCdrs[0].SetupTime, "") rcvAnswerTime, _ := utils.ParseTimeDetectLayout(rcvedCdrs[0].AnswerTime, "") //rcvUsage, _ := utils.ParseDurationWithSecs(rcvedCdrs[0].Usage) if rcvedCdrs[0].CGRID != testCdr1.CGRID || rcvedCdrs[0].ToR != testCdr1.ToR || rcvedCdrs[0].OriginHost != testCdr1.OriginHost || rcvedCdrs[0].Source != testCdr1.Source || rcvedCdrs[0].RequestType != testCdr1.RequestType || rcvedCdrs[0].Direction != testCdr1.Direction || rcvedCdrs[0].Tenant != testCdr1.Tenant || rcvedCdrs[0].Category != testCdr1.Category || rcvedCdrs[0].Account != testCdr1.Account || rcvedCdrs[0].Subject != testCdr1.Subject || rcvedCdrs[0].Destination != testCdr1.Destination || !rcvSetupTime.Equal(testCdr1.SetupTime) || !rcvAnswerTime.Equal(testCdr1.AnswerTime) || //rcvUsage != 10 || rcvedCdrs[0].RunID != testCdr1.RunID { //rcvedCdrs[0].Cost != testCdr1.Cost || //!reflect.DeepEqual(rcvedCdrs[0].ExtraFields, testCdr1.ExtraFields) { t.Errorf("Expected: %+v, received: %+v", testCdr1, rcvedCdrs[0]) } } }
func TestOsipsEventAsStoredCdr(t *testing.T) { setupTime, _ := utils.ParseTimeDetectLayout("1406370492") answerTime, _ := utils.ParseTimeDetectLayout("1406370499") eStoredCdr := &utils.StoredCdr{CgrId: utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", setupTime.UTC().String()), TOR: utils.VOICE, AccId: "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ;eb082607;4ea9687f", CdrHost: "172.16.254.77", CdrSource: "OSIPS_E_ACC_CDR", ReqType: "prepaid", Direction: utils.OUT, Tenant: "itsyscom.com", Category: "call", Account: "dan", Subject: "dan", Destination: "+4986517174963", SetupTime: setupTime, AnswerTime: answerTime, Usage: time.Duration(20) * time.Second, ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1} if storedCdr := osipsEv.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) } }
func TestOsipsEventAsStoredCdr(t *testing.T) { setupTime, _ := utils.ParseTimeDetectLayout("1406370492", "") answerTime, _ := utils.ParseTimeDetectLayout("1406370499", "") eStoredCdr := &engine.CDR{CGRID: utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", setupTime.UTC().String()), ToR: utils.VOICE, OriginID: "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", OriginHost: "172.16.254.77", Source: "OSIPS_E_ACC_CDR", RequestType: utils.META_PREPAID, Direction: utils.OUT, Tenant: "itsyscom.com", Category: "call", Account: "dan", Subject: "dan", Destination: "+4986517174963", SetupTime: setupTime, AnswerTime: answerTime, Usage: time.Duration(20) * time.Second, PDD: time.Duration(3) * time.Second, Supplier: "supplier3", DisconnectCause: "200", ExtraFields: map[string]string{"extra1": "val1", "extra2": "val2"}, Cost: -1} if storedCdr := osipsEv.AsStoredCdr(""); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) } }
func TestOsipsEventGetValues(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg) setupTime, _ := osipsEv.GetSetupTime(utils.META_DEFAULT, "") eSetupTime, _ := utils.ParseTimeDetectLayout("1406370492", "") answerTime, _ := osipsEv.GetAnswerTime(utils.META_DEFAULT, "") eAnswerTime, _ := utils.ParseTimeDetectLayout("1406370499", "") dur, _ := osipsEv.GetDuration(utils.META_DEFAULT) pdd, _ := osipsEv.GetPdd(utils.META_DEFAULT) endTime, _ := osipsEv.GetEndTime(utils.META_DEFAULT, "") if osipsEv.GetName() != "E_ACC_CDR" || osipsEv.GetCgrId("") != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", setupTime.UTC().String()) || osipsEv.GetUUID() != "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ" || osipsEv.GetDirection(utils.META_DEFAULT) != utils.OUT || osipsEv.GetSubject(utils.META_DEFAULT) != "dan" || osipsEv.GetAccount(utils.META_DEFAULT) != "dan" || osipsEv.GetDestination(utils.META_DEFAULT) != "+4986517174963" || osipsEv.GetCallDestNr(utils.META_DEFAULT) != "+4986517174963" || osipsEv.GetCategory(utils.META_DEFAULT) != cfg.DefaultCategory || osipsEv.GetTenant(utils.META_DEFAULT) != "itsyscom.com" || osipsEv.GetReqType(utils.META_DEFAULT) != utils.META_PREPAID || !setupTime.Equal(eSetupTime) || !answerTime.Equal(eAnswerTime) || !endTime.Equal(eAnswerTime.Add(dur)) || dur != time.Duration(20*time.Second) || pdd != time.Duration(3)*time.Second || osipsEv.GetSupplier(utils.META_DEFAULT) != "supplier3" || osipsEv.GetDisconnectCause(utils.META_DEFAULT) != "200" || osipsEv.GetOriginatorIP(utils.META_DEFAULT) != "172.16.254.77" { t.Error("GetValues not matching: ", osipsEv.GetName() != "E_ACC_CDR", osipsEv.GetCgrId("") != utils.Sha1("ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", setupTime.UTC().String()), osipsEv.GetUUID() != "ODVkMDI2Mzc2MDY5N2EzODhjNTAzNTdlODhiZjRlYWQ", osipsEv.GetDirection(utils.META_DEFAULT) != utils.OUT, osipsEv.GetSubject(utils.META_DEFAULT) != "dan", osipsEv.GetAccount(utils.META_DEFAULT) != "dan", osipsEv.GetDestination(utils.META_DEFAULT) != "+4986517174963", osipsEv.GetCallDestNr(utils.META_DEFAULT) != "+4986517174963", osipsEv.GetCategory(utils.META_DEFAULT) != cfg.DefaultCategory, osipsEv.GetTenant(utils.META_DEFAULT) != "itsyscom.com", osipsEv.GetReqType(utils.META_DEFAULT) != utils.META_PREPAID, !setupTime.Equal(time.Date(2014, 7, 26, 12, 28, 12, 0, time.UTC)), !answerTime.Equal(time.Date(2014, 7, 26, 12, 28, 19, 0, time.Local)), !endTime.Equal(time.Date(2014, 7, 26, 12, 28, 39, 0, time.Local)), dur != time.Duration(20*time.Second), pdd != time.Duration(3)*time.Second, osipsEv.GetSupplier(utils.META_DEFAULT) != "supplier3", osipsEv.GetDisconnectCause(utils.META_DEFAULT) != "200", osipsEv.GetOriginatorIP(utils.META_DEFAULT) != "172.16.254.77", ) } }
func TestFsEvAsStoredCdr(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg) ev := new(FSEvent).AsEvent(hangupEv) setupTime, _ := utils.ParseTimeDetectLayout("1436280728", "") aTime, _ := utils.ParseTimeDetectLayout("1436280728", "") eStoredCdr := &engine.StoredCdr{CgrId: "164b0422fdc6a5117031b427439482c6a4f90e41", TOR: utils.VOICE, AccId: "e3133bf7-dcde-4daf-9663-9a79ffcef5ad", CdrHost: "10.0.3.15", CdrSource: "FS_CHANNEL_HANGUP_COMPLETE", ReqType: utils.META_PREPAID, Direction: utils.OUT, Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1003", SetupTime: setupTime, AnswerTime: aTime, Usage: time.Duration(66) * time.Second, Pdd: time.Duration(28) * time.Millisecond, Supplier: "supplier1", DisconnectCause: "NORMAL_CLEARING", ExtraFields: make(map[string]string), Cost: -1} if storedCdr := ev.AsStoredCdr(""); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) } }
func TestFsEvAsStoredCdr(t *testing.T) { cfg, _ = config.NewDefaultCGRConfig() config.SetCgrConfig(cfg) ev := new(FSEvent).New(hangupEv) setupTime, _ := utils.ParseTimeDetectLayout("1398442107") aTime, _ := utils.ParseTimeDetectLayout("1398442120") eStoredCdr := &utils.StoredCdr{CgrId: utils.Sha1("37e9b766-5256-4e4b-b1ed-3767b930fec8", setupTime.UTC().String()), TOR: utils.VOICE, AccId: "37e9b766-5256-4e4b-b1ed-3767b930fec8", CdrHost: "10.0.2.15", CdrSource: "FS_CHANNEL_HANGUP_COMPLETE", ReqType: utils.PSEUDOPREPAID, Direction: utils.OUT, Tenant: "cgrates.org", Category: "call", Account: "1003", Subject: "1003", Destination: "1002", SetupTime: setupTime, AnswerTime: aTime, Usage: time.Duration(5) * time.Second, ExtraFields: make(map[string]string), Cost: -1} if storedCdr := ev.AsStoredCdr(); !reflect.DeepEqual(eStoredCdr, storedCdr) { t.Errorf("Expecting: %+v, received: %+v", eStoredCdr, storedCdr) } }
// Populates the func populateStoredCdrField(cdr *engine.StoredCdr, fieldId, fieldVal, timezone string) error { var err error switch fieldId { case utils.TOR: cdr.TOR += fieldVal case utils.ACCID: cdr.AccId += fieldVal case utils.REQTYPE: cdr.ReqType += fieldVal case utils.DIRECTION: cdr.Direction += fieldVal case utils.TENANT: cdr.Tenant += fieldVal case utils.CATEGORY: cdr.Category += fieldVal case utils.ACCOUNT: cdr.Account += fieldVal case utils.SUBJECT: cdr.Subject += fieldVal case utils.DESTINATION: cdr.Destination += fieldVal case utils.RATED: cdr.Rated, _ = strconv.ParseBool(fieldVal) case utils.SETUP_TIME: if cdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal, timezone); err != nil { return fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.PDD: if cdr.Pdd, err = utils.ParseDurationWithSecs(fieldVal); err != nil { return fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.ANSWER_TIME: if cdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal, timezone); err != nil { return fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error()) } case utils.USAGE: if cdr.Usage, err = utils.ParseDurationWithSecs(fieldVal); err != nil { return fmt.Errorf("Cannot parse duration field with value: %s, err: %s", fieldVal, err.Error()) } case utils.SUPPLIER: cdr.Supplier += fieldVal case utils.DISCONNECT_CAUSE: cdr.DisconnectCause += fieldVal default: // Extra fields will not match predefined so they all show up here cdr.ExtraFields[fieldId] += fieldVal } return nil }
func (self SMGenericEvent) GetAnswerTime(fieldName, timezone string) (time.Time, error) { if fieldName == utils.META_DEFAULT { fieldName = utils.ANSWER_TIME } result, _ := utils.ConvertIfaceToString(self[fieldName]) return utils.ParseTimeDetectLayout(result, timezone) }
func (osipsev *OsipsEvent) GetSetupTime(fieldName, timezone string) (time.Time, error) { sTimeStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[OSIPS_SETUP_TIME], osipsev.osipsEvent.AttrValues[OSIPS_EVENT_TIME]) if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value sTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):] } return utils.ParseTimeDetectLayout(sTimeStr, timezone) }
func (self *UsageRecord) AsCallDescriptor(timezone string) (*CallDescriptor, error) { var err error cd := &CallDescriptor{ CgrID: self.GetId(), TOR: self.ToR, Direction: self.Direction, Tenant: self.Tenant, Category: self.Category, Subject: self.Subject, Account: self.Account, Destination: self.Destination, } timeStr := self.AnswerTime if len(timeStr) == 0 { // In case of auth, answer time will not be defined, so take it out of setup one timeStr = self.SetupTime } if cd.TimeStart, err = utils.ParseTimeDetectLayout(timeStr, timezone); err != nil { return nil, err } if usage, err := utils.ParseDurationWithSecs(self.Usage); err != nil { return nil, err } else { cd.TimeEnd = cd.TimeStart.Add(usage) } if self.ExtraFields != nil { cd.ExtraFields = make(map[string]string) } for k, v := range self.ExtraFields { cd.ExtraFields[k] = v } return cd, nil }
func TestAPItoResourceLimit(t *testing.T) { tpRL := &utils.TPResourceLimit{ TPID: testTPID, ID: "ResGroup1", Filters: []*utils.TPRequestFilter{ &utils.TPRequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}}, &utils.TPRequestFilter{Type: MetaStringPrefix, FieldName: "Destination", Values: []string{"10", "20"}}, &utils.TPRequestFilter{Type: MetaCDRStats, Values: []string{"CDRST1:*min_ASR:34", "CDRST_1001:*min_ASR:20"}}, &utils.TPRequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}}, }, ActivationTime: "2014-07-29T15:00:00Z", Weight: 10, Limit: "2", } eRL := &ResourceLimit{ID: tpRL.ID, Weight: tpRL.Weight, Filters: make([]*RequestFilter, len(tpRL.Filters)), Usage: make(map[string]*ResourceUsage)} eRL.Filters[0] = &RequestFilter{Type: MetaString, FieldName: "Account", Values: []string{"1001", "1002"}} eRL.Filters[1] = &RequestFilter{Type: MetaStringPrefix, FieldName: "Destination", Values: []string{"10", "20"}} eRL.Filters[2] = &RequestFilter{Type: MetaCDRStats, Values: []string{"CDRST1:*min_ASR:34", "CDRST_1001:*min_ASR:20"}, cdrStatSThresholds: []*RFStatSThreshold{ &RFStatSThreshold{QueueID: "CDRST1", ThresholdType: "*MIN_ASR", ThresholdValue: 34}, &RFStatSThreshold{QueueID: "CDRST_1001", ThresholdType: "*MIN_ASR", ThresholdValue: 20}, }} eRL.Filters[3] = &RequestFilter{Type: MetaRSRFields, Values: []string{"Subject(~^1.*1$)", "Destination(1002)"}, rsrFields: utils.ParseRSRFieldsMustCompile("Subject(~^1.*1$);Destination(1002)", utils.INFIELD_SEP), } eRL.ActivationTime, _ = utils.ParseTimeDetectLayout("2014-07-29T15:00:00Z", "UTC") eRL.Limit = 2 if rl, err := APItoResourceLimit(tpRL, "UTC"); err != nil { t.Error(err) } else if !reflect.DeepEqual(eRL, rl) { t.Errorf("Expecting: %+v, received: %+v", eRL, rl) } }
func (cgrCdr CgrCdr) getCGRID(timezone string) string { if CGRID, hasIt := cgrCdr[utils.CGRID]; hasIt { return CGRID } setupTime, _ := utils.ParseTimeDetectLayout(cgrCdr[utils.SETUP_TIME], timezone) return utils.Sha1(cgrCdr[utils.ACCID], setupTime.UTC().String()) }
func (dbr *DbReader) LoadActionTriggers() (err error) { atrsMap, err := dbr.storDb.GetTpActionTriggers(dbr.tpid, "") if err != nil { return err } for key, atrsLst := range atrsMap { atrs := make([]*ActionTrigger, len(atrsLst)) for idx, apiAtr := range atrsLst { balance_expiration_date, _ := utils.ParseTimeDetectLayout(apiAtr.BalanceExpirationDate) atrs[idx] = &ActionTrigger{ Id: utils.GenUUID(), BalanceType: apiAtr.BalanceType, Direction: apiAtr.Direction, ThresholdType: apiAtr.ThresholdType, ThresholdValue: apiAtr.ThresholdValue, Recurrent: apiAtr.Recurrent, MinSleep: apiAtr.MinSleep, DestinationId: apiAtr.DestinationId, BalanceWeight: apiAtr.BalanceWeight, BalanceExpirationDate: balance_expiration_date, BalanceRatingSubject: apiAtr.BalanceRatingSubject, BalanceCategory: apiAtr.BalanceCategory, BalanceSharedGroup: apiAtr.BalanceSharedGroup, Weight: apiAtr.Weight, ActionsId: apiAtr.ActionsId, MinQueuedItems: apiAtr.MinQueuedItems, } } dbr.actionsTriggers[key] = atrs } return err }
func TestCDRFields(t *testing.T) { fsCdrCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "sip_user_agent"}} fsCdr, err := NewFSCdr(body, fsCdrCfg) if err != nil { t.Errorf("Error loading cdr: %v", err) } setupTime, _ := utils.ParseTimeDetectLayout("1436280728", "") answerTime, _ := utils.ParseTimeDetectLayout("1436280728", "") expctCDR := &CDR{CGRID: "164b0422fdc6a5117031b427439482c6a4f90e41", ToR: utils.VOICE, OriginID: "e3133bf7-dcde-4daf-9663-9a79ffcef5ad", OriginHost: "127.0.0.1", Source: "freeswitch_json", Direction: utils.OUT, Category: "call", RequestType: utils.META_PREPAID, Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1003", SetupTime: setupTime, PDD: time.Duration(28) * time.Millisecond, AnswerTime: answerTime, Usage: time.Duration(66) * time.Second, Supplier: "supplier1", DisconnectCause: "NORMAL_CLEARING", ExtraFields: map[string]string{"sip_user_agent": "PJSUA v2.3 Linux-3.2.0.4/x86_64/glibc-2.13"}, Cost: -1} if CDR := fsCdr.AsStoredCdr(""); !reflect.DeepEqual(expctCDR, CDR) { t.Errorf("Expecting: %v, received: %v", expctCDR, CDR) } }
func (kev KamEvent) GetSetupTime(fieldName, timezone string) (time.Time, error) { sTimeStr := utils.FirstNonEmpty(kev[fieldName], kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME]) if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value sTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):] } return utils.ParseTimeDetectLayout(sTimeStr, timezone) }
func (self *ApierV1) RemoveBalances(attr *utils.AttrSetBalance, reply *string) error { if missing := utils.MissingStructFields(attr, []string{"Tenant", "Account", "BalanceType"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } var expTime *time.Time if attr.ExpiryTime != nil { expTimeVal, err := utils.ParseTimeDetectLayout(*attr.ExpiryTime, self.Config.DefaultTimezone) if err != nil { *reply = err.Error() return err } expTime = &expTimeVal } accID := utils.AccountKey(attr.Tenant, attr.Account) if _, err := self.AccountDb.GetAccount(accID); err != nil { return utils.ErrNotFound } at := &engine.ActionTiming{} at.SetAccountIDs(utils.StringMap{accID: true}) a := &engine.Action{ ActionType: engine.REMOVE_BALANCE, Balance: &engine.BalanceFilter{ Uuid: attr.BalanceUUID, ID: attr.BalanceID, Type: utils.StringPointer(attr.BalanceType), ExpirationDate: expTime, RatingSubject: attr.RatingSubject, Weight: attr.Weight, Blocker: attr.Blocker, Disabled: attr.Disabled, }, } if attr.Value != nil { a.Balance.Value = &utils.ValueFormula{Static: *attr.Value} } if attr.Directions != nil { a.Balance.Directions = utils.StringMapPointer(utils.ParseStringMap(*attr.Directions)) } if attr.DestinationIds != nil { a.Balance.DestinationIDs = utils.StringMapPointer(utils.ParseStringMap(*attr.DestinationIds)) } if attr.Categories != nil { a.Balance.Categories = utils.StringMapPointer(utils.ParseStringMap(*attr.Categories)) } if attr.SharedGroups != nil { a.Balance.SharedGroups = utils.StringMapPointer(utils.ParseStringMap(*attr.SharedGroups)) } if attr.TimingIds != nil { a.Balance.TimingIDs = utils.StringMapPointer(utils.ParseStringMap(*attr.TimingIds)) } at.SetActions(engine.Actions{a}) if err := at.Execute(); err != nil { *reply = err.Error() return err } *reply = OK return nil }
func TestCDRFields(t *testing.T) { cfg, _ = config.NewDefaultCGRConfig() cfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "sip_user_agent"}} fsCdr, err := NewFSCdr(body) if err != nil { t.Errorf("Error loading cdr: %v", err) } setupTime, _ := utils.ParseTimeDetectLayout(fsCdr.vars[FS_SETUP_TIME]) answerTime, _ := utils.ParseTimeDetectLayout(fsCdr.vars[FS_ANSWER_TIME]) expctStoredCdr := &utils.StoredCdr{CgrId: utils.Sha1("01df56f4-d99a-4ef6-b7fe-b924b2415b7f", setupTime.UTC().String()), TOR: utils.VOICE, AccId: "01df56f4-d99a-4ef6-b7fe-b924b2415b7f", CdrHost: "127.0.0.1", CdrSource: "freeswitch_json", Direction: "*out", Category: "call", ReqType: utils.RATED, Tenant: "ipbx.itsyscom.com", Account: "dan", Subject: "dan", Destination: "+4986517174963", SetupTime: setupTime, AnswerTime: answerTime, Usage: time.Duration(4) * time.Second, ExtraFields: map[string]string{"sip_user_agent": "Jitsi2.2.4603.9615Linux"}, Cost: -1} if storedCdr := fsCdr.AsStoredCdr(); !reflect.DeepEqual(expctStoredCdr, storedCdr) { t.Errorf("Expecting: %v, received: %v", expctStoredCdr, storedCdr) } }
// Deprecated in rc8, replaced by AddAccountActionTriggers func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } at := &engine.ActionTrigger{ ID: attr.ActionTriggersId, ThresholdType: attr.ThresholdType, ThresholdValue: attr.ThresholdValue, Balance: new(engine.BalanceFilter), Weight: attr.Weight, ActionsID: attr.ActionsId, } if attr.BalanceId != "" { at.Balance.ID = utils.StringPointer(attr.BalanceId) } if attr.BalanceType != "" { at.Balance.Type = utils.StringPointer(attr.BalanceType) } if attr.BalanceDirection != "" { at.Balance.Directions = &utils.StringMap{attr.BalanceDirection: true} } if attr.BalanceDestinationIds != "" { dstIDsMp := utils.StringMapFromSlice(strings.Split(attr.BalanceDestinationIds, utils.INFIELD_SEP)) at.Balance.DestinationIDs = &dstIDsMp } if attr.BalanceRatingSubject != "" { at.Balance.RatingSubject = utils.StringPointer(attr.BalanceRatingSubject) } if attr.BalanceWeight != 0.0 { at.Balance.Weight = utils.Float64Pointer(attr.BalanceWeight) } if balExpiryTime, err := utils.ParseTimeDetectLayout(attr.BalanceExpiryTime, self.Config.DefaultTimezone); err != nil { return utils.NewErrServerError(err) } else { at.Balance.ExpirationDate = &balExpiryTime } if attr.BalanceSharedGroup != "" { at.Balance.SharedGroups = &utils.StringMap{attr.BalanceSharedGroup: true} } acntID := utils.AccountKey(attr.Tenant, attr.Account) _, err := engine.Guardian.Guard(func() (interface{}, error) { acnt, err := self.AccountDb.GetAccount(acntID) if err != nil { return 0, err } acnt.ActionTriggers = append(acnt.ActionTriggers, at) if err = self.AccountDb.SetAccount(acnt); err != nil { return 0, err } return 0, nil }, 0, acntID) if err != nil { return err } *reply = OK return nil }
func (osipsev *OsipsEvent) GetAnswerTime(fieldName, timezone string) (time.Time, error) { aTimeStr := utils.FirstNonEmpty(osipsev.osipsEvent.AttrValues[fieldName], osipsev.osipsEvent.AttrValues[CGR_ANSWERTIME]) if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value aTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):] } else if fieldName == utils.META_DEFAULT { aTimeStr = osipsev.osipsEvent.AttrValues[CGR_ANSWERTIME] } return utils.ParseTimeDetectLayout(aTimeStr, timezone) }
func (self *ApierV1) AddAccountActionTriggers(attr AttrAddAccountActionTriggers, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } actTime, err := utils.ParseTimeDetectLayout(attr.ActivationDate, self.Config.DefaultTimezone) if err != nil { *reply = err.Error() return err } accID := utils.AccountKey(attr.Tenant, attr.Account) var account *engine.Account _, err = engine.Guardian.Guard(func() (interface{}, error) { if acc, err := self.AccountDb.GetAccount(accID); err == nil { account = acc } else { return 0, err } if attr.ActionTriggerIDs != nil { if attr.ActionTriggerOverwrite { account.ActionTriggers = make(engine.ActionTriggers, 0) } for _, actionTriggerID := range *attr.ActionTriggerIDs { atrs, err := self.RatingDb.GetActionTriggers(actionTriggerID, false, utils.NonTransactional) if err != nil { return 0, err } for _, at := range atrs { var found bool for _, existingAt := range account.ActionTriggers { if existingAt.Equals(at) { found = true break } } at.ActivationDate = actTime at.Executed = attr.Executed if !found { account.ActionTriggers = append(account.ActionTriggers, at) } } } } account.InitCounters() if err := self.AccountDb.SetAccount(account); err != nil { return 0, err } return 0, nil }, 0, accID) if err != nil { *reply = err.Error() return err } *reply = utils.OK return nil }
func TestCosts1GetCost1(t *testing.T) { tStart, _ := utils.ParseTimeDetectLayout("2013-08-07T17:30:00Z", "") tEnd, _ := utils.ParseTimeDetectLayout("2013-08-07T17:31:30Z", "") cd := &engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "+4986517174963", TimeStart: tStart, TimeEnd: tEnd, } if cc, err := cd.GetCost(); err != nil { t.Error(err) } else if cc.Cost != 90 { t.Error("Wrong cost returned: ", cc.Cost) } }