Add tests for multiple DB conns

This commit is contained in:
arberkatellari
2025-10-20 18:19:24 +02:00
committed by Dan Christian Bogos
parent a3f25d1ec5
commit 8af781fb75
10 changed files with 936 additions and 19 deletions

View File

@@ -1029,7 +1029,7 @@ func (cfg *CGRConfig) checkConfigSanity() error {
for _, dbcfg := range cfg.dbCfg.DBConns {
if dbcfg.Type == utils.MetaInternal {
if hasOneInternalDB {
return fmt.Errorf("<%s> There can only be 1 internal DataDB", utils.DB)
return fmt.Errorf("<%s> There can only be 1 internal DB", utils.DB)
}
if (cfg.dbCfg.Opts.InternalDBDumpInterval != 0 ||
cfg.dbCfg.Opts.InternalDBRewriteInterval != 0) &&

View File

@@ -40,9 +40,13 @@
}
},
"items":{
"*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "intrnl"},
"*charger_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "redis2"},
"*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "redis2"},
"*charger_filter_indexes" : {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false, "dbConn": "intrnl"},
"*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "StorDB"}
},
"opts": {
"internalDBDumpPath": "/tmp/internal_db/db",
"internalDBRewriteInterval": "0s"
}
},
@@ -54,9 +58,9 @@
"attributes": {
"enabled": true,
"stats_conns": ["*localhost"],
"resources_conns": ["*localhost"],
"accounts_conns": ["*localhost"]
"stats_conns": ["*internal"],
"resources_conns": ["*internal"],
"accounts_conns": ["*internal"]
},
"chargers": {
@@ -124,7 +128,7 @@
"actions": {
"enabled": true,
"accounts_conns": ["*localhost"]
"accounts_conns": ["*internal"]
},
@@ -141,6 +145,16 @@
"tpes": {
"enabled": true
}
},
"loaders": [
{
"enabled":true,
"id": "*default",
"tp_in_dir": "/usr/share/cgrates/tariffplans/testit",
"tp_out_dir": "",
"lockfile_path": ".cgr.lck"
}
]
}

View File

@@ -0,0 +1,154 @@
{
"general": {
"reply_timeout": "50s"
},
"logger": {
"level": 7
},
"listen": {
"rpc_json": ":2012",
"rpc_gob": ":2013",
"http": ":2080"
},
"db": {
"db_conns": {
"*default": { // using redis a default to see which files are dumped in internal
"db_type": "*redis",
"db_port": 6379,
"db_name": "10"
},
"internalDBConn": {
"db_type": "*internal"
},
"mongoDBConn": {
"db_type": "mongo",
"db_host": "127.0.0.1",
"db_port": 27017,
"db_name": "cgrates",
"db_user": "cgrates"
}
},
"items":{
"*charger_profiles": {"dbConn": "mongoDBConn"},
"*charger_filter_indexes" : {"dbConn": "internalDBConn"},
"*cdrs": {"dbConn": "mongoDBConn"}
},
"opts": {
"internalDBDumpPath": "/tmp/internal_db/db",
"internalDBRewriteInterval": "0s"
}
},
"cdrs": {
"enabled": true,
"chargers_conns":["*internal"]
},
"attributes": {
"enabled": true,
"stats_conns": ["*localhost"],
"resources_conns": ["*localhost"],
"accounts_conns": ["*localhost"]
},
"chargers": {
"enabled": true,
"attributes_conns": ["*internal"]
},
"resources": {
"enabled": true,
"store_interval": "1s",
"thresholds_conns": ["*internal"]
},
"stats": {
"enabled": true,
"store_interval": "1s",
"thresholds_conns": ["*internal"]
},
"thresholds": {
"enabled": true,
"store_interval": "1s"
},
"routes": {
"enabled": true,
"prefix_indexed_fields":["*req.Destination"],
"stats_conns": ["*internal"],
"resources_conns": ["*internal"],
"rates_conns": ["*internal"]
},
"sessions": {
"enabled": true,
"routes_conns": ["*internal"],
"resources_conns": ["*internal"],
"attributes_conns": ["*internal"],
"rates_conns": ["*internal"],
"cdrs_conns": ["*internal"],
"chargers_conns": ["*internal"]
},
"migrator":{
"users_filters":["Account"]
},
"admins": {
"enabled": true,
"scheduler_conns": ["*internal"]
},
"rates": {
"enabled": true
},
"actions": {
"enabled": true,
"accounts_conns": ["*localhost"]
},
"accounts": {
"enabled": true
},
"filters": {
"stats_conns": ["*internal"],
"resources_conns": ["*internal"],
"accounts_conns": ["*internal"]
},
"tpes": {
"enabled": true
},
"loaders": [
{
"enabled":true,
"id": "*default",
"tp_in_dir": "/usr/share/cgrates/tariffplans/testit",
"tp_out_dir": "",
"lockfile_path": ".cgr.lck"
}
]
}

View File

@@ -18,14 +18,16 @@
},
"opts": {
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "-1"
"internalDBDumpInterval": "-1",
"internalDBRewriteInterval": "0s"
}
},
"config_db": {
"db_type": "*internal",
"opts": {
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "-1"
"internalDBDumpInterval": "-1",
"internalDBRewriteInterval": "0s"
}
},
"cdrs": {

View File

@@ -18,7 +18,8 @@
"opts":{
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "-1",
"internalDBFileSizeLimit": "4KB"
"internalDBFileSizeLimit": "4KB",
"internalDBRewriteInterval": "0s"
}
},
"config_db": {
@@ -26,7 +27,8 @@
"opts": {
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "-1",
"internalDBFileSizeLimit": "4KB"
"internalDBFileSizeLimit": "4KB",
"internalDBRewriteInterval": "0s"
}
},
"cdrs": {

View File

@@ -17,14 +17,16 @@
},
"opts":{
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "500ms"
"internalDBDumpInterval": "500ms",
"internalDBRewriteInterval": "0s"
}
},
"config_db": {
"db_type": "*internal",
"opts": {
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "500ms"
"internalDBDumpInterval": "500ms",
"internalDBRewriteInterval": "0s"
}
},
"cdrs": {

View File

@@ -18,7 +18,8 @@
"opts":{
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "500ms",
"internalDBFileSizeLimit": "4KB"
"internalDBFileSizeLimit": "4KB",
"internalDBRewriteInterval": "0s"
}
},
"config_db": {
@@ -26,7 +27,8 @@
"opts": {
"internalDBStartTimeout": "1m",
"internalDBDumpInterval": "500ms",
"internalDBFileSizeLimit": "4KB"
"internalDBFileSizeLimit": "4KB",
"internalDBRewriteInterval": "0s"
}
},
"cdrs": {

View File

@@ -125,6 +125,35 @@ func StartEngine(cfgPath string, waitEngine int) (*exec.Cmd, error) {
return engine, nil
}
// Starts the engine from a string JSON config
func StartEngineFromString(cfgJSON string, waitEngine int, t testing.TB) (*exec.Cmd, error) {
cfgPath := t.TempDir()
// A JSON configuration string has been passed to the object.
// It can be standalone or used to overwrite sections from an
// existing configuration file. In case it's the latter, ensure
// the file is processed towards the end.
filePath := filepath.Join(cfgPath, "zzz_dynamic_cgrates.json")
if err := os.WriteFile(filePath, []byte(cfgJSON), 0644); err != nil {
return nil, err
}
var err error
cfg, err := config.NewCGRConfigFromPath(context.TODO(), cfgPath)
if err != nil {
return nil, fmt.Errorf("could not init config from path %s: %v", cfgPath, err)
}
binPath, err := exec.LookPath("cgr-engine")
if err != nil {
return nil, err
}
flags := []string{"-config_path", cfg.ConfigPath}
engine := exec.Command(binPath, flags...)
if err := engine.Start(); err != nil {
return nil, fmt.Errorf("cgr-engine command failed: %v", err)
}
time.Sleep(time.Duration(waitEngine) * time.Millisecond) // wait for rater to register all subsystems
return engine, nil
}
// StartEngineWithContext return reference towards the command started so we can stop it if necessary
func StartEngineWithContext(ctx context.Context, cfgPath string, waitEngine int) (engine *exec.Cmd, err error) {
engine = exec.CommandContext(ctx, "cgr-engine", "-config_path", cfgPath)

View File

@@ -0,0 +1,715 @@
//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 <https://www.gnu.org/licenses/>
*/
package general_tests
import (
"io/fs"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/apis"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/loaders"
"github.com/cgrates/cgrates/utils"
)
func TestMultipleDBs(t *testing.T) {
if err := os.MkdirAll("/tmp/internal_db/db", 0755); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.RemoveAll("/tmp/internal_db"); err != nil {
t.Error(err)
}
})
ng := engine.TestEngine{
ConfigPath: filepath.Join(*utils.DataDir, "conf", "samples", "multiple_dbs"),
GracefulShutdown: true,
Encoding: *utils.Encoding,
}
client, cfg := ng.Run(t)
t.Run("LoadTariffPlans", func(t *testing.T) {
var reply string
if err := client.Call(context.Background(), utils.LoaderSv1Run,
&loaders.ArgsProcessFolder{
APIOpts: map[string]any{
utils.MetaCache: utils.MetaNone,
},
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply returned:", reply)
}
time.Sleep(100 * time.Millisecond)
})
t.Run("CheckChargers", func(t *testing.T) { // stored in redis2
var chrgrs []*utils.ChargerProfile
if err := client.Call(context.Background(), utils.AdminSv1GetChargerProfiles,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &chrgrs); err != nil {
t.Errorf("AdminSv1GetChargerProfiles failed unexpectedly: %v", err)
}
if len(chrgrs) != 3 {
t.Fatalf("AdminSv1GetChargerProfiles len(chrgrs)=%v, want 3", len(chrgrs))
}
sort.Slice(chrgrs, func(i, j int) bool {
return chrgrs[i].ID > chrgrs[j].ID
})
exp := []*utils.ChargerProfile{
{
ID: "SupplierCharges",
Tenant: "cgrates.org",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
RunID: "SupplierCharges",
AttributeIDs: []string{"ATTR_SUPPLIER1"},
},
{
ID: "Raw",
Tenant: "cgrates.org",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
RunID: "raw",
AttributeIDs: []string{"*constant:*req.RequestType:*none"},
},
{
ID: "CustomerCharges",
Tenant: "cgrates.org",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
RunID: "CustomerCharges",
AttributeIDs: []string{"*none"},
},
}
if !reflect.DeepEqual(exp, chrgrs) {
t.Errorf("Expected <%+v>,\n received <%+v>", exp, chrgrs)
}
})
t.Run("CheckChargerFilterIndexes", func(t *testing.T) { // stored in internal
var replyIdx []string
expectedIDx := []string{"*none:*any:*any:CustomerCharges", "*none:*any:*any:Raw",
"*none:*any:*any:SupplierCharges"}
if err := client.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
&apis.AttrGetFilterIndexes{
Tenant: utils.CGRateSorg,
ItemType: utils.MetaChargers,
},
&replyIdx); err != nil {
t.Error(err)
} else {
sort.Strings(replyIdx)
sort.Strings(expectedIDx)
if !reflect.DeepEqual(expectedIDx, replyIdx) {
t.Errorf("Expected %+v, \nreceived %+v", utils.ToJSON(expectedIDx), utils.ToJSON(replyIdx))
}
}
})
t.Run("CheckAccounts", func(t *testing.T) { // stored in *default (redis in this case)
var acnts []*utils.Account
if err := client.Call(context.Background(), utils.AdminSv1GetAccounts,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &acnts); err != nil {
t.Errorf("AdminSv2GetAccounts failed unexpectedly: %v", err)
}
if len(acnts) != 2 {
t.Fatalf("AdminSv2GetAccounts len(acnts)=%v, want 2", len(acnts))
}
sort.Slice(acnts, func(i, j int) bool {
return acnts[i].ID > acnts[j].ID
})
exp := []*utils.Account{
{
Tenant: "cgrates.org",
ID: "ACC_PRF_1",
Opts: map[string]any{},
Balances: map[string]*utils.Balance{
"MonetaryBalance": {
ID: "MonetaryBalance",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Type: "*monetary",
Opts: map[string]any{},
Units: utils.NewDecimal(14, 0),
UnitFactors: []*utils.UnitFactor{
{
FilterIDs: []string{"fltr1", "fltr2"},
Factor: utils.NewDecimal(100, 0),
},
{
FilterIDs: []string{"fltr3"},
Factor: utils.NewDecimal(200, 0),
},
},
CostIncrements: []*utils.CostIncrement{
{
FilterIDs: []string{"fltr1", "fltr2"},
Increment: utils.NewDecimal(13, 1),
FixedFee: utils.NewDecimal(23, 1),
RecurrentFee: utils.NewDecimal(33, 1),
},
},
AttributeIDs: []string{"attr1", "attr2"},
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
ThresholdIDs: []string{utils.MetaNone},
},
{
Tenant: "cgrates.org",
ID: "1001",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Opts: map[string]any{},
Balances: map[string]*utils.Balance{
"MonetaryBalance": {
ID: "MonetaryBalance",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Type: "*monetary",
Opts: map[string]any{},
Units: utils.NewDecimal(14, 0),
UnitFactors: []*utils.UnitFactor{
{
FilterIDs: []string{"fltr1", "fltr2"},
Factor: utils.NewDecimal(100, 0),
},
{
FilterIDs: []string{"fltr3"},
Factor: utils.NewDecimal(200, 0),
},
},
CostIncrements: []*utils.CostIncrement{
{
FilterIDs: []string{"fltr1", "fltr2"},
Increment: utils.NewDecimal(13, 1),
FixedFee: utils.NewDecimal(23, 1),
RecurrentFee: utils.NewDecimal(33, 1),
},
},
AttributeIDs: []string{"attr1", "attr2"},
},
"VoiceBalance": {
ID: "VoiceBalance",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
{
Weight: 10,
},
},
Blockers: utils.DynamicBlockers{
{
FilterIDs: []string{"*string:~*req.Destination:1002"},
Blocker: true,
},
{
Blocker: false,
},
},
Type: "*voice",
Opts: map[string]any{},
Units: utils.NewDecimalFromUsageIgnoreErr("1h"),
},
},
ThresholdIDs: []string{utils.MetaNone},
},
}
if !reflect.DeepEqual(exp, acnts) {
t.Errorf("Expected <%+v>,\n received <%+v>", exp, acnts)
}
})
t.Run("CheckCdrs", func(t *testing.T) { // stored in mysql
var cdrs []*utils.CDR
if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err == nil || err.Error() != "retrieving CDRs failed: NOT_FOUND" {
t.Errorf("Expecting error <%v>, received: <%v>", "retrieving CDRs failed: NOT_FOUND", err)
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestEv1",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.OriginID: "TestEv1",
utils.RequestType: utils.MetaPrepaid,
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.Usage: time.Minute,
},
APIOpts: map[string]any{
utils.MetaRates: true,
utils.MetaAccounts: false,
},
}
var rply string
client.Call(context.Background(), utils.CDRsV1ProcessEvent, ev, &rply)
if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err != nil {
t.Error(err)
}
if len(cdrs) != 1 {
t.Errorf("unexpected number of cdrs found: %v", len(cdrs))
}
exp := &utils.CDR{
Tenant: utils.CGRateSorg,
Opts: map[string]any{
utils.MetaCDRID: cdrs[0].Opts[utils.MetaCDRID],
utils.MetaRates: true,
utils.MetaAccounts: false,
},
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "1002",
utils.OriginID: "TestEv1",
utils.RequestType: utils.MetaPrepaid,
utils.Subject: "1001",
utils.ToR: utils.MetaVoice,
utils.Usage: 6e+10,
},
CreatedAt: cdrs[0].CreatedAt,
UpdatedAt: cdrs[0].UpdatedAt,
}
if !reflect.DeepEqual(exp, cdrs[0]) {
t.Errorf("Expecting <%#v>, \nreceived <%#v>", exp, cdrs[0])
}
})
t.Run("EngineShutdown", func(t *testing.T) {
if err := engine.KillEngine(100); err != nil {
t.Error(err)
}
})
t.Run("CountDBFiles", func(t *testing.T) {
var dirs, files int
if err := filepath.WalkDir(cfg.DbCfg().Opts.InternalDBDumpPath, func(root string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() {
dirs++
} else {
if !strings.HasPrefix(root, "/tmp/internal_db/db/*charger_filter_indexes") &&
!strings.HasPrefix(root, "/tmp/internal_db/db/*versions") {
t.Fatalf("got unexpected folder <%s>", root)
}
files++
}
return nil
}); err != nil {
t.Error(err)
} else if dirs != 37 {
t.Errorf("expected <%d> directories, received <%d>", 37, dirs)
} else if files != 2 {
t.Errorf("expected <%d> files, received <%d>", 2, files)
}
})
}
func TestMultipleDBsMongo(t *testing.T) {
if err := os.MkdirAll("/tmp/internal_db/db", 0755); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.RemoveAll("/tmp/internal_db"); err != nil {
t.Error(err)
}
})
ng := engine.TestEngine{
ConfigPath: filepath.Join(*utils.DataDir, "conf", "samples", "multiple_dbs_mongo"),
GracefulShutdown: true,
Encoding: *utils.Encoding,
}
client, cfg := ng.Run(t)
t.Run("LoadTariffPlans", func(t *testing.T) {
var reply string
if err := client.Call(context.Background(), utils.LoaderSv1Run,
&loaders.ArgsProcessFolder{
APIOpts: map[string]any{
utils.MetaCache: utils.MetaNone,
},
}, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected reply returned:", reply)
}
time.Sleep(100 * time.Millisecond)
})
t.Run("CheckChargers", func(t *testing.T) { // stored in mongo
var chrgrs []*utils.ChargerProfile
if err := client.Call(context.Background(), utils.AdminSv1GetChargerProfiles,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &chrgrs); err != nil {
t.Errorf("AdminSv1GetChargerProfiles failed unexpectedly: %v", err)
}
if len(chrgrs) != 3 {
t.Fatalf("AdminSv1GetChargerProfiles len(chrgrs)=%v, want 3", len(chrgrs))
}
sort.Slice(chrgrs, func(i, j int) bool {
return chrgrs[i].ID > chrgrs[j].ID
})
exp := []*utils.ChargerProfile{
{
ID: "SupplierCharges",
Tenant: "cgrates.org",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
RunID: "SupplierCharges",
AttributeIDs: []string{"ATTR_SUPPLIER1"},
},
{
ID: "Raw",
Tenant: "cgrates.org",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
RunID: "raw",
AttributeIDs: []string{"*constant:*req.RequestType:*none"},
},
{
ID: "CustomerCharges",
Tenant: "cgrates.org",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
RunID: "CustomerCharges",
AttributeIDs: []string{"*none"},
},
}
if !reflect.DeepEqual(exp, chrgrs) {
t.Errorf("Expected <%+v>,\n received <%+v>", exp, chrgrs)
}
})
t.Run("CheckChargerFilterIndexes", func(t *testing.T) { // stored in internal
var replyIdx []string
expectedIDx := []string{"*none:*any:*any:CustomerCharges", "*none:*any:*any:Raw",
"*none:*any:*any:SupplierCharges"}
if err := client.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
&apis.AttrGetFilterIndexes{
Tenant: utils.CGRateSorg,
ItemType: utils.MetaChargers,
},
&replyIdx); err != nil {
t.Error(err)
} else {
sort.Strings(replyIdx)
sort.Strings(expectedIDx)
if !reflect.DeepEqual(expectedIDx, replyIdx) {
t.Errorf("Expected %+v, \nreceived %+v", utils.ToJSON(expectedIDx), utils.ToJSON(replyIdx))
}
}
})
t.Run("CheckAccounts", func(t *testing.T) { // stored in *default (redis in this case)
var acnts []*utils.Account
if err := client.Call(context.Background(), utils.AdminSv1GetAccounts,
&utils.ArgsItemIDs{
Tenant: "cgrates.org",
}, &acnts); err != nil {
t.Errorf("AdminSv2GetAccounts failed unexpectedly: %v", err)
}
if len(acnts) != 2 {
t.Fatalf("AdminSv2GetAccounts len(acnts)=%v, want 2", len(acnts))
}
sort.Slice(acnts, func(i, j int) bool {
return acnts[i].ID > acnts[j].ID
})
exp := []*utils.Account{
{
Tenant: "cgrates.org",
ID: "ACC_PRF_1",
Opts: map[string]any{},
Balances: map[string]*utils.Balance{
"MonetaryBalance": {
ID: "MonetaryBalance",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Type: "*monetary",
Opts: map[string]any{},
Units: utils.NewDecimal(14, 0),
UnitFactors: []*utils.UnitFactor{
{
FilterIDs: []string{"fltr1", "fltr2"},
Factor: utils.NewDecimal(100, 0),
},
{
FilterIDs: []string{"fltr3"},
Factor: utils.NewDecimal(200, 0),
},
},
CostIncrements: []*utils.CostIncrement{
{
FilterIDs: []string{"fltr1", "fltr2"},
Increment: utils.NewDecimal(13, 1),
FixedFee: utils.NewDecimal(23, 1),
RecurrentFee: utils.NewDecimal(33, 1),
},
},
AttributeIDs: []string{"attr1", "attr2"},
},
},
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
ThresholdIDs: []string{utils.MetaNone},
},
{
Tenant: "cgrates.org",
ID: "1001",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Opts: map[string]any{},
Balances: map[string]*utils.Balance{
"MonetaryBalance": {
ID: "MonetaryBalance",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Type: "*monetary",
Opts: map[string]any{},
Units: utils.NewDecimal(14, 0),
UnitFactors: []*utils.UnitFactor{
{
FilterIDs: []string{"fltr1", "fltr2"},
Factor: utils.NewDecimal(100, 0),
},
{
FilterIDs: []string{"fltr3"},
Factor: utils.NewDecimal(200, 0),
},
},
CostIncrements: []*utils.CostIncrement{
{
FilterIDs: []string{"fltr1", "fltr2"},
Increment: utils.NewDecimal(13, 1),
FixedFee: utils.NewDecimal(23, 1),
RecurrentFee: utils.NewDecimal(33, 1),
},
},
AttributeIDs: []string{"attr1", "attr2"},
},
"VoiceBalance": {
ID: "VoiceBalance",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
{
Weight: 10,
},
},
Blockers: utils.DynamicBlockers{
{
FilterIDs: []string{"*string:~*req.Destination:1002"},
Blocker: true,
},
{
Blocker: false,
},
},
Type: "*voice",
Opts: map[string]any{},
Units: utils.NewDecimalFromUsageIgnoreErr("1h"),
},
},
ThresholdIDs: []string{utils.MetaNone},
},
}
if !reflect.DeepEqual(exp, acnts) {
t.Errorf("Expected <%+v>,\n received <%+v>", exp, acnts)
}
})
t.Run("CheckCdrs", func(t *testing.T) { // stored in mongo
var cdrs []*utils.CDR
if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err == nil || err.Error() != "retrieving CDRs failed: NOT_FOUND" {
t.Errorf("Expecting error <%v>, received: <%v>", "retrieving CDRs failed: NOT_FOUND", err)
}
ev := &utils.CGREvent{
Tenant: "cgrates.org",
ID: "TestEv1",
Event: map[string]any{
utils.ToR: utils.MetaVoice,
utils.OriginID: "TestEv1",
utils.RequestType: utils.MetaPrepaid,
utils.AccountField: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.Usage: time.Minute,
},
APIOpts: map[string]any{
utils.MetaRates: true,
utils.MetaAccounts: false,
},
}
var rply string
client.Call(context.Background(), utils.CDRsV1ProcessEvent, ev, &rply)
if err := client.Call(context.Background(), utils.AdminSv1GetCDRs, &utils.CDRFilters{Tenant: "cgrates.org"}, &cdrs); err != nil {
t.Error(err)
}
if len(cdrs) != 1 {
t.Errorf("unexpected number of cdrs found: %v", len(cdrs))
}
exp := &utils.CDR{
Tenant: utils.CGRateSorg,
Opts: map[string]any{
utils.MetaCDRID: cdrs[0].Opts[utils.MetaCDRID],
utils.MetaRates: true,
utils.MetaAccounts: false,
},
Event: map[string]any{
utils.AccountField: "1001",
utils.Destination: "1002",
utils.OriginID: "TestEv1",
utils.RequestType: utils.MetaPrepaid,
utils.Subject: "1001",
utils.ToR: utils.MetaVoice,
utils.Usage: 6e+10,
},
CreatedAt: cdrs[0].CreatedAt,
UpdatedAt: cdrs[0].UpdatedAt,
}
if !reflect.DeepEqual(exp, cdrs[0]) {
t.Errorf("Expecting <%#v>, \nreceived <%#v>", exp, cdrs[0])
}
})
t.Run("EngineShutdown", func(t *testing.T) {
if err := engine.KillEngine(100); err != nil {
t.Error(err)
}
})
t.Run("CountDBFiles", func(t *testing.T) {
var dirs, files int
if err := filepath.WalkDir(cfg.DbCfg().Opts.InternalDBDumpPath, func(root string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() {
dirs++
} else {
if !strings.HasPrefix(root, "/tmp/internal_db/db/*charger_filter_indexes") &&
!strings.HasPrefix(root, "/tmp/internal_db/db/*versions") {
t.Fatalf("got unexpected folder <%s>", root)
}
files++
}
return nil
}); err != nil {
t.Error(err)
} else if dirs != 37 {
t.Errorf("expected <%d> directories, received <%d>", 37, dirs)
} else if files != 2 {
t.Errorf("expected <%d> files, received <%d>", 2, files)
}
})
}
func TestMultipleDBsInternalFail(t *testing.T) {
if err := os.MkdirAll("/tmp/internal_db/db", 0755); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.RemoveAll("/tmp/internal_db"); err != nil {
t.Error(err)
}
})
cfgJSON := `{
"logger": {
"level": 7
},
"db": {
"db_conns": {
"intrnl": {
"db_type": "*internal"
},
},
"items":{
"*charger_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false, "dbConn": "intrnl"},
},
"opts": {
"internalDBDumpPath": "/tmp/internal_db/db",
"internalDBRewriteInterval": "0s"
}
},
}
`
expErr := `/001: <db> There can only be 1 internal DB`
if _, err := engine.StartEngineFromString(cfgJSON, 200, t); err == nil ||
!strings.Contains(err.Error(), expErr) {
t.Errorf("expected error <%v>, received <%v>", expErr, err)
}
}

View File

@@ -62,9 +62,6 @@ func TestOfflineInternal(t *testing.T) { // run with sudo
if err := os.MkdirAll(dfltCfg.DbCfg().Opts.InternalDBDumpPath, 0755); err != nil {
t.Fatal(err)
}
// if err := os.MkdirAll(dfltCfg.StorDbCfg().Opts.InternalDBDumpPath, 0755); err != nil {
// t.Fatal(err)
// }
if err := os.MkdirAll(dfltCfg.ConfigDBCfg().Opts.InternalDBDumpPath, 0755); err != nil {
t.Fatal(err)
}