Documentation
¶
Overview ¶
Package httpclient provides HTTP, HTTPS, and mTLS assertions for explicit test endpoints.
Index ¶
- type Client
- type Config
- type EchoBody
- type PollTimeoutSnapshot
- type Request
- func (r *Request) Do(ctx context.Context) (*Response, error)
- func (r *Request) ExpectEchoEnvironment(t *testing.T, want string) *Response
- func (r *Request) ExpectEchoHeader(t *testing.T, name, want string) *Response
- func (r *Request) ExpectEchoPath(t *testing.T, want string) *Response
- func (r *Request) ExpectHeader(t *testing.T, name, want string) *Response
- func (r *Request) ExpectMatching(t *testing.T, description string, predicate func(*Response) bool) *Response
- func (r *Request) ExpectOK(t *testing.T) *Response
- func (r *Request) ExpectRedirect(t *testing.T, want string) *Response
- func (r *Request) ExpectStatus(t *testing.T, code int) *Response
- func (r *Request) WithBasicAuth(user, password string) *Request
- func (r *Request) WithBody(body string) *Request
- func (r *Request) WithChunkedBody(body string) *Request
- func (r *Request) WithClientCert(certPEM, keyPEM, caPEM []byte) *Request
- func (r *Request) WithHeader(name, value string) *Request
- func (r *Request) WithHostHeader(host string) *Request
- func (r *Request) WithMethod(method string) *Request
- type Response
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a fluent HTTP client targeting an explicit test endpoint. One Client may serve many Requests; concurrent use is safe.
func (*Client) CloseIdleConnections ¶
func (c *Client) CloseIdleConnections()
CloseIdleConnections drops the shared transport's pooled keepalive connections so the next request dials a fresh one. Useful when polling for a not-yet-live route across a reload: a request that 404s on the pre-change HAProxy worker pins a keepalive connection to that (draining) worker, which keeps answering with the old config until it closes; forcing a fresh dial lets the retry reach a current worker generation.
func (*Client) GET ¶
GET builds an HTTP GET against http://<endpointHost>:<httpPort>/<path> with Host: <host>.
func (*Client) HTTPS ¶
HTTPS builds an HTTPS GET against https://<host>/<path>. The shared transport's DialContext rewrites :443 to the kind HTTPS NodePort, so the TLS SNI carries the original host. Equivalent to:
curl --resolve <host>:443:<endpointHost>:<httpsPort> https://<host><path>
func (*Client) PeerCertificate ¶
PeerCertificate observes the served leaf independently of certificate trust.
type EchoBody ¶
type EchoBody struct {
// Host is the value of the Host header echo-server saw.
Host string `json:"host"`
// Method is the HTTP method (GET, POST, ...).
Method string `json:"method"`
// Path is the request path as the upstream backend received it. After
// HAProxy URL rewriting this is the rewritten path, not what the client
// sent.
Path string `json:"path"`
// Headers maps lower-cased header names to their first value. Useful for
// asserting things like X-Forwarded-For or X-Auth-User without parsing
// raw HTTP.
Headers map[string]string `json:"-"`
// Environment is the value of the ENVIRONMENT env var the echo-server
// pod was started with. The dev env uses this to distinguish
// echo-server (no ENVIRONMENT) from echo-server-v2 (ENVIRONMENT=v2)
// for weighted-routing tests.
Environment string `json:"-"`
// PodHostname is the serving pod's hostname — the container HOSTNAME env,
// which Kubernetes sets to the pod name. Distinct values across requests
// identify distinct backend pods; Host is the request Host header (constant
// across requests to one Ingress), so use this to tell pods apart.
PodHostname string `json:"-"`
}
EchoBody is a typed view of the JSON the echo-server emits.
The dev-env echo-server is jmalloc/echo-server, which serializes the incoming request as JSON; tests assert against structured fields rather than substring-matching the response body.
Only fields the test suite asserts against are mapped.
type PollTimeoutSnapshot ¶
PollTimeoutSnapshot is the callback invoked when a poll exhausts its retry budget. Implementations receive the test handle, a human-readable description of what was being polled, the last response observed (may be nil if every attempt errored), and the last error returned by the inner Do (may be nil if responses came back but the predicate never matched).
Implementations MUST be best-effort and side-effect-only: they cannot influence the test outcome (the timeout error still propagates) and they must not call t.FailNow / t.Fatalf themselves, which would short-circuit the existing diagnostic chain.
type Request ¶
type Request struct {
// contains filtered or unexported fields
}
Request is a fluent HTTP request builder. Build a Request with one of the Client GET/HTTPS factory methods, chain With* mutators, then terminate with one of the Expect* sinks (which retry under the client's wait budget) or Do (which makes a single request).
func (*Request) Do ¶
Do executes the request once and returns the response. Tests typically use the Expect* sinks instead; Do is the escape hatch for assertions that need to inspect Response directly (e.g., counting backend hits across requests).
func (*Request) ExpectEchoEnvironment ¶
ExpectEchoEnvironment asserts that the upstream backend (echo-server) was started with ENVIRONMENT=want, retrying under the client's wait budget. The dev-env echo-server pods use ENVIRONMENT="" (the default v1 pod) or ENVIRONMENT="v2" (the v2 pod) to distinguish backends for routing tests; passing "" verifies routing landed on the default backend.
Polling on the echo'd Environment (rather than ExpectOK followed by a manual resp.Echo.Environment check) closes the route-readiness race: during HTTPRoute provisioning HAProxy can return 200 from a partial rule state (e.g. the host map is populated before req.gw_routeId is set to the compound rule-id needed for -m str matches), so a request that races ahead lands on a catch-all rule and gets the wrong backend. ExpectOK accepts the wrong-backend 200; ExpectEchoEnvironment retries until the backend matches.
func (*Request) ExpectEchoHeader ¶
ExpectEchoHeader asserts that the upstream backend (echo-server) saw a request header `name` with value containing `want`. Used to verify HAProxy / SPOA forwarded an auth-derived header through to the backend.
Polling on the echo'd header (rather than just polling on a 200 with ExpectOK and then asserting) is the correct shape: the controller can land a config that returns 200 from the auth flow before the matching `http-request set-header` rules that forward auth-response headers to the backend are live, so a request that races ahead can see 200 with no forwarded header. Polling closes that window.
func (*Request) ExpectEchoPath ¶
ExpectEchoPath asserts that the upstream backend (echo-server) received the request at path `want`, retrying under the client's wait budget. Used by URL-rewriting tests to verify the post-rewrite path that reached the backend, not the path the client sent.
Same race rationale as ExpectEchoEnvironment: ExpectOK can accept a 200 returned before the rewrite rule is live, so polling on the echo'd path is the correct shape.
func (*Request) ExpectHeader ¶
ExpectHeader asserts a response header contains the given value (substring match, not equality). Retries under the client's wait budget.
func (*Request) ExpectMatching ¶
func (r *Request) ExpectMatching(t *testing.T, description string, predicate func(*Response) bool) *Response
ExpectMatching asserts the response satisfies a caller-supplied predicate, retrying under the client's wait budget. Use this when a single assertion depends on multiple response signals being simultaneously consistent (e.g. "status=401 AND has WWW-Authenticate header" — polling on either signal alone leaves a race window where one rule has landed but the other has not). The description is used for diagnostic messages on timeout.
func (*Request) ExpectOK ¶
ExpectOK asserts the request returns HTTP 200, retrying under the client's wait budget. Failures call t.Fatalf with the last response (or last error) for diagnostic context.
func (*Request) ExpectRedirect ¶
ExpectRedirect asserts the response is a redirect (3xx) with a Location header containing want.
func (*Request) ExpectStatus ¶
ExpectStatus asserts the response status equals code, retrying under the client's wait budget.
func (*Request) WithBasicAuth ¶
WithBasicAuth attaches HTTP Basic credentials.
func (*Request) WithBody ¶
WithBody attaches a request body. It intentionally does not imply a method or Content-Type; tests set those explicitly to keep request shape visible.
func (*Request) WithChunkedBody ¶
WithChunkedBody attaches a request body without a Content-Length so Go sends it using HTTP/1.1 chunked transfer encoding. Use this when tests need to exercise proxy behavior for unknown-length request bodies.
func (*Request) WithClientCert ¶
WithClientCert configures mTLS for HTTPS requests. The CA is used to verify the server certificate (replacing the default insecure-skip-verify). Returns the request unchanged if cert parsing fails; the failure surfaces from Do.
func (*Request) WithHeader ¶
WithHeader adds a header. Subsequent calls with the same name append.
func (*Request) WithHostHeader ¶ added in v0.8.0
WithHostHeader sends host as the Host header while the TLS SNI keeps the request's original host.
func (*Request) WithMethod ¶
WithMethod overrides the HTTP method (default GET).
type Response ¶
type Response struct {
// Status is the HTTP status code.
Status int
// Header is the response headers, normalized via http.Header.
Header http.Header
// Body is the raw response body bytes.
Body []byte
// Echo is the parsed echo-server JSON, or nil if the body wasn't
// recognizable echo-server output.
Echo *EchoBody
}
Response is the result of a single HTTP exchange. Tests typically use the Expect* sinks instead, but Do returns Response for assertions that need custom logic.