Files
x/resolver/plugin/http_test.go
T
ginuerzh 28d3ffa310 fix(resolver): fix async context leak, gRPC error swallowing, add 48 tests
Fix async resolve goroutine using caller's cancelable context by
switching to context.WithoutCancel. Fix NewGRPCPlugin silently
returning broken resolver on connection failure. Fix "resolover"
typo in log field. Add doc comments to all exported symbols.
2026-05-25 20:52:19 +08:00

212 lines
6.2 KiB
Go

package resolver
import (
"context"
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/plugin"
xlogger "github.com/go-gost/x/logger"
)
func init() {
logger.SetDefault(xlogger.Nop())
}
func TestHTTPPlugin_ResolveSuccess(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
t.Errorf("expected application/json content-type, got %s", ct)
}
var req httpPluginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Errorf("decode request: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if req.Host != "example.com" {
t.Errorf("expected host example.com, got %s", req.Host)
}
resp := httpPluginResponse{
IPs: []string{"10.0.0.1", "10.0.0.2"},
OK: true,
}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL, plugin.TimeoutOption(5*time.Second))
ips, err := p.Resolve(context.Background(), "ip", "example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(ips) != 2 {
t.Fatalf("expected 2 IPs, got %d: %v", len(ips), ips)
}
if !ips[0].Equal(net.ParseIP("10.0.0.1")) {
t.Errorf("first IP = %v, want 10.0.0.1", ips[0])
}
if !ips[1].Equal(net.ParseIP("10.0.0.2")) {
t.Errorf("second IP = %v, want 10.0.0.2", ips[1])
}
}
func TestHTTPPlugin_ResolveEmptyIPs(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := httpPluginResponse{IPs: []string{}, OK: true}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL, plugin.TimeoutOption(5*time.Second))
ips, err := p.Resolve(context.Background(), "ip", "example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(ips) != 0 {
t.Fatalf("expected 0 IPs, got %d: %v", len(ips), ips)
}
}
func TestHTTPPlugin_ResolveNotOK(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := httpPluginResponse{IPs: []string{}, OK: false}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL, plugin.TimeoutOption(5*time.Second))
_, err := p.Resolve(context.Background(), "ip", "example.com")
if err == nil {
t.Fatal("expected error when OK=false")
}
}
func TestHTTPPlugin_ResolveInvalidIP(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := httpPluginResponse{
IPs: []string{"not-an-ip", "10.0.0.1"},
OK: true,
}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL, plugin.TimeoutOption(5*time.Second))
ips, err := p.Resolve(context.Background(), "ip", "example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(ips) != 1 {
t.Fatalf("expected 1 valid IP (invalid skipped), got %d: %v", len(ips), ips)
}
if !ips[0].Equal(net.ParseIP("10.0.0.1")) {
t.Errorf("IP = %v, want 10.0.0.1", ips[0])
}
}
func TestHTTPPlugin_ResolveHTTPError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "internal error", http.StatusInternalServerError)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL, plugin.TimeoutOption(5*time.Second))
_, err := p.Resolve(context.Background(), "ip", "example.com")
if err == nil {
t.Fatal("expected error for HTTP 500")
}
}
func TestHTTPPlugin_ResolveCancelledContext(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond)
resp := httpPluginResponse{IPs: []string{"10.0.0.1"}, OK: true}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL, plugin.TimeoutOption(10*time.Second))
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := p.Resolve(ctx, "ip", "example.com")
if err == nil {
t.Error("expected error for cancelled context")
}
}
func TestHTTPPlugin_ResolveConnectionRefused(t *testing.T) {
// Use a port that's not listening
p := NewHTTPPlugin("test", "http://127.0.0.1:1/nonexistent", plugin.TimeoutOption(100*time.Millisecond))
_, err := p.Resolve(context.Background(), "ip", "example.com")
if err == nil {
t.Error("expected error for connection refused")
}
}
func TestHTTPPlugin_NilClient(t *testing.T) {
p := &httpPlugin{client: nil, log: xlogger.Nop()}
ips, err := p.Resolve(context.Background(), "ip", "example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(ips) != 0 {
t.Fatalf("expected no IPs with nil client, got %v", ips)
}
}
func TestHTTPPlugin_CustomHeader(t *testing.T) {
var receivedAuth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
receivedAuth = r.Header.Get("Authorization")
resp := httpPluginResponse{IPs: []string{"10.0.0.1"}, OK: true}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
headers := http.Header{}
headers.Set("Authorization", "Bearer test-token")
p := NewHTTPPlugin("test", srv.URL, plugin.HeaderOption(headers))
_, err := p.Resolve(context.Background(), "ip", "example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if receivedAuth != "Bearer test-token" {
t.Errorf("Authorization header = %q, want %q", receivedAuth, "Bearer test-token")
}
}
func TestHTTPPlugin_ClientID(t *testing.T) {
var receivedClient string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req httpPluginRequest
json.NewDecoder(r.Body).Decode(&req)
receivedClient = req.Client
resp := httpPluginResponse{IPs: []string{"10.0.0.1"}, OK: true}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
p := NewHTTPPlugin("test", srv.URL)
_, err := p.Resolve(context.Background(), "ip", "example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Client ID is empty when not set in context
if receivedClient != "" {
t.Errorf("expected empty client, got %q", receivedClient)
}
}