Skip to content

Commit a4e3a65

Browse files
committed
Add unit tests for request body
1 parent 2d2db35 commit a4e3a65

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

pkg/epp/handlers/request_test.go

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package handlers
18+
19+
import (
20+
"context"
21+
"strings"
22+
"testing"
23+
"time"
24+
25+
extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
26+
"github.com/google/go-cmp/cmp"
27+
corev1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
31+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
32+
"sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2"
33+
backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
34+
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
35+
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling"
36+
errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error"
37+
logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
38+
testutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
39+
)
40+
41+
const (
42+
DefaultDestinationEndpointHintMetadataNamespace = "envoy.lb" // default for --destinationEndpointHintMetadataNamespace
43+
DefaultDestinationEndpointHintKey = "x-gateway-destination-endpoint" // default for --destinationEndpointHintKey
44+
)
45+
46+
func TestHandleRequestBody(t *testing.T) {
47+
ctx := logutil.NewTestLoggerIntoContext(context.Background())
48+
49+
// Setup datastore
50+
tsModel := "food-review"
51+
modelWithTarget := "food-review-0"
52+
model1 := testutil.MakeInferenceModel("model1").
53+
CreationTimestamp(metav1.Unix(1000, 0)).
54+
ModelName(tsModel).ObjRef()
55+
model2 := testutil.MakeInferenceModel("model2").
56+
CreationTimestamp(metav1.Unix(1000, 0)).
57+
ModelName(modelWithTarget).ObjRef()
58+
pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
59+
ds := datastore.NewDatastore(t.Context(), pmf)
60+
ds.ModelSetIfOlder(model1)
61+
ds.ModelSetIfOlder(model2)
62+
63+
pool := &v1alpha2.InferencePool{
64+
Spec: v1alpha2.InferencePoolSpec{
65+
TargetPortNumber: int32(8000),
66+
Selector: map[v1alpha2.LabelKey]v1alpha2.LabelValue{
67+
"some-key": "some-val",
68+
},
69+
},
70+
}
71+
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}, Status: corev1.PodStatus{PodIP: "address-1"}}
72+
scheme := runtime.NewScheme()
73+
_ = clientgoscheme.AddToScheme(scheme)
74+
fakeClient := fake.NewClientBuilder().
75+
WithScheme(scheme).
76+
Build()
77+
if err := ds.PoolSet(ctx, fakeClient, pool); err != nil {
78+
t.Error(err, "Error while setting inference pool")
79+
}
80+
ds.PodUpdateOrAddIfNotExist(pod)
81+
82+
tests := []struct {
83+
name string
84+
reqBodyMap map[string]interface{}
85+
wantErrCode string
86+
wantReqCtx *RequestContext
87+
wantRespBody map[string]interface{}
88+
}{
89+
{
90+
name: "successful request",
91+
reqBodyMap: map[string]interface{}{
92+
"model": tsModel,
93+
"prompt": "test prompt",
94+
},
95+
wantReqCtx: &RequestContext{
96+
Model: tsModel,
97+
ResolvedTargetModel: tsModel,
98+
TargetPod: "/pod1",
99+
TargetEndpoint: "address-1:8000",
100+
},
101+
wantRespBody: map[string]interface{}{
102+
"model": tsModel,
103+
"prompt": "test prompt",
104+
},
105+
},
106+
{
107+
name: "successful request with target model",
108+
reqBodyMap: map[string]interface{}{
109+
"model": modelWithTarget,
110+
"prompt": "test prompt",
111+
},
112+
wantReqCtx: &RequestContext{
113+
Model: modelWithTarget,
114+
ResolvedTargetModel: modelWithTarget,
115+
TargetPod: "/pod1",
116+
TargetEndpoint: "address-1:8000",
117+
},
118+
wantRespBody: map[string]interface{}{
119+
"model": modelWithTarget,
120+
"prompt": "test prompt",
121+
},
122+
},
123+
{
124+
name: "no model defined, expect err",
125+
wantErrCode: errutil.BadRequest,
126+
},
127+
{
128+
name: "invalid model defined, expect err",
129+
reqBodyMap: map[string]interface{}{
130+
"model": "non-existent-model",
131+
"prompt": "test prompt",
132+
},
133+
wantErrCode: errutil.BadConfiguration,
134+
},
135+
{
136+
name: "invalid target defined, expect err",
137+
reqBodyMap: map[string]interface{}{
138+
"model": "food-review-1",
139+
"prompt": "test prompt",
140+
},
141+
wantErrCode: errutil.BadConfiguration,
142+
},
143+
}
144+
145+
for _, test := range tests {
146+
t.Run(test.name, func(t *testing.T) {
147+
server := NewStreamingServer(scheduling.NewScheduler(ds), DefaultDestinationEndpointHintMetadataNamespace, DefaultDestinationEndpointHintKey, ds)
148+
reqCtx := &RequestContext{}
149+
req := &extProcPb.ProcessingRequest{}
150+
reqCtx, err := server.HandleRequestBody(ctx, reqCtx, req, test.reqBodyMap)
151+
152+
if test.wantErrCode != "" {
153+
if err == nil {
154+
t.Fatalf("HandleRequestBody should have returned an error containing '%s', but got nil", test.wantErrCode)
155+
}
156+
if !strings.Contains(err.Error(), test.wantErrCode) {
157+
t.Fatalf("HandleRequestBody returned error '%v', which does not contain expected substring '%s'", err, test.wantErrCode)
158+
}
159+
return
160+
}
161+
162+
if err != nil {
163+
t.Fatalf("HandleRequestBody returned unexpected error: %v", err)
164+
}
165+
166+
if test.wantReqCtx != nil {
167+
if diff := cmp.Diff(test.wantReqCtx.Model, reqCtx.Model); diff != "" {
168+
t.Errorf("HandleRequestBody returned unexpected reqCtx.Model, diff(-want, +got): %v", diff)
169+
}
170+
if diff := cmp.Diff(test.wantReqCtx.ResolvedTargetModel, reqCtx.ResolvedTargetModel); diff != "" {
171+
t.Errorf("HandleRequestBody returned unexpected reqCtx.ResolvedTargetModel, diff(-want, +got): %v", diff)
172+
}
173+
if diff := cmp.Diff(test.wantReqCtx.TargetPod, reqCtx.TargetPod); diff != "" {
174+
t.Errorf("HandleRequestBody returned unexpected reqCtx.TargetPod, diff(-want, +got): %v", diff)
175+
}
176+
if diff := cmp.Diff(test.wantReqCtx.TargetEndpoint, reqCtx.TargetEndpoint); diff != "" {
177+
t.Errorf("HandleRequestBody returned unexpected reqCtx.TargetEndpoint, diff(-want, +got): %v", diff)
178+
}
179+
}
180+
})
181+
}
182+
}

0 commit comments

Comments
 (0)