/* 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 */ package engine import ( "encoding/json" "reflect" "testing" "time" "github.com/cgrates/birpc" "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) var ( NAT = &Destination{Id: "NAT", Prefixes: []string{"0257", "0256", "0723"}} RET = &Destination{Id: "RET", Prefixes: []string{"0723", "0724"}} ) func TestBalanceStoreRestore(t *testing.T) { b := &Balance{Value: 14, Weight: 1, Uuid: "test", ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)} marsh := NewCodecMsgpackMarshaler() output, err := marsh.Marshal(b) if err != nil { t.Error("Error storing balance: ", err) } b1 := &Balance{} err = marsh.Unmarshal(output, b1) if err != nil { t.Error("Error restoring balance: ", err) } //t.Logf("INITIAL: %+v", b) if !b.Equal(b1) { t.Errorf("Balance store/restore failed: expected %+v was %+v", b, b1) } } func TestBalanceStoreRestoreZero(t *testing.T) { b := &Balance{} output, err := marsh.Marshal(b) if err != nil { t.Error("Error storing balance: ", err) } b1 := &Balance{} err = marsh.Unmarshal(output, b1) if err != nil { t.Error("Error restoring balance: ", err) } if !b.Equal(b1) { t.Errorf("Balance store/restore failed: expected %v was %v", b, b1) } } func TestBalancesStoreRestore(t *testing.T) { bc := Balances{&Balance{Value: 14, ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)}, &Balance{Value: 1024}} output, err := marsh.Marshal(bc) if err != nil { t.Error("Error storing balance chain: ", err) } bc1 := Balances{} err = marsh.Unmarshal(output, &bc1) if err != nil { t.Error("Error restoring balance chain: ", err) } if !bc.Equal(bc1) { t.Errorf("Balance chain store/restore failed: expected %v was %v", bc, bc1) } } func TestAccountStorageStoreRestore(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}} rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}}} dm.SetAccount(rifsBalance) ub1, err := dm.GetAccount("other") if err != nil || !ub1.BalanceMap[utils.MONETARY].Equal(rifsBalance.BalanceMap[utils.MONETARY]) { t.Log("UB: ", ub1) t.Errorf("Expected %v was %v", rifsBalance, ub1) } } func TestGetSecondsForPrefix(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}} ub1 := &Account{ID: "CUSTOMER_1:rif", BalanceMap: map[string]Balances{ utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 200}}}} cd := &CallDescriptor{ Category: "0", Tenant: "vdf", TimeStart: time.Date(2013, 10, 4, 15, 46, 0, 0, time.UTC), TimeEnd: time.Date(2013, 10, 4, 15, 46, 10, 0, time.UTC), LoopIndex: 0, DurationIndex: 10 * time.Second, Destination: "0723", ToR: utils.VOICE, } seconds, credit, bucketList := ub1.getCreditForPrefix(cd) expected := 110 * time.Second if credit != 200 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight { t.Log(seconds, credit, bucketList) t.Errorf("Expected %v was %v", expected, seconds) } } func TestGetSpecialPricedSeconds(t *testing.T) { b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "minu"} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}, RatingSubject: "minu"} ub1 := &Account{ ID: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]Balances{ utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}, }, } cd := &CallDescriptor{ Category: "0", Tenant: "vdf", TimeStart: time.Date(2013, 10, 4, 15, 46, 0, 0, time.UTC), TimeEnd: time.Date(2013, 10, 4, 15, 46, 60, 0, time.UTC), LoopIndex: 0, Destination: "0723", ToR: utils.VOICE, } seconds, credit, bucketList := ub1.getCreditForPrefix(cd) expected := 20 * time.Second if credit != 0 || seconds != expected || len(bucketList) != 2 || bucketList[0].Weight < bucketList[1].Weight { t.Errorf("Expected %v was %v", expected, seconds) } } func TestAccountStorageStore(t *testing.T) { if DB == "mongo" { return // mongo will have a problem with null and {} so the Equal will not work } b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}} rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}}} dm.SetAccount(rifsBalance) result, err := dm.GetAccount(rifsBalance.ID) if err != nil || rifsBalance.ID != result.ID || len(rifsBalance.BalanceMap[utils.VOICE]) < 2 || len(result.BalanceMap[utils.VOICE]) < 2 || !(rifsBalance.BalanceMap[utils.VOICE][0].Equal(result.BalanceMap[utils.VOICE][0])) || !(rifsBalance.BalanceMap[utils.VOICE][1].Equal(result.BalanceMap[utils.VOICE][1])) || !rifsBalance.BalanceMap[utils.MONETARY].Equal(result.BalanceMap[utils.MONETARY]) { t.Errorf("Expected %s was %s", utils.ToIJSON(rifsBalance), utils.ToIJSON(result)) } } func TestDebitCreditZeroSecond(t *testing.T) { b1 := &Balance{ Uuid: "testb", Value: 10 * float64(time.Second), Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "*zero1s"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{Rating: &RIRate{ Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), Destination: "0723045326", Category: "0", ToR: utils.VOICE, testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, utils.MONETARY: {&Balance{ Categories: utils.NewStringMap("0"), Value: 21}}}} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" { t.Logf("%+v", cc.Timespans[0]) t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 0 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.VOICE][0]) } } func TestDebitCreditBlocker(t *testing.T) { b1 := &Balance{Uuid: "testa", Value: 0.1152, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "passmonde", Blocker: true} b2 := &Balance{Uuid: utils.MetaDefault, Value: 1.5, Weight: 0} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ConnectFee: 0.15, Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 0.1, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, deductConnectFee: true, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), Destination: "0723045326", Category: "0", ToR: utils.VOICE, testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{utils.MONETARY: {b1, b2}}} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, true, true) if err != nil { t.Error("Error debiting balance: ", err) } if len(cc.Timespans) != 0 { t.Error("Wrong call cost: ", utils.ToIJSON(cc)) } if rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 0.1152 || rifsBalance.BalanceMap[utils.MONETARY][1].GetValue() != 1.5 { t.Error("should not have touched the balances: ", utils.ToIJSON(rifsBalance.BalanceMap[utils.MONETARY])) } } func TestDebitFreeEmpty(t *testing.T) { cc := &CallCost{ Destination: "112", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{Rating: &RIRate{ ConnectFee: 0, Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 0, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, deductConnectFee: true, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), Tenant: "CUSTOMER_1", Subject: "rif:from:tm", Destination: "112", Category: "0", ToR: utils.VOICE, testCallcost: cc, } // empty account rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{utils.MONETARY: {}}} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, true, true) if err != nil { t.Error("Error debiting balance: ", err) } if len(cc.Timespans) == 0 || cc.Cost != 0 { t.Error("Wrong call cost: ", utils.ToIJSON(cc)) } if len(rifsBalance.BalanceMap[utils.MONETARY]) != 0 { t.Error("should not have touched the balances: ", utils.ToIJSON(rifsBalance.BalanceMap[utils.MONETARY])) } } func TestDebitCreditZeroMinute(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 70 * float64(time.Second), Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "*zero1m"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), Destination: "0723045326", Category: "0", ToR: utils.VOICE, testCallcost: cc, } rifsBalance := &Account{ ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, utils.MONETARY: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } //t.Logf("%+v", cc.Timespans) if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Errorf("Error setting balance id to increment: %s", utils.ToJSON(cc)) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Errorf("Error extracting minutes from balance: %s", utils.ToJSON(rifsBalance.BalanceMap[utils.VOICE][0])) } } func TestDebitCreditZeroMixedMinute(t *testing.T) { b1 := &Balance{ Uuid: "testm", Value: 70 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "*zero1m", Weight: 5} b2 := &Balance{Uuid: "tests", Value: 10 * float64(time.Second), Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "*zero1s"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 20, 0, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.Timespans[0].GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "tests" || cc.Timespans[1].Increments[0].BalanceInfo.Unit.UUID != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans) } if rifsBalance.BalanceMap[utils.VOICE][1].GetValue() != 0 || rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Logf("TS0: %+v", cc.Timespans[0]) t.Logf("TS1: %+v", cc.Timespans[1]) t.Errorf("Error extracting minutes from balance: %+v", rifsBalance.BalanceMap[utils.VOICE][1]) } } func TestDebitCreditNoCredit(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 70 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "*zero1m", Weight: 10} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, { TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), DurationIndex: 10 * time.Second, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[1].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err == nil { t.Error("Showing no enough credit error ") } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 10*float64(time.Second) { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.VOICE][0]) } if len(cc.Timespans) != 1 || cc.Timespans[0].GetDuration() != time.Minute { t.Error("Error truncating extra timespans: ", cc.Timespans) } } func TestDebitCreditHasCredit(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 70 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, Weight: 10, RatingSubject: "*zero1m"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, { TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), DurationIndex: 10 * time.Second, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[1].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, utils.MONETARY: {&Balance{Uuid: "moneya", Value: 110}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 30 { t.Errorf("Error extracting minutes from balance: %+v, %+v", rifsBalance.BalanceMap[utils.VOICE][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } if len(cc.Timespans) != 3 || cc.Timespans[0].GetDuration() != time.Minute { t.Error("Error truncating extra timespans: ", cc.Timespans) } } func TestDebitCreditSplitMinutesMoney(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 10 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, Weight: 10, RatingSubject: "*zero1s"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 20, 0, time.UTC), DurationIndex: 0, ratingInfo: &RatingInfo{}, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, utils.MONETARY: {&Balance{Uuid: "moneya", Value: 50}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].Duration != 1*time.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0].Duration) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 0 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 30 { t.Errorf("Error extracting minutes from balance: %+v, %+v", rifsBalance.BalanceMap[utils.VOICE][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != 10*time.Second || cc.Timespans[1].GetDuration() != 20*time.Second { t.Error("Error truncating extra timespans: ", cc.Timespans[1].GetDuration()) } } func TestDebitCreditMoreTimespans(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 150 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, Weight: 10, RatingSubject: "*zero1m"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, { TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), DurationIndex: 10 * time.Second, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[1].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 30*float64(time.Second) { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.VOICE][0]) } } func TestDebitCreditMoreTimespansMixed(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 70 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, Weight: 10, RatingSubject: "*zero1m"} b2 := &Balance{Uuid: "testa", Value: 150 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, Weight: 5, RatingSubject: "*zero1s"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, { TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), DurationIndex: 10 * time.Second, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[1].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1, b2}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.VOICE][1].GetValue() != 130*float64(time.Second) { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.VOICE][1], cc.Timespans[1]) } } func TestDebitCreditNoConectFeeCredit(t *testing.T) { b1 := &Balance{Uuid: "testb", Value: 70 * float64(time.Second), DestinationIDs: utils.StringMap{"NAT": true}, Weight: 10, RatingSubject: "*zero1m"} cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ConnectFee: 10.0, Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, { TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), DurationIndex: 10 * time.Second, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, deductConnectFee: true, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[1].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err == nil { t.Error("Error showing debiting balance error: ", err) } if len(cc.Timespans) != 1 || rifsBalance.BalanceMap[utils.MONETARY].GetTotalValue() != 0 { t.Error("Error cutting at no connect fee: ", rifsBalance.BalanceMap[utils.MONETARY]) } } func TestDebitCreditMoneyOnly(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, { TimeStart: time.Date(2013, 9, 24, 10, 48, 10, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), DurationIndex: 10 * time.Second, ratingInfo: &RatingInfo{}, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[1].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Uuid: "money", Value: 50}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err == nil { t.Error("Missing noy enough credit error ") } if cc.Timespans[0].Increments[0].BalanceInfo.Monetary.UUID != "money" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Logf("%+v", cc.Timespans[0].Increments) t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0].BalanceInfo) } if rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 0 { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MONETARY][0]) } if len(cc.Timespans) != 2 || cc.Timespans[0].GetDuration() != 10*time.Second || cc.Timespans[1].GetDuration() != 40*time.Second { t.Error("Error truncating extra timespans: ", cc.Timespans) } } func TestDebitCreditSubjectMinutes(t *testing.T) { b1 := &Balance{Uuid: "testb", Categories: utils.NewStringMap("0"), Value: 250 * float64(time.Second), Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "minu"} cc := &CallCost{ Tenant: "vdf", Category: "0", Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, deductConnectFee: true, } cd := &CallDescriptor{ Tenant: cc.Tenant, Category: "0", TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.VOICE: {b1}, utils.MONETARY: {&Balance{Uuid: "moneya", Value: 350}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testb" || cc.Timespans[0].Increments[0].BalanceInfo.Monetary.UUID != "moneya" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Errorf("Error setting balance id to increment: %+v", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.VOICE][0].GetValue() != 180*float64(time.Second) || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 280 { t.Errorf("Error extracting minutes from balance: %+v, %+v", rifsBalance.BalanceMap[utils.VOICE][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } if len(cc.Timespans) != 1 || cc.Timespans[0].GetDuration() != 70*time.Second { for _, ts := range cc.Timespans { t.Log(ts) } t.Error("Error truncating extra timespans: ", cc.Timespans) } } func TestDebitCreditSubjectMoney(t *testing.T) { cc := &CallCost{ Tenant: "vdf", Category: "0", Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 10, 0, time.UTC), DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, deductConnectFee: true, } cd := &CallDescriptor{ Tenant: cc.Tenant, Category: cc.Category, TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{Uuid: "moneya", Value: 75, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "minu"}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Monetary.UUID != "moneya" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 5 { t.Errorf("Error extracting minutes from balance: %+v", rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } if len(cc.Timespans) != 1 || cc.Timespans[0].GetDuration() != 70*time.Second { t.Error("Error truncating extra timespans: ", cc.Timespans) } } func TestAccountdebitBalance(t *testing.T) { ub := &Account{ ID: "rif", AllowNegative: true, BalanceMap: map[string]Balances{ utils.SMS: {&Balance{Value: 14}}, utils.DATA: {&Balance{Value: 1204}}, utils.VOICE: { &Balance{Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } newMb := &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NEW": true}), } a := &Action{Balance: newMb} ub.debitBalanceAction(a, false, false) if len(ub.BalanceMap[utils.VOICE]) != 3 || !ub.BalanceMap[utils.VOICE][2].DestinationIDs.Equal(*newMb.DestinationIDs) { t.Errorf("Error adding minute bucket! %d %+v %+v", len(ub.BalanceMap[utils.VOICE]), ub.BalanceMap[utils.VOICE][2], newMb) } } func TestAccountdebitBalanceExists(t *testing.T) { ub := &Account{ ID: "rif", AllowNegative: true, BalanceMap: map[string]Balances{ utils.SMS: {&Balance{Value: 14}}, utils.DATA: {&Balance{Value: 1024}}, utils.VOICE: { &Balance{ Value: 15, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } newMb := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), } a := &Action{Balance: newMb} ub.debitBalanceAction(a, false, false) if len(ub.BalanceMap[utils.VOICE]) != 2 || ub.BalanceMap[utils.VOICE][0].GetValue() != 25 { t.Error("Error adding minute bucket!") } } func TestAccountAddMinuteNil(t *testing.T) { ub := &Account{ ID: "rif", AllowNegative: true, BalanceMap: map[string]Balances{ utils.SMS: {&Balance{Value: 14}}, utils.DATA: {&Balance{Value: 1024}}, utils.VOICE: { &Balance{Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } ub.debitBalanceAction(nil, false, false) if len(ub.BalanceMap[utils.VOICE]) != 2 { t.Error("Error adding minute bucket!") } } func TestAccountAddMinutBucketEmpty(t *testing.T) { mb1 := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), } mb2 := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), } mb3 := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.VOICE), DestinationIDs: utils.StringMapPointer(utils.StringMap{"OTHER": true}), } ub := &Account{} a := &Action{Balance: mb1} ub.debitBalanceAction(a, false, false) if len(ub.BalanceMap[utils.VOICE]) != 1 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE]) } a = &Action{Balance: mb2} ub.debitBalanceAction(a, false, false) if len(ub.BalanceMap[utils.VOICE]) != 1 || ub.BalanceMap[utils.VOICE][0].GetValue() != 20 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE]) } a = &Action{Balance: mb3} ub.debitBalanceAction(a, false, false) if len(ub.BalanceMap[utils.VOICE]) != 2 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.VOICE]) } } func TestAccountExecuteTriggeredActions(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{Value: 100}}, utils.VOICE: { &Balance{Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Value: 1, Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ActionsID: "TEST_ACTIONS"}}, } ub.countUnits(1, utils.MONETARY, new(CallCost), nil) if ub.BalanceMap[utils.MONETARY][0].GetValue() != 110 || ub.BalanceMap[utils.VOICE][0].GetValue() != 20*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue()) } // are set to executed ub.countUnits(1, utils.MONETARY, nil, nil) if ub.BalanceMap[utils.MONETARY][0].GetValue() != 110 || ub.BalanceMap[utils.VOICE][0].GetValue() != 20*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue()) } // we can reset them ub.ResetActionTriggers(nil) ub.countUnits(10, utils.MONETARY, nil, nil) if ub.BalanceMap[utils.MONETARY][0].GetValue() != 120 || ub.BalanceMap[utils.VOICE][0].GetValue() != 30*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue()) } } func TestAccountExecuteTriggeredActionsBalance(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ Value: 100}}, utils.VOICE: { &Balance{ Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{ Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}, Value: 1.0}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 100, ThresholdType: utils.TRIGGER_MIN_EVENT_COUNTER, ActionsID: "TEST_ACTIONS"}}, } ub.countUnits(1, utils.MONETARY, nil, nil) if ub.BalanceMap[utils.MONETARY][0].GetValue() != 110 || ub.BalanceMap[utils.VOICE][0].GetValue() != 20*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue(), len(ub.BalanceMap[utils.MONETARY])) } } func TestAccountExecuteTriggeredActionsOrder(t *testing.T) { ub := &Account{ ID: "TEST_UB_OREDER", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{Value: 100}}}, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Value: 1, Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ActionsID: "TEST_ACTIONS_ORDER"}}, } ub.countUnits(1, utils.MONETARY, new(CallCost), nil) if len(ub.BalanceMap[utils.MONETARY]) != 1 || ub.BalanceMap[utils.MONETARY][0].GetValue() != 10 { t.Errorf("Error executing triggered actions in order %v", ub.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestAccountExecuteTriggeredDayWeek(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{Value: 100}}, utils.VOICE: { &Balance{Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{UniqueID: "day_trigger", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 10, ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ActionsID: "TEST_ACTIONS"}, &ActionTrigger{UniqueID: "week_trigger", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 100, ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ActionsID: "TEST_ACTIONS"}, }, } ub.InitCounters() if len(ub.UnitCounters) != 1 || len(ub.UnitCounters[utils.MONETARY][0].Counters) != 2 { t.Error("Error initializing counters: ", ub.UnitCounters[utils.MONETARY][0].Counters[0]) } ub.countUnits(1, utils.MONETARY, new(CallCost), nil) if ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[1].Value != 1 { t.Error("Error incrementing both counters", ub.UnitCounters[utils.MONETARY][0].Counters[0].Value, ub.UnitCounters[utils.MONETARY][0].Counters[1].Value) } // we can reset them resetCountersAction(ub, &Action{ Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), ID: utils.StringPointer("day_trigger")}}, nil, nil) if ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 0 || ub.UnitCounters[utils.MONETARY][0].Counters[1].Value != 1 { t.Error("Error reseting both counters", ub.UnitCounters[utils.MONETARY][0].Counters[0].Value, ub.UnitCounters[utils.MONETARY][0].Counters[1].Value) } } func TestAccountExpActionTrigger(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{Value: 100, ExpirationDate: time.Date(2015, time.November, 9, 9, 48, 0, 0, time.UTC)}}, utils.VOICE: { &Balance{Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10 * float64(time.Second), DestinationIDs: utils.StringMap{"RET": true}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ID: "check expired balances", Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 10, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsID: "TEST_ACTIONS"}, }, } ub.ExecuteActionTriggers(nil) if ub.BalanceMap[utils.MONETARY][0].IsExpiredAt(time.Now()) || ub.BalanceMap[utils.MONETARY][0].GetValue() != 10 || // expired was cleaned ub.BalanceMap[utils.VOICE][0].GetValue() != 20*float64(time.Second) || ub.ActionTriggers[0].Executed != true { t.Log(ub.BalanceMap[utils.MONETARY][0].IsExpiredAt(time.Now())) t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue(), len(ub.BalanceMap[utils.MONETARY])) } } func TestAccountExpActionTriggerNotActivated(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Value: 100}}, utils.VOICE: { &Balance{Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ID: "check expired balances", ActivationDate: time.Date(2116, 2, 5, 18, 0, 0, 0, time.UTC), Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 10, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsID: "TEST_ACTIONS"}, }, } ub.ExecuteActionTriggers(nil) if ub.BalanceMap[utils.MONETARY][0].IsExpiredAt(time.Now()) || ub.BalanceMap[utils.MONETARY][0].GetValue() != 100 || ub.BalanceMap[utils.VOICE][0].GetValue() != 10 || ub.ActionTriggers[0].Executed != false { t.Log(ub.BalanceMap[utils.MONETARY][0].IsExpiredAt(time.Now())) t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue(), len(ub.BalanceMap[utils.MONETARY])) } } func TestAccountExpActionTriggerExpired(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Value: 100}}, utils.VOICE: {&Balance{Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ID: "check expired balances", ExpirationDate: time.Date(2016, 2, 4, 18, 0, 0, 0, time.UTC), Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 10, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsID: "TEST_ACTIONS"}, }, } ub.ExecuteActionTriggers(nil) if ub.BalanceMap[utils.MONETARY][0].IsExpiredAt(time.Now()) || ub.BalanceMap[utils.MONETARY][0].GetValue() != 100 || ub.BalanceMap[utils.VOICE][0].GetValue() != 10 || len(ub.ActionTriggers) != 0 { t.Log(ub.BalanceMap[utils.MONETARY][0].IsExpiredAt(time.Now())) t.Error("Error executing triggered actions", ub.BalanceMap[utils.MONETARY][0].GetValue(), ub.BalanceMap[utils.VOICE][0].GetValue(), len(ub.BalanceMap[utils.MONETARY])) } } func TestCleanExpired(t *testing.T) { ub := &Account{ ID: "TEST_UB_OREDER", BalanceMap: map[string]Balances{utils.MONETARY: { &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}, &Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}}, utils.VOICE: { &Balance{ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC)}, &Balance{ExpirationDate: time.Now().Add(10 * time.Second)}, }}, ActionTriggers: ActionTriggers{ &ActionTrigger{ ExpirationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC), }, &ActionTrigger{ ActivationDate: time.Date(2013, 7, 18, 14, 33, 0, 0, time.UTC), }, }, } ub.CleanExpiredStuff() if len(ub.BalanceMap[utils.MONETARY]) != 2 { t.Error("Error cleaning expired balances!") } if len(ub.BalanceMap[utils.VOICE]) != 1 { t.Error("Error cleaning expired minute buckets!") } if len(ub.ActionTriggers) != 1 { t.Error("Error cleaning expired action triggers!") } } func TestAccountUnitCounting(t *testing.T) { ub := &Account{UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{{ Counters: CounterFilters{&CounterFilter{Value: 0}}}}}} ub.countUnits(10, utils.MONETARY, &CallCost{}, nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 10 { t.Error("Error counting units") } ub.countUnits(10, utils.MONETARY, &CallCost{}, nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 20 { t.Error("Error counting units") } } func TestAccountUnitCountingOutbound(t *testing.T) { ub := &Account{UnitCounters: UnitCounters{utils.MONETARY: []*UnitCounter{ {Counters: CounterFilters{&CounterFilter{Value: 0}}}}}} ub.countUnits(10, utils.MONETARY, new(CallCost), nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 10 { t.Error("Error counting units") } ub.countUnits(10, utils.MONETARY, new(CallCost), nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 20 { t.Error("Error counting units") } ub.countUnits(10, utils.MONETARY, new(CallCost), nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 30 { t.Error("Error counting units") } } func TestAccountUnitCountingOutboundInbound(t *testing.T) { ub := &Account{UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{ {Counters: CounterFilters{&CounterFilter{Value: 0}}}}}} ub.countUnits(10, utils.MONETARY, new(CallCost), nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 10 { t.Errorf("Error counting units: %+v", ub.UnitCounters[utils.MONETARY][0].Counters[0]) } ub.countUnits(10, utils.MONETARY, new(CallCost), nil) if len(ub.UnitCounters[utils.MONETARY]) != 1 || ub.UnitCounters[utils.MONETARY][0].Counters[0].Value != 20 { t.Error("Error counting units") } } func TestDebitShared(t *testing.T) { cc := &CallCost{ Tenant: "vdf", Category: "0", Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 0, 0, time.UTC), DurationIndex: 55 * time.Second, RateInterval: &RateInterval{Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 2, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, deductConnectFee: true, } cd := &CallDescriptor{ Tenant: cc.Tenant, Category: cc.Category, TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rif := &Account{ID: "rif", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Uuid: "moneya", Value: 0, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} groupie := &Account{ID: "groupie", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Uuid: "moneyc", Value: 130, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} sg := &SharedGroup{Id: "SG_TEST", MemberIds: utils.NewStringMap(rif.ID, groupie.ID), AccountParameters: map[string]*SharingParameters{"*any": {Strategy: STRATEGY_MINE_RANDOM}}} dm.SetAccount(groupie) dm.SetSharedGroup(sg, utils.NonTransactional) cc, err := rif.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if rif.BalanceMap[utils.MONETARY][0].GetValue() != 0 { t.Errorf("Error debiting from shared group: %+v", rif.BalanceMap[utils.MONETARY][0]) } groupie, _ = dm.GetAccount("groupie") if groupie.BalanceMap[utils.MONETARY][0].GetValue() != 10 { t.Errorf("Error debiting from shared group: %+v", groupie.BalanceMap[utils.MONETARY][0]) } if len(cc.Timespans) != 1 { t.Errorf("Wrong number of timespans: %v", cc.Timespans) } if len(cc.Timespans[0].Increments) != 6 { t.Errorf("Wrong number of increments: %v", cc.Timespans[0].Increments) for index, incr := range cc.Timespans[0].Increments { t.Errorf("I%d: %+v (%+v)", index, incr, incr.BalanceInfo) } } if cc.Timespans[0].Increments[0].BalanceInfo.AccountID != "groupie" || cc.Timespans[0].Increments[1].BalanceInfo.AccountID != "groupie" || cc.Timespans[0].Increments[2].BalanceInfo.AccountID != "groupie" || cc.Timespans[0].Increments[3].BalanceInfo.AccountID != "groupie" || cc.Timespans[0].Increments[4].BalanceInfo.AccountID != "groupie" || cc.Timespans[0].Increments[5].BalanceInfo.AccountID != "groupie" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } } func TestMaxDurationShared(t *testing.T) { cc := &CallCost{ Tenant: "vdf", Category: "0", Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 0, 0, time.UTC), DurationIndex: 55 * time.Second, RateInterval: &RateInterval{ Rating: &RIRate{Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 2, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, deductConnectFee: true, } cd := &CallDescriptor{ Tenant: cc.Tenant, Category: cc.Category, TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rif := &Account{ID: "rif", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Uuid: "moneya", Value: 0, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} groupie := &Account{ID: "groupie", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Uuid: "moneyc", Value: 130, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} sg := &SharedGroup{Id: "SG_TEST", MemberIds: utils.NewStringMap(rif.ID, groupie.ID), AccountParameters: map[string]*SharingParameters{"*any": {Strategy: STRATEGY_MINE_RANDOM}}} dm.SetAccount(groupie) dm.SetSharedGroup(sg, utils.NonTransactional) duration, err := cd.getMaxSessionDuration(rif) if err != nil { t.Error("Error getting max session duration from shared group: ", err) } if duration != 1*time.Minute { t.Error("Wrong max session from shared group: ", duration) } } func TestMaxDurationConnectFeeOnly(t *testing.T) { cd := &CallDescriptor{ Tenant: "cgrates.org", Category: "call", TimeStart: time.Date(2015, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2015, 9, 24, 10, 58, 1, 0, time.UTC), Destination: "4444", Subject: "dy", Account: "dy", ToR: utils.VOICE, DurationIndex: 600, } rif := &Account{ID: "rif", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Uuid: "moneya", Value: 0.2}}, }} duration, err := cd.getMaxSessionDuration(rif) if err != nil { t.Error("Error getting max session duration: ", err) } if duration != 0 { t.Error("Wrong max session: ", duration) } } func TestDebitSMS(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 0, 1, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1, RateUnit: time.Nanosecond}}}}, }, }, ToR: utils.SMS, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.SMS: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MONETARY: { &Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.SMS][0].GetValue() != 99 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Log(cc.Timespans[0].Increments) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.SMS][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestDebitGeneric(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 0, 1, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{ GroupIntervalStart: 0, Value: 100, RateIncrement: 1, RateUnit: time.Nanosecond, }, }, }, }, }, }, ToR: utils.GENERIC, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.GENERIC: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MONETARY: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Log(cc.Timespans[0].Increments) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestDebitGenericBalance(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 30, 0, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ ID: "other", BalanceMap: map[string]Balances{ utils.GENERIC: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}, Factor: ValueFactor{utils.VOICE: 60 * float64(time.Second)}}}, utils.MONETARY: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.49999 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Logf("%+v", cc.Timespans[0].Increments[0]) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestDebitGenericBalanceWithRatingSubject(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 30, 0, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 0, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.VOICE, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.GENERIC: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}, Factor: ValueFactor{utils.VOICE: 60 * float64(time.Second)}, RatingSubject: "free"}}, utils.MONETARY: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if cc.Timespans[0].Increments[0].BalanceInfo.Unit.UUID != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans[0]) } if rifsBalance.BalanceMap[utils.GENERIC][0].GetValue() != 99.49999 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Logf("%+v", cc.Timespans[0].Increments[0]) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.GENERIC][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestDebitDataUnits(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 48, 0, 80, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 2, RateIncrement: 1, RateUnit: 1}, &Rate{GroupIntervalStart: 60, Value: 1, RateIncrement: 1, RateUnit: 1}, }, }, }, }, }, ToR: utils.DATA, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.DATA: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MONETARY: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) // test rating information ts := cc.Timespans[0] if ts.MatchedSubject != "testm" || ts.MatchedPrefix != "0723" || ts.MatchedDestId != "NAT" || ts.RatingPlanId != utils.META_NONE { t.Errorf("Error setting rating info: %+v", ts.ratingInfo) } if err != nil { t.Error("Error debiting balance: ", err) } if ts.Increments[0].BalanceInfo.Unit.UUID != "testm" { t.Error("Error setting balance id to increment: ", ts.Increments[0]) } if rifsBalance.BalanceMap[utils.DATA][0].GetValue() != 20 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 21 { t.Log(ts.Increments) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.DATA][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestDebitDataMoney(t *testing.T) { cc := &CallCost{ Destination: "0723045326", Timespans: []*TimeSpan{ { TimeStart: time.Date(2013, 9, 24, 10, 48, 0, 0, time.UTC), TimeEnd: time.Date(2013, 9, 24, 10, 49, 20, 0, time.UTC), ratingInfo: &RatingInfo{}, DurationIndex: 0, RateInterval: &RateInterval{ Rating: &RIRate{ Rates: RateGroups{ &Rate{GroupIntervalStart: 0, Value: 2, RateIncrement: time.Minute, RateUnit: time.Second}, }, }, }, }, }, ToR: utils.DATA, } cd := &CallDescriptor{ TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.DATA: {&Balance{Uuid: "testm", Value: 0, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MONETARY: {&Balance{Value: 160}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true) if err != nil { t.Error("Error debiting balance: ", err) } if rifsBalance.BalanceMap[utils.DATA][0].GetValue() != 0 || rifsBalance.BalanceMap[utils.MONETARY][0].GetValue() != 0 { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.DATA][0].GetValue(), rifsBalance.BalanceMap[utils.MONETARY][0].GetValue()) } } func TestAccountGetDefaultMoneyBalanceEmpty(t *testing.T) { acc := &Account{} defBal := acc.GetDefaultMoneyBalance() if defBal == nil || len(acc.BalanceMap) != 1 || !defBal.IsDefault() { t.Errorf("Bad default money balance: %+v", defBal) } } func TestAccountGetDefaultMoneyBalance(t *testing.T) { acc := &Account{} acc.BalanceMap = make(map[string]Balances) tag := utils.MONETARY acc.BalanceMap[tag] = append(acc.BalanceMap[tag], &Balance{Weight: 10}) defBal := acc.GetDefaultMoneyBalance() if defBal == nil || len(acc.BalanceMap[tag]) != 2 || !defBal.IsDefault() { t.Errorf("Bad default money balance: %+v", defBal) } } func TestAccountInitCounters(t *testing.T) { a := &Account{ ActionTriggers: ActionTriggers{ &ActionTrigger{ UniqueID: "TestTR1", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR11", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR2", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR3", ThresholdType: utils.TRIGGER_MAX_BALANCE_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR4", ThresholdType: utils.TRIGGER_MAX_BALANCE_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.SMS), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR5", ThresholdType: utils.TRIGGER_MAX_BALANCE, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.SMS), Weight: utils.Float64Pointer(10), }, }, }, } a.InitCounters() if len(a.UnitCounters) != 3 || len(a.UnitCounters[utils.MONETARY][0].Counters) != 2 || len(a.UnitCounters[utils.VOICE][0].Counters) != 1 || len(a.UnitCounters[utils.VOICE][1].Counters) != 1 || len(a.UnitCounters[utils.SMS][0].Counters) != 1 { for key, counters := range a.UnitCounters { t.Log(key) for _, uc := range counters { t.Logf("UC: %+v", uc) for _, c := range uc.Counters { t.Logf("B: %+v", c) } } } t.Errorf("Error Initializing unit counters: %v", len(a.UnitCounters)) } } func TestAccountDoubleInitCounters(t *testing.T) { a := &Account{ ActionTriggers: ActionTriggers{ &ActionTrigger{ UniqueID: "TestTR1", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR11", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR2", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR3", ThresholdType: utils.TRIGGER_MAX_BALANCE_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR4", ThresholdType: utils.TRIGGER_MAX_BALANCE_COUNTER, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.SMS), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR5", ThresholdType: utils.TRIGGER_MAX_BALANCE, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.SMS), Weight: utils.Float64Pointer(10), }, }, }, } a.InitCounters() a.InitCounters() if len(a.UnitCounters) != 3 || len(a.UnitCounters[utils.MONETARY][0].Counters) != 2 || len(a.UnitCounters[utils.VOICE][0].Counters) != 1 || len(a.UnitCounters[utils.VOICE][1].Counters) != 1 || len(a.UnitCounters[utils.SMS][0].Counters) != 1 { for key, counters := range a.UnitCounters { t.Log(key) for _, uc := range counters { t.Logf("UC: %+v", uc) for _, c := range uc.Counters { t.Logf("B: %+v", c) } } } t.Errorf("Error Initializing unit counters: %v", len(a.UnitCounters)) } } func TestAccountGetBalancesForPrefixMixed(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": true, "RET": false}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MONETARY, "", time.Now()) if len(bcs) != 0 { t.Error("error excluding on mixed balances") } } func TestAccountGetBalancesForPrefixAllExcl(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": false, "RET": false}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MONETARY, "", time.Now()) if len(bcs) == 0 { t.Error("error finding balance on all excluded") } } func TestAccountGetBalancesForPrefixMixedGood(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": true, "RET": false, "EXOTIC": true}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MONETARY, "", time.Now()) if len(bcs) == 0 { t.Error("error finding on mixed balances good") } } func TestAccountGetBalancesForPrefixMixedBad(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": true, "RET": false, "EXOTIC": false}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MONETARY, "", time.Now()) if len(bcs) != 0 { t.Error("error excluding on mixed balances bad") } } func TestAccountNewAccountSummaryFromJSON(t *testing.T) { if acnt, err := NewAccountSummaryFromJSON("null"); err != nil { t.Error(err) } else if acnt != nil { t.Errorf("Expecting nil, received: %+v", acnt) } } func TestAccountAsAccountDigest(t *testing.T) { acnt1 := &Account{ ID: "cgrates.org:account1", AllowNegative: true, BalanceMap: map[string]Balances{ utils.SMS: {&Balance{ID: "sms1", Value: 14}}, utils.MMS: {&Balance{ID: "mms1", Value: 140}}, utils.DATA: {&Balance{ID: "data1", Value: 1204}}, utils.VOICE: { &Balance{ID: "voice1", Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}, Value: 3600}, &Balance{ID: "voice2", Weight: 10, DestinationIDs: utils.StringMap{"RET": true}, Value: 1200}, }, }, } expectacntSummary := &AccountSummary{ Tenant: "cgrates.org", ID: "account1", BalanceSummaries: []*BalanceSummary{ {ID: "data1", Type: utils.DATA, Value: 1204, Disabled: false}, {ID: "sms1", Type: utils.SMS, Value: 14, Disabled: false}, {ID: "mms1", Type: utils.MMS, Value: 140, Disabled: false}, {ID: "voice1", Type: utils.VOICE, Value: 3600, Disabled: false}, {ID: "voice2", Type: utils.VOICE, Value: 1200, Disabled: false}, }, AllowNegative: true, Disabled: false, } acntSummary := acnt1.AsAccountSummary() // Since maps are unordered, slices will be too so we need to find element to compare if !reflect.DeepEqual(expectacntSummary, acntSummary) { t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(expectacntSummary), utils.ToJSON(acntSummary)) } } func TestAccountGetBalancesGetBalanceWithSameWeight(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ ID: "SpecialBalance1", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance2", Value: 10, Weight: 10.0, }, }, }, } bcs := acc.getBalancesForPrefix("", "", utils.MONETARY, "", time.Now()) if len(bcs) != 2 && bcs[0].ID != "SpecialBalance1" && bcs[1].ID != "SpecialBalance2" { t.Errorf("Unexpected order balances : %+v", utils.ToJSON(bcs)) } } func TestAccountGetBalancesForPrefix2(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ ID: "SpecialBalance1", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance2", Value: 10, Weight: 20.0, }, }, }, } bcs := acc.getBalancesForPrefix("", "", utils.MONETARY, "", time.Now()) if len(bcs) != 2 && bcs[0].ID != "SpecialBalance2" && bcs[0].Weight != 20.0 { t.Errorf("Unexpected order balances : %+v", utils.ToJSON(bcs)) } } func TestAccountGetMultipleBalancesForPrefixWithSameWeight(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ ID: "SpecialBalance1", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance2", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance3", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance4", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance5", Value: 10, Weight: 10.0, }, }, }, } bcs := acc.getBalancesForPrefix("", "", utils.MONETARY, "", time.Now()) if len(bcs) != 5 && bcs[0].ID != "SpecialBalance1" && bcs[1].ID != "SpecialBalance2" && bcs[2].ID != "SpecialBalance3" && bcs[3].ID != "SpecialBalance4" && bcs[4].ID != "SpecialBalance5" { t.Errorf("Unexpected order balances : %+v", utils.ToJSON(bcs)) } } func TestAccountClone(t *testing.T) { account := &Account{} eOut := &Account{} if rcv := account.Clone(); !reflect.DeepEqual(eOut, rcv) { t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eOut), utils.ToJSON(rcv)) } account = &Account{ ID: "testID", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Value: 10, Weight: 10}}}, ActionTriggers: []*ActionTrigger{ { ID: "ActionTriggerID1", }, { ID: "ActionTriggerID2", }, }, AllowNegative: true, Disabled: true, } eOut = &Account{ ID: "testID", BalanceMap: map[string]Balances{ utils.MONETARY: {&Balance{Value: 10, Weight: 10}}}, ActionTriggers: []*ActionTrigger{ { ID: "ActionTriggerID1", }, { ID: "ActionTriggerID2", }, }, AllowNegative: true, Disabled: true, } if rcv := account.Clone(); !reflect.DeepEqual(eOut, rcv) { t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eOut), utils.ToJSON(rcv)) } } func TestAccountGetBalanceWithID(t *testing.T) { account := &Account{ BalanceMap: map[string]Balances{ "type1": {&Balance{ID: "test1", Value: 0.7}}, "type2": {&Balance{ID: "test2", Value: 0.8}}, }, } if rcv := account.GetBalanceWithID("type1", "test1"); rcv.Value != 0.7 { t.Errorf("Expecting: 0.7, received: %+v", rcv) } if rcv := account.GetBalanceWithID("type2", "test2"); rcv.Value != 0.8 { t.Errorf("Expecting: 0.8, received: %+v", rcv) } if rcv := account.GetBalanceWithID("unknown", "unknown"); rcv != nil { t.Errorf("Expecting: nil, received: %+v", rcv) } } func TestAccExecuteAT(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() tmpDm := dm defer func() { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) SetDataStorage(tmpDm) }() db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(db, cfg.CacheCfg(), nil) acc := &Account{ ID: "cgrates.org:1001", ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ ID: utils.StringPointer("test_1234"), Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 100, ThresholdType: "*max_*monetary_counter", ActionsID: "TEST_ACTIONS"}, }, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{ { CounterType: "*monetary", Counters: CounterFilters{&CounterFilter{ Value: 101, Filter: &BalanceFilter{ ID: utils.StringPointer("test_1234"), }, }, }}, }, }, BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{Value: 10}, }, utils.VOICE: { &Balance{Value: 10, Weight: 20, DestinationIDs: utils.NewStringMap(utils.MetaDDC + "DEST1")}, &Balance{Weight: 10, DestinationIDs: utils.NewStringMap(utils.MetaDDC + "DEST2")}, }, }, } aT := &Action{ ActionType: utils.TOPUP, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 15}, }, } dm.SetActions("TEST_ACTIONS", Actions{&Action{ Id: "MINI", ActionType: utils.SET_DDESTINATIONS, ExpirationString: utils.UNLIMITED, Weight: 10, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Value: &utils.ValueFormula{Static: 100, Params: make(map[string]any)}, Weight: utils.Float64Pointer(10), Blocker: utils.BoolPointer(false), }, }}, utils.NonTransactional) SetDataStorage(dm) acc.ExecuteActionTriggers(aT) if _, err := dm.GetAccount("cgrates.org:1001"); err != nil { t.Error(err) } } func TestAccountSetRrecurrentAction(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() tmpDm := dm defer func() { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) SetDataStorage(tmpDm) }() db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(db, cfg.CacheCfg(), nil) ub := &Account{ ID: "cgrates.org:1001", ActionTriggers: ActionTriggers{ &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ActionsID: "ACT_1", Executed: true}, &ActionTrigger{Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY)}, ThresholdValue: 2, ActionsID: "TEST_ACTIONS", Executed: true}}, } aT := ActionTrigger{ UniqueID: "TestTR3", ActionsID: "ACT_1", Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(10), }, } dm.SetActions("ACT_1", Actions{ {ActionType: utils.SET_RECURRENT, Balance: &BalanceFilter{Type: utils.StringPointer(utils.MONETARY), Value: &utils.ValueFormula{Static: 25}, DestinationIDs: utils.StringMapPointer(utils.NewStringMap("RET")), Weight: utils.Float64Pointer(20)}}, }, utils.NonTransactional) config.SetCgrConfig(cfg) SetDataStorage(dm) if err := aT.Execute(ub); err != nil { t.Error(err) } } func TestAccountEnableAccAct(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() tmpDm := dm db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(db, cfg.CacheCfg(), nil) defer func() { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) SetDataStorage(tmpDm) }() acc := &Account{ ID: "cgrates.org:1001", BalanceMap: map[string]Balances{ utils.VOICE: { &Balance{ Weight: 20, DestinationIDs: utils.StringMap{"DEST1": true}, ExpirationDate: time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC), }, &Balance{ Weight: 10, DestinationIDs: utils.StringMap{"DEST2": true}, }}, }, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), ExpirationDate: utils.TimePointer(time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC)), Weight: utils.Float64Pointer(20), DestinationIDs: &utils.StringMap{ "DEST1": true, }, }, ThresholdValue: 2, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsID: "ACT_1", Executed: false}, }, } a := &Action{ ActionType: utils.ENABLE_ACCOUNT, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), ExpirationDate: utils.TimePointer(time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC)), Weight: utils.Float64Pointer(20), DestinationIDs: &utils.StringMap{ "DEST1": true, }, }, } dm.SetActions("ACT_1", Actions{a}, utils.NonTransactional) SetDataStorage(dm) acc.ExecuteActionTriggers(a) if acc, err := dm.GetAccount("cgrates.org:1001"); err != nil || acc.Disabled { t.Error(err) } } func TestAccountPublishAct(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() tmpDm := dm db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(db, cfg.CacheCfg(), nil) defer func() { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) SetDataStorage(tmpDm) }() ub := &Account{ ID: "cgrates.org:1002", BalanceMap: map[string]Balances{ utils.MONETARY: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"DEST1": true, "DEST2": false}, ExpirationDate: time.Date(2022, 12, 23, 20, 0, 0, 0, time.UTC), }, }}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), DestinationIDs: &utils.StringMap{"DEST1": true, "DEST2": false}, ExpirationDate: utils.TimePointer(time.Date(2022, 12, 23, 20, 0, 0, 0, time.UTC)), }, ThresholdValue: 2, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsID: "TEST_ACTIONS_ORDER"}, }, } a := &Action{} SetDataStorage(dm) ub.ExecuteActionTriggers(a) } func TestTestAccountActUnSetCurr(t *testing.T) { cfg, _ := config.NewDefaultCGRConfig() tmpDm := dm db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(db, cfg.CacheCfg(), nil) defer func() { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) SetDataStorage(tmpDm) }() acc := &Account{ ID: "cgrates.org:1001", BalanceMap: map[string]Balances{ utils.VOICE: { &Balance{ Weight: 20, DestinationIDs: utils.StringMap{"DEST1": true}, ExpirationDate: time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC), }, }, }, ActionTriggers: ActionTriggers{ &ActionTrigger{ Recurrent: true, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), ExpirationDate: utils.TimePointer(time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC)), Weight: utils.Float64Pointer(20), DestinationIDs: &utils.StringMap{ "DEST1": true, }, }, ThresholdValue: 2, ThresholdType: utils.TRIGGER_BALANCE_EXPIRED, ActionsID: "ACT_1", Executed: false}, }, } a := &Action{ ActionType: utils.UNSET_RECURRENT, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), ExpirationDate: utils.TimePointer(time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC)), Weight: utils.Float64Pointer(20), DestinationIDs: &utils.StringMap{ "DEST1": true, }, }, } dm.SetActions("ACT_1", Actions{a}, utils.NonTransactional) SetDataStorage(dm) acc.ExecuteActionTriggers(a) if acc, err := dm.GetAccount("cgrates.org:1001"); err != nil || acc.ActionTriggers[0].Recurrent { t.Error(err) } } func TestPublishAccount(t *testing.T) { cfgfunc := func() *config.CGRConfig { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) return cfg2 } cfg := cfgfunc() cfg.RalsCfg().StatSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStatS)} cfg.RalsCfg().ThresholdSConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaThresholds)} defer func() { cfgfunc() }() clientConn := make(chan birpc.ClientConnector, 1) clientConn <- clMock(func(ctx *context.Context, serviceMethod string, _, _ any) error { if serviceMethod == utils.StatSv1ProcessEvent { return nil } if serviceMethod == utils.ThresholdSv1ProcessEvent { return nil } return utils.ErrNotFound }) conngMgr := NewConnManager(cfg, map[string]chan birpc.ClientConnector{ utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStatS): clientConn, utils.ConcatenatedKey(utils.MetaInternal, utils.MetaThresholds): clientConn, }) SetConnManager(conngMgr) acc := &Account{ ID: "cgrates.org:1001", BalanceMap: map[string]Balances{ utils.VOICE: { &Balance{ Weight: 20, DestinationIDs: utils.StringMap{"DEST1": true}, ExpirationDate: time.Date(2023, 3, 12, 0, 0, 0, 0, time.UTC), }, &Balance{ Weight: 10, DestinationIDs: utils.StringMap{"DEST2": true}, }}, }, } acc.Publish() time.Sleep(5 * time.Millisecond) } func TestAccActDisable(t *testing.T) { cfgfunc := func() *config.CGRConfig { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) return cfg2 } Cache.Clear(nil) cfg := cfgfunc() db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) tmpDm := dm dm := NewDataManager(db, cfg.CacheCfg(), nil) dm.SetActions("ACTS", Actions{ &Action{ ActionType: utils.DISABLE_ACCOUNT, }, }, "") defer func() { cfgfunc() SetDataStorage(tmpDm) }() acc := &Account{ ID: "cgrates.org:1001", ActionTriggers: ActionTriggers{ &ActionTrigger{ ActionsID: "ACTS", UniqueID: "TestTR1", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ThresholdValue: 20, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20.0), }, }, }, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{{ CounterType: "max_event_counter", Counters: CounterFilters{&CounterFilter{Value: 21, Filter: &BalanceFilter{ ID: utils.StringPointer("TestTR1"), }}}}}, }, BalanceMap: map[string]Balances{ utils.VOICE: { &Balance{Value: 10, Weight: 20, DestinationIDs: utils.NewStringMap(utils.MetaDDC + "DEST1")}, }, }, } SetDataStorage(dm) a := &Action{} acc.ExecuteActionTriggers(a) if acc, err := dm.GetAccount("cgrates.org:1001"); err != nil || !acc.Disabled { t.Error(err) } } func TestAccActCdrLog(t *testing.T) { cfgfunc := func() *config.CGRConfig { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) return cfg2 } Cache.Clear(nil) cfg := cfgfunc() cfg.SchedulerCfg().CDRsConns = []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaCDRs)} db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) tmpDm := dm dm := NewDataManager(db, cfg.CacheCfg(), nil) dm.SetActions("ACTS", Actions{ &Action{ ActionType: utils.CDRLOG, ExtraParameters: `{"BalanceType": "60.0"}`, }, }, "") defer func() { cfgfunc() SetDataStorage(tmpDm) }() acc := &Account{ ID: "cgrates.org:1001", ActionTriggers: ActionTriggers{ &ActionTrigger{ ActionsID: "ACTS", UniqueID: "TestTR1", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ThresholdValue: 20, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20.0), }, }, }, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{{ CounterType: "max_event_counter", Counters: CounterFilters{&CounterFilter{Value: 21, Filter: &BalanceFilter{ ID: utils.StringPointer("TestTR1"), }}}}}, }, BalanceMap: map[string]Balances{ utils.VOICE: { &Balance{Value: 10, Weight: 20, DestinationIDs: utils.NewStringMap(utils.MetaDDC + "DEST1")}, }, }, } SetDataStorage(dm) a := &Action{} acc.ExecuteActionTriggers(a) } func TestAccActDebitResetAction(t *testing.T) { cfgfunc := func() *config.CGRConfig { cfg2, _ := config.NewDefaultCGRConfig() config.SetCgrConfig(cfg2) return cfg2 } Cache.Clear(nil) cfg := cfgfunc() db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) tmpDm := dm dm := NewDataManager(db, cfg.CacheCfg(), nil) dm.SetActions("ACTS", Actions{ &Action{ ActionType: utils.DEBIT_RESET, }, }, "") defer func() { cfgfunc() SetDataStorage(tmpDm) }() acc := &Account{ ID: "cgrates.org:1001", ActionTriggers: ActionTriggers{ &ActionTrigger{ ActionsID: "ACTS", UniqueID: "TestTR1", ThresholdType: utils.TRIGGER_MAX_EVENT_COUNTER, ThresholdValue: 20, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.VOICE), Weight: utils.Float64Pointer(20.0), }, }, }, UnitCounters: UnitCounters{ utils.MONETARY: []*UnitCounter{{ CounterType: "max_event_counter", Counters: CounterFilters{&CounterFilter{Value: 21, Filter: &BalanceFilter{ ID: utils.StringPointer("TestTR1"), }}}}}, }, BalanceMap: map[string]Balances{ utils.VOICE: { &Balance{Value: 10, Weight: 20, DestinationIDs: utils.NewStringMap(utils.MetaDDC + "DEST1")}, }, }, } SetDataStorage(dm) a := &Action{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MONETARY), ID: utils.StringPointer("Bal1"), }} acc.ExecuteActionTriggers(a) // unfinished } var bl bool = true var str2 string = "test" var fl2 float64 = 1.2 var nm2 int = 1 var tm2 time.Time = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) func TestAccountsetBalanceAction(t *testing.T) { var acc *Account err := acc.setBalanceAction(nil) if err != nil { if err.Error() != "nil action" { t.Error(err) } } sm := utils.StringMap{str: bl} rtm := RITiming{ Years: utils.Years{2021}, Months: utils.Months{8}, MonthDays: utils.MonthDays{28}, WeekDays: utils.WeekDays{5}, StartTime: str2, EndTime: str2, cronString: str2, tag: str2, } vf := ValueFactor{str2: fl2} vfr := utils.ValueFormula{ Method: str2, Params: map[string]any{str2: str2}, Static: fl2, } bf := BalanceFilter{ Uuid: &str2, ID: &str2, Type: &str2, Value: &vfr, ExpirationDate: &tm2, Weight: &fl2, DestinationIDs: &sm, RatingSubject: &str2, Categories: &sm, SharedGroups: &sm, TimingIDs: &sm, Timings: []*RITiming{&rtm}, Disabled: &bl, Factor: &vf, Blocker: &bl, } cf := CounterFilter{ Value: fl, Filter: &bf, } uc := UnitCounter{ CounterType: "*balance", Counters: CounterFilters{&cf}, } at := ActionTrigger{ ID: str2, UniqueID: str2, ThresholdType: str2, Recurrent: bl, MinSleep: 1 * time.Millisecond, ExpirationDate: tm2, ActivationDate: tm2, Balance: &bf, Weight: fl2, ActionsID: str2, MinQueuedItems: nm2, Executed: bl, LastExecutionTime: tm2, } ac := Account{ ID: str2, UnitCounters: UnitCounters{str: {&uc}}, ActionTriggers: ActionTriggers{&at}, AllowNegative: bl, Disabled: bl, UpdateTime: tm2, executingTriggers: bl, } a := Action{ Id: str2, ActionType: str2, ExtraParameters: str2, Filter: str2, ExpirationString: str2, Weight: fl2, Balance: &bf, balanceValue: fl2, } err = ac.setBalanceAction(&a) if err != nil { if err.Error() != "cannot find balance with uuid: " { t.Error(err) } } } func TestAccountGetSharedGroups(t *testing.T) { sm := utils.StringMap{ "test1": bl, } rtm := RITiming{ Years: utils.Years{2021}, Months: utils.Months{8}, MonthDays: utils.MonthDays{28}, WeekDays: utils.WeekDays{5}, StartTime: str, EndTime: str, cronString: str, tag: str, } vf := ValueFactor{str: fl} vfr := utils.ValueFormula{ Method: str, Params: map[string]any{str: str}, Static: fl, } bf := BalanceFilter{ Uuid: &str, ID: &str, Type: &str, Value: &vfr, ExpirationDate: &tm, Weight: &fl, DestinationIDs: &sm, RatingSubject: &str, Categories: &sm, SharedGroups: &sm, TimingIDs: &sm, Timings: []*RITiming{&rtm}, Disabled: &bl, Factor: &vf, Blocker: &bl, } cf := CounterFilter{ Value: fl, Filter: &bf, } uc := UnitCounter{ CounterType: "*balance", Counters: CounterFilters{&cf}, } at := ActionTrigger{ ID: str, UniqueID: str, ThresholdType: str, Recurrent: bl, MinSleep: 1 * time.Millisecond, ExpirationDate: tm, ActivationDate: tm, Balance: &bf, Weight: fl, ActionsID: str, MinQueuedItems: nm, Executed: bl, LastExecutionTime: tm, } blc := Balance{ Uuid: str2, ID: str2, Value: fl2, ExpirationDate: tm2, Weight: fl2, DestinationIDs: sm, RatingSubject: str2, Categories: sm, SharedGroups: sm, Timings: []*RITiming{&rtm}, TimingIDs: sm, Disabled: bl, Factor: ValueFactor{str2: fl2}, Blocker: bl, precision: nm2, dirty: bl, } ac := Account{ ID: str, BalanceMap: map[string]Balances{str: {&blc}}, UnitCounters: UnitCounters{str: {&uc}}, ActionTriggers: ActionTriggers{&at}, AllowNegative: bl, Disabled: bl, UpdateTime: tm, executingTriggers: bl, } rcv := ac.GetSharedGroups() exp := []string{"test1"} if !reflect.DeepEqual(rcv, exp) { t.Errorf("expected %v, received %v", exp, rcv) } } func TestAccountSummaryFieldAsInterface(t *testing.T) { bs := BalanceSummary{ UUID: str2, ID: str2, Type: str2, Value: fl2, Disabled: bl, } as := AccountSummary{ Tenant: str2, ID: str2, BalanceSummaries: BalanceSummaries{&bs}, AllowNegative: bl, Disabled: bl, } type exp struct { val any err string } tests := []struct { name string arg []string exp exp }{ { name: "empty field path", arg: []string{}, exp: exp{val: nil, err: "NOT_FOUND"}, }, { name: "default case", arg: []string{"BalanceSummaries[0]"}, exp: exp{val: &bs, err: ""}, }, { name: "unsupported field prefix", arg: []string{"test"}, exp: exp{val: nil, err: "unsupported field prefix: "}, }, { name: "case Tenant not found", arg: []string{"Tenant", "test"}, exp: exp{val: nil, err: "NOT_FOUND"}, }, { name: "case BalanceSummaries not found", arg: []string{"BalanceSummaries", "test"}, exp: exp{val: nil, err: "NOT_FOUND"}, }, { name: "case AllowNegative not found", arg: []string{"AllowNegative", "test"}, exp: exp{val: nil, err: "NOT_FOUND"}, }, { name: "case ID not found", arg: []string{"ID", "test"}, exp: exp{val: nil, err: "NOT_FOUND"}, }, { name: "case Disabled not found", arg: []string{"Disabled", "test"}, exp: exp{val: nil, err: "NOT_FOUND"}, }, { name: "case Tenant", arg: []string{"Tenant"}, exp: exp{val: str2, err: ""}, }, { name: "case ID", arg: []string{"ID"}, exp: exp{val: str2, err: ""}, }, { name: "case BalanceSummaries", arg: []string{"BalanceSummaries"}, exp: exp{val: BalanceSummaries{&bs}, err: ""}, }, { name: "case AllowNegative", arg: []string{"AllowNegative"}, exp: exp{val: bl, err: ""}, }, { name: "case Disabled", arg: []string{"Disabled"}, exp: exp{val: bl, err: ""}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rcv, err := as.FieldAsInterface(tt.arg) if err != nil { if err.Error() != tt.exp.err { t.Error(err) } } if !reflect.DeepEqual(rcv, tt.exp.val) { t.Errorf("expected %v, received %v", tt.exp.val, rcv) } }) } } func TestAccountSummaryUpdateBalances(t *testing.T) { bs := BalanceSummary{ UUID: str2, ID: str2, Type: str2, Value: fl2, Disabled: bl, } as := AccountSummary{ Tenant: str2, ID: str2, BalanceSummaries: BalanceSummaries{&bs}, AllowNegative: bl, Disabled: bl, } exp := as as.UpdateBalances(nil) if !reflect.DeepEqual(as, exp) { t.Error("Account summary was updated") } } func TestNewAccountSummaryFromJSON(t *testing.T) { bs := BalanceSummary{ UUID: str2, ID: str2, Type: str2, Value: fl2, Disabled: bl, } as := &AccountSummary{ Tenant: str2, ID: str2, BalanceSummaries: BalanceSummaries{&bs}, AllowNegative: bl, Disabled: bl, } arg, errJ := json.Marshal(as) if errJ != nil { t.Error(errJ) } rcv, err := NewAccountSummaryFromJSON(string(arg)) if err != nil { t.Error(err) } if !reflect.DeepEqual(rcv, as) { t.Errorf("expected %v, received %v", as, rcv) } } func TestAsOldStructure(t *testing.T) { sm := utils.StringMap{str: bl} rtm := RITiming{ Years: utils.Years{2021}, Months: utils.Months{8}, MonthDays: utils.MonthDays{28}, WeekDays: utils.WeekDays{5}, StartTime: str2, EndTime: str2, cronString: str2, tag: str2, } vf := ValueFactor{str2: fl2} vfr := utils.ValueFormula{ Method: str2, Params: map[string]any{str2: str2}, Static: fl2, } bf := BalanceFilter{ Uuid: &str2, ID: &str2, Type: &str2, Value: &vfr, ExpirationDate: &tm2, Weight: &fl2, DestinationIDs: &sm, RatingSubject: &str2, Categories: &sm, SharedGroups: &sm, TimingIDs: &sm, Timings: []*RITiming{&rtm}, Disabled: &bl, Factor: &vf, Blocker: &bl, } cf := CounterFilter{ Value: fl, Filter: &bf, } uc := UnitCounter{ CounterType: "*balance", Counters: CounterFilters{&cf}, } at := ActionTrigger{ ID: str2, UniqueID: str2, ThresholdType: str2, Recurrent: bl, MinSleep: 1 * time.Millisecond, ExpirationDate: tm2, ActivationDate: tm2, Balance: &bf, Weight: fl2, ActionsID: str2, MinQueuedItems: nm2, Executed: bl, LastExecutionTime: tm2, } blc := Balance{ Uuid: str2, ID: str2, Value: fl2, ExpirationDate: tm2, Weight: fl2, DestinationIDs: sm, RatingSubject: str2, Categories: sm, SharedGroups: sm, Timings: []*RITiming{&rtm}, TimingIDs: sm, Disabled: bl, Factor: ValueFactor{str2: fl2}, Blocker: bl, precision: nm2, dirty: bl, } acc := Account{ ID: str2, BalanceMap: map[string]Balances{str: {&blc}}, UnitCounters: UnitCounters{str: {&uc, nil}}, ActionTriggers: ActionTriggers{&at}, AllowNegative: bl, Disabled: bl, UpdateTime: tm2, executingTriggers: bl, } type Balance struct { Uuid string //system wide unique Id string // account wide unique Value float64 ExpirationDate time.Time Weight float64 DestinationIds string RatingSubject string Category string SharedGroup string Timings []*RITiming TimingIDs string Disabled bool precision int account *Account dirty bool } type Balances []*Balance type UnitsCounter struct { BalanceType string // Units float64 Balances Balances // first balance is the general one (no destination) } type ActionTrigger struct { Id string ThresholdType string ThresholdValue float64 Recurrent bool MinSleep time.Duration BalanceId string BalanceType string BalanceDestinationIds string BalanceWeight float64 BalanceExpirationDate time.Time BalanceTimingTags string BalanceRatingSubject string BalanceCategory string BalanceSharedGroup string BalanceDisabled bool Weight float64 ActionsId string MinQueuedItems int Executed bool } type ActionTriggers []*ActionTrigger type Account struct { Id string BalanceMap map[string]Balances UnitCounters []*UnitsCounter ActionTriggers ActionTriggers AllowNegative bool Disabled bool } rcv := acc.AsOldStructure() mrsh, err := json.Marshal(rcv) if err != nil { t.Error(err) } var ac *Account err = json.Unmarshal(mrsh, &ac) if err != nil { t.Error(err) } exp := utils.META_OUT + ":" + acc.ID if ac.Id != exp { t.Errorf("expected %s, received %s", ac.Id, exp) } if ac.AllowNegative != acc.AllowNegative { t.Error(ac.AllowNegative) } if ac.Disabled != acc.Disabled { t.Error(ac.Disabled) } } func TestAccountGetID(t *testing.T) { acc := Account{ ID: str2, } rcv := acc.GetID() if rcv != "" { t.Error(rcv) } } func TestAccountmatchActionFilter(t *testing.T) { acc := &Account{ ID: str2, UnitCounters: UnitCounters{str: {&uc}}, ActionTriggers: ActionTriggers{&at}, AllowNegative: bl, Disabled: bl, UpdateTime: tm2, executingTriggers: bl, } rcv, err := acc.matchActionFilter("test") if err != nil { if err.Error() != "invalid character 'e' in literal true (expecting 'r')" { t.Error(err) } } if rcv != false { t.Error(rcv) } } /*********************************** Benchmarks *******************************/ func BenchmarkGetSecondForPrefix(b *testing.B) { b.StopTimer() b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}} ub1 := &Account{ID: "other", BalanceMap: map[string]Balances{utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}}} cd := &CallDescriptor{ Destination: "0723", } b.StartTimer() for i := 0; i < b.N; i++ { ub1.getCreditForPrefix(cd) } } func BenchmarkAccountStorageStoreRestore(b *testing.B) { b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}} rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { dm.SetAccount(rifsBalance) dm.GetAccount(rifsBalance.ID) } } func BenchmarkGetSecondsForPrefix(b *testing.B) { b1 := &Balance{Value: 10, Weight: 10, DestinationIDs: utils.StringMap{"NAT": true}} b2 := &Balance{Value: 100, Weight: 20, DestinationIDs: utils.StringMap{"RET": true}} ub1 := &Account{ID: "OUT:CUSTOMER_1:rif", BalanceMap: map[string]Balances{utils.VOICE: {b1, b2}, utils.MONETARY: {&Balance{Value: 21}}}} cd := &CallDescriptor{ Destination: "0723", } for i := 0; i < b.N; i++ { ub1.getCreditForPrefix(cd) } }