diff --git a/engine/libtrends_test.go b/engine/libtrends_test.go
index 15199ad0f..ce82d3532 100644
--- a/engine/libtrends_test.go
+++ b/engine/libtrends_test.go
@@ -19,7 +19,9 @@ along with this program. If not, see
package engine
import (
+ "errors"
"reflect"
+ "sync"
"testing"
"time"
@@ -318,3 +320,57 @@ func TestGetTrendLabel(t *testing.T) {
}
}
}
+
+func TestGetTrendGrowth(t *testing.T) {
+
+ trend := Trend{
+ tMux: &sync.RWMutex{},
+ mLast: map[string]time.Time{},
+ Metrics: map[time.Time]map[string]*MetricWithTrend{},
+ mTotals: map[string]float64{},
+ mCounts: map[string]int{},
+ }
+
+ _, err := trend.getTrendGrowth("unknownID", 100, utils.MetaLast, 2)
+ if !errors.Is(err, utils.ErrNotFound) {
+ t.Errorf("Expected error ErrNotFound, got: %v", err)
+ }
+
+ now := time.Now()
+ trend.mLast["metric1"] = now
+
+ _, err = trend.getTrendGrowth("metric1", 100, utils.MetaLast, 2)
+ if !errors.Is(err, utils.ErrNotFound) {
+ t.Errorf("Expected error ErrNotFound, got: %v", err)
+ }
+
+ trend.Metrics = map[time.Time]map[string]*MetricWithTrend{
+ now: {
+ "metric1": {ID: "metric1", Value: 80},
+ },
+ }
+
+ got, err := trend.getTrendGrowth("metric1", 100, utils.MetaLast, 2)
+ expected := utils.Round(20.0/100, 2, utils.MetaRoundingMiddle)
+ if err != nil || !reflect.DeepEqual(got, expected) {
+ t.Errorf("Mismatch for MetaLast correlation. Got: %v, expected: %v", got, expected)
+ }
+
+ trend.mTotals = map[string]float64{
+ "metric1": 400,
+ }
+ trend.mCounts = map[string]int{
+ "metric1": 4,
+ }
+
+ got, err = trend.getTrendGrowth("metric1", 120, utils.MetaAverage, 2)
+ expected = utils.Round(20.0/100, 2, utils.MetaRoundingMiddle)
+ if err != nil || !reflect.DeepEqual(got, expected) {
+ t.Errorf("Mismatch for MetaAverage correlation. Got: %v, expected: %v", got, expected)
+ }
+
+ _, err = trend.getTrendGrowth("metric1", 100, "invalidCorrelation", 2)
+ if !errors.Is(err, utils.ErrCorrelationUndefined) {
+ t.Errorf("Expected error ErrCorrelationUndefined, got: %v", err)
+ }
+}
diff --git a/services/trends_test.go b/services/trends_test.go
new file mode 100644
index 000000000..eb8967823
--- /dev/null
+++ b/services/trends_test.go
@@ -0,0 +1,97 @@
+/*
+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
+*/
+
+package services
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/cgrates/birpc"
+ "github.com/cgrates/birpc/context"
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/cores"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
+func TestNewTrendService(t *testing.T) {
+ cfg := &config.CGRConfig{}
+ dm := &DataDBService{}
+ cacheS := &CacheService{}
+ filterSChan := make(chan *engine.FilterS)
+ server := &cores.Server{}
+ internalTrendSChan := make(chan birpc.ClientConnector)
+ connMgr := &engine.ConnManager{}
+ anz := &AnalyzerService{}
+ srvDep := make(map[string]*sync.WaitGroup)
+
+ service := NewTrendService(cfg, dm, cacheS, filterSChan, server, internalTrendSChan, connMgr, anz, srvDep)
+
+ trendService, ok := service.(*TrendService)
+ if !ok {
+ t.Errorf("Expected type *TrendService, but got %T", service)
+ }
+
+ if trendService.cfg != cfg {
+ t.Errorf("Expected cfg to be %v, but got %v", cfg, trendService.cfg)
+ }
+ if trendService.dm != dm {
+ t.Errorf("Expected dm to be %v, but got %v", dm, trendService.dm)
+ }
+ if trendService.cacheS != cacheS {
+ t.Errorf("Expected cacheS to be %v, but got %v", cacheS, trendService.cacheS)
+ }
+
+ if trendService.server != server {
+ t.Errorf("Expected server to be %v, but got %v", server, trendService.server)
+ }
+ if trendService.connChan != internalTrendSChan {
+ t.Errorf("Expected connChan to be %v, but got %v", internalTrendSChan, trendService.connChan)
+ }
+ if trendService.connMgr != connMgr {
+ t.Errorf("Expected connMgr to be %v, but got %v", connMgr, trendService.connMgr)
+ }
+ if trendService.anz != anz {
+ t.Errorf("Expected anz to be %v, but got %v", anz, trendService.anz)
+ }
+
+}
+
+func TestTrendServiceServiceName(t *testing.T) {
+ tr := &TrendService{}
+
+ serviceName := tr.ServiceName()
+
+ expected := utils.TrendS
+ if serviceName != expected {
+ t.Errorf("Expected service name to be %s, but got %s", expected, serviceName)
+ }
+}
+
+func TestTrendService_Reload(t *testing.T) {
+ tr := &TrendService{}
+
+ ctx := context.Background()
+
+ err := tr.Reload(ctx, nil)
+
+ if err != nil {
+ t.Errorf("Expected no error, but got: %v", err)
+ }
+}
diff --git a/utils/cdr_test.go b/utils/cdr_test.go
new file mode 100644
index 000000000..d6576f7e8
--- /dev/null
+++ b/utils/cdr_test.go
@@ -0,0 +1,253 @@
+/*
+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
+*/
+
+package utils
+
+import (
+ "encoding/json"
+ "errors"
+ "reflect"
+ "testing"
+)
+
+func TestTableName(t *testing.T) {
+ cdrSQLTable := CDRSQLTable{}
+
+ tableName := cdrSQLTable.TableName()
+
+ if tableName != CDRsTBL {
+ t.Errorf("TableName() = %s, expected %s", tableName, CDRsTBL)
+ }
+}
+
+func TestJSONB_GormDataType(t *testing.T) {
+ j := JSONB{"key": "value"}
+
+ dataType := j.GormDataType()
+
+ if dataType != "JSONB" {
+ t.Errorf("GormDataType() returned %v, expected 'JSONB'", dataType)
+ }
+}
+
+func TestJSONBScan(t *testing.T) {
+ {
+ j := &JSONB{}
+ value := []byte(`{"key": "value"}`)
+
+ err := j.Scan(value)
+
+ if err != nil {
+ t.Errorf("Scan([]byte) returned error: %v, expected no error", err)
+ }
+
+ expected := JSONB{"key": "value"}
+ if !reflect.DeepEqual(*j, expected) {
+ t.Errorf("Scan([]byte) resulted in %v, expected %v", *j, expected)
+ }
+ }
+
+ {
+ j := &JSONB{}
+ value := `{"key": "value"}`
+
+ err := j.Scan(value)
+
+ if err != nil {
+ t.Errorf("Scan(string) returned error: %v, expected no error", err)
+ }
+
+ expected := JSONB{"key": "value"}
+ if !reflect.DeepEqual(*j, expected) {
+ t.Errorf("Scan(string) resulted in %v, expected %v", *j, expected)
+ }
+ }
+
+ {
+ j := &JSONB{}
+ value := 123
+
+ err := j.Scan(value)
+
+ if err == nil {
+ t.Errorf("Scan(int) did not return an error, expected an error")
+ } else {
+ expectedError := errors.New("Failed to unmarshal JSONB value:123") // Removed space after ":"
+ if err.Error() != expectedError.Error() {
+ t.Errorf("Scan(int) returned error: %v, expected %v", err, expectedError)
+ }
+ }
+ }
+}
+
+func TestJSONBValue(t *testing.T) {
+ {
+ j := JSONB{"key": "value"}
+
+ value, err := j.Value()
+
+ if err != nil {
+ t.Errorf("Value() returned error: %v, expected no error", err)
+ }
+
+ expected, _ := json.Marshal(j)
+ if string(value.([]byte)) != string(expected) {
+ t.Errorf("Value() resulted in %s, expected %s", value, expected)
+ }
+ }
+
+ {
+ j := JSONB{}
+
+ value, err := j.Value()
+
+ if err != nil {
+ t.Errorf("Value() returned error: %v, expected no error", err)
+ }
+
+ expected, _ := json.Marshal(j)
+ if string(value.([]byte)) != string(expected) {
+ t.Errorf("Value() resulted in %s, expected %s", value, expected)
+ }
+ }
+}
+
+func TestGetUniqueCDRID(t *testing.T) {
+ {
+ cgrEv := &CGREvent{
+ APIOpts: map[string]interface{}{
+ MetaChargeID: "charge_id_123",
+ },
+ }
+
+ result := GetUniqueCDRID(cgrEv)
+
+ expected := "charge_id_123"
+ if result != expected {
+ t.Errorf("GetUniqueCDRID() returned %s, expected %s", result, expected)
+ }
+ }
+
+ {
+ cgrEv := &CGREvent{
+ APIOpts: map[string]interface{}{
+ MetaOriginID: "origin_id_456",
+ },
+ }
+
+ result := GetUniqueCDRID(cgrEv)
+
+ expected := "origin_id_456"
+ if result != expected {
+ t.Errorf("GetUniqueCDRID() returned %s, expected %s", result, expected)
+ }
+ }
+
+ {
+ cgrEv := &CGREvent{
+ APIOpts: map[string]interface{}{},
+ }
+
+ result := GetUniqueCDRID(cgrEv)
+
+ if len(result) != 7 {
+ t.Errorf("GetUniqueCDRID() returned %s, expected a 7-character UUID prefix", result)
+ }
+ }
+}
+
+func TestCDR_CGREvent(t *testing.T) {
+ {
+ cdr := &CDR{
+ Tenant: "test_tenant",
+ Event: map[string]interface{}{"key": "value"},
+ Opts: map[string]interface{}{"opt_key": "opt_value"},
+ }
+
+ cgrEvent := cdr.CGREvent()
+
+ if cgrEvent.Tenant != cdr.Tenant {
+ t.Errorf("CGREvent().Tenant = %s, expected %s", cgrEvent.Tenant, cdr.Tenant)
+ }
+
+ if cgrEvent.Event["key"] != cdr.Event["key"] {
+ t.Errorf("CGREvent().Event = %v, expected %v", cgrEvent.Event, cdr.Event)
+ }
+
+ if cgrEvent.APIOpts["opt_key"] != cdr.Opts["opt_key"] {
+ t.Errorf("CGREvent().APIOpts = %v, expected %v", cgrEvent.APIOpts, cdr.Opts)
+ }
+
+ if cgrEvent.ID == "" {
+ t.Errorf("CGREvent().ID is empty, expected a generated ID")
+ }
+ }
+}
+
+func TestCDRsToCGREvents(t *testing.T) {
+ {
+ cdrs := []*CDR{
+ {
+ Tenant: "tenant1",
+ Event: map[string]interface{}{"event_key1": "event_value1"},
+ Opts: map[string]interface{}{"opt_key1": "opt_value1"},
+ },
+ {
+ Tenant: "tenant2",
+ Event: map[string]interface{}{"event_key2": "event_value2"},
+ Opts: map[string]interface{}{"opt_key2": "opt_value2"},
+ },
+ }
+
+ cgrEvents := CDRsToCGREvents(cdrs)
+
+ if len(cgrEvents) != len(cdrs) {
+ t.Errorf("CDRsToCGREvents() returned %d events, expected %d", len(cgrEvents), len(cdrs))
+ }
+
+ for i, cgrEvent := range cgrEvents {
+ cdr := cdrs[i]
+
+ if cgrEvent.Tenant != cdr.Tenant {
+ t.Errorf("CGREvent[%d].Tenant = %s, expected %s", i, cgrEvent.Tenant, cdr.Tenant)
+ }
+
+ if cgrEvent.Event["event_key1"] != cdr.Event["event_key1"] {
+ t.Errorf("CGREvent[%d].Event = %v, expected %v", i, cgrEvent.Event, cdr.Event)
+ }
+
+ if cgrEvent.APIOpts["opt_key1"] != cdr.Opts["opt_key1"] {
+ t.Errorf("CGREvent[%d].APIOpts = %v, expected %v", i, cgrEvent.APIOpts, cdr.Opts)
+ }
+
+ if cgrEvent.ID == "" {
+ t.Errorf("CGREvent[%d].ID is empty, expected a generated ID", i)
+ }
+ }
+ }
+
+ {
+ cdrs := []*CDR{}
+
+ cgrEvents := CDRsToCGREvents(cdrs)
+
+ if len(cgrEvents) != 0 {
+ t.Errorf("CDRsToCGREvents() returned %d events, expected 0", len(cgrEvents))
+ }
+ }
+}