diff --git a/data/conf/samples/session_volume_discount_internal/cgrates.json b/data/conf/samples/session_volume_discount_internal/cgrates.json new file mode 100644 index 000000000..7f0ed11da --- /dev/null +++ b/data/conf/samples/session_volume_discount_internal/cgrates.json @@ -0,0 +1,41 @@ +{ + // CGRateS Configuration file + // + // Used for sessions/sessions_volume_discount_it_test.go + + "general": { + "log_level": 7, + }, + + "data_db": { + "db_type": "*internal", + }, + + "stor_db": { + "db_type": "*internal", + }, + + "sessions": { + "enabled": true, + "routes_conns": ["*internal"], + }, + + "loaders": [ + { + "id": "*default", + "enabled": true, + "tenant": "cgrates.org", + "tp_in_dir": "/usr/share/cgrates/tariffplans/sessions_discount_volume", + "tp_out_dir": "", + "lockfile_path": "", + }, + ], + + "routes": { + "enabled": true, + }, + + "admins": { + "enabled": true, + }, +} \ No newline at end of file diff --git a/data/tariffplans/sessions_discount_volume/RateProfiles.csv b/data/tariffplans/sessions_discount_volume/RateProfiles.csv new file mode 100644 index 000000000..2b4303620 --- /dev/null +++ b/data/tariffplans/sessions_discount_volume/RateProfiles.csv @@ -0,0 +1,16 @@ +#Tenant,ID,FilterIDs,Weights,MinCost,MaxCost,MaxCostStrategy,RateID,RateFilterIDs,RateActivationStart,RateWeights,RateBlocker,RateIntervalStart,RateFixedFee,RateRecurrentFee,RateUnit,RateIncrement + +cgrates.org,RP1,,,,,,RT_10,*prefix:~*req.Destination:10,,;10,,,,0.03,1m,1s +cgrates.org,RP1,,,,,,RT_20,*prefix:~*req.Destination:20,,;10,,,,0.01,1m,1s +cgrates.org,RP1,,,,,,RT_DFLT,,,,,,,0.92,1m,1s + +cgrates.org,RP2,,,,,,RT_100,*prefix:~*req.Destination:100,,;10,,,,0.92,1m,1s +cgrates.org,RP2,,,,,,RT_DFLT,,,,,,,0.95,1m,1s + +cgrates.org,RP3,,,,,,RT_1,*prefix:~*req.Destination:1,,;10,,,,0.01,1m,1s +cgrates.org,RP3,,,,,,RT_DFLT,,,,,,,0.97,1m,1s + +cgrates.org,RP4,,,,,,RT_200,*prefix:~*req.Destination:200,,,,,,0.02,1m,1s +cgrates.org,RP4,,,,,,RT_1002,*prefix:~*req.Destination:1002,,;10,,,,0.09,1m,1s +cgrates.org,RP4,,,,,,RT_6,*prefix:~*req.Destination:6,,;10,,,,0.09,1m,1s +cgrates.org,RP4,,,,,,RT_DFLT,,,,,,,0.91,1m,1s diff --git a/data/tariffplans/sessions_discount_volume/Routes.csv b/data/tariffplans/sessions_discount_volume/Routes.csv new file mode 100644 index 000000000..4ca996a8a --- /dev/null +++ b/data/tariffplans/sessions_discount_volume/Routes.csv @@ -0,0 +1,14 @@ +#Tenant,ID,FilterIDs,Weight,Sorting,SortingParameters,RouteID,RouteFilterIDs,RouteAccountIDs,RouteRateProfileIDs,RouteResourceIDs,RouteStatIDs,RouteWeight,RouteBlocker,RouteParameters + +cgrates.org,LC1,,,*lc,,,,,,,,,, +cgrates.org,LC1,,,,,supplier1,,,RP1,,,,, +cgrates.org,LC1,,,,,supplier2,,,RP2,,,,, +cgrates.org,LC1,,,,,supplier3,,,RP3,,,,, +cgrates.org,LC1,,,,,supplier4,,,RP4,,,,, + + +#cgrates.org,DEFAULT_ROUTES,,,*weight,,,,,,,,,, +#cgrates.org,DEFAULT_ROUTES,,,,,supplier1,,,,,,10,, +#cgrates.org,DEFAULT_ROUTES,,,,,supplier2,,,,,,20,, +#cgrates.org,DEFAULT_ROUTES,,,,,supplier3,,,,,,40,, +#cgrates.org,DEFAULT_ROUTES,,,,,supplier4,,,,,,35,, diff --git a/sessions/session_volume_discount_it_test.go b/sessions/session_volume_discount_it_test.go new file mode 100644 index 000000000..2c6934ea0 --- /dev/null +++ b/sessions/session_volume_discount_it_test.go @@ -0,0 +1,382 @@ +//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 sessions + +import ( + "errors" + "path" + "reflect" + "sort" + "testing" + "time" + + "github.com/cgrates/birpc" + "github.com/cgrates/birpc/context" + "github.com/cgrates/birpc/jsonrpc" + + "github.com/cgrates/cgrates/apis" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/loaders" + "github.com/cgrates/cgrates/utils" +) + +var ( + tSessVolDiscCfgPath string + tSessVolDiscCfgDIR string + tSessVolDiscCfg *config.CGRConfig + tSessVolDiscBiRPC *birpc.Client + + tSessVolDiscITtests = []func(t *testing.T){ + testSessVolDiscInitCfg, + testSessVolDiscResetDataDb, + testSessVolDiscResetStorDb, + testSessVolDiscStartEngine, + testSessVolDiscApierRpcConn, + testSessVolDiscLoadersLoad, + testSessVolDiscCheckRoutesAndRateProfiles, + testSessVolDiscSetAccounts, + testSessVolDiscStopCgrEngine, + } +) + +func newBiRPCClient(cfg *config.ListenCfg) (c *birpc.Client, err error) { + switch *encoding { + case utils.MetaJSON: + return jsonrpc.Dial(utils.TCP, cfg.RPCJSONListen) + case utils.MetaGOB: + return birpc.Dial(utils.TCP, cfg.RPCGOBListen) + default: + return nil, errors.New("UNSUPPORTED_RPC") + } +} + +func TestSessVolDiscount(t *testing.T) { + switch *dbType { + case utils.MetaInternal: + tSessVolDiscCfgDIR = "session_volume_discount_internal" + case utils.MetaMySQL: + tSessVolDiscCfgDIR = "session_volume_discount_mysql" + case utils.MetaMongo: + tSessVolDiscCfgDIR = "session_volume_discount_mongo" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + for _, stest := range tSessVolDiscITtests { + t.Run(tSessVolDiscCfgDIR, stest) + } +} + +// Init config firs +func testSessVolDiscInitCfg(t *testing.T) { + tSessVolDiscCfgPath = path.Join(*dataDir, "conf", "samples", tSessVolDiscCfgDIR) + var err error + tSessVolDiscCfg, err = config.NewCGRConfigFromPath(context.Background(), tSessVolDiscCfgPath) + if err != nil { + t.Error(err) + } +} + +// Remove data in both rating and accounting db +func testSessVolDiscResetDataDb(t *testing.T) { + if err := engine.InitDataDB(tSessVolDiscCfg); err != nil { + t.Fatal(err) + } +} + +// Wipe out the cdr database +func testSessVolDiscResetStorDb(t *testing.T) { + if err := engine.InitStorDB(tSessVolDiscCfg); err != nil { + t.Fatal(err) + } +} + +// Start CGR Engine +func testSessVolDiscStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(tSessVolDiscCfgPath, *waitRater); err != nil { + t.Fatal(err) + } +} + +// Connect rpc client to rater +func testSessVolDiscApierRpcConn(t *testing.T) { + var err error + tSessVolDiscBiRPC, err = newBiRPCClient(tSessVolDiscCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal(err) + } +} + +func testSessVolDiscLoadersLoad(t *testing.T) { + var reply string + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.LoaderSv1Load, + &loaders.ArgsProcessFolder{ + // StopOnError: true, + Caching: utils.StringPointer(utils.MetaReload), + }, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } +} + +func testSessVolDiscCheckRoutesAndRateProfiles(t *testing.T) { + expRp := &utils.RateProfile{ + Tenant: "cgrates.org", + ID: "RP1", + FilterIDs: []string{}, + MinCost: utils.NewDecimal(0, 0), + MaxCost: utils.NewDecimal(0, 0), + Rates: map[string]*utils.Rate{ + "RT_10": { + ID: "RT_10", + FilterIDs: []string{"*prefix:~*req.Destination:10"}, + IntervalRates: []*utils.IntervalRate{ + { + IntervalStart: utils.NewDecimal(0, 0), + FixedFee: utils.NewDecimal(0, 0), + RecurrentFee: utils.NewDecimal(3, 2), + Unit: utils.NewDecimal(60000000000, 0), + Increment: utils.NewDecimal(1000000000, 0), + }, + }, + }, + "RT_20": { + ID: "RT_20", + FilterIDs: []string{"*prefix:~*req.Destination:20"}, + IntervalRates: []*utils.IntervalRate{ + { + IntervalStart: utils.NewDecimal(0, 0), + FixedFee: utils.NewDecimal(0, 0), + RecurrentFee: utils.NewDecimal(1, 2), + Unit: utils.NewDecimal(60000000000, 0), + Increment: utils.NewDecimal(1000000000, 0), + }, + }, + }, + "RT_DFLT": { + ID: "RT_DFLT", + IntervalRates: []*utils.IntervalRate{ + { + IntervalStart: utils.NewDecimal(0, 0), + FixedFee: utils.NewDecimal(0, 0), + RecurrentFee: utils.NewDecimal(92, 2), + Unit: utils.NewDecimal(60000000000, 0), + Increment: utils.NewDecimal(1000000000, 0), + }, + }, + }, + }, + } + var reply *utils.RateProfile + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1GetRateProfile, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "RP1", + }, + }, &reply); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(reply, expRp) { + t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expRp), utils.ToJSON(reply)) + } + + expRoutePrf := &engine.APIRouteProfile{ + Tenant: "cgrates.org", + ID: "LC1", + FilterIDs: []string{}, + Sorting: "*lc", + SortingParameters: []string{}, + Routes: []*engine.ExternalRoute{ + { + ID: "supplier1", + RateProfileIDs: []string{"RP1"}, + }, + { + ID: "supplier2", + RateProfileIDs: []string{"RP2"}, + }, + { + ID: "supplier3", + RateProfileIDs: []string{"RP3"}, + }, + { + ID: "supplier4", + RateProfileIDs: []string{"RP4"}, + }, + }, + } + var result *engine.APIRouteProfile + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1GetRouteProfile, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "LC1", + }, + }, &result); err != nil { + t.Error(err) + } else { + sort.Slice(result.Routes, func(i, j int) bool { + return result.Routes[i].ID < result.Routes[j].ID + }) + if !reflect.DeepEqual(result, expRoutePrf) { + t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expRoutePrf), utils.ToJSON(result)) + } + } +} + +func testSessVolDiscSetAccounts(t *testing.T) { + accPrf1 := &apis.APIAccountWithAPIOpts{ + APIAccount: &utils.APIAccount{ + Tenant: "cgrates.org", + ID: "ACC1", + FilterIDs: []string{"*string:~*req.Account:1"}, + Balances: map[string]*utils.APIBalance{ + "AbstractBalance1": { + ID: "AbstractBalance1", + Type: utils.MetaAbstract, + Units: float64(40 * time.Second), + CostIncrements: []*utils.APICostIncrement{ + { + Increment: utils.Float64Pointer(float64(time.Second)), + FixedFee: utils.Float64Pointer(float64(0)), + RecurrentFee: utils.Float64Pointer(float64(1)), + }, + }, + }, + }, + Weights: ";10", + }, + APIOpts: nil, + } + accPrf2 := &apis.APIAccountWithAPIOpts{ + APIAccount: &utils.APIAccount{ + Tenant: "cgrates.org", + ID: "ACC2", + FilterIDs: []string{"*string:~*req.Account:2"}, + Balances: map[string]*utils.APIBalance{ + "AbstractBalance2": { + ID: "AbstractBalance2", + Type: utils.MetaAbstract, + Units: float64(80 * time.Second), + CostIncrements: []*utils.APICostIncrement{ + { + Increment: utils.Float64Pointer(float64(time.Minute)), + FixedFee: utils.Float64Pointer(float64(0)), + RecurrentFee: utils.Float64Pointer(float64(1)), + }, + }, + }, + }, + Weights: ";10", + }, + APIOpts: nil, + } + accPrf3 := &apis.APIAccountWithAPIOpts{ + APIAccount: &utils.APIAccount{ + Tenant: "cgrates.org", + ID: "ACC3", + FilterIDs: []string{"*string:~*req.Account:3"}, + Balances: map[string]*utils.APIBalance{ + "AbstractBalance3": { + ID: "AbstractBalance3", + Type: utils.MetaAbstract, + Units: float64(120 * time.Second), + UnitFactors: []*utils.APIUnitFactor{ + { + Factor: 100, + }, + }, + CostIncrements: []*utils.APICostIncrement{ + { + Increment: utils.Float64Pointer(float64(time.Minute)), + FixedFee: utils.Float64Pointer(float64(0)), + RecurrentFee: utils.Float64Pointer(float64(1)), + }, + }, + }, + }, + Weights: ";10", + }, + APIOpts: nil, + } + accPrf4 := &apis.APIAccountWithAPIOpts{ + APIAccount: &utils.APIAccount{ + Tenant: "cgrates.org", + ID: "ACC3", + FilterIDs: []string{"*string:~*req.Account:4"}, + Balances: map[string]*utils.APIBalance{ + "AbstractBalance4": { + ID: "AbstractBalance4", + Type: utils.MetaAbstract, + Units: float64(30 * time.Second), + UnitFactors: []*utils.APIUnitFactor{ + { + Factor: 100, + }, + }, + CostIncrements: []*utils.APICostIncrement{ + { + Increment: utils.Float64Pointer(float64(time.Second)), + FixedFee: utils.Float64Pointer(float64(0.01)), + RecurrentFee: utils.Float64Pointer(float64(2)), + }, + }, + }, + }, + Weights: ";10", + }, + APIOpts: nil, + } + var reply string + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1SetAccount, + accPrf1, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error(err) + } + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1SetAccount, + accPrf2, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error(err) + } + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1SetAccount, + accPrf3, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error(err) + } + if err := tSessVolDiscBiRPC.Call(context.Background(), utils.AdminSv1SetAccount, + accPrf4, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error(err) + } +} + +func testSessVolDiscStopCgrEngine(t *testing.T) { + if err := engine.KillEngine(*waitRater); err != nil { + t.Error(err) + } +}