diff --git a/sessions/libsessions_test.go b/sessions/libsessions_test.go index ba0a69a07..5009faf9e 100644 --- a/sessions/libsessions_test.go +++ b/sessions/libsessions_test.go @@ -443,3 +443,29 @@ func TestGetMaxUsageFromRuns(t *testing.T) { }) } } +func TestApplyFlags(t *testing.T) { + flags := utils.FlagsWithParams{ + utils.MetaAccounts: utils.FlagParams{}, + utils.MetaResources: utils.FlagParams{"false": {""}}, + utils.MetaIPs: utils.FlagParams{"true": {""}}, + } + + opts := make(map[string]any) + ApplyFlags(utils.MetaAuthorize, flags, opts) + + if v, ok := opts[utils.MetaAccounts]; !ok || v != true { + t.Errorf("expected %s=true, got %v", utils.MetaAccounts, opts[utils.MetaAccounts]) + } + if v, ok := opts[utils.MetaResources]; !ok || v != false { + t.Errorf("expected %s=false, got %v", utils.MetaResources, opts[utils.MetaResources]) + } + if v, ok := opts[utils.MetaIPs]; !ok || v != true { + t.Errorf("expected %s=true, got %v", utils.MetaIPs, opts[utils.MetaIPs]) + } + + opts2 := make(map[string]any) + ApplyFlags("unknownType", flags, opts2) + if len(opts2) != 0 { + t.Errorf("expected no opts set for unknown reqType, got %+v", opts2) + } +} diff --git a/sessions/session_test.go b/sessions/session_test.go index bafbc472c..05ff35c8f 100644 --- a/sessions/session_test.go +++ b/sessions/session_test.go @@ -750,3 +750,143 @@ func TestNewSession(t *testing.T) { t.Errorf("Expected second SRuns to have runID 'run2', got %s", session.SRuns[1].CGREvent.APIOpts["runID"]) } } + +func TestSessionAsExternalSession(t *testing.T) { + tTime1 := time.Now() + tTime2 := time.Date(2020, time.April, 18, 23, 0, 0, 0, time.UTC) + + session := &Session{ + ID: "sess1", + SRuns: []*SRun{ + { + ID: "run1", + CGREvent: &utils.CGREvent{Tenant: "cgrates1.org", ID: "event1"}, + NextAutoDebit: &tTime1, + }, + { + ID: "run2", + CGREvent: &utils.CGREvent{Tenant: "cgrates2.org", ID: "event2"}, + NextAutoDebit: &tTime2, + }, + { + ID: "run3", + CGREvent: &utils.CGREvent{Tenant: "cgrates3.org", ID: "event3"}, + }, + }, + } + + tests := []struct { + name string + sRunIdx int + nodeID string + expectedRunID string + expectedTenant string + expectedDebit *time.Time + }{ + { + name: "First run with debit", + sRunIdx: 0, + nodeID: "nodeA", + expectedRunID: "run1", + expectedTenant: "cgrates1.org", + expectedDebit: &tTime1, + }, + { + name: "Second run with debit", + sRunIdx: 1, + nodeID: "nodeB", + expectedRunID: "run2", + expectedTenant: "cgrates2.org", + expectedDebit: &tTime2, + }, + { + name: "Third run without debit", + sRunIdx: 2, + nodeID: "nodeC", + expectedRunID: "run3", + expectedTenant: "cgrates3.org", + expectedDebit: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + aS := session.AsExternalSession(tt.sRunIdx, tt.nodeID) + + if aS.ID != session.ID { + t.Errorf("Expected ID %s, got %s", session.ID, aS.ID) + } + if aS.RunID != tt.expectedRunID { + t.Errorf("Expected RunID %s, got %s", tt.expectedRunID, aS.RunID) + } + if aS.CGREvent.Tenant != tt.expectedTenant { + t.Errorf("Expected Tenant %s, got %s", tt.expectedTenant, aS.CGREvent.Tenant) + } + if aS.NodeID != tt.nodeID { + t.Errorf("Expected NodeID %s, got %s", tt.nodeID, aS.NodeID) + } + if tt.expectedDebit != nil { + if aS.NextAutoDebit != *tt.expectedDebit { + t.Errorf("Expected NextAutoDebit %v, got %v", *tt.expectedDebit, aS.NextAutoDebit) + } + } else if aS.NextAutoDebit != (time.Time{}) { + t.Errorf("Expected NextAutoDebit to be zero, got %v", aS.NextAutoDebit) + } + }) + } +} + +func TestSessionUpdateSRuns(t *testing.T) { + sr1 := &SRun{ + ID: "run1", + CGREvent: &utils.CGREvent{ + Event: map[string]any{ + "Destination": "1001", + "Subject": "1002", + }, + }, + } + sr2 := &SRun{ + ID: "run2", + CGREvent: &utils.CGREvent{ + Event: map[string]any{ + "Destination": "1001", + "Subject": "2001", + }, + }, + } + + session := &Session{ + ID: "sess1", + SRuns: []*SRun{sr1, sr2}, + } + + updEv := engine.MapEvent{ + "Destination": "3001", + "Subject": "4001", + } + alterableFields := utils.StringSet{"Destination": {}} + + session.updateSRuns(updEv, alterableFields) + + for i, sr := range session.SRuns { + if sr.CGREvent.Event["Destination"] != "3001" { + t.Errorf("SRun[%d]: Expected Destination '3001', got %v", i, sr.CGREvent.Event["Destination"]) + } + expectedSubject := "1002" + if i == 1 { + expectedSubject = "2001" + } + if sr.CGREvent.Event["Subject"] != expectedSubject { + t.Errorf("SRun[%d]: Expected Subject '%s', got %v", i, expectedSubject, sr.CGREvent.Event["Subject"]) + } + } + + session2 := &Session{ + SRuns: []*SRun{sr1}, + } + session2.updateSRuns(updEv, utils.StringSet{}) + if session2.SRuns[0].CGREvent.Event["Destination"] != "3001" { + t.Errorf("Expected Destination to remain '3001' when alterableFields is empty, got %v", session2.SRuns[0].CGREvent.Event["Destination"]) + } +} diff --git a/utils/ips_test.go b/utils/ips_test.go index b0b324e2b..4a0126b27 100644 --- a/utils/ips_test.go +++ b/utils/ips_test.go @@ -2227,3 +2227,99 @@ func TestIPAllocationsComputeUnexported(t *testing.T) { }) } } + +func TestIPAllocationsClearAllocations(t *testing.T) { + addr1 := netip.MustParseAddr("192.168.1.10") + addr2 := netip.MustParseAddr("192.168.1.11") + + ip1 := netip.MustParseAddr("192.168.1.10") + ip2 := netip.MustParseAddr("192.168.1.11") + + alloc := &IPAllocations{ + Tenant: "cgrates.org", + ID: "profile1", + Allocations: map[string]*PoolAllocation{ + "a1": { + PoolID: "pool1", + Address: ip1, + Time: time.Now().Add(-2 * time.Minute), + }, + "a2": { + PoolID: "pool1", + Address: ip2, + Time: time.Now().Add(-1 * time.Minute), + }, + }, + TTLIndex: []string{"a1", "a2"}, + prfl: &IPProfile{ + Tenant: "cgrates.org", + ID: "profile1", + TTL: time.Minute, + Stored: true, + Pools: []*IPPool{ + { + ID: "pool1", + Type: "*ipv4", + Range: "192.168.1.0/24", + Strategy: "*ascending", + }, + }, + }, + poolRanges: map[string]netip.Prefix{ + "pool1": netip.MustParsePrefix("192.168.1.0/24"), + }, + poolAllocs: map[string]map[netip.Addr]string{ + "pool1": { + ip1: "a1", + ip2: "a2", + }, + }, + } + + alloc.Allocations["a1"] = &PoolAllocation{PoolID: "p1", Address: addr1, Time: time.Now()} + alloc.Allocations["a2"] = &PoolAllocation{PoolID: "p1", Address: addr2, Time: time.Now()} + alloc.TTLIndex = []string{"a1", "a2"} + alloc.poolAllocs["p1"] = map[netip.Addr]string{ + addr1: "a1", + addr2: "a2", + } + + err := alloc.ClearAllocations([]string{"doesNotExist"}) + if err == nil { + t.Errorf("expected error for missing allocation ID") + } + if len(alloc.Allocations) != 2 { + t.Errorf("expected allocations unchanged, got %d", len(alloc.Allocations)) + } + + err = alloc.ClearAllocations([]string{"a1"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if _, ok := alloc.Allocations["a1"]; ok { + t.Errorf("expected a1 to be cleared") + } + if _, ok := alloc.Allocations["a2"]; !ok { + t.Errorf("expected a2 to remain") + } + if _, ok := alloc.poolAllocs["p1"][addr1]; ok { + t.Errorf("expected addr1 to be removed from poolAllocs") + } + if len(alloc.TTLIndex) != 1 || alloc.TTLIndex[0] != "a2" { + t.Errorf("expected TTLIndex to contain only a2, got %v", alloc.TTLIndex) + } + + err = alloc.ClearAllocations(nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(alloc.Allocations) != 0 { + t.Errorf("expected all allocations cleared, got %d", len(alloc.Allocations)) + } + if len(alloc.poolAllocs["p1"]) != 0 { + t.Errorf("expected all poolAllocs cleared, got %d", len(alloc.poolAllocs["p1"])) + } + if len(alloc.TTLIndex) != 0 { + t.Errorf("expected TTLIndex cleared, got %v", alloc.TTLIndex) + } +} diff --git a/utils/librates_test.go b/utils/librates_test.go index 3a51ff3a9..619df8246 100644 --- a/utils/librates_test.go +++ b/utils/librates_test.go @@ -20,6 +20,7 @@ package utils import ( "reflect" + "strings" "testing" "time" @@ -2492,3 +2493,96 @@ func TestNewRateProfileFromMapDataDBMap(t *testing.T) { t.Errorf("Expected error <%v>, received <%v>", expErr, err) } } + +func TestRateSIncrementFieldAsInterface(t *testing.T) { + incStart := &Decimal{Big: decimal.New(123, -2)} + usage := &Decimal{Big: decimal.New(456, -2)} + + rI := &RateSIncrement{ + IncrementStart: incStart, + RateIntervalIndex: 5, + RateID: "rateID1", + CompressFactor: 2, + Usage: usage, + } + + tests := []struct { + name string + fldPath []string + expectedVal any + expectErr bool + expectedErr string + }{ + { + name: "IncrementStart returns Decimal", + fldPath: []string{IncrementStart}, + expectedVal: incStart, + expectErr: false, + }, + { + name: "RateIntervalIndex returns int", + fldPath: []string{RateIntervalIndex}, + expectedVal: 5, + expectErr: false, + }, + { + name: "RateID returns string", + fldPath: []string{RateID}, + expectedVal: "rateID1", + expectErr: false, + }, + { + name: "CompressFactor returns int64", + fldPath: []string{CompressFactor}, + expectedVal: int64(2), + expectErr: false, + }, + { + name: "Usage returns Decimal", + fldPath: []string{Usage}, + expectedVal: usage, + expectErr: false, + }, + { + name: "UnknownField returns error", + fldPath: []string{"UnknownField"}, + expectedVal: nil, + expectErr: true, + expectedErr: "unsupported field prefix", + }, + { + name: "Empty path returns error", + fldPath: []string{}, + expectedVal: nil, + expectErr: true, + expectedErr: "NOT_FOUND", + }, + { + name: "Too many path parts returns error", + fldPath: []string{"NFound", "NFound"}, + expectedVal: nil, + expectErr: true, + expectedErr: "NOT_FOUND", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + val, err := rI.FieldAsInterface(tt.fldPath) + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error for field path %v, got nil", tt.fldPath) + } else if tt.expectedErr != "" && !strings.Contains(strings.ToUpper(err.Error()), strings.ToUpper(tt.expectedErr)) { + t.Errorf("Expected error containing %q, got %v", tt.expectedErr, err) + } + } else { + if err != nil { + t.Errorf("Unexpected error for field path %v: %v", tt.fldPath, err) + } else if !reflect.DeepEqual(val, tt.expectedVal) { + t.Errorf("For field path %v, expected %v, got %v", tt.fldPath, tt.expectedVal, val) + } + } + }) + } +}