/* 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 ( "bytes" "encoding/json" "log" "os" "reflect" "sort" "strings" "testing" "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" "github.com/google/go-cmp/cmp" ) 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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&Balance{Value: 21}}}} dm.SetAccount(rifsBalance) ub1, err := dm.GetAccount("other") if err != nil || !ub1.BalanceMap[utils.MetaMonetary].Equal(rifsBalance.BalanceMap[utils.MetaMonetary]) { 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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&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.MetaVoice, } 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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&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.MetaVoice, } 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) { 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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&Balance{Value: 21}}}} dm.SetAccount(rifsBalance) result, err := dm.GetAccount(rifsBalance.ID) if err != nil || rifsBalance.ID != result.ID || len(rifsBalance.BalanceMap[utils.MetaVoice]) < 2 || len(result.BalanceMap[utils.MetaVoice]) < 2 || !(rifsBalance.BalanceMap[utils.MetaVoice][0].Equal(result.BalanceMap[utils.MetaVoice][0])) || !(rifsBalance.BalanceMap[utils.MetaVoice][1].Equal(result.BalanceMap[utils.MetaVoice][1])) || !rifsBalance.BalanceMap[utils.MetaMonetary].Equal(result.BalanceMap[utils.MetaMonetary]) { 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{&RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice, testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.MetaVoice: {b1}, utils.MetaMonetary: {&Balance{ Categories: utils.NewStringMap("0"), Value: 21}}}} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) if err != nil { t.Fatal("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.MetaVoice][0].GetValue() != 0 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaVoice][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{&RGRate{GroupIntervalStart: 0, Value: 0.1, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, deductConnectFee: true, ToR: utils.MetaVoice, } 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.MetaVoice, testCallcost: cc, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{utils.MetaMonetary: {b1, b2}}} expCC := &CallCost{ Category: "0", Destination: "0723045326", ToR: "*voice", deductConnectFee: true, } if cc, err := rifsBalance.debitCreditBalance(cd, false, true, true, nil); err != nil { t.Error(err) } else if !reflect.DeepEqual(expCC, cc) { t.Errorf("expected <%+v>, \nreceived <%+v>", expCC, cc) } if rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 0.1152 || rifsBalance.BalanceMap[utils.MetaMonetary][1].GetValue() != 1.5 { t.Error("should not have touched the balances: ", utils.ToJSON(rifsBalance.BalanceMap[utils.MetaMonetary])) } } 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{ &RGRate{GroupIntervalStart: 0, Value: 0, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, deductConnectFee: true, ToR: utils.MetaVoice, } 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.MetaVoice, testCallcost: cc, } // empty account rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{utils.MetaMonetary: {}}} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, true, true, nil) 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.MetaMonetary]) != 0 { t.Error("should not have touched the balances: ", utils.ToIJSON(rifsBalance.BalanceMap[utils.MetaMonetary])) } } 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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice, testCallcost: cc, } rifsBalance := &Account{ ID: "other", BalanceMap: map[string]Balances{ utils.MetaVoice: {b1}, utils.MetaMonetary: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Errorf("Error extracting minutes from balance: %s", utils.ToJSON(rifsBalance.BalanceMap[utils.MetaVoice][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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][1].GetValue() != 0 || rifsBalance.BalanceMap[utils.MetaVoice][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MetaMonetary][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.MetaVoice][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{ &RGRate{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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice: {b1}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][0].GetValue() != 10*float64(time.Second) { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaVoice][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{ &RGRate{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{ &RGRate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice: {b1}, utils.MetaMonetary: {{Uuid: "moneya", Value: 110}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 30 { t.Errorf("Error extracting minutes from balance: %+v, %+v", rifsBalance.BalanceMap[utils.MetaVoice][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice: {b1}, utils.MetaMonetary: {&Balance{Uuid: "moneya", Value: 50}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0].Duration) } if rifsBalance.BalanceMap[utils.MetaVoice][0].GetValue() != 0 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 30 { t.Errorf("Error extracting minutes from balance: %+v, %+v", rifsBalance.BalanceMap[utils.MetaVoice][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice: {b1}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][0].GetValue() != 30*float64(time.Second) { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaVoice][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{ &RGRate{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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaVoice: {b1, b2}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][0].GetValue() != 10*float64(time.Second) || rifsBalance.BalanceMap[utils.MetaVoice][1].GetValue() != 130*float64(time.Second) { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaVoice][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{ &RGRate{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{ &RGRate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, 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.MetaVoice: {b1}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) if err == nil { t.Error("Error showing debiting balance error: ", err) } if len(cc.Timespans) != 1 || rifsBalance.BalanceMap[utils.MetaMonetary].GetTotalValue() != 0 { t.Error("Error cutting at no connect fee: ", rifsBalance.BalanceMap[utils.MetaMonetary]) } } 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{ &RGRate{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{ &RGRate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } 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.MetaMonetary: {&Balance{Uuid: "money", Value: 50}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaMonetary][0].GetValue() != 0 { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, 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.MetaVoice: {b1}, utils.MetaMonetary: {&Balance{Uuid: "moneya", Value: 350}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaVoice][0].GetValue() != 180*float64(time.Second) || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 280 { t.Errorf("Error extracting minutes from balance: %+v, %+v", rifsBalance.BalanceMap[utils.MetaVoice][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{GroupIntervalStart: 0, Value: 1, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, 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.MetaMonetary: { &Balance{Uuid: "moneya", Value: 75, DestinationIDs: utils.StringMap{"NAT": true}, RatingSubject: "minu"}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaMonetary][0].GetValue() != 5 { t.Errorf("Error extracting minutes from balance: %+v", rifsBalance.BalanceMap[utils.MetaMonetary][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.MetaSMS: {&Balance{Value: 14}}, utils.MetaData: {&Balance{Value: 1204}}, utils.MetaVoice: { &Balance{Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } newMb := &BalanceFilter{ Type: utils.StringPointer(utils.MetaVoice), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NEW": true}), } a := &Action{Balance: newMb} ub.debitBalanceAction(a, false, false, nil) if len(ub.BalanceMap[utils.MetaVoice]) != 3 || !ub.BalanceMap[utils.MetaVoice][2].DestinationIDs.Equal(*newMb.DestinationIDs) { t.Errorf("Error adding minute bucket! %d %+v %+v", len(ub.BalanceMap[utils.MetaVoice]), ub.BalanceMap[utils.MetaVoice][2], newMb) } } func TestAccountdebitBalanceExists(t *testing.T) { ub := &Account{ ID: "rif", AllowNegative: true, BalanceMap: map[string]Balances{ utils.MetaSMS: {&Balance{Value: 14}}, utils.MetaData: {&Balance{Value: 1024}}, utils.MetaVoice: { &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.MetaVoice), Weight: utils.Float64Pointer(20), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), } a := &Action{Balance: newMb} ub.debitBalanceAction(a, false, false, nil) if len(ub.BalanceMap[utils.MetaVoice]) != 2 || ub.BalanceMap[utils.MetaVoice][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.MetaSMS: {&Balance{Value: 14}}, utils.MetaData: {&Balance{Value: 1024}}, utils.MetaVoice: { &Balance{Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, } ub.debitBalanceAction(nil, false, false, nil) if len(ub.BalanceMap[utils.MetaVoice]) != 2 { t.Error("Error adding minute bucket!") } } func TestAccountAddMinutBucketEmpty(t *testing.T) { mb1 := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), } mb2 := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.StringMap{"NAT": true}), } mb3 := &BalanceFilter{ Value: &utils.ValueFormula{Static: -10}, Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.StringMap{"OTHER": true}), } ub := &Account{} a := &Action{Balance: mb1} ub.debitBalanceAction(a, false, false, nil) if len(ub.BalanceMap[utils.MetaVoice]) != 1 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.MetaVoice]) } a = &Action{Balance: mb2} ub.debitBalanceAction(a, false, false, nil) if len(ub.BalanceMap[utils.MetaVoice]) != 1 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 20 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.MetaVoice]) } a = &Action{Balance: mb3} ub.debitBalanceAction(a, false, false, nil) if len(ub.BalanceMap[utils.MetaVoice]) != 2 { t.Error("Error adding minute bucket: ", ub.BalanceMap[utils.MetaVoice]) } } func TestAccountExecuteTriggeredActions(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{Value: 100}}, utils.MetaVoice: { &Balance{Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, UnitCounters: UnitCounters{ utils.MetaMonetary: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Value: 1, Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 2, ThresholdType: utils.TriggerMaxEventCounter, ActionsID: "TEST_ACTIONS"}}, } ub.countUnits(1, utils.MetaMonetary, new(CallCost), nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 110 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 20*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue()) } // are set to executed ub.countUnits(1, utils.MetaMonetary, nil, nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 110 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 20*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue()) } // we can reset them ub.ResetActionTriggers(nil, nil) ub.countUnits(10, utils.MetaMonetary, nil, nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 120 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 30*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue()) } } func TestAccountExecuteTriggeredActionsBalance(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{ Value: 100}}, utils.MetaVoice: { &Balance{ Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{ Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, UnitCounters: UnitCounters{ utils.MetaMonetary: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, Value: 1.0}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 100, ThresholdType: utils.TriggerMinEventCounter, ActionsID: "TEST_ACTIONS"}}, } ub.countUnits(1, utils.MetaMonetary, nil, nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 110 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 20*float64(time.Second) { t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue(), len(ub.BalanceMap[utils.MetaMonetary])) } } func TestAccountExecuteTriggeredActionsOrder(t *testing.T) { ub := &Account{ ID: "TEST_UB_OREDER", BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{Value: 100}}}, UnitCounters: UnitCounters{ utils.MetaMonetary: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Value: 1, Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 2, ThresholdType: utils.TriggerMaxEventCounter, ActionsID: "TEST_ACTIONS_ORDER"}}, } ub.countUnits(1, utils.MetaMonetary, new(CallCost), nil, nil) if len(ub.BalanceMap[utils.MetaMonetary]) != 1 || ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 10 { t.Errorf("Error executing triggered actions in order %v", ub.BalanceMap[utils.MetaMonetary][0].GetValue()) } } func TestAccountExecuteTriggeredDayWeek(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{Value: 100}}, utils.MetaVoice: { &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.MetaMonetary)}, ThresholdValue: 10, ThresholdType: utils.TriggerMaxEventCounter, ActionsID: "TEST_ACTIONS"}, &ActionTrigger{UniqueID: "week_trigger", Balance: &BalanceFilter{Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 100, ThresholdType: utils.TriggerMaxEventCounter, ActionsID: "TEST_ACTIONS"}, }, } ub.InitCounters() if len(ub.UnitCounters) != 1 || len(ub.UnitCounters[utils.MetaMonetary][0].Counters) != 2 { t.Error("Error initializing counters: ", ub.UnitCounters[utils.MetaMonetary][0].Counters[0]) } ub.countUnits(1, utils.MetaMonetary, new(CallCost), nil, nil) if ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[1].Value != 1 { t.Error("Error incrementing both counters", ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value, ub.UnitCounters[utils.MetaMonetary][0].Counters[1].Value) } // we can reset them resetCountersAction(ub, &Action{ Balance: &BalanceFilter{Type: utils.StringPointer(utils.MetaMonetary), ID: utils.StringPointer("day_trigger")}}, nil, nil, nil, SharedActionsData{}, ActionConnCfg{}) if ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 0 || ub.UnitCounters[utils.MetaMonetary][0].Counters[1].Value != 1 { t.Error("Error reseting both counters", ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value, ub.UnitCounters[utils.MetaMonetary][0].Counters[1].Value) } } func TestAccountExpActionTrigger(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{Value: 100, ExpirationDate: time.Date(2015, time.November, 9, 9, 48, 0, 0, time.UTC)}}, utils.MetaVoice: { &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.MetaMonetary)}, ThresholdValue: 10, ThresholdType: utils.TriggerBalanceExpired, ActionsID: "TEST_ACTIONS"}, }, } ub.ExecuteActionTriggers(nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].IsExpiredAt(time.Now()) || ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 10 || // expired was cleaned ub.BalanceMap[utils.MetaVoice][0].GetValue() != 20*float64(time.Second) || ub.ActionTriggers[0].Executed != true { t.Log(ub.BalanceMap[utils.MetaMonetary][0].IsExpiredAt(time.Now())) t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue(), len(ub.BalanceMap[utils.MetaMonetary])) } } func TestAccountExpActionTriggerNotActivated(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MetaMonetary: {&Balance{Value: 100}}, utils.MetaVoice: { &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.MetaMonetary)}, ThresholdValue: 10, ThresholdType: utils.TriggerBalanceExpired, ActionsID: "TEST_ACTIONS"}, }, } ub.ExecuteActionTriggers(nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].IsExpiredAt(time.Now()) || ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 100 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 10 || ub.ActionTriggers[0].Executed != false { t.Log(ub.BalanceMap[utils.MetaMonetary][0].IsExpiredAt(time.Now())) t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue(), len(ub.BalanceMap[utils.MetaMonetary])) } } func TestAccountExpActionTriggerExpired(t *testing.T) { ub := &Account{ ID: "TEST_UB", BalanceMap: map[string]Balances{ utils.MetaMonetary: {&Balance{Value: 100}}, utils.MetaVoice: {&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.MetaMonetary)}, ThresholdValue: 10, ThresholdType: utils.TriggerBalanceExpired, ActionsID: "TEST_ACTIONS"}, }, } ub.ExecuteActionTriggers(nil, nil) if ub.BalanceMap[utils.MetaMonetary][0].IsExpiredAt(time.Now()) || ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 100 || ub.BalanceMap[utils.MetaVoice][0].GetValue() != 10 || len(ub.ActionTriggers) != 0 { t.Log(ub.BalanceMap[utils.MetaMonetary][0].IsExpiredAt(time.Now())) t.Error("Error executing triggered actions", ub.BalanceMap[utils.MetaMonetary][0].GetValue(), ub.BalanceMap[utils.MetaVoice][0].GetValue(), len(ub.BalanceMap[utils.MetaMonetary])) } } func TestCleanExpired(t *testing.T) { ub := &Account{ ID: "TEST_UB_OREDER", BalanceMap: map[string]Balances{utils.MetaMonetary: { &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.MetaVoice: { &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.MetaMonetary]) != 2 { t.Error("Error cleaning expired balances!") } if len(ub.BalanceMap[utils.MetaVoice]) != 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.MetaMonetary: []*UnitCounter{{ Counters: CounterFilters{&CounterFilter{Value: 0}}}}}} ub.countUnits(10, utils.MetaMonetary, &CallCost{}, nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 10 { t.Error("Error counting units") } ub.countUnits(10, utils.MetaMonetary, &CallCost{}, nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 20 { t.Error("Error counting units") } } func TestAccountUnitCountingOutbound(t *testing.T) { ub := &Account{UnitCounters: UnitCounters{utils.MetaMonetary: []*UnitCounter{ {Counters: CounterFilters{&CounterFilter{Value: 0}}}}}} ub.countUnits(10, utils.MetaMonetary, new(CallCost), nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 10 { t.Error("Error counting units") } ub.countUnits(10, utils.MetaMonetary, new(CallCost), nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 20 { t.Error("Error counting units") } ub.countUnits(10, utils.MetaMonetary, new(CallCost), nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 30 { t.Error("Error counting units") } } func TestAccountUnitCountingOutboundInbound(t *testing.T) { ub := &Account{UnitCounters: UnitCounters{ utils.MetaMonetary: []*UnitCounter{ {Counters: CounterFilters{&CounterFilter{Value: 0}}}}}} ub.countUnits(10, utils.MetaMonetary, new(CallCost), nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][0].Counters[0].Value != 10 { t.Errorf("Error counting units: %+v", ub.UnitCounters[utils.MetaMonetary][0].Counters[0]) } ub.countUnits(10, utils.MetaMonetary, new(CallCost), nil, nil) if len(ub.UnitCounters[utils.MetaMonetary]) != 1 || ub.UnitCounters[utils.MetaMonetary][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{ &RGRate{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.MetaMonetary: {&Balance{Uuid: "moneya", Value: 0, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} groupie := &Account{ID: "groupie", BalanceMap: map[string]Balances{ utils.MetaMonetary: {&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) cc, err := rif.debitCreditBalance(cd, false, false, true, nil) if err != nil { t.Error("Error debiting balance: ", err) } if rif.BalanceMap[utils.MetaMonetary][0].GetValue() != 0 { t.Errorf("Error debiting from shared group: %+v", rif.BalanceMap[utils.MetaMonetary][0]) } groupie, _ = dm.GetAccount("groupie") if groupie.BalanceMap[utils.MetaMonetary][0].GetValue() != 10 { t.Errorf("Error debiting from shared group: %+v", groupie.BalanceMap[utils.MetaMonetary][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{ &RGRate{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.MetaMonetary: {&Balance{Uuid: "moneya", Value: 0, SharedGroups: utils.NewStringMap("SG_TEST")}}, }} groupie := &Account{ID: "groupie", BalanceMap: map[string]Balances{ utils.MetaMonetary: {&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) duration, err := cd.getMaxSessionDuration(rif, nil) if err != nil { t.Error("Error getting max session duration from shared group: ", err) } if duration != 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.MetaVoice, DurationIndex: 600, } rif := &Account{ID: "rif", BalanceMap: map[string]Balances{ utils.MetaMonetary: {&Balance{Uuid: "moneya", Value: 0.2}}, }} duration, err := cd.getMaxSessionDuration(rif, nil) 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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1, RateUnit: time.Nanosecond}}}}, }, }, ToR: utils.MetaSMS, } 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.MetaSMS: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MetaMonetary: { &Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaSMS][0].GetValue() != 99 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Log(cc.Timespans[0].Increments) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaSMS][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{ GroupIntervalStart: 0, Value: 100, RateIncrement: 1, RateUnit: time.Nanosecond, }, }, }, }, }, }, ToR: utils.MetaGeneric, } 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.MetaGeneric: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MetaMonetary: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaGeneric][0].GetValue() != 99 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Log(cc.Timespans[0].Increments) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaGeneric][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue()) } } func TestDebitGenericBalance(t *testing.T) { cc := &CallCost{ Category: "call", 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{ &RGRate{GroupIntervalStart: 0, Value: 100, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } cd := &CallDescriptor{ Category: "call", TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, ExtraFields: map[string]string{ utils.BalanceFactorID: "call", }, } rifsBalance := &Account{ ID: "other", BalanceMap: map[string]Balances{ utils.MetaGeneric: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}, Factors: ValueFactors{"call": 1 / (60 * float64(time.Second))}}}, utils.MetaMonetary: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaGeneric][0].GetValue() != 99.49999 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Logf("%+v", cc.Timespans[0].Increments[0]) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaGeneric][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue()) } } func TestDebitGenericBalanceWithRatingSubject(t *testing.T) { cc := &CallCost{ Category: "call", 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{ &RGRate{GroupIntervalStart: 0, Value: 0, RateIncrement: time.Second, RateUnit: time.Second}}}}, }, }, ToR: utils.MetaVoice, } cd := &CallDescriptor{ Category: "call", TimeStart: cc.Timespans[0].TimeStart, TimeEnd: cc.Timespans[0].TimeEnd, Destination: cc.Destination, ToR: cc.ToR, DurationIndex: cc.GetDuration(), testCallcost: cc, ExtraFields: map[string]string{ utils.BalanceFactorID: "call", }, } rifsBalance := &Account{ID: "other", BalanceMap: map[string]Balances{ utils.MetaGeneric: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}, Factors: ValueFactors{"call": 1 / (60 * float64(time.Second))}, RatingSubject: "free"}}, utils.MetaMonetary: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) 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.MetaGeneric][0].GetValue() != 99.49999 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Logf("%+v", cc.Timespans[0].Increments[0]) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaGeneric][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{GroupIntervalStart: 0, Value: 2, RateIncrement: 1, RateUnit: 1}, &RGRate{GroupIntervalStart: 60, Value: 1, RateIncrement: 1, RateUnit: 1}, }, }, }, }, }, ToR: utils.MetaData, } 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.MetaData: { &Balance{Uuid: "testm", Value: 100, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MetaMonetary: {&Balance{Value: 21}}, }} var err error cc, err = rifsBalance.debitCreditBalance(cd, false, false, true, nil) // test rating information ts := cc.Timespans[0] if ts.MatchedSubject != "testm" || ts.MatchedPrefix != "0723" || ts.MatchedDestId != "NAT" || ts.RatingPlanId != utils.MetaNone { 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.MetaData][0].GetValue() != 20 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 21 { t.Log(ts.Increments) t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaData][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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{ &RGRate{GroupIntervalStart: 0, Value: 2, RateIncrement: time.Minute, RateUnit: time.Second}, }, }, }, }, }, ToR: utils.MetaData, } 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.MetaData: {&Balance{Uuid: "testm", Value: 0, Weight: 5, DestinationIDs: utils.StringMap{"NAT": true}}}, utils.MetaMonetary: {&Balance{Value: 160}}, }} if _, err := rifsBalance.debitCreditBalance(cd, false, false, true, nil); err != nil { t.Error("Error debiting balance: ", err) } if rifsBalance.BalanceMap[utils.MetaData][0].GetValue() != 0 || rifsBalance.BalanceMap[utils.MetaMonetary][0].GetValue() != 0 { t.Error("Error extracting minutes from balance: ", rifsBalance.BalanceMap[utils.MetaData][0].GetValue(), rifsBalance.BalanceMap[utils.MetaMonetary][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.MetaMonetary 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.TriggerMaxEventCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR11", ThresholdType: utils.TriggerMaxEventCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR2", ThresholdType: utils.TriggerMaxEventCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaVoice), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR3", ThresholdType: utils.TriggerMaxBalanceCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaVoice), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR4", ThresholdType: utils.TriggerMaxBalanceCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaSMS), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR5", ThresholdType: utils.TriggerMaxBalance, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaSMS), Weight: utils.Float64Pointer(10), }, }, }, } a.InitCounters() if len(a.UnitCounters) != 3 || len(a.UnitCounters[utils.MetaMonetary][0].Counters) != 2 || len(a.UnitCounters[utils.MetaVoice][0].Counters) != 1 || len(a.UnitCounters[utils.MetaVoice][1].Counters) != 1 || len(a.UnitCounters[utils.MetaSMS][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.TriggerMaxEventCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR11", ThresholdType: utils.TriggerMaxEventCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR2", ThresholdType: utils.TriggerMaxEventCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaVoice), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR3", ThresholdType: utils.TriggerMaxBalanceCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaVoice), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR4", ThresholdType: utils.TriggerMaxBalanceCounter, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaSMS), Weight: utils.Float64Pointer(10), }, }, &ActionTrigger{ UniqueID: "TestTR5", ThresholdType: utils.TriggerMaxBalance, Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaSMS), Weight: utils.Float64Pointer(10), }, }, }, } a.InitCounters() a.InitCounters() if len(a.UnitCounters) != 3 || len(a.UnitCounters[utils.MetaMonetary][0].Counters) != 2 || len(a.UnitCounters[utils.MetaVoice][0].Counters) != 1 || len(a.UnitCounters[utils.MetaVoice][1].Counters) != 1 || len(a.UnitCounters[utils.MetaSMS][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.MetaMonetary: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": true, "RET": false}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MetaMonetary, "", 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.MetaMonetary: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": false, "RET": false}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MetaMonetary, "", 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.MetaMonetary: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": true, "RET": false, "EXOTIC": true}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MetaMonetary, "", 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.MetaMonetary: { &Balance{ Value: 10, DestinationIDs: utils.StringMap{"NAT": true, "RET": false, "EXOTIC": false}, }, }, }, } bcs := acc.getBalancesForPrefix("999123", "", utils.MetaMonetary, "", 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.MetaSMS: {&Balance{ID: "sms1", Value: 14}}, utils.MetaMMS: {&Balance{ID: "mms1", Value: 140}}, utils.MetaData: {&Balance{ID: "data1", Value: 1204}}, utils.MetaVoice: { &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.MetaData, Value: 1204, Disabled: false}, {ID: "sms1", Type: utils.MetaSMS, Value: 14, Disabled: false}, {ID: "mms1", Type: utils.MetaMMS, Value: 140, Disabled: false}, {ID: "voice1", Type: utils.MetaVoice, Weight: 20, Value: 3600, Disabled: false}, {ID: "voice2", Type: utils.MetaVoice, Weight: 10, 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.MetaMonetary: { &Balance{ ID: "SpecialBalance1", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance2", Value: 10, Weight: 10.0, }, }, }, } bcs := acc.getBalancesForPrefix("", "", utils.MetaMonetary, "", 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.MetaMonetary: { &Balance{ ID: "SpecialBalance1", Value: 10, Weight: 10.0, }, &Balance{ ID: "SpecialBalance2", Value: 10, Weight: 20.0, }, }, }, } bcs := acc.getBalancesForPrefix("", "", utils.MetaMonetary, "", 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.MetaMonetary: { &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.MetaMonetary, "", 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.MetaMonetary: {&Balance{Value: 10, Weight: 10}}}, ActionTriggers: []*ActionTrigger{ { ID: "ActionTriggerID1", }, { ID: "ActionTriggerID2", }, }, AllowNegative: true, Disabled: true, } eOut = &Account{ ID: "testID", BalanceMap: map[string]Balances{ utils.MetaMonetary: {&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 TestAccountSetRecurrent(t *testing.T) { aType := "testType" var ptrType *string = &aType acc := &Account{ ActionTriggers: ActionTriggers{ &ActionTrigger{ ID: "testID1", Weight: 10, Balance: &BalanceFilter{ Type: ptrType, }, }, &ActionTrigger{ ID: "testID2", Weight: 10, }, }, } act := &Action{ Id: "testID", Weight: 10, Balance: &BalanceFilter{ Type: ptrType, }, } exp := ActionTriggers{ &ActionTrigger{ ID: "testID1", Weight: 10, Recurrent: true, Balance: &BalanceFilter{ Type: ptrType, }, }, &ActionTrigger{ ID: "testID2", Weight: 10, }, } acc.SetRecurrent(act, true) rcv := acc.ActionTriggers if len(rcv) != len(exp) { t.Fatalf("\nexpected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(rcv)) } for i := range rcv { if rcv[i].Recurrent != exp[i].Recurrent { t.Fatalf("\nexpected: <%+v>,\nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&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.MetaVoice: {b1, b2}, utils.MetaMonetary: {&Balance{Value: 21}}}} cd := &CallDescriptor{ Destination: "0723", } for i := 0; i < b.N; i++ { ub1.getCreditForPrefix(cd) } } func TestAccountSummaryFieldAsInterface(t *testing.T) { as := AccountSummary{ BalanceSummaries: BalanceSummaries{ &BalanceSummary{ UUID: "uId", ID: "id", Type: "*data", Initial: 20.54, Value: 1, }}, Tenant: "tenant", ID: "accID", } if _, err := as.FieldAsInterface(nil); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err := as.FieldAsInterface([]string{"test"}); err == nil { t.Error(err) } if val, err := as.FieldAsInterface([]string{"BalanceSummaries[0]"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(val, as.BalanceSummaries[0]) { t.Errorf("expected %v ,received %v", utils.ToJSON(val), utils.ToJSON(as.BalanceSummaries[0])) } else if _, err = as.FieldAsInterface([]string{"Tenant"}); err != nil { t.Error(err) } else if _, err = as.FieldAsInterface([]string{"Tenant", "Value"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = as.FieldAsInterface([]string{"ID"}); err != nil { t.Error(err) } else if _, err = as.FieldAsInterface([]string{"ID", "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } if val, err := as.FieldAsInterface([]string{"BalanceSummaries"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(val, as.BalanceSummaries) { t.Errorf("expected %v ,received %v ", utils.ToJSON(val), utils.ToJSON(as.BalanceSummaries)) } as.BalanceSummaries = BalanceSummaries{ &BalanceSummary{ UUID: "uId", ID: "id", Type: "*data", Initial: 20.54, Value: 1, }, &BalanceSummary{ UUID: "uId2", ID: "id2", Type: "*data", Initial: 20.54, Value: 1, }} if _, err := as.FieldAsInterface([]string{"BalanceSummaries", "id3"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if val, err := as.FieldAsInterface([]string{"BalanceSummaries", "id2"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(val, as.BalanceSummaries[1]) { t.Errorf("expected %v ,received %v", utils.ToJSON(val), utils.ToJSON(as.BalanceSummaries[1])) } if _, err = as.FieldAsInterface([]string{"AllowNegative"}); err != nil { t.Error(err) } else if _, err = as.FieldAsInterface([]string{"AllowNegative", "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = as.FieldAsInterface([]string{"Disabled"}); err != nil { t.Error(err) } else if _, err = as.FieldAsInterface([]string{"Disabled", "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } } func TestAccountSummaryFieldAsString(t *testing.T) { as := AccountSummary{ BalanceSummaries: BalanceSummaries{ &BalanceSummary{ UUID: "uId", ID: "id", Type: "*data", Initial: 20.54, Value: 1, }}, } rec := &BalanceSummary{} if _, err := as.FieldAsString([]string{}); err == nil { t.Error(err) } if val, err := as.FieldAsString([]string{"BalanceSummaries[0]"}); err != nil { t.Error(err) } else if err := json.Unmarshal([]byte(val), rec); err != nil { t.Error("Error converting value") } else if !reflect.DeepEqual(rec, as.BalanceSummaries[0]) { t.Errorf("expected %v ,received %v", utils.ToJSON(as.BalanceSummaries[0]), utils.ToJSON(rec)) } } func TestAccountFieldAsInterface(t *testing.T) { var acc *Account if _, err := acc.FieldAsInterface([]string{}); err != nil { t.Error(err) } acc = &Account{ ID: "id", BalanceMap: map[string]Balances{ "first": { &Balance{ Uuid: "uuid1", ID: "id", Value: 20.44, }, &Balance{ Uuid: "uuid2", ID: "id2", Value: 12.2, }, }}, UnitCounters: UnitCounters{ "first": []*UnitCounter{ { CounterType: "balance", Counters: CounterFilters{ { Value: 20.44, Filter: &BalanceFilter{ Uuid: utils.StringPointer("filterUuid"), ID: utils.StringPointer("filterId"), Type: utils.StringPointer("type"), }, }, }, }}}, ActionTriggers: ActionTriggers{ { UniqueID: "uniId", ID: "id", ThresholdType: "*min_event_counter", ThresholdValue: 20.55, }, { UniqueID: "uniId2", ID: "id2", ThresholdType: "*max_event_counter", ThresholdValue: 19.22, Recurrent: true, }, }, } if _, err := acc.FieldAsInterface([]string{"Balance"}); err == nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{"BalanceMap[second]"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if val, err := acc.FieldAsInterface([]string{"BalanceMap[first]"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(acc.BalanceMap["first"], val) { t.Errorf("expected %v ,received %v", utils.ToJSON(val), utils.ToJSON(acc.BalanceMap["first"])) } else if _, err := acc.FieldAsInterface([]string{"BalanceMap[first]", "UnitCounters[test]"}); err == nil { t.Error(err) } else if _, err := acc.FieldAsInterface([]string{"UnitCounters[test]"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if val, err := acc.FieldAsInterface([]string{"UnitCounters[first]"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(val, acc.UnitCounters["first"]) { t.Errorf("expected %v ,received %v", val, acc.UnitCounters["first"]) } if _, err := acc.FieldAsInterface([]string{"ActionTriggers[3]"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err := acc.FieldAsInterface([]string{"ActionTriggers[three]"}); err == nil { t.Error(err) } else if val, err := acc.FieldAsInterface([]string{"ActionTriggers[0]"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(val, acc.ActionTriggers[0]) { t.Errorf("expected %v ,received %v", utils.ToJSON(acc.ActionTriggers[0]), utils.ToJSON(val)) } else if _, err = acc.FieldAsInterface([]string{"ActionTriggers[0]", "test"}); err == nil { t.Error(err) } if _, err := acc.FieldAsInterface([]string{"ID"}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{"ID", "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } if _, err := acc.FieldAsInterface([]string{"BalanceMap"}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{"BalanceMap", "first[0]"}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{"BalanceMap", "first[3]"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{"BalanceMap", "first[first]"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UnitCounters}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UnitCounters, "first"}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UnitCounters, "first", "sec"}); err == nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UnitCounters, "first", "2"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err := acc.FieldAsInterface([]string{utils.UnitCounters, "first", "0"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UnitCounters, "sec"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.ActionTriggers}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.ActionTriggers, "val"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if val, err := acc.FieldAsInterface([]string{utils.ActionTriggers, "id"}); err != nil { t.Error(err) } else if !reflect.DeepEqual(val, acc.ActionTriggers[0]) { t.Errorf("expected %v ,received %v", utils.ToJSON(val), utils.ToJSON(acc.ActionTriggers[0])) } else if _, err = acc.FieldAsInterface([]string{utils.AllowNegative}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.AllowNegative, "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.Disabled}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.Disabled, "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UpdateTime}); err != nil { t.Error(err) } else if _, err = acc.FieldAsInterface([]string{utils.UpdateTime, "test"}); err == nil || err != utils.ErrNotFound { t.Error(err) } } func TestAccountFieldAsString(t *testing.T) { acc := &Account{ ID: "id", BalanceMap: map[string]Balances{ "first": { &Balance{ Uuid: "uuid1", ID: "id", Value: 20.44, }, &Balance{ Uuid: "uuid2", ID: "id2", Value: 12.2, }, }}, UnitCounters: UnitCounters{ "first": []*UnitCounter{ { CounterType: "balance", Counters: CounterFilters{ { Value: 20.44, Filter: &BalanceFilter{ Uuid: utils.StringPointer("filterUuid"), ID: utils.StringPointer("filterId"), Type: utils.StringPointer("type"), }, }, }, }}}, ActionTriggers: ActionTriggers{ { UniqueID: "uniId", ID: "id", ThresholdType: "*min_event_counter", ThresholdValue: 20.55, }, { UniqueID: "uniId2", ID: "id2", ThresholdType: "*max_event_counter", ThresholdValue: 19.22, Recurrent: true, }, }, } if _, err := acc.FieldAsString([]string{"val"}); err == nil { t.Error(err) } else if _, err := acc.FieldAsString([]string{"ID"}); err != nil { t.Error(err) } } func TestGetSharedGroups(t *testing.T) { acc := &Account{ BalanceMap: map[string]Balances{ "first": { {SharedGroups: utils.StringMap{ "firstval": true, "firstval2": true, }}}, "second": { { SharedGroups: utils.StringMap{ "secondval": true, "secondval2": false, }}}, }, } exp := []string{"firstval", "firstval2", "secondval", "secondval2"} val := acc.GetSharedGroups() sort.Slice(val, func(i, j int) bool { return val[i] < val[j] }) if !reflect.DeepEqual(val, exp) { t.Errorf("expected %v ,received %v", utils.ToJSON(val), utils.ToJSON(exp)) } } func TestAccountAsOldStructure(t *testing.T) { acc := &Account{ ID: "TEST_ID", BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{ Value: 100}}, utils.MetaVoice: { &Balance{ Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{ Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, UnitCounters: UnitCounters{ utils.MetaMonetary: []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, Value: 1.0}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 100, ThresholdType: utils.TriggerMinEventCounter, ActionsID: "TEST_ACTIONS"}}, AllowNegative: true, Disabled: true, } expAcc := &Account{ ID: "*out:TEST_ID", BalanceMap: map[string]Balances{ "*monetary*out": { &Balance{ Value: 100}}, "*voice*out": { &Balance{ Value: 10 * float64(time.Second), Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}}, &Balance{ Weight: 10, DestinationIDs: utils.StringMap{"RET": true}}}}, UnitCounters: UnitCounters{ "*monetary": []*UnitCounter{ {Counters: CounterFilters{ &CounterFilter{Filter: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, Value: 1.0}}}}}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 100, ThresholdType: utils.TriggerMinEventCounter, ActionsID: "TEST_ACTIONS"}}, AllowNegative: true, Disabled: true, } if val := acc.AsOldStructure(); reflect.DeepEqual(val, expAcc) { t.Errorf("expected %+v ,received %+v ", utils.ToJSON(expAcc), utils.ToJSON(val)) } } func TestAccountSummary(t *testing.T) { as := &AccountSummary{ Tenant: "cgrates.org", ID: "CGRATES_1", BalanceSummaries: BalanceSummaries{ &BalanceSummary{ ID: "summary_1", UUID: "summary_uuid", Type: "*voice", Initial: 2.0, Value: 12.2, Disabled: true, }, &BalanceSummary{ ID: "summary_2", UUID: "summary_uuid2", Type: "*voice", Initial: 4.0, Value: 20.2, Disabled: false, }, }, AllowNegative: false, Disabled: false, } asOld := &AccountSummary{ Tenant: "cgrates.org", ID: "CGRATES_1", BalanceSummaries: BalanceSummaries{ &BalanceSummary{ ID: "oldsummary_1", UUID: "oldsummary_uuid", Type: "*voice", Initial: 2.0, Value: 12.2, Disabled: true, }, &BalanceSummary{ ID: "old_summary_2", UUID: "old_summary_uuid2", Type: "*data", Initial: 4.0, Value: 20.2, Disabled: false, }, }, AllowNegative: false, Disabled: false, } expAs := &AccountSummary{ Tenant: "cgrates.org", ID: "CGRATES_1", BalanceSummaries: BalanceSummaries{ &BalanceSummary{ ID: "summary_1", UUID: "summary_uuid", Type: "*voice", Initial: 2.0, Value: 12.2, Disabled: true, }, &BalanceSummary{ ID: "summary_2", UUID: "summary_uuid2", Type: "*voice", Initial: 4.0, Value: 20.2, Disabled: false, }, &BalanceSummary{ ID: "oldsummary_1", UUID: "oldsummary_uuid", Type: "*voice", Initial: 0, Value: 0, Disabled: true, }, &BalanceSummary{ ID: "old_summary_2", UUID: "old_summary_uuid2", Type: "*data", Initial: 0, Value: 0, Disabled: false, }, }, AllowNegative: false, Disabled: false, } as.SetInitialValue(asOld) if !reflect.DeepEqual(as, expAs) { t.Errorf("expected %+v ,received %+v", utils.ToJSON(expAs), utils.ToJSON(as)) } } func TestAccountGetCreditForPrefix(t *testing.T) { tmp := Cache defer func() { Cache = tmp }() Cache.Clear(nil) acc := &Account{ ID: "cgrates.org:account1", AllowNegative: true, BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{ ID: "voice1", Weight: 20, DestinationIDs: utils.StringMap{utils.MetaAny: false}, precision: 0, SharedGroups: utils.NewStringMap("SG_TEST"), Value: 3600}, }, }, } Cache.Set(utils.CacheSharedGroups, "SG_TEST", &SharedGroup{ Id: "SG_TEST", MemberIds: utils.NewStringMap("cgrates.org:account1"), AccountParameters: map[string]*SharingParameters{ "*any": {Strategy: STRATEGY_MINE_RANDOM}, }, }, []string{}, true, utils.NonTransactional) 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.MetaMonetary, } if _, _, balances := acc.getCreditForPrefix(cd); len(balances) == 0 { t.Errorf("received %+v", utils.ToJSON(balances)) } } func TestAcountSetBalanceAction(t *testing.T) { cfg := config.NewDefaultCGRConfig() tmpDm := dm defer func() { dm = tmpDm }() cfg.DataDbCfg().Items = map[string]*config.ItemOpt{ utils.CacheSharedGroups: { Limit: 3, StaticTTL: true, }, } db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items) if dErr != nil { t.Error(dErr) } dm := NewDataManager(db, cfg.CacheCfg(), nil) SetDataStorage(dm) acc := &Account{ ID: "vdf:minu", BalanceMap: map[string]Balances{ utils.MetaVoice: { &Balance{ Uuid: "uuid2", ExpirationDate: time.Date(2022, 12, 2, 2, 0, 0, 0, time.UTC), Value: 200 * float64(time.Second), DestinationIDs: utils.NewStringMap("NAT"), Weight: 10, SharedGroups: utils.StringMap{ "SharedGroups_true": true, "SharedGroups_false": false, }, }, &Balance{ Uuid: "uuid1", ExpirationDate: time.Date(2021, 12, 2, 2, 0, 0, 0, time.UTC), Value: 100 * float64(time.Second), DestinationIDs: utils.NewStringMap("RET"), Weight: 20}, }}, } fltrs := NewFilterS(cfg, nil, nil) a := &Action{ Balance: &BalanceFilter{ ID: utils.StringPointer("id"), Type: utils.StringPointer("b_type"), Value: &utils.ValueFormula{ Method: "value_method", }, SharedGroups: utils.StringMapPointer(utils.NewStringMap("shrdGroup")), }, } if err := dm.dataDB.SetSharedGroupDrv(&SharedGroup{ Id: "shrdGroup", MemberIds: utils.StringMap{}}); err != nil { t.Error(err) } if err = acc.setBalanceAction(a, fltrs); err != nil { t.Error(err) } exp := utils.StringMap{ "vdf:minu": true, } if val, err := dm.dataDB.GetSharedGroupDrv("shrdGroup"); err != nil { t.Error(err) } else if !reflect.DeepEqual(val.MemberIds, exp) { t.Errorf("expected %v,received %v", utils.ToJSON(exp), utils.ToJSON(val.MemberIds)) } if err = acc.setBalanceAction(nil, fltrs); err == nil || err.Error() != "nil action" { t.Error(err) } } func TestAccGetAllBalancesForPrefixLogg(t *testing.T) { tmp := Cache utils.Logger.SetLogLevel(4) utils.Logger.SetSyslog(nil) buf := new(bytes.Buffer) log.SetOutput(buf) defer func() { utils.Logger.SetLogLevel(0) log.SetOutput(os.Stderr) Cache = tmp }() Cache.Clear(nil) acc := &Account{ ID: "cgrates.org:account1", AllowNegative: true, BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{ ID: "voice1", Weight: 20, DestinationIDs: utils.StringMap{utils.MetaAny: false}, precision: 0, SharedGroups: utils.NewStringMap("SG_TEST"), Value: 3600}, }, }, } Cache.Set(utils.CacheSharedGroups, "SG_TEST", nil, []string{}, true, utils.NonTransactional) acc.getAlldBalancesForPrefix("0723", "0", utils.MetaMonetary, time.Date(2013, 10, 4, 15, 46, 0, 0, time.UTC)) expLog := "Could not get shared group:" if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) { t.Errorf("Logger %v doesn't containe %v ", rcvLog, expLog) } } func TestGetUniqueSharedGroupMemebersErr(t *testing.T) { utils.Logger.SetLogLevel(4) utils.Logger.SetSyslog(nil) buf := new(bytes.Buffer) log.SetOutput(buf) tmp := Cache defer func() { utils.Logger.SetLogLevel(0) log.SetOutput(os.Stderr) Cache = tmp }() Cache.Clear(nil) acc := &Account{ ID: "cgrates.org:account1", AllowNegative: true, BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{ ID: "voice1", Weight: 20, DestinationIDs: utils.StringMap{utils.MetaAny: false}, precision: 0, SharedGroups: utils.NewStringMap("SG_TEST"), Value: 3600}, }, }, } Cache.Set(utils.CacheSharedGroups, "SG_TEST", nil, []string{}, true, utils.NonTransactional) 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.MetaMonetary, } expLog := `Could not get shared group: ` if _, err := acc.GetUniqueSharedGroupMembers(cd); err == nil || err != utils.ErrNotFound { t.Error(err) } else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) { t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog) } } func TestAccSetBalanceAction(t *testing.T) { tmp := Cache defer func() { Cache = tmp }() a := &Action{ ActionType: "*topup", Balance: &BalanceFilter{ ID: utils.StringPointer(utils.MetaDefault), Type: utils.StringPointer(utils.MetaMonetary), Value: &utils.ValueFormula{Static: 10}, SharedGroups: &utils.StringMap{ "string1": true, }}, } acc := &Account{ ID: "cgrates.org:account1", AllowNegative: true, BalanceMap: map[string]Balances{ utils.MetaMonetary: { &Balance{ ID: "voice1", Weight: 20, DestinationIDs: utils.StringMap{utils.MetaAny: false}, precision: 0, SharedGroups: utils.NewStringMap("SG_TEST"), Value: 3600}, }, }, } Cache.Set(utils.CacheSharedGroups, "string1", nil, []string{}, false, utils.NonTransactional) if err := acc.setBalanceAction(a, nil); err != nil { t.Error(err) } } func TestAccEnableAccountAction(t *testing.T) { cfg := config.NewDefaultCGRConfig() tmpDm := dm defer func() { dm = tmpDm }() db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items) if dErr != nil { t.Error(dErr) } dm := NewDataManager(db, cfg.CacheCfg(), nil) acc := &Account{ ID: "cgrates.org:1001", BalanceMap: map[string]Balances{ utils.MetaVoice: { &Balance{ ID: "BALANCE_ID", Value: 20 * float64(time.Second), Weight: 10}, &Balance{Value: 100 * float64(time.Second), Weight: 20}, }}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaMonetary)}, ThresholdValue: 100, ThresholdType: utils.TriggerMinEventCounter, ActionsID: "TEST_ACTIONS"}, }, UnitCounters: UnitCounters{ utils.MetaVoice: []*UnitCounter{ { CounterType: "*event", Counters: CounterFilters{ &CounterFilter{ Value: 0, Filter: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("GERMANY_O2")), }, }, }, }, }, }, } acc2 := &Account{ ID: "cgrates.org:1002", BalanceMap: map[string]Balances{ utils.MetaVoice: { &Balance{ ID: "BALANCE_ID", Value: 20 * float64(time.Second), Weight: 10}, }}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice)}, ThresholdValue: 100, ThresholdType: utils.TriggerMinEventCounter, ActionsID: "TEST_ACTIONS2"}, }, UnitCounters: UnitCounters{ utils.MetaVoice: []*UnitCounter{ { CounterType: "*event", Counters: CounterFilters{ &CounterFilter{ Value: 0, Filter: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("GERMANY_O2")), }, }, }, }, }, }, } acc3 := &Account{ ID: "cgrates.org:1003", BalanceMap: map[string]Balances{ utils.MetaVoice: { &Balance{ dirty: true, ID: "BALANCE_ID", Value: 150 * float64(time.Second), Weight: 10}, }}, ActionTriggers: ActionTriggers{ &ActionTrigger{ Balance: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice)}, ThresholdValue: 100, ThresholdType: utils.TriggerMaxBalance, ActionsID: "TEST_ACTIONS3"}, }, } testCases := []struct { name string account *Account actions Actions key string actiontype string }{ {name: "Enable account action", account: acc, actions: Actions{ &Action{ ActionType: utils.MetaEnableAccount, Filters: []string{"*gte:~*req.BalanceMap.*voice[0].Value:20"}, Balance: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("GERMANY_O2")), }, Weight: 9, }, }, key: "TEST_ACTIONS", actiontype: utils.MetaEnableAccount, }, { name: "Disable account action", account: acc2, actions: Actions{ &Action{ ActionType: utils.MetaDisableAccount, Filters: []string{"*string:~*req.BalanceMap.*voice[0].ID:BALANCE_ID"}, Balance: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("GERMANY_O2")), }, Weight: 9, }, }, key: "TEST_ACTIONS2", actiontype: utils.MetaDisableAccount, }, { name: "Set recurrent Action", account: acc3, actions: Actions{ &Action{ ActionType: utils.MetaSetRecurrent, Filters: []string{"*string:~*req.BalanceMap.*voice[0].ID:BALANCE_ID"}, Balance: &BalanceFilter{ ID: utils.StringPointer("BALANCE_ID"), Type: utils.StringPointer(utils.MetaVoice), DestinationIDs: utils.StringMapPointer(utils.NewStringMap("GERMANY_O2")), }, Weight: 9, }, }, key: "TEST_ACTIONS3", actiontype: utils.MetaSetRecurrent, }, } SetDataStorage(dm) for _, tc := range testCases { dm.SetActions(tc.key, tc.actions) t.Run(tc.name, func(t *testing.T) { tc.account.ExecuteActionTriggers(&Action{}, NewFilterS(cfg, nil, dm)) switch tc.actiontype { case utils.MetaEnableAccount: if acc, err := dm.GetAccount(tc.account.ID); err != nil { t.Error(err) } else if acc.Disabled != false { t.Errorf("account should be enabled") } case utils.MetaDisableAccount: if acc, err := dm.GetAccount(tc.account.ID); err != nil { t.Error(err) } else if acc.Disabled != true { t.Errorf("account should be disabled") } } }) } } func TestEngineToStringJSON(t *testing.T) { acc := &Account{ ID: "acc_123", AllowNegative: false, Disabled: true, UpdateTime: time.Now(), executingTriggers: false, } want, err := json.Marshal(acc) if err != nil { t.Error(err) } got := acc.String() if got != string(want) { t.Errorf("acc.String()=%s, want%s", got, want) } } func TestEngineGetID(t *testing.T) { tests := []struct { name string accID string want string }{ { name: "Valid ID format", accID: "prefix" + utils.ConcatenatedKeySep + "suffix", want: "suffix", }, { name: "Invalid ID format (missing separator)", accID: "invalidID", want: "", }, { name: "Invalid ID format (too many parts)", accID: "prefix" + utils.ConcatenatedKeySep + "suffix" + utils.ConcatenatedKeySep + "extra", want: "", }, { name: "Empty ID", accID: "", want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { acc := &Account{ID: tt.accID} got := acc.GetID() if got != tt.want { t.Errorf("Expected ID: %s, Got: %s", tt.want, got) } }) } } func TestEngineNewAccountSummaryFromJSON(t *testing.T) { tests := []struct { name string jsonStr string want *AccountSummary }{ { name: "Valid JSON", jsonStr: `{"tenant": "cgrates.org", "id": "1234", "balanceSummaries": [], "allowNegative": false, "disabled": true}`, want: &AccountSummary{ Tenant: "cgrates.org", ID: "1234", BalanceSummaries: BalanceSummaries{}, AllowNegative: false, Disabled: true, }, }, { name: "Empty JSON", jsonStr: "", want: nil, }, { name: "Null JSON", jsonStr: "null", want: nil, }, { name: "Invalid JSON", jsonStr: "{invalid: json}", want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, _ := NewAccountSummaryFromJSON(tt.jsonStr) if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewAccountSummaryFromJSON(%q) got = %v, want %v", tt.jsonStr, got, tt.want) } }) } } func TestAccountSummaryString(t *testing.T) { account := &AccountSummary{ Tenant: "cgrates.org", ID: "2012", AllowNegative: true, Disabled: false, BalanceSummaries: BalanceSummaries{ &BalanceSummary{ Factors: ValueFactors{ "factor1": 1.0, "factor2": 2.0, }, }, }, } result := account.String() var parsedResult map[string]interface{} err := json.Unmarshal([]byte(result), &parsedResult) if err != nil { t.Errorf("Error unmarshalling result: %v", err) } } func TestAccountProcessAccountSummaryField(t *testing.T) { type args struct { fldPath []string accSummary any event map[string]any } tests := []struct { name string args args want any wantErr bool }{ { name: "Direct access for *AccountSummary (Tenant)", args: args{ fldPath: []string{"Tenant"}, accSummary: &AccountSummary{Tenant: "test_tenant", ID: "id1"}, event: make(map[string]any), }, want: "test_tenant", wantErr: false, }, { name: "Unmarshal with ValueFactors field (expecting error on missing factor)", args: args{ fldPath: []string{"BalanceSummaries", "0", "Factors", "factor1"}, accSummary: `{"BalanceSummaries": [{"UUID": "summary1", "Factors": {"factor1": 0.2}}]}`, event: make(map[string]any), }, want: nil, wantErr: true, }, { name: "Error on missing ValueFactors field", args: args{ fldPath: []string{"BalanceSummaries", "0", "Factors", "missing_factor"}, accSummary: `{"BalanceSummaries": [{"UUID": "summary1"}]}`, event: make(map[string]any), }, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := processAccountSummaryField(tt.args.fldPath, tt.args.accSummary, tt.args.event) if (err != nil) != tt.wantErr { t.Errorf("processAccountSummaryField error = %v, wantErr = %v", err, tt.wantErr) return } if diff := cmp.Diff(tt.want, got); diff != "" && !tt.wantErr { t.Errorf("processAccountSummaryField mismatch (-want +got):\n%s", diff) } }) } } func TestProcessAccountSummaryFieldNonStringAccSummary(t *testing.T) { type args struct { fldPath []string accSummary any event map[string]any } tests := []struct { name string args args wantErr bool }{ { name: "Error on non-string accSummary (marshal)", args: args{ fldPath: []string{"Tenant"}, accSummary: map[string]string{"Name": "Test Account"}, event: make(map[string]any), }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := processAccountSummaryField(tt.args.fldPath, tt.args.accSummary, tt.args.event) if (err == nil) != tt.wantErr { t.Errorf("processAccountSummaryField error = %v, wantErr = %v", err, tt.wantErr) return } }) } } func TestAccountFieldAsInterfaceNilAccount(t *testing.T) { var acc *Account fldPath := []string{"ID"} _, err := acc.FieldAsInterface(fldPath) if err != utils.ErrNotFound { t.Errorf("Expected error %v, got %v", utils.ErrNotFound, err) } }