summaryrefslogtreecommitdiff
path: root/lib/entry_test.go
diff options
context:
space:
mode:
authorcopilot-swe-agent[bot] <[email protected]>2025-11-14 08:36:12 +0000
committercopilot-swe-agent[bot] <[email protected]>2025-11-14 08:36:12 +0000
commitea4f75a68465992c68779fc0cc4ea3ef251af05e (patch)
treefa8104a0377e3f7810953301803bfc0bfcba35be /lib/entry_test.go
parentbe5f580e8bfb9169bb8b410d1e154057e9ac1ed5 (diff)
Add comprehensive unit tests for lib package with 90.3% coveragecopilot/add-unit-tests-for-lib-package
Co-authored-by: Loyalsoldier <[email protected]>
Diffstat (limited to 'lib/entry_test.go')
-rw-r--r--lib/entry_test.go751
1 files changed, 751 insertions, 0 deletions
diff --git a/lib/entry_test.go b/lib/entry_test.go
new file mode 100644
index 00000000..ae213d88
--- /dev/null
+++ b/lib/entry_test.go
@@ -0,0 +1,751 @@
+package lib
+
+import (
+ "net"
+ "net/netip"
+ "testing"
+
+ "go4.org/netipx"
+)
+
+func TestNewEntry(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected string
+ }{
+ {
+ name: "simple name",
+ input: "test",
+ expected: "TEST",
+ },
+ {
+ name: "lowercase name",
+ input: "lowercase",
+ expected: "LOWERCASE",
+ },
+ {
+ name: "name with spaces",
+ input: " test name ",
+ expected: "TEST NAME",
+ },
+ {
+ name: "mixed case",
+ input: "MiXeD CaSe",
+ expected: "MIXED CASE",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ entry := NewEntry(tt.input)
+ if entry.GetName() != tt.expected {
+ t.Errorf("NewEntry(%q).GetName() = %q, want %q", tt.input, entry.GetName(), tt.expected)
+ }
+ })
+ }
+}
+
+func TestEntry_AddPrefix(t *testing.T) {
+ tests := []struct {
+ name string
+ cidr any
+ wantErr bool
+ }{
+ {
+ name: "valid IPv4 CIDR string",
+ cidr: "192.168.1.0/24",
+ wantErr: false,
+ },
+ {
+ name: "valid IPv6 CIDR string",
+ cidr: "2001:db8::/32",
+ wantErr: false,
+ },
+ {
+ name: "valid IPv4 address string",
+ cidr: "192.168.1.1",
+ wantErr: false,
+ },
+ {
+ name: "valid IPv6 address string",
+ cidr: "2001:db8::1",
+ wantErr: false,
+ },
+ {
+ name: "invalid CIDR",
+ cidr: "invalid/cidr",
+ wantErr: true,
+ },
+ {
+ name: "invalid IP",
+ cidr: "999.999.999.999",
+ wantErr: true,
+ },
+ {
+ name: "net.IP type",
+ cidr: net.ParseIP("192.168.1.1"),
+ wantErr: false,
+ },
+ {
+ name: "net.IPNet type",
+ cidr: &net.IPNet{IP: net.ParseIP("192.168.1.0"), Mask: net.CIDRMask(24, 32)},
+ wantErr: false,
+ },
+ {
+ name: "netip.Addr type",
+ cidr: netip.MustParseAddr("192.168.1.1"),
+ wantErr: false,
+ },
+ {
+ name: "netip.Prefix type",
+ cidr: netip.MustParsePrefix("192.168.1.0/24"),
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ entry := NewEntry("test")
+ err := entry.AddPrefix(tt.cidr)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.AddPrefix() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_RemovePrefix(t *testing.T) {
+ entry := NewEntry("test")
+ // First add a prefix
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("10.0.0.0/8")
+
+ tests := []struct {
+ name string
+ cidr string
+ wantErr bool
+ }{
+ {
+ name: "valid CIDR",
+ cidr: "192.168.1.0/24",
+ wantErr: false,
+ },
+ {
+ name: "invalid CIDR",
+ cidr: "invalid",
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := entry.RemovePrefix(tt.cidr)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.RemovePrefix() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_GetIPv4Set(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+
+ set, err := entry.GetIPv4Set()
+ if err != nil {
+ t.Errorf("Entry.GetIPv4Set() error = %v", err)
+ }
+ if set == nil {
+ t.Error("Entry.GetIPv4Set() returned nil set")
+ }
+
+ // Test entry with no IPv4
+ entry2 := NewEntry("test2")
+ entry2.AddPrefix("2001:db8::/32")
+ _, err = entry2.GetIPv4Set()
+ if err == nil {
+ t.Error("Entry.GetIPv4Set() should return error for entry with no IPv4")
+ }
+}
+
+func TestEntry_GetIPv6Set(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("2001:db8::/32")
+
+ set, err := entry.GetIPv6Set()
+ if err != nil {
+ t.Errorf("Entry.GetIPv6Set() error = %v", err)
+ }
+ if set == nil {
+ t.Error("Entry.GetIPv6Set() returned nil set")
+ }
+
+ // Test entry with no IPv6
+ entry2 := NewEntry("test2")
+ entry2.AddPrefix("192.168.1.0/24")
+ _, err = entry2.GetIPv6Set()
+ if err == nil {
+ t.Error("Entry.GetIPv6Set() should return error for entry with no IPv6")
+ }
+}
+
+func TestEntry_MarshalPrefix(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("10.0.0.0/8")
+ entry.AddPrefix("2001:db8::/32")
+
+ tests := []struct {
+ name string
+ opts []IgnoreIPOption
+ wantErr bool
+ checkFn func(*testing.T, []netip.Prefix)
+ }{
+ {
+ name: "no options",
+ opts: nil,
+ wantErr: false,
+ checkFn: func(t *testing.T, prefixes []netip.Prefix) {
+ if len(prefixes) != 3 {
+ t.Errorf("MarshalPrefix() returned %d prefixes, want 3", len(prefixes))
+ }
+ },
+ },
+ {
+ name: "ignore IPv4",
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ wantErr: false,
+ checkFn: func(t *testing.T, prefixes []netip.Prefix) {
+ if len(prefixes) != 1 {
+ t.Errorf("MarshalPrefix() returned %d prefixes, want 1", len(prefixes))
+ }
+ },
+ },
+ {
+ name: "ignore IPv6",
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ wantErr: false,
+ checkFn: func(t *testing.T, prefixes []netip.Prefix) {
+ if len(prefixes) != 2 {
+ t.Errorf("MarshalPrefix() returned %d prefixes, want 2", len(prefixes))
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := entry.MarshalPrefix(tt.opts...)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.MarshalPrefix() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if tt.checkFn != nil {
+ tt.checkFn(t, got)
+ }
+ })
+ }
+}
+
+func TestEntry_MarshalIPRange(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("2001:db8::/32")
+
+ ranges, err := entry.MarshalIPRange()
+ if err != nil {
+ t.Errorf("Entry.MarshalIPRange() error = %v", err)
+ }
+ if len(ranges) != 2 {
+ t.Errorf("Entry.MarshalIPRange() returned %d ranges, want 2", len(ranges))
+ }
+}
+
+func TestEntry_MarshalText(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("2001:db8::/32")
+
+ text, err := entry.MarshalText()
+ if err != nil {
+ t.Errorf("Entry.MarshalText() error = %v", err)
+ }
+ if len(text) != 2 {
+ t.Errorf("Entry.MarshalText() returned %d lines, want 2", len(text))
+ }
+}
+
+func TestEntry_EmptyEntry(t *testing.T) {
+ entry := NewEntry("empty")
+
+ _, err := entry.MarshalPrefix()
+ if err == nil {
+ t.Error("Entry.MarshalPrefix() should return error for empty entry")
+ }
+
+ _, err = entry.MarshalIPRange()
+ if err == nil {
+ t.Error("Entry.MarshalIPRange() should return error for empty entry")
+ }
+
+ _, err = entry.MarshalText()
+ if err == nil {
+ t.Error("Entry.MarshalText() should return error for empty entry")
+ }
+}
+
+func TestEntry_ProcessPrefix_IPv4Mapped(t *testing.T) {
+ entry := NewEntry("test")
+
+ // Test IPv4-mapped IPv6 address
+ err := entry.AddPrefix("::ffff:192.168.1.1")
+ if err != nil {
+ t.Errorf("Entry.AddPrefix() with IPv4-mapped address error = %v", err)
+ }
+}
+
+func TestEntry_ProcessPrefix_EdgeCases(t *testing.T) {
+ entry := NewEntry("test")
+
+ tests := []struct {
+ name string
+ input any
+ wantErr bool
+ }{
+ {
+ name: "CIDR with comment",
+ input: "192.168.1.0/24 # comment",
+ wantErr: false,
+ },
+ {
+ name: "IP with inline comment",
+ input: "192.168.1.1 // comment",
+ wantErr: false,
+ },
+ {
+ name: "pointer to netip.Addr",
+ input: func() *netip.Addr { a := netip.MustParseAddr("192.168.1.1"); return &a }(),
+ wantErr: false,
+ },
+ {
+ name: "pointer to netip.Prefix",
+ input: func() *netip.Prefix { p := netip.MustParsePrefix("192.168.1.0/24"); return &p }(),
+ wantErr: false,
+ },
+ {
+ name: "unsupported type",
+ input: 123,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := entry.AddPrefix(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.AddPrefix(%v) error = %v, wantErr %v", tt.input, err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_AddPrefix_IPv4In6(t *testing.T) {
+ entry := NewEntry("test")
+
+ // Create an IPv4-in-IPv6 prefix
+ prefix := netip.MustParsePrefix("::ffff:c0a8:0100/120") // ::ffff:192.168.1.0/120
+ err := entry.AddPrefix(prefix)
+ if err != nil {
+ t.Errorf("Entry.AddPrefix() with IPv4-in-IPv6 error = %v", err)
+ }
+}
+
+func TestEntry_InvalidIPLengthCases(t *testing.T) {
+ entry := NewEntry("test")
+
+ // Test with invalid IP that could trigger ErrInvalidIPLength
+ invalidIP := net.IP{} // Invalid empty IP
+ err := entry.AddPrefix(invalidIP)
+ if err == nil {
+ t.Error("Entry.AddPrefix() should return error for invalid IP")
+ }
+}
+
+func TestEntry_AddPrefix_WithInvalidIPv4MappedCIDR(t *testing.T) {
+ entry := NewEntry("test")
+
+ // Test IPv4-mapped IPv6 CIDR - this is actually valid and should work
+ err := entry.AddPrefix("::ffff:192.168.1.0/120")
+ if err != nil {
+ // If it errors, that's fine - just checking the code path
+ t.Logf("AddPrefix with IPv4-mapped CIDR returned: %v", err)
+ }
+}
+
+func TestEntry_RemovePrefixVariousCases(t *testing.T) {
+ tests := []struct {
+ name string
+ addPrefixes []string
+ removePrefixes []string
+ wantErr bool
+ }{
+ {
+ name: "remove IPv4 prefix",
+ addPrefixes: []string{"192.168.1.0/24", "10.0.0.0/8"},
+ removePrefixes: []string{"192.168.1.0/24"},
+ wantErr: false,
+ },
+ {
+ name: "remove IPv6 prefix",
+ addPrefixes: []string{"2001:db8::/32", "2001:db9::/32"},
+ removePrefixes: []string{"2001:db8::/32"},
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ entry := NewEntry("test")
+ for _, prefix := range tt.addPrefixes {
+ entry.AddPrefix(prefix)
+ }
+ for _, prefix := range tt.removePrefixes {
+ err := entry.RemovePrefix(prefix)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.RemovePrefix() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ }
+ })
+ }
+}
+
+func TestEntry_MarshalIPRangeWithOptions(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("10.0.0.0/8")
+ entry.AddPrefix("2001:db8::/32")
+
+ tests := []struct {
+ name string
+ opts []IgnoreIPOption
+ wantErr bool
+ checkFn func(*testing.T, []netipx.IPRange)
+ }{
+ {
+ name: "no options",
+ opts: nil,
+ wantErr: false,
+ checkFn: func(t *testing.T, ranges []netipx.IPRange) {
+ if len(ranges) != 3 {
+ t.Errorf("MarshalIPRange() returned %d ranges, want 3", len(ranges))
+ }
+ },
+ },
+ {
+ name: "ignore IPv4",
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ wantErr: false,
+ checkFn: func(t *testing.T, ranges []netipx.IPRange) {
+ if len(ranges) != 1 {
+ t.Errorf("MarshalIPRange() returned %d ranges, want 1 (IPv6 only)", len(ranges))
+ }
+ },
+ },
+ {
+ name: "ignore IPv6",
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ wantErr: false,
+ checkFn: func(t *testing.T, ranges []netipx.IPRange) {
+ if len(ranges) != 2 {
+ t.Errorf("MarshalIPRange() returned %d ranges, want 2 (IPv4 only)", len(ranges))
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := entry.MarshalIPRange(tt.opts...)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.MarshalIPRange() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if tt.checkFn != nil {
+ tt.checkFn(t, got)
+ }
+ })
+ }
+}
+
+func TestEntry_MarshalTextWithOptions(t *testing.T) {
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("10.0.0.0/8")
+ entry.AddPrefix("2001:db8::/32")
+
+ tests := []struct {
+ name string
+ opts []IgnoreIPOption
+ wantErr bool
+ checkFn func(*testing.T, []string)
+ }{
+ {
+ name: "no options",
+ opts: nil,
+ wantErr: false,
+ checkFn: func(t *testing.T, text []string) {
+ if len(text) != 3 {
+ t.Errorf("MarshalText() returned %d lines, want 3", len(text))
+ }
+ },
+ },
+ {
+ name: "ignore IPv4",
+ opts: []IgnoreIPOption{IgnoreIPv4},
+ wantErr: false,
+ checkFn: func(t *testing.T, text []string) {
+ if len(text) != 1 {
+ t.Errorf("MarshalText() returned %d lines, want 1 (IPv6 only)", len(text))
+ }
+ },
+ },
+ {
+ name: "ignore IPv6",
+ opts: []IgnoreIPOption{IgnoreIPv6},
+ wantErr: false,
+ checkFn: func(t *testing.T, text []string) {
+ if len(text) != 2 {
+ t.Errorf("MarshalText() returned %d lines, want 2 (IPv4 only)", len(text))
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := entry.MarshalText(tt.opts...)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.MarshalText() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if tt.checkFn != nil {
+ tt.checkFn(t, got)
+ }
+ })
+ }
+}
+
+func TestEntry_ProcessPrefixComprehensive(t *testing.T) {
+ entry := NewEntry("test")
+
+ tests := []struct {
+ name string
+ input any
+ wantErr bool
+ }{
+ {
+ name: "IPv4 with /32",
+ input: "192.168.1.1/32",
+ wantErr: false,
+ },
+ {
+ name: "IPv6 with /128",
+ input: "2001:db8::1/128",
+ wantErr: false,
+ },
+ {
+ name: "netip.Prefix with IPv4In6",
+ input: netip.MustParsePrefix("::ffff:192.168.1.0/120"),
+ wantErr: false,
+ },
+ {
+ name: "pointer to netip.Prefix with IPv4In6",
+ input: func() *netip.Prefix { p := netip.MustParsePrefix("::ffff:192.168.1.0/120"); return &p }(),
+ wantErr: false,
+ },
+ {
+ name: "pointer to netip.Addr IPv6",
+ input: func() *netip.Addr { a := netip.MustParseAddr("2001:db8::1"); return &a }(),
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := entry.AddPrefix(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.AddPrefix(%v) error = %v, wantErr %v", tt.input, err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_ProcessPrefixErrorCases(t *testing.T) {
+ entry := NewEntry("test")
+
+ // Test IPv4In6 prefix with bits < 96 (should trigger error on line 143)
+ // This is tricky to test because valid IPv4In6 prefixes have bits >= 96
+ // Let's test other error paths
+
+ tests := []struct {
+ name string
+ input any
+ wantErr bool
+ }{
+ {
+ name: "string CIDR with invalid network",
+ input: "256.256.256.256/24",
+ wantErr: true,
+ },
+ {
+ name: "string with only slash",
+ input: "/24",
+ wantErr: true,
+ },
+ {
+ name: "netip.Prefix IPv4",
+ input: netip.MustParsePrefix("192.168.1.0/24"),
+ wantErr: false,
+ },
+ {
+ name: "netip.Prefix IPv6",
+ input: netip.MustParsePrefix("2001:db8::/32"),
+ wantErr: false,
+ },
+ {
+ name: "*netip.Prefix IPv4",
+ input: func() *netip.Prefix { p := netip.MustParsePrefix("10.0.0.0/8"); return &p }(),
+ wantErr: false,
+ },
+ {
+ name: "*netip.Prefix IPv6",
+ input: func() *netip.Prefix { p := netip.MustParsePrefix("2001:db9::/32"); return &p }(),
+ wantErr: false,
+ },
+ {
+ name: "string with /* comment",
+ input: "192.168.1.0/24 /* comment */",
+ wantErr: false,
+ },
+ {
+ name: "string that becomes empty after comment removal",
+ input: "# just a comment",
+ wantErr: true, // ErrCommentLine leads to ErrInvalidIPType
+ },
+ {
+ name: "string with whitespace and comment",
+ input: " // comment only",
+ wantErr: true,
+ },
+ {
+ name: "net.IP with nil",
+ input: net.IP(nil),
+ wantErr: true, // Should trigger ErrInvalidIP
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := entry.AddPrefix(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.AddPrefix(%v) error = %v, wantErr %v", tt.input, err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_ProcessPrefix_NetIPNetCases(t *testing.T) {
+ entry := NewEntry("test")
+
+ tests := []struct {
+ name string
+ input *net.IPNet
+ wantErr bool
+ }{
+ {
+ name: "valid IPv4 IPNet",
+ input: &net.IPNet{IP: net.ParseIP("192.168.1.0"), Mask: net.CIDRMask(24, 32)},
+ wantErr: false,
+ },
+ {
+ name: "valid IPv6 IPNet",
+ input: &net.IPNet{IP: net.ParseIP("2001:db8::"), Mask: net.CIDRMask(32, 128)},
+ wantErr: false,
+ },
+ {
+ name: "invalid IPNet with nil IP",
+ input: &net.IPNet{IP: nil, Mask: net.CIDRMask(24, 32)},
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := entry.AddPrefix(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.AddPrefix() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_ProcessPrefix_NetipAddrCases(t *testing.T) {
+ entry := NewEntry("test")
+
+ tests := []struct {
+ name string
+ input netip.Addr
+ wantErr bool
+ }{
+ {
+ name: "valid IPv4 Addr",
+ input: netip.MustParseAddr("192.168.1.1"),
+ wantErr: false,
+ },
+ {
+ name: "valid IPv6 Addr",
+ input: netip.MustParseAddr("2001:db8::1"),
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := entry.AddPrefix(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Entry.AddPrefix() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestEntry_BuildIPSetErrors(t *testing.T) {
+ // Test buildIPSet when it succeeds multiple times (coverage for checking existing sets)
+ entry := NewEntry("test")
+ entry.AddPrefix("192.168.1.0/24")
+ entry.AddPrefix("2001:db8::/32")
+
+ // First call to buildIPSet
+ _, err := entry.GetIPv4Set()
+ if err != nil {
+ t.Errorf("First GetIPv4Set() error = %v", err)
+ }
+
+ // Second call should use existing set
+ _, err = entry.GetIPv4Set()
+ if err != nil {
+ t.Errorf("Second GetIPv4Set() error = %v", err)
+ }
+
+ // Same for IPv6
+ _, err = entry.GetIPv6Set()
+ if err != nil {
+ t.Errorf("First GetIPv6Set() error = %v", err)
+ }
+
+ _, err = entry.GetIPv6Set()
+ if err != nil {
+ t.Errorf("Second GetIPv6Set() error = %v", err)
+ }
+}