added tests on thresholds processing

This commit is contained in:
gezimbll
2025-06-11 15:45:36 +02:00
committed by Dan Christian Bogos
parent f3ca5b0016
commit fbb625962c
6 changed files with 675 additions and 0 deletions

View File

@@ -768,3 +768,122 @@ func TestCheckDefaultTiming(t *testing.T) {
})
}
}
func TestActionTimingGetNextStartTime2(t *testing.T) {
tests := []struct {
name string
actionTiming *ActionTiming
startTime time.Time
expectedTime time.Time
expectedError bool
}{
{
name: "MonthlyEstimatedTiming",
actionTiming: &ActionTiming{
Timing: &RateInterval{
Timing: &RITiming{
ID: utils.MetaMonthlyEstimated,
StartTime: "00:00:00",
Years: []int{2024},
Months: []time.Month{1, 2, 3},
MonthDays: []int{31},
},
},
},
startTime: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC),
expectedTime: time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC),
},
{
name: "WeekDaysCron",
actionTiming: &ActionTiming{
Timing: &RateInterval{
Timing: &RITiming{
StartTime: "09:00:00",
Years: []int{2024},
Months: []time.Month{1, 2, 3},
MonthDays: []int{1, 15},
WeekDays: []time.Weekday{time.Monday, time.Wednesday, time.Friday},
EndTime: "17:00:00",
},
},
},
startTime: time.Date(2024, 1, 1, 10, 0, 0, 0, time.UTC),
expectedTime: time.Date(2024, 1, 3, 9, 0, 0, 0, time.UTC),
},
{
name: "YearTransition",
actionTiming: &ActionTiming{
Timing: &RateInterval{
Timing: &RITiming{
StartTime: "00:00:00",
Years: []int{2024, 2025},
Months: []time.Month{12, 1},
MonthDays: []int{31, 1},
},
},
},
startTime: time.Date(2024, 12, 31, 23, 0, 0, 0, time.UTC),
expectedTime: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
name: "LeapYear",
actionTiming: &ActionTiming{
Timing: &RateInterval{
Timing: &RITiming{
StartTime: "00:00:00",
Years: []int{2024},
Months: []time.Month{2},
MonthDays: []int{29},
},
},
},
startTime: time.Date(2024, 2, 28, 0, 0, 0, 0, time.UTC),
expectedTime: time.Date(2024, 2, 29, 0, 0, 0, 0, time.UTC),
},
{
name: "NilTiming",
actionTiming: &ActionTiming{
Timing: nil,
},
startTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
expectedTime: time.Time{},
expectedError: true,
},
{
name: "EmptyTiming",
actionTiming: &ActionTiming{
Timing: &RateInterval{
Timing: nil,
},
},
startTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
expectedTime: time.Time{},
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.actionTiming != nil {
tt.actionTiming.ResetStartTimeCache()
}
result := tt.actionTiming.GetNextStartTime(tt.startTime)
if tt.expectedError {
if !result.IsZero() {
t.Errorf("Expected zero time for error case, got: %v", result)
}
return
}
if !result.Equal(tt.expectedTime) {
t.Errorf("GetNextStartTime(%v) = %v; want %v",
tt.startTime, result, tt.expectedTime)
}
cachedResult := tt.actionTiming.GetNextStartTime(tt.startTime)
if !cachedResult.Equal(result) {
t.Errorf("Cached result differs: got %v, want %v",
cachedResult, result)
}
})
}
}

View File

