diff --git a/general_tests/sessions_message_it_test.go b/general_tests/sessions_message_it_test.go new file mode 100644 index 000000000..3eb242af7 --- /dev/null +++ b/general_tests/sessions_message_it_test.go @@ -0,0 +1,211 @@ +//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 general_tests + +import ( + "net/rpc" + "path" + "testing" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/sessions" + "github.com/cgrates/cgrates/utils" +) + +var ( + sesMFDCfgDir string + sesMFDCfgPath string + sesMFDCfg *config.CGRConfig + sesMFDRPC *rpc.Client + + sesMFDTests = []func(t *testing.T){ + testSesMFDItLoadConfig, + testSesMFDItResetDataDB, + testSesMFDItResetStorDb, + testSesMFDItStartEngine, + testSesMFDItRPCConn, + testSesMFDItSetChargers, + testSesMFDItAddVoiceBalance, + testSesMFDItProcessMessage, + testSesMFDItGetAccountAfter, + testSesMFDItStopCgrEngine, + } +) + +func TestSesMFDIt(t *testing.T) { + switch *dbType { + case utils.MetaInternal: + sesMFDCfgDir = "sessions_internal" + case utils.MetaMySQL: + sesMFDCfgDir = "sessions_mysql" + case utils.MetaMongo: + sesMFDCfgDir = "sessions_mongo" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + for _, stest := range sesMFDTests { + t.Run(sesMFDCfgDir, stest) + } +} + +func testSesMFDItLoadConfig(t *testing.T) { + sesMFDCfgPath = path.Join(*dataDir, "conf", "samples", sesMFDCfgDir) + if sesMFDCfg, err = config.NewCGRConfigFromPath(sesMFDCfgPath); err != nil { + t.Error(err) + } +} + +func testSesMFDItResetDataDB(t *testing.T) { + if err := engine.InitDataDb(sesMFDCfg); err != nil { + t.Fatal(err) + } +} + +func testSesMFDItResetStorDb(t *testing.T) { + if err := engine.InitStorDb(sesMFDCfg); err != nil { + t.Fatal(err) + } +} + +func testSesMFDItStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(sesMFDCfgPath, *waitRater); err != nil { + t.Fatal(err) + } +} + +func testSesMFDItRPCConn(t *testing.T) { + var err error + sesMFDRPC, err = newRPCClient(sesMFDCfg.ListenCfg()) + if err != nil { + t.Fatal(err) + } +} + +func testSesMFDItSetChargers(t *testing.T) { + //add a default charger + var result string + if err := sesMFDRPC.Call(utils.APIerSv1SetChargerProfile, &engine.ChargerProfile{ + Tenant: "cgrates.org", + ID: "default", + RunID: utils.MetaDefault, + AttributeIDs: []string{"*none"}, + Weight: 20, + }, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } + if err := sesMFDRPC.Call(utils.APIerSv1SetChargerProfile, &engine.ChargerProfile{ + Tenant: "cgrates.org", + ID: "default2", + RunID: "default2", + AttributeIDs: []string{"*none"}, + Weight: 10, + }, &result); err != nil { + t.Error(err) + } else if result != utils.OK { + t.Error("Unexpected reply returned", result) + } +} + +func testSesMFDItAddVoiceBalance(t *testing.T) { + var reply string + if err := sesMFDRPC.Call(utils.APIerSv2SetBalance, utils.AttrSetBalance{ + Tenant: "cgrates.org", + Account: "1001", + BalanceType: utils.SMS, + Value: 1, + Balance: map[string]interface{}{ + utils.ID: "TestSesBal1", + utils.RatingSubject: "*zero1", + }, + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Errorf("Received: %s", reply) + } + + var acnt engine.Account + if err := sesMFDRPC.Call(utils.APIerSv2GetAccount, + &utils.AttrGetAccount{ + Tenant: "cgrates.org", + Account: "1001", + }, &acnt); err != nil { + t.Fatal(err) + } + expected := 1. + if rply := acnt.BalanceMap[utils.SMS].GetTotalValue(); rply != expected { + t.Errorf("Expected: %v, received: %v", expected, rply) + } +} +func testSesMFDItProcessMessage(t *testing.T) { + var initRpl *sessions.V1ProcessMessageReply + if err := sesMFDRPC.Call(utils.SessionSv1ProcessMessage, + &sessions.V1ProcessMessageArgs{ + Debit: true, + ForceDuration: true, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: utils.UUIDSha1Prefix(), + Event: map[string]interface{}{ + utils.OriginID: utils.UUIDSha1Prefix(), + utils.ToR: utils.SMS, + utils.Category: utils.SMS, + utils.Tenant: "cgrates.org", + utils.Account: "1001", + utils.Subject: "1001", + utils.Destination: "1002", + utils.RequestType: utils.META_PREPAID, + utils.Usage: 1, + utils.AnswerTime: time.Date(2016, time.January, 5, 18, 31, 05, 0, time.UTC), + }, + }, + }, &initRpl); err == nil || err.Error() != utils.NewErrRALs(utils.ErrRatingPlanNotFound).Error() { + t.Fatal(err) + } + +} + +func testSesMFDItGetAccountAfter(t *testing.T) { + var acnt engine.Account + if err := sesMFDRPC.Call(utils.APIerSv2GetAccount, + &utils.AttrGetAccount{ + Tenant: "cgrates.org", + Account: "1001", + }, &acnt); err != nil { + t.Fatal(err) + } + expected := 1. + if rply := acnt.BalanceMap[utils.SMS].GetTotalValue(); rply != expected { + t.Errorf("Expected: %v, received: %v", expected, rply) + } +} + +func testSesMFDItStopCgrEngine(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } +} diff --git a/sessions/sessions.go b/sessions/sessions.go index bbbf25fc4..87082d54a 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -1502,36 +1502,36 @@ func (sS *SessionS) endSession(s *Session, tUsage, lastUsage *time.Duration, sUsage = sr.TotalUsage } if sr.EventCost != nil { - if !isMsg { // in case of one time charge there is no need of corrections - if notCharged := sUsage - sr.EventCost.GetUsage(); notCharged > 0 { // we did not charge enough, make a manual debit here - if sr.CD.LoopIndex > 0 { - sr.CD.TimeStart = sr.CD.TimeEnd - } - sr.CD.TimeEnd = sr.CD.TimeStart.Add(notCharged) - sr.CD.DurationIndex += notCharged - cc := new(engine.CallCost) - if err = sS.connMgr.Call(sS.cgrCfg.SessionSCfg().RALsConns, nil, utils.ResponderDebit, - &engine.CallDescriptorWithArgDispatcher{ - CallDescriptor: sr.CD, - ArgDispatcher: s.ArgDispatcher}, cc); err == nil { - sr.EventCost.Merge( - engine.NewEventCostFromCallCost(cc, s.CGRID, - sr.Event.GetStringIgnoreErrors(utils.RunID))) - } - } else if notCharged < 0 { // charged too much, try refund - if err = sS.refundSession(s, sRunIdx, -notCharged); err != nil { - utils.Logger.Warning( - fmt.Sprintf( - "<%s> failed refunding session: <%s>, srIdx: <%d>, error: <%s>", - utils.SessionS, s.CGRID, sRunIdx, err.Error())) - } + // if !isMsg { // in case of one time charge there is no need of corrections + if notCharged := sUsage - sr.EventCost.GetUsage(); notCharged > 0 { // we did not charge enough, make a manual debit here + if sr.CD.LoopIndex > 0 { + sr.CD.TimeStart = sr.CD.TimeEnd } - if err := sS.roundCost(s, sRunIdx); err != nil { // will round the cost and refund the extra increment + sr.CD.TimeEnd = sr.CD.TimeStart.Add(notCharged) + sr.CD.DurationIndex += notCharged + cc := new(engine.CallCost) + if err = sS.connMgr.Call(sS.cgrCfg.SessionSCfg().RALsConns, nil, utils.ResponderDebit, + &engine.CallDescriptorWithArgDispatcher{ + CallDescriptor: sr.CD, + ArgDispatcher: s.ArgDispatcher}, cc); err == nil { + sr.EventCost.Merge( + engine.NewEventCostFromCallCost(cc, s.CGRID, + sr.Event.GetStringIgnoreErrors(utils.RunID))) + } + } else if notCharged < 0 { // charged too much, try refund + if err = sS.refundSession(s, sRunIdx, -notCharged); err != nil { utils.Logger.Warning( - fmt.Sprintf("<%s> failed rounding session cost for <%s>, srIdx: <%d>, error: <%s>", + fmt.Sprintf( + "<%s> failed refunding session: <%s>, srIdx: <%d>, error: <%s>", utils.SessionS, s.CGRID, sRunIdx, err.Error())) } } + if err := sS.roundCost(s, sRunIdx); err != nil { // will round the cost and refund the extra increment + utils.Logger.Warning( + fmt.Sprintf("<%s> failed rounding session cost for <%s>, srIdx: <%d>, error: <%s>", + utils.SessionS, s.CGRID, sRunIdx, err.Error())) + } + // } // compute the event cost before saving the SessionCost // add here to be applied for messages also sr.EventCost.Compute()