diff --git a/accounts/abstractbalance.go b/accounts/abstractbalance.go
index 564fdf8dd..9c4eda71e 100644
--- a/accounts/abstractbalance.go
+++ b/accounts/abstractbalance.go
@@ -123,5 +123,5 @@ func (aB *abstractBalance) debitAbstracts(usage *decimal.Big,
// debitConcretes implements the balanceOperator interface
func (aB *abstractBalance) debitConcretes(usage *decimal.Big,
cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error) {
- return
+ return nil, utils.ErrNotImplemented
}
diff --git a/accounts/accounts.go b/accounts/accounts.go
index e7566d44d..3ec44033d 100644
--- a/accounts/accounts.go
+++ b/accounts/accounts.go
@@ -135,7 +135,7 @@ func (aS *AccountS) matchingAccountsForEvent(tnt string, cgrEv *utils.CGREvent,
return
}
-// accountDebitAbstracts will debit the usage out of an Account
+// accountDebit will debit the usage out of an Account
func (aS *AccountS) accountDebit(acnt *utils.AccountProfile, usage *decimal.Big,
cgrEv *utils.CGREvent, concretes bool) (ec *utils.EventCharges, err error) {
@@ -169,7 +169,8 @@ func (aS *AccountS) accountDebit(acnt *utils.AccountProfile, usage *decimal.Big,
}
var ecDbt *utils.EventCharges
if ecDbt, err = debFunc(new(decimal.Big).Copy(usage), cgrEv); err != nil {
- if err == utils.ErrFilterNotPassingNoCaps {
+ if err == utils.ErrFilterNotPassingNoCaps ||
+ err == utils.ErrNotImplemented {
err = nil
continue
}
diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go
new file mode 100644
index 000000000..e05373ffd
--- /dev/null
+++ b/accounts/accounts_test.go
@@ -0,0 +1,391 @@
+/*
+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 (
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+ "github.com/ericlagergren/decimal"
+)
+
+func TestListenAndServe(t *testing.T) {
+ 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{}{}
+ }()
+ accnts.ListenAndServe(stopChan, cfgRld)
+
+ if err := accnts.Shutdown(); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestRPCCall(t *testing.T) {
+ cfg := config.NewDefaultCGRConfig()
+ dm := engine.NewDataManager(nil, cfg.CacheCfg(), nil)
+ fltr := engine.NewFilterS(cfg, nil, dm)
+ accnts := NewAccountS(cfg, fltr, nil, dm)
+ method := "ApierSv1Ping"
+ expected := "UNSUPPORTED_SERVICE_METHOD"
+ if err := accnts.Call(method, nil, nil); err == nil || err.Error() != expected {
+ t.Errorf("Expected %+v, received %+v", expected, err)
+ }
+}
+
+type dataDBMockErrorNotFound struct {
+ *engine.DataDBMock
+}
+
+func (dB *dataDBMockErrorNotFound) GetAccountProfileDrv(string, string) (*utils.AccountProfile, error) {
+ return nil, utils.ErrNotFound
+}
+
+func TestMatchingAccountsForEventMockingErrors(t *testing.T) {
+ cfg := config.NewDefaultCGRConfig()
+ fltr := engine.NewFilterS(cfg, nil, nil)
+
+ accPrf := &utils.AccountProfile{
+ Tenant: "cgrates.org",
+ ID: "1004",
+ FilterIDs: []string{"*string:~*req.Account:1004"},
+ Balances: map[string]*utils.Balance{
+ "ConcreteBalance1": &utils.Balance{
+ ID: "ConcreteBalance1",
+ Weights: utils.DynamicWeights{
+ {
+ Weight: 20,
+ },
+ },
+ Type: utils.MetaConcrete,
+ Units: &utils.Decimal{decimal.New(0, 0)},
+ CostIncrements: []*utils.CostIncrement{
+ &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.SetAccountProfile(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) {
+ 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.AccountProfile{
+ 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": &utils.Balance{
+ 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.SetAccountProfile(accPrf, true); err != nil {
+ t.Error(err)
+ }
+
+ cgrEvent.Opts = 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.AccountProfilesWithWeight{
+ {
+ AccountProfile: 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) {
+ 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.AccountProfile{
+ Tenant: "cgrates.org",
+ ID: "TestAccountDebit",
+ FilterIDs: []string{"*string:~*req.Account:1004"},
+ Balances: map[string]*utils.Balance{
+ "ConcreteBalance1": &utils.Balance{
+ ID: "ConcreteBalance1",
+ Weights: utils.DynamicWeights{
+ {
+ FilterIDs: []string{"invalid_filter_format"},
+ Weight: 20,
+ },
+ },
+ Type: utils.MetaConcrete,
+ Units: &utils.Decimal{decimal.New(200, 0)},
+ },
+ "AbstractBalance1": &utils.Balance{
+ ID: "AbstractBalance1",
+ Weights: utils.DynamicWeights{
+ {
+ Weight: 5,
+ },
+ },
+ Type: utils.MetaAbstract,
+ Units: &utils.Decimal{decimal.New(20, 0)},
+ CostIncrements: []*utils.CostIncrement{
+ &utils.CostIncrement{
+ FilterIDs: []string{"*string:~*req.ToR:*data"},
+ Increment: &utils.Decimal{decimal.New(int64(time.Second), 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(210, 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: "
+ 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(210, 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(200, 0)}
+ if evCh, err := accnts.accountDebit(accPrf, usage.Big, cgrEvent, true); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(evCh.Usage.Big, expectedUsage.Big) {
+ t.Errorf("Expected %+v, received %+v", utils.ToJSON(expectedUsage), utils.ToJSON(evCh.Usage))
+ }
+}
+
+func TestAccountsDebit(t *testing.T) {
+ 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.AccountProfileWithWeight{
+ {
+ AccountProfile: &utils.AccountProfile{
+ Tenant: "cgrates.org",
+ ID: "TestAccountsDebit",
+ FilterIDs: []string{"*string:~*req.Account:1004"},
+ Balances: map[string]*utils.Balance{
+ "AbstractBalance1": &utils.Balance{
+ ID: "AbstractBalance1",
+ Weights: utils.DynamicWeights{
+ {
+ Weight: 5,
+ },
+ },
+ Type: utils.MetaAbstract,
+ Units: &utils.Decimal{decimal.New(int64(40*time.Second), 0)},
+ CostIncrements: []*utils.CostIncrement{
+ &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)},
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cgrEvent := &utils.CGREvent{
+ ID: "TEST_EVENT",
+ Tenant: "cgrates.org",
+ Event: map[string]interface{}{
+ utils.AccountField: "1004",
+ utils.Usage: "not_time_format",
+ },
+ Opts: 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.Opts[utils.MetaUsage] = "not_time_format"
+ if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, false); err != nil {
+ t.Error(err)
+ }
+ cgrEvent.Opts[utils.MetaUsage] = "55s"
+
+ if _, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, false); err != nil {
+ t.Error(err)
+ }
+
+ cgrEvent.Event[utils.Usage] = "55s"
+ expectedUsage := &utils.Decimal{decimal.New(0, 0)}
+ if evCh, err := accnts.accountsDebit(accntsPrf, cgrEvent, false, false); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedUsage, evCh.Usage) {
+ t.Errorf("Expected %+v, received %+v", utils.ToJSON(expectedUsage), utils.ToJSON(evCh.Usage))
+ }
+}
diff --git a/apier/v1/accountsv1_it_test.go b/apier/v1/accountsv1_it_test.go
index d9a49c6f8..583ca7472 100644
--- a/apier/v1/accountsv1_it_test.go
+++ b/apier/v1/accountsv1_it_test.go
@@ -1046,11 +1046,11 @@ func testAccountSv1MaxConcretes(t *testing.T) {
ID: "ConcreteBalance1",
Weights: ";20",
Type: utils.MetaConcrete,
- Units: float64(20 * time.Second),
+ Units: 21,
CostIncrements: []*utils.APICostIncrement{
&utils.APICostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*data"},
- Increment: utils.Float64Pointer(float64(time.Second)),
+ Increment: utils.Float64Pointer(1),
FixedFee: utils.Float64Pointer(0),
RecurrentFee: utils.Float64Pointer(1),
},
@@ -1060,7 +1060,21 @@ func testAccountSv1MaxConcretes(t *testing.T) {
ID: "ConcreteBalance2",
Weights: ";10",
Type: utils.MetaConcrete,
- Units: float64(20 * time.Second),
+ Units: 20,
+ CostIncrements: []*utils.APICostIncrement{
+ &utils.APICostIncrement{
+ FilterIDs: []string{"*string:~*req.ToR:*data"},
+ Increment: utils.Float64Pointer(1),
+ FixedFee: utils.Float64Pointer(0),
+ RecurrentFee: utils.Float64Pointer(1),
+ },
+ },
+ },
+ "AbstractBalance1": &utils.APIBalance{
+ ID: "AbstractBalance1",
+ Weights: ";5",
+ Type: utils.MetaAbstract,
+ Units: 20,
CostIncrements: []*utils.APICostIncrement{
&utils.APICostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*data"},
@@ -1070,23 +1084,6 @@ func testAccountSv1MaxConcretes(t *testing.T) {
},
},
},
- /*
- "AbstractBalance1": &utils.APIBalance{
- ID: "AbstractBalance1",
- Weights: ";10",
- Type: utils.MetaAbstract,
- Units: float64(30 * time.Minute),
- CostIncrements: []*utils.APICostIncrement{
- &utils.APICostIncrement{
- FilterIDs: []string{"*string:~*req.ToR:*data"},
- Increment: utils.Float64Pointer(float64(time.Second)),
- FixedFee: utils.Float64Pointer(0),
- RecurrentFee: utils.Float64Pointer(1),
- },
- },
- },
-
- */
},
ThresholdIDs: []string{utils.MetaNone},
},
@@ -1119,15 +1116,15 @@ func testAccountSv1MaxConcretes(t *testing.T) {
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.ToR: utils.MetaData,
- utils.Usage: "50s",
+ utils.Usage: "50ns",
},
}}, &eEc); err != nil {
t.Error(err)
- } else if eEc.Usage == nil || *eEc.Usage != float64(40*time.Second) {
+ } else if eEc.Usage == nil || *eEc.Usage != 41 {
t.Errorf("received usage: %v", *eEc.Usage)
}
- //make sure we did nto Debit from our Account
+ //make sure we did not Debit from our Account
exp, err = apiAccPrf.AsAccountProfile()
if err != nil {
t.Error(err)
@@ -1149,11 +1146,11 @@ func testAccountSv1DebitConcretes(t *testing.T) {
Event: map[string]interface{}{
utils.AccountField: "1004",
utils.ToR: utils.MetaData,
- utils.Usage: "50s",
+ utils.Usage: "50ns",
},
}}, &eEc); err != nil {
t.Error(err)
- } else if eEc.Usage == nil || *eEc.Usage != float64(40*time.Second) {
+ } else if eEc.Usage == nil || *eEc.Usage != 41 {
t.Errorf("received usage: %v", *eEc.Usage)
}
@@ -1174,7 +1171,7 @@ func testAccountSv1DebitConcretes(t *testing.T) {
CostIncrements: []*utils.CostIncrement{
&utils.CostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*data"},
- Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
+ Increment: &utils.Decimal{decimal.New(1, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 0)},
},
@@ -1189,6 +1186,24 @@ func testAccountSv1DebitConcretes(t *testing.T) {
},
Type: utils.MetaConcrete,
Units: &utils.Decimal{decimal.New(0, 0)},
+ CostIncrements: []*utils.CostIncrement{
+ &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)},
+ },
+ },
+ },
+ "AbstractBalance1": &utils.Balance{
+ ID: "AbstractBalance1",
+ Weights: utils.DynamicWeights{
+ {
+ Weight: 5,
+ },
+ },
+ Type: utils.MetaAbstract,
+ Units: &utils.Decimal{decimal.New(20, 0)},
CostIncrements: []*utils.CostIncrement{
&utils.CostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*data"},