AccountS - integration test for AccountSv1AccountProfileForEvent API

This commit is contained in:
DanB
2021-02-03 20:12:57 +01:00
parent 54ea09b6fd
commit 825eea9907
6 changed files with 322 additions and 14 deletions

View File

@@ -24,6 +24,7 @@ import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/guardian"
"github.com/cgrates/cgrates/utils"
"github.com/ericlagergren/decimal"
)
@@ -65,7 +66,10 @@ func (aS *AccountS) Call(serviceMethod string, args interface{}, reply interface
}
// matchingAccountForEvent returns the matched Account for the given event
func (aS *AccountS) matchingAccountForEvent(tnt string, cgrEv *utils.CGREvent, acntIDs []string) (acnt *utils.AccountProfile, err error) {
// if lked option is passed, the AccountProfile will be also locked
// so it becomes responsibility of upper layers to release the lock
func (aS *AccountS) matchingAccountForEvent(tnt string, cgrEv *utils.CGREvent,
acntIDs []string, lked bool) (acnt *utils.AccountProfile, lkID string, err error) {
evNm := utils.MapStorage{
utils.MetaReq: cgrEv.Event,
utils.MetaOpts: cgrEv.Opts,
@@ -78,7 +82,7 @@ func (aS *AccountS) matchingAccountForEvent(tnt string, cgrEv *utils.CGREvent, a
aS.cfg.AccountSCfg().PrefixIndexedFields,
aS.cfg.AccountSCfg().SuffixIndexedFields,
aS.dm,
utils.CacheActionProfilesFilterIndexes,
utils.CacheAccountProfilesFilterIndexes,
tnt,
aS.cfg.AccountSCfg().IndexedSelects,
aS.cfg.AccountSCfg().NestedFields,
@@ -88,9 +92,18 @@ func (aS *AccountS) matchingAccountForEvent(tnt string, cgrEv *utils.CGREvent, a
acntIDs = actIDsMp.AsSlice()
}
for _, acntID := range acntIDs {
var refID string
if lked {
cacheKey := utils.ConcatenatedKey(utils.CacheAccountProfiles, acntID)
refID = guardian.Guardian.GuardIDs("",
aS.cfg.GeneralCfg().LockingTimeout, cacheKey) // RPC caching needs to be atomic
}
var qAcnt *utils.AccountProfile
if qAcnt, err = aS.dm.GetAccountProfile(tnt, acntID,
true, true, utils.NonTransactional); err != nil {
if lked {
guardian.Guardian.UnguardIDs(refID)
}
if err == utils.ErrNotFound {
err = nil
continue
@@ -98,24 +111,41 @@ func (aS *AccountS) matchingAccountForEvent(tnt string, cgrEv *utils.CGREvent, a
return
}
if _, isDisabled := qAcnt.Opts[utils.Disabled]; isDisabled {
if lked {
guardian.Guardian.UnguardIDs(refID)
}
continue
}
if qAcnt.ActivationInterval != nil && cgrEv.Time != nil &&
!qAcnt.ActivationInterval.IsActiveAtTime(*cgrEv.Time) { // not active
if lked {
guardian.Guardian.UnguardIDs(refID)
}
continue
}
var pass bool
if pass, err = aS.fltrS.Pass(tnt, qAcnt.FilterIDs, evNm); err != nil {
if lked {
guardian.Guardian.UnguardIDs(refID)
}
return
} else if !pass {
if lked {
guardian.Guardian.UnguardIDs(refID)
}
continue
}
if acnt == nil || acnt.Weight < qAcnt.Weight {
acnt = qAcnt
if lked {
lkID = refID
}
} else if lked {
guardian.Guardian.UnguardIDs(refID)
}
}
if acnt == nil {
return nil, utils.ErrNotFound
return nil, "", utils.ErrNotFound
}
return
}
@@ -167,27 +197,29 @@ func (aS *AccountS) accountProcessEvent(acnt *utils.AccountProfile,
// V1AccountProfileForEvent returns the matching AccountProfile for Event
func (aS *AccountS) V1AccountProfileForEvent(args *utils.ArgsAccountForEvent, ap *utils.AccountProfile) (err error) {
var acnt *utils.AccountProfile
if acnt, err = aS.matchingAccountForEvent(args.CGREvent.Tenant,
args.CGREvent, args.AccountIDs); err != nil {
if acnt, _, err = aS.matchingAccountForEvent(args.CGREvent.Tenant,
args.CGREvent, args.AccountIDs, false); err != nil {
if err != utils.ErrNotFound {
err = utils.NewErrServerError(err)
}
return
}
*ap = *acnt // Make sure we clone in RPC
*ap = *acnt // ToDo: make sure we clone in RPC
return
}
// V1MaxUsage returns the maximum usage for the event, based on matching Account
func (aS *AccountS) V1MaxUsage(args *utils.ArgsAccountForEvent, ec *utils.EventCharges) (err error) {
var acnt *utils.AccountProfile
if acnt, err = aS.matchingAccountForEvent(args.CGREvent.Tenant,
args.CGREvent, args.AccountIDs); err != nil {
var lkID string
if acnt, lkID, err = aS.matchingAccountForEvent(args.CGREvent.Tenant,
args.CGREvent, args.AccountIDs, true); err != nil {
if err != utils.ErrNotFound {
err = utils.NewErrServerError(err)
}
return
}
defer guardian.Guardian.UnguardIDs(lkID)
var procEC *utils.EventCharges
if procEC, err = aS.accountProcessEvent(acnt, args.CGREvent); err != nil {
@@ -201,13 +233,15 @@ func (aS *AccountS) V1MaxUsage(args *utils.ArgsAccountForEvent, ec *utils.EventC
// V1DebitUsage performs debit for the provided event
func (aS *AccountS) V1DebitUsage(args *utils.ArgsAccountForEvent, ec *utils.EventCharges) (err error) {
var acnt *utils.AccountProfile
if acnt, err = aS.matchingAccountForEvent(args.CGREvent.Tenant,
args.CGREvent, args.AccountIDs); err != nil {
var lkID string
if acnt, lkID, err = aS.matchingAccountForEvent(args.CGREvent.Tenant,
args.CGREvent, args.AccountIDs, true); err != nil {
if err != utils.ErrNotFound {
err = utils.NewErrServerError(err)
}
return
}
defer guardian.Guardian.UnguardIDs(lkID)
var procEC *utils.EventCharges
if procEC, err = aS.accountProcessEvent(acnt, args.CGREvent); err != nil {

View File

@@ -157,3 +157,21 @@ func (aSv1 *AccountSv1) Ping(ign *utils.CGREvent, reply *string) error {
*reply = utils.Pong
return nil
}
// AccountProfileForEvent returns the matching AccountProfile for Event
func (aSv1 *AccountSv1) AccountProfileForEvent(args *utils.ArgsAccountForEvent,
ap *utils.AccountProfile) (err error) {
return aSv1.aS.V1AccountProfileForEvent(args, ap)
}
// MaxUsage returns the maximum usage for the event, based on matching Account
func (aSv1 *AccountSv1) MaxUsage(args *utils.ArgsAccountForEvent,
ec *utils.EventCharges) (err error) {
return aSv1.aS.V1MaxUsage(args, ec)
}
// DebitUsage performs debit for the provided event
func (aSv1 *AccountSv1) DebitUsage(args *utils.ArgsAccountForEvent,
ec *utils.EventCharges) (err error) {
return aSv1.aS.V1DebitUsage(args, ec)
}

View File

@@ -0,0 +1,208 @@
// +build integration
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 v1
import (
"net/rpc"
"path"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/ericlagergren/decimal"
)
var (
acntSConfigDIR string //run tests for specific configuration
acntSCfgPath string
acntSCfg *config.CGRConfig
acntSRPC *rpc.Client
acntSDataDir = "/usr/share/cgrates"
)
//Test start here
func TestAccountSv1IT(t *testing.T) {
sTestsAccountS := []func(t *testing.T){
testAccountSv1InitCfg,
testAccountSv1InitDataDb,
testAccountSv1ResetStorDb,
testAccountSv1StartEngine,
testAccountSv1RPCConn,
testAccountSv1LoadFromFolder,
testAccountSv1AccountProfileForEvent,
}
switch *dbType {
case utils.MetaInternal:
acntSConfigDIR = "accounts_internal"
case utils.MetaMySQL:
t.SkipNow()
case utils.MetaMongo:
t.SkipNow()
case utils.MetaPostgres:
t.SkipNow()
default:
t.Fatalf("unknown Database type <%s>", *dbType)
}
for _, stest := range sTestsAccountS {
t.Run(acntSConfigDIR, stest)
}
}
func testAccountSv1InitCfg(t *testing.T) {
var err error
acntSCfgPath = path.Join(acntSDataDir, "conf", "samples", acntSConfigDIR)
acntSCfg, err = config.NewCGRConfigFromPath(acntSCfgPath)
if err != nil {
t.Error(err)
}
}
func testAccountSv1InitDataDb(t *testing.T) {
if err := engine.InitDataDb(acntSCfg); err != nil {
t.Fatal(err)
}
}
// Wipe out the cdr database
func testAccountSv1ResetStorDb(t *testing.T) {
if err := engine.InitStorDb(acntSCfg); err != nil {
t.Fatal(err)
}
}
// Start CGR Engine
func testAccountSv1StartEngine(t *testing.T) {
if _, err := engine.StopStartEngine(acntSCfgPath, *waitRater); err != nil {
t.Fatal(err)
}
}
// Connect rpc client to rater
func testAccountSv1RPCConn(t *testing.T) {
var err error
acntSRPC, err = newRPCClient(acntSCfg.ListenCfg()) // We connect over JSON so we can also troubleshoot if needed
if err != nil {
t.Fatal(err)
}
}
func testAccountSv1LoadFromFolder(t *testing.T) {
var reply string
attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutaccounts")}
if err := acntSRPC.Call(utils.APIerSv1LoadTariffPlanFromFolder, attrs, &reply); err != nil {
t.Error(err)
}
time.Sleep(100 * time.Millisecond)
}
func testAccountSv1AccountProfileForEvent(t *testing.T) {
eAcnt := &utils.AccountProfile{
Tenant: "cgrates.org",
ID: "1001",
FilterIDs: []string{"*string:~*req.Account:1001"},
Balances: map[string]*utils.Balance{
"GenericBalance1": &utils.Balance{
ID: "GenericBalance1",
FilterIDs: []string{},
Weight: 20,
Type: utils.MetaAbstract,
CostIncrements: []*utils.CostIncrement{
&utils.CostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*voice"},
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 2)},
},
&utils.CostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*data"},
Increment: &utils.Decimal{decimal.New(1024, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 2)},
},
},
AttributeIDs: []string{},
RateProfileIDs: []string{},
UnitFactors: []*utils.UnitFactor{
&utils.UnitFactor{
FilterIDs: []string{"*string:~*req.ToR:*voice"},
Factor: &utils.Decimal{decimal.New(int64(time.Second), 0)},
},
&utils.UnitFactor{
FilterIDs: []string{"*string:~*req.ToR:*data"},
Factor: &utils.Decimal{decimal.New(int64(1024*time.Second), 0)},
},
},
Units: &utils.Decimal{decimal.New(int64(time.Hour), 0)},
},
"MonetaryBalance1": &utils.Balance{
ID: "MonetaryBalance1",
FilterIDs: []string{},
Weight: 30,
Type: utils.MetaConcrete,
CostIncrements: []*utils.CostIncrement{
&utils.CostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*voice"},
Increment: &utils.Decimal{decimal.New(int64(time.Second), 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 2)},
},
&utils.CostIncrement{
FilterIDs: []string{"*string:~*req.ToR:*data"},
Increment: &utils.Decimal{decimal.New(1024, 0)},
FixedFee: &utils.Decimal{decimal.New(0, 0)},
RecurrentFee: &utils.Decimal{decimal.New(1, 2)},
},
},
AttributeIDs: []string{},
RateProfileIDs: []string{},
UnitFactors: []*utils.UnitFactor{},
Units: &utils.Decimal{decimal.New(5, 0)},
},
"MonetaryBalance2": &utils.Balance{
ID: "MonetaryBalance2",
FilterIDs: []string{},
Weight: 10,
Type: utils.MetaConcrete,
CostIncrements: []*utils.CostIncrement{},
AttributeIDs: []string{},
RateProfileIDs: []string{},
UnitFactors: []*utils.UnitFactor{},
Units: &utils.Decimal{decimal.New(3, 0)},
},
},
ThresholdIDs: []string{utils.MetaNone},
}
var acnt *utils.AccountProfile
if err := acntSRPC.Call(utils.AccountSv1AccountProfileForEvent,
&utils.ArgsAccountForEvent{CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "testAccountSv1AccountProfileForEvent",
Event: map[string]interface{}{
utils.AccountField: "1001",
}}}, &acnt); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eAcnt, acnt) {
t.Errorf("Expecting : %s \n received: %s", utils.ToJSON(eAcnt), utils.ToJSON(acnt))
}
}

View File

@@ -0,0 +1,43 @@
{
// CGRateS sample configuration file
// Copyright (C) ITsysCOM GmbH
//
"data_db": {
"db_type": "*internal",
},
"stor_db": {
"db_type": "*internal",
},
"attributes": {
"enabled": true,
},
"rates": {
"enabled": true,
},
"accounts": {
"enabled": true,
"attributes_conns": ["*localhost"],
"rates_conns": ["*localhost"],
},
"apiers": {
"enabled": true,
},
}

View File

@@ -1,3 +1,5 @@
#Tenant,ID,FilterIDs,ActivationInterval,Weight,BalanceID,BalanceFilterIDs,BalanceWeight,BalanceBlocker,BalanceType,BalanceOpts,BalanceCostIncrements,BalanceAttributeIDs,BalanceRateProfileIDs,BalanceUnitFactors,BalanceUnits,ThresholdIDs
cgrates.org,1001,,,20,MonetaryBalance,,10,,*monetary,,fltr1&fltr2;1.3;2.3;3.3,attr1;attr2,,fltr1&fltr2;100;fltr3;200,14,*none
cgrates.org,1001,,,,VoiceBalance,,10,,*voice,,,,,,3600000000000,
cgrates.org,1001,*string:~*req.Account:1001,,,MonetaryBalance1,,30,,*concrete,,*string:~*req.ToR:*voice;1000000000;0;0.01;*string:~*req.ToR:*data;1024;0;0.01,,,,5,*none
cgrates.org,1001,,,,GenericBalance1,,20,,*abstract,,*string:~*req.ToR:*voice;1000000000;0;0.01;*string:~*req.ToR:*data;1024;0;0.01,,,*string:~*req.ToR:*voice;1000000000;*string:~*req.ToR:*data;1024000000000,3600000000000,
cgrates.org,1001,,,,MonetaryBalance2,,10,,*concrete,,,,,,3,
cgrates.org,1002,*string:~*req.Account:1002,,10,MonetaryBalance1,,,,*concrete,,*string:~*req.ToR:*voice;1000000000;0;0.01;;1;0;1,,,,10,*none
1 #Tenant ID FilterIDs ActivationInterval Weight BalanceID BalanceFilterIDs BalanceWeight BalanceBlocker BalanceType BalanceOpts BalanceCostIncrements BalanceAttributeIDs BalanceRateProfileIDs BalanceUnitFactors BalanceUnits ThresholdIDs
2 cgrates.org 1001 *string:~*req.Account:1001 20 MonetaryBalance MonetaryBalance1 10 30 *monetary *concrete fltr1&fltr2;1.3;2.3;3.3 *string:~*req.ToR:*voice;1000000000;0;0.01;*string:~*req.ToR:*data;1024;0;0.01 attr1;attr2 fltr1&fltr2;100;fltr3;200 14 5 *none
3 cgrates.org 1001 VoiceBalance GenericBalance1 10 20 *voice *abstract *string:~*req.ToR:*voice;1000000000;0;0.01;*string:~*req.ToR:*data;1024;0;0.01 *string:~*req.ToR:*voice;1000000000;*string:~*req.ToR:*data;1024000000000 3600000000000
4 cgrates.org 1001 MonetaryBalance2 10 *concrete 3
5 cgrates.org 1002 *string:~*req.Account:1002 10 MonetaryBalance1 *concrete *string:~*req.ToR:*voice;1000000000;0;0.01;;1;0;1 10 *none

View File

@@ -1564,8 +1564,11 @@ const (
)
const (
AccountSv1 = "AccountSv1"
AccountSv1Ping = "AccountSv1.Ping"
AccountSv1 = "AccountSv1"
AccountSv1Ping = "AccountSv1.Ping"
AccountSv1AccountProfileForEvent = "AccountSv1.AccountProfileForEvent"
AccountSv1MaxUsage = "AccountSv1.MaxUsage"
AccountSv1DebitUsage = "AccountSv1.DebitUsage"
)
const (