From d6b8c8386e125395afe2134267843024120b2a00 Mon Sep 17 00:00:00 2001 From: armirveliaj Date: Fri, 15 Aug 2025 10:47:44 -0400 Subject: [PATCH] Add coverage tests for ips & statmetrics --- engine/ips_test.go | 208 +++++++++++++++++++++++++++++++++++++ engine/statmetrics_test.go | 104 +++++++++++++++++++ 2 files changed, 312 insertions(+) diff --git a/engine/ips_test.go b/engine/ips_test.go index 8ac794c88..377ffb565 100644 --- a/engine/ips_test.go +++ b/engine/ips_test.go @@ -20,9 +20,11 @@ package engine import ( "reflect" + "slices" "testing" "time" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) @@ -482,3 +484,209 @@ func TestIPRecordUsage(t *testing.T) { } }) } + +func TestIPClearUsage(t *testing.T) { + t.Run("usage not found", func(t *testing.T) { + ip := &IP{Usages: make(map[string]*IPUsage)} + err := ip.clearUsage("missing") + if err == nil { + t.Fatal("expected error, got nil") + } + expected := "cannot find usage record with id: missing" + if err.Error() != expected { + t.Errorf("expected error %q, got %q", expected, err.Error()) + } + }) + + t.Run("usage found with zero expiry time", func(t *testing.T) { + totalUsage := 10.0 + ip := &IP{ + Usages: map[string]*IPUsage{ + "id1": {Units: 5.0}, + }, + tUsage: &totalUsage, + TTLIdx: []string{"id2"}, + } + + err := ip.clearUsage("id1") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if _, exists := ip.Usages["id1"]; exists { + t.Error("expected id1 to be deleted from Usages") + } + if *ip.tUsage != 5.0 { + t.Errorf("expected tUsage=5.0, got %v", *ip.tUsage) + } + if !slices.Equal(ip.TTLIdx, []string{"id2"}) { + t.Errorf("TTLIdx changed unexpectedly: %v", ip.TTLIdx) + } + }) + + t.Run("usage found with non-zero expiry time", func(t *testing.T) { + totalUsage := 20.0 + expTime := time.Now().Add(time.Hour) + ip := &IP{ + Usages: map[string]*IPUsage{ + "id2": {ExpiryTime: expTime, Units: 7.0}, + }, + TTLIdx: []string{"id2", "other"}, + tUsage: &totalUsage, + } + + err := ip.clearUsage("id2") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if _, exists := ip.Usages["id2"]; exists { + t.Error("expected id2 to be deleted from Usages") + } + if *ip.tUsage != 13.0 { + t.Errorf("expected tUsage=13.0, got %v", *ip.tUsage) + } + if slices.Contains(ip.TTLIdx, "id2") { + t.Errorf("expected id2 to be removed from TTLIdx, got %v", ip.TTLIdx) + } + }) +} + +func TestIPsSort(t *testing.T) { + ip1 := &IP{cfg: &IPProfile{Weight: 1.0}} + ip2 := &IP{cfg: &IPProfile{Weight: 3.0}} + ip3 := &IP{cfg: &IPProfile{Weight: 2.0}} + + ips := IPs{ip1, ip2, ip3} + ips.Sort() + + expected := IPs{ip2, ip3, ip1} + for i := range ips { + if ips[i] != expected[i] { + t.Errorf("expected index %d to be %+v, got %+v", i, expected[i], ips[i]) + } + } +} + +func TestIPsUnlock(t *testing.T) { + ip1 := &IP{lkID: "lock1", cfg: &IPProfile{lkID: "cfgLock1"}} + ip2 := &IP{lkID: "lock2", cfg: &IPProfile{lkID: "cfgLock2"}} + ip3 := &IP{lkID: "lock3", cfg: nil} + + ips := IPs{ip1, ip2, ip3} + ips.unlock() + + if ip1.lkID != "" { + t.Errorf("expected ip1.lkID to be empty, got %q", ip1.lkID) + } + if ip2.lkID != "" { + t.Errorf("expected ip2.lkID to be empty, got %q", ip2.lkID) + } + if ip3.lkID != "" { + t.Errorf("expected ip3.lkID to be empty, got %q", ip3.lkID) + } + + if ip1.cfg.lkID != "" { + t.Errorf("expected ip1.cfg.lkID to be empty, got %q", ip1.cfg.lkID) + } + if ip2.cfg.lkID != "" { + t.Errorf("expected ip2.cfg.lkID to be empty, got %q", ip2.cfg.lkID) + } +} + +func TestIPsIds(t *testing.T) { + ip1 := &IP{ID: "ip1"} + ip2 := &IP{ID: "ip2"} + ip3 := &IP{ID: "ip3"} + + ips := IPs{ip1, ip2, ip3} + + got := ips.ids() + + if len(got) != 3 { + t.Errorf("expected 3 IDs in set, got %d", len(got)) + } + + expectedIDs := []string{"ip1", "ip2", "ip3"} + for _, id := range expectedIDs { + if _, exists := got[id]; !exists { + t.Errorf("expected ID %q in set, but it was missing", id) + } + } +} + +func TestIPCacheClone(t *testing.T) { + ttl := 10 * time.Minute + tUsage := 123.45 + dirty := true + + original := &IP{ + Tenant: "cgrates.org", + ID: "ip01", + TTLIdx: []string{"idx1", "idx2"}, + Usages: map[string]*IPUsage{ + "u1": {Tenant: "cgrates.org", ID: "u1", Units: 50.0}, + }, + ttl: &ttl, + tUsage: &tUsage, + dirty: &dirty, + cfg: &IPProfile{ + Tenant: "cgrates.org", + ID: "profile1", + Weight: 1.5, + }, + } + + cloneAny := original.CacheClone() + if cloneAny == nil { + t.Fatal("CacheClone returned nil") + } + + clone, ok := cloneAny.(*IP) + if !ok { + t.Fatalf("expected type *IP, got %T", cloneAny) + } + + if clone == original { + t.Error("expected clone to be a different pointer than original") + } + + if !reflect.DeepEqual(clone, original) { + t.Errorf("expected clone to be deeply equal to original:\noriginal: %+v\nclone: %+v", original, clone) + } +} + +func TestNewIPService(t *testing.T) { + dm := &DataManager{} + cgrcfg := &config.CGRConfig{} + filterS := &FilterS{} + connMgr := &ConnManager{} + + service := NewIPService(dm, cgrcfg, filterS, connMgr) + if service == nil { + t.Fatal("expected NewIPService to return non-nil") + } + + if service.dm != dm { + t.Errorf("expected dm=%+v, got %+v", dm, service.dm) + } + if service.cfg != cgrcfg { + t.Errorf("expected cfg=%+v, got %+v", cgrcfg, service.cfg) + } + if service.fs != filterS { + t.Errorf("expected fs=%+v, got %+v", filterS, service.fs) + } + if service.cm != connMgr { + t.Errorf("expected cm=%+v, got %+v", connMgr, service.cm) + } + + if service.storedIPs == nil { + t.Error("expected storedIPs map to be initialized") + } + if service.loopStopped == nil { + t.Error("expected loopStopped channel to be initialized") + } + if service.stopBackup == nil { + t.Error("expected stopBackup channel to be initialized") + } +} diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index ae32848f7..233632753 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -6324,3 +6324,107 @@ func TestStatREPSCGetFloat64Value(t *testing.T) { } }) } + +func TestNewStatREPSC(t *testing.T) { + filterIDs := []string{"fltr1", "fltr2"} + minItems := 5 + + metric, err := NewStatREPSC(minItems, "ignored", filterIDs) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + stat, ok := metric.(*StatREPSC) + if !ok { + t.Fatalf("expected type *StatREPSC, got %T", metric) + } + + if !reflect.DeepEqual(stat.FilterIDs, filterIDs) { + t.Errorf("expected FilterIDs %v, got %v", filterIDs, stat.FilterIDs) + } + + if stat.MinItems != minItems { + t.Errorf("expected MinItems %d, got %d", minItems, stat.MinItems) + } + + if stat.Events == nil { + t.Error("expected Events map to be initialized, got nil") + } else if len(stat.Events) != 0 { + t.Errorf("expected Events to be empty, got length %d", len(stat.Events)) + } + + if stat.Count != 0 { + t.Errorf("expected Count 0, got %d", stat.Count) + } + + if stat.cachedVal != nil { + t.Errorf("expected cachedVal to be nil, got %v", stat.cachedVal) + } +} + +func TestStatREPSCGetStringValue(t *testing.T) { + t.Run("returns NotAvailable when StatsNA", func(t *testing.T) { + s := &StatREPSC{} + na := utils.StatsNA + s.cachedVal = &na + got := s.GetStringValue(2) + if got != utils.NotAvailable { + t.Errorf("expected %q, got %q", utils.NotAvailable, got) + } + }) + + t.Run("returns formatted float when value is not StatsNA", func(t *testing.T) { + s := &StatREPSC{} + val := 123.456 + s.cachedVal = &val + + got := s.GetStringValue(2) + expected := strconv.FormatFloat(val, 'f', -1, 64) + if got != expected { + t.Errorf("expected %q, got %q", expected, got) + } + }) +} + +func TestStatLowestGetCompressFactor(t *testing.T) { + t.Run("adds missing IDs with value 1", func(t *testing.T) { + s := &StatLowest{ + Events: map[string]float64{ + "id1": 10.0, + "id2": 20.0, + }, + } + events := map[string]int{ + "id2": 5, + } + + got := s.GetCompressFactor(events) + + expected := map[string]int{ + "id1": 1, + "id2": 5, + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("expected %v, got %v", expected, got) + } + }) + + t.Run("no changes when all IDs exist", func(t *testing.T) { + s := &StatLowest{ + Events: map[string]float64{ + "id1": 10.0, + }, + } + events := map[string]int{ + "id1": 2, + } + + got := s.GetCompressFactor(events) + expected := map[string]int{"id1": 2} + + if !reflect.DeepEqual(got, expected) { + t.Errorf("expected %v, got %v", expected, got) + } + }) +}