//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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package general_tests import ( "path" "testing" "time" "github.com/cgrates/birpc" "github.com/cgrates/birpc/context" v1 "github.com/cgrates/cgrates/apier/v1" v2 "github.com/cgrates/cgrates/apier/v2" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) var ( tpCfgPath string tpCfgDIR string tpCfg *config.CGRConfig tpRPC *birpc.Client tpLoadInst utils.LoadInstance // Share load information between tests sTestTp = []func(t *testing.T){ testTpInitCfg, testTpResetDataDb, testTpResetStorDb, testTpStartEngine, testTpRpcConn, testTpLoadTariffPlanFromFolder, testTpBalanceCounter, testTpActionTriggers, testTpZeroCost, testTpZeroNegativeCost, testTpExecuteActionCgrRpc, testTpExecuteActionCgrRpcAcc, //testTpExecuteActionCgrRpcCdrStats, testTpCreateExecuteActionMatch, testTpSetRemoveActions, testTpRemoveActionsRefenced, testTpApierResetAccountActionTriggers, testTpStopCgrEngine, } ) func TestTp(t *testing.T) { switch *utils.DBType { case utils.MetaInternal: tpCfgDIR = "tutinternal" case utils.MetaMySQL: tpCfgDIR = "tutmysql" case utils.MetaMongo: tpCfgDIR = "tutmongo" case utils.MetaPostgres: t.SkipNow() default: t.Fatal("Unknown Database type") } for _, stest := range sTestTp { t.Run(tpCfgDIR, stest) } } func testTpInitCfg(t *testing.T) { tpCfgPath = path.Join(*utils.DataDir, "conf", "samples", tpCfgDIR) // Init config first var err error tpCfg, err = config.NewCGRConfigFromPath(tpCfgPath) if err != nil { t.Error(err) } } // Remove data in both rating and accounting db func testTpResetDataDb(t *testing.T) { if err := engine.InitDataDB(tpCfg); err != nil { t.Fatal(err) } } // Wipe out the cdr database func testTpResetStorDb(t *testing.T) { if err := engine.InitStorDb(tpCfg); err != nil { t.Fatal(err) } } // Start CGR Engine func testTpStartEngine(t *testing.T) { if _, err := engine.StopStartEngine(tpCfgPath, 1000); err != nil { t.Fatal(err) } } // Connect rpc client to rater func testTpRpcConn(t *testing.T) { tpRPC = engine.NewRPCClient(t, tpCfg.ListenCfg()) } // Load the tariff plan, creating accounts and their balances func testTpLoadTariffPlanFromFolder(t *testing.T) { attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*utils.DataDir, "tariffplans", "testtp")} if err := tpRPC.Call(context.Background(), utils.APIerSv2LoadTariffPlanFromFolder, attrs, &tpLoadInst); err != nil { t.Error(err) } time.Sleep(time.Duration(*utils.WaitRater) * time.Millisecond) // Give time for scheduler to execute topups } func testTpBalanceCounter(t *testing.T) { tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := &engine.CallDescriptorWithAPIOpts{ CallDescriptor: &engine.CallDescriptor{ Category: "call", Tenant: "cgrates.org", Subject: "1001", Destination: "+49", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(20 * time.Second), }, } var cc engine.CallCost if err := tpRPC.Call(context.Background(), utils.ResponderDebit, cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != 20*time.Second { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error("Got error on APIerSv2.GetAccount: ", err.Error()) } else if acnt.UnitCounters[utils.MetaMonetary][1].Counters[0].Value != 20.0 { t.Errorf("Calling APIerSv2.GetBalance received: %s", utils.ToIJSON(acnt)) } } func testTpActionTriggers(t *testing.T) { var atrs engine.ActionTriggers if err := tpRPC.Call(context.Background(), utils.APIerSv1GetActionTriggers, &v1.AttrGetActionTriggers{GroupIDs: []string{}}, &atrs); err != nil { t.Error("Got error on APIerSv1.GetActionTriggers: ", err.Error()) } else if len(atrs) != 4 { t.Errorf("Calling v1.GetActionTriggers got: %v", atrs) } var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv1SetActionTrigger, engine.AttrSetActionTrigger{ GroupID: "TestATR", UniqueID: "Unique atr id", ActionTrigger: map[string]any{ utils.BalanceID: utils.StringPointer("BID1"), }, }, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Calling v1.SetActionTrigger got: %v", reply) } if err := tpRPC.Call(context.Background(), utils.APIerSv1GetActionTriggers, &v1.AttrGetActionTriggers{GroupIDs: []string{}}, &atrs); err != nil { t.Error(err) } else if len(atrs) != 5 { t.Errorf("Calling v1.GetActionTriggers got: %v", atrs) } if err := tpRPC.Call(context.Background(), utils.APIerSv1GetActionTriggers, &v1.AttrGetActionTriggers{GroupIDs: []string{"TestATR"}}, &atrs); err != nil { t.Error("Got error on APIerSv1.GetActionTriggers: ", err.Error()) } else if len(atrs) != 1 { t.Errorf("Calling v1.GetActionTriggers got: %v", atrs) } if atrs == nil { t.Errorf("Expecting atrs to not be nil") // atrs shoud not be nil so exit function // to avoid nil segmentation fault; // if this happens try to run this test manualy return } if atrs[0].ID != "TestATR" || atrs[0].UniqueID != "Unique atr id" || *atrs[0].Balance.ID != "BID1" { t.Error("Wrong action trigger set: ", utils.ToIJSON(atrs[0])) } } func testTpZeroCost(t *testing.T) { var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1012"} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error("Got error on APIerSv2.GetAccount: ", err.Error()) } if acnt == nil { t.Errorf("Expecting acnt to not be nil") // acnt shoud not be nil so exit function // to avoid nil segmentation fault; // if this happens try to run this test manualy return } balanceValueBefore := acnt.BalanceMap[utils.MetaMonetary][0].Value tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := &engine.CallDescriptorWithAPIOpts{ CallDescriptor: &engine.CallDescriptor{ Category: "call", Tenant: "cgrates.org", Subject: "free", Account: "1012", Destination: "+49", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(20 * time.Second), }, } var cc engine.CallCost if err := tpRPC.Call(context.Background(), utils.ResponderDebit, cd, &cc); err != nil { t.Error("Got error on Responder.Debit: ", err.Error()) } else if cc.GetDuration() != 20*time.Second { t.Errorf("Calling Responder.MaxDebit got callcost: %v", utils.ToIJSON(cc)) } if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error("Got error on APIerSv2.GetAccount: ", err.Error()) } else if acnt.BalanceMap[utils.MetaMonetary][0].Value != balanceValueBefore { t.Errorf("Calling APIerSv2.GetAccount received: %s", utils.ToIJSON(acnt)) } } func testTpZeroNegativeCost(t *testing.T) { tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := &engine.CallDescriptorWithAPIOpts{ CallDescriptor: &engine.CallDescriptor{ Category: "call", Tenant: "cgrates.org", Subject: "free", Account: "1013", Destination: "+4915", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(20 * time.Second), }, } var cc engine.CallCost if err := tpRPC.Call(context.Background(), utils.ResponderDebit, cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != 20*time.Second { t.Errorf("Calling Responder.MaxDebit got callcost: %v", utils.ToIJSON(cc)) } var acnt engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1013"} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error("Got error on APIerSv2.GetAccount: ", err.Error()) } else if acnt.BalanceMap[utils.MetaVoice][0].Value != 100.0 { t.Errorf("Calling APIerSv2.GetAccount received: %s", utils.ToIJSON(acnt)) } } func testTpExecuteActionCgrRpc(t *testing.T) { var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv1ExecuteAction, utils.AttrExecuteAction{ActionsId: "RPC"}, &reply); err != nil { t.Error("Got error on APIerSv1.ExecuteAction: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling ExecuteAction got reply: %s", reply) } var acnt engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1013"} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error("Got error on APIerSv2.GetAccount: ", err.Error()) } } func testTpExecuteActionCgrRpcAcc(t *testing.T) { var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv1ExecuteAction, utils.AttrExecuteAction{ Tenant: "cgrates.org", Account: "1016", ActionsId: "RPC_DEST", }, &reply); err != nil { t.Error("Got error on APIerSv1.ExecuteAction: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling ExecuteAction got reply: %s", reply) } var dests []*engine.Destination attrs := &v2.AttrGetDestinations{DestinationIDs: []string{}} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetDestinations, attrs, &dests); err != nil { t.Error("Got error on APIerSv2.GetDestinations: ", err.Error()) } } func testTpCreateExecuteActionMatch(t *testing.T) { var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv2SetActions, &utils.AttrSetActions{ ActionsId: "PAYMENT_2056bd2fe137082970f97102b64e42fd", Actions: []*utils.TPAction{ { BalanceType: "*monetary", Identifier: "*topup", RatingSubject: "", Units: "10.500000", Weight: 10, }, }, }, &reply); err != nil { t.Error("Got error on APIerSv2.SetActions: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling APIerSv2.SetActions got reply: %s", reply) } if err := tpRPC.Call(context.Background(), utils.APIerSv1ExecuteAction, utils.AttrExecuteAction{ Tenant: "cgrates.org", Account: "1015", ActionsId: "PAYMENT_2056bd2fe137082970f97102b64e42fd", }, &reply); err != nil { t.Error("Got error on APIerSv1.ExecuteAction: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling ExecuteAction got reply: %s", reply) } if err := tpRPC.Call(context.Background(), utils.APIerSv1ExecuteAction, utils.AttrExecuteAction{ Tenant: "cgrates.org", Account: "1015", ActionsId: "PAYMENT_2056bd2fe137082970f97102b64e42fd", }, &reply); err != nil { t.Error("Got error on APIerSv1.ExecuteAction: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling ExecuteAction got reply: %s", reply) } var acnt engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1015"} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error("Got error on APIerSv2.GetAccount: ", err.Error()) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 1 || acnt.BalanceMap[utils.MetaMonetary].GetTotalValue() != 21 { t.Error("error matching previous created balance: ", utils.ToIJSON(acnt.BalanceMap)) } } func testTpSetRemoveActions(t *testing.T) { var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv2SetActions, &utils.AttrSetActions{ ActionsId: "TO_BE_DELETED", Actions: []*utils.TPAction{ { BalanceType: "*monetary", Identifier: "*topup", RatingSubject: "", Units: "10.500000", Weight: 10, }, }, }, &reply); err != nil { t.Error("Got error on APIerSv2.SetActions: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling APIerSv2.SetActions got reply: %s", reply) } actionsMap := make(map[string]engine.Actions) if err := tpRPC.Call(context.Background(), utils.APIerSv2GetActions, &v2.AttrGetActions{ ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, }, &actionsMap); err != nil { t.Error("Got error on APIerSv2.GetActions: ", err.Error()) } else if len(actionsMap) != 1 { t.Errorf("Calling APIerSv2.GetActions got reply: %s", utils.ToIJSON(actionsMap)) } if err := tpRPC.Call(context.Background(), utils.APIerSv2RemoveActions, v1.AttrRemoveActions{ ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, }, &reply); err != nil { t.Error("Got error on APIerSv2.RemoveActions: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling APIerSv2.RemoveActions got reply: %s", reply) } if err := tpRPC.Call(context.Background(), utils.APIerSv2GetActions, &v2.AttrGetActions{ ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, }, &actionsMap); err == nil { t.Error("no error on APIerSv2.GetActions: ", err) } } func testTpRemoveActionsRefenced(t *testing.T) { actionsMap := make(map[string]engine.Actions) if err := tpRPC.Call(context.Background(), utils.APIerSv2GetActions, &v2.AttrGetActions{ ActionIDs: []string{"TOPUP_VOICE"}, }, &actionsMap); err != nil { t.Error("Got error on APIerSv2.GetActions: ", err.Error()) } else if len(actionsMap) != 1 { t.Errorf("Calling APIerSv2.GetActions got reply: %s", utils.ToIJSON(actionsMap)) } var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv2RemoveActions, v1.AttrRemoveActions{ ActionIDs: []string{"TOPUP_VOICE"}, }, &reply); err != nil { t.Error("Error on APIerSv2.RemoveActions: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling APIerSv2.RemoveActions got reply: %s", reply) } /* if err := tpRPC.Call(context.Background(),utils.APIerSv2GetActions, v2.AttrGetActions{ ActionIDs: []string{"PAYMENT_2056bd2fe137082970f97102b64e42fd"}, }, &actionsMap); err == nil { t.Error("no error on APIerSv2.GetActions: ", err) } */ } func testTpApierResetAccountActionTriggers(t *testing.T) { var acnt engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1005"} if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error(err) } else if acnt.ActionTriggers[0].Executed != true { t.Skip("Skipping test since Executed is not yet true") } var reply string if err := tpRPC.Call(context.Background(), utils.APIerSv2ResetAccountActionTriggers, v1.AttrResetAccountActionTriggers{ Tenant: "cgrates.org", Account: "1005", GroupID: "STANDARD_TRIGGERS", Executed: true, }, &reply); err != nil { t.Error("Error on APIerSv2.ResetAccountActionTriggers: ", err.Error()) } else if reply != utils.OK { t.Errorf("Calling APIerSv2.ResetAccountActionTriggers got reply: %s", reply) } if err := tpRPC.Call(context.Background(), utils.APIerSv2GetAccount, attrs, &acnt); err != nil { t.Error(err) } else if acnt.ActionTriggers[0].Executed == false { t.Errorf("wrong action trigger executed flag: %s", utils.ToIJSON(acnt.ActionTriggers)) } } func testTpStopCgrEngine(t *testing.T) { if err := engine.KillEngine(100); err != nil { t.Error(err) } }