// Package testsupport provides the local-development stub that every upstream // in testdata/config/harness.yaml targets. It exists so tests under // internal/proxy (future) and here can verify streaming semantics without a // Kubernetes cluster or real credentials. // // The harness binds ONE http.Server on 127.0.0.1:9080 by default — this matches // the addresses baked into testdata/config/harness.yaml so that fixture is valid // before any code runs, and no network traffic leaves localhost during verification. package testsupport import ( "fmt" "net" "net/http" "os" "sync" ) const defaultLocalPort = "9080" // DefaultConfigPath is the committed fixture that points every upstream at the // same local port; clients load it via config.LoadRoutesFromFile. Tests can // override the path if they want a different schema. const DefaultConfigPath = "testdata/config/harness.yaml" // Snapshot describes a running stub server. type Snapshot struct { BaseURL string Client *http.Client } // BaseAddr returns the host:port form of the bound address, no scheme. func (s *Snapshot) BaseAddr() string { return s.BaseURL[len("http://"):] } // URL joins the stub base URL with one of the Path* constants. func (s *Snapshot) URL(path string) string { return s.BaseURL + path } // harness is the singleton that owns the stub server for a single process. type harness struct { mu sync.Mutex srv *http.Server // nil when not running; set exactly once per Close/Start cycle addr string // bound address, valid only while srv != nil } // global is the singleton used by tests. A fresh server is bound lazily on // first Start() and shared thereafter for the lifetime of the harness process // (usually a single TestMain run). var global = &harness{} // Start binds the stub server if it is not already running and returns a // Snapshot describing it. It is safe to call from multiple tests; the second // and later calls return the already-bound server. func Start() (*Snapshot, error) { return global.Start() } // Close stops the stub server. Safe to call multiple times. func Close() { global.Close() } func (h *harness) Start() (*Snapshot, error) { h.mu.Lock() defer h.mu.Unlock() if h.srv != nil { return h.snapshotLocked(), nil } // Listen separately from Serve. srv.ListenAndServe would block until // shutdown, so Start could never return; binding first also guarantees the // port is accepting connections by the time the caller gets the Snapshot. ln, err := net.Listen("tcp", defaultStubAddr()) if err != nil { return nil, fmt.Errorf("bind stub server at %s: %w", defaultStubAddr(), err) } srv := &http.Server{Handler: newStubHandler()} h.srv = srv h.addr = ln.Addr().String() // Serve always returns a non-nil error; after Close that error is // ErrServerClosed, which is the expected path and not worth reporting. go func() { _ = srv.Serve(ln) }() return h.snapshotLocked(), nil } // snapshotLocked builds a Snapshot for the running server. Caller holds h.mu. func (h *harness) snapshotLocked() *Snapshot { return &Snapshot{BaseURL: "http://" + h.addr, Client: http.DefaultClient} } // Close stops the underlying stub server. Safe to call on any harness instance // or multiple times — it is idempotent within a process. func (h *harness) Close() { h.mu.Lock() defer h.mu.Unlock() if h.srv == nil { return } s := h.srv h.srv = nil h.addr = "" _ = s.Close() // best-effort shutdown; test output not dependent on it } // defaultStubAddr constructs "127.0.0.1:" honoring HARNESS_STUB_PORT if // set, falling back to 9080 — which matches every address in the committed YAML // fixture. Set only when you need parallel test runs within a single process; // otherwise leave unset. func defaultStubAddr() string { port := os.Getenv("HARNESS_STUB_PORT") if port == "" { port = defaultLocalPort } return "127.0.0.1:" + port }