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