//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 main import ( "bytes" "errors" "os/exec" "path" "reflect" "sort" "testing" "time" "github.com/cgrates/birpc/context" "github.com/cgrates/birpc" "github.com/cgrates/birpc/jsonrpc" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) var ( cgrLdrCfgPath string cgrLdrCfgDir string cgrLdrCfg *config.CGRConfig cgrLdrRPC *birpc.Client cgrLdrTests = []func(t *testing.T){ testCgrLdrInitCfg, testCgrLdrInitDataDB, testCgrLdrInitStorDB, testCgrLdrStartEngine, testCgrLdrRPCConn, testCgrLdrGetSubsystemsNotLoadedLoad, testCgrLdrLoadData, testCgrLdrGetAttributeProfileAfterLoad, testCgrLdrGetFilterAfterLoad, testCgrLdrGetResourceProfileAfterLoad, testCgrLdrGetResourceAfterLoad, testCgrLdrGetRouteProfileAfterLoad, testCgrLdrGetStatsProfileAfterLoad, testCgrLdrGetStatQueueAfterLoad, testCgrLdrGetThresholdProfileAfterLoad, testCgrLdrGetThresholdAfterLoad, testCgrLdrGetChargerProfileAfterLoad, //remove all data with cgr-loader and remove flag testCgrLdrRemoveData, testCgrLdrGetSubsystemsNotLoadedLoad, testCgrLdrKillEngine, } ) func TestCGRLoaderRemove(t *testing.T) { switch *utils.DBType { case utils.MetaInternal: t.SkipNow() case utils.MetaMongo: cgrLdrCfgDir = "tutmongo" case utils.MetaMySQL: cgrLdrCfgDir = "tutmysql" case utils.MetaPostgres: t.SkipNow() default: t.Fatal("Unknown Database type") } for _, test := range cgrLdrTests { t.Run("cgr-loader remove tests", test) } } func testCgrLdrInitCfg(t *testing.T) { var err error cgrLdrCfgPath = path.Join(*utils.DataDir, "conf", "samples", cgrLdrCfgDir) cgrLdrCfg, err = config.NewCGRConfigFromPath(cgrLdrCfgPath) if err != nil { t.Error(err) } } func testCgrLdrInitDataDB(t *testing.T) { if err := engine.InitDataDB(cgrLdrCfg); err != nil { t.Fatal(err) } } func testCgrLdrInitStorDB(t *testing.T) { if err := engine.InitStorDb(cgrLdrCfg); err != nil { t.Fatal(err) } } func testCgrLdrStartEngine(t *testing.T) { if _, err := engine.StartEngine(cgrLdrCfgPath, *utils.WaitRater); err != nil { t.Fatal(err) } } func testCgrLdrRPCConn(t *testing.T) { var err error cgrLdrRPC, err = newRPCClient(cgrLdrCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed if err != nil { t.Fatal(err) } } func testCgrLdrGetSubsystemsNotLoadedLoad(t *testing.T) { //attributesPrf var replyAttr *engine.AttributeProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_ACNT_1001"}}, &replyAttr); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } //filtersPrf var replyFltr *engine.Filter if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetFilter, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "FLTR_1"}}, &replyFltr); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // resourcesPrf var replyResPrf *engine.ResourceProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetResourceProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RES_ACNT_1001"}}, &replyResPrf); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // resource var replyRes *engine.Resource if err := cgrLdrRPC.Call(context.Background(), utils.ResourceSv1GetResource, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RES_ACNT_1001"}}, &replyRes); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // routesPrf var replyRts *engine.RouteProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetRouteProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ROUTE_ACNT_1001"}}, &replyRts); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // statsPrf var replySts *engine.StatQueueProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetStatQueueProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "Stat_1"}}, &replySts); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // statQueue var replyStQue *engine.StatQueue if err := cgrLdrRPC.Call(context.Background(), utils.StatSv1GetStatQueue, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "Stat_1"}}, &replyStQue); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // thresholdPrf var replyThdPrf *engine.ThresholdProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetThresholdProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "THD_ACNT_1"}}, &replyThdPrf); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } // threshold var rplyThd *engine.Threshold if err := cgrLdrRPC.Call(context.Background(), utils.ThresholdSv1GetThreshold, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "THD_ACNT_1"}}, &rplyThd); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+q, received %v", utils.ErrNotFound.Error(), err) } //chargers var replyChrgr *engine.ChargerProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetChargerProfile, &utils.TenantID{Tenant: "cgrates.org", ID: "Raw"}, &replyChrgr); err == nil || err.Error() != utils.ErrNotFound.Error() { t.Errorf("Expected %+v, received %v", utils.ErrNotFound.Error(), err) } } func testCgrLdrLoadData(t *testing.T) { // *cacheSAddress = "127.0.0.1:2012" cmd := exec.Command("cgr-loader", "-config_path="+cgrLdrCfgPath, "-path="+path.Join(*utils.DataDir, "tariffplans", "testit")) output := bytes.NewBuffer(nil) outerr := bytes.NewBuffer(nil) cmd.Stdout = output cmd.Stderr = outerr if err := cmd.Run(); err != nil { t.Log(cmd.Args) t.Log(output.String()) t.Log(outerr.String()) t.Fatal(err) } } func testCgrLdrGetAttributeProfileAfterLoad(t *testing.T) { extAttrPrf := &engine.AttributeProfile{ Tenant: "cgrates.org", ID: "ATTR_ACNT_1001", Contexts: []string{utils.MetaSessionS}, FilterIDs: []string{"FLTR_ACCOUNT_1001"}, Weight: 10, Attributes: []*engine.Attribute{ { FilterIDs: []string{}, Path: "*req.OfficeGroup", Type: utils.MetaConstant, Value: config.NewRSRParsersMustCompile("Marketing", utils.InfieldSep), }, }, } var replyAttr *engine.AttributeProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_ACNT_1001"}}, &replyAttr); err != nil { t.Error(err) } else { sort.Strings(extAttrPrf.FilterIDs) sort.Strings(replyAttr.FilterIDs) replyAttr.Attributes[0].Value.Compile() extAttrPrf.Attributes[0].Value.Compile() if !reflect.DeepEqual(extAttrPrf.Attributes[0].Value[0], replyAttr.Attributes[0].Value[0]) { t.Errorf("Expected %T \n, received %T", extAttrPrf.Attributes[0].Value, replyAttr.Attributes[0].Value) } } } func testCgrLdrGetFilterAfterLoad(t *testing.T) { expFilter := &engine.Filter{ Tenant: "cgrates.org", ID: "FLTR_1", Rules: []*engine.FilterRule{ { Type: utils.MetaString, Element: "~*req.Account", Values: []string{"1003", "1002"}, }, { Type: utils.MetaPrefix, Element: "~*req.Destination", Values: []string{"10", "20"}, }, { Type: utils.MetaRSR, Element: "~*req.Destination", Values: []string{"1002"}, }, }, ActivationInterval: &utils.ActivationInterval{ ActivationTime: time.Date(2014, time.July, 29, 15, 0, 0, 0, time.UTC), }, } var replyFltr *engine.Filter if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetFilter, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "FLTR_1"}}, &replyFltr); err != nil { t.Error(err) } else if !reflect.DeepEqual(expFilter, replyFltr) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expFilter), utils.ToJSON(replyFltr)) } } func testCgrLdrGetResourceProfileAfterLoad(t *testing.T) { expREsPrf := &engine.ResourceProfile{ Tenant: "cgrates.org", ID: "RES_ACNT_1001", FilterIDs: []string{"FLTR_ACCOUNT_1001"}, Weight: 10, UsageTTL: time.Hour, Limit: 1, ThresholdIDs: []string{}, } var replyRes *engine.ResourceProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetResourceProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RES_ACNT_1001"}}, &replyRes); err != nil { t.Error(err) } else if !reflect.DeepEqual(expREsPrf, replyRes) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expREsPrf), utils.ToJSON(replyRes)) } } func testCgrLdrGetResourceAfterLoad(t *testing.T) { expREsPrf := &engine.Resource{ Tenant: "cgrates.org", ID: "RES_ACNT_1001", Usages: map[string]*engine.ResourceUsage{}, } var replyRes *engine.Resource if err := cgrLdrRPC.Call(context.Background(), utils.ResourceSv1GetResource, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "RES_ACNT_1001"}}, &replyRes); err != nil { t.Error(err) } else if !reflect.DeepEqual(expREsPrf, replyRes) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expREsPrf), utils.ToJSON(replyRes)) } } func testCgrLdrGetRouteProfileAfterLoad(t *testing.T) { expRoutePrf := &engine.RouteProfile{ ID: "ROUTE_ACNT_1001", Tenant: "cgrates.org", FilterIDs: []string{"FLTR_ACCOUNT_1001"}, Weight: 10, Sorting: utils.MetaWeight, SortingParameters: []string{}, Routes: []*engine.Route{ { ID: "route1", Weight: 20, }, { ID: "route2", Weight: 10, }, }, } var replyRts *engine.RouteProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetRouteProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ROUTE_ACNT_1001"}}, &replyRts); err != nil { t.Error(err) } else { sort.Slice(expRoutePrf.Routes, func(i, j int) bool { return expRoutePrf.Routes[i].ID < expRoutePrf.Routes[j].ID }) sort.Slice(replyRts.Routes, func(i, j int) bool { return replyRts.Routes[i].ID < replyRts.Routes[j].ID }) if !reflect.DeepEqual(expRoutePrf, replyRts) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expRoutePrf), utils.ToJSON(replyRts)) } } } func testCgrLdrGetStatsProfileAfterLoad(t *testing.T) { expStatsprf := &engine.StatQueueProfile{ Tenant: "cgrates.org", ID: "Stat_1", FilterIDs: []string{"FLTR_STAT_1"}, Weight: 30, QueueLength: 100, TTL: 10 * time.Second, MinItems: 0, Metrics: []*engine.MetricWithFilters{ { MetricID: "*tcd", }, { MetricID: "*asr", }, { MetricID: "*acd", }, }, Blocker: true, ThresholdIDs: []string{utils.MetaNone}, ActivationInterval: &utils.ActivationInterval{ ActivationTime: time.Date(2014, time.July, 29, 15, 0, 0, 0, time.UTC), }, } var replySts *engine.StatQueueProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetStatQueueProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "Stat_1"}}, &replySts); err != nil { t.Error(err) } else { sort.Slice(expStatsprf.Metrics, func(i, j int) bool { return expStatsprf.Metrics[i].MetricID < expStatsprf.Metrics[j].MetricID }) sort.Slice(replySts.Metrics, func(i, j int) bool { return replySts.Metrics[i].MetricID < replySts.Metrics[j].MetricID }) if !reflect.DeepEqual(expStatsprf, replySts) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expStatsprf), utils.ToJSON(replySts)) } } } func testCgrLdrGetStatQueueAfterLoad(t *testing.T) { expStatQueue := map[string]string{ "*acd": "N/A", "*tcd": "N/A", "*asr": "N/A", } replyStQue := make(map[string]string) if err := cgrLdrRPC.Call(context.Background(), utils.StatSv1GetQueueStringMetrics, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "Stat_1"}}, &replyStQue); err != nil { t.Error(err) } else if !reflect.DeepEqual(expStatQueue, replyStQue) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expStatQueue), utils.ToJSON(replyStQue)) } } func testCgrLdrGetThresholdProfileAfterLoad(t *testing.T) { expThPrf := &engine.ThresholdProfile{ Tenant: "cgrates.org", ID: "THD_ACNT_1001", FilterIDs: []string{"FLTR_ACCOUNT_1001"}, ActivationInterval: &utils.ActivationInterval{ ActivationTime: time.Date(2014, time.July, 29, 15, 0, 0, 0, time.UTC), }, Weight: 10, MaxHits: -1, MinHits: 0, ActionIDs: []string{"TOPUP_MONETARY_10"}, } var replyThdPrf *engine.ThresholdProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetThresholdProfile, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "THD_ACNT_1001"}}, &replyThdPrf); err != nil { t.Error(err) } else if !reflect.DeepEqual(expThPrf, replyThdPrf) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expThPrf), utils.ToJSON(replyThdPrf)) } } func testCgrLdrGetThresholdAfterLoad(t *testing.T) { expThPrf := &engine.Threshold{ Tenant: "cgrates.org", ID: "THD_ACNT_1001", Hits: 0, } var replyThdPrf *engine.Threshold if err := cgrLdrRPC.Call(context.Background(), utils.ThresholdSv1GetThreshold, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "THD_ACNT_1001"}}, &replyThdPrf); err != nil { t.Error(err) } else if !reflect.DeepEqual(expThPrf, replyThdPrf) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expThPrf), utils.ToJSON(replyThdPrf)) } } func testCgrLdrGetChargerProfileAfterLoad(t *testing.T) { expChPrf := &engine.ChargerProfile{ Tenant: "cgrates.org", ID: "Raw", FilterIDs: []string{}, RunID: "raw", AttributeIDs: []string{"*constant:*req.RequestType:*none"}, Weight: 20, } var replyChrgr *engine.ChargerProfile if err := cgrLdrRPC.Call(context.Background(), utils.APIerSv1GetChargerProfile, &utils.TenantID{Tenant: "cgrates.org", ID: "Raw"}, &replyChrgr); err != nil { t.Error(err) } else if !reflect.DeepEqual(expChPrf, replyChrgr) { t.Errorf("Expected %+v, received %+v", utils.ToJSON(expChPrf), utils.ToJSON(replyChrgr)) } } func testCgrLdrRemoveData(t *testing.T) { // *cacheSAddress = "127.0.0.1:2012" cmd := exec.Command("cgr-loader", "-config_path="+cgrLdrCfgPath, "-path="+path.Join(*utils.DataDir, "tariffplans", "testit"), "-remove") output := bytes.NewBuffer(nil) outerr := bytes.NewBuffer(nil) cmd.Stdout = output cmd.Stderr = outerr if err := cmd.Run(); err != nil { t.Log(cmd.Args) t.Log(output.String()) t.Log(outerr.String()) t.Fatal(err) } } // Kill the engine when it is about to be finished func testCgrLdrKillEngine(t *testing.T) { if err := engine.KillEngine(100); err != nil { t.Error(err) } } func newRPCClient(cfg *config.ListenCfg) (c *birpc.Client, err error) { switch *utils.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") } }