mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-16 05:39:54 +05:00
AccountS - integration test for AccountSv1AccountProfileForEvent API
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
208
apier/v1/accountsv1_it_test.go
Normal file
208
apier/v1/accountsv1_it_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
43
data/conf/samples/accounts_internal/cgrates.json
Normal file
43
data/conf/samples/accounts_internal/cgrates.json
Normal 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,
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user