diff options
Diffstat (limited to 'lib/entry_test.go')
| -rw-r--r-- | lib/entry_test.go | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/lib/entry_test.go b/lib/entry_test.go new file mode 100644 index 00000000..f3a11989 --- /dev/null +++ b/lib/entry_test.go @@ -0,0 +1,414 @@ +package lib + +import ( + "net" + "net/netip" + "testing" + + "go4.org/netipx" +) + +func TestProcessPrefixVariants(t *testing.T) { + e := NewEntry("proc") + + ipv4 := net.ParseIP("1.1.1.1") + p, ipType, err := e.processPrefix(ipv4) + if err != nil || ipType != IPv4 || p.String() != "1.1.1.1/32" { + t.Fatalf("processPrefix(net.IPv4) = %v %v %v", p, ipType, err) + } + + if _, _, err := e.processPrefix(net.IP{}); err != ErrInvalidIP { + t.Fatalf("expected ErrInvalidIP for empty net.IP, got %v", err) + } + + ipv6 := net.ParseIP("2001:db8::1") + p, ipType, err = e.processPrefix(ipv6) + if err != nil || ipType != IPv6 || p.String() != "2001:db8::1/128" { + t.Fatalf("processPrefix(net.IPv6) = %v %v %v", p, ipType, err) + } + + _, n, _ := net.ParseCIDR("10.0.0.0/24") + p, ipType, err = e.processPrefix(n) + if err != nil || ipType != IPv4 || p.String() != "10.0.0.0/24" { + t.Fatalf("processPrefix(*net.IPNet) = %v %v %v", p, ipType, err) + } + + _, n6, _ := net.ParseCIDR("2001:db8:ffff::/48") + p, ipType, err = e.processPrefix(n6) + if err != nil || ipType != IPv6 || p.String() != "2001:db8:ffff::/48" { + t.Fatalf("processPrefix(*net.IPNet ipv6) = %v %v %v", p, ipType, err) + } + + badNet := &net.IPNet{IP: net.IPv4(1, 2, 3, 4), Mask: net.IPMask{1}} + if _, _, err := e.processPrefix(badNet); err != ErrInvalidIPNet { + t.Fatalf("expected ErrInvalidIPNet, got %v", err) + } + + addr := netip.MustParseAddr("192.0.2.1") + p, ipType, err = e.processPrefix(addr) + if err != nil || ipType != IPv4 || p.String() != "192.0.2.1/32" { + t.Fatalf("processPrefix(netip.Addr) = %v %v %v", p, ipType, err) + } + + ipv6Addr := netip.MustParseAddr("2001:db8::3") + p, ipType, err = e.processPrefix(ipv6Addr) + if err != nil || ipType != IPv6 || p.String() != "2001:db8::3/128" { + t.Fatalf("processPrefix(netip.Addr ipv6) = %v %v %v", p, ipType, err) + } + + addrPtr := netip.MustParseAddr("2001:db8::2") + p, ipType, err = e.processPrefix(&addrPtr) + if err != nil || ipType != IPv6 || p.String() != "2001:db8::2/128" { + t.Fatalf("processPrefix(*netip.Addr) = %v %v %v", p, ipType, err) + } + + addrPtr4 := netip.MustParseAddr("198.18.0.1") + p, ipType, err = e.processPrefix(&addrPtr4) + if err != nil || ipType != IPv4 || p.String() != "198.18.0.1/32" { + t.Fatalf("processPrefix(*netip.Addr ipv4) = %v %v %v", p, ipType, err) + } + + prefix := netip.MustParsePrefix("198.51.100.0/24") + p, ipType, err = e.processPrefix(prefix) + if err != nil || ipType != IPv4 || p.String() != "198.51.100.0/24" { + t.Fatalf("processPrefix(netip.Prefix) = %v %v %v", p, ipType, err) + } + + ipv6PrefixVal := netip.MustParsePrefix("2001:db8:abcd::/48") + if p, ipType, err := e.processPrefix(ipv6PrefixVal); err != nil || ipType != IPv6 || p.String() != "2001:db8:abcd::/48" { + t.Fatalf("processPrefix(netip.Prefix ipv6) = %v %v %v", p, ipType, err) + } + + prefixPtr := netip.MustParsePrefix("2001:db8:ffff::/48") + p, ipType, err = e.processPrefix(&prefixPtr) + if err != nil || ipType != IPv6 || p.String() != "2001:db8:ffff::/48" { + t.Fatalf("processPrefix(*netip.Prefix) = %v %v %v", p, ipType, err) + } + + prefixPtr4 := netip.MustParsePrefix("198.51.100.0/24") + p, ipType, err = e.processPrefix(&prefixPtr4) + if err != nil || ipType != IPv4 || p.String() != "198.51.100.0/24" { + t.Fatalf("processPrefix(*netip.Prefix ipv4) = %v %v %v", p, ipType, err) + } + + // IPv4-mapped IPv6 with insufficient bits should be rejected + badPrefix := netip.MustParsePrefix("::ffff:192.0.2.1/95") + if _, _, err := e.processPrefix(badPrefix); err != ErrInvalidPrefix { + t.Fatalf("expected ErrInvalidPrefix, got %v", err) + } + + mappedPrefix := netip.MustParsePrefix("::ffff:192.0.2.0/120") + if p, ipType, err := e.processPrefix(mappedPrefix); err != nil || ipType != IPv4 || p.String() != "192.0.2.0/24" { + t.Fatalf("processPrefix(mappedPrefix) = %v %v %v", p, ipType, err) + } + + invalidPrefix4 := netip.PrefixFrom(netip.MustParseAddr("1.1.1.1"), 40) + if _, _, err := e.processPrefix(invalidPrefix4); err != ErrInvalidPrefix { + t.Fatalf("expected ErrInvalidPrefix for invalid ipv4 prefix, got %v", err) + } + + invalidPrefix6 := netip.PrefixFrom(netip.MustParseAddr("2001:db8::1"), 200) + if _, _, err := e.processPrefix(invalidPrefix6); err != ErrInvalidPrefix { + t.Fatalf("expected ErrInvalidPrefix for invalid ipv6 prefix, got %v", err) + } + + invalidPrefix4Ptr := invalidPrefix4 + if _, _, err := e.processPrefix(&invalidPrefix4Ptr); err != ErrInvalidPrefix { + t.Fatalf("expected ErrInvalidPrefix for invalid ipv4 prefix pointer, got %v", err) + } + + invalidPrefix6Ptr := invalidPrefix6 + if _, _, err := e.processPrefix(&invalidPrefix6Ptr); err != ErrInvalidPrefix { + t.Fatalf("expected ErrInvalidPrefix for invalid ipv6 prefix pointer, got %v", err) + } + + zeroPrefix := netip.Prefix{} + if _, _, err := e.processPrefix(zeroPrefix); err != ErrInvalidIPLength { + t.Fatalf("expected ErrInvalidIPLength for zero prefix, got %v", err) + } + + badPrefixPtr := badPrefix + if _, _, err := e.processPrefix(&badPrefixPtr); err != ErrInvalidPrefix { + t.Fatalf("expected ErrInvalidPrefix for pointer bad prefix, got %v", err) + } + + zeroPrefixPtr := zeroPrefix + if _, _, err := e.processPrefix(&zeroPrefixPtr); err != ErrInvalidIPLength { + t.Fatalf("expected ErrInvalidIPLength for zero prefix pointer, got %v", err) + } + + addrZero := netip.Addr{} + if _, _, err := e.processPrefix(&addrZero); err != ErrInvalidIPLength { + t.Fatalf("expected ErrInvalidIPLength for zero addr pointer, got %v", err) + } + + mappedPrefixPtr := mappedPrefix + if p, ipType, err := e.processPrefix(&mappedPrefixPtr); err != nil || ipType != IPv4 || p.String() != "192.0.2.0/24" { + t.Fatalf("processPrefix(mappedPrefixPtr) = %v %v %v", p, ipType, err) + } + + if _, _, err := e.processPrefix(netip.Addr{}); err != ErrInvalidIPLength { + t.Fatalf("expected ErrInvalidIPLength, got %v", err) + } + + if _, _, err := e.processPrefix("1.2.3.4"); err != nil { + t.Fatalf("processPrefix(string ip) error = %v", err) + } + + if _, _, err := e.processPrefix("2001:db8::1"); err != nil { + t.Fatalf("processPrefix(string ipv6) error = %v", err) + } + + if _, _, err := e.processPrefix("10.0.0.0/8"); err != nil { + t.Fatalf("processPrefix(string cidr) error = %v", err) + } + + if _, _, err := e.processPrefix("2001:db8::/32"); err != nil { + t.Fatalf("processPrefix(string cidr ipv6) error = %v", err) + } + + if _, _, err := e.processPrefix("invalid/24"); err != ErrInvalidCIDR { + t.Fatalf("expected ErrInvalidCIDR, got %v", err) + } + + if _, _, err := e.processPrefix(" //comment"); err != ErrCommentLine { + t.Fatalf("expected ErrCommentLine, got %v", err) + } + + if _, _, err := e.processPrefix(123); err != ErrInvalidPrefixType { + t.Fatalf("expected ErrInvalidPrefixType, got %v", err) + } +} + +func TestEntryAddAndRemovePrefix(t *testing.T) { + e := NewEntry("demo") + + if err := e.AddPrefix("10.0.0.0/24"); err != nil { + t.Fatalf("AddPrefix() error = %v", err) + } + if err := e.AddPrefix("2001:db8::/32"); err != nil { + t.Fatalf("AddPrefix() error = %v", err) + } + + ipv4set, err := e.GetIPv4Set() + if err != nil || !ipv4set.Contains(netip.MustParseAddr("10.0.0.1")) { + t.Fatalf("IPv4 set missing data: %v %v", ipv4set, err) + } + + ipv6set, err := e.GetIPv6Set() + if err != nil || !ipv6set.Contains(netip.MustParseAddr("2001:db8::1")) { + t.Fatalf("IPv6 set missing data: %v %v", ipv6set, err) + } + + if err := e.RemovePrefix("10.0.0.0/24"); err != nil { + t.Fatalf("RemovePrefix() error = %v", err) + } + e.ipv4Set = nil + ipv4set, _ = e.GetIPv4Set() + if ipv4set.Contains(netip.MustParseAddr("10.0.0.1")) { + t.Fatalf("prefix should be removed") + } + + if err := e.RemovePrefix("2001:db8::/32"); err != nil { + t.Fatalf("RemovePrefix() error = %v", err) + } + e.ipv6Set = nil + ipv6set, _ = e.GetIPv6Set() + if ipv6set.Contains(netip.MustParseAddr("2001:db8::1")) { + t.Fatalf("ipv6 prefix should be removed") + } + + if err := e.RemovePrefix("invalid"); err == nil { + t.Fatalf("expected error for invalid prefix") + } +} + +func TestEntryAddRemoveInvalidIPType(t *testing.T) { + e := NewEntry("invalid") + if err := e.add(nil, IPType("unknown")); err != ErrInvalidIPType { + t.Fatalf("expected ErrInvalidIPType, got %v", err) + } + if err := e.remove(nil, IPType("unknown")); err != ErrInvalidIPType { + t.Fatalf("expected ErrInvalidIPType, got %v", err) + } +} + +func TestEntryMarshalFunctions(t *testing.T) { + e := NewEntry("marshal") + _ = e.AddPrefix("203.0.113.0/24") + _ = e.AddPrefix("2001:db8:abcd::/48") + + prefixes, err := e.MarshalPrefix() + if err != nil || len(prefixes) != 2 { + t.Fatalf("MarshalPrefix() = %v, %v", prefixes, err) + } + + prefixes, err = e.MarshalPrefix(IgnoreIPv6) + if err != nil || len(prefixes) != 1 { + t.Fatalf("MarshalPrefix(IgnoreIPv6) = %v, %v", prefixes, err) + } + + ranges, err := e.MarshalIPRange() + if err != nil || len(ranges) != 2 { + t.Fatalf("MarshalIPRange() = %v, %v", ranges, err) + } + + ranges, err = e.MarshalIPRange(IgnoreIPv4) + if err != nil || len(ranges) != 1 { + t.Fatalf("MarshalIPRange(IgnoreIPv4) = %v, %v", ranges, err) + } + + text, err := e.MarshalText() + if err != nil || len(text) != 2 { + t.Fatalf("MarshalText() = %v, %v", text, err) + } + + // Ignore IPv4 results + prefixes, err = e.MarshalPrefix(IgnoreIPv4) + if err != nil || len(prefixes) != 1 { + t.Fatalf("MarshalPrefix(IgnoreIPv4) = %v, %v", prefixes, err) + } + + text, err = e.MarshalText(IgnoreIPv4) + if err != nil || len(text) != 1 { + t.Fatalf("MarshalText(IgnoreIPv4) = %v, %v", text, err) + } + + text, err = e.MarshalText(IgnoreIPv6) + if err != nil || len(text) != 1 { + t.Fatalf("MarshalText(IgnoreIPv6) = %v, %v", text, err) + } +} + +func TestEntryMarshalErrors(t *testing.T) { + e := NewEntry("empty") + + if _, err := e.MarshalPrefix(); err == nil { + t.Fatalf("expected error for empty entry") + } + if _, err := e.MarshalIPRange(); err == nil { + t.Fatalf("expected error for empty entry") + } + if _, err := e.MarshalText(); err == nil { + t.Fatalf("expected error for empty entry") + } +} + +func TestEntryBuildIPSetError(t *testing.T) { + e := NewEntry("errbuild") + builder := &netipx.IPSetBuilder{} + builder.AddPrefix(netip.Prefix{}) // invalid prefix triggers builder error + e.ipv4Builder = builder + + if _, err := e.GetIPv4Set(); err == nil { + t.Fatalf("expected buildIPSet error") + } + + builder6 := &netipx.IPSetBuilder{} + builder6.AddPrefix(netip.Prefix{}) + e.ipv6Builder = builder6 + if _, err := e.GetIPv6Set(); err == nil { + t.Fatalf("expected buildIPSet error for ipv6") + } + + if _, err := e.MarshalPrefix(); err == nil { + t.Fatalf("expected MarshalPrefix error from builder") + } + + if _, err := e.MarshalIPRange(); err == nil { + t.Fatalf("expected MarshalIPRange error from builder") + } + + if _, err := e.MarshalText(); err == nil { + t.Fatalf("expected MarshalText error from builder") + } + + // Use fresh entries to ensure builder errors are preserved + e2 := NewEntry("errbuild2") + b2 := &netipx.IPSetBuilder{} + b2.AddPrefix(netip.Prefix{}) + e2.ipv4Builder = b2 + if _, err := e2.MarshalPrefix(); err == nil { + t.Fatalf("expected MarshalPrefix error from builder") + } + + e3 := NewEntry("errbuild3") + b3 := &netipx.IPSetBuilder{} + b3.AddPrefix(netip.Prefix{}) + e3.ipv4Builder = b3 + if _, err := e3.MarshalIPRange(); err == nil { + t.Fatalf("expected MarshalIPRange error from builder") + } + + e4 := NewEntry("errbuild4") + b4 := &netipx.IPSetBuilder{} + b4.AddPrefix(netip.Prefix{}) + e4.ipv4Builder = b4 + if _, err := e4.MarshalText(); err == nil { + t.Fatalf("expected MarshalText error from builder") + } +} + +func TestEntryGetSetErrors(t *testing.T) { + e := NewEntry("sets") + _ = e.AddPrefix("2001:db8::/32") + + if _, err := e.GetIPv4Set(); err == nil { + t.Fatalf("expected error for missing IPv4 set") + } + + e2 := NewEntry("sets2") + _ = e2.AddPrefix("192.0.2.0/24") + if _, err := e2.GetIPv6Set(); err == nil { + t.Fatalf("expected error for missing IPv6 set") + } +} + +func TestAddPrefixErrorPath(t *testing.T) { + e := NewEntry("err") + if err := e.AddPrefix("bad-prefix"); err == nil { + t.Fatalf("expected error for bad prefix") + } + if err := e.RemovePrefix("bad-prefix"); err == nil { + t.Fatalf("expected error for bad prefix removal") + } +} + +func TestEntryCommentLineHandling(t *testing.T) { + e := NewEntry("comment") + if err := e.AddPrefix("# this is comment"); err != ErrInvalidIPType { + t.Fatalf("expected ErrInvalidIPType for comment line, got %v", err) + } + if err := e.RemovePrefix("// another comment"); err != ErrInvalidIPType { + t.Fatalf("expected ErrInvalidIPType for comment line removal, got %v", err) + } +} + +func TestEntryMarshalIgnoreIPv6(t *testing.T) { + e := NewEntry("ignore6") + _ = e.AddPrefix("2001:db8::/32") + _ = e.AddPrefix("198.51.100.0/24") + + ranges, err := e.MarshalIPRange(IgnoreIPv6) + if err != nil || len(ranges) != 1 { + t.Fatalf("MarshalIPRange(IgnoreIPv6) = %v, %v", ranges, err) + } +} + +func TestEntryBuildIPSetReuse(t *testing.T) { + e := NewEntry("reuse") + builder := netipx.IPSetBuilder{} + builder.AddPrefix(netip.MustParsePrefix("10.10.0.0/16")) + e.ipv4Builder = &builder + + if err := e.buildIPSet(); err != nil { + t.Fatalf("buildIPSet() error = %v", err) + } + if !e.hasIPv4Set() { + t.Fatalf("expected ipv4 set to be built") + } + // Second call should be a no-op but still succeed + if err := e.buildIPSet(); err != nil { + t.Fatalf("buildIPSet() second call error = %v", err) + } +} |
