// Copyright (c) 2026, Pim van Pelt package prober import ( "context" "fmt" "io" "net" "net/http" "net/http/httptest" "regexp" "testing" "time" "git.ipng.ch/ipng/vpp-maglev/internal/config" ) // dialAndProbe dials addr directly (bypassing netns/interface binding) and // exercises the HTTP probe response-checking logic. func dialAndProbe(ctx context.Context, addr string, cfg ProbeConfig) (bool, error) { if cfg.HTTP == nil { return false, fmt.Errorf("dialAndProbe requires HTTP params") } p := cfg.HTTP path := p.Path if path == "" { path = "/" } conn, err := net.DialTimeout("tcp", addr, cfg.Timeout) if err != nil { return false, err } transport := &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { return conn, nil }, DisableKeepAlives: true, } client := &http.Client{ Transport: transport, Timeout: cfg.Timeout, CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }, } target := "http://" + addr + path req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil) if err != nil { return false, err } if p.Host != "" { req.Host = p.Host } resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if resp.StatusCode < p.ResponseCodeMin || resp.StatusCode > p.ResponseCodeMax { return false, nil } if p.ResponseRegexp != nil { body, _ := io.ReadAll(resp.Body) if !p.ResponseRegexp.Match(body) { return false, nil } } return true, nil } func TestHTTPProbeStatusCode(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprint(w, "healthy") })) defer srv.Close() cfg := ProbeConfig{ Timeout: 2 * time.Second, HTTP: &config.HTTPParams{ Path: "/healthz", ResponseCodeMin: 200, ResponseCodeMax: 200, }, } ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Error("expected probe success") } } func TestHTTPProbeWrongStatusCode(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) })) defer srv.Close() cfg := ProbeConfig{ Timeout: 2 * time.Second, HTTP: &config.HTTPParams{ Path: "/", ResponseCodeMin: 200, ResponseCodeMax: 200, }, } ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if ok { t.Error("expected probe failure on wrong status code") } } func TestHTTPProbeRegexpMatch(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, `{"status":"ok"}`) })) defer srv.Close() cfg := ProbeConfig{ Timeout: 2 * time.Second, HTTP: &config.HTTPParams{ Path: "/", ResponseCodeMin: 200, ResponseCodeMax: 200, ResponseRegexp: regexp.MustCompile(`"status":"ok"`), }, } ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Error("expected probe success with matching regexp") } } func TestHTTPProbeRegexpNoMatch(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, `{"status":"degraded"}`) })) defer srv.Close() cfg := ProbeConfig{ Timeout: 2 * time.Second, HTTP: &config.HTTPParams{ Path: "/", ResponseCodeMin: 200, ResponseCodeMax: 200, ResponseRegexp: regexp.MustCompile(`"status":"ok"`), }, } ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if ok { t.Error("expected probe failure when regexp does not match") } } func TestHTTPProbeNoRedirect(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/other", http.StatusFound) })) defer srv.Close() // Probe expects 302 — redirect is not followed. cfg := ProbeConfig{ Timeout: 2 * time.Second, HTTP: &config.HTTPParams{ Path: "/", ResponseCodeMin: 302, ResponseCodeMax: 302, }, } ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Error("expected success when probe expects 302 and server returns 302") } }