From 39bca3495ad6eef0c309e667e35c9ec6e9036a44 Mon Sep 17 00:00:00 2001 From: arberkatellari Date: Fri, 14 Feb 2025 17:44:30 +0200 Subject: [PATCH] Improve *dynaperpaid events for sessions --- .../samples/sess_dynaprepaid/cgrates.json | 56 ++++ general_tests/sessions_dynaprepaid_it_test.go | 287 ++++++++++++++++++ sessions/sessions.go | 14 +- 3 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 data/conf/samples/sess_dynaprepaid/cgrates.json create mode 100644 general_tests/sessions_dynaprepaid_it_test.go diff --git a/data/conf/samples/sess_dynaprepaid/cgrates.json b/data/conf/samples/sess_dynaprepaid/cgrates.json new file mode 100644 index 000000000..fd2e222d9 --- /dev/null +++ b/data/conf/samples/sess_dynaprepaid/cgrates.json @@ -0,0 +1,56 @@ +{ + // CGRateS Configuration file + // + // Used in apier_local_tests + // Starts rater, cdrs and mediator connecting over internal channel + + "general": { + "log_level": 7, + }, + + + "stor_db": { + "db_password": "CGRateS.org", + }, + + + "rals": { + "enabled": true, + }, + + "schedulers": { + "enabled": true, + "dynaprepaid_actionplans": ["PACKAGE_1001"] + }, + + "cdrs": { + "enabled": true, + "chargers_conns":["*localhost"], + "rals_conns": ["*localhost"], + "scheduler_conns": ["*localhost"], + }, + + "chargers": { + "enabled": true, + "attributes_conns": ["*localhost"], + }, + + "attributes": { + "enabled": true, + }, + + "sessions": { + "enabled": true, + "rals_conns": ["*localhost"], + "cdrs_conns": ["*localhost"], + "chargers_conns": ["*localhost"], + "scheduler_conns": ["*localhost"], + }, + + "apiers": { + "enabled": true, + "scheduler_conns": ["*localhost"], + }, + + } + \ No newline at end of file diff --git a/general_tests/sessions_dynaprepaid_it_test.go b/general_tests/sessions_dynaprepaid_it_test.go new file mode 100644 index 000000000..2be2867fa --- /dev/null +++ b/general_tests/sessions_dynaprepaid_it_test.go @@ -0,0 +1,287 @@ +//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 ( + "path" + "reflect" + "testing" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/sessions" + "github.com/cgrates/cgrates/utils" +) + +func TestSessDynaprepaidInit(t *testing.T) { + ng := engine.TestEngine{ + ConfigPath: path.Join(*utils.DataDir, "conf", "samples", "sess_dynaprepaid"), + TpPath: path.Join(*utils.DataDir, "tariffplans", "testit"), + } + client, _ := ng.Run(t) + time.Sleep(50 * time.Millisecond) + t.Run("GetAccount", func(t *testing.T) { + var acnt engine.Account + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, + &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "CreatedAccount"}, &acnt); err == nil || + err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } + }) + + t.Run("InitSession", func(t *testing.T) { + args1 := &sessions.V1InitSessionArgs{ + InitSession: true, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + Event: map[string]any{ + utils.OriginID: "sessDynaprepaid", + utils.OriginHost: "192.168.1.1", + utils.Source: "sessDynaprepaid", + utils.ToR: utils.MetaData, + utils.RequestType: utils.MetaDynaprepaid, + utils.AccountField: "CreatedAccount", + utils.Subject: "NoSubject", + utils.Destination: "+1234567", + utils.AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC), + utils.Usage: 1024, + }, + }, + } + var rply1 sessions.V1InitSessionReply + if err := client.Call(context.Background(), utils.SessionSv1InitiateSession, + args1, &rply1); err != nil { + t.Error(err) + return + } else if *rply1.MaxUsage != 1024*time.Nanosecond { + t.Errorf("Expected <%+v>, received <%+v>", 1024*time.Nanosecond, *rply1.MaxUsage) + } + }) + + t.Run("GetAccount2", func(t *testing.T) { + var acnt engine.Account + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, + &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "CreatedAccount"}, &acnt); err != nil { + t.Error(err) + } + expAcc := &engine.Account{ + ID: "cgrates.org:CreatedAccount", + BalanceMap: map[string]engine.Balances{ + utils.MetaMonetary: { + &engine.Balance{ + Uuid: acnt.BalanceMap[utils.MetaMonetary][0].Uuid, + ID: "", + Categories: utils.StringMap{}, + SharedGroups: utils.StringMap{}, + TimingIDs: utils.StringMap{}, + Value: 9.99966, + Weight: 10, + DestinationIDs: utils.StringMap{}, + }, + }, + utils.MetaSMS: { + &engine.Balance{ + Uuid: acnt.BalanceMap[utils.MetaSMS][0].Uuid, + Value: 500, + Weight: 10, + DestinationIDs: utils.StringMap{}, + Categories: utils.StringMap{}, + SharedGroups: utils.StringMap{}, + TimingIDs: utils.StringMap{}, + }, + }, + }, + UpdateTime: acnt.UpdateTime, + } + if !reflect.DeepEqual(utils.ToJSON(expAcc), utils.ToJSON(expAcc)) { + t.Errorf("Expected <%v>, \nreceived <%v>", utils.ToJSON(expAcc.BalanceMap), utils.ToJSON(expAcc.BalanceMap)) + } + }) +} + +func TestSessDynaprepaidUpdate(t *testing.T) { + ng := engine.TestEngine{ + ConfigPath: path.Join(*utils.DataDir, "conf", "samples", "sess_dynaprepaid"), + TpPath: path.Join(*utils.DataDir, "tariffplans", "testit"), + } + client, _ := ng.Run(t) + time.Sleep(50 * time.Millisecond) + t.Run("GetAccount", func(t *testing.T) { + var acnt engine.Account + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, + &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "CreatedAccount"}, &acnt); err == nil || + err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } + }) + + t.Run("UpdateSession", func(t *testing.T) { + args1 := &sessions.V1UpdateSessionArgs{ + UpdateSession: true, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + Event: map[string]any{ + utils.OriginID: "sessDynaprepaid", + utils.OriginHost: "192.168.1.1", + utils.Source: "sessDynaprepaid", + utils.ToR: utils.MetaData, + utils.RequestType: utils.MetaDynaprepaid, + utils.AccountField: "CreatedAccount", + utils.Subject: "NoSubject", + utils.Destination: "+1234567", + utils.AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC), + utils.Usage: 1024, + }, + }, + } + var rply1 sessions.V1UpdateSessionReply + if err := client.Call(context.Background(), utils.SessionSv1UpdateSession, + args1, &rply1); err != nil { + t.Error(err) + return + } + }) + + t.Run("GetAccount2", func(t *testing.T) { + var acnt engine.Account + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, + &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "CreatedAccount"}, &acnt); err != nil { + t.Error(err) + } + expAcc := &engine.Account{ + ID: "cgrates.org:CreatedAccount", + BalanceMap: map[string]engine.Balances{ + utils.MetaMonetary: { + &engine.Balance{ + Uuid: acnt.BalanceMap[utils.MetaMonetary][0].Uuid, + ID: "", + Categories: utils.StringMap{}, + SharedGroups: utils.StringMap{}, + TimingIDs: utils.StringMap{}, + Value: 9.99966, + Weight: 10, + DestinationIDs: utils.StringMap{}, + }, + }, + utils.MetaSMS: { + &engine.Balance{ + Uuid: acnt.BalanceMap[utils.MetaSMS][0].Uuid, + Value: 500, + Weight: 10, + DestinationIDs: utils.StringMap{}, + Categories: utils.StringMap{}, + SharedGroups: utils.StringMap{}, + TimingIDs: utils.StringMap{}, + }, + }, + }, + UpdateTime: acnt.UpdateTime, + } + if !reflect.DeepEqual(utils.ToJSON(expAcc), utils.ToJSON(expAcc)) { + t.Errorf("Expected <%v>, \nreceived <%v>", utils.ToJSON(expAcc.BalanceMap), utils.ToJSON(expAcc.BalanceMap)) + } + }) +} + +func TestSessDynaprepaidTerminate(t *testing.T) { + ng := engine.TestEngine{ + ConfigPath: path.Join(*utils.DataDir, "conf", "samples", "sess_dynaprepaid"), + TpPath: path.Join(*utils.DataDir, "tariffplans", "testit"), + } + client, _ := ng.Run(t) + time.Sleep(50 * time.Millisecond) + t.Run("GetAccount", func(t *testing.T) { + var acnt engine.Account + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, + &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "CreatedAccount"}, &acnt); err == nil || + err.Error() != utils.ErrNotFound.Error() { + t.Error(err) + } + }) + + t.Run("TerminateSession", func(t *testing.T) { + args1 := &sessions.V1TerminateSessionArgs{ + TerminateSession: true, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + Event: map[string]any{ + utils.OriginID: "sessDynaprepaid", + utils.OriginHost: "192.168.1.1", + utils.Source: "sessDynaprepaid", + utils.ToR: utils.MetaData, + utils.RequestType: utils.MetaDynaprepaid, + utils.AccountField: "CreatedAccount", + utils.Subject: "NoSubject", + utils.Destination: "+1234567", + utils.AnswerTime: time.Date(2018, 8, 24, 16, 00, 26, 0, time.UTC), + utils.Usage: 1024, + }, + }, + } + var rply1 string + if err := client.Call(context.Background(), utils.SessionSv1TerminateSession, + args1, &rply1); err != nil { + t.Error(err) + return + } + }) + + t.Run("GetAccount2", func(t *testing.T) { + var acnt engine.Account + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, + &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "CreatedAccount"}, &acnt); err != nil { + t.Error(err) + } + expAcc := &engine.Account{ + ID: "cgrates.org:CreatedAccount", + BalanceMap: map[string]engine.Balances{ + utils.MetaMonetary: { + &engine.Balance{ + Uuid: acnt.BalanceMap[utils.MetaMonetary][0].Uuid, + ID: "", + Categories: utils.StringMap{}, + SharedGroups: utils.StringMap{}, + TimingIDs: utils.StringMap{}, + Value: 9.99966, + Weight: 10, + DestinationIDs: utils.StringMap{}, + }, + }, + utils.MetaSMS: { + &engine.Balance{ + Uuid: acnt.BalanceMap[utils.MetaSMS][0].Uuid, + Value: 500, + Weight: 10, + DestinationIDs: utils.StringMap{}, + Categories: utils.StringMap{}, + SharedGroups: utils.StringMap{}, + TimingIDs: utils.StringMap{}, + }, + }, + }, + UpdateTime: acnt.UpdateTime, + } + if !reflect.DeepEqual(utils.ToJSON(expAcc), utils.ToJSON(expAcc)) { + t.Errorf("Expected <%v>, \nreceived <%v>", utils.ToJSON(expAcc.BalanceMap), utils.ToJSON(expAcc.BalanceMap)) + } + }) +} diff --git a/sessions/sessions.go b/sessions/sessions.go index 43ecaf769..084c60b31 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -1542,9 +1542,11 @@ func (sS *SessionS) initSessionDebitLoops(s *Session) { return } for i, sr := range s.SRuns { - if s.DebitInterval > 0 && - sr.Event.GetStringIgnoreErrors(utils.RequestType) == utils.MetaPrepaid { - if s.debitStop == nil { // init the debitStop only for the first sRun with DebitInterval and RequestType MetaPrepaid + if (s.DebitInterval > 0 && + sr.Event.GetStringIgnoreErrors(utils.RequestType) == utils.MetaPrepaid) || + (s.DebitInterval > 0 && sr.Event.GetStringIgnoreErrors(utils.RequestType) == + utils.MetaDynaprepaid) { + if s.debitStop == nil { // init the debitStop only for the first sRun with DebitInterval and RequestType MetaPrepaids.DebitInterval > 0 && s.debitStop = make(chan struct{}) } go sS.debitLoopSession(s, i, s.DebitInterval) @@ -1666,7 +1668,7 @@ func (sS *SessionS) updateSession(s *Session, updtEv, opts engine.MapEvent, isMs reqType := sr.Event.GetStringIgnoreErrors(utils.RequestType) var rplyMaxUsage time.Duration switch reqType { - case utils.MetaPrepaid: + case utils.MetaPrepaid, utils.MetaDynaprepaid: if s.debitStop == nil { if rplyMaxUsage, err = sS.debitSession(s, i, reqMaxUsage, updtEv.GetDurationPtrIgnoreErrors(utils.LastUsed)); err != nil { @@ -2455,9 +2457,9 @@ func (sS *SessionS) BiRPCv1InitiateSession(ctx *context.Context, return err } s.RLock() // avoid concurrency with activeDebit - isPrepaid := s.debitStop != nil + hasDebitLoops := s.debitStop != nil s.RUnlock() - if isPrepaid { //active debit + if hasDebitLoops { //active debit rply.MaxUsage = utils.DurationPointer(sS.cgrCfg.SessionSCfg().GetDefaultUsage(utils.IfaceAsString(args.CGREvent.Event[utils.ToR]))) } else { var sRunsUsage map[string]time.Duration