From 681cd3c58a0da31b4dfbeec4f5755317c72b0741 Mon Sep 17 00:00:00 2001 From: arberkatellari Date: Tue, 7 Nov 2023 11:55:10 -0500 Subject: [PATCH] Add dryrun for CDRsV2ProcessEvent --- .../samples/rerate_cdrs_mysql/cgrates.json | 4 +- .../tariffplans/reratecdrs/RatingProfiles.csv | 2 + engine/calldesc.go | 3 +- engine/responder.go | 5 + general_tests/cdrs_processevent_dr_it_test.go | 216 ++++++++++++++++++ utils/consts.go | 1 + 6 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 general_tests/cdrs_processevent_dr_it_test.go diff --git a/data/conf/samples/rerate_cdrs_mysql/cgrates.json b/data/conf/samples/rerate_cdrs_mysql/cgrates.json index 0fd250355..a81981c30 100644 --- a/data/conf/samples/rerate_cdrs_mysql/cgrates.json +++ b/data/conf/samples/rerate_cdrs_mysql/cgrates.json @@ -1,5 +1,7 @@ { - + "general": { + "log_level": 7 + }, "rals": { "enabled": true }, diff --git a/data/tariffplans/reratecdrs/RatingProfiles.csv b/data/tariffplans/reratecdrs/RatingProfiles.csv index a7fc96b85..4403ccf20 100644 --- a/data/tariffplans/reratecdrs/RatingProfiles.csv +++ b/data/tariffplans/reratecdrs/RatingProfiles.csv @@ -1,2 +1,4 @@ #Tenant,Category,Subject,ActivationTime,RatingPlanId,RatesFallbackSubject cgrates.org,call,1001,2014-01-14T00:00:00Z,RP_ANY, +cgrates.org,call,rs1,2014-01-14T00:00:00Z,RP_ANY, +cgrates.org,call,rs2,2014-01-14T00:00:00Z,RP_ANY, \ No newline at end of file diff --git a/engine/calldesc.go b/engine/calldesc.go index 732a4af57..2d02f4a14 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -137,6 +137,7 @@ type CallDescriptor struct { DenyNegativeAccount bool // prevent account going on negative during debit account *Account testCallcost *CallCost // testing purpose only! + DryRun bool } // AsCGREvent converts the CallDescriptor into CGREvent @@ -769,7 +770,7 @@ func (cd *CallDescriptor) Debit(fltrS *FilterS) (cc *CallCost, err error) { } } return guardian.Guardian.Guard(func() (err error) { - cc, err = cd.debit(account, false, !cd.DenyNegativeAccount, fltrS) + cc, err = cd.debit(account, cd.DryRun, !cd.DenyNegativeAccount, fltrS) if err == nil { cc.AccountSummary = cd.AccountSummary(initialAcnt) } diff --git a/engine/responder.go b/engine/responder.go index 923055c8e..f174dce9d 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -202,6 +202,11 @@ func (rs *Responder) Debit(ctx *context.Context, arg *CallDescriptorWithAPIOpts, err = utils.ErrMaxUsageExceeded return } + if ralsDryRun, exists := arg.APIOpts[utils.MetaRALsDryRun]; exists { + if arg.DryRun, err = utils.IfaceAsBool(ralsDryRun); err != nil { + return err + } + } var r *CallCost if r, err = arg.Debit(rs.FilterS); err != nil { return diff --git a/general_tests/cdrs_processevent_dr_it_test.go b/general_tests/cdrs_processevent_dr_it_test.go new file mode 100644 index 000000000..c982b134d --- /dev/null +++ b/general_tests/cdrs_processevent_dr_it_test.go @@ -0,0 +1,216 @@ +//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/utils" +) + +func TestCDRsProcessEventDryrun(t *testing.T) { + cfgPath := path.Join(*dataDir, "conf", "samples", "rerate_cdrs_mysql") + tpPath := path.Join(*dataDir, "tariffplans", "reratecdrs") + client, _, shutdown, err := setupTest(t, "TestRerateCDRs", cfgPath, tpPath, utils.EmptyString, nil) + if err != nil { + t.Fatal(err) + } + defer shutdown() + + CGRID := utils.GenUUID() + + var aSummaryBefore *engine.AccountSummary + + t.Run("SetBalance", func(t *testing.T) { + var reply string + if err := client.Call(context.Background(), utils.APIerSv1SetBalances, + utils.AttrSetBalances{ + Tenant: "cgrates.org", + Account: "1001", + Balances: []*utils.AttrBalance{ + { + BalanceType: utils.MetaVoice, + Value: float64(3 * time.Minute), + Balance: map[string]any{ + utils.ID: "voiceBalance1", + utils.RatingSubject: "rs1", + }, + }, + { + BalanceType: utils.MetaVoice, + Value: float64(4 * time.Minute), + Balance: map[string]any{ + utils.ID: "voiceBalance2", + utils.RatingSubject: "rs2", + }, + }, + }, + }, &reply); err != nil { + t.Fatal(err) + } + }) + + t.Run("CheckInitialBalance", func(t *testing.T) { + expAcnt := engine.Account{ + ID: "cgrates.org:1001", + BalanceMap: map[string]engine.Balances{ + utils.MetaVoice: { + { + ID: "voiceBalance1", + Value: float64(3 * time.Minute), + RatingSubject: "rs1", + }, + { + ID: "voiceBalance2", + Value: float64(4 * time.Minute), + RatingSubject: "rs2", + }, + }, + }, + } + var acnt engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { + t.Fatal(err) + } else { + expAcnt.UpdateTime = acnt.UpdateTime + expAcnt.BalanceMap[utils.MetaVoice][0].Uuid = acnt.BalanceMap[utils.MetaVoice][0].Uuid + expAcnt.BalanceMap[utils.MetaVoice][1].Uuid = acnt.BalanceMap[utils.MetaVoice][1].Uuid + if !reflect.DeepEqual(acnt, expAcnt) { + t.Fatalf("expected: <%+v>,\nreceived: <%+v>", utils.ToJSON(expAcnt), utils.ToJSON(acnt)) + } + } + + expAcntS := &engine.AccountSummary{ + Tenant: "cgrates.org", + ID: "1001", + BalanceSummaries: engine.BalanceSummaries{ + { + UUID: acnt.BalanceMap[utils.MetaVoice][0].Uuid, + ID: "voiceBalance1", + Type: utils.MetaVoice, + Value: float64(3 * time.Minute), + }, + { + UUID: acnt.BalanceMap[utils.MetaVoice][1].Uuid, + ID: "voiceBalance2", + Type: utils.MetaVoice, + Value: float64(4 * time.Minute), + }, + }, + } + aSummaryBefore = acnt.AsAccountSummary() + if !reflect.DeepEqual(expAcntS, aSummaryBefore) { + t.Fatalf("expected: <%+v>,\nreceived: <%+v>", utils.ToJSON(expAcntS), utils.ToJSON(aSummaryBefore)) + } + + }) + + t.Run("ProcessFirstCDR", func(t *testing.T) { + var reply []*utils.EventWithFlags + err := client.Call(context.Background(), utils.CDRsV2ProcessEvent, + &engine.ArgV1ProcessEvent{ + Flags: []string{utils.MetaRALs}, + CGREvent: utils.CGREvent{ + Tenant: "cgrates.org", + ID: "event1", + Event: map[string]any{ + utils.RunID: "run_1", + utils.CGRID: CGRID, + utils.Tenant: "cgrates.org", + utils.Category: "call", + utils.ToR: utils.MetaVoice, + utils.OriginID: "processCDR1", + utils.OriginHost: "OriginHost1", + utils.RequestType: utils.MetaPostpaid, + utils.AccountField: "1001", + utils.Destination: "1002", + utils.SetupTime: time.Date(2021, time.February, 2, 16, 14, 50, 0, time.UTC), + utils.AnswerTime: time.Date(2021, time.February, 2, 16, 15, 0, 0, time.UTC), + utils.Usage: 4 * time.Minute, + }, + APIOpts: map[string]any{ + utils.MetaRALsDryRun: true, + }, + }, + }, &reply) + if err != nil { + t.Fatal(err) + } + + var cdrs []*engine.CDR + err = client.Call(context.Background(), utils.CDRsV1GetCDRs, &utils.RPCCDRsFilterWithAPIOpts{ + RPCCDRsFilter: &utils.RPCCDRsFilter{ + RunIDs: []string{"run_1"}, + }}, &cdrs) + if err != nil { + t.Fatal(err) + } + if cdrs[0].Usage != 4*time.Minute { + t.Errorf("expected usage to be <%+v>, received <%+v>", 2*time.Minute, cdrs[0].Usage) + } else if cdrs[0].Cost != 2.4 { + t.Errorf("expected cost to be <%+v>, received <%+v>", 0.6, cdrs[0].Cost) + } + }) + + t.Run("CheckAccountBalancesAfterFirstProcessCDR", func(t *testing.T) { + expAcnt := engine.Account{ + ID: "cgrates.org:1001", + BalanceMap: map[string]engine.Balances{ + utils.MetaVoice: { + { + ID: "voiceBalance1", + Value: float64(3 * time.Minute), + RatingSubject: "rs1", + }, + { + ID: "voiceBalance2", + Value: float64(4 * time.Minute), + RatingSubject: "rs2", + }, + }, + }, + } + var acnt engine.Account + attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} + if err := client.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { + t.Fatal(err) + } else { + expAcnt.UpdateTime = acnt.UpdateTime + expAcnt.BalanceMap[utils.MetaVoice][0].Uuid = acnt.BalanceMap[utils.MetaVoice][0].Uuid + expAcnt.BalanceMap[utils.MetaVoice][1].Uuid = acnt.BalanceMap[utils.MetaVoice][1].Uuid + if !reflect.DeepEqual(acnt, expAcnt) { + t.Fatalf("expected: <%+v>,\nreceived: <%+v>", utils.ToJSON(expAcnt), utils.ToJSON(acnt)) + } + } + aSummaryAfter := acnt.AsAccountSummary() + if !reflect.DeepEqual(aSummaryBefore, aSummaryAfter) { + t.Errorf("expected <%+v>, \nreceived <%+v>", utils.ToJSON(aSummaryBefore), utils.ToJSON(aSummaryAfter)) + } + + }) + +} diff --git a/utils/consts.go b/utils/consts.go index 92e54dd52..af0d62c52 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -666,6 +666,7 @@ const ( MetaEvent = "*event" MetaMessage = "*message" MetaDryRun = "*dryrun" + MetaRALsDryRun = "*ralsDryRun" Event = "Event" EmptyString = "" DynamicDataPrefix = "~"