@@ -3451,3 +3451,101 @@ func TestFilterToSQLQueryValidations(t *testing.T) {
})
}
}
func TestWeightFromDynamics2(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
connMgr = NewConnManager(cfg, nil)
db, _ := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
dm := NewDataManager(db, cfg.CacheCfg(), nil)
filterS := NewFilterS(cfg, connMgr, dm)
testCases := []struct {
name string
dynamicWeights []*utils.DynamicWeight
tenant string
event utils.DataProvider
expectedWeight float64
expectedErr bool
}{
{
name: "EmptyDynamicWeight",
dynamicWeights: []*utils.DynamicWeight{},
tenant: "cgrates.org",
event: utils.MapStorage{},
expectedWeight: 0.0,
expectedErr: false,
},
{
name: "MatchingWeight",
dynamicWeights: []*utils.DynamicWeight{
{
FilterIDs: []string{"*string:~*req.Account:1001"},
Weight: 10.0,
},
},
tenant: "cgrates.org",
event: utils.MapStorage{
"*req": utils.MapStorage{
"Account": "1001",
},
},
expectedWeight: 10.0,
},
{
name: "MultipleMatchingWeights",
dynamicWeights: []*utils.DynamicWeight{
{
FilterIDs: []string{"*prefix:~*req.Destination:GER"},
Weight: 10.0,
},
{
FilterIDs: []string{"*string:~*opts.subsys:*sessions"},
Weight: 5.0,
},
},
tenant: "cgrates.org",
event: utils.MapStorage{
"*req": utils.MapStorage{
"Destination": "GER_0055",
},
"*opts": utils.MapStorage{
"*subsys": "*sessions",
},
},
expectedWeight: 10.0,
},
{
name: "NoMatchingWeightFilterErr",
dynamicWeights: []*utils.DynamicWeight{
{
FilterIDs: []string{"FLTR1"},
Weight: 10.0,
},
},
tenant: "cgrates.org",
event: utils.MapStorage{
"*req": utils.MapStorage{
"Account": "1001",
},
},
expectedErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
weight, err := WeightFromDynamics(tc.dynamicWeights, filterS, tc.tenant, tc.event)
if tc.expectedErr {
if err == nil {
t.Error("Expected error but got none")
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if weight != tc.expectedWeight {
t.Errorf("Expected weight %v, got %v", tc.expectedWeight, weight)
}
}
})
}
}

View File

@@ -23,6 +23,7 @@ import (
"log"
"os"
"reflect"
"slices"
"sort"
"strings"
"testing"
@@ -2163,3 +2164,77 @@ func TestThresholdsStoreThresholdCacheSetErr(t *testing.T) {
utils.Logger.SetLogLevel(0)
}
func TestThresholdProcessEvent(t *testing.T) {
cfg := config.NewDefaultCGRConfig()
cfg.ThresholdSCfg().IndexedSelects = false
db, _ := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
dm := NewDataManager(db, cfg.CacheCfg(), nil)
fS := NewFilterS(cfg, nil, dm)
ths := NewThresholdService(dm, cfg, fS, nil)
thps := []*ThresholdProfile{
{
Tenant: "cgrates.org",
ID: "TH1",
FilterIDs: []string{"*string:~*req.Account:1001"},
MinHits: 3,
MaxHits: 2,
},
{
Tenant: "cgrates.org",
ID: "TH2",
FilterIDs: []string{"*string:~*req.Account:1002"},
MinHits: 2,
MaxHits: 3,
}, {
Tenant: "cgrates.org",
ID: "TH3",
FilterIDs: []string{"*string:~*req.Account:1003"},
MinHits: 1,
MaxHits: -1,
},
}
for _, thP := range thps {
if err := ths.dm.SetThresholdProfile(thP, false); err != nil {
t.Error(err)
}
}
tts := []struct {
name string
runs int
cgrEvnt map[string]any
matchedthIDs []string
}{
{
name: "MinHitsLargerThanMaxHits",
runs: 3,
cgrEvnt: map[string]any{
utils.AccountField: "1001",
},
},
{
name: "MinHitsLargerThanMaxHits",
runs: 4,
cgrEvnt: map[string]any{
utils.AccountField: "1002",
},
},
{},
}
for _, tt := range tts {
t.Run(tt.name, func(t *testing.T) {
var thIDs []string
for range tt.runs {
var err error
thIDs, err = ths.processEvent("cgrates.org", &utils.CGREvent{Event: tt.cgrEvnt})
if err != nil {
t.Error(err)
}
}
if !slices.Equal(thIDs, tt.matchedthIDs) {
t.Errorf("expected: %v, received: %v", tt.matchedthIDs, thIDs)
}
})
}
}

View File

@@ -978,3 +978,175 @@ func TestEngineUnitCounterString(t *testing.T) {
t.Errorf("Expected JSON: %s, got: %s", want, got)
}
}
func TestResetCounters(t *testing.T) {
tests := []struct {
name string
initialCounters UnitCounters
action *Action
expectedCounters UnitCounters
}{
{
name: "ResetAlCountersNilAction",
initialCounters: UnitCounters{
utils.MetaMonetary: []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 100.0, Filter: &BalanceFilter{ID: utils.StringPointer("BAL_MON1")}},
{Value: 200.0, Filter: &BalanceFilter{ID: utils.StringPointer("BAL_MON2")}},
},
},
{
CounterType: utils.MetaBalance,
Counters: CounterFilters{
{Value: 50.0, Filter: &BalanceFilter{ID: utils.StringPointer("BAL_MON1")}},
},
},
},
utils.MetaVoice: []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 150.0, Filter: &BalanceFilter{ID: utils.StringPointer("VOICE1")}},
},
},
},
},
action: nil,
expectedCounters: UnitCounters{
utils.MetaMonetary: []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 0.0, Filter: &BalanceFilter{ID: utils.StringPointer("BAL_MON1")}},
{Value: 0.0, Filter: &BalanceFilter{ID: utils.StringPointer("BAL_MON2")}},
},
},
{
CounterType: utils.MetaBalance,
Counters: CounterFilters{
{Value: 0.0, Filter: &BalanceFilter{ID: utils.StringPointer("BAL_MON1")}},
},
},
},
utils.MetaVoice: []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 0.0, Filter: &BalanceFilter{ID: utils.StringPointer("VOICE1")}},
},
},
},
},
},
{
name: "ResetCountersMonetaryBalanceType",
initialCounters: UnitCounters{
"*monetary": []*UnitCounter{
{
CounterType: utils.MetaBalance,
Counters: CounterFilters{
{Value: 100.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON1")}},
{Value: 200.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON2")}},
},
},
},
"*data": []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 50.0, Filter: &BalanceFilter{ID: utils.StringPointer("MB_BAL")}},
},
},
},
},
action: &Action{
Balance: &BalanceFilter{Type: utils.StringPointer("*monetary")},
},
expectedCounters: UnitCounters{
"*monetary": []*UnitCounter{
{
CounterType: utils.MetaBalance,
Counters: CounterFilters{
{Value: 100.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON1")}},
{Value: 200.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON2")}},
},
},
},
"*data": []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 50.0, Filter: &BalanceFilter{ID: utils.StringPointer("MB_BAL")}},
},
},
},
},
},
{
name: "ResetSpecificBalanceType",
initialCounters: UnitCounters{
"*monetary": []*UnitCounter{
{
CounterType: utils.MetaBalance,
Counters: CounterFilters{
{Value: 100.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON1"), Type: utils.StringPointer("*monetary")}},
{Value: 200.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON2"), Type: utils.StringPointer("*monetary")}},
},
},
},
},
action: &Action{
Balance: &BalanceFilter{ID: utils.StringPointer("MON1"), Type: utils.StringPointer("*monetary")},
},
expectedCounters: UnitCounters{
"*monetary": []*UnitCounter{
{
CounterType: utils.MetaBalance,
Counters: CounterFilters{
{Value: 0.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON1"), Type: utils.StringPointer("*monetary")}},
{Value: 200.0, Filter: &BalanceFilter{ID: utils.StringPointer("MON2"), Type: utils.StringPointer("*monetary")}},
},
},
},
},
},
{
name: "ActionBalanceTypeNotExist",
initialCounters: UnitCounters{
"*data": []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 150.0, Filter: &BalanceFilter{ID: utils.StringPointer("DATA1"), Type: utils.StringPointer("*data")}},
},
},
},
},
action: &Action{
Balance: &BalanceFilter{Type: utils.StringPointer("*monetary")},
},
expectedCounters: UnitCounters{
"*data": []*UnitCounter{
{
CounterType: utils.MetaCounterEvent,
Counters: CounterFilters{
{Value: 150.0, Filter: &BalanceFilter{ID: utils.StringPointer("DATA1"), Type: utils.StringPointer("*data")}},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cloneInitialCounters := tt.initialCounters.Clone()
cloneInitialCounters.resetCounters(tt.action)
if !reflect.DeepEqual(cloneInitialCounters, tt.expectedCounters) {
t.Errorf("mismatch after resetCounters.\nExpected:\n%s\nGot:\n%s",
utils.ToJSON(tt.expectedCounters), utils.ToJSON(cloneInitialCounters))
}
})
}
}