diff options
Diffstat (limited to 'lib/entry_test.go')
| -rw-r--r-- | lib/entry_test.go | 751 |
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) + } +} |
