/* 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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package engine import ( "encoding/json" "testing" "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" "github.com/google/go-cmp/cmp" ) func TestGetRatingProfileForPrefix(t *testing.T) { cd := &CallDescriptor{ TimeStart: time.Date(2013, 11, 18, 13, 45, 1, 0, time.UTC), TimeEnd: time.Date(2013, 11, 18, 13, 47, 30, 0, time.UTC), Tenant: "vdf", Category: "0", Subject: "fallback1", Destination: "0256098", } cd.LoadRatingPlans() if len(cd.RatingInfos) != 3 || !cd.continousRatingInfos() { t.Logf("0: %+v", cd.RatingInfos[0]) t.Logf("1: %+v", cd.RatingInfos[1]) t.Logf("2: %+v", cd.RatingInfos[2]) t.Errorf("Error loading rating information: %+v %+v", cd.RatingInfos, cd.continousRatingInfos()) } } func TestGetRatingProfileForPrefixFirstEmpty(t *testing.T) { cd := &CallDescriptor{ TimeStart: time.Date(2013, 11, 18, 13, 44, 1, 0, time.UTC), TimeEnd: time.Date(2013, 11, 18, 13, 47, 30, 0, time.UTC), Tenant: "vdf", Category: "0", Subject: "fallback1", Destination: "0256098", } cd.LoadRatingPlans() if len(cd.RatingInfos) != 4 || !cd.continousRatingInfos() { t.Logf("0: %+v", cd.RatingInfos[0]) t.Logf("1: %+v", cd.RatingInfos[1]) t.Logf("2: %+v", cd.RatingInfos[2]) t.Logf("3: %+v", cd.RatingInfos[3]) t.Errorf("Error loading rating information: %+v %+v", cd.RatingInfos, cd.continousRatingInfos()) } } func TestGetRatingProfileNotFound(t *testing.T) { cd := &CallDescriptor{ TimeStart: time.Date(2015, 8, 18, 22, 05, 0, 0, time.UTC), TimeEnd: time.Date(2015, 8, 18, 22, 06, 30, 0, time.UTC), Tenant: "vdf", Category: "0", Subject: "no_rating_profile", Destination: "0256098", } cd.LoadRatingPlans() if len(cd.RatingInfos) != 1 || !cd.continousRatingInfos() { t.Errorf("Error loading rating information: %+v %+v", cd.RatingInfos, cd.continousRatingInfos()) } } func TestGetRatingProfileFoundButNoDestination(t *testing.T) { cd := &CallDescriptor{ TimeStart: time.Date(2015, 8, 18, 22, 05, 0, 0, time.UTC), TimeEnd: time.Date(2015, 8, 18, 22, 06, 30, 0, time.UTC), Tenant: "cgrates.org", Category: "call", Subject: "nt", Destination: "447956", } cd.LoadRatingPlans() if len(cd.RatingInfos) != 0 { t.Errorf("Error loading rating information: %+v %+v", cd.RatingInfos, cd.continousRatingInfos()) } } func TestRatingProfileRISorter(t *testing.T) { ris := RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, }, } sorter := &RateIntervalTimeSorter{referenceTime: time.Date(2016, 1, 6, 19, 0, 0, 0, time.UTC), ris: ris} rIntervals := sorter.Sort() if len(rIntervals) != 3 || rIntervals[0].Timing.StartTime != "00:00:00" || rIntervals[1].Timing.StartTime != "09:00:00" || rIntervals[2].Timing.StartTime != "19:00:00" { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileRIforTSOne(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 19, 0, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 6, 19, 1, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 1 || rIntervals[0].Timing.StartTime != "19:00:00" { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileRIforTSTwo(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 10, 0, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 6, 19, 1, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 2 || rIntervals[0].Timing.StartTime != "09:00:00" || rIntervals[1].Timing.StartTime != "19:00:00" { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileRIforTSThree(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 8, 0, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 6, 19, 1, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 3 || rIntervals[0].Timing.StartTime != "00:00:00" || rIntervals[1].Timing.StartTime != "09:00:00" || rIntervals[2].Timing.StartTime != "19:00:00" { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileRIforTSMidnight(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 23, 40, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 7, 1, 1, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 1 || rIntervals[0].Timing.StartTime != "19:00:00" { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileYearMonthDay(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, }, &RateInterval{ Timing: &RITiming{ Years: utils.Years{2016}, Months: utils.Months{1}, MonthDays: utils.MonthDays{6, 7}, WeekDays: utils.WeekDays{}, StartTime: "19:00:00", }, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 23, 40, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 7, 1, 1, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 1 || rIntervals[0].Timing.StartTime != "19:00:00" { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileWeighted(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, Weight: 10, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, Weight: 10, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, Weight: 10, }, &RateInterval{ Timing: &RITiming{ Years: utils.Years{2016}, Months: utils.Months{1}, MonthDays: utils.MonthDays{6}, WeekDays: utils.WeekDays{}, StartTime: "00:00:00", }, Weight: 11, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 23, 40, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 6, 23, 45, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 1 || rIntervals[0].Timing.StartTime != "00:00:00" || rIntervals[0].Weight != 11 { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileWeightedMultiple(t *testing.T) { ri := &RatingInfo{ RateIntervals: RateIntervalList{ &RateInterval{ Timing: &RITiming{ StartTime: "09:00:00", }, Weight: 10, }, &RateInterval{ Timing: &RITiming{ StartTime: "00:00:00", }, Weight: 10, }, &RateInterval{ Timing: &RITiming{ StartTime: "19:00:00", }, Weight: 10, }, &RateInterval{ Timing: &RITiming{ Years: utils.Years{2016}, Months: utils.Months{1}, MonthDays: utils.MonthDays{6}, WeekDays: utils.WeekDays{}, StartTime: "00:00:00", }, Weight: 11, }, &RateInterval{ Timing: &RITiming{ Years: utils.Years{2016}, Months: utils.Months{1}, MonthDays: utils.MonthDays{6}, WeekDays: utils.WeekDays{}, StartTime: "18:00:00", }, Weight: 11, }, }, } ts := &TimeSpan{ TimeStart: time.Date(2016, 1, 6, 17, 40, 0, 0, time.UTC), TimeEnd: time.Date(2016, 1, 6, 23, 45, 30, 0, time.UTC), } rIntervals := ri.SelectRatingIntevalsForTimespan(ts) if len(rIntervals) != 2 || rIntervals[0].Timing.StartTime != "00:00:00" || rIntervals[0].Weight != 11 || rIntervals[1].Timing.StartTime != "18:00:00" || rIntervals[1].Weight != 11 { t.Error("Wrong interval list: ", utils.ToIJSON(rIntervals)) } } func TestRatingProfileSubjectPrefixMatching(t *testing.T) { rpSubjectPrefixMatching = true rp, err := RatingProfileSubjectPrefixMatching("*out:cgrates.org:data:rif") if rp == nil || err != nil { t.Errorf("Error getting rating profile by prefix: %+v (%v)", rp, err) } rp, err = RatingProfileSubjectPrefixMatching("*out:cgrates.org:data:rifescu") if rp == nil || err != nil { t.Errorf("Error getting rating profile by prefix: %+v (%v)", rp, err) } rpSubjectPrefixMatching = false } func TestRatingProfileGetRatingPlansPrefixAny(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) rpf := &RatingProfile{ Id: "*out:cgrates.org:call:1001", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), RatingPlanId: "RP_2CNT", }}, } cd := &CallDescriptor{ ToR: "*prepaid", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1003", TimeStart: time.Date(2023, 11, 7, 8, 42, 26, 0, time.UTC), TimeEnd: time.Date(2023, 11, 7, 8, 42, 26, 0, time.UTC).Add(50 * time.Second), } dm.SetRatingPlan(&RatingPlan{ Id: "RP_2CNT", Timings: map[string]*RITiming{ "30eab300": { Years: utils.Years{}, Months: utils.Months{}, MonthDays: utils.MonthDays{}, WeekDays: utils.WeekDays{}, StartTime: "00:00:00", }, }, Ratings: map[string]*RIRate{ "b457f86d": { Rates: []*RGRate{ { GroupIntervalStart: 0, Value: 0, RateIncrement: 60 * time.Second, RateUnit: 60 * time.Second, }, }, RoundingMethod: utils.MetaRoundingMiddle, RoundingDecimals: 4, }, }, DestinationRates: map[string]RPRateList{ "*any": []*RPRate{ { Timing: "30eab300", Rating: "b457f86d", Weight: 10, }, }, }, }) dm.SetReverseDestination("DEST", []string{"1003"}, "") SetDataStorage(dm) if err := rpf.GetRatingPlansForPrefix(cd); err != nil { t.Error("Error getting rating plans for prefix: ", err) } } func TestEngineSwapRpasIndex(t *testing.T) { activationTime1 := time.Now() activationTime2 := activationTime1.Add(time.Hour) plan1 := &RatingPlanActivation{ ActivationTime: activationTime1, RatingPlanId: "planTest1", FallbackKeys: []string{"key1", "key2"}, } plan2 := &RatingPlanActivation{ ActivationTime: activationTime2, RatingPlanId: "planTest2", FallbackKeys: []string{"key3"}, } initialRPAs := RatingPlanActivations{plan1, plan2} want := RatingPlanActivations{plan2, plan1} rpas := make(RatingPlanActivations, len(initialRPAs)) copy(rpas, initialRPAs) rpas.Swap(0, 1) diff := cmp.Diff(rpas, want) if diff != "" { t.Errorf("Expected RatingPlanActivations after swap to be equal to want: (-got, +want)\n%s", diff) } } func TestEngineEqualRpaOrpa(t *testing.T) { activationTime1 := time.Now() activationTime2 := activationTime1.Add(time.Hour) plan1 := &RatingPlanActivation{ ActivationTime: activationTime1, RatingPlanId: "planId1", FallbackKeys: []string{"key1", "key2"}, } plan2 := &RatingPlanActivation{ ActivationTime: activationTime2, RatingPlanId: "planId2", FallbackKeys: []string{"key3"}, } tests := []struct { name string rpa *RatingPlanActivation orpa *RatingPlanActivation expected bool }{ {name: "Equal plans", rpa: plan1, orpa: plan1, expected: true}, {name: "Different activation time", rpa: plan1, orpa: plan2, expected: false}, {name: "Nil arguments (both nil)", rpa: nil, orpa: nil, expected: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var result bool if tc.rpa == nil && tc.orpa == nil { result = tc.expected } else { result = tc.rpa.Equal(tc.orpa) } if result != tc.expected { t.Errorf("Expected Equal(%v, %v) to be %v, got %v", tc.rpa, tc.orpa, tc.expected, result) } }) } } func TestEngineSwapIndexRis(t *testing.T) { ris := RatingInfos{ &RatingInfo{MatchedSubject: "Cgrates1"}, &RatingInfo{MatchedSubject: "Cgrates2"}, &RatingInfo{MatchedSubject: "Cgrates3"}, } i, j := 0, 2 ris.Swap(i, j) if ris[i].MatchedSubject != "Cgrates3" || ris[j].MatchedSubject != "Cgrates1" { t.Errorf("Swap did not swap elements correctly") } } func TestEngineLessRis(t *testing.T) { ris := RatingInfos{ &RatingInfo{ActivationTime: time.Now()}, &RatingInfo{ActivationTime: time.Now().Add(time.Hour)}, &RatingInfo{ActivationTime: time.Now().Add(-time.Hour)}, } if ris.Less(0, 2) { t.Errorf("Expected first element not to be less than the third one") } } func TestRatingInfosString(t *testing.T) { ratingInfos := RatingInfos{ {MatchedSubject: "Info1", RatingPlanId: "Value1"}, {MatchedSubject: "Info2", RatingPlanId: "Value2"}, } expectedJSON, err := json.MarshalIndent(ratingInfos, "", " ") if err != nil { t.Fatalf("Failed to marshal expected JSON: %v", err) } result := ratingInfos.String() if result != string(expectedJSON) { t.Errorf("Expected %s, but got %s", string(expectedJSON), result) } }