Files
caddy/caddytest/integration/intercept_test.go
long.black 16235cced5 intercept: fix replace_status being silently dropped (#7810)
* test: add failing tests for intercept replace_status (#7805)

Add integration tests that verify replace_status actually substitutes
the HTTP status code sent to the client. Currently these tests fail
because replace_status is silently a no-op due to value-receiver
boxing and shouldBuffer returning false.

Tests added:
- TestInterceptReplaceStatusWithMatcher: 500 -> 200 with @err matcher
- TestInterceptReplaceStatusWithoutMatcher: 403 -> 200 unconditionally
- TestInterceptReplaceStatusNotMatched: 200 passes through unchanged

* fix: make intercept replace_status actually substitute the status code

Fix #7805: replace_status was silently a no-op because:
1. shouldBuffer returned false when a replacement status was set,
   causing the original status to be streamed directly to the wire
2. The value-receiver WriteHeader method operated on a stale copy

The fix:
- shouldBuffer now returns true when replace_status matches, so
  the response is buffered instead of streamed
- After next.ServeHTTP returns, if routes are nil (replace_status
  only), write the substituted status and buffered body to the client

The interceptedResponseHandler.WriteHeader substitution branch is no
longer needed for this path since substitution happens post-ServeHTTP.

* refactor: remove dead WriteHeader method and resolved TODO

The value-receiver WriteHeader on interceptedResponseHandler was
unreachable dead code — the substitution is now handled post-ServeHTTP
via buffering. Remove it along with the TODO comment that noted
status code replacement was unfinished.

* style: apply nit suggestions from dunglas code review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:50:58 -06:00

110 lines
2.4 KiB
Go

package integration
import (
"testing"
"github.com/caddyserver/caddy/v2/caddytest"
)
func TestIntercept(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
grace_period 1ns
}
localhost:9080 {
respond /intercept "I'm a teapot" 408
header /intercept To-Intercept ok
respond /no-intercept "I'm not a teapot"
intercept {
@teapot status 408
handle_response @teapot {
header /intercept intercepted {resp.header.To-Intercept}
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
}
}
}
`, "caddyfile")
r, _ := tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
if r.Header.Get("intercepted") != "ok" {
t.Fatalf(`header "intercepted" value is not "ok": %s`, r.Header.Get("intercepted"))
}
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
}
func TestInterceptReplaceStatusWithMatcher(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
grace_period 1ns
}
localhost:9080 {
respond /error "boom" 500
intercept {
@err status 5xx
replace_status @err 200
}
}
`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/error", 200, "boom")
}
func TestInterceptReplaceStatusWithoutMatcher(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
grace_period 1ns
}
localhost:9080 {
respond /forbidden "denied" 403
intercept {
replace_status 200
}
}
`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/forbidden", 200, "denied")
}
func TestInterceptReplaceStatusNotMatched(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
grace_period 1ns
}
localhost:9080 {
respond /ok "all good" 200
intercept {
@err status 5xx
replace_status @err 503
}
}
`, "caddyfile")
// 200 does not match @err (5xx), so status should pass through unchanged
tester.AssertGetResponse("http://localhost:9080/ok", 200, "all good")
}