From 233064fff777cee4c059468e723966de959fbff1 Mon Sep 17 00:00:00 2001 From: armirveliaj Date: Wed, 20 Aug 2025 10:52:46 -0400 Subject: [PATCH] Add coverage tests for ips & statmetrics --- engine/ips_test.go | 141 +++++++++++++++++++++++++++++ engine/statmetrics_test.go | 181 +++++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+) diff --git a/engine/ips_test.go b/engine/ips_test.go index 377ffb565..844826416 100644 --- a/engine/ips_test.go +++ b/engine/ips_test.go @@ -690,3 +690,144 @@ func TestNewIPService(t *testing.T) { t.Error("expected stopBackup channel to be initialized") } } + +func TestIPServiceStoreMatchedIPs(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + data, err := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items) + if err != nil { + t.Error(err) + } + dm := NewDataManager(data, config.CgrConfig().CacheCfg(), nil) + + t.Run("StoreInterval=0", func(t *testing.T) { + cfg.IPsCfg().StoreInterval = 0 + svc := &IPService{ + cfg: cfg, + dm: dm, + storedIPs: make(utils.StringSet), + } + + dirty := false + ip := &IP{Tenant: "cgrates.org", ID: "ip01", dirty: &dirty} + + err := svc.storeMatchedIPs(IPs{ip}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if dirty { + t.Errorf("expected dirty=false, got true") + } + if len(svc.storedIPs) != 0 { + t.Errorf("expected storedIPs empty, got %+v", svc.storedIPs) + } + }) + + t.Run("StoreInterval>0 marks dirty and schedules for backup", func(t *testing.T) { + cfg.IPsCfg().StoreInterval = 10 + svc := &IPService{ + cfg: cfg, + dm: dm, + storedIPs: make(utils.StringSet), + } + + dirty := false + ip := &IP{Tenant: "cgrates.org", ID: "ip02", dirty: &dirty} + + err := svc.storeMatchedIPs(IPs{ip}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !dirty { + t.Errorf("expected dirty=true, got false") + } + if _, exists := svc.storedIPs[ip.TenantID()]; !exists { + t.Errorf("expected ip02 in storedIPs, got %+v", svc.storedIPs) + } + }) + + t.Run("StoreInterval<0 stores immediately", func(t *testing.T) { + cfg.IPsCfg().StoreInterval = -1 + svc := &IPService{ + cfg: cfg, + dm: dm, + storedIPs: make(utils.StringSet), + } + + dirty := true + ip := &IP{Tenant: "cgrates.org", ID: "ip03", dirty: &dirty} + + Cache.Set(utils.CacheIPs, ip.TenantID(), ip, nil, true, utils.NonTransactional) + + err := svc.storeMatchedIPs(IPs{ip}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if dirty { + t.Errorf("expected dirty=false after storeIP, got true") + } + }) +} + +func TestIPServiceMatchingIPsForEvent(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + data, err := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items) + if err != nil { + t.Error(err) + } + dm := NewDataManager(data, cfg.CacheCfg(), nil) + fs := NewFilterS(cfg, nil, dm) + + svc := &IPService{ + cfg: cfg, + dm: dm, + fs: fs, + } + + profile := &IPProfile{ + Tenant: "cgrates.org", + ID: "ip01", + Stored: true, + TTL: 5 * time.Minute, + } + if err := dm.SetIPProfile(profile, true); err != nil { + t.Fatalf("failed to set IPProfile: %v", err) + } + + ip := &IP{ + Tenant: "cgrates.org", + ID: "ip01", + } + Cache.Set(utils.CacheIPs, ip.TenantID(), ip, nil, true, utils.NonTransactional) + if err := dm.SetIP(ip); err != nil { + t.Fatalf("failed to set IP: %v", err) + } + + ev := &utils.CGREvent{ + Event: map[string]interface{}{ + "type": "testEvent", + }, + Time: func() *time.Time { t := time.Now(); return &t }(), + } + evUUID := "event-uuid-001" + + ips, err := svc.matchingIPsForEvent("cgrates.org", ev, evUUID, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(ips) != 1 { + t.Errorf("expected 1 IP, got %d", len(ips)) + } + if ips[0].ID != "ip01" { + t.Errorf("expected IP ID 'ip01', got '%s'", ips[0].ID) + } + + if ips[0].ttl == nil || *ips[0].ttl != profile.TTL { + t.Errorf("expected IP TTL %v, got %v", profile.TTL, ips[0].ttl) + } + + cached, ok := Cache.Get(utils.CacheEventIPs, evUUID) + if !ok || cached == nil { + t.Errorf("expected cached IPIDs for event UUID") + } +} diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 233632753..c819dfd38 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -6428,3 +6428,184 @@ func TestStatLowestGetCompressFactor(t *testing.T) { } }) } + +func TestStatLowestMarshal(t *testing.T) { + lowest := &StatLowest{ + FilterIDs: []string{"filter1", "filter2"}, + FieldName: "Usage", + MinItems: 2, + Lowest: 15.5, + Count: 1, + Events: map[string]float64{ + "Event101": 15.5, + }, + } + + ev := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "Event102", + Event: map[string]any{ + "Usage": 20.0, + "Account": "3003", + "Destination": "0077", + }, + } + lowest.Events[ev.ID] = ev.Event["Usage"].(float64) + lowest.Count++ + + var nLowest StatLowest + + expected := []byte(`{"FilterIDs":["filter1","filter2"],"FieldName":"Usage","MinItems":2,"Lowest":15.5,"Count":2,"Events":{"Event101":15.5,"Event102":20}}`) + + if b, err := lowest.Marshal(&jMarshaler); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(expected, b) { + t.Errorf("Expected: %s , received: %s", string(expected), string(b)) + } else if err := nLowest.LoadMarshaled(&jMarshaler, b); err != nil { + t.Error(err) + } else if reflect.DeepEqual(lowest, nLowest) { + t.Errorf("Expected objects to differ, got same: %s", utils.ToJSON(lowest)) + } +} + +func TestStatTCDClone(t *testing.T) { + val := 5 * time.Minute + orig := &StatTCD{ + FilterIDs: []string{"filter1", "filter2"}, + Sum: 10 * time.Minute, + Count: 2, + MinItems: 1, + Events: map[string]*DurationWithCompress{ + "Event201": {Duration: 4 * time.Minute, CompressFactor: 1}, + "Event202": {Duration: 6 * time.Minute, CompressFactor: 2}, + }, + val: &val, + } + + clone := orig.Clone().(*StatTCD) + + if orig == clone { + t.Fatal("Clone returned the same pointer, expected a deep copy") + } + + if !reflect.DeepEqual(orig, clone) { + t.Errorf("Expected clone to equal original.\nOrig: %+v\nClone: %+v", orig, clone) + } + + clone.FilterIDs[0] = "modifiedFilter" + clone.Events["Event201"].Duration = 9 * time.Minute + *clone.val = 42 * time.Minute + + if orig.FilterIDs[0] == "modifiedFilter" { + t.Error("Expected original FilterIDs to remain unchanged after clone modification") + } + if orig.Events["Event201"].Duration == 9*time.Minute { + t.Error("Expected original Events to remain unchanged after clone modification") + } + if *orig.val == 42*time.Minute { + t.Error("Expected original val to remain unchanged after clone modification") + } + + var nilTCD *StatTCD + if clonedNil := nilTCD.Clone(); clonedNil != nil { + t.Error("Expected nil Clone() to return nil") + } +} + +func TestREPFCMarshal(t *testing.T) { + repfc := &StatREPFC{ + MinItems: 2, + FilterIDs: []string{"filter1", "filter2"}, + } + + ev := &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "Event1", + Event: map[string]any{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + "Account": "1001", + "Destination": "0034", + }, + } + repfc.AddEvent(ev.ID, utils.MapStorage{utils.MetaReq: ev.Event}) + + var nrepfc StatREPFC + + expected := []byte(`{"FilterIDs":["filter1","filter2"],"MinItems":2,"ErrorType":"","Count":0,"Events":null}`) + + if b, err := repfc.Marshal(&jMarshaler); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(expected, b) { + t.Errorf("Expected: %s , received: %s", string(expected), string(b)) + } else if err := nrepfc.LoadMarshaled(&jMarshaler, b); err != nil { + t.Error(err) + } else if reflect.DeepEqual(repfc, nrepfc) { + t.Errorf("Expected objects to differ, got same: %s", utils.ToJSON(repfc)) + } +} + +func TestREPSCMarshal(t *testing.T) { + repsc := &StatREPSC{ + FilterIDs: []string{"filter1", "filter2"}, + MinItems: 3, + } + + var nrepsc StatREPSC + + expected := []byte(`{"FilterIDs":["filter1","filter2"],"MinItems":3,"Count":0,"Events":null}`) + + if b, err := repsc.Marshal(&jMarshaler); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(expected, b) { + t.Errorf("Expected: %s , received: %s", string(expected), string(b)) + } else if err := nrepsc.LoadMarshaled(&jMarshaler, b); err != nil { + t.Error(err) + } else if reflect.DeepEqual(repsc, nrepsc) { + t.Errorf("Expected objects to differ, got same: %s", utils.ToJSON(repsc)) + } +} + +func TestStatTCCClone(t *testing.T) { + val := 11.45 + orig := &StatTCC{ + FilterIDs: []string{"Filter1", "Filter2"}, + Sum: 200.75, + Count: 3, + MinItems: 1, + Events: map[string]*StatWithCompress{ + "EventA": {Stat: 100.50, CompressFactor: 1}, + "EventB": {Stat: 50.25, CompressFactor: 2}, + }, + val: &val, + } + + clone := orig.Clone().(*StatTCC) + + if orig == clone { + t.Fatal("Clone returned the same pointer, expected a deep copy") + } + + if !reflect.DeepEqual(orig, clone) { + t.Errorf("Expected clone to equal original.\nOrig: %+v\nClone: %+v", orig, clone) + } + + clone.FilterIDs[0] = "modifiedCostFilter" + clone.Events["EventA"].Stat = 999.99 + *clone.val = 888.88 + + if orig.FilterIDs[0] == "modifiedCostFilter" { + t.Error("Expected original FilterIDs to remain unchanged after clone modification") + } + + if orig.Events["EventA"].Stat == 999.99 { + t.Error("Expected original Events to remain unchanged after clone modification") + } + if *orig.val == 888.88 { + t.Error("Expected original val to remain unchanged after clone modification") + } + + var nilTCC *StatTCC + if clonedNil := nilTCC.Clone(); clonedNil != nil { + t.Error("Expected nil Clone() to return nil") + } +}