- Add IP validation in auth handler with detailed logging - Implement apiKeyMiddleware helper for API key verification - Add IP and CIDR validation for whitelist operations - Support pagination for listing temp and permanent entries - Add parsePagination helper to extract limit and offset - Include comprehensive unit tests for IsIP, IsCIDR, and IPMatch
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestIsIP(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantOK bool
|
|
}{
|
|
{"ipv4", "192.168.1.1", true},
|
|
{"ipv4 loopback", "127.0.0.1", true},
|
|
{"ipv6", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", true},
|
|
{"ipv6 short", "::1", true},
|
|
{"empty", "", false},
|
|
{"hostname", "example.com", false},
|
|
{"garbage", "not-an-ip!!", false},
|
|
{"partial", "192.168", false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := IsIP(tt.input); got != tt.wantOK {
|
|
t.Errorf("IsIP(%q) = %v, want %v", tt.input, got, tt.wantOK)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsCIDR(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantOK bool
|
|
}{
|
|
{"ipv4 cidr", "192.168.1.0/24", true},
|
|
{"ipv6 cidr", "2001:db8::/32", true},
|
|
{"single host", "10.0.0.0/32", true},
|
|
{"plain ip", "10.0.0.1", false},
|
|
{"missing prefix", "192.168.1.0", false},
|
|
{"garbage", "foo/bar", false},
|
|
{"empty", "", false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := IsCIDR(tt.input); got != tt.wantOK {
|
|
t.Errorf("IsCIDR(%q) = %v, want %v", tt.input, got, tt.wantOK)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPMatchesEntry(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ip string
|
|
entry string
|
|
wantOK bool
|
|
wantErr bool
|
|
}{
|
|
{"exact match", "192.168.1.5", "192.168.1.5", true, false},
|
|
{"ipv4 cidr match", "10.0.0.5", "10.0.0.0/24", true, false},
|
|
{"ipv4 cidr no match", "10.0.1.5", "10.0.0.0/24", false, false},
|
|
{"ipv6 cidr match", "2001:db8::1", "2001:db8::/32", true, false},
|
|
{"invalid entry", "10.0.0.1", "not-a-cidr", false, false},
|
|
{"empty entry", "10.0.0.1", "", false, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := ipMatchesEntry(tt.ip, tt.entry)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ipMatchesEntry(%q, %q) = %v, want error", tt.ip, tt.entry, got)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("ipMatchesEntry(%q, %q) error: %v", tt.ip, tt.entry, err)
|
|
}
|
|
if got != tt.wantOK {
|
|
t.Errorf("ipMatchesEntry(%q, %q) = %v, want %v", tt.ip, tt.entry, got, tt.wantOK)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVerifyAPIKey(t *testing.T) {
|
|
token := "super-secret"
|
|
|
|
tests := []struct {
|
|
name string
|
|
auth string
|
|
wantOK bool
|
|
}{
|
|
{"correct token", "Bearer super-secret", true},
|
|
{"missing Bearer", "super-secret", false},
|
|
{"wrong token", "Bearer wrong", false},
|
|
{"empty", "", false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := http.Request{Header: http.Header{}}
|
|
r.Header.Set("Authorization", tt.auth)
|
|
if got := verifyAPIKey(&r, token); got != tt.wantOK {
|
|
t.Errorf("verifyAPIKey(%q) = %v, want %v", tt.auth, got, tt.wantOK)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApiKeyMiddleware(t *testing.T) {
|
|
token := "test-token"
|
|
called := false
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
called = true
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
wrapped := apiKeyMiddleware(next, token)
|
|
|
|
t.Run("valid key", func(t *testing.T) {
|
|
called = false
|
|
r := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
r.Header.Set("Authorization", "Bearer test-token")
|
|
w := httptest.NewRecorder()
|
|
wrapped(w, r)
|
|
if !called {
|
|
t.Error("middleware did not call next handler")
|
|
}
|
|
if w.Code != http.StatusNoContent {
|
|
t.Errorf("expected 204, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid key", func(t *testing.T) {
|
|
called = false
|
|
r := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
r.Header.Set("Authorization", "Bearer wrong")
|
|
w := httptest.NewRecorder()
|
|
wrapped(w, r)
|
|
if called {
|
|
t.Error("middleware called next handler despite invalid key")
|
|
}
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("missing key", func(t *testing.T) {
|
|
called = false
|
|
r := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
wrapped(w, r)
|
|
if called {
|
|
t.Error("middleware called next handler despite missing key")
|
|
}
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
})
|
|
}
|