118 lines
3.8 KiB
Go
118 lines
3.8 KiB
Go
// 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:<port>" 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
|
||
|
|
}
|