Ctrl K
ComunidadGuías…

pkg/util_simulate_refuse_integration_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package pkg

import (
	"context"
	"net"
	"net/http"
	"strconv"
	"strings"
	"testing"
	"time"

	"go.keploy.io/server/v3/internal/testnet"
	"go.keploy.io/server/v3/pkg/models"
	"go.uber.org/zap"
)

// startRefuseThenServe holds a free port so connects to it are REFUSED for
// `refuse`, then listens there and serves GET /ping -> 200 {"msg":"pong"}.
// This reproduces an app container that briefly refuses connections while it is
// still coming up (the CI contention scenario the retry exists for). The port
// is held for the test's life (testnet.SharedPort), so no other socket can
// take it and answer meanwhile. stop ends the server, and the wait if the
// server has not listened yet.
func startRefuseThenServe(t *testing.T, refuse time.Duration) (baseURL string, stop func()) {
	t.Helper()
	addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(testnet.SharedPort(t)))

	mux := http.NewServeMux()
	mux.HandleFunc("/ping", func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"msg":"pong"}`))
	})
	srv := &http.Server{Handler: mux}
	done, served := make(chan struct{}), make(chan struct{})
	go func() {
		defer close(served)
		select {
		case <-time.After(refuse): // connects to addr are refused during this window
		case <-done:
			return
		}
		ln, lerr := testnet.OpenShared("tcp4", addr)
		if lerr != nil {
			t.Errorf("the server could not listen at the held port %s: %v", addr, lerr)
			return
		}
		_ = srv.Serve(ln)
	}()
	return "http://" + addr, func() { close(done); _ = srv.Close(); <-served }
}

func pingTestCase(baseURL string) *models.TestCase {
	return &models.TestCase{
		Name: "ping",
		Kind: models.HTTP,
		HTTPReq: models.HTTPReq{
			Method: "GET",
			URL:    baseURL + "/ping",
			Header: map[string]string{},
		},
		HTTPResp: models.HTTPResp{StatusCode: http.StatusOK, Body: `{"msg":"pong"}`},
	}
}

// SimulateHTTP (the real replay send path) must RECOVER a request to a port that
// refuses briefly, via the bounded connection-refused retry — proving the retry
// is wired into the production code path, not just the helper in isolation. If
// the retry is ever removed, SimulateHTTP returns the refused error and this
// test fails (red), guarding the fix end-to-end without any CI credential.
func TestSimulateHTTP_RecoversBriefConnRefused_RefuseInteg(t *testing.T) {
	baseURL, stop := startRefuseThenServe(t, 300*time.Millisecond)
	defer stop()
	resp, err := SimulateHTTP(context.Background(), pingTestCase(baseURL), "test-set", zap.NewNop(), SimulationConfig{APITimeout: 10})
	if err != nil {
		t.Fatalf("expected recovery via the connection-refused retry, got error: %v", err)
	}
	if resp == nil || resp.StatusCode != http.StatusOK || !strings.Contains(resp.Body, "pong") {
		t.Fatalf("expected 200 pong, got %+v", resp)
	}
}

// A refuse longer than the ~900ms retry budget must FAIL — the retry is bounded
// and must never fabricate a pass for a genuinely-down app.
func TestSimulateHTTP_LongConnRefusedFailsBounded_RefuseInteg(t *testing.T) {
	baseURL, stop := startRefuseThenServe(t, 3*time.Second)
	defer stop()
	_, err := SimulateHTTP(context.Background(), pingTestCase(baseURL), "test-set", zap.NewNop(), SimulationConfig{APITimeout: 10})
	if err == nil {
		t.Fatal("expected a connection error after the retry budget exhausts (must not fabricate a pass)")
	}
}