//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 ( "testing" "time" "github.com/cgrates/birpc/context" v1 "github.com/cgrates/cgrates/apier/v1" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) func TestBalanceTimings(t *testing.T) { switch *utils.DBType { case utils.MetaInternal: case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres: t.SkipNow() default: t.Fatal("unsupported dbtype value") } content := `{ "general": { "log_level": 7, "reply_timeout": "50s" }, "listen": { "rpc_json": ":2012", "rpc_gob": ":2013", "http": ":2080" }, "rals": { "enabled": true, }, "schedulers": { "enabled": true, "cdrs_conns": ["*internal"], }, "cdrs": { "enabled": true, "chargers_conns":["*internal"], "rals_conns": ["*localhost"], }, "attributes": { "enabled": true, "apiers_conns": ["*localhost"] }, "chargers": { "enabled": true, "attributes_conns": ["*internal"] }, "sessions": { "enabled": true, "attributes_conns": ["*internal"], "rals_conns": ["*internal"], "cdrs_conns": ["*internal"], "chargers_conns": ["*internal"] }, "apiers": { "enabled": true, "scheduler_conns": ["*internal"] }, } ` tpFiles := map[string]string{ utils.AccountActionsCsv: `#Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled cgrates.org,1001,AP_PACKAGE_10,,, cgrates.org,1002,AP_PACKAGE_10,,,`, utils.ActionPlansCsv: `#Id,ActionsId,TimingId,Weight AP_PACKAGE_10,ACT_TOPUP_RST_10,*asap,10`, utils.ActionsCsv: `#ActionsId[0],Action[1],ExtraParameters[2],Filter[3],BalanceId[4],BalanceType[5],Categories[6],DestinationIds[7],RatingSubject[8],SharedGroup[9],ExpiryTime[10],TimingIds[11],Units[12],BalanceWeight[13],BalanceBlocker[14],BalanceDisabled[15],Weight[16] ACT_TOPUP_RST_10,*topup_reset,,,bal1,*monetary,,*any,,,*unlimited,HALF1,10,10,false,false,10 ACT_TOPUP_RST_10,*topup_reset,,,bal2,*monetary,,*any,,,*unlimited,HALF2,10,10,false,false,99`, utils.ChargersCsv: `#Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight cgrates.org,DEFAULT,,,*default,*none,0 cgrates.org,Raw,,,*raw,*constant:*req.RequestType:*none,0`, utils.DestinationRatesCsv: `#Id,DestinationId,RatesTag,RoundingMethod,RoundingDecimals,MaxCost,MaxCostStrategy DR_1002_20CNT,DST_1002,RT_20CNT,*up,4,0,`, utils.DestinationsCsv: `#Id,Prefix DST_1002,1002 DST_1001,1001`, utils.RatesCsv: `#Id,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart RT_20CNT,0.4,0.2,60s,60s,0s RT_20CNT,0,0.1,60s,1s,60s`, utils.RatingPlansCsv: `#Id,DestinationRatesId,TimingTag,Weight RP_1001,DR_1002_20CNT,*any,10`, utils.RatingProfilesCsv: `#Tenant,Category,Subject,ActivationTime,RatingPlanId,RatesFallbackSubject cgrates.org,call,1001,2014-01-14T00:00:00Z,RP_1001,`, utils.TimingsCsv: `#Tag,Years,Months,MonthDays,WeekDays,Time HALF1,*any,*any,*any,*any,00:00:00;11:59:59 HALF2,*any,*any,*any,*any,12:00:00;23:59:59`, } ng := engine.TestEngine{ ConfigJSON: content, TpFiles: tpFiles, } client, _ := ng.Run(t) time.Sleep(50 * time.Millisecond) t.Run("TimingIsActiveAt", func(t *testing.T) { var reply *bool params := &v1.TimeParams{ TimingID: "HALF1", Time: "2024-09-17T10:00:00Z", } if err := client.Call(context.Background(), utils.APIerSV1TimingIsActiveAt, params, &reply); err != nil { t.Fatal(err) } else if !*reply { t.Errorf("expected TimingID to be Active") } params = &v1.TimeParams{ TimingID: "HALF2", Time: "2024-09-17T10:00:00Z", } if err := client.Call(context.Background(), utils.APIerSV1TimingIsActiveAt, params, &reply); err != nil { t.Fatal(err) } else if *reply { t.Errorf("expected TimingID to be inactive") } params = &v1.TimeParams{ TimingID: "HALF2", Time: "2024-09-17T12:00:00Z", } if err := client.Call(context.Background(), utils.APIerSV1TimingIsActiveAt, params, &reply); err != nil { t.Fatal(err) } else if !*reply { t.Errorf("expected TimingID to be Active") } }) t.Run("GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) t.Run("Half2ProcessExternalCDR", func(t *testing.T) { var reply string args := &engine.ExternalCDRWithAPIOpts{ ExternalCDR: &engine.ExternalCDR{ OriginID: "TestBalanceTimings", ToR: utils.MetaVoice, RequestType: utils.MetaPrepaid, AnswerTime: "2024-08-04T15:00:07Z", SetupTime: "2024-08-04T15:00:00Z", Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1002", Usage: "1", }, } if err := client.Call(context.Background(), utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Received: %s", reply) } time.Sleep(100 * time.Millisecond) }) t.Run("Half2GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) t.Run("Half1ProcessExternalCDR", func(t *testing.T) { var reply string args := &engine.ExternalCDRWithAPIOpts{ ExternalCDR: &engine.ExternalCDR{ OriginID: "TestBalanceTimings2", ToR: utils.MetaVoice, RequestType: utils.MetaPrepaid, AnswerTime: "2024-08-04T11:00:07Z", SetupTime: "2024-08-04T11:00:00Z", Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1002", Usage: "1", }, } if err := client.Call(context.Background(), utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Received: %s", reply) } time.Sleep(100 * time.Millisecond) }) t.Run("Half1GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) } func TestBalanceTimingsSetActions(t *testing.T) { switch *utils.DBType { case utils.MetaInternal: case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres: t.SkipNow() default: t.Fatal("unsupported dbtype value") } content := `{ "general": { "log_level": 7, "reply_timeout": "50s" }, "listen": { "rpc_json": ":2012", "rpc_gob": ":2013", "http": ":2080" }, "rals": { "enabled": true, }, "schedulers": { "enabled": true, "cdrs_conns": ["*internal"], }, "cdrs": { "enabled": true, "chargers_conns":["*internal"], "rals_conns": ["*localhost"], }, "attributes": { "enabled": true, "apiers_conns": ["*localhost"] }, "chargers": { "enabled": true, "attributes_conns": ["*internal"] }, "sessions": { "enabled": true, "attributes_conns": ["*internal"], "rals_conns": ["*internal"], "cdrs_conns": ["*internal"], "chargers_conns": ["*internal"] }, "apiers": { "enabled": true, "scheduler_conns": ["*internal"] }, } ` tpFiles := map[string]string{ utils.AccountActionsCsv: `#Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled cgrates.org,1001,AP_PACKAGE_10,,, cgrates.org,1002,AP_PACKAGE_10,,,`, utils.ActionPlansCsv: `#Id,ActionsId,TimingId,Weight AP_PACKAGE_10,ACT_TOPUP_RST_10,*asap,10`, utils.ChargersCsv: `#Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight cgrates.org,DEFAULT,,,*default,*none,0 cgrates.org,Raw,,,*raw,*constant:*req.RequestType:*none,0`, utils.DestinationRatesCsv: `#Id,DestinationId,RatesTag,RoundingMethod,RoundingDecimals,MaxCost,MaxCostStrategy DR_1002_20CNT,DST_1002,RT_20CNT,*up,4,0,`, utils.DestinationsCsv: `#Id,Prefix DST_1002,1002 DST_1001,1001`, utils.RatesCsv: `#Id,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart RT_20CNT,0.4,0.2,60s,60s,0s RT_20CNT,0,0.1,60s,1s,60s`, utils.RatingPlansCsv: `#Id,DestinationRatesId,TimingTag,Weight RP_1001,DR_1002_20CNT,*any,10`, utils.RatingProfilesCsv: `#Tenant,Category,Subject,ActivationTime,RatingPlanId,RatesFallbackSubject cgrates.org,call,1001,2014-01-14T00:00:00Z,RP_1001,`, } ng := engine.TestEngine{ ConfigJSON: content, } client, _ := ng.Run(t) time.Sleep(50 * time.Millisecond) t.Run("SetTimings", func(t *testing.T) { timing := &utils.TPTimingWithAPIOpts{ TPTiming: &utils.TPTiming{ ID: "HALF1", StartTime: "00:00:00", EndTime: "11:59:59", }, } var reply string if err := client.Call(context.Background(), utils.APIerSv1SetTiming, timing, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Error("Unexpected reply returned", reply) } timing2 := &utils.TPTimingWithAPIOpts{ TPTiming: &utils.TPTiming{ ID: "HALF2", StartTime: "12:00:00", EndTime: "23:59:59", }, } if err := client.Call(context.Background(), utils.APIerSv1SetTiming, timing2, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Error("Unexpected reply returned", reply) } }) t.Run("Sv1SetActions", func(t *testing.T) { attrs1 := &v1.V1AttrSetActions{ ActionsId: "ACT_TOPUP_RST_10", Actions: []*v1.V1TPAction{ { Identifier: utils.MetaTopUpReset, BalanceId: "bal1", TimingTags: "HALF1", BalanceType: utils.MetaMonetary, Units: 10.0, ExpiryTime: utils.MetaUnlimited, Weight: 10.0, }, { Identifier: utils.MetaTopUpReset, BalanceId: "bal2", TimingTags: "HALF2", BalanceType: utils.MetaMonetary, Units: 10.0, ExpiryTime: utils.MetaUnlimited, Weight: 99.0, }, }, Overwrite: true, } var reply string if err := client.Call(context.Background(), utils.APIerSv1SetActions, &attrs1, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Unexpected reply returned: %s", reply) } // LoadTPFromFolder engine.LoadCSVs(t, client, "", tpFiles) attrsEA := &utils.AttrExecuteAction{Tenant: "cgrates.org", Account: "1001", ActionsId: "ACT_TOPUP_RST_10"} if err := client.Call(context.Background(), utils.APIerSv1ExecuteAction, attrsEA, &reply); err != nil { t.Errorf("APIerSv1ExecuteAction failed unexpectedly: %v", err) } }) t.Run("GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) t.Run("Half2ProcessExternalCDR", func(t *testing.T) { var reply string args := &engine.ExternalCDRWithAPIOpts{ ExternalCDR: &engine.ExternalCDR{ OriginID: "TestBalanceTimings", ToR: utils.MetaVoice, RequestType: utils.MetaPrepaid, AnswerTime: "2024-08-04T15:00:07Z", SetupTime: "2024-08-04T15:00:00Z", Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1002", Usage: "1", }, } if err := client.Call(context.Background(), utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Received: %s", reply) } time.Sleep(100 * time.Millisecond) }) t.Run("Half2GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) t.Run("Half1ProcessExternalCDR", func(t *testing.T) { var reply string args := &engine.ExternalCDRWithAPIOpts{ ExternalCDR: &engine.ExternalCDR{ OriginID: "TestBalanceTimings2", ToR: utils.MetaVoice, RequestType: utils.MetaPrepaid, AnswerTime: "2024-08-04T11:00:07Z", SetupTime: "2024-08-04T11:00:00Z", Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1002", Usage: "1", }, } if err := client.Call(context.Background(), utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Received: %s", reply) } time.Sleep(100 * time.Millisecond) }) t.Run("Half1GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) } func TestBalanceTimingsV2SetActions(t *testing.T) { switch *utils.DBType { case utils.MetaInternal: case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres: t.SkipNow() default: t.Fatal("unsupported dbtype value") } content := `{ "general": { "log_level": 7, "reply_timeout": "50s" }, "listen": { "rpc_json": ":2012", "rpc_gob": ":2013", "http": ":2080" }, "rals": { "enabled": true, }, "schedulers": { "enabled": true, "cdrs_conns": ["*internal"], }, "cdrs": { "enabled": true, "chargers_conns":["*internal"], "rals_conns": ["*localhost"], }, "attributes": { "enabled": true, "apiers_conns": ["*localhost"] }, "chargers": { "enabled": true, "attributes_conns": ["*internal"] }, "sessions": { "enabled": true, "attributes_conns": ["*internal"], "rals_conns": ["*internal"], "cdrs_conns": ["*internal"], "chargers_conns": ["*internal"] }, "apiers": { "enabled": true, "scheduler_conns": ["*internal"] }, } ` tpFiles := map[string]string{ utils.AccountActionsCsv: `#Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled cgrates.org,1001,AP_PACKAGE_10,,, cgrates.org,1002,AP_PACKAGE_10,,,`, utils.ActionPlansCsv: `#Id,ActionsId,TimingId,Weight AP_PACKAGE_10,ACT_TOPUP_RST_10,*asap,10`, utils.ChargersCsv: `#Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight cgrates.org,DEFAULT,,,*default,*none,0 cgrates.org,Raw,,,*raw,*constant:*req.RequestType:*none,0`, utils.DestinationRatesCsv: `#Id,DestinationId,RatesTag,RoundingMethod,RoundingDecimals,MaxCost,MaxCostStrategy DR_1002_20CNT,DST_1002,RT_20CNT,*up,4,0,`, utils.DestinationsCsv: `#Id,Prefix DST_1002,1002 DST_1001,1001`, utils.RatesCsv: `#Id,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart RT_20CNT,0.4,0.2,60s,60s,0s RT_20CNT,0,0.1,60s,1s,60s`, utils.RatingPlansCsv: `#Id,DestinationRatesId,TimingTag,Weight RP_1001,DR_1002_20CNT,*any,10`, utils.RatingProfilesCsv: `#Tenant,Category,Subject,ActivationTime,RatingPlanId,RatesFallbackSubject cgrates.org,call,1001,2014-01-14T00:00:00Z,RP_1001,`, } ng := engine.TestEngine{ ConfigJSON: content, } client, _ := ng.Run(t) time.Sleep(50 * time.Millisecond) t.Run("SetTimings", func(t *testing.T) { timing := &utils.TPTimingWithAPIOpts{ TPTiming: &utils.TPTiming{ ID: "HALF1", StartTime: "00:00:00", EndTime: "11:59:59", }, } var reply string if err := client.Call(context.Background(), utils.APIerSv1SetTiming, timing, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Error("Unexpected reply returned", reply) } timing2 := &utils.TPTimingWithAPIOpts{ TPTiming: &utils.TPTiming{ ID: "HALF2", StartTime: "12:00:00", EndTime: "23:59:59", }, } if err := client.Call(context.Background(), utils.APIerSv1SetTiming, timing2, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Error("Unexpected reply returned", reply) } }) t.Run("Sv2SetActions", func(t *testing.T) { attrs1 := &utils.AttrSetActions{ ActionsId: "ACT_TOPUP_RST_10", Actions: []*utils.TPAction{ { Identifier: utils.MetaTopUpReset, BalanceId: "bal1", DestinationIds: utils.MetaAny, TimingTags: "HALF1", BalanceType: utils.MetaMonetary, Units: "10", ExpiryTime: utils.MetaUnlimited, Weight: 10.0, }, { Identifier: utils.MetaTopUpReset, BalanceId: "bal2", DestinationIds: utils.MetaAny, TimingTags: "HALF2", BalanceType: utils.MetaMonetary, Units: "10", ExpiryTime: utils.MetaUnlimited, Weight: 99.0, }, }, } var reply string if err := client.Call(context.Background(), utils.APIerSv2SetActions, &attrs1, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Unexpected reply returned: %s", reply) } // LoadTPFromFolder engine.LoadCSVs(t, client, "", tpFiles) attrsEA := &utils.AttrExecuteAction{Tenant: "cgrates.org", Account: "1001", ActionsId: "ACT_TOPUP_RST_10"} if err := client.Call(context.Background(), utils.APIerSv1ExecuteAction, attrsEA, &reply); err != nil { t.Errorf("APIerSv1ExecuteAction failed unexpectedly: %v", err) } }) t.Run("GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) t.Run("Half2ProcessExternalCDR", func(t *testing.T) { var reply string args := &engine.ExternalCDRWithAPIOpts{ ExternalCDR: &engine.ExternalCDR{ OriginID: "TestBalanceTimings", ToR: utils.MetaVoice, RequestType: utils.MetaPrepaid, AnswerTime: "2024-08-04T15:00:07Z", SetupTime: "2024-08-04T15:00:00Z", Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1002", Usage: "1", }, } if err := client.Call(context.Background(), utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Received: %s", reply) } time.Sleep(100 * time.Millisecond) }) t.Run("Half2GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 10 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) t.Run("Half1ProcessExternalCDR", func(t *testing.T) { var reply string args := &engine.ExternalCDRWithAPIOpts{ ExternalCDR: &engine.ExternalCDR{ OriginID: "TestBalanceTimings2", ToR: utils.MetaVoice, RequestType: utils.MetaPrepaid, AnswerTime: "2024-08-04T11:00:07Z", SetupTime: "2024-08-04T11:00:00Z", Tenant: "cgrates.org", Account: "1001", Subject: "1001", Destination: "1002", Usage: "1", }, } if err := client.Call(context.Background(), utils.CDRsV1ProcessExternalCDR, args, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("Received: %s", reply) } time.Sleep(100 * time.Millisecond) }) t.Run("Half1GetAccount", func(t *testing.T) { 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) } if len(acnt.BalanceMap) != 1 || len(acnt.BalanceMap[utils.MetaMonetary]) != 2 { t.Fatalf("expected account to have 1 balance of type *monetary, received %v", utils.ToJSON(acnt)) } monetaryBalance := acnt.BalanceMap[utils.MetaMonetary][1] if monetaryBalance.ID != "bal1" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal1" && monetaryBalance.Timings[0].ID != "HALF1" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF1", monetaryBalance.Timings) } monetaryBalance = acnt.BalanceMap[utils.MetaMonetary][0] if monetaryBalance.ID != "bal2" || monetaryBalance.Value != 9.4 { t.Fatalf("received account with unexpected *monetary balance: %v", monetaryBalance) } else if monetaryBalance.ID == "bal2" && monetaryBalance.Timings[0].ID != "HALF2" { t.Fatalf("expected TimingIDs %v, received: %v", "HALF2", monetaryBalance.Timings) } }) }