Files
cgrates/engine/balances_test.go
2024-08-02 09:36:54 +02:00

1079 lines
31 KiB
Go

/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package engine
import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"
"strings"
"testing"
"time"
"github.com/cgrates/cgrates/utils"
"github.com/google/go-cmp/cmp"
)
func TestBalanceSortPrecision(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 2}
mb2 := &Balance{Weight: 2, precision: 1}
var bs Balances
bs = append(bs, mb2, mb1)
bs.Sort()
if bs[0] != mb1 || bs[1] != mb2 {
t.Error("Buckets not sorted by weight!")
}
}
func TestBalanceSortPrecisionWeightEqual(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 2}
mb2 := &Balance{Weight: 1, precision: 1}
var bs Balances
bs = append(bs, mb2, mb1)
bs.Sort()
if bs[0] != mb1 || bs[1] != mb2 {
t.Error("Buckets not sorted by precision!")
}
}
func TestBalanceSortPrecisionWeightGreater(t *testing.T) {
mb1 := &Balance{Weight: 2, precision: 2}
mb2 := &Balance{Weight: 1, precision: 1}
var bs Balances
bs = append(bs, mb2, mb1)
bs.Sort()
if bs[0] != mb1 || bs[1] != mb2 {
t.Error("Buckets not sorted by precision!")
}
}
func TestBalanceSortWeight(t *testing.T) {
mb1 := &Balance{Weight: 2, precision: 1}
mb2 := &Balance{Weight: 1, precision: 1}
var bs Balances
bs = append(bs, mb2, mb1)
bs.Sort()
if bs[0] != mb1 || bs[1] != mb2 {
t.Error("Buckets not sorted by precision!")
}
}
func TestBalanceSortWeight2(t *testing.T) {
bs := Balances{
&Balance{ID: "B1", Weight: 2, precision: 1},
&Balance{ID: "B2", Weight: 1, precision: 1},
}
bs.Sort()
if bs[0].ID != "B1" && bs[1].ID != "B2" {
t.Error("Buckets not sorted by precision!")
}
}
func TestBalanceSortWeight3(t *testing.T) {
bs := Balances{
&Balance{ID: "B1", Weight: 2, Value: 10.0},
&Balance{ID: "B2", Weight: 4, Value: 10.0},
}
bs.Sort()
if bs[0].ID != "B2" && bs[1].ID != "B1" {
t.Error(utils.ToJSON(bs))
}
}
func TestBalanceSortWeightLess(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 1}
mb2 := &Balance{Weight: 2, precision: 1}
var bs Balances
bs = append(bs, mb2, mb1)
bs.Sort()
if bs[0] != mb2 || bs[1] != mb1 {
t.Error("Buckets not sorted by precision!")
}
}
func TestBalanceEqual(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb3 := &Balance{Weight: 1, precision: 1, RatingSubject: "2", DestinationIDs: utils.StringMap{}}
if !mb1.Equal(mb2) || mb2.Equal(mb3) {
t.Error("Equal failure!", mb1 == mb2, mb3)
}
}
func TestBalanceMatchFilter(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &BalanceFilter{Weight: utils.Float64Pointer(1), RatingSubject: nil, DestinationIDs: nil}
if !mb1.MatchFilter(mb2, "", false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
mb1.Uuid, mb2.Uuid = "id", utils.StringPointer("id")
if !mb1.MatchFilter(mb2, "", false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
}
func TestBalanceMatchFilterEmpty(t *testing.T) {
mb1 := &Balance{Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &BalanceFilter{}
if !mb1.MatchFilter(mb2, "", false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
}
func TestBalanceMatchFilterId(t *testing.T) {
mb1 := &Balance{ID: "T1", Weight: 2, precision: 2, RatingSubject: "2", DestinationIDs: utils.NewStringMap("NAT")}
mb2 := &BalanceFilter{ID: utils.StringPointer("T1"), Weight: utils.Float64Pointer(1), RatingSubject: utils.StringPointer("1"), DestinationIDs: nil}
if !mb1.MatchFilter(mb2, "", false, false) {
t.Errorf("Match filter failure: %+v == %+v", mb1, mb2)
}
}
func TestBalanceMatchFilterDiffId(t *testing.T) {
mb1 := &Balance{ID: "T1", Weight: 1, precision: 1, RatingSubject: "1", DestinationIDs: utils.StringMap{}}
mb2 := &BalanceFilter{ID: utils.StringPointer("T2"), Weight: utils.Float64Pointer(1), RatingSubject: utils.StringPointer("1"), DestinationIDs: nil}
if mb1.MatchFilter(mb2, "", false, false) {
t.Errorf("Match filter failure: %+v != %+v", mb1, mb2)
}
}
func TestBalanceClone(t *testing.T) {
var mb1 *Balance
if mb2 := mb1.Clone(); mb2 != nil {
t.Errorf("Balance should be %v", mb2)
}
mb1 = &Balance{Value: 1, Weight: 2, RatingSubject: "test", DestinationIDs: utils.NewStringMap("5")}
mb2 := mb1.Clone()
if mb1 == mb2 || !mb1.Equal(mb2) {
t.Errorf("Cloning failure: \n%+v\n%+v", mb1, mb2)
}
}
func TestBalanceMatchActionTriggerId(t *testing.T) {
at := &ActionTrigger{Balance: &BalanceFilter{ID: utils.StringPointer("test")}}
b := &Balance{ID: "test"}
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.ID = "test1"
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.ID = ""
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.ID = "test"
at.Balance.ID = nil
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
}
func TestBalanceMatchActionTriggerDestination(t *testing.T) {
at := &ActionTrigger{Balance: &BalanceFilter{DestinationIDs: utils.StringMapPointer(utils.NewStringMap("test"))}}
b := &Balance{DestinationIDs: utils.NewStringMap("test")}
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.DestinationIDs = utils.NewStringMap("test1")
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.DestinationIDs = utils.NewStringMap("")
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.DestinationIDs = utils.NewStringMap("test")
at.Balance.DestinationIDs = nil
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
}
func TestBalanceMatchActionTriggerWeight(t *testing.T) {
at := &ActionTrigger{Balance: &BalanceFilter{Weight: utils.Float64Pointer(1)}}
b := &Balance{Weight: 1}
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.Weight = 2
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.Weight = 0
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.Weight = 1
at.Balance.Weight = nil
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
}
func TestBalanceMatchActionTriggerRatingSubject(t *testing.T) {
at := &ActionTrigger{Balance: &BalanceFilter{RatingSubject: utils.StringPointer("test")}}
b := &Balance{RatingSubject: "test"}
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.RatingSubject = "test1"
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.RatingSubject = ""
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.RatingSubject = "test"
at.Balance.RatingSubject = nil
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
}
func TestBalanceMatchActionTriggerSharedGroup(t *testing.T) {
at := &ActionTrigger{Balance: &BalanceFilter{SharedGroups: utils.StringMapPointer(utils.NewStringMap("test"))}}
b := &Balance{SharedGroups: utils.NewStringMap("test")}
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.SharedGroups = utils.NewStringMap("test1")
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.SharedGroups = utils.NewStringMap("")
if b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
b.SharedGroups = utils.NewStringMap("test")
at.Balance.SharedGroups = nil
if !b.MatchActionTrigger(at) {
t.Errorf("Error matching action trigger: %+v %+v", b, at)
}
}
func TestBalanceIsDefault(t *testing.T) {
b := &Balance{Weight: 0}
if b.IsDefault() {
t.Errorf("Balance should not be default: %+v", b)
}
b = &Balance{ID: utils.MetaDefault}
if !b.IsDefault() {
t.Errorf("Balance should be default: %+v", b)
}
}
func TestBalanceIsExpiredAt(t *testing.T) {
//expiration date is 0
balance := &Balance{}
var date2 time.Time
if rcv := balance.IsExpiredAt(date2); rcv {
t.Errorf("Expecting: false , received: %+v", rcv)
}
//expiration date before time t
balance.ExpirationDate = time.Date(2020, time.April, 18, 23, 0, 3, 0, time.UTC)
date2 = time.Date(2020, time.April, 18, 23, 0, 4, 0, time.UTC)
if rcv := balance.IsExpiredAt(date2); !rcv {
t.Errorf("Expecting: true , received: %+v", rcv)
}
//expiration date after time t
date2 = time.Date(2020, time.April, 18, 23, 0, 2, 0, time.UTC)
if rcv := balance.IsExpiredAt(date2); rcv {
t.Errorf("Expecting: false , received: %+v", rcv)
}
//time t = 0
var date3 time.Time
if rcv := balance.IsExpiredAt(date3); rcv {
t.Errorf("Expecting: false , received: %+v", rcv)
}
}
func TestBalanceAsInterface(t *testing.T) {
b := &Balance{
Uuid: "uuid",
ID: "id",
Value: 2.21,
ExpirationDate: time.Date(2022, 11, 22, 9, 0, 0, 0, time.UTC),
Weight: 2.88,
DestinationIDs: utils.StringMap{
"destId1": true,
"destId2": true,
},
RatingSubject: "rating",
Categories: utils.StringMap{
"ctg1": true,
"ctg2": false,
},
SharedGroups: utils.StringMap{
"shgp1": false,
"shgp2": true,
},
Timings: []*RITiming{
{
ID: "id",
Years: utils.Years{2, 3},
},
},
TimingIDs: utils.StringMap{
"timingid1": true,
"timingid2": false,
},
Factors: ValueFactors{
"factor1": 2.21,
"factor2": 1.34,
},
}
if _, err := b.FieldAsInterface([]string{}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"value"}); err == nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"DestinationIDs[destId1]", "secondVal"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Categories[ctg1]", "secondVal"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"SharedGroups[shgp1]", "secondVal"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"TimingIDs[timingid1]", "secondVal"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Timings[zero]"}); err == nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Timings[2]"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Timings[2]", "val"}); err == nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Factors[factor1]", "secondVal"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
if _, err = b.FieldAsInterface([]string{"DestinationIDs[destId1]"}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Categories[ctg1]"}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"SharedGroups[shgp1]"}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"TimingIDs[timingid1]"}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Timings[0]"}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{"Factors[factor1]"}); err != nil {
t.Error(err)
}
if _, err = b.FieldAsInterface([]string{utils.Uuid}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.ExpirationDate}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Weight}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.DestinationIDs}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.DestinationIDs}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.RatingSubject}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Categories}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.SharedGroups}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Timings}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Disabled}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Factors}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Blocker}); err != nil {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.TimingIDs}); err != nil {
t.Error(err)
}
if _, err = b.FieldAsInterface([]string{utils.TimingIDs, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Uuid, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.ID, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Value, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.ExpirationDate, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Weight, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.DestinationIDs, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.RatingSubject, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.Categories, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = b.FieldAsInterface([]string{utils.SharedGroups, "val"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
}
}
func TestValueFactorsFieldAsInterface(t *testing.T) {
v := &ValueFactors{
"FACT_VAL": 20.22,
}
if _, err := v.FieldAsInterface([]string{}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = v.FieldAsInterface([]string{"TEST"}); err == nil || err != utils.ErrNotFound {
t.Error(err)
} else if _, err = v.FieldAsInterface([]string{"FACT_VAL"}); err != nil {
t.Error(err)
}
}
func TestValueFactorFieldAsString(t *testing.T) {
v := &ValueFactors{
"FACT_VAL": 20.22,
}
if _, err = v.FieldAsString([]string{"TEST"}); err == nil {
t.Error(err)
} else if _, err = v.FieldAsString([]string{"FACT_VAL"}); err != nil {
t.Error(err)
}
}
func TestBalancesHasBalance(t *testing.T) {
bc := Balances{
{
Uuid: "uuid",
ID: "id",
Value: 12.22,
ExpirationDate: time.Date(2022, 11, 1, 20, 0, 0, 0, time.UTC),
Blocker: true,
Disabled: true,
precision: 2,
},
{
Uuid: "uuid2",
ID: "id2",
Value: 133.22,
ExpirationDate: time.Date(2023, 3, 21, 5, 0, 0, 0, time.UTC),
Blocker: true,
Disabled: true,
precision: 2,
},
}
balance := &Balance{
Uuid: "uuid",
ID: "id",
Value: 12.22,
ExpirationDate: time.Date(2022, 11, 1, 20, 0, 0, 0, time.UTC),
Blocker: true,
Disabled: true,
precision: 2,
}
if !bc.HasBalance(balance) {
t.Error("should be true")
}
}
func TestGetMinutesForCredi(t *testing.T) {
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)
}()
b := &Balance{
Value: 20 * float64(time.Second),
DestinationIDs: utils.NewStringMap("NAT"),
Weight: 10, RatingSubject: "rif",
}
cd := &CallDescriptor{
Category: "postpaid",
ToR: utils.MetaVoice,
Tenant: "foehn",
Subject: "foehn",
Account: "foehn",
Destination: "0034678096720",
TimeStart: time.Date(2015, 4, 24, 7, 59, 4, 0, time.UTC),
TimeEnd: time.Date(2015, 4, 24, 8, 2, 0, 0, time.UTC),
LoopIndex: 0,
DurationIndex: 176 * time.Second,
}
if dur, _ := b.GetMinutesForCredit(cd, 12); dur != 0 {
t.Error(err)
}
expLog := `Error getting new cost for balance subject:`
if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
t.Errorf("expected %v,received %v", utils.ToJSON(expLog), utils.ToJSON(rcvLog))
}
}
func TestBalancesStringValFactors(t *testing.T) {
factors := ValueFactors{
"factor1": 10.5,
"factor2": 20.3,
}
jsonString := factors.String()
if jsonString == "" {
t.Errorf("String(): expected non-empty JSON string, got empty")
}
}
func TestBalancesHasDestination(t *testing.T) {
balance := &Balance{
DestinationIDs: utils.StringMap{
"destination1": true,
"destination2": false,
},
}
hasDest := balance.HasDestination()
expected := true
if hasDest != expected {
t.Errorf("HasDestination(): expected %t, got %t", expected, hasDest)
}
balanceEmpty := &Balance{
DestinationIDs: utils.StringMap{},
}
hasDestEmpty := balanceEmpty.HasDestination()
expectedEmpty := false
if hasDestEmpty != expectedEmpty {
t.Errorf("HasDestination(): expected %t, got %t for empty DestinationIDs map", expectedEmpty, hasDestEmpty)
}
}
func TestBalancesMatchDestination(t *testing.T) {
balanceWithDestinations := &Balance{
DestinationIDs: utils.StringMap{
"destination1": true,
"destination2": false,
},
}
if !balanceWithDestinations.MatchDestination("destination1") {
t.Errorf("MatchDestination(destination1): expected true, got false")
}
if balanceWithDestinations.MatchDestination("destination2") {
t.Errorf("MatchDestination(destination2): expected false, got true")
}
if balanceWithDestinations.MatchDestination("nonexistent") {
t.Errorf("MatchDestination(nonexistent): expected false, got true")
}
balanceNoDestinations := &Balance{
DestinationIDs: utils.StringMap{},
}
if !balanceNoDestinations.MatchDestination("anydestination") {
t.Errorf("MatchDestination(anydestination): expected true, got false for empty DestinationIDs map")
}
}
func TestBalancesSetInitialValue(t *testing.T) {
t.Run("OldNotNull", func(t *testing.T) {
as := &AccountSummary{
BalanceSummaries: BalanceSummaries{
{UUID: "1", Value: 100, Initial: 0},
{UUID: "2", Value: 200, Initial: 0},
},
}
old := &AccountSummary{
BalanceSummaries: BalanceSummaries{
{UUID: "1", Value: 50, Initial: 0},
{UUID: "3", Value: 300, Initial: 0},
},
}
as.SetInitialValue(old)
expected := &AccountSummary{
BalanceSummaries: BalanceSummaries{
{UUID: "1", Value: 100, Initial: 50},
{UUID: "2", Value: 200, Initial: 0},
{UUID: "3", Value: 0, Initial: 0},
},
}
if diff := cmp.Diff(as, expected); diff != "" {
t.Errorf("Unexpected result (-got +want):\n%s", diff)
}
})
t.Run("OldNull", func(t *testing.T) {
as := &AccountSummary{
BalanceSummaries: BalanceSummaries{
{UUID: "1", Value: 100, Initial: 0},
{UUID: "2", Value: 200, Initial: 0},
},
}
as.SetInitialValue(nil)
expected := &AccountSummary{
BalanceSummaries: BalanceSummaries{
{UUID: "1", Value: 100, Initial: 0},
{UUID: "2", Value: 200, Initial: 0},
},
}
if diff := cmp.Diff(as, expected); diff != "" {
t.Errorf("Unexpected result (-got +want):\n%s", diff)
}
})
}
func TestBalancesValueFactorsGetValue(t *testing.T) {
testCases := []struct {
factors ValueFactors
category string
expected float64
}{
{
factors: nil,
category: "category1",
expected: 1.0,
},
{
factors: ValueFactors{"category2": 2.0, "category3": 3.0},
category: "category1",
expected: 1.0,
},
{
factors: ValueFactors{"category1": 1.5, "category2": 2.0, "category3": 3.0},
category: "category1",
expected: 1.5,
},
{
factors: ValueFactors{"category1": 0.0, "category2": 2.0, "category3": 3.0},
category: "category1",
expected: 0.0,
},
}
for _, tc := range testCases {
result := tc.factors.GetValue(tc.category)
if result != tc.expected {
t.Errorf("Test failed for category '%s': expected %.2f, got %.2f", tc.category, tc.expected, result)
}
}
}
func TestBalancesStringJson(t *testing.T) {
balances := Balances{
&Balance{
Uuid: "uuid123",
ID: "balance123",
Value: 100.0,
ExpirationDate: time.Date(2024, time.December, 31, 23, 59, 59, 0, time.UTC),
Weight: 1.5,
DestinationIDs: utils.StringMap{},
RatingSubject: "ratingSub",
Categories: utils.StringMap{},
SharedGroups: utils.StringMap{},
Timings: []*RITiming{},
TimingIDs: utils.StringMap{},
Disabled: false,
Blocker: true,
precision: 2,
account: nil,
dirty: false,
},
}
result := balances.String()
if result == "" {
t.Error("Expected non-empty JSON string, but got empty string")
}
}
func TestBalanceFieldAsString(t *testing.T) {
balance := &Balance{
Uuid: "uuid123",
}
val, err := balance.FieldAsString([]string{"Uuid"})
if err != nil {
t.Errorf("Unexpected error for field 'Uuid': %v", err)
}
expected := balance.Uuid
if val != expected {
t.Errorf("Expected value '%s' for field 'Uuid', but got '%s'", expected, val)
}
_, err = balance.FieldAsString([]string{"InvalidField"})
if err == nil {
t.Error("Expected error for invalid field path, but got nil")
}
}
func TestBalancesString(t *testing.T) {
balance := &Balance{
Uuid: "123e4567-e89b-12d3-a456-426614174000",
}
jsonStr := balance.String()
var data map[string]interface{}
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
t.Errorf("Error unmarshalling JSON string: %v", err)
}
expectedUuid := balance.Uuid
if uuid, ok := data["Uuid"].(string); !ok || uuid != expectedUuid {
t.Errorf("Expected Uuid '%s' in JSON, but got '%v'", expectedUuid, data["Uuid"])
}
}
func TestBalancesHasBalanceReturn(t *testing.T) {
balances := Balances{
{ID: "1"},
{ID: "2"},
{ID: "3"},
}
existingBalance := &Balance{ID: "2"}
if !balances.HasBalance(existingBalance) {
t.Errorf("Expected balance with ID '%s' to exist, but it does not", existingBalance.ID)
}
nonExistingBalance := &Balance{ID: "4"}
if balances.HasBalance(nonExistingBalance) {
t.Errorf("Expected balance with ID '%s' to not exist, but it does", nonExistingBalance.ID)
}
}
func TestBalancesEqual(t *testing.T) {
balances1 := Balances{
{ID: "1"},
{ID: "2"},
{ID: "3"},
}
balances2 := Balances{
{ID: "1"},
{ID: "2"},
{ID: "3"},
}
if !balances1.Equal(balances1) {
t.Errorf("Expected balances1 to equal itself, but it does not")
}
if !balances1.Equal(balances2) {
t.Errorf("Expected balances1 to equal balances2, but they are not equal")
}
balances3 := Balances{
{ID: "1"},
{ID: "2"},
}
if balances1.Equal(balances3) {
t.Errorf("Expected balances1 to not equal balances3, but they are equal")
}
balances4 := Balances{
{ID: "1"},
{ID: "2"},
{ID: "4"},
}
if balances1.Equal(balances4) {
t.Errorf("Expected balances1 to not equal balances4, but they are equal")
}
}
func TestBalancesFieldAsString(t *testing.T) {
bc := Balances{
&Balance{
Uuid: "uuid123",
ID: "balance123",
Value: 100.0,
ExpirationDate: time.Date(2024, time.December, 31, 23, 59, 59, 0, time.UTC),
Weight: 1.5,
DestinationIDs: utils.StringMap{},
RatingSubject: "ratingSub",
Categories: utils.StringMap{},
SharedGroups: utils.StringMap{},
Timings: []*RITiming{},
TimingIDs: utils.StringMap{},
Disabled: false,
Blocker: true,
precision: 2,
account: nil,
dirty: false,
},
}
t.Run("empty field path", func(t *testing.T) {
val, err := bc.FieldAsString([]string{})
if err == nil {
t.Error("expected error, got nil")
}
if val != "" {
t.Errorf("expected empty string, got '%v'", val)
}
})
t.Run("invalid field path", func(t *testing.T) {
val, err := bc.FieldAsString([]string{"invalid"})
if err == nil {
t.Error("expected error, got nil")
}
if val != "" {
t.Errorf("expected empty string, got '%v'", val)
}
})
t.Run("valid field path for non-existing ID", func(t *testing.T) {
val, err := bc.FieldAsString([]string{"2", "ID"})
if err == nil {
t.Error("expected error, got nil")
}
if val != "" {
t.Errorf("expected empty string, got '%v'", val)
}
})
}
func TestBalancesIsActiveAt(t *testing.T) {
testTime := time.Now()
t.Run("balance is disabled", func(t *testing.T) {
balance := &Balance{
Disabled: true,
}
if balance.IsActiveAt(testTime) {
t.Errorf("expected false, got true")
}
})
}
func TestBalancesFieldAsInterfaceIndexPath(t *testing.T) {
bc := Balances{
&Balance{
Uuid: "uuid123",
ID: "balance123",
Value: 100.0,
ExpirationDate: time.Date(2024, time.December, 31, 23, 59, 59, 0, time.UTC),
Weight: 1.5,
DestinationIDs: utils.StringMap{},
RatingSubject: "ratingSub",
Categories: utils.StringMap{},
SharedGroups: utils.StringMap{},
Timings: []*RITiming{},
TimingIDs: utils.StringMap{},
Disabled: false,
Blocker: true,
precision: 2,
account: nil,
dirty: false,
},
}
t.Run("test index path in FieldAsInterface", func(t *testing.T) {
idx := "0"
val, err := bc.FieldAsInterface([]string{idx})
if err != nil {
t.Errorf("expected no error, got %v", err)
}
expected := bc[0]
if val != expected {
t.Errorf("expected '%v', got '%v'", expected, val)
}
})
}
func TestBalanceSummaryFieldAsInterfaceUnsupportedField(t *testing.T) {
balanceSummary := &BalanceSummary{}
fldPath := []string{"unsupportedField"}
expectedError := fmt.Sprintf("unsupported field prefix: <%s>", fldPath[0])
_, err := balanceSummary.FieldAsInterface(fldPath)
if err == nil {
t.Fatalf("Expected an error but got nil")
}
if err.Error() != expectedError {
t.Errorf("Expected error: %s, but got: %s", expectedError, err.Error())
}
}
func TestBalancesSummaryFieldAsInterface(t *testing.T) {
var balanceSummary *BalanceSummary
fldPath := []string{"Filed"}
_, err := balanceSummary.FieldAsInterface(fldPath)
if err != utils.ErrNotFound {
t.Fatalf("Expected utils.ErrNotFound but got: %v", err)
}
balanceSummary = &BalanceSummary{}
fldPath = []string{}
_, err = balanceSummary.FieldAsInterface(fldPath)
if err != utils.ErrNotFound {
t.Fatalf("Expected utils.ErrNotFound but got: %v", err)
}
fldPath = []string{"unsupportedField"}
expectedError := fmt.Sprintf("unsupported field prefix: <%s>", fldPath[0])
_, err = balanceSummary.FieldAsInterface(fldPath)
if err == nil {
t.Fatalf("Expected an error but got nil")
}
if err.Error() != expectedError {
t.Errorf("Expected error: %s, but got: %s", expectedError, err.Error())
}
}
func TestBalancesHardMatchFilterNilFilter(t *testing.T) {
balance := &Balance{}
var balanceFilter *BalanceFilter = nil
result := balance.HardMatchFilter(balanceFilter, false)
if result != true {
t.Errorf("Expected true when balanceFilter is nil, but got %v", result)
}
}
func TestBalancesHardMatchFilter(t *testing.T) {
t.Run("NilFilter", func(t *testing.T) {
balance := &Balance{}
var balanceFilter *BalanceFilter = nil
result := balance.HardMatchFilter(balanceFilter, false)
if result != true {
t.Errorf("Expected true when balanceFilter is nil, but got %v", result)
}
})
t.Run("UuidMatch", func(t *testing.T) {
expectedUuid := "1"
balance := &Balance{Uuid: expectedUuid}
balanceFilter := &BalanceFilter{Uuid: &expectedUuid}
result := balance.HardMatchFilter(balanceFilter, false)
if result != true {
t.Errorf("Expected true when Uuid matches, but got %v", result)
}
})
t.Run("UuidNoMatch", func(t *testing.T) {
balance := &Balance{Uuid: "1"}
nonMatchingUuid := "2"
balanceFilter := &BalanceFilter{Uuid: &nonMatchingUuid}
result := balance.HardMatchFilter(balanceFilter, false)
if result != false {
t.Errorf("Expected false when Uuid doesn't match, but got %v", result)
}
})
}
func TestFieldAsStringCases(t *testing.T) {
tests := []struct {
name string
balance Balance
fldPath []string
expected string
wantErr bool
}{
{
name: "ID field",
balance: Balance{ID: "test-id", Value: 10.5},
fldPath: []string{"ID"},
expected: "test-id",
wantErr: false,
},
{
name: "Value field",
balance: Balance{ID: "test-id", Value: 10.5},
fldPath: []string{"Value"},
expected: "10.5",
wantErr: false,
},
{
name: "Non-existent field",
balance: Balance{ID: "test-id", Value: 10.5},
fldPath: []string{"NonExistent"},
expected: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.balance.FieldAsString(tt.fldPath)
if (err != nil) != tt.wantErr {
t.Errorf("FieldAsString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.expected {
t.Errorf("FieldAsString() = %v, want %v", got, tt.expected)
}
})
}
}
func TestString(t *testing.T) {
tests := []struct {
name string
balance Balance
expected string
}{
{
name: "Default case",
balance: Balance{
ID: "test-id",
Value: 10.5,
},
expected: `{"Uuid":"","ID":"test-id","Value":10.5,"ExpirationDate":"0001-01-01T00:00:00Z","Weight":0,"DestinationIDs":null,"RatingSubject":"","Categories":null,"SharedGroups":null,"Timings":null,"TimingIDs":null,"Disabled":false,"Factors":null,"Blocker":false}`,
},
{
name: "Zero value case",
balance: Balance{},
expected: `{"Uuid":"","ID":"","Value":0,"ExpirationDate":"0001-01-01T00:00:00Z","Weight":0,"DestinationIDs":null,"RatingSubject":"","Categories":null,"SharedGroups":null,"Timings":null,"TimingIDs":null,"Disabled":false,"Factors":null,"Blocker":false}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.balance.String()
if got != tt.expected {
t.Errorf("String() = %v, want %v", got, tt.expected)
}
})
}
}
func TestFieldAsStringCase(t *testing.T) {
tests := []struct {
name string
balances Balances
fldPath []string
expected string
wantErr bool
}{
{
name: "Invalid field path",
balances: Balances{{ID: "test-id", Value: 10.5}},
fldPath: []string{"NonExistentField"},
expected: "",
wantErr: true,
},
{
name: "Empty field path",
balances: Balances{{ID: "test-id", Value: 10.5}},
fldPath: []string{},
expected: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.balances.FieldAsString(tt.fldPath)
if (err != nil) != tt.wantErr {
t.Errorf("FieldAsString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.expected {
t.Errorf("FieldAsString() = %v, want %v", got, tt.expected)
}
})
}
}