package delivery import ( "net" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestIsBlockedIP_PrivateRanges(t *testing.T) { t.Parallel() tests := []struct { name string ip string blocked bool }{ // Loopback {"loopback 127.0.0.1", "127.0.0.1", true}, {"loopback 127.0.0.2", "127.0.0.2", true}, {"loopback 127.255.255.255", "127.255.255.255", true}, // RFC 1918 - Class A {"10.0.0.0", "10.0.0.0", true}, {"10.0.0.1", "10.0.0.1", true}, {"10.255.255.255", "10.255.255.255", true}, // RFC 1918 - Class B {"172.16.0.1", "172.16.0.1", true}, {"172.31.255.255", "172.31.255.255", true}, {"172.15.255.255", "172.15.255.255", false}, {"172.32.0.0", "172.32.0.0", false}, // RFC 1918 - Class C {"192.168.0.1", "192.168.0.1", true}, {"192.168.255.255", "192.168.255.255", true}, // Link-local / cloud metadata {"169.254.0.1", "169.254.0.1", true}, {"169.254.169.254", "169.254.169.254", true}, // Public IPs (should NOT be blocked) {"8.8.8.8", "8.8.8.8", false}, {"1.1.1.1", "1.1.1.1", false}, {"93.184.216.34", "93.184.216.34", false}, // IPv6 loopback {"::1", "::1", true}, // IPv6 unique local {"fd00::1", "fd00::1", true}, {"fc00::1", "fc00::1", true}, // IPv6 link-local {"fe80::1", "fe80::1", true}, // IPv6 public (should NOT be blocked) {"2607:f8b0:4004:800::200e", "2607:f8b0:4004:800::200e", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() ip := net.ParseIP(tt.ip) require.NotNil(t, ip, "failed to parse IP %s", tt.ip) assert.Equal(t, tt.blocked, isBlockedIP(ip), "isBlockedIP(%s) = %v, want %v", tt.ip, isBlockedIP(ip), tt.blocked) }) } } func TestValidateTargetURL_Blocked(t *testing.T) { t.Parallel() blockedURLs := []string{ "http://127.0.0.1/hook", "http://127.0.0.1:8080/hook", "https://10.0.0.1/hook", "http://192.168.1.1/webhook", "http://172.16.0.1/api", "http://169.254.169.254/latest/meta-data/", "http://[::1]/hook", "http://[fc00::1]/hook", "http://[fe80::1]/hook", "http://0.0.0.0/hook", } for _, u := range blockedURLs { t.Run(u, func(t *testing.T) { t.Parallel() err := ValidateTargetURL(u) assert.Error(t, err, "URL %s should be blocked", u) }) } } func TestValidateTargetURL_Allowed(t *testing.T) { t.Parallel() // These are public IPs and should be allowed allowedURLs := []string{ "https://example.com/hook", "http://93.184.216.34/webhook", "https://hooks.slack.com/services/T00/B00/xxx", } for _, u := range allowedURLs { t.Run(u, func(t *testing.T) { t.Parallel() err := ValidateTargetURL(u) assert.NoError(t, err, "URL %s should be allowed", u) }) } } func TestValidateTargetURL_InvalidScheme(t *testing.T) { t.Parallel() err := ValidateTargetURL("ftp://example.com/hook") assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported URL scheme") } func TestValidateTargetURL_EmptyHost(t *testing.T) { t.Parallel() err := ValidateTargetURL("http:///path") assert.Error(t, err) } func TestValidateTargetURL_InvalidURL(t *testing.T) { t.Parallel() err := ValidateTargetURL("://invalid") assert.Error(t, err) } func TestBlockedNetworks_Initialized(t *testing.T) { t.Parallel() assert.NotEmpty(t, blockedNetworks, "blockedNetworks should be initialized") // Should have at least the main RFC 1918 + loopback + link-local ranges assert.GreaterOrEqual(t, len(blockedNetworks), 8, "should have at least 8 blocked network ranges") }