Skip to content

Commit 55859fa

Browse files
committed
test(integration): expect drop-reason header on no-candidates rejection
The director sets x-llm-d-request-dropped-reason: rejected-no-endpoints on the 503 returned when subsetting leaves no endpoint candidates. Update the hermetic integration fixtures to expect the header. Signed-off-by: Luke Van Drie <lukevandrie@google.com>
1 parent 08954b8 commit 55859fa

4 files changed

Lines changed: 31 additions & 11 deletions

File tree

test/integration/epp/common_tests.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
2727
envoyTypePb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
2828

29+
errcommon "github.com/llm-d/llm-d-router/pkg/common/error"
2930
reqcommon "github.com/llm-d/llm-d-router/pkg/common/request"
3031
integration "github.com/llm-d/llm-d-router/test/integration"
3132
)
@@ -208,6 +209,17 @@ func ExpectReject(code envoyTypePb.StatusCode, msg string) []*extProcPb.Processi
208209
return integration.NewImmediateErrorResponse(code, msg)
209210
}
210211

212+
// ExpectRejectWithDropReason asserts that the EPP immediately rejected the request with the given code
213+
// and message, and that the response carries the drop-reason header.
214+
func ExpectRejectWithDropReason(code envoyTypePb.StatusCode, msg string, reason errcommon.RequestDroppedReason) []*extProcPb.ProcessingResponse {
215+
return integration.NewImmediateErrorResponse(code, msg, &envoyCorev3.HeaderValueOption{
216+
Header: &envoyCorev3.HeaderValue{
217+
Key: errcommon.RequestDroppedReasonHeaderKey,
218+
RawValue: []byte(reason),
219+
},
220+
})
221+
}
222+
211223
// ExpectBufferResp asserts that the EPP buffers the response and rewrites the body.
212224
// This uses the shared primitive but adds EPP-specific headers we expect.
213225
func ExpectBufferResp(body string, contentType string) []*extProcPb.ProcessingResponse {

test/integration/epp/grpc_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/stretchr/testify/require"
2828
"google.golang.org/protobuf/testing/protocmp"
2929

30+
errcommon "github.com/llm-d/llm-d-router/pkg/common/error"
3031
reqcommon "github.com/llm-d/llm-d-router/pkg/common/request"
3132
pb "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/requesthandling/parsers/vllmgrpc/api/gen"
3233
integration "github.com/llm-d/llm-d-router/test/integration"
@@ -204,8 +205,9 @@ func TestFullDuplexStreamed_GRPC_KubeInferenceObjectiveRequest(t *testing.T) {
204205
P(0, 0, 0.2, "foo"),
205206
P(1, 0, 0.1, "foo", modelSQLLoraTarget),
206207
},
207-
wantResponses: ExpectReject(envoyTypePb.StatusCode_ServiceUnavailable,
208-
"inference error: ServiceUnavailable - failed to find endpoint candidates for serving the request"),
208+
wantResponses: ExpectRejectWithDropReason(envoyTypePb.StatusCode_ServiceUnavailable,
209+
"inference error: ServiceUnavailable - failed to find endpoint candidates for serving the request",
210+
errcommon.RequestDroppedReasonNoEndpoints),
209211
},
210212

211213
// --- Response Processing (Non-streaming) ---

test/integration/epp/hermetic_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
4545

4646
"github.com/llm-d/llm-d-router/apix/v1alpha2"
47+
errcommon "github.com/llm-d/llm-d-router/pkg/common/error"
4748
reqcommon "github.com/llm-d/llm-d-router/pkg/common/request"
4849
"github.com/llm-d/llm-d-router/pkg/epp/metadata"
4950
"github.com/llm-d/llm-d-router/pkg/epp/metrics"
@@ -293,8 +294,9 @@ dataLayer:
293294
P(0, 0, 0.2, "foo"),
294295
P(1, 0, 0.1, "foo", modelSQLLoraTarget),
295296
},
296-
wantResponses: ExpectReject(envoyTypePb.StatusCode_ServiceUnavailable,
297-
"inference error: ServiceUnavailable - failed to find endpoint candidates for serving the request"),
297+
wantResponses: ExpectRejectWithDropReason(envoyTypePb.StatusCode_ServiceUnavailable,
298+
"inference error: ServiceUnavailable - failed to find endpoint candidates for serving the request",
299+
errcommon.RequestDroppedReasonNoEndpoints),
298300
},
299301

300302
// --- Request Modification (Passthrough & Rewrite) ---

test/integration/util.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,16 +441,20 @@ func NewResponseStreamChunk(body string, endOfStream bool) *extProcPb.Processing
441441
}
442442
}
443443

444-
// NewImmediateErrorResponse creates a response that immediately terminates the request with a specific HTTP status code
445-
// and body.
444+
// NewImmediateErrorResponse creates a response that immediately terminates the request with a specific HTTP status code,
445+
// body, and optional response headers.
446446
// Use this for testing Load Shedding (503), Rate Limiting (429), or Bad Request (400) logic.
447-
func NewImmediateErrorResponse(code envoyTypePb.StatusCode, body string) []*extProcPb.ProcessingResponse {
447+
func NewImmediateErrorResponse(code envoyTypePb.StatusCode, body string, headers ...*envoyCorev3.HeaderValueOption) []*extProcPb.ProcessingResponse {
448+
immediateResponse := &extProcPb.ImmediateResponse{
449+
Status: &envoyTypePb.HttpStatus{Code: code},
450+
Body: []byte(body),
451+
}
452+
if len(headers) > 0 {
453+
immediateResponse.Headers = &extProcPb.HeaderMutation{SetHeaders: headers}
454+
}
448455
return []*extProcPb.ProcessingResponse{{
449456
Response: &extProcPb.ProcessingResponse_ImmediateResponse{
450-
ImmediateResponse: &extProcPb.ImmediateResponse{
451-
Status: &envoyTypePb.HttpStatus{Code: code},
452-
Body: []byte(body),
453-
},
457+
ImmediateResponse: immediateResponse,
454458
},
455459
}}
456460
}

0 commit comments

Comments
 (0)