Files
card-to-iban/backend/internal/httpapi/client_ip_test.go

144 lines
3.4 KiB
Go

package httpapi
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestExtractClientIP(t *testing.T) {
tests := []struct {
name string
request *http.Request
headers map[string]string
remoteAddr string
wantIP *string
}{
{
name: "nil request",
request: nil,
wantIP: nil,
},
{
name: "CF-Connecting-IP takes precedence",
headers: map[string]string{
"CF-Connecting-IP": "203.0.113.10",
"X-Real-IP": "198.51.100.20",
"X-Forwarded-For": "192.0.2.30",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("203.0.113.10"),
},
{
name: "True-Client-IP takes precedence over X-Real-IP",
headers: map[string]string{
"True-Client-IP": "203.0.113.11",
"X-Real-IP": "198.51.100.20",
"X-Forwarded-For": "192.0.2.30",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("203.0.113.11"),
},
{
name: "X-Real-IP takes precedence over X-Forwarded-For",
headers: map[string]string{
"X-Real-IP": "198.51.100.20",
"X-Forwarded-For": "192.0.2.30",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("198.51.100.20"),
},
{
name: "X-Forwarded-For first entry is extracted",
headers: map[string]string{
"X-Forwarded-For": "192.0.2.30, 10.0.0.2, 172.16.0.5",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("192.0.2.30"),
},
{
name: "X-Forwarded-For single entry",
headers: map[string]string{
"X-Forwarded-For": " 192.0.2.31 ",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("192.0.2.31"),
},
{
name: "X-Forwarded-For skips invalid first entry to find valid IP",
headers: map[string]string{
"X-Forwarded-For": "unknown, 192.0.2.32",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("192.0.2.32"),
},
{
name: "Fallback to RemoteAddr with port",
headers: map[string]string{},
remoteAddr: "192.168.1.100:12345",
wantIP: ptr("192.168.1.100"),
},
{
name: "Fallback to RemoteAddr without port",
headers: map[string]string{},
remoteAddr: "192.168.1.101",
wantIP: ptr("192.168.1.101"),
},
{
name: "IPv6 in RemoteAddr with port",
headers: map[string]string{},
remoteAddr: "[2001:db8::1]:8080",
wantIP: ptr("2001:db8::1"),
},
{
name: "IPv6 in header",
headers: map[string]string{
"X-Real-IP": "2001:db8::2",
},
remoteAddr: "10.0.0.1:54321",
wantIP: ptr("2001:db8::2"),
},
{
name: "Invalid headers fall back to RemoteAddr",
headers: map[string]string{
"X-Real-IP": "not-an-ip",
"X-Forwarded-For": "also-not-an-ip",
},
remoteAddr: "192.168.1.102:443",
wantIP: ptr("192.168.1.102"),
},
{
name: "No headers and empty RemoteAddr",
headers: map[string]string{},
remoteAddr: "",
wantIP: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var req *http.Request
if tt.name != "nil request" {
req = httptest.NewRequest(http.MethodGet, "/", nil)
for k, v := range tt.headers {
req.Header.Set(k, v)
}
req.RemoteAddr = tt.remoteAddr
}
got := extractClientIP(req)
if (got == nil) != (tt.wantIP == nil) {
t.Fatalf("extractClientIP() = %v, want %v", got, tt.wantIP)
}
if got != nil && tt.wantIP != nil && *got != *tt.wantIP {
t.Errorf("extractClientIP() = %q, want %q", *got, *tt.wantIP)
}
})
}
}
func ptr(s string) *string {
return &s
}