Files
cgrates/accounts/accounts_test.go
2021-04-26 18:15:50 +02:00

1792 lines
52 KiB
Go

/*
Real-time Online/Offline Charging System (OerS) 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 <http://www.gnu.org/licenses/>
*/
package accounts
import (
"bytes"
"log"
"os"
"reflect"
"strings"
"testing"
"time"
"github.com/cgrates/birpc"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/rates"
"github.com/cgrates/cgrates/utils"
"github.com/ericlagergren/decimal"
)
func TestShutDownCoverage(t *testing.T) {
//this is called in order to cover the ListenAndServe method
cfg := config.NewDefaultCGRConfig()
dm := engine.NewDataManager(nil, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
stopChan := make(chan struct{}, 1)
cfgRld := make(chan struct{}, 1)
cfgRld <- struct{}{}
go func() {
time.Sleep(10)
stopChan <- struct{}{}
}()
var err error
utils.Logger, err = utils.Newlogger(utils.MetaStdLog, utils.EmptyString)
if err != nil {
t.Error(err)
}
utils.Logger.SetLogLevel(7)
buff := new(bytes.Buffer)
log.SetOutput(buff)
accnts.ListenAndServe(stopChan, cfgRld)
//this is called in order to cover the ShutDown method
accnts.Shutdown()
expected := "CGRateS <> [INFO] <CoreS> shutdown <AccountS>"
if rcv := buff.String(); !strings.Contains(rcv, expected) {
t.Errorf("Expected %+v, received %+v", expected, rcv)
}
log.SetOutput(os.Stderr)
utils.Logger.SetLogLevel(6)
}
type dataDBMockErrorNotFound struct {
*engine.DataDBMock
}
func (dB *dataDBMockErrorNotFound) GetAccountDrv(string, string) (*utils.Account, error) {
return nil, utils.ErrNotFound
}
func TestMatchingAccountsForEventMockingErrors(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
fltr := engine.NewFilterS(cfg, nil, nil)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "1004",
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"ConcreteBalance1": {
ID: "ConcreteBalance1",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(0, 0)},
CostIncrements: []*utils.CostIncrement{
{
FilterIDs: []string{"*string:~*req.ToR:*data"},
Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
}
cgrEvent := &utils.CGREvent{
ID: "TestMatchingAccountsForEvent",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
},
}
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
accnts := NewAccountS(cfg, fltr, nil, dm)
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
mockDataDB := &dataDBMockErrorNotFound{}
//if the error is NOT_FOUND, continue to match the
newDm := engine.NewDataManager(mockDataDB, cfg.CacheCfg(), nil)
accnts = NewAccountS(cfg, fltr, nil, newDm)
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err != utils.ErrNotFound {
t.Errorf("Expected %+v, received %+v", utils.ErrNotFound, err)
}
//mocking error in order to get from data base
dataDB := &dataDBMockError{}
newDm = engine.NewDataManager(dataDB, cfg.CacheCfg(), nil)
accnts = NewAccountS(cfg, fltr, nil, newDm)
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err != utils.ErrNotImplemented {
t.Errorf("Expected %+v, received %+v", utils.ErrNotImplemented, err)
}
}
func TestMatchingAccountsForEvent(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "1004",
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2020, 7, 21, 0, 0, 0, 0, time.UTC),
ExpiryTime: time.Date(2020, 7, 21, 10, 0, 0, 0, time.UTC),
},
Weights: utils.DynamicWeights{
{
FilterIDs: []string{"invalid_filter_format"},
Weight: 20,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(0, 0)},
},
},
}
cgrEvent := &utils.CGREvent{
ID: "TestMatchingAccountsForEvent",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1003",
},
}
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err != utils.ErrNotFound {
t.Errorf("Expected %+v, received %+v", utils.ErrNotFound, err)
}
cgrEvent.Event[utils.AccountField] = "1004"
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
cgrEvent.APIOpts = make(map[string]interface{})
cgrEvent.Time = utils.TimePointer(time.Date(2020, 8, 21, 0, 0, 0, 0, time.UTC))
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err != utils.ErrNotFound {
t.Errorf("Expected %+v, received %+v", utils.ErrNotFound, err)
}
cgrEvent.Time = utils.TimePointer(time.Date(2020, 7, 21, 5, 0, 0, 0, time.UTC))
accPrf.FilterIDs = []string{"invalid_filter_format"}
expected := "NOT_FOUND:invalid_filter_format"
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.FilterIDs = []string{"*string:~*req.Account:1003"}
expected = "NOT_FOUND"
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.FilterIDs = []string{"*string:~*req.Account:1004"}
expected = "NOT_FOUND:invalid_filter_format"
if _, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Weights[0].FilterIDs = []string{}
expectedAccPrfWeght := utils.AccountsWithWeight{
{
Account: accPrf,
Weight: 20,
},
}
if rcv, err := accnts.matchingAccountsForEvent("cgrates.org", cgrEvent,
[]string{}, false); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedAccPrfWeght, rcv) {
t.Errorf("Expected %+v, received %+v", expectedAccPrfWeght, utils.ToJSON(rcv))
}
}
func TestAccountDebit(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "TestAccountDebit",
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"ConcreteBalance1": {
ID: "ConcreteBalance1",
Weights: utils.DynamicWeights{
{
FilterIDs: []string{"invalid_filter_format"},
Weight: 20,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(150, 0)},
},
"AbstractBalance1": {
ID: "AbstractBalance1",
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(200, 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
ThresholdIDs: []string{utils.MetaNone},
}
cgrEvent := &utils.CGREvent{
ID: "TEST_EVENT",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
},
}
usage := &utils.Decimal{decimal.New(190, 0)}
expected := "NOT_FOUND:invalid_filter_format"
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["ConcreteBalance1"].Weights[0].FilterIDs = []string{}
accPrf.Balances["ConcreteBalance1"].Type = "not_a_type"
expected = "unsupported balance type: <not_a_type>"
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["ConcreteBalance1"].Type = utils.MetaConcrete
usage = &utils.Decimal{decimal.New(0, 0)}
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err != nil {
t.Error(err)
}
usage = &utils.Decimal{decimal.New(190, 0)}
accPrf.Balances["ConcreteBalance1"].UnitFactors = []*utils.UnitFactor{
{
FilterIDs: []string{"invalid_format_type"},
Factor: &utils.Decimal{decimal.New(1, 0)},
},
}
expected = "NOT_FOUND:invalid_format_type"
if _, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["ConcreteBalance1"].UnitFactors[0].FilterIDs = []string{}
expectedUsage := &utils.Decimal{decimal.New(150, 0)}
if evCh, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(evCh.Concretes.Big, expectedUsage.Big) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expectedUsage.Big), utils.ToJSON(evCh.Concretes.Big))
}
}
func TestAccountsDebit(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accntsPrf := []*utils.AccountWithWeight{
{
Account: &utils.Account{
Tenant: "cgrates.org",
ID: "TestAccountsDebit",
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(40, 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(213, 0)},
},
},
},
},
}
cgrEvent := &utils.CGREvent{
ID: "TEST_EVENT",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.Usage: "not_time_format",
},
APIOpts: map[string]interface{}{},
}
expected := "time: invalid duration \"not_time_format\""
if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, false); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
delete(cgrEvent.Event, utils.Usage)
cgrEvent.APIOpts[utils.MetaUsage] = "not_time_format"
expected = "time: invalid duration \"not_time_format\""
if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, false); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
delete(cgrEvent.APIOpts, utils.MetaUsage)
if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, true, false); err != nil {
t.Error(err)
}
cgrEvent.APIOpts[utils.MetaUsage] = "0"
if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, false); err != nil {
t.Error(err)
}
cgrEvent.APIOpts[utils.MetaUsage] = "55s"
accntsPrf[0].Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{"invalid_filter_format"}
expected = "NOT_FOUND:invalid_filter_format"
if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accntsPrf[0].Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{}
cgrEvent.Event[utils.Usage] = "300ns"
if evCh, err := accnts.accountsDebit(accntsPrf, cgrEvent, true, true); err != nil {
t.Error(err)
} else if evCh != nil {
t.Errorf("received %+v", utils.ToJSON(evCh))
}
var err error
utils.Logger, err = utils.Newlogger(utils.MetaStdLog, utils.EmptyString)
if err != nil {
t.Error(err)
}
utils.Logger.SetLogLevel(7)
buff := new(bytes.Buffer)
log.SetOutput(buff)
accntsPrf[0].Balances["ConcreteBalance2"].Units = &utils.Decimal{decimal.New(213, 0)}
accnts.dm = nil
expected = utils.ErrNoDatabaseConn.Error()
if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, true, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
subString := "<AccountS> error <NO_DATABASE_CONNECTION> restoring account <cgrates.org:TestAccountsDebit>"
if rcv := buff.String(); !strings.Contains(rcv, subString) {
t.Errorf("Expected %+q, received %+q", subString, rcv)
}
log.SetOutput(os.Stderr)
utils.Logger.SetLogLevel(6)
}
func TestV1AccountsForEvent(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "1004",
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2020, 7, 21, 0, 0, 0, 0, time.UTC),
ExpiryTime: time.Date(2020, 7, 21, 10, 0, 0, 0, time.UTC),
},
Weights: utils.DynamicWeights{
{
FilterIDs: []string{"invalid_filter_format"},
Weight: 20,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(0, 0)},
},
},
}
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestMatchingAccountsForEvent",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
},
},
}
rply := make([]*utils.Account, 0)
expected := "SERVER_ERROR: NOT_FOUND:invalid_filter_format"
if err := accnts.V1AccountsForEvent(args, &rply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Weights[0].FilterIDs = []string{}
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
} else if err := accnts.V1AccountsForEvent(args, &rply); err != nil {
t.Errorf("Expected %+v, received %+v", expected, err)
} else if !reflect.DeepEqual(rply[0], accPrf) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(rply[0]), utils.ToJSON(accPrf))
}
}
func TestV1MaxAbstracts(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "TestV1MaxAbstracts",
Weights: []*utils.DynamicWeight{
{
FilterIDs: []string{"invalid_filter"},
Weight: 0,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(int64(40*time.Second), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(0, 0)},
},
},
},
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 20,
FilterIDs: []string{"invalid_filter"},
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(213, 0)},
},
},
}
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestMatchingAccountsForEvent",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.Usage: "210ns",
},
},
}
reply := utils.ExtEventCharges{}
expected := "SERVER_ERROR: NOT_FOUND:invalid_filter"
if err := accnts.V1MaxAbstracts(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Weights[0].FilterIDs = []string{}
expected = "NOT_FOUND:invalid_filter"
if err := accnts.V1MaxAbstracts(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
delete(accPrf.Balances, "ConcreteBalance2")
extAccPrf, err := accPrf.AsExtAccount()
if err != nil {
t.Error(err)
}
extAccPrf.Balances["AbstractBalance1"].Units = utils.Float64Pointer(float64(40*time.Second - 210*time.Nanosecond))
exEvCh := utils.ExtEventCharges{
Abstracts: utils.Float64Pointer(210),
Charges: []*utils.ChargeEntry{
{
ChargingID: "GENUUID1",
CompressFactor: 1,
},
},
Accounting: map[string]*utils.ExtAccountCharge{
"GENUUID1": {
AccountID: "TestV1MaxAbstracts",
BalanceID: "AbstractBalance1",
BalanceLimit: utils.Float64Pointer(0),
RatingID: "GENUUID_RATING",
},
},
UnitFactors: map[string]*utils.ExtUnitFactor{},
Rating: map[string]*utils.ExtRateSInterval{
"GENUUID_RATING": {
Increments: []*utils.ExtRateSIncrement{
{
Rate: &utils.ExtRate{
ID: "*costIncrement",
IntervalRates: []*utils.ExtIntervalRate{
{
FixedFee: utils.Float64Pointer(0),
RecurrentFee: utils.Float64Pointer(0),
},
},
},
IntervalRateIndex: 0,
CompressFactor: 1,
},
},
CompressFactor: 1,
},
},
Accounts: map[string]*utils.ExtAccount{
"TestV1MaxAbstracts": extAccPrf,
},
}
if err := accnts.V1MaxAbstracts(args, &reply); err != nil {
t.Error(err)
} else {
exEvCh.Charges = reply.Charges
exEvCh.Rating = reply.Rating
exEvCh.Accounting = reply.Accounting
if !reflect.DeepEqual(exEvCh, reply) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(exEvCh), utils.ToJSON(reply))
}
}
}
func TestV1DebitAbstracts(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "TestV1DebitAbstracts",
Weights: []*utils.DynamicWeight{
{
FilterIDs: []string{"invalid_filter"},
Weight: 0,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Weights: utils.DynamicWeights{
{
Weight: 25,
FilterIDs: []string{"invalid_filter"},
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(int64(40*time.Second), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
}
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestV1DebitID",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.Usage: "27s",
},
},
}
reply := utils.ExtEventCharges{}
expected := "SERVER_ERROR: NOT_FOUND:invalid_filter"
if err := accnts.V1DebitAbstracts(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Weights[0].FilterIDs = []string{}
expected = "NOT_FOUND:invalid_filter"
if err := accnts.V1DebitAbstracts(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{}
/*
exEvCh := utils.ExtEventCharges{
Abstracts: utils.Float64Pointer(float64(27 * time.Second)),
}
if err := accnts.V1DebitAbstracts(args, &reply); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(exEvCh, reply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(exEvCh), utils.ToJSON(reply))
}
*/
//now we'll check the debited account
accPrf.Balances["AbstractBalance1"].Units = &utils.Decimal{decimal.New(39999999973, 0)}
if debitedAcc, err := accnts.dm.GetAccount(accPrf.Tenant, accPrf.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(accPrf, debitedAcc) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(accPrf), utils.ToJSON(debitedAcc))
}
}
func TestV1MaxConcretes(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "TestV1DebitAbstracts",
Weights: []*utils.DynamicWeight{
{
FilterIDs: []string{"invalid_filter"},
Weight: 0,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Weights: utils.DynamicWeights{
{
Weight: 15,
FilterIDs: []string{"invalid_filter"},
},
},
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(int64(40*time.Second), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
"ConcreteBalance1": {
ID: "ConcreteBalance1",
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(int64(time.Minute), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 5,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(int64(30*time.Second), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
}
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestV1DebitID",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.Usage: "3m",
},
},
}
reply := utils.ExtEventCharges{}
expected := "SERVER_ERROR: NOT_FOUND:invalid_filter"
if err := accnts.V1MaxConcretes(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Weights[0].FilterIDs = []string{}
expected = "NOT_FOUND:invalid_filter"
if err := accnts.V1MaxConcretes(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{}
extAccPrf, err := accPrf.AsExtAccount()
if err != nil {
t.Error(err)
}
extAccPrf.Balances["ConcreteBalance1"].Units = utils.Float64Pointer(0)
extAccPrf.Balances["ConcreteBalance2"].Units = utils.Float64Pointer(0)
exEvCh := utils.ExtEventCharges{
Concretes: utils.Float64Pointer(float64(time.Minute + 30*time.Second)),
Charges: []*utils.ChargeEntry{
{
ChargingID: "GENUUID1",
CompressFactor: 1,
},
{
ChargingID: "GENUUID2",
CompressFactor: 1,
},
},
Accounting: map[string]*utils.ExtAccountCharge{
"GENUUID1": {
AccountID: "TestV1DebitAbstracts",
BalanceID: "ConcreteBalance1",
Units: utils.Float64Pointer(float64(time.Minute)),
BalanceLimit: utils.Float64Pointer(0),
},
"GENUUID2": {
AccountID: "TestV1DebitAbstracts",
BalanceID: "ConcreteBalance2",
Units: utils.Float64Pointer(float64(30 * time.Second)),
BalanceLimit: utils.Float64Pointer(0),
},
},
UnitFactors: map[string]*utils.ExtUnitFactor{},
Rating: map[string]*utils.ExtRateSInterval{},
Accounts: map[string]*utils.ExtAccount{
"TestV1DebitAbstracts": extAccPrf,
},
}
if err := accnts.V1MaxConcretes(args, &reply); err != nil {
t.Error(err)
} else {
exEvCh.Charges = reply.Charges
exEvCh.Accounting = reply.Accounting
if !reflect.DeepEqual(exEvCh, reply) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(exEvCh), utils.ToJSON(reply))
}
}
}
func TestV1DebitConcretes(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := &utils.Account{
Tenant: "cgrates.org",
ID: "TestV1DebitAbstracts",
Weights: []*utils.DynamicWeight{
{
FilterIDs: []string{"invalid_filter"},
Weight: 0,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Weights: utils.DynamicWeights{
{
Weight: 15,
FilterIDs: []string{"invalid_filter"},
},
},
Type: utils.MetaAbstract,
Units: &utils.Decimal{decimal.New(int64(40*time.Second), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
"ConcreteBalance1": {
ID: "ConcreteBalance1",
Weights: utils.DynamicWeights{
{
Weight: 25,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(int64(time.Minute), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 5,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(int64(30*time.Second), 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
}
if err := accnts.dm.SetAccount(accPrf, true); err != nil {
t.Error(err)
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestV1DebitID",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.Usage: "3m",
},
},
}
reply := utils.ExtEventCharges{}
expected := "SERVER_ERROR: NOT_FOUND:invalid_filter"
if err := accnts.V1DebitConcretes(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Weights[0].FilterIDs = []string{}
expected = "NOT_FOUND:invalid_filter"
if err := accnts.V1DebitConcretes(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accPrf.Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{}
extAccPrf, err := accPrf.AsExtAccount()
if err != nil {
t.Error(err)
}
extAccPrf.Balances["ConcreteBalance1"].Units = utils.Float64Pointer(0)
extAccPrf.Balances["ConcreteBalance2"].Units = utils.Float64Pointer(0)
exEvCh := utils.ExtEventCharges{
Concretes: utils.Float64Pointer(float64(time.Minute + 30*time.Second)),
Charges: []*utils.ChargeEntry{
{
ChargingID: "GENUUID1",
CompressFactor: 1,
},
{
ChargingID: "GENUUID2",
CompressFactor: 1,
},
},
Accounting: map[string]*utils.ExtAccountCharge{
"GENUUID1": {
AccountID: "TestV1DebitAbstracts",
BalanceID: "ConcreteBalance1",
Units: utils.Float64Pointer(60000000000),
BalanceLimit: utils.Float64Pointer(0),
},
"GENUUID2": {
AccountID: "TestV1DebitAbstracts",
BalanceID: "ConcreteBalance2",
Units: utils.Float64Pointer(30000000000),
BalanceLimit: utils.Float64Pointer(0),
},
},
UnitFactors: map[string]*utils.ExtUnitFactor{},
Rating: map[string]*utils.ExtRateSInterval{},
Accounts: map[string]*utils.ExtAccount{
"TestV1DebitAbstracts": extAccPrf,
},
}
if err := accnts.V1DebitConcretes(args, &reply); err != nil {
t.Error(err)
} else {
exEvCh.Accounting = reply.Accounting
exEvCh.Charges = reply.Charges
if !reflect.DeepEqual(exEvCh, reply) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(exEvCh), utils.ToJSON(reply))
}
}
//now we will check the debited account
rcv, err := accnts.dm.GetAccount("cgrates.org", "TestV1DebitAbstracts")
if err != nil {
t.Error(err)
}
accPrf.Balances["ConcreteBalance1"].Units = &utils.Decimal{decimal.New(0, 0)}
accPrf.Balances["ConcreteBalance2"].Units = &utils.Decimal{decimal.New(0, 0)}
if !reflect.DeepEqual(rcv, accPrf) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(accPrf), utils.ToJSON(rcv))
}
}
func TestMultipleAccountsFail(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
accPrf := []*utils.Account{
{
Tenant: "cgrates.org",
ID: "TestV1MaxAbstracts",
Weights: []*utils.DynamicWeight{
{
Weight: 20,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(213, 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
},
{
Tenant: "cgrates.org",
ID: "TestV1MaxAbstracts2",
Weights: []*utils.DynamicWeight{
{
Weight: 10,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(213, 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
},
{
Tenant: "cgrates.org",
ID: "TestV1MaxAbstracts3",
Weights: []*utils.DynamicWeight{
{
FilterIDs: []string{"invalid_format"},
Weight: 5,
},
},
FilterIDs: []string{"*string:~*req.Account:1004"},
Balances: map[string]*utils.Balance{
"ConcreteBalance2": {
ID: "ConcreteBalance2",
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(213, 0)},
CostIncrements: []*utils.CostIncrement{
{
Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
},
},
},
},
}
args := &utils.CGREvent{
ID: "TestMatchingAccountsForEvent",
Tenant: "cgrates.org",
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.Usage: "210ns",
},
}
if err := accnts.dm.SetAccount(accPrf[0], true); err != nil {
t.Error(err)
}
if err := accnts.dm.SetAccount(accPrf[1], true); err != nil {
t.Error(err)
}
if err := accnts.dm.SetAccount(accPrf[2], true); err != nil {
t.Error(err)
}
expected := "NOT_FOUND:invalid_format"
if _, err := accnts.matchingAccountsForEvent("cgrates.org", args,
[]string{"TestV1MaxAbstracts", "TestV1MaxAbstracts2", "TestV1MaxAbstracts3"}, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
expected = "NOT_FOUND:invalid_format"
if _, err := accnts.matchingAccountsForEvent("cgrates.org", args,
[]string{"TestV1MaxAbstracts", "TestV1MaxAbstracts2", "TestV1MaxAbstracts3"}, true); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
}
func TestV1ActionSetBalance(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
args := &utils.ArgsActSetBalance{
Reset: false,
}
var reply string
args.AccountID = ""
expected := "MANDATORY_IE_MISSING: [AccountID]"
if err := accnts.V1ActionSetBalance(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
args.AccountID = "TestV1ActionSetBalance"
expected = "MANDATORY_IE_MISSING: [Diktats]"
if err := accnts.V1ActionSetBalance(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
args.Diktats = []*utils.BalDiktat{
{
Path: "*balance.AbstractBalance1",
Value: "10",
},
}
expected = "WRONG_PATH"
if err := accnts.V1ActionSetBalance(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
args.Diktats = []*utils.BalDiktat{
{
Path: "*balance.AbstractBalance1.Units",
Value: "10",
},
}
args.Tenant = "cgrates.org"
if err := accnts.V1ActionSetBalance(args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected status reply", reply)
}
expectedAcc := &utils.Account{
Tenant: "cgrates.org",
ID: "TestV1ActionSetBalance",
Balances: map[string]*utils.Balance{
"AbstractBalance1": {
ID: "AbstractBalance1",
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(10, 0)},
CostIncrements: []*utils.CostIncrement{
{
FilterIDs: []string{"*string:~*req.ToR:*voice"},
Increment: utils.NewDecimal(int64(time.Second), 0),
RecurrentFee: utils.NewDecimal(0, 0),
},
{
FilterIDs: []string{"*string:~*req.ToR:*data"},
Increment: utils.NewDecimal(1024*1024, 0),
RecurrentFee: utils.NewDecimal(0, 0),
},
{
FilterIDs: []string{"*string:~*req.ToR:*sms"},
Increment: utils.NewDecimal(1, 0),
RecurrentFee: utils.NewDecimal(0, 0),
},
},
},
},
}
if rcv, err := accnts.dm.GetAccount(args.Tenant, args.AccountID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedAcc, rcv) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedAcc), utils.ToJSON(rcv))
}
}
func TestV1ActionRemoveBalance(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltr := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltr, nil, dm)
//firstly we will set a balance in order to remove it
argsSet := &utils.ArgsActSetBalance{
AccountID: "TestV1ActionRemoveBalance",
Tenant: "cgrates.org",
Diktats: []*utils.BalDiktat{
{
Path: "*balance.AbstractBalance1.Units",
Value: "10",
},
},
Reset: false,
}
var reply string
if err := accnts.V1ActionSetBalance(argsSet, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected status reply", reply)
}
//remove it
args := &utils.ArgsActRemoveBalances{}
expected := "MANDATORY_IE_MISSING: [AccountID]"
if err := accnts.V1ActionRemoveBalance(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
args.AccountID = "TestV1ActionRemoveBalance"
expected = "MANDATORY_IE_MISSING: [BalanceIDs]"
if err := accnts.V1ActionRemoveBalance(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
args.BalanceIDs = []string{"AbstractBalance1"}
expected = utils.ErrNoDatabaseConn.Error()
accnts.dm = nil
if err := accnts.V1ActionRemoveBalance(args, &reply); err == nil || err.Error() != expected {
t.Errorf("Expected %+v, received %+v", expected, err)
}
accnts.dm = engine.NewDataManager(data, cfg.CacheCfg(), nil)
if err := accnts.V1ActionRemoveBalance(args, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Error("Unexpected status reply", reply)
}
}
// TestV1DebitAbstractsEventCharges is designed to cover multiple EventCharges merges
func TestV1DebitAbstractsEventCharges(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltrS := engine.NewFilterS(cfg, nil, dm)
// Set the internal rateS within connMngr
rateSConn := make(chan birpc.ClientConnector, 1)
srv, _ := birpc.NewService(rates.NewRateS(cfg, fltrS, dm), utils.RateSv1, true)
srv.UpdateMethodName(func(key string) (newKey string) { return strings.TrimPrefix(key, "V1") }) // update the name of the functions
rateSConn <- srv
connMngr := engine.NewConnManager(cfg, map[string]chan birpc.ClientConnector{
utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS): rateSConn,
})
cfg.AccountSCfg().RateSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS)}
rtPfl := &utils.RateProfile{
Tenant: utils.CGRateSorg,
ID: "RP_1",
Rates: map[string]*utils.Rate{
"RT_1": {
ID: "RT_1",
IntervalRates: []*utils.IntervalRate{
{
IntervalStart: utils.NewDecimal(0, 0),
RecurrentFee: utils.NewDecimal(1, 2), // 0.01 per second
Unit: utils.NewDecimal(int64(time.Second), 0),
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
},
},
}
if err := dm.SetRateProfile(context.Background(), rtPfl, true); err != nil {
t.Error(err)
}
accnts := NewAccountS(cfg, fltrS, connMngr, dm)
ab1ID := "AB1"
ab2ID := "AB2"
ab3ID := "AB3"
cb1ID := "CB1"
cb2ID := "CB2"
// populate the Account
acnt1 := &utils.Account{
Tenant: utils.CGRateSorg,
ID: "TestV1DebitAbstractsEventCharges1",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
Balances: map[string]*utils.Balance{
ab1ID: { // cost: 0.4 connectFee plus 0.2 per minute, available 2 minutes, should remain 10s
ID: ab1ID,
Type: utils.MetaAbstract,
Weights: utils.DynamicWeights{
{
Weight: 40,
},
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Minute), 0),
FixedFee: utils.NewDecimal(4, 1), // 0.4
RecurrentFee: utils.NewDecimal(2, 1)}, // 0.2 per minute
},
Units: utils.NewDecimal(int64(130*time.Second), 0), // 2 Minute 10s, rest 10s
},
cb1ID: { // paying the AB1 plus own debit of 0.1 per second, limit of -200 cents
ID: cb1ID,
Type: utils.MetaConcrete,
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
Opts: map[string]interface{}{
utils.MetaBalanceLimit: -200.0,
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
RecurrentFee: utils.NewDecimal(1, 1)}, // 0.1 per second
},
UnitFactors: []*utils.UnitFactor{
{
Factor: utils.NewDecimal(100, 0), // EuroCents
},
},
Units: utils.NewDecimal(80, 0), // 80 EuroCents for the debit from AB1, rest for 20 seconds of limit
},
// 2m20s ABSTR, 2.8 CONCR
ab2ID: { // continues debitting after CB1, 0 cost in increments of seconds, maximum of 1 minute
ID: ab2ID,
Type: utils.MetaAbstract,
Weights: utils.DynamicWeights{
{
Weight: 20,
},
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
RecurrentFee: utils.NewDecimal(0, 0)},
},
Units: utils.NewDecimal(int64(1*time.Minute), 0), // 1 Minute, no cost
},
// 3m20s ABSTR, 2.8 CONCR
ab3ID: { // not matching due to filter
ID: ab3ID,
Type: utils.MetaAbstract,
FilterIDs: []string{"*string:*~req.Account:AnotherAccount"},
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
RecurrentFee: utils.NewDecimal(1, 0)},
},
Units: utils.NewDecimal(int64(60*time.Second), 0), // 1 Minute
},
//3m20s ABSTR 2.8 CONCR
cb2ID: &utils.Balance{ //125s with rating from RateS
ID: cb2ID,
Type: utils.MetaConcrete,
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
Units: utils.NewDecimal(125, 2), // 1.25
},
//5m25s ABSTR, 4.05 CONCR
},
}
if err := dm.SetAccount(acnt1, true); err != nil {
t.Error(err)
}
acnt2 := &utils.Account{
Tenant: utils.CGRateSorg,
ID: "TestV1DebitAbstractsEventCharges2",
Balances: map[string]*utils.Balance{
ab1ID: &utils.Balance{ // cost: 0.4 connectFee plus 0.2 per minute, available 2 minutes, should remain 10s
ID: ab1ID,
Type: utils.MetaAbstract,
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Minute), 0),
FixedFee: utils.NewDecimal(4, 1), // 0.4
RecurrentFee: utils.NewDecimal(2, 1)}, // 0.2 per minute
},
Units: utils.NewDecimal(int64(130*time.Second), 0),
},
//7m25s ABSTR, 4.85 CONCR
cb1ID: &utils.Balance{ // absorb all costs, standard rating used when primary debiting
ID: cb1ID,
Type: utils.MetaConcrete,
Opts: map[string]interface{}{
utils.MetaBalanceUnlimited: true,
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
},
},
Units: utils.NewDecimal(1, 0), //0.8 covering the first balance 20s on it's own with RateS
},
//7m45s ABSTR, 5.05 CONCR on 0 for CB1
//7m55s ABSTR, 5.15 CONCR on -0.1 for CB1
},
}
if err := dm.SetAccount(acnt2, true); err != nil {
t.Error(err)
}
eEvChgs := utils.ExtEventCharges{
Abstracts: utils.Float64Pointer(475000000000),
Concretes: utils.Float64Pointer(5.15),
Charges: []*utils.ChargeEntry{
{
ChargingID: "GENUUID1",
CompressFactor: 1,
},
{
ChargingID: "GENUUID2",
CompressFactor: 1,
},
{
ChargingID: "GENUUID3",
CompressFactor: 1,
},
{
ChargingID: "GENUUID4",
CompressFactor: 1,
},
{
ChargingID: "GENUUID5",
CompressFactor: 1,
},
{
ChargingID: "GENUUID6",
CompressFactor: 1,
},
},
Accounting: map[string]*utils.ExtAccountCharge{
"GENUUID_GHOST1": {
AccountID: "TestV1DebitAbstractsEventCharges1",
BalanceID: cb1ID,
Units: utils.Float64Pointer(0.8),
BalanceLimit: utils.Float64Pointer(-200),
UnitFactorID: "GENUUID_FACTOR1",
},
"GENUUID3": {
AccountID: "TestV1DebitAbstractsEventCharges1",
BalanceID: ab2ID,
BalanceLimit: utils.Float64Pointer(0),
RatingID: "GENUUID_RATING1",
},
"GENUUID2": {
AccountID: "TestV1DebitAbstractsEventCharges1",
BalanceID: cb1ID,
Units: utils.Float64Pointer(2),
BalanceLimit: utils.Float64Pointer(-200),
UnitFactorID: "GENUUID_FACTOR2",
},
"GENUUID5": {
AccountID: "TestV1DebitAbstractsEventCharges2",
BalanceID: ab1ID,
BalanceLimit: utils.Float64Pointer(0),
RatingID: "GENUUID_RATING2",
JoinedChargeIDs: []string{"GENUUID_GHOST2"},
},
"GENUUID6": {
AccountID: "TestV1DebitAbstractsEventCharges2",
BalanceID: cb1ID,
Units: utils.Float64Pointer(0.3),
},
"GENUUID4": {
AccountID: "TestV1DebitAbstractsEventCharges1",
BalanceID: cb2ID,
Units: utils.Float64Pointer(1.25),
BalanceLimit: utils.Float64Pointer(0),
},
"GENUUID_GHOST2": {
AccountID: "TestV1DebitAbstractsEventCharges2",
BalanceID: cb1ID,
Units: utils.Float64Pointer(0.6),
},
"GENUUID1": {
AccountID: "TestV1DebitAbstractsEventCharges1",
BalanceID: ab1ID,
BalanceLimit: utils.Float64Pointer(0),
RatingID: "GENUUID_RATING3",
JoinedChargeIDs: []string{"GENUUID_GHOST1"},
},
},
UnitFactors: map[string]*utils.ExtUnitFactor{
"GENUUID_FACTOR1": {
Factor: utils.Float64Pointer(100),
},
"GENUUID_FACTOR2": {
Factor: utils.Float64Pointer(100),
},
},
Rating: map[string]*utils.ExtRateSInterval{
"GENUUID_RATING1": {
Increments: []*utils.ExtRateSIncrement{
{
Rate: &utils.ExtRate{
ID: "*costIncrement",
IntervalRates: []*utils.ExtIntervalRate{
{
RecurrentFee: utils.Float64Pointer(0),
},
},
},
IntervalRateIndex: 0,
CompressFactor: 1,
},
},
CompressFactor: 1,
},
"GENUUID_RATING2": {
Increments: []*utils.ExtRateSIncrement{
{
Rate: &utils.ExtRate{
ID: "*costIncrement",
IntervalRates: []*utils.ExtIntervalRate{
{
FixedFee: utils.Float64Pointer(0.4),
RecurrentFee: utils.Float64Pointer(0.2),
},
},
},
IntervalRateIndex: 0,
CompressFactor: 1,
},
},
CompressFactor: 1,
},
"GENUUID_RATING3": {
Increments: []*utils.ExtRateSIncrement{
{
Rate: &utils.ExtRate{
ID: "*costIncrement",
IntervalRates: []*utils.ExtIntervalRate{
{
FixedFee: utils.Float64Pointer(0.4),
RecurrentFee: utils.Float64Pointer(0.2),
},
},
},
IntervalRateIndex: 0,
CompressFactor: 1,
},
},
CompressFactor: 1,
},
},
Accounts: make(map[string]*utils.ExtAccount),
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestV1DebitAbstractsEventCharges",
Tenant: utils.CGRateSorg,
APIOpts: map[string]interface{}{
utils.MetaUsage: "7m55s", // 7m55s to debit both accounts
//utils.MetaUsage: "1m",
},
},
}
var rply utils.ExtEventCharges
if err := accnts.V1DebitAbstracts(args, &rply); err != nil {
t.Error(err)
}
acnt1.Balances[ab1ID].Units = utils.NewDecimal(int64(10*time.Second), 0)
acnt1.Balances[cb1ID].Units = utils.NewDecimal(-200, 0)
acnt1.Balances[ab2ID].Units = &utils.Decimal{new(decimal.Big).CopySign(decimal.New(0, 0), decimal.New(-1, 0))} // negative 0
acnt1.Balances[cb2ID].Units = utils.NewDecimal(0, 0)
if rcv, err := dm.GetAccount(acnt1.Tenant, acnt1.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt1) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt1), utils.ToJSON(rcv))
}
acnt2.Balances[ab1ID].Units = utils.NewDecimal(int64(10*time.Second), 0)
acnt2.Balances[cb1ID].Units = utils.NewDecimal(-1, 1)
if rcv, err := dm.GetAccount(acnt2.Tenant, acnt2.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt2) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt2), utils.ToJSON(rcv))
}
extAcnt1, err := acnt1.AsExtAccount()
if err != nil {
t.Error(err)
}
extAcnt2, err := acnt2.AsExtAccount()
if err != nil {
t.Error(err)
}
//as the names of accounting, charges, UF are GENUUIDs generator, we will change their names for comparing
eEvChgs.Accounts = map[string]*utils.ExtAccount{
"TestV1DebitAbstractsEventCharges1": extAcnt1,
"TestV1DebitAbstractsEventCharges2": extAcnt2,
}
eEvChgs.Charges = rply.Charges
eEvChgs.Accounting = rply.Accounting
eEvChgs.UnitFactors = rply.UnitFactors
eEvChgs.Accounts = rply.Accounts
eEvChgs.Rating = rply.Rating
if !reflect.DeepEqual(eEvChgs, rply) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(eEvChgs), utils.ToJSON(rply))
}
}
/*
func TestV1DebitAbstractsWithRecurrentFeeNegative(t *testing.T) {
engine.Cache.Clear(nil)
cfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
dm := engine.NewDataManager(data, cfg.CacheCfg(), nil)
fltrS := engine.NewFilterS(cfg, nil, dm)
accnts := NewAccountS(cfg, fltrS, nil, dm)
acnt := &utils.Account{
Tenant: "cgrates.org",
ID: "TestV1DebitAbstractsWithRecurrentFeeNegative",
Balances: map[string]*utils.Balance{
"ab1": &utils.Balance{
ID: "ab1",
Type: utils.MetaAbstract,
Weights: utils.DynamicWeights{
{
Weight: 30,
},
},
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
RecurrentFee: utils.NewDecimal(1, 0)}, // 1.0 per minute
},
Units: utils.NewDecimal(int64(40*time.Second), 0),
},
"cb1": &utils.Balance{
ID: "cb1",
Type: utils.MetaConcrete,
CostIncrements: []*utils.CostIncrement{
{
Increment: utils.NewDecimal(int64(time.Second), 0),
RecurrentFee: utils.NewDecimal(-1, 0),
},
},
Units: utils.NewDecimal(1, 0),
},
},
}
if err := dm.SetAccount(acnt, true); err != nil {
t.Error(err)
}
args := &utils.ArgsAccountsForEvent{
CGREvent: &utils.CGREvent{
ID: "TestV1DebitAbstractsWithRecurrentFeeNegative",
Tenant: "cgrates.org",
APIOpts: map[string]interface{}{
utils.MetaUsage: "72h",
},
},
}
expEvCh := &utils.ExtEventCharges{
Abstracts: utils.Float64Pointer(259200000000000),
Concretes: utils.Float64Pointer(-259198),
Accounting: map[string]*utils.ExtAccountCharge{},
UnitFactors: map[string]*utils.ExtUnitFactor{},
Rating: map[string]*utils.ExtRateSInterval{}}
ev := &utils.ExtEventCharges{}
if err := accnts.V1DebitAbstracts(args, ev); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(ev, expEvCh) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvCh), utils.ToJSON(ev))
}
acnt.Balances["ab1"].Units = utils.NewDecimal(int64(39*time.Second), 0)
acnt.Balances["cb1"].Units = utils.NewDecimal(259199, 0)
if rcv, err := dm.GetAccount(acnt.Tenant, acnt.ID); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(rcv, acnt) {
t.Errorf("Expected %+v,received %+v", utils.ToJSON(acnt), utils.ToJSON(rcv))
}
}
*/