/* 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 v1 import ( "reflect" "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" ) func TestComposeArgsReload(t *testing.T) { apv1 := &APIerSv1{} expArgs := map[string][]string{utils.CacheAttributeProfiles: {"cgrates.org:ATTR1"}} if rply, err := apv1.composeArgsReload("cgrates.org", utils.CacheAttributeProfiles, "cgrates.org:ATTR1", nil, nil); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(expArgs, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(expArgs), utils.ToJSON(rply)) } expArgs[utils.CacheAttributeFilterIndexes] = []string{"cgrates.org:*cdrs:*none:*any:*any"} if rply, err := apv1.composeArgsReload("cgrates.org", utils.CacheAttributeProfiles, "cgrates.org:ATTR1", &[]string{}, []string{utils.MetaCDRs}); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(expArgs, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(expArgs), utils.ToJSON(rply)) } expArgs[utils.CacheAttributeFilterIndexes] = []string{ "cgrates.org:*cdrs:*string:*req.Account:1001", "cgrates.org:*cdrs:*prefix:*req.Destination:1001", } if rply, err := apv1.composeArgsReload("cgrates.org", utils.CacheAttributeProfiles, "cgrates.org:ATTR1", &[]string{"*string:~*req.Account:1001|~req.Subject", "*prefix:1001:~*req.Destination", "*gt:~req.Usage:0"}, []string{utils.MetaCDRs}); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(expArgs, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(expArgs), utils.ToJSON(rply)) } expArgs = map[string][]string{ utils.CacheStatQueueProfiles: {"cgrates.org:Stat2"}, utils.CacheStatQueues: {"cgrates.org:Stat2"}, utils.CacheStatFilterIndexes: { "cgrates.org:*string:*req.Account:1001", "cgrates.org:*prefix:*req.Destination:1001", }, } if rply, err := apv1.composeArgsReload("cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", &[]string{"*string:~*req.Account:1001|~req.Subject", "*prefix:1001:~*req.Destination"}, nil); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(expArgs, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(expArgs), utils.ToJSON(rply)) } expArgs[utils.CacheStatFilterIndexes] = []string{"cgrates.org:*none:*any:*any"} if rply, err := apv1.composeArgsReload("cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", &[]string{}, []string{utils.MetaCDRs}); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(expArgs, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(expArgs), utils.ToJSON(rply)) } if _, err := apv1.composeArgsReload("cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", &[]string{"FLTR1"}, []string{utils.MetaCDRs}); err != utils.ErrNoDatabaseConn { t.Fatal(err) } } type rpcRequest struct { Method string Params any } type rpcMock chan *rpcRequest func (r rpcMock) Call(_ *context.Context, method string, args, _ any) error { r <- &rpcRequest{ Method: method, Params: args, } return nil } func TestCallCache(t *testing.T) { cache := make(rpcMock, 1) ch := make(chan birpc.ClientConnector, 1) ch <- cache cn := engine.NewConnManager(config.CgrConfig(), map[string]chan birpc.ClientConnector{ utils.ConcatenatedKey(utils.MetaInternal, utils.MetaCaches): ch, }) cn.Reload() apv1 := &APIerSv1{ ConnMgr: cn, Config: config.CgrConfig(), } if err := apv1.CallCache(utils.MetaNone, "", "", "", utils.EmptyString, nil, nil, nil); err != nil { t.Fatal(err) } else if len(cache) != 0 { t.Fatal("Expected call cache to not be called") } exp := &rpcRequest{ Method: utils.CacheSv1Clear, Params: &utils.AttrCacheIDsWithAPIOpts{ Tenant: "cgrates.org", CacheIDs: []string{utils.CacheStatQueueProfiles, utils.CacheStatFilterIndexes, utils.CacheStatQueues}, APIOpts: make(map[string]any), }, } if err := apv1.CallCache(utils.MetaClear, "cgrates.org", utils.CacheStatQueueProfiles, "", utils.EmptyString, nil, nil, make(map[string]any)); err != nil { t.Fatal(err) } else if len(cache) != 1 { t.Fatal("Expected call cache to be called") } else if rply := <-cache; !reflect.DeepEqual(exp, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(exp), utils.ToJSON(rply)) } exp = &rpcRequest{ Method: utils.CacheSv1ReloadCache, Params: &utils.AttrReloadCacheWithAPIOpts{ APIOpts: make(map[string]any), Tenant: "cgrates.org", StatsQueueProfileIDs: []string{"cgrates.org:Stat2"}, StatsQueueIDs: []string{"cgrates.org:Stat2"}, StatFilterIndexIDs: []string{ "cgrates.org:*string:*req.Account:1001", "cgrates.org:*prefix:*req.Destination:1001", }, }, } if err := apv1.CallCache(utils.MetaReload, "cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", utils.EmptyString, &[]string{"*string:~*req.Account:1001|~req.Subject", "*prefix:1001:~*req.Destination"}, nil, make(map[string]any)); err != nil { t.Fatal(err) } else if len(cache) != 1 { t.Fatal("Expected call cache to be called") } else if rply := <-cache; !reflect.DeepEqual(exp, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(exp), utils.ToJSON(rply)) } exp.Method = utils.CacheSv1LoadCache if err := apv1.CallCache(utils.MetaLoad, "cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", utils.EmptyString, &[]string{"*string:~*req.Account:1001|~req.Subject", "*prefix:1001:~*req.Destination"}, nil, make(map[string]any)); err != nil { t.Fatal(err) } else if len(cache) != 1 { t.Fatal("Expected call cache to be called") } else if rply := <-cache; !reflect.DeepEqual(exp, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(exp), utils.ToJSON(rply)) } exp.Method = utils.CacheSv1RemoveItems if err := apv1.CallCache(utils.MetaRemove, "cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", utils.EmptyString, &[]string{"*string:~*req.Account:1001|~req.Subject", "*prefix:1001:~*req.Destination"}, nil, make(map[string]any)); err != nil { t.Fatal(err) } else if len(cache) != 1 { t.Fatal("Expected call cache to be called") } else if rply := <-cache; !reflect.DeepEqual(exp, rply) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(exp), utils.ToJSON(rply)) } if err := apv1.CallCache(utils.MetaLoad, "cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", utils.EmptyString, &[]string{"FLTR1", "*prefix:1001:~*req.Destination"}, nil, make(map[string]any)); err != utils.ErrNoDatabaseConn { t.Fatal(err) } else if len(cache) != 0 { t.Fatal("Expected call cache to not be called") } if err := apv1.CallCache(utils.MetaRemove, "cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", utils.EmptyString, &[]string{"FLTR1", "*prefix:1001:~*req.Destination"}, nil, make(map[string]any)); err != utils.ErrNoDatabaseConn { t.Fatal(err) } else if len(cache) != 0 { t.Fatal("Expected call cache to not be called") } if err := apv1.CallCache(utils.MetaReload, "cgrates.org", utils.CacheStatQueueProfiles, "cgrates.org:Stat2", utils.EmptyString, &[]string{"FLTR1", "*prefix:1001:~*req.Destination"}, nil, make(map[string]any)); err != utils.ErrNoDatabaseConn { t.Fatal(err) } else if len(cache) != 0 { t.Fatal("Expected call cache to not be called") } } func TestCallCacheForFilter(t *testing.T) { cfg := config.NewDefaultCGRConfig() idb, err := engine.NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items) if err != nil { t.Error(err) } dm := engine.NewDataManager(idb, cfg.CacheCfg(), nil) tnt := "cgrates.org" flt := &engine.Filter{ Tenant: tnt, ID: "FLTR1", Rules: []*engine.FilterRule{{ Type: utils.MetaString, Element: "~*req.Account", Values: []string{"1001"}, }}, } if err := flt.Compile(); err != nil { t.Fatal(err) } if err := dm.SetFilter(flt, true); err != nil { t.Fatal(err) } th := &engine.ThresholdProfile{ Tenant: tnt, ID: "TH1", FilterIDs: []string{flt.ID}, } if err := dm.SetThresholdProfile(th, true); err != nil { t.Fatal(err) } attr := &engine.AttributeProfile{ Tenant: tnt, ID: "Attr1", Contexts: []string{utils.MetaAny}, FilterIDs: []string{flt.ID}, } if err := dm.SetAttributeProfile(attr, true); err != nil { t.Fatal(err) } exp := map[string][]string{ utils.CacheFilters: {"cgrates.org:FLTR1"}, utils.CacheAttributeFilterIndexes: {"cgrates.org:*any:*string:*req.Account:1001"}, utils.CacheThresholdFilterIndexes: {"cgrates.org:*string:*req.Account:1001"}, } rpl, err := composeCacheArgsForFilter(dm, flt, tnt, flt.TenantID(), map[string][]string{utils.CacheFilters: {"cgrates.org:FLTR1"}}) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(rpl, exp) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(exp), utils.ToJSON(rpl)) } flt = &engine.Filter{ Tenant: tnt, ID: "FLTR1", Rules: []*engine.FilterRule{{ Type: utils.MetaString, Element: "~*req.Account", Values: []string{"1002"}, }}, } if err := flt.Compile(); err != nil { t.Fatal(err) } if err := dm.SetFilter(flt, true); err != nil { t.Fatal(err) } exp = map[string][]string{ utils.CacheFilters: {"cgrates.org:FLTR1"}, utils.CacheAttributeFilterIndexes: {"cgrates.org:*any:*string:*req.Account:1001", "cgrates.org:*any:*string:*req.Account:1002"}, utils.CacheThresholdFilterIndexes: {"cgrates.org:*string:*req.Account:1001", "cgrates.org:*string:*req.Account:1002"}, } rpl, err = composeCacheArgsForFilter(dm, flt, tnt, flt.TenantID(), rpl) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(rpl, exp) { t.Errorf("Expected %s ,received: %s", utils.ToJSON(exp), utils.ToJSON(rpl)) } }