@@ -135,14 +135,14 @@ func TestNewForwarder_DisableKeepAlives_OpensNewConnectionPerRequest(t *testing.
135135}
136136
137137// NewForwarder defaults reflect the configured wire.go constants. /ready keeps
138- // a 60s budget (matches the platform hook timeout); the remaining hooks default
139- // to 1s. Changing these defaults is a deliberate behavior change — the test
140- // failure is intentional.
138+ // a 60s budget, /validate gets a 10s smoke-test budget, and runtime hooks
139+ // default to 1s. Changing these defaults is a deliberate behavior change — the
140+ // test failure is intentional.
141141func TestNewForwarder_Defaults (t * testing.T ) {
142142 f := NewForwarder (8080 , defaultForwardTimeout , defaultReadyTimeout , defaultValidateTimeout )
143143 assert .Equal (t , 1 * time .Second , f .forwardTimeout , "forwardTimeout default must be 1s" )
144144 assert .Equal (t , 60 * time .Second , f .readyTimeout , "readyTimeout default must be 60s (matches platform /ready hook timeout)" )
145- assert .Equal (t , 1 * time .Second , f .validateTimeout , "validateTimeout default must be 1s " )
145+ assert .Equal (t , 10 * time .Second , f .validateTimeout , "validateTimeout default must be 10s " )
146146 assert .Equal (t , defaultMaxResponseBodyBytes , f .maxResponseBodyBytes , "maxResponseBodyBytes default must be 1 MiB" )
147147 tr , ok := f .client .Transport .(* http.Transport )
148148 require .True (t , ok , "transport must be *http.Transport" )
@@ -239,7 +239,8 @@ func TestForwarder_PassThroughWaiting_MirrorsStatusAndBody(t *testing.T) {
239239// PassThroughWaiting must honor the timeout it receives. The caller (server.go)
240240// passes readyTimeout or validateTimeout — PassThroughWaiting must not apply
241241// any other field. This test uses a 50ms timeout against a 200ms upstream: the
242- // context expires and the call returns 504.
242+ // context expires and the call returns 503 (per the /ready and /validate hook
243+ // contract, only 200 and 503 are meaningful — never 504).
243244func TestForwarder_PassThroughWaiting_HonorsProvidedTimeout (t * testing.T ) {
244245 srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
245246 time .Sleep (200 * time .Millisecond )
@@ -251,8 +252,8 @@ func TestForwarder_PassThroughWaiting_HonorsProvidedTimeout(t *testing.T) {
251252 resp := f .PassThroughWaiting (50 * time .Millisecond , "/ready" , nil , nil )
252253 require .NotNil (t , resp )
253254 defer resp .Body .Close ()
254- assert .Equal (t , http .StatusGatewayTimeout , resp .StatusCode ,
255- "PassThroughWaiting must time out on the provided timeout" )
255+ assert .Equal (t , http .StatusServiceUnavailable , resp .StatusCode ,
256+ "PassThroughWaiting must time out on the provided timeout and return 503, never 504 " )
256257}
257258
258259// On the happy path the response body is wrapped by cancelOnCloseReader.
@@ -301,29 +302,33 @@ func TestWrapResponseBody_CapZero_DisablesCap(t *testing.T) {
301302 "cap=0 must read the full body — LimitReader must NOT be applied" )
302303}
303304
304- // PassThroughWaiting retries TCP dials until the context expires when the port
305- // is unbound, so the response is always 504 (deadline exceeded) — never 503.
306- // This is distinct from PassThrough which returns 503 on the very first
307- // connect-refused error. The behavioral difference is intentional: /ready and
308- // /validate must wait for the user app to start, not fail-fast on a transient
309- // dial error .
310- func TestForwarder_PassThroughWaiting_UnboundPort_RetriesUntilTimeout504 (t * testing.T ) {
305+ // PassThroughWaiting makes a single reachability dial attempt, bounded by
306+ // dialCheckTimeout — it must not retry/poll, and must not wait out the
307+ // timeout parameter, before answering. Per the hook contract the platform
308+ // (not the hook) owns the retry loop for /ready and /validate, so an
309+ // unreachable app must return 503 fast, the same fail-fast contract
310+ // PassThrough already applies to connect-refused errors .
311+ func TestForwarder_PassThroughWaiting_UnboundPort_FailsFastWith503 (t * testing.T ) {
311312 f := & Forwarder {
312- target : "http://127.0.0.1:1" , // unbound — every dial attempt is refused
313+ target : "http://127.0.0.1:1" , // unbound — dial attempt is refused
313314 client : & http.Client {},
314315 }
316+ start := time .Now ()
315317 resp := f .PassThroughWaiting (150 * time .Millisecond , "/ready" , nil , nil )
318+ elapsed := time .Since (start )
316319 require .NotNil (t , resp )
317320 defer resp .Body .Close ()
318- assert .Equal (t , http .StatusGatewayTimeout , resp .StatusCode ,
319- "unbound port must retry until timeout (504), not immediately 503 like PassThrough" )
321+ assert .Equal (t , http .StatusServiceUnavailable , resp .StatusCode ,
322+ "unreachable app must return 503, not 504" )
323+ assert .Less (t , elapsed , 100 * time .Millisecond ,
324+ "an unreachable app must fail the single dial attempt fast, not retry until the timeout parameter (150ms) or dialCheckTimeout (200ms) elapse" )
320325}
321326
322- // passThroughWaiting buffers the request body before the TCP wait so it is
323- // still available after waitForUserApp returns. Without buffering, the body
324- // reader would be exhausted during the wait loop and f.do would send an empty
325- // body to the user app. This pins the buffer-then-forward contract.
326- func TestForwarder_PassThroughWaiting_ForwardsBodyAfterTCPWait (t * testing.T ) {
327+ // passThroughWaiting buffers the request body before the reachability check
328+ // so it is still available once that check returns. Without buffering, the
329+ // body reader would be exhausted during the dial attempt and f.do would send
330+ // an empty body to the user app. This pins the buffer-then-forward contract.
331+ func TestForwarder_PassThroughWaiting_ForwardsBodyAfterReachabilityCheck (t * testing.T ) {
327332 received := make (chan []byte , 1 )
328333 srv := httptest .NewServer (http .HandlerFunc (func (_ http.ResponseWriter , r * http.Request ) {
329334 b , _ := io .ReadAll (r .Body )
@@ -353,7 +358,8 @@ func (errReader) Read([]byte) (int, error) { return 0, io.ErrUnexpectedEOF }
353358
354359// A failed read of the inbound body must NOT forward a partial body to the
355360// user app. PassThroughWaiting returns 500 (server-side read failure) and
356- // short-circuits before the TCP wait so no partial body reaches the user app.
361+ // short-circuits before the reachability check so no partial body reaches the
362+ // user app.
357363func TestForwarder_PassThroughWaiting_BodyReadError_Returns500 (t * testing.T ) {
358364 var reached atomic.Int32
359365 srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
0 commit comments