/* 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 */ 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] shutdown " 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(*context.Context, 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(context.Background(), 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(context.Background(), "cgrates.org", cgrEvent, []string{}, false,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(context.Background(), "cgrates.org", cgrEvent, []string{}, false, 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", Weights: utils.DynamicWeights{ { FilterIDs: []string{"invalid_filter_format"}, Weight: 20, }, }, FilterIDs: []string{"*string:~*req.Account:1004", "*ai:~*req.AnswerTime:2020-07-21T00:00:00Z|2020-07-21T10:00:00Z"}, 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(context.Background(), "cgrates.org", cgrEvent, []string{}, false, 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(context.Background(), accPrf, true); err != nil { t.Error(err) } cgrEvent.APIOpts = make(map[string]interface{}) if _, err := accnts.matchingAccountsForEvent(context.Background(), "cgrates.org", cgrEvent, []string{}, false, true); err == nil || err != utils.ErrNotFound { t.Errorf("Expected %+v, received %+v", utils.ErrNotFound, err) } accPrf.FilterIDs = []string{"invalid_filter_format"} expected := "NOT_FOUND:invalid_filter_format" if _, err := accnts.matchingAccountsForEvent(context.Background(), "cgrates.org", cgrEvent, []string{}, false, 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(context.Background(), "cgrates.org", cgrEvent, []string{}, false, 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(context.Background(), "cgrates.org", cgrEvent, []string{}, false, 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(context.Background(), "cgrates.org", cgrEvent, []string{}, false, 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(context.Background(), accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); 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: " if _, err := accnts.accountDebit(context.Background(), accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); 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(context.Background(), accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); 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(context.Background(), accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); 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(context.Background(), accPrf, usage.Big, cgrEvent, true, decimal.New(0, 0)); 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 TestAccountsDebitGetUsage(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: "TestAccountsDebitGetUsage", FilterIDs: []string{"*prefix:~*req.Destination:+44"}, Balances: map[string]*utils.Balance{ "ConcreteBal1": { ID: "ConcreteBal1", Weights: utils.DynamicWeights{ { Weight: 10, }, }, Type: utils.MetaConcrete, Units: &utils.Decimal{decimal.New(90, 0)}, CostIncrements: []*utils.CostIncrement{ { Increment: &utils.Decimal{decimal.New(1, 0)}, FixedFee: &utils.Decimal{decimal.New(2, 1)}, RecurrentFee: &utils.Decimal{decimal.New(1, 0)}, }, }, }, }, }, }, } evChExp := &utils.EventCharges{ Abstracts: utils.NewDecimal(89, 0), Concretes: utils.NewDecimal(1484, 1), Charges: []*utils.ChargeEntry{ { ChargingID: "CHARGING1", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "CHARGING1": { AccountID: "TestAccountsDebitGetUsage", BalanceID: "*transabstract", Units: utils.NewDecimal(89, 0), RatingID: "RATING1", JoinedChargeIDs: []string{"JND_CHRG1", "JND_CHRG2"}, }, "JND_CHRG1": { AccountID: "TestAccountsDebitGetUsage", BalanceID: "ConcreteBal1", BalanceLimit: utils.NewDecimal(0, 0), Units: utils.NewDecimal(592, 1), }, "JND_CHRG2": { AccountID: "TestAccountsDebitGetUsage", BalanceID: "ConcreteBal1", BalanceLimit: utils.NewDecimal(0, 0), Units: utils.NewDecimal(892, 1), }, }, Rating: map[string]*utils.RateSInterval{ "RATING1": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, RateID: "RATE1", CompressFactor: 1, }, }, CompressFactor: 1, }, }, UnitFactors: map[string]*utils.UnitFactor{}, Rates: map[string]*utils.IntervalRate{}, Accounts: map[string]*utils.Account{ "TestAccountsDebitGetUsage": accntsPrf[0].Account, }, } // get usage from *ratesUsage cgrEvent := &utils.CGREvent{ ID: "TEST_EVENT_get_usage", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.Destination: "+445643", }, APIOpts: map[string]interface{}{ utils.OptsAccountsUsage: "2s", }, } if rcv, err := accnts.accountsDebit(context.Background(), accntsPrf, cgrEvent, false, false); err != nil { t.Error(err) } else if !rcv.Equals(evChExp) { t.Errorf("Expected %v, \n received %v", utils.ToJSON(evChExp), utils.ToJSON(rcv)) } // get usage from *usage //firstly reset the account accntsPrf[0].Account.Balances["ConcreteBal1"].Units = &utils.Decimal{decimal.New(90, 0)} accnts = NewAccountS(cfg, fltr, nil, dm) cgrEvent = &utils.CGREvent{ ID: "TEST_EVENT_get_usage", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.Destination: "+445643", }, APIOpts: map[string]interface{}{ utils.MetaUsage: "2s", }, } if rcv, err := accnts.accountsDebit(context.Background(), accntsPrf, cgrEvent, false, false); err != nil { t.Error(err) } else if !rcv.Equals(evChExp) { t.Errorf("Expected %v, \n received %v", utils.ToJSON(evChExp), utils.ToJSON(rcv)) } } 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", }, APIOpts: map[string]interface{}{ utils.OptsAccountsUsage: "not_time_format", }, } expected := "can't convert to decimal" if _, err := accnts.accountsDebit(context.Background(), accntsPrf, cgrEvent, false, false); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } delete(cgrEvent.APIOpts, utils.OptsAccountsUsage) cgrEvent.APIOpts[utils.MetaUsage] = "not_time_format" if _, err := accnts.accountsDebit(context.Background(), 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(context.Background(), accntsPrf, cgrEvent, true, false); err != nil { t.Error(err) } cgrEvent.APIOpts[utils.MetaUsage] = "0" if _, err := accnts.accountsDebit(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), accntsPrf, cgrEvent, true, true); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } subString := " error restoring account " 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", 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(context.Background(), accPrf, true); err != nil { t.Error(err) } ev := &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(context.Background(), ev, &rply); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } accPrf.Weights[0].FilterIDs = []string{} if err := accnts.dm.SetAccount(context.Background(), accPrf, true); err != nil { t.Error(err) } else if err := accnts.V1AccountsForEvent(context.Background(), ev, &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.NewDecimal(int64(40*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(0, 0), }, }, }, "ConcreteBalance2": { ID: "ConcreteBalance2", Weights: utils.DynamicWeights{ { Weight: 20, FilterIDs: []string{"invalid_filter"}, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(213, 0), }, }, } if err := accnts.dm.SetAccount(context.Background(), accPrf, true); err != nil { t.Error(err) } ev := &utils.CGREvent{ ID: "TestMatchingAccountsForEvent", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.AccountField: "1004", }, APIOpts: map[string]interface{}{ utils.MetaUsage: "210ns", }, } reply := utils.EventCharges{} expected := "SERVER_ERROR: NOT_FOUND:invalid_filter" if err := accnts.V1MaxAbstracts(context.Background(), ev, &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(context.Background(), ev, &reply); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } delete(accPrf.Balances, "ConcreteBalance2") exEvCh := utils.EventCharges{ Abstracts: utils.NewDecimal(210, 0), Charges: []*utils.ChargeEntry{ { ChargingID: "GENUUID1", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "GENUUID1": { AccountID: "TestV1MaxAbstracts", BalanceID: "AbstractBalance1", Units: utils.NewDecimal(210, 0), BalanceLimit: utils.NewDecimal(0, 0), RatingID: "GENUUID_RATING", }, }, UnitFactors: map[string]*utils.UnitFactor{}, Rating: map[string]*utils.RateSInterval{ "GENUUID_RATING": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, CompressFactor: 1, }, }, CompressFactor: 1, }, }, Rates: make(map[string]*utils.IntervalRate), Accounts: map[string]*utils.Account{ "TestV1MaxAbstracts": accPrf, }, } if err := accnts.V1MaxAbstracts(context.Background(), ev, &reply); err != nil { t.Error(err) } else { exEvCh.Accounts["TestV1MaxAbstracts"].Balances["AbstractBalance1"].Units = utils.NewDecimal(int64(40*time.Second-210), 0) if !reply.Equals(&exEvCh) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(exEvCh), utils.ToJSON(reply)) } } } func TestV1DebitAbstracts1(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, FilterIDs: []string{"invalid_filter"}, }, }, Type: utils.MetaAbstract, Units: utils.NewDecimal(int64(40*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, "ConcreteBalance2": { ID: "ConcreteBalance2", Weights: utils.DynamicWeights{ { Weight: 20, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(213, 0), // 213 - 27 }, }, } if err := accnts.dm.SetAccount(context.Background(), accPrf, true); err != nil { t.Error(err) } ev := &utils.CGREvent{ ID: "TestV1DebitID", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.AccountField: "1004", }, APIOpts: map[string]interface{}{ utils.MetaUsage: "27s", }, } reply := utils.EventCharges{} expected := "SERVER_ERROR: NOT_FOUND:invalid_filter" if err := accnts.V1DebitAbstracts(context.Background(), ev, &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(context.Background(), ev, &reply); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } accPrf.Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{} exEvCh := utils.EventCharges{ Abstracts: utils.NewDecimal(int64(27*time.Second), 0), Concretes: utils.NewDecimal(27, 0), Charges: []*utils.ChargeEntry{ { ChargingID: "CHARGE1", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "CHARGE1": { AccountID: "TestV1MaxAbstracts", BalanceID: "AbstractBalance1", Units: utils.NewDecimal(int64(27*time.Second), 0), BalanceLimit: utils.NewDecimal(0, 0), RatingID: "RATING1", JoinedChargeIDs: []string{"JoinedCh1"}, }, "JoinedCh1": { AccountID: "TestV1MaxAbstracts", BalanceID: "ConcreteBalance2", BalanceLimit: utils.NewDecimal(0, 0), Units: utils.NewDecimal(27, 0), }, }, UnitFactors: make(map[string]*utils.UnitFactor), Rating: map[string]*utils.RateSInterval{ "RATING1": { Increments: []*utils.RateSIncrement{ { RateID: "41ded73", CompressFactor: 1, }, }, CompressFactor: 1, }, }, Rates: make(map[string]*utils.IntervalRate), Accounts: map[string]*utils.Account{ "TestV1MaxAbstracts": accPrf, }, } if err := accnts.V1DebitAbstracts(context.Background(), ev, &reply); err != nil { t.Error(err) } else { exEvCh.Accounts["TestV1MaxAbstracts"].Balances["AbstractBalance1"].Units = utils.NewDecimal(int64(13*time.Second), 0) exEvCh.Accounts["TestV1MaxAbstracts"].Balances["ConcreteBalance2"].Units = utils.NewDecimal(186, 0) if !exEvCh.Equals(&reply) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(exEvCh), utils.ToJSON(reply)) } } //now we'll check the debited account accPrf.Balances["AbstractBalance1"].Units = utils.NewDecimal(int64(13*time.Second), 0) accPrf.Balances["ConcreteBalance2"].Units = utils.NewDecimal(186, 0) if debitedAcc, err := accnts.dm.GetAccount(context.Background(), accPrf.Tenant, accPrf.ID); err != nil { t.Error(err) } else if !reflect.DeepEqual(accPrf, debitedAcc) { t.Errorf("Expected %+v \n, 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.NewDecimal(int64(40*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, "ConcreteBalance1": { ID: "ConcreteBalance1", Weights: utils.DynamicWeights{ { Weight: 25, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(int64(time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, "ConcreteBalance2": { ID: "ConcreteBalance2", Weights: utils.DynamicWeights{ { Weight: 5, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(int64(30*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, }, } if err := accnts.dm.SetAccount(context.Background(), accPrf, true); err != nil { t.Error(err) } ev := &utils.CGREvent{ ID: "TestV1DebitID", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.AccountField: "1004", }, APIOpts: map[string]interface{}{ utils.MetaUsage: "3m", }, } reply := utils.EventCharges{} expected := "SERVER_ERROR: NOT_FOUND:invalid_filter" if err := accnts.V1MaxConcretes(context.Background(), ev, &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(context.Background(), ev, &reply); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } accPrf.Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{} exEvCh := utils.EventCharges{ Concretes: utils.NewDecimal(int64(31*time.Second), 0), Charges: []*utils.ChargeEntry{ { ChargingID: "GENUUID1", CompressFactor: 1, }, { ChargingID: "GENUUID2", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "GENUUID1": { AccountID: "TestV1DebitAbstracts", BalanceID: "ConcreteBalance1", Units: utils.NewDecimal(int64(time.Second), 0), BalanceLimit: utils.NewDecimal(0, 0), }, "GENUUID2": { AccountID: "TestV1DebitAbstracts", BalanceID: "ConcreteBalance2", Units: utils.NewDecimal(int64(30*time.Second), 0), BalanceLimit: utils.NewDecimal(0, 0), }, }, UnitFactors: map[string]*utils.UnitFactor{}, Rating: map[string]*utils.RateSInterval{}, Rates: map[string]*utils.IntervalRate{}, Accounts: map[string]*utils.Account{ "TestV1DebitAbstracts": accPrf, }, } if err := accnts.V1MaxConcretes(context.Background(), ev, &reply); err != nil { t.Error(err) } else { exEvCh.Accounts["TestV1DebitAbstracts"].Balances["ConcreteBalance1"].Units = utils.NewDecimal(0, 0) exEvCh.Accounts["TestV1DebitAbstracts"].Balances["ConcreteBalance2"].Units = utils.NewDecimal(0, 0) if !exEvCh.Equals(&reply) { //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.NewDecimal(int64(40*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, "ConcreteBalance1": { ID: "ConcreteBalance1", Weights: utils.DynamicWeights{ { Weight: 25, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(int64(time.Minute), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Minute), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, "ConcreteBalance2": { ID: "ConcreteBalance2", Weights: utils.DynamicWeights{ { Weight: 5, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(int64(30*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Minute), 0), FixedFee: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 0), }, }, }, }, } if err := accnts.dm.SetAccount(context.Background(), accPrf, true); err != nil { t.Error(err) } args := &utils.CGREvent{ ID: "TestV1DebitID", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.AccountField: "1004", }, APIOpts: map[string]interface{}{ utils.MetaUsage: "3m", }, } reply := utils.EventCharges{} expected := "SERVER_ERROR: NOT_FOUND:invalid_filter" if err := accnts.V1DebitConcretes(context.Background(), 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(context.Background(), args, &reply); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } accPrf.Balances["AbstractBalance1"].Weights[0].FilterIDs = []string{} exEvCh := utils.EventCharges{ Concretes: utils.NewDecimal(int64(time.Minute+30*time.Second), 0), Charges: []*utils.ChargeEntry{ { ChargingID: "GENUUID1", CompressFactor: 1, }, { ChargingID: "GENUUID2", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "GENUUID1": { AccountID: "TestV1DebitAbstracts", BalanceID: "ConcreteBalance1", Units: utils.NewDecimal(60000000000, 0), BalanceLimit: utils.NewDecimal(0, 0), }, "GENUUID2": { AccountID: "TestV1DebitAbstracts", BalanceID: "ConcreteBalance2", Units: utils.NewDecimal(30000000000, 0), BalanceLimit: utils.NewDecimal(0, 0), }, }, UnitFactors: map[string]*utils.UnitFactor{}, Rating: map[string]*utils.RateSInterval{}, Rates: map[string]*utils.IntervalRate{}, Accounts: map[string]*utils.Account{ "TestV1DebitAbstracts": accPrf, }, } if err := accnts.V1DebitConcretes(context.Background(), args, &reply); err != nil { t.Error(err) } else { exEvCh.Accounts["TestV1DebitAbstracts"].Balances["ConcreteBalance1"].Units = utils.NewDecimal(0, 0) exEvCh.Accounts["TestV1DebitAbstracts"].Balances["ConcreteBalance2"].Units = utils.NewDecimal(0, 0) if !exEvCh.Equals(&reply) { //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(context.Background(), "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 TestMultipleAccountsErr(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(context.Background(), accPrf[0], true); err != nil { t.Error(err) } if err := accnts.dm.SetAccount(context.Background(), accPrf[1], true); err != nil { t.Error(err) } if err := accnts.dm.SetAccount(context.Background(), accPrf[2], true); err != nil { t.Error(err) } expected := "NOT_FOUND:invalid_format" if _, err := accnts.matchingAccountsForEvent(context.Background(), "cgrates.org", args, []string{"TestV1MaxAbstracts", "TestV1MaxAbstracts2", "TestV1MaxAbstracts3"}, false, true); err == nil || err.Error() != expected { t.Errorf("Expected %+v, received %+v", expected, err) } expected = "NOT_FOUND:invalid_format" if _, err := accnts.matchingAccountsForEvent(context.Background(), "cgrates.org", args, []string{"TestV1MaxAbstracts", "TestV1MaxAbstracts2", "TestV1MaxAbstracts3"}, false, 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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 AttributeS within connMngr attrSConn := make(chan birpc.ClientConnector, 1) attrSrv, _ := birpc.NewServiceWithMethodsRename(engine.NewAttributeService(dm, fltrS, cfg), utils.AttributeSv1, true, func(key string) (newKey string) { return strings.TrimPrefix(key, utils.V1Prfx) }) // update the name of the functions attrSConn <- attrSrv cfg.AccountSCfg().AttributeSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes)} // Set the internal rateS within connMngr rateSConn := make(chan birpc.ClientConnector, 1) rateSrv, _ := birpc.NewServiceWithMethodsRename(rates.NewRateS(cfg, fltrS, dm), utils.RateSv1, true, func(key string) (newKey string) { return strings.TrimPrefix(key, utils.V1Prfx) }) // update the name of the functions rateSConn <- rateSrv cfg.AccountSCfg().RateSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS)} connMngr := engine.NewConnManager(cfg) connMngr.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes), utils.AttributeSv1, attrSConn) connMngr.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS), utils.RateSv1, rateSConn) // provision the data atrPrfl := &engine.AttributeProfile{ Tenant: utils.CGRateSorg, ID: "ATTR_ATTACH_RATES_PROFILE_RP_2", Attributes: []*engine.Attribute{ { Path: "*opts.RateSProfile", Type: utils.MetaConstant, Value: config.NewRSRParsersMustCompile("RP_2", utils.InfieldSep), }, }, Blocker: false, } if err := dm.SetAttributeProfile(context.Background(), atrPrfl, true); err != nil { t.Error(err) } rtPflls := []*utils.RateProfile{ { Tenant: utils.CGRateSorg, ID: "RP_1", Rates: map[string]*utils.Rate{ "RT_1": { ID: "RT_1", // lower preference, matching always 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), }, }, }, }}, { Tenant: utils.CGRateSorg, ID: "RP_2", // higher preference, only matching with filter FilterIDs: []string{"*string:~*opts.RateSProfile:RP_2"}, Weights: utils.DynamicWeights{ { Weight: 10, }, }, Rates: map[string]*utils.Rate{ "RT_2": { ID: "RT_2", IntervalRates: []*utils.IntervalRate{ { IntervalStart: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(2, 2), // 0.02 per second Unit: utils.NewDecimal(int64(time.Second), 0), Increment: utils.NewDecimal(int64(time.Second), 0), }, { IntervalStart: utils.NewDecimal(int64(time.Minute), 0), FixedFee: utils.NewDecimal(1, 1), // 0.1 for the rest of call Unit: utils.NewDecimal(int64(time.Second), 0), Increment: utils.NewDecimal(int64(time.Second), 0), }, }, }, }}, } for _, rtPfl := range rtPflls { 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, }, }, // RecurrentFee/Increment 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 }, // total 0.8 needs to be debited from CB1, with UnitFactor will be 0.8 * 100 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 (1.25/0.01 from rates) ID: cb2ID, Type: utils.MetaConcrete, CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, AttributeIDs: []string{utils.MetaNone}, Units: utils.NewDecimal(125, 2), // 1.25 }, //5m25s ABSTR, 4.05 CONCR }, } if err := dm.SetAccount(context.Background(), 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 10 units 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 }, UnitFactors: []*utils.UnitFactor{ { Factor: utils.NewDecimal(1, 9), // Nanoseconds }, }, Units: utils.NewDecimal(130, 0), }, //7m25s ABSTR, 4.05 CONCR cb1ID: &utils.Balance{ // absorb all costs, standard rating used when primary debiting ID: cb1ID, Type: utils.MetaConcrete, Weights: utils.DynamicWeights{ { Weight: 20, }, }, AttributeIDs: []string{utils.MetaNone}, Units: utils.NewDecimal(5, 1), //0.5 covering partially the AB1 }, // 7m25s ABSTR, 4.55 CONCR cb2ID: &utils.Balance{ // absorb all costs, standard rating used when primary debiting ID: cb2ID, Type: utils.MetaConcrete, Opts: map[string]interface{}{ utils.MetaBalanceUnlimited: true, }, CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, AttributeIDs: []string{"ATTR_ATTACH_RATES_PROFILE_RP_2"}, Units: utils.NewDecimal(35, 2), //0.3 + 0.05 covering 5s it's own with RateS // ToDo: change rating with a lower preference here, should have multiple groups also // RateProfileIDs: ["TWOCENTS"] }, // 7m25s ABSTR, 4.85 CONCR to cover the AB1 // 7m26 ABSTR, 4.95 CONCR with remaining flat covered by CB2 with RateS, RP_2, -0.05 on CB2 }, } if err := dm.SetAccount(context.Background(), acnt2, true); err != nil { t.Error(err) } args := &utils.CGREvent{ ID: "TestV1DebitAbstractsEventCharges", Tenant: utils.CGRateSorg, APIOpts: map[string]interface{}{ utils.MetaUsage: "7m26s", }, } var rcvEC utils.EventCharges if err := accnts.V1DebitAbstracts(context.Background(), args, &rcvEC); err != nil { t.Error(err) } // expected EventCharges eEvChgs := &utils.EventCharges{ Abstracts: utils.NewDecimal(446000000000, 0), Concretes: utils.NewDecimal(495, 2), 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.AccountCharge{ "GENUUID_GHOST1": { AccountID: "TestV1DebitAbstractsEventCharges1", BalanceID: cb1ID, Units: utils.NewDecimal(8, 1), BalanceLimit: utils.NewDecimal(-200, 0), // -200 UnitFactorID: "GENUUID_FACTOR1", }, "GENUUID3": { AccountID: "TestV1DebitAbstractsEventCharges1", BalanceID: ab2ID, BalanceLimit: utils.NewDecimal(0, 0), RatingID: "GENUUID_RATING1", }, "GENUUID2": { AccountID: "TestV1DebitAbstractsEventCharges1", BalanceID: cb1ID, Units: utils.NewDecimal(2, 0), BalanceLimit: utils.NewDecimal(-200, 0), UnitFactorID: "GENUUID_FACTOR2", }, "GENUUID5": { AccountID: "TestV1DebitAbstractsEventCharges2", BalanceID: ab1ID, BalanceLimit: utils.NewDecimal(0, 0), RatingID: "GENUUID_RATING2", JoinedChargeIDs: []string{"GENUUID_GHOST2"}, }, "GENUUID6": { AccountID: "TestV1DebitAbstractsEventCharges2", BalanceID: cb1ID, Units: utils.NewDecimal(3, 1), }, "GENUUID4": { AccountID: "TestV1DebitAbstractsEventCharges1", BalanceID: cb2ID, Units: utils.NewDecimal(125, 2), BalanceLimit: utils.NewDecimal(0, 0), }, "GENUUID_GHOST2": { AccountID: "TestV1DebitAbstractsEventCharges2", BalanceID: cb1ID, Units: utils.NewDecimal(6, 1), }, "GENUUID1": { AccountID: "TestV1DebitAbstractsEventCharges1", BalanceID: ab1ID, BalanceLimit: utils.NewDecimal(0, 0), RatingID: "GENUUID_RATING3", JoinedChargeIDs: []string{"GENUUID_GHOST1"}, }, }, UnitFactors: map[string]*utils.UnitFactor{ "GENUUID_FACTOR1": { Factor: utils.NewDecimal(100, 0), }, "GENUUID_FACTOR2": { Factor: utils.NewDecimal(100, 0), }, }, Rating: map[string]*utils.RateSInterval{ "GENUUID_RATING1": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, CompressFactor: 1, }, }, CompressFactor: 1, }, "GENUUID_RATING2": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, CompressFactor: 1, }, }, CompressFactor: 1, }, "GENUUID_RATING3": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, CompressFactor: 1, }, }, CompressFactor: 1, }, }, Rates: map[string]*utils.IntervalRate{}, Accounts: map[string]*utils.Account{ "TestV1DebitAbstractsEventCharges1": acnt1, "TestV1DebitAbstractsEventCharges2": acnt2, }, } if !eEvChgs.Equals(&rcvEC) { t.Errorf("expecting: %s, \nreceived: %s\n", utils.ToJSON(eEvChgs), utils.ToJSON(rcvEC)) } /* 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": { 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": { 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(context.Background(), acnt, true); err != nil { t.Error(err) } args := &utils.CGREvent{ ID: "TestV1DebitAbstractsWithRecurrentFeeNegative", Tenant: "cgrates.org", APIOpts: map[string]interface{}{ utils.MetaUsage: "30s", }, } expEvCh := &utils.EventCharges{ Abstracts: utils.NewDecimal(int64(30*time.Second), 0), Concretes: utils.NewDecimal(-28, 0), Charges: []*utils.ChargeEntry{ { ChargingID: "CHARGE1", CompressFactor: 1, }, { ChargingID: "CHARGE2", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "CHARGE1": { AccountID: "TestV1DebitAbstractsWithRecurrentFeeNegative", BalanceID: "ab1", Units: utils.NewDecimal(int64(time.Second), 0), BalanceLimit: utils.NewDecimal(0, 0), RatingID: "RATING1", JoinedChargeIDs: []string{"JOINED1"}, }, "JOINED1": { AccountID: "TestV1DebitAbstractsWithRecurrentFeeNegative", BalanceID: "cb1", Units: utils.NewDecimal(1, 0), BalanceLimit: utils.NewDecimal(0, 0), }, "CHARGE2": { AccountID: "TestV1DebitAbstractsWithRecurrentFeeNegative", BalanceID: utils.MetaTransAbstract, Units: utils.NewDecimal(int64(29*time.Second), 0), RatingID: "RATING2", JoinedChargeIDs: []string{"JOINED2"}, }, "JOINED2": { AccountID: "TestV1DebitAbstractsWithRecurrentFeeNegative", BalanceID: "cb1", Units: utils.NewDecimal(-29, 0), BalanceLimit: utils.NewDecimal(0, 0), }, }, UnitFactors: map[string]*utils.UnitFactor{}, Rating: map[string]*utils.RateSInterval{ "RATING1": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, RateID: "5772dd3", CompressFactor: 1, }, }, CompressFactor: 1, }, "RATING2": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, RateID: "75a070c", CompressFactor: 1, }, }, CompressFactor: 1, }, }, Rates: map[string]*utils.IntervalRate{}, Accounts: map[string]*utils.Account{ "TestV1DebitAbstractsWithRecurrentFeeNegative": acnt, }, } ev := &utils.EventCharges{} if err := accnts.V1DebitAbstracts(context.Background(), args, ev); err != nil { t.Error(err) } else { expEvCh.Accounts["TestV1DebitAbstractsWithRecurrentFeeNegative"].Balances["ab1"].Units = utils.NewDecimal(int64(39*time.Second), 0) expEvCh.Accounts["TestV1DebitAbstractsWithRecurrentFeeNegative"].Balances["cb1"].Units = utils.NewDecimal(29, 0) if !ev.Equals(expEvCh) { t.Errorf("Expected %+v \n, 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(29, 0) if rcv, err := dm.GetAccount(context.Background(), acnt.Tenant, acnt.ID); err != nil { t.Error(err) } else if !reflect.DeepEqual(rcv, acnt) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(acnt), utils.ToJSON(rcv)) } } func TestDebitAbstractsMaxDebitAbstractFromConcreteNoConcrBal(t *testing.T) { // this test will call maxDebitAbstractsFromConcretes but without any concreteBal and not calling rates cache := engine.Cache engine.Cache.Clear(nil) cfg := config.NewDefaultCGRConfig() dm := engine.NewDataManager(engine.NewInternalDB(nil, nil, true), cfg.CacheCfg(), nil) filterS := engine.NewFilterS(cfg, nil, dm) acnts := NewAccountS(cfg, filterS, nil, dm) acnt := &utils.Account{ Tenant: "cgrates.org", ID: "TestV1DebitAbstractsWithRecurrentFeeNegative", Balances: map[string]*utils.Balance{ "ab1": &utils.Balance{ ID: "ab1", Type: utils.MetaAbstract, Units: utils.NewDecimal(int64(60*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { FixedFee: utils.NewDecimal(1, 1), Increment: utils.NewDecimal(1, 0), }, }, }, }, } if err := dm.SetAccount(context.Background(), acnt, true); err != nil { t.Error(err) } cgrEv := &utils.CGREvent{ Tenant: "cgrates.org", ID: "EV", Event: map[string]interface{}{ utils.ToR: utils.MetaVoice, utils.AccountField: "1001", utils.Destination: "1002", }, APIOpts: map[string]interface{}{ utils.MetaUsage: 2 * time.Minute, utils.OptsAccountS: true, }, } expEcCh := utils.EventCharges{ Abstracts: utils.NewDecimal(0, 0), Accounting: make(map[string]*utils.AccountCharge), UnitFactors: make(map[string]*utils.UnitFactor), Rating: make(map[string]*utils.RateSInterval), Rates: make(map[string]*utils.IntervalRate), Accounts: map[string]*utils.Account{ "TestV1DebitAbstractsWithRecurrentFeeNegative": acnt, }, } // not having concrBal and connection to rates, this will not perform a debit, so the EventChargers abstract will be empty var eEc utils.EventCharges if err := acnts.V1DebitAbstracts(context.Background(), cgrEv, &eEc); err != nil { t.Error(err) } else if !reflect.DeepEqual(eEc, expEcCh) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expEcCh), utils.ToJSON(eEc)) } engine.Cache = cache } func TestDebitAbstractUsingRatesWithRoundByIncrement(t *testing.T) { // get the config engine.Cache.Clear(nil) cfg := config.NewDefaultCGRConfig() cfg.AccountSCfg().Enabled = true cfg.AccountSCfg().RateSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS)} //cfg.AccountSCfg().IndexedSelects = false cfg.RateSCfg().Enabled = true // get the connMngr connMngr := engine.NewConnManager(cfg) // data manager dm := engine.NewDataManager(engine.NewInternalDB(nil, nil, true), cfg.CacheCfg(), connMngr) // configure filters fltrs := engine.NewFilterS(cfg, connMngr, dm) // add the internal connection between accounts and rates ratesConns := make(chan birpc.ClientConnector, 1) rateSrv, err := birpc.NewServiceWithMethodsRename(rates.NewRateS(cfg, fltrs, dm), utils.RateSv1, true, func(key string) (newKey string) { return strings.TrimPrefix(key, utils.V1Prfx) }) if err != nil { t.Error(err) } ratesConns <- rateSrv connMngr.AddInternalConn(utils.ConcatenatedKey(utils.MetaInternal, utils.MetaRateS), utils.RateSv1, ratesConns) //create the accounts obj acnts := NewAccountS(cfg, fltrs, connMngr, dm) // set an AccountProfile with balances that contains connection with RateProfiles accPrf := &utils.Account{ Tenant: "cgrates.org", ID: "ACNT1", FilterIDs: []string{"*string:~*req.Account:1001"}, Balances: map[string]*utils.Balance{ "ABS1": { ID: "ABS1", Weights: utils.DynamicWeights{ { Weight: 15, }, }, Type: utils.MetaAbstract, Units: utils.NewDecimal(int64(30*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), }, }, }, "ABS2": { ID: "ABS2", Weights: utils.DynamicWeights{ { Weight: 10, }, }, Type: utils.MetaAbstract, Units: utils.NewDecimal(int64(15*time.Second), 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, RateProfileIDs: []string{"RP1"}, }, "CNCRT1": { ID: "CNCRT1", Weights: utils.DynamicWeights{ { Weight: 5, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(100, 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, RateProfileIDs: []string{"RP1"}, }, }, } if err := acnts.dm.SetAccount(context.Background(), accPrf, true); err != nil { t.Error(err) } // set the rate profile which is used in accounts rtPrf := &utils.RateProfile{ ID: "RP1", Tenant: "cgrates.org", FilterIDs: []string{"*string:~*req.Destination:1234"}, Rates: map[string]*utils.Rate{ "RT1": { ID: "RT1", IntervalRates: []*utils.IntervalRate{ { IntervalStart: utils.NewDecimal(0, 0), RecurrentFee: utils.NewDecimal(1, 2), Unit: utils.NewDecimal(int64(time.Second), 0), Increment: utils.NewDecimal(int64(time.Second), 0), }, }, }, }, } if err := dm.SetRateProfile(context.Background(), rtPrf, true); err != nil { t.Error(err) } // now we will try to debit account using rates instead for the second balance cgrEv := &utils.CGREvent{ ID: "TestDebitAbstractUsingRatesWithRoundByIncrement", Tenant: "cgrates.org", Event: map[string]interface{}{ utils.AccountField: "1001", utils.Destination: "1234", }, APIOpts: map[string]interface{}{ utils.StartTime: time.Date(2020, time.January, 7, 16, 60, 0, 0, time.UTC), utils.MetaUsage: "44825100us", }, } var reply utils.EventCharges if err := acnts.V1DebitAbstracts(context.Background(), cgrEv, &reply); err != nil { t.Error(err) } // verify the EvChargers expEvCHargers := &utils.EventCharges{ Abstracts: utils.NewDecimal(44825100000, 0), Concretes: utils.NewDecimal(15, 2), Charges: []*utils.ChargeEntry{ { ChargingID: "CHARGER1", CompressFactor: 1, }, { ChargingID: "CHARGER2", CompressFactor: 1, }, }, Accounting: map[string]*utils.AccountCharge{ "CHARGER1": { AccountID: "ACNT1", BalanceID: "ABS1", Units: utils.NewDecimal(int64(30*time.Second), 0), // 30 seconds from *abstract ABS1 BalanceLimit: utils.NewDecimal(0, 0), RatingID: "Rating1", }, "CHARGER2": { AccountID: "ACNT1", BalanceID: "ABS2", Units: utils.NewDecimal(14825100000, 0), // 14.8251 seconds from *abstract ABS2 ((it should be debited all 15s from the entire balance)) BalanceLimit: utils.NewDecimal(0, 0), RatingID: "Rating2", JoinedChargeIDs: []string{"Joined1"}, }, "Joined1": { AccountID: "ACNT1", BalanceID: "CNCRT1", Units: utils.NewDecimal(15, 2), // 15s seconds from *concrete BalanceLimit: utils.NewDecimal(0, 0), }, }, UnitFactors: make(map[string]*utils.UnitFactor), Rating: map[string]*utils.RateSInterval{ "Rating1": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, RateID: "RateID1", CompressFactor: 1, }, }, CompressFactor: 1, }, "Rating2": { Increments: []*utils.RateSIncrement{ { RateIntervalIndex: 0, RateID: "RateID2", CompressFactor: 1, }, }, CompressFactor: 1, }, }, Rates: make(map[string]*utils.IntervalRate), Accounts: map[string]*utils.Account{ "ACNT1": { Tenant: "cgrates.org", ID: "ACNT1", FilterIDs: []string{"*string:~*req.Account:1001"}, Balances: map[string]*utils.Balance{ "ABS1": { ID: "ABS1", Weights: utils.DynamicWeights{ { Weight: 15, }, }, Type: utils.MetaAbstract, Units: utils.NewDecimal(0, 0), CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), }, }, }, "ABS2": { ID: "ABS2", Weights: utils.DynamicWeights{ { Weight: 10, }, }, Type: utils.MetaAbstract, Units: utils.NewDecimal(174900000, 0), // 0.17.. s available CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, RateProfileIDs: []string{"RP1"}, }, "CNCRT1": { ID: "CNCRT1", Weights: utils.DynamicWeights{ { Weight: 5, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(9985, 2), // 99.85 available CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, RateProfileIDs: []string{"RP1"}, }, }, }, }, } if !expEvCHargers.Equals(&reply) { t.Errorf("Expected %+v \n, received %+v", utils.ToIJSON(expEvCHargers), utils.ToJSON(reply)) } // as we checked the EventCharges, we should check if the debit in our account was correct expAcc := &utils.Account{ Tenant: "cgrates.org", ID: "ACNT1", FilterIDs: []string{"*string:~*req.Account:1001"}, Balances: map[string]*utils.Balance{ "ABS1": { ID: "ABS1", Weights: utils.DynamicWeights{ { Weight: 15, }, }, Type: utils.MetaAbstract, Units: &utils.Decimal{utils.SumDecimalAsBig(&utils.Decimal{utils.NewDecimal(0, 0).Neg(utils.NewDecimal(1, 0).Big)}, utils.NewDecimal(1, 0))}, // this should be -0 CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), FixedFee: utils.NewDecimal(0, 0), }, }, }, "ABS2": { ID: "ABS2", Weights: utils.DynamicWeights{ { Weight: 10, }, }, Type: utils.MetaAbstract, Units: utils.NewDecimal(174900000, 0), // 0.17.. s available (should be 0 because of Increment of 1s) CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, RateProfileIDs: []string{"RP1"}, }, "CNCRT1": { ID: "CNCRT1", Weights: utils.DynamicWeights{ { Weight: 5, }, }, Type: utils.MetaConcrete, Units: utils.NewDecimal(9985, 2), // 99.85 available CostIncrements: []*utils.CostIncrement{ { Increment: utils.NewDecimal(int64(time.Second), 0), }, }, RateProfileIDs: []string{"RP1"}, }, }, } if accRPly, err := acnts.dm.GetAccount(context.Background(), "cgrates.org", "ACNT1"); err != nil { t.Error(err) } else if !reflect.DeepEqual(accRPly, expAcc) { t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expAcc), utils.ToJSON(accRPly)) } }