mirror of
https://github.com/hanzo-ml/kserve.git
synced 2026-10-30 20:53:49 +00:00
217 lines
6.2 KiB
Go
217 lines
6.2 KiB
Go
/*
|
|
Copyright 2025 The KServe Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package testing
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
"k8s.io/utils/ptr"
|
|
|
|
"github.com/onsi/gomega/types"
|
|
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
|
|
)
|
|
|
|
// extractHTTPRoute safely extracts HTTPRoute from either pointer or value type
|
|
func extractHTTPRoute(actual any) (*gwapiv1.HTTPRoute, error) {
|
|
switch v := actual.(type) {
|
|
case gwapiv1.HTTPRouteSpec:
|
|
return &gwapiv1.HTTPRoute{Spec: v}, nil
|
|
case *gwapiv1.HTTPRouteSpec:
|
|
if v == nil {
|
|
return nil, errors.New("expected non-nil gatewayapi.HTTPRouteSpec, but got nil")
|
|
}
|
|
return &gwapiv1.HTTPRoute{Spec: *v}, nil
|
|
case *gwapiv1.HTTPRoute:
|
|
if v == nil {
|
|
return nil, errors.New("expected non-nil gatewayapi.HTTPRoute, but got nil")
|
|
}
|
|
return v, nil
|
|
case gwapiv1.HTTPRoute:
|
|
return &v, nil
|
|
default:
|
|
return nil, fmt.Errorf("expected gatewayapi.HTTPRoute gatewayapi.HTTPRouteSpec, but got %T", actual)
|
|
}
|
|
}
|
|
|
|
// HaveGatewayRefs returns a matcher that checks if an HTTPRoute has the specified gateway parent refs
|
|
func HaveGatewayRefs(expectedGateways ...gwapiv1.ParentReference) types.GomegaMatcher {
|
|
return &haveGatewayRefsMatcher{
|
|
expected: expectedGateways,
|
|
}
|
|
}
|
|
|
|
type haveGatewayRefsMatcher struct {
|
|
expected []gwapiv1.ParentReference
|
|
actualParentRefs []gwapiv1.ParentReference
|
|
}
|
|
|
|
func (matcher *haveGatewayRefsMatcher) Match(actual any) (success bool, err error) {
|
|
httpRoute, err := extractHTTPRoute(actual)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
matcher.actualParentRefs = httpRoute.Spec.ParentRefs
|
|
|
|
if len(matcher.actualParentRefs) != len(matcher.expected) {
|
|
return false, nil
|
|
}
|
|
|
|
for _, want := range matcher.expected {
|
|
found := false
|
|
for _, got := range matcher.actualParentRefs {
|
|
if parentRefMatches(want, got) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// parentRefMatches checks whether two ParentReferences match on Name, Namespace,
|
|
// and SectionName. Nil fields on the expected ref are treated as "don't care".
|
|
func parentRefMatches(want, got gwapiv1.ParentReference) bool {
|
|
if want.Name != got.Name {
|
|
return false
|
|
}
|
|
if want.Namespace != nil && ptr.Deref(want.Namespace, "") != ptr.Deref(got.Namespace, "") {
|
|
return false
|
|
}
|
|
if want.SectionName != nil && ptr.Deref(want.SectionName, "") != ptr.Deref(got.SectionName, "") {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (matcher *haveGatewayRefsMatcher) FailureMessage(actual any) string {
|
|
return fmt.Sprintf("Expected %T to have gateway refs %v, but found %v",
|
|
actual, matcher.expected, matcher.actualParentRefs)
|
|
}
|
|
|
|
func (matcher *haveGatewayRefsMatcher) NegatedFailureMessage(actual any) string {
|
|
return fmt.Sprintf("Expected %T to not have gateway refs %v, but they were found",
|
|
actual, matcher.expected)
|
|
}
|
|
|
|
// HaveBackendRefs returns a matcher that checks if an HTTPRoute contains the specified backend refs.
|
|
// It uses "contains" semantics: the route may have additional backend refs beyond those listed.
|
|
func HaveBackendRefs(backends ...gwapiv1.HTTPBackendRef) types.GomegaMatcher {
|
|
return &haveBackendRefsMatcher{
|
|
expectedBackendRefs: backends,
|
|
}
|
|
}
|
|
|
|
type haveBackendRefsMatcher struct {
|
|
expectedBackendRefs []gwapiv1.HTTPBackendRef
|
|
actualBackendRefs []gwapiv1.HTTPBackendRef
|
|
}
|
|
|
|
func (matcher *haveBackendRefsMatcher) Match(actual any) (success bool, err error) {
|
|
httpRoute, err := extractHTTPRoute(actual)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, rule := range httpRoute.Spec.Rules {
|
|
matcher.actualBackendRefs = append(matcher.actualBackendRefs, rule.BackendRefs...)
|
|
}
|
|
|
|
for _, want := range matcher.expectedBackendRefs {
|
|
found := false
|
|
for _, got := range matcher.actualBackendRefs {
|
|
if equality.Semantic.DeepEqual(want, got) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (matcher *haveBackendRefsMatcher) FailureMessage(actual any) string {
|
|
expected, _ := json.MarshalIndent(matcher.expectedBackendRefs, "", " ")
|
|
got, _ := json.MarshalIndent(matcher.actualBackendRefs, "", " ")
|
|
return fmt.Sprintf("Expected %T to have backend refs:\n%s\nbut found:\n%s",
|
|
actual, expected, got)
|
|
}
|
|
|
|
func (matcher *haveBackendRefsMatcher) NegatedFailureMessage(actual any) string {
|
|
expected, _ := json.MarshalIndent(matcher.expectedBackendRefs, "", " ")
|
|
got, _ := json.MarshalIndent(matcher.actualBackendRefs, "", " ")
|
|
return fmt.Sprintf("Expected %T to not have backend refs:\n%s, got:\n%s",
|
|
actual, expected, got)
|
|
}
|
|
|
|
// HaveHeaderMatch returns a matcher that checks if any rule in an HTTPRoute
|
|
// contains a match with the specified header name and value.
|
|
func HaveHeaderMatch(headerName, headerValue string) types.GomegaMatcher {
|
|
return &haveHeaderMatchMatcher{
|
|
headerName: headerName,
|
|
headerValue: headerValue,
|
|
}
|
|
}
|
|
|
|
type haveHeaderMatchMatcher struct {
|
|
headerName string
|
|
headerValue string
|
|
allHeaders []gwapiv1.HTTPHeaderMatch
|
|
}
|
|
|
|
func (m *haveHeaderMatchMatcher) Match(actual any) (bool, error) {
|
|
httpRoute, err := extractHTTPRoute(actual)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
m.allHeaders = nil
|
|
for _, rule := range httpRoute.Spec.Rules {
|
|
for _, match := range rule.Matches {
|
|
for _, h := range match.Headers {
|
|
m.allHeaders = append(m.allHeaders, h)
|
|
if string(h.Name) == m.headerName && h.Value == m.headerValue {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (m *haveHeaderMatchMatcher) FailureMessage(actual any) string {
|
|
got, _ := json.MarshalIndent(m.allHeaders, "", " ")
|
|
return fmt.Sprintf("Expected %T to have header match %s=%s, but found:\n%s",
|
|
actual, m.headerName, m.headerValue, got)
|
|
}
|
|
|
|
func (m *haveHeaderMatchMatcher) NegatedFailureMessage(actual any) string {
|
|
return fmt.Sprintf("Expected %T to not have header match %s=%s, but it was found",
|
|
actual, m.headerName, m.headerValue)
|
|
}
|