Files
cgrates/apier/v1/libapier_test.go
2025-10-29 19:42:40 +01:00

288 lines
10 KiB
Go

/*
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 <https://www.gnu.org/licenses/>
*/
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))
}
}