summaryrefslogtreecommitdiff
path: root/lib/container_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/container_test.go')
-rw-r--r--lib/container_test.go717
1 files changed, 717 insertions, 0 deletions
diff --git a/lib/container_test.go b/lib/container_test.go
new file mode 100644
index 00000000..fbc20a8f
--- /dev/null
+++ b/lib/container_test.go
@@ -0,0 +1,717 @@
+package lib
+
+import (
+ "testing"
+)
+
+func TestNewContainer(t *testing.T) {
+ c := NewContainer()
+ if c == nil {
+ t.Fatal("NewContainer() returned nil")
+ }
+ if c.Len() != 0 {
+ t.Errorf("NewContainer().Len() = %d, want 0", c.Len())
+ }
+}
+
+func TestContainer_GetEntry(t *testing.T) {
+ c := NewContainer()
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ c.Add(entry)
+
+ tests := []struct {
+ name string
+ entryName string
+ wantFound bool
+ }{
+ {
+ name: "existing entry",
+ entryName: "test",
+ wantFound: true,
+ },
+ {
+ name: "existing entry uppercase",
+ entryName: "TEST",
+ wantFound: true,
+ },
+ {
+ name: "existing entry with spaces",
+ entryName: " test ",
+ wantFound: true,
+ },
+ {
+ name: "non-existing entry",
+ entryName: "nonexistent",
+ wantFound: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, found := c.GetEntry(tt.entryName)
+ if found != tt.wantFound {
+ t.Errorf("Container.GetEntry() found = %v, wantFound %v", found, tt.wantFound)
+ }
+ if tt.wantFound && got == nil {
+ t.Error("Container.GetEntry() returned nil entry")
+ }
+ })
+ }
+}
+
+func TestContainer_Add(t *testing.T) {
+ tests := []struct {
+ name string
+ entries []*Entry
+ opts []IgnoreIPOption
+ wantLen int
+ }{
+ {
+ name: "add single entry",
+ entries: []*Entry{
+ func() *Entry {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ return e
+ }(),
+ },
+ opts: nil,
+ wantLen: 1,
+ },
+ {
+ name: "add multiple entries",
+ entries: []*Entry{
+ func() *Entry {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ return e
+ }(),
+ func() *Entry {
+ e := NewEntry("test2")
+ e.AddPrefix("10.0.0.0/8")
+ return e
+ }(),
+ },
+ opts: nil,
+ wantLen: 2,
+ },
+ {
+ name: "add duplicate entry",
+ entries: []*Entry{
+ func() *Entry {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ return e
+ }(),
+ func() *Entry {
+ e := NewEntry("test1")
+ e.AddPrefix("10.0.0.0/8")
+ return e
+ }(),
+ },
+ opts: nil,
+ wantLen: 1, // Should merge into one
+ },
+ {
+ name: "add with ignore IPv4",
+ entries: []*Entry{
+ func() *Entry {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ e.AddPrefix("2001:db8::/32")
+ return e
+ }(),
+ },
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ wantLen: 1,
+ },
+ {
+ name: "add with ignore IPv6",
+ entries: []*Entry{
+ func() *Entry {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ e.AddPrefix("2001:db8::/32")
+ return e
+ }(),
+ },
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ wantLen: 1,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := NewContainer()
+ for _, entry := range tt.entries {
+ if err := c.Add(entry, tt.opts...); err != nil {
+ t.Errorf("Container.Add() error = %v", err)
+ }
+ }
+ if c.Len() != tt.wantLen {
+ t.Errorf("Container.Len() = %d, want %d", c.Len(), tt.wantLen)
+ }
+ })
+ }
+}
+
+func TestContainer_Remove(t *testing.T) {
+ tests := []struct {
+ name string
+ setupFn func(Container)
+ removeName string
+ removeCase CaseRemove
+ opts []IgnoreIPOption
+ wantErr bool
+ checkFn func(*testing.T, Container)
+ }{
+ {
+ name: "remove non-existent entry",
+ setupFn: func(c Container) {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ c.Add(e)
+ },
+ removeName: "nonexistent",
+ removeCase: CaseRemoveEntry,
+ wantErr: true,
+ },
+ {
+ name: "remove entry completely",
+ setupFn: func(c Container) {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ c.Add(e)
+ },
+ removeName: "test1",
+ removeCase: CaseRemoveEntry,
+ wantErr: false,
+ checkFn: func(t *testing.T, c Container) {
+ if c.Len() != 0 {
+ t.Errorf("Container.Len() = %d, want 0", c.Len())
+ }
+ },
+ },
+ {
+ name: "remove prefix",
+ setupFn: func(c Container) {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ e.AddPrefix("10.0.0.0/8")
+ c.Add(e)
+ },
+ removeName: "test1",
+ removeCase: CaseRemovePrefix,
+ wantErr: false,
+ checkFn: func(t *testing.T, c Container) {
+ if c.Len() != 1 {
+ t.Errorf("Container.Len() = %d, want 1", c.Len())
+ }
+ },
+ },
+ {
+ name: "remove with ignore IPv4",
+ setupFn: func(c Container) {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ e.AddPrefix("2001:db8::/32")
+ c.Add(e)
+ },
+ removeName: "test1",
+ removeCase: CaseRemoveEntry,
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ wantErr: false,
+ checkFn: func(t *testing.T, c Container) {
+ if c.Len() != 1 {
+ t.Errorf("Container.Len() = %d, want 1 (IPv4 should be removed)", c.Len())
+ }
+ },
+ },
+ {
+ name: "remove with ignore IPv6",
+ setupFn: func(c Container) {
+ e := NewEntry("test1")
+ e.AddPrefix("192.168.1.0/24")
+ e.AddPrefix("2001:db8::/32")
+ c.Add(e)
+ },
+ removeName: "test1",
+ removeCase: CaseRemoveEntry,
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ wantErr: false,
+ checkFn: func(t *testing.T, c Container) {
+ if c.Len() != 1 {
+ t.Errorf("Container.Len() = %d, want 1 (IPv6 should be removed)", c.Len())
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := NewContainer()
+ if tt.setupFn != nil {
+ tt.setupFn(c)
+ }
+
+ removeEntry := NewEntry(tt.removeName)
+ removeEntry.AddPrefix("192.168.1.0/24")
+
+ err := c.Remove(removeEntry, tt.removeCase, tt.opts...)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Container.Remove() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if tt.checkFn != nil {
+ tt.checkFn(t, c)
+ }
+ })
+ }
+}
+
+func TestContainer_Loop(t *testing.T) {
+ c := NewContainer()
+
+ // Add some entries
+ for i := 1; i <= 3; i++ {
+ e := NewEntry("test" + string(rune('0'+i)))
+ e.AddPrefix("192.168.1.0/24")
+ c.Add(e)
+ }
+
+ count := 0
+ for entry := range c.Loop() {
+ if entry == nil {
+ t.Error("Container.Loop() returned nil entry")
+ }
+ count++
+ }
+
+ if count != 3 {
+ t.Errorf("Container.Loop() iterated %d times, want 3", count)
+ }
+}
+
+func TestContainer_Lookup(t *testing.T) {
+ c := NewContainer()
+
+ // Setup test data
+ e1 := NewEntry("CN")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("10.0.0.0/8")
+ e1.AddPrefix("2001:db8:1::/48") // Add IPv6 for CN
+ c.Add(e1)
+
+ e2 := NewEntry("US")
+ e2.AddPrefix("172.16.0.0/12")
+ e2.AddPrefix("2001:db8::/32")
+ c.Add(e2)
+
+ tests := []struct {
+ name string
+ ipOrCidr string
+ searchList []string
+ wantFound bool
+ wantErr bool
+ checkFn func(*testing.T, []string)
+ }{
+ {
+ name: "lookup IPv4 address",
+ ipOrCidr: "192.168.1.100",
+ searchList: nil,
+ wantFound: true,
+ wantErr: false,
+ checkFn: func(t *testing.T, results []string) {
+ if len(results) == 0 {
+ t.Error("Expected at least one result")
+ }
+ },
+ },
+ {
+ name: "lookup IPv4 CIDR",
+ ipOrCidr: "192.168.1.0/24",
+ searchList: nil,
+ wantFound: true,
+ wantErr: false,
+ },
+ {
+ name: "lookup IPv6 address",
+ ipOrCidr: "2001:db8::1",
+ searchList: nil,
+ wantFound: true,
+ wantErr: false,
+ },
+ {
+ name: "lookup IPv6 CIDR",
+ ipOrCidr: "2001:db8::/32",
+ searchList: nil,
+ wantFound: true,
+ wantErr: false,
+ },
+ {
+ name: "lookup with search list",
+ ipOrCidr: "192.168.1.100",
+ searchList: []string{"CN"},
+ wantFound: true,
+ wantErr: false,
+ },
+ {
+ name: "lookup not in search list",
+ ipOrCidr: "172.16.1.1",
+ searchList: []string{"CN"},
+ wantFound: false,
+ wantErr: false,
+ },
+ {
+ name: "lookup non-existent IP",
+ ipOrCidr: "1.1.1.1",
+ searchList: nil,
+ wantFound: false,
+ wantErr: false,
+ },
+ {
+ name: "invalid IP",
+ ipOrCidr: "invalid",
+ searchList: nil,
+ wantFound: false,
+ wantErr: true,
+ },
+ {
+ name: "invalid CIDR",
+ ipOrCidr: "invalid/24",
+ searchList: nil,
+ wantFound: false,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ results, found, err := c.Lookup(tt.ipOrCidr, tt.searchList...)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Container.Lookup() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if found != tt.wantFound {
+ t.Errorf("Container.Lookup() found = %v, wantFound %v", found, tt.wantFound)
+ }
+ if tt.checkFn != nil {
+ tt.checkFn(t, results)
+ }
+ })
+ }
+}
+
+func TestContainer_RemoveWithPrefixCase(t *testing.T) {
+ c := NewContainer()
+
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("10.0.0.0/8")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ // Create entry with prefixes to remove
+ removeEntry := NewEntry("test")
+ removeEntry.AddPrefix("192.168.1.0/24")
+
+ // Remove with CaseRemovePrefix and ignore IPv4
+ err := c.Remove(removeEntry, CaseRemovePrefix, IgnoreIPv4)
+ if err != nil {
+ t.Errorf("Container.Remove() error = %v", err)
+ }
+}
+
+func TestContainer_InvalidRemoveCase(t *testing.T) {
+ c := NewContainer()
+
+ e := NewEntry("test")
+ e.AddPrefix("192.168.1.0/24")
+ c.Add(e)
+
+ // Try to remove with invalid case
+ err := c.Remove(e, CaseRemove(99))
+ if err == nil {
+ t.Error("Container.Remove() should return error for invalid case")
+ }
+}
+
+func TestContainer_AddWithMerging(t *testing.T) {
+ c := NewContainer()
+
+ // Add first entry
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ // Add second entry with same name - should merge
+ e2 := NewEntry("test")
+ e2.AddPrefix("10.0.0.0/8")
+ e2.AddPrefix("2001:db9::/32")
+ c.Add(e2)
+
+ if c.Len() != 1 {
+ t.Errorf("Container.Len() = %d, want 1 (entries should merge)", c.Len())
+ }
+
+ // Verify merged entry has prefixes from both
+ entry, found := c.GetEntry("test")
+ if !found {
+ t.Fatal("Entry not found after merge")
+ }
+
+ prefixes, err := entry.MarshalPrefix()
+ if err != nil {
+ t.Errorf("MarshalPrefix() error = %v", err)
+ }
+ // After merging, we should have at least some prefixes
+ if len(prefixes) == 0 {
+ t.Error("Merged entry has no prefixes")
+ }
+}
+
+func TestContainer_AddWithIgnoreOptions(t *testing.T) {
+ tests := []struct {
+ name string
+ opts []IgnoreIPOption
+ checkFn func(*testing.T, Container)
+ }{
+ {
+ name: "add new entry with ignore IPv4",
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ checkFn: func(t *testing.T, c Container) {
+ entry, _ := c.GetEntry("test2")
+ // When adding a new entry with ignore IPv4, IPv4 should not be added
+ _, err := entry.GetIPv4Set()
+ if err == nil {
+ t.Error("Expected no IPv4 set for new entry when ignored")
+ }
+ },
+ },
+ {
+ name: "add new entry with ignore IPv6",
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ checkFn: func(t *testing.T, c Container) {
+ entry, _ := c.GetEntry("test2")
+ // When adding a new entry with ignore IPv6, IPv6 should not be added
+ _, err := entry.GetIPv6Set()
+ if err == nil {
+ t.Error("Expected no IPv6 set for new entry when ignored")
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := NewContainer()
+
+ e := NewEntry("test2")
+ e.AddPrefix("192.168.1.0/24")
+ e.AddPrefix("2001:db8::/32")
+ c.Add(e, tt.opts...)
+
+ if tt.checkFn != nil {
+ tt.checkFn(t, c)
+ }
+ })
+ }
+}
+
+func TestContainer_RemoveWithPrefixAndIgnoreOptions(t *testing.T) {
+ tests := []struct {
+ name string
+ opts []IgnoreIPOption
+ checkFn func(*testing.T, Container)
+ }{
+ {
+ name: "remove prefix with ignore IPv4",
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ checkFn: func(t *testing.T, c Container) {
+ entry, _ := c.GetEntry("test")
+ // IPv6 should be removed, IPv4 should remain
+ _, err := entry.GetIPv4Set()
+ if err != nil {
+ t.Error("IPv4 should still exist")
+ }
+ },
+ },
+ {
+ name: "remove prefix with ignore IPv6",
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ checkFn: func(t *testing.T, c Container) {
+ entry, _ := c.GetEntry("test")
+ // IPv4 should be removed, IPv6 should remain
+ _, err := entry.GetIPv6Set()
+ if err != nil {
+ t.Error("IPv6 should still exist")
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := NewContainer()
+
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ removeEntry := NewEntry("test")
+ removeEntry.AddPrefix("192.168.1.0/24")
+ removeEntry.AddPrefix("2001:db8::/32")
+
+ err := c.Remove(removeEntry, CaseRemovePrefix, tt.opts...)
+ if err != nil {
+ t.Errorf("Container.Remove() error = %v", err)
+ }
+
+ if tt.checkFn != nil {
+ tt.checkFn(t, c)
+ }
+ })
+ }
+}
+
+func TestContainer_GetEntryInvalidContainer(t *testing.T) {
+ // Create an invalid container (nil map)
+ c := &container{entries: nil}
+
+ _, found := c.GetEntry("test")
+ if found {
+ t.Error("GetEntry() should return false for invalid container")
+ }
+}
+
+func TestContainer_LenInvalidContainer(t *testing.T) {
+ // Create an invalid container (nil map)
+ c := &container{entries: nil}
+
+ if c.Len() != 0 {
+ t.Errorf("Len() = %d, want 0 for invalid container", c.Len())
+ }
+}
+
+func TestContainer_AddMergingEdgeCases(t *testing.T) {
+ c := NewContainer()
+
+ // Add entry with both IPv4 and IPv6
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ // Merge with entry that has only IPv4 - should merge both
+ e2 := NewEntry("test")
+ e2.AddPrefix("10.0.0.0/8")
+ c.Add(e2)
+
+ entry, _ := c.GetEntry("test")
+ // Should have both IPv4 and IPv6
+ _, err4 := entry.GetIPv4Set()
+ _, err6 := entry.GetIPv6Set()
+ if err4 != nil || err6 != nil {
+ t.Error("Both IPv4 and IPv6 should exist after merge")
+ }
+
+ // Now merge with ignore options on existing entry
+ e3 := NewEntry("test")
+ e3.AddPrefix("172.16.0.0/12")
+ e3.AddPrefix("2001:db9::/32")
+ c.Add(e3, IgnoreIPv4)
+
+ // IPv4 should still exist, IPv6 should be updated
+ entry, _ = c.GetEntry("test")
+ _, err := entry.GetIPv4Set()
+ if err != nil {
+ t.Error("IPv4 should still exist when merging with IgnoreIPv4")
+ }
+}
+
+func TestContainer_AddMergingWithExistingBuilders(t *testing.T) {
+ c := NewContainer()
+
+ // Test merge when val already has builders (lines 102-109)
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ // Merge another entry (default case, both builders exist)
+ e2 := NewEntry("test")
+ e2.AddPrefix("10.0.0.0/8")
+ e2.AddPrefix("2001:db9::/32")
+ c.Add(e2) // This should hit lines 102-109
+
+ entry, _ := c.GetEntry("test")
+ prefixes, _ := entry.MarshalPrefix()
+ if len(prefixes) == 0 {
+ t.Error("Should have prefixes after merge")
+ }
+}
+
+func TestContainer_AddMergingIgnoreIPv6WithExistingBuilder(t *testing.T) {
+ c := NewContainer()
+
+ // Add entry with IPv4 and IPv6
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ // Merge with IgnoreIPv6 when val already has IPv4 builder (lines 97-100)
+ e2 := NewEntry("test")
+ e2.AddPrefix("10.0.0.0/8")
+ e2.AddPrefix("2001:db9::/32")
+ c.Add(e2, IgnoreIPv6)
+
+ entry, _ := c.GetEntry("test")
+ _, err := entry.GetIPv4Set()
+ if err != nil {
+ t.Error("IPv4 should exist after merge with IgnoreIPv6")
+ }
+}
+
+func TestContainer_AddMergingIgnoreIPv4WithExistingBuilder(t *testing.T) {
+ c := NewContainer()
+
+ // Add entry with IPv4 and IPv6
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ e1.AddPrefix("2001:db8::/32")
+ c.Add(e1)
+
+ // Merge with IgnoreIPv4 when val already has IPv6 builder (lines 92-95)
+ e2 := NewEntry("test")
+ e2.AddPrefix("10.0.0.0/8")
+ e2.AddPrefix("2001:db9::/32")
+ c.Add(e2, IgnoreIPv4)
+
+ entry, _ := c.GetEntry("test")
+ _, err := entry.GetIPv6Set()
+ if err != nil {
+ t.Error("IPv6 should exist after merge with IgnoreIPv4")
+ }
+}
+
+func TestContainer_RemoveNilBuilders(t *testing.T) {
+ c := NewContainer()
+
+ // Add entry
+ e1 := NewEntry("test")
+ e1.AddPrefix("192.168.1.0/24")
+ c.Add(e1)
+
+ // Try to remove prefixes when entry has no IPv6 builder
+ removeEntry := NewEntry("test")
+ removeEntry.AddPrefix("2001:db8::/32") // IPv6
+
+ err := c.Remove(removeEntry, CaseRemovePrefix)
+ if err != nil {
+ t.Errorf("Remove should handle missing builder gracefully, got error: %v", err)
+ }
+}