karmada/pkg/webhook/configuration/validating_test.go

306 lines
8.7 KiB
Go

/*
Copyright 2021 The Karmada 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 configuration
import (
"fmt"
"strings"
"testing"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
)
func strPtr(s string) *string { return &s }
func int32Ptr(i int32) *int32 { return &i }
func TestHasWildcardOperation(t *testing.T) {
tests := []struct {
name string
operations []configv1alpha1.InterpreterOperation
expected bool
}{
{
name: "no operations",
operations: nil,
expected: false,
},
{
name: "has wildcard operation",
operations: []configv1alpha1.InterpreterOperation{
configv1alpha1.InterpreterOperationAll,
},
expected: true,
},
{
name: "no have wildcard operation",
operations: []configv1alpha1.InterpreterOperation{
configv1alpha1.InterpreterOperationInterpretReplica,
},
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := hasWildcardOperation(test.operations)
if result != test.expected {
t.Errorf("Case: %s failed: expected get %v, but got %v", test.name, test.expected, result)
}
})
}
}
func TestIsAcceptedExploreReviewVersions(t *testing.T) {
tests := []struct {
name string
version string
expected bool
}{
{
name: "is accepted interpreter context versions",
version: acceptedInterpreterContextVersions[0],
expected: true,
},
{
name: "is not accepted interpreter context versions",
version: "",
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := isAcceptedExploreReviewVersions(test.version)
if result != test.expected {
t.Errorf("Case: %s failed: expected get %v, but got %v", test.name, test.expected, result)
}
})
}
}
func TestValidateRuleWithOperations(t *testing.T) {
fldPath := field.NewPath("webhooks").Child("rules")
notSupportedOperation := []configv1alpha1.InterpreterOperation{(configv1alpha1.InterpreterOperation)("notsupported")}
tests := []struct {
name string
ruleWithOperations *configv1alpha1.RuleWithOperations
expectedError string
}{
{
name: "no operations",
ruleWithOperations: &configv1alpha1.RuleWithOperations{},
expectedError: "operations: Required value",
},
{
name: "2 operations with wildcard",
ruleWithOperations: &configv1alpha1.RuleWithOperations{
Operations: []configv1alpha1.InterpreterOperation{
configv1alpha1.InterpreterOperationAll,
configv1alpha1.InterpreterOperationInterpretReplica,
},
},
expectedError: "if '*' is present, must not specify other operations",
},
{
name: "not supported operations",
ruleWithOperations: &configv1alpha1.RuleWithOperations{Operations: notSupportedOperation},
expectedError: "operations[0]: Unsupported value",
},
{
name: "not validated rule",
ruleWithOperations: &configv1alpha1.RuleWithOperations{
Operations: []configv1alpha1.InterpreterOperation{
configv1alpha1.InterpreterOperationAll,
},
Rule: configv1alpha1.Rule{
APIGroups: []string{""},
}},
expectedError: "apiVersions: Required value",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
errs := validateRuleWithOperations(test.ruleWithOperations, fldPath)
err := errs.ToAggregate()
if err != nil {
if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
t.Errorf("Case: %s failed: expected to contain %s, got %s", test.name, e, a)
}
} else {
if test.expectedError != "" {
t.Errorf("Case: %s failed: unexpected no error, expected to contain %s", test.name, test.expectedError)
}
}
})
}
}
func TestValidateExploreReviewVersions(t *testing.T) {
fldPath := field.NewPath("webhooks").Child("interpreterContextVersions")
tests := []struct {
name string
versions []string
expectedError string
}{
{
name: "no versions",
versions: nil,
expectedError: fmt.Sprintf("must specify one of %v", strings.Join(acceptedInterpreterContextVersions, ", ")),
},
{
name: "duplicate versions",
versions: []string{"v1", "v1"},
expectedError: "duplicate version",
},
{
name: "invalid versions",
versions: []string{"test", "test"},
expectedError: fmt.Sprintf("must include at least one of %v", strings.Join(acceptedInterpreterContextVersions, ", ")),
},
{
name: "invalid DNS (RFC 1035) label",
versions: []string{"a b"},
expectedError: "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
errs := validateInterpreterContextVersions(test.versions, fldPath)
err := errs.ToAggregate()
if err != nil {
if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
t.Errorf("Case: %s failed: expected to contain %s, got %s", test.name, e, a)
}
} else {
if test.expectedError != "" {
t.Errorf("Case: %s failed: unexpected no error, expected to contain %s", test.name, test.expectedError)
}
}
})
}
}
func TestValidateWebhook(t *testing.T) {
fldPath := field.NewPath("webhooks")
tests := []struct {
name string
hook *configv1alpha1.ResourceInterpreterWebhook
expectedError string
}{
{
name: "not qualified domain name",
hook: &configv1alpha1.ResourceInterpreterWebhook{
Name: "",
},
expectedError: "webhooks.name: Required value",
},
{
name: "invalid rules",
hook: &configv1alpha1.ResourceInterpreterWebhook{
Rules: []configv1alpha1.RuleWithOperations{
{
Operations: []configv1alpha1.InterpreterOperation{},
},
},
},
expectedError: "operations: Required value",
},
{
name: "invalid timeout",
hook: &configv1alpha1.ResourceInterpreterWebhook{
TimeoutSeconds: int32Ptr(60),
},
expectedError: "the timeout value must be between 1 and 30 seconds",
},
{
name: "ClientConfig: exactly one of url or service is required",
hook: &configv1alpha1.ResourceInterpreterWebhook{
ClientConfig: admissionregistrationv1.WebhookClientConfig{},
},
expectedError: "exactly one of url or service is required",
},
{
name: "ClientConfig: invalid URL",
hook: &configv1alpha1.ResourceInterpreterWebhook{
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: strPtr(""),
},
},
expectedError: "host must be specified",
},
{
name: "ClientConfig: invalid service",
hook: &configv1alpha1.ResourceInterpreterWebhook{
ClientConfig: admissionregistrationv1.WebhookClientConfig{
Service: &admissionregistrationv1.ServiceReference{
Name: "",
Port: int32Ptr(8080),
Path: strPtr(""),
},
},
},
expectedError: "service name is required",
},
{
name: "invalid interpreter context versions",
hook: &configv1alpha1.ResourceInterpreterWebhook{
InterpreterContextVersions: []string{""},
},
expectedError: fmt.Sprintf("must include at least one of %v", strings.Join(acceptedInterpreterContextVersions, ", ")),
},
{
name: "valid webhook configuration: use Service in ClientConfig but with port unspecified",
hook: &configv1alpha1.ResourceInterpreterWebhook{
Name: "workloads.karmada.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
Service: &admissionregistrationv1.ServiceReference{
Namespace: "default",
Name: "svc",
Path: strPtr("/interpreter"),
},
},
InterpreterContextVersions: []string{"v1alpha1"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
errs := validateWebhook(test.hook, fldPath)
err := errs.ToAggregate()
if err != nil {
if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
t.Errorf("Case: %s failed: expected to contain %s, got %s", test.name, e, a)
}
} else {
if test.expectedError != "" {
t.Errorf("Case: %s failed: unexpected no error, expected to contain %s", test.name, test.expectedError)
}
}
})
}
}