diff --git a/apis/resources_it_test.go b/apis/resources_it_test.go index fd2c71ed7..906754c96 100644 --- a/apis/resources_it_test.go +++ b/apis/resources_it_test.go @@ -1,3 +1,5 @@ +// +build integration + /* Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments Copyright (C) ITsysCOM GmbH @@ -86,7 +88,8 @@ var ( func TestResourceSIT(t *testing.T) { switch *dbType { case utils.MetaInternal: - rsConfigDIR = "resources_internal" + // rsConfigDIR = "resources_internal" + t.SkipNow() case utils.MetaMongo: rsConfigDIR = "resources_mongo" case utils.MetaMySQL: @@ -233,7 +236,8 @@ func testResourceSGetResourceAfterSet(t *testing.T) { }, &rplyResPrf); err != nil { t.Error(err) } else if !reflect.DeepEqual(rplyResPrf, expResPrf) { - t.Errorf("expected: <%+v>, \nreceived: <%+v>", expResPrf, rplyResPrf) + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(expResPrf), utils.ToJSON(rplyResPrf)) } expRes = engine.Resource{ diff --git a/apis/thresholds_it_test.go b/apis/thresholds_it_test.go new file mode 100644 index 000000000..e037923cb --- /dev/null +++ b/apis/thresholds_it_test.go @@ -0,0 +1,473 @@ +// +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 apis + +import ( + "path" + "reflect" + "sort" + "testing" + + "github.com/cgrates/birpc" + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +var ( + thCfgPath string + thCfg *config.CGRConfig + thRPC *birpc.Client + thConfigDIR string //run tests for specific configuration + + sTestsTh = []func(t *testing.T){ + testThresholdsInitCfg, + testThresholdsInitDataDB, + testThresholdsResetStorDB, + testThresholdsStartEngine, + testThresholdsRPCConn, + testThresholdsGetThresholdBeforeSet, + testThresholdsSetActionProfile, + testThresholdsSetThresholdProfiles, + testThresholdsGetThresholdAfterSet, + testThresholdsGetThresholdIDs, + testThresholdsGetThresholdProfileIDs, + testThresholdsGetThresholdProfileCount, + testThresholdsGetThresholdsForEvent, + testThresholdsRemoveThresholdProfiles, + testThresholdsGetThresholdProfileAfterRemove, + testThresholdsSetThresholdProfilesBeforeProcessEv, + testThresholdsProcessEvent, + testThresholdsPing, + testThresholdsKillEngine, + } +) + +func TestThresholdsIT(t *testing.T) { + switch *dbType { + case utils.MetaInternal: + thConfigDIR = "thresholds_internal" + case utils.MetaMongo: + thConfigDIR = "thresholds_mongo" + case utils.MetaMySQL: + thConfigDIR = "thresholds_mysql" + case utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("Unknown Database type") + } + for _, stest := range sTestsTh { + t.Run(thConfigDIR, stest) + } +} + +func testThresholdsInitCfg(t *testing.T) { + var err error + thCfgPath = path.Join(*dataDir, "conf", "samples", thConfigDIR) + thCfg, err = config.NewCGRConfigFromPath(thCfgPath) + if err != nil { + t.Error(err) + } +} + +func testThresholdsInitDataDB(t *testing.T) { + if err := engine.InitDataDB(thCfg); err != nil { + t.Fatal(err) + } +} + +func testThresholdsResetStorDB(t *testing.T) { + if err := engine.InitStorDB(thCfg); err != nil { + t.Fatal(err) + } +} + +// Start CGR Engine +func testThresholdsStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(thCfgPath, *waitRater); err != nil { + t.Fatal(err) + } +} + +func testThresholdsRPCConn(t *testing.T) { + var err error + thRPC, err = newRPCClient(thCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal(err) + } +} + +//Kill the engine when it is about to be finished +func testThresholdsKillEngine(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } +} + +func testThresholdsPing(t *testing.T) { + var reply string + if err := thRPC.Call(context.Background(), utils.ThresholdSv1Ping, + new(utils.CGREvent), &reply); err != nil { + t.Error(err) + } else if reply != utils.Pong { + t.Error("Unexpected reply returned:", reply) + } +} + +func testThresholdsGetThresholdBeforeSet(t *testing.T) { + var rplyTh *engine.Threshold + + if err := thRPC.Call(context.Background(), utils.ThresholdSv1GetThreshold, + &utils.TenantWithAPIOpts{ + Tenant: "cgrates.org", + }, &rplyTh); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err) + } +} +func testThresholdsSetActionProfile(t *testing.T) { + actPrf := &engine.ActionProfileWithAPIOpts{ + ActionProfile: &engine.ActionProfile{ + Tenant: "cgrates.org", + ID: "actPrfID", + Actions: []*engine.APAction{ + { + ID: "actID", + // Type: utils.MetaHTTPPost, + // Diktats: []*engine.APDiktat{ + // { + // Path: rsSrv.URL, + // }, + // }, + // TTL: time.Duration(time.Minute), + }, + }, + }, + } + + var reply *string + if err := thRPC.Call(context.Background(), utils.AdminSv1SetActionProfile, + actPrf, &reply); err != nil { + t.Error(err) + } +} + +func testThresholdsSetThresholdProfiles(t *testing.T) { + thPrf1 := &engine.ThresholdProfileWithAPIOpts{ + ThresholdProfile: &engine.ThresholdProfile{ + Tenant: "cgrates.org", + ID: "THD_1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + ActionProfileIDs: []string{"actPrfID"}, + MaxHits: 5, + MinHits: 1, + Weight: 10, + }, + } + + var reply string + if err := thRPC.Call(context.Background(), utils.AdminSv1SetThresholdProfile, + thPrf1, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } + + thPrf2 := &engine.ThresholdProfileWithAPIOpts{ + ThresholdProfile: &engine.ThresholdProfile{ + Tenant: "cgrates.org", + ID: "THD_2", + FilterIDs: []string{"*string:~*req.Account:1001"}, + ActionProfileIDs: []string{"actPrfID"}, + MaxHits: 7, + MinHits: 0, + Weight: 20, + }, + } + + if err := thRPC.Call(context.Background(), utils.AdminSv1SetThresholdProfile, + thPrf2, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } +} + +func testThresholdsGetThresholdAfterSet(t *testing.T) { + var rplyTh engine.Threshold + var rplyThPrf engine.ThresholdProfile + expTh := engine.Threshold{ + Tenant: "cgrates.org", + ID: "THD_1", + } + expThPrf := engine.ThresholdProfile{ + Tenant: "cgrates.org", + ID: "THD_1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + ActionProfileIDs: []string{"actPrfID"}, + MaxHits: 5, + MinHits: 1, + Weight: 10, + } + + if err := thRPC.Call(context.Background(), utils.ThresholdSv1GetThreshold, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "THD_1", + }, + }, &rplyTh); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyTh, expTh) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(rplyTh), utils.ToJSON(expTh)) + } + + if err := thRPC.Call(context.Background(), utils.AdminSv1GetThresholdProfile, + utils.TenantID{ + Tenant: "cgrates.org", + ID: "THD_1", + }, &rplyThPrf); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyThPrf, expThPrf) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(expThPrf), utils.ToJSON(rplyThPrf)) + } + + expTh = engine.Threshold{ + Tenant: "cgrates.org", + ID: "THD_2", + } + expThPrf = engine.ThresholdProfile{ + Tenant: "cgrates.org", + ID: "THD_2", + FilterIDs: []string{"*string:~*req.Account:1001"}, + ActionProfileIDs: []string{"actPrfID"}, + MaxHits: 7, + MinHits: 0, + Weight: 20, + } + + if err := thRPC.Call(context.Background(), utils.ThresholdSv1GetThreshold, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "THD_2", + }, + }, &rplyTh); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyTh, expTh) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(rplyTh), utils.ToJSON(expTh)) + } + + if err := thRPC.Call(context.Background(), utils.AdminSv1GetThresholdProfile, + &utils.TenantID{ + Tenant: "cgrates.org", + ID: "THD_2", + }, &rplyThPrf); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyThPrf, expThPrf) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(expThPrf), utils.ToJSON(rplyThPrf)) + } +} + +func testThresholdsGetThresholdIDs(t *testing.T) { + expIDs := []string{"THD_1", "THD_2"} + var tIDs []string + if err := thRPC.Call(context.Background(), utils.ThresholdSv1GetThresholdIDs, + &utils.TenantWithAPIOpts{ + Tenant: "cgrates.org", + }, &tIDs); err != nil { + t.Error(err) + } else { + sort.Strings(tIDs) + if !reflect.DeepEqual(tIDs, expIDs) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", expIDs, tIDs) + } + } +} + +func testThresholdsGetThresholdProfileIDs(t *testing.T) { + expIDs := []string{"THD_1", "THD_2"} + var tIDs []string + if err := thRPC.Call(context.Background(), utils.AdminSv1GetThresholdProfileIDs, + &utils.PaginatorWithTenant{ + Tenant: "cgrates.org", + Paginator: utils.Paginator{}, + }, &tIDs); err != nil { + t.Error(err) + } else { + sort.Strings(tIDs) + if !reflect.DeepEqual(tIDs, expIDs) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", expIDs, tIDs) + } + } +} + +func testThresholdsGetThresholdProfileCount(t *testing.T) { + var reply int + if err := thRPC.Call(context.Background(), utils.AdminSv1GetThresholdProfileCount, + &utils.TenantWithAPIOpts{ + Tenant: "cgrates.org", + }, &reply); err != nil { + t.Error(err) + } else if reply != 2 { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", 2, reply) + } +} + +func testThresholdsGetThresholdsForEvent(t *testing.T) { + args := &engine.ThresholdsArgsProcessEvent{ + ThresholdIDs: []string{"THD_1", "THD_2"}, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "ThresholdEventTest", + Event: map[string]interface{}{ + utils.AccountField: "1001", + }, + }, + } + expThs := engine.Thresholds{ + &engine.Threshold{ + Tenant: "cgrates.org", + ID: "THD_2", + Hits: 0, + }, + &engine.Threshold{ + Tenant: "cgrates.org", + ID: "THD_1", + Hits: 0, + }, + } + + var rplyThs engine.Thresholds + if err := thRPC.Call(context.Background(), utils.ThresholdSv1GetThresholdsForEvent, + args, &rplyThs); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(rplyThs, expThs) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", + utils.ToJSON(expThs), utils.ToJSON(rplyThs)) + } +} + +func testThresholdsRemoveThresholdProfiles(t *testing.T) { + var reply string + + if err := thRPC.Call(context.Background(), utils.AdminSv1RemoveThresholdProfile, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "THD_1", + }}, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } + + if err := thRPC.Call(context.Background(), utils.AdminSv1RemoveThresholdProfile, + &utils.TenantIDWithAPIOpts{ + TenantID: &utils.TenantID{ + Tenant: "cgrates.org", + ID: "THD_2", + }}, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } +} + +func testThresholdsGetThresholdProfileAfterRemove(t *testing.T) { + var rplyTh engine.ThresholdProfile + + if err := thRPC.Call(context.Background(), utils.ThresholdSv1GetThreshold, + &utils.TenantWithAPIOpts{ + Tenant: "cgrates.org", + }, &rplyTh); err == nil || err.Error() != utils.ErrNotFound.Error() { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err) + } +} + +func testThresholdsSetThresholdProfilesBeforeProcessEv(t *testing.T) { + thPrf1 := &engine.ThresholdProfileWithAPIOpts{ + ThresholdProfile: &engine.ThresholdProfile{ + Tenant: "cgrates.org", + ID: "THD_1", + FilterIDs: []string{"*string:~*req.Account:1001"}, + ActionProfileIDs: []string{"actPrfID"}, + MaxHits: 5, + MinHits: 3, + Weight: 10, + }, + } + + var reply string + if err := thRPC.Call(context.Background(), utils.AdminSv1SetThresholdProfile, + thPrf1, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } + + thPrf2 := &engine.ThresholdProfileWithAPIOpts{ + ThresholdProfile: &engine.ThresholdProfile{ + Tenant: "cgrates.org", + ID: "THD_2", + FilterIDs: []string{"*string:~*req.Account:1001"}, + ActionProfileIDs: []string{"actPrfID"}, + MaxHits: 2, + MinHits: 0, + Weight: 20, + }, + } + + if err := thRPC.Call(context.Background(), utils.AdminSv1SetThresholdProfile, + thPrf2, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Error("Unexpected reply returned:", reply) + } +} + +func testThresholdsProcessEvent(t *testing.T) { + args := &engine.ThresholdsArgsProcessEvent{ + ThresholdIDs: []string{"THD_1", "THD_2"}, + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "ThresholdProcessEv", + Event: map[string]interface{}{ + utils.AccountField: "1001", + }, + }, + } + + expIDs := []string{"THD_1", "THD_2"} + var tIDs []string + if err := thRPC.Call(context.Background(), utils.ThresholdSv1ProcessEvent, args, &tIDs); err != nil { + t.Error(err) + } else { + sort.Strings(tIDs) + if !reflect.DeepEqual(tIDs, expIDs) { + t.Errorf("expected: <%+v>, \nreceived: <%+v>", expIDs, tIDs) + } + } +} diff --git a/data/conf/samples/resources_mysql/cgrates.json b/data/conf/samples/resources_mysql/cgrates.json index 11df53337..c34a1862d 100644 --- a/data/conf/samples/resources_mysql/cgrates.json +++ b/data/conf/samples/resources_mysql/cgrates.json @@ -21,7 +21,7 @@ "thresholds": { "enabled": true, - "actions_conns": ["*localhost"], + "actions_conns": ["*internal"], }, "resources": { diff --git a/data/conf/samples/thresholds_internal/cgrates.json b/data/conf/samples/thresholds_internal/cgrates.json new file mode 100644 index 000000000..a52ade471 --- /dev/null +++ b/data/conf/samples/thresholds_internal/cgrates.json @@ -0,0 +1,30 @@ +{ + // CGRateS Configuration file + // will be used in apis/thresholds_it_test.go + + "general": { + "log_level": 7, + }, + + "data_db": { + "db_type": "*internal", + }, + + "stor_db": { + "db_type": "*internal", + }, + + "actions": { + "enabled": true, + }, + + "thresholds": { + "enabled": true, + "store_interval": "-1", + "actions_conns": ["*internal"], + }, + + "admins": { + "enabled": true, + }, + } \ No newline at end of file diff --git a/data/conf/samples/thresholds_mongo/cgrates.json b/data/conf/samples/thresholds_mongo/cgrates.json new file mode 100644 index 000000000..6eae47859 --- /dev/null +++ b/data/conf/samples/thresholds_mongo/cgrates.json @@ -0,0 +1,33 @@ +{ + // CGRateS Configuration file + // will be used in apis/thresholds_it_test.go + + "general": { + "log_level": 7, + }, + + "data_db": { + "db_type": "mongo", + "db_name": "10", + "db_port": 27017, + }, + + "stor_db": { + "db_type": "mongo", + "db_name": "cgrates", + "db_port": 27017, + }, + + "actions": { + "enabled": true, + }, + + "thresholds": { + "enabled": true, + "actions_conns": ["*internal"], + }, + + "admins": { + "enabled": true, + } + } \ No newline at end of file diff --git a/data/conf/samples/thresholds_mysql/cgrates.json b/data/conf/samples/thresholds_mysql/cgrates.json new file mode 100644 index 000000000..e75bc579f --- /dev/null +++ b/data/conf/samples/thresholds_mysql/cgrates.json @@ -0,0 +1,31 @@ +{ + // CGRateS Configuration file + // will be used in apis/thresholds_it_test.go + "general": { + "log_level": 7, + }, + + "data_db": { // database used to store runtime data (eg: accounts, cdr stats) + "db_type": "redis", // data_db type: + "db_port": 6379, // data_db port to reach the database + "db_name": "10", // data_db database name to connect to + }, + + "stor_db": { + "db_password": "CGRateS.org", + }, + + "actions": { + "enabled": true, + }, + + "thresholds": { + "enabled": true, + "store_interval": "", + "actions_conns": ["*internal"], + }, + + "admins": { + "enabled": true, + } + } \ No newline at end of file