diff --git a/engine/cdr_test.go b/engine/cdr_test.go index 564d3f5e1..de876ac61 100644 --- a/engine/cdr_test.go +++ b/engine/cdr_test.go @@ -1053,3 +1053,130 @@ func TestCompressedCDR(t *testing.T) { t.Errorf("expected %v,\nreceived %v\n", utils.ToJSON(cdr), utils.ToJSON(cdrs[0])) } } + +func TestCDRCacheClone(t *testing.T) { + cc := &CallCost{} + + tests := []struct { + name string + input *CDR + }{ + { + name: "nil CDR", + input: nil, + }, + { + name: "empty CDR", + input: &CDR{}, + }, + { + name: "fully populated CDR", + input: &CDR{ + CGRID: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), + OrderID: 123, + ToR: utils.MetaVoice, + OriginID: "dsafdsaf", + OriginHost: "192.168.1.1", + Source: utils.UnitTest, + RequestType: utils.MetaRated, + Tenant: "cgrates.org", + Category: "call", + Account: "1002", + Subject: "1001", + Destination: "+4986517174963", + SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), + AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), + RunID: utils.MetaDefault, + Usage: 10 * time.Second, + ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, + Cost: 1.01, + CostDetails: NewEventCostFromCallCost(cc, "TestCDRTestCDRAsMapStringIface2", utils.MetaDefault), + }, + }, + { + name: "CDR with nil maps", + input: &CDR{ + CGRID: "cgrid1", + OrderID: 456, + ToR: utils.MetaVoice, + OriginID: "origin1", + ExtraFields: nil, + CostDetails: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cloneAny := tt.input.CacheClone() + + if tt.input == nil { + if cloneAny != nil { + clone, ok := cloneAny.(*CDR) + if !ok { + t.Fatalf("CacheClone returned %T, expected *CDR", cloneAny) + } + if clone != nil { + t.Errorf("Expected nil clone for nil CDR, got %v", clone) + } + } + return + } + + if cloneAny == nil { + t.Fatal("CacheClone returned nil for non-nil CDR") + } + + clone, ok := cloneAny.(*CDR) + if !ok { + t.Fatalf("CacheClone returned %T, expected *CDR", cloneAny) + } + + if clone == tt.input { + t.Error("Clone should be a different instance") + } + + if !reflect.DeepEqual(clone, tt.input) { + t.Errorf("Clone doesn't match original value.\nGot: %+v\nExpected: %+v", clone, tt.input) + } + + if tt.input.ExtraFields != nil { + originalField := "" + var fieldKey string + + for k, v := range tt.input.ExtraFields { + fieldKey = k + originalField = v + break + } + + if fieldKey != "" { + tt.input.ExtraFields[fieldKey] = "modified value" + + if clone.ExtraFields[fieldKey] != originalField { + t.Errorf("Modifying original ExtraFields should not affect clone. Expected %q, got %q", + originalField, clone.ExtraFields[fieldKey]) + } + } + } + + if tt.input.CGRID != "" { + originalCGRID := tt.input.CGRID + tt.input.CGRID = "modified cgrid" + + if clone.CGRID != originalCGRID { + t.Error("Modifying original CGRID should not affect clone") + } + } + + if tt.input.CostDetails != nil { + originalCostSource := tt.input.CostSource + tt.input.CostSource = "modified source" + + if clone.CostSource != originalCostSource { + t.Error("Modifying original CostSource should not affect clone") + } + } + }) + } +} diff --git a/engine/destinations_test.go b/engine/destinations_test.go index 108c92e18..e71e6d638 100644 --- a/engine/destinations_test.go +++ b/engine/destinations_test.go @@ -305,3 +305,175 @@ func TestDMSetTimingInvalidTime(t *testing.T) { t.Errorf("Expected error <%v>, received <%v>", expErr, err) } } + +func TestDestinationClone(t *testing.T) { + t.Run("nil destination", func(t *testing.T) { + var d *Destination + clone := d.Clone() + if clone != nil { + t.Errorf("Expected nil clone for nil destination, got %v", clone) + } + }) + + t.Run("empty destination", func(t *testing.T) { + d := &Destination{} + clone := d.Clone() + + if clone == nil { + t.Fatal("Clone should not be nil") + } + + if clone == d { + t.Error("Clone should be a different instance") + } + + if clone.Id != d.Id { + t.Errorf("Expected empty Id, got %q", clone.Id) + } + + if clone.Prefixes != nil { + t.Errorf("Expected nil Prefixes, got %v", clone.Prefixes) + } + }) + + t.Run("destination with data", func(t *testing.T) { + d := &Destination{ + Id: "ID1", + Prefixes: []string{"prefix1", "prefix2", "prefix3"}, + } + + clone := d.Clone() + + if clone == nil { + t.Fatal("Clone should not be nil") + } + + if clone == d { + t.Error("Clone should be a different instance") + } + + if clone.Id != d.Id { + t.Errorf("Expected Id %q, got %q", d.Id, clone.Id) + } + + if !reflect.DeepEqual(clone.Prefixes, d.Prefixes) { + t.Errorf("Expected Prefixes %v, got %v", d.Prefixes, clone.Prefixes) + } + + d.Id = "changed-id" + d.Prefixes[0] = "changed-prefix" + + if clone.Id == d.Id { + t.Error("Changing original Id should not affect clone") + } + + if clone.Prefixes[0] == d.Prefixes[0] { + t.Error("Changing original Prefixes should not affect clone") + } + }) + + t.Run("destination with empty prefixes", func(t *testing.T) { + d := &Destination{ + Id: "dest-456", + Prefixes: []string{}, + } + + clone := d.Clone() + + if clone == nil { + t.Fatal("Clone should not be nil") + } + + if clone.Prefixes == nil { + t.Error("Clone should have an empty slice, not nil") + } + + if len(clone.Prefixes) != 0 { + t.Errorf("Expected empty Prefixes slice, got %v", clone.Prefixes) + } + }) +} + +func TestDestinationCacheClone(t *testing.T) { + tests := []struct { + name string + input *Destination + }{ + { + name: "nil destination", + input: nil, + }, + { + name: "empty destination", + input: &Destination{}, + }, + { + name: "destination with data", + input: &Destination{ + Id: "ID1", + Prefixes: []string{"prefix1", "prefix2", "prefix3"}, + }, + }, + { + name: "destination with empty prefixes", + input: &Destination{ + Id: "dest-456", + Prefixes: []string{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cloneAny := tt.input.CacheClone() + + if tt.input == nil { + if cloneAny != nil { + clone, ok := cloneAny.(*Destination) + if !ok { + t.Fatalf("CacheClone returned %T, expected *Destination", cloneAny) + } + if clone != nil { + t.Errorf("Expected nil clone for nil destination, got %v", clone) + } + } + return + } + + if cloneAny == nil { + t.Fatal("CacheClone returned nil for non-nil destination") + } + + clone, ok := cloneAny.(*Destination) + if !ok { + t.Fatalf("CacheClone returned %T, expected *Destination", cloneAny) + } + + if clone == tt.input { + t.Error("Clone should be a different instance") + } + + if !reflect.DeepEqual(clone, tt.input) { + t.Errorf("Clone doesn't match original value.\nGot: %+v\nExpected: %+v", clone, tt.input) + } + + if tt.input.Id != "" { + originalId := tt.input.Id + tt.input.Id = "modified-id" + + if clone.Id != originalId { + t.Error("Modifying original Id should not affect clone") + } + } + + if tt.input.Prefixes != nil && len(tt.input.Prefixes) > 0 { + originalPrefix := tt.input.Prefixes[0] + tt.input.Prefixes[0] = "modified-prefix" + + if clone.Prefixes[0] != originalPrefix { + t.Error("Modifying original Prefixes should not affect clone") + } + } + }) + } +}