golib/net/ipcohort/ipcohort_test.go
AJ ONeal 896031b6a8
fix: idiomatic Go cleanup across net packages
- gitshallow: replace in-place Depth mutation with effectiveDepth() method;
  remove depth normalisation from New() since it was masking the bug
- ipcohort: extract sortNets() helper using cmp.Compare, eliminating 3 identical
  sort closures; add ContainsAddr(netip.Addr) for pre-parsed callers; guard
  Contains() against IPv6 panic (As4 panics on non-v4); add IPv6 test
- dataset: Add() now sets NopSyncer{} so callers cannot panic by accidentally
  calling Init/Sync/Run on a Group-managed Dataset
2026-04-20 09:47:50 -06:00

191 lines
4.3 KiB
Go

package ipcohort_test
import (
"os"
"path/filepath"
"testing"
"github.com/therootcompany/golib/net/ipcohort"
)
func TestParseIPv4(t *testing.T) {
tests := []struct {
raw string
wantErr bool
}{
{"1.2.3.4", false},
{"1.2.3.4/32", false},
{"10.0.0.0/8", false},
{"192.168.0.0/16", false},
{"0.0.0.0/0", false},
{"", true},
{"not-an-ip", true},
{"1.2.3.4/33", true},
{"::1", true}, // IPv6 not supported
}
for _, tt := range tests {
_, err := ipcohort.ParseIPv4(tt.raw)
if (err != nil) != tt.wantErr {
t.Errorf("ParseIPv4(%q): got err=%v, wantErr=%v", tt.raw, err, tt.wantErr)
}
}
}
func TestContains_SingleHosts(t *testing.T) {
c, err := ipcohort.Parse([]string{"1.2.3.4", "5.6.7.8", "10.0.0.1"})
if err != nil {
t.Fatal(err)
}
hits := []string{"1.2.3.4", "5.6.7.8", "10.0.0.1"}
misses := []string{"1.2.3.5", "5.6.7.7", "10.0.0.2", "0.0.0.0"}
for _, ip := range hits {
if !c.Contains(ip) {
t.Errorf("expected %s to be in cohort", ip)
}
}
for _, ip := range misses {
if c.Contains(ip) {
t.Errorf("expected %s NOT to be in cohort", ip)
}
}
}
func TestContains_CIDRRanges(t *testing.T) {
c, err := ipcohort.Parse([]string{"10.0.0.0/8", "192.168.1.0/24"})
if err != nil {
t.Fatal(err)
}
hits := []string{
"10.0.0.0", "10.0.0.1", "10.255.255.255",
"192.168.1.0", "192.168.1.1", "192.168.1.254", "192.168.1.255",
}
misses := []string{
"9.255.255.255", "11.0.0.0",
"192.168.0.255", "192.168.2.0",
}
for _, ip := range hits {
if !c.Contains(ip) {
t.Errorf("expected %s to be in cohort (CIDR)", ip)
}
}
for _, ip := range misses {
if c.Contains(ip) {
t.Errorf("expected %s NOT to be in cohort (CIDR)", ip)
}
}
}
func TestContains_Mixed(t *testing.T) {
c, err := ipcohort.Parse([]string{"1.2.3.4", "10.0.0.0/8"})
if err != nil {
t.Fatal(err)
}
if !c.Contains("1.2.3.4") {
t.Error("host miss")
}
if !c.Contains("10.5.5.5") {
t.Error("CIDR miss")
}
if c.Contains("1.2.3.5") {
t.Error("false positive for host-adjacent")
}
}
func TestContains_FailClosed(t *testing.T) {
c, _ := ipcohort.Parse([]string{"1.2.3.4"})
// Unparseable input should return true (fail-closed).
if !c.Contains("not-an-ip") {
t.Error("expected fail-closed true for unparseable IP")
}
}
func TestContains_IPv6NeverBlocked(t *testing.T) {
c, _ := ipcohort.Parse([]string{"1.2.3.4", "10.0.0.0/8"})
// IPv6 addresses are not stored; should return false, not panic.
if c.Contains("::1") {
t.Error("IPv6 address should not be in an IPv4-only cohort")
}
if c.Contains("2001:db8::1") {
t.Error("IPv6 address should not be in an IPv4-only cohort")
}
}
func TestContains_Empty(t *testing.T) {
c, err := ipcohort.Parse(nil)
if err != nil {
t.Fatal(err)
}
if c.Contains("1.2.3.4") {
t.Error("empty cohort should not contain anything")
}
}
func TestSize(t *testing.T) {
c, _ := ipcohort.Parse([]string{"1.2.3.4", "5.6.7.8", "10.0.0.0/8"})
if got, want := c.Size(), 3; got != want {
t.Errorf("Size() = %d, want %d", got, want)
}
}
func TestLoadFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "list.txt")
content := "# comment\n1.2.3.4\n10.0.0.0/8\n\n5.6.7.8\n"
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
c, err := ipcohort.LoadFile(path)
if err != nil {
t.Fatal(err)
}
if c.Size() != 3 {
t.Errorf("Size() = %d, want 3", c.Size())
}
if !c.Contains("1.2.3.4") {
t.Error("missing 1.2.3.4")
}
if !c.Contains("10.5.5.5") {
t.Error("missing CIDR member 10.5.5.5")
}
}
func TestLoadFiles_Merge(t *testing.T) {
dir := t.TempDir()
f1 := filepath.Join(dir, "singles.txt")
f2 := filepath.Join(dir, "networks.txt")
os.WriteFile(f1, []byte("1.2.3.4\n5.6.7.8\n"), 0o644)
os.WriteFile(f2, []byte("192.168.0.0/24\n"), 0o644)
c, err := ipcohort.LoadFiles(f1, f2)
if err != nil {
t.Fatal(err)
}
if c.Size() != 3 {
t.Errorf("Size() = %d, want 3", c.Size())
}
if !c.Contains("192.168.0.100") {
t.Error("missing merged CIDR member")
}
}
func TestCSVSkipsIPv6(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "mixed.txt")
os.WriteFile(path, []byte("1.2.3.4\n::1\n2001:db8::/32\n5.6.7.8\n"), 0o644)
c, err := ipcohort.LoadFile(path)
if err != nil {
t.Fatal(err)
}
if c.Size() != 2 {
t.Errorf("Size() = %d, want 2 (IPv6 should be skipped)", c.Size())
}
}