k8s.io/apiserver@v0.31.1/pkg/cel/library/format_test.go (about)

     1  /*
     2  Copyright 2024 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 library_test
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/google/cel-go/common/types"
    24  	"github.com/google/cel-go/common/types/ref"
    25  	"k8s.io/apiserver/pkg/cel/library"
    26  )
    27  
    28  func TestFormat(t *testing.T) {
    29  	type testcase struct {
    30  		name               string
    31  		expr               string
    32  		expectValue        ref.Val
    33  		expectedCompileErr []string
    34  		expectedRuntimeErr string
    35  	}
    36  
    37  	cases := []testcase{
    38  		{
    39  			name:        "example_usage_dns1123Label",
    40  			expr:        `format.dns1123Label().validate("my-label-name")`,
    41  			expectValue: types.OptionalNone,
    42  		},
    43  		{
    44  			name:        "example_usage_dns1123Subdomain",
    45  			expr:        `format.dns1123Subdomain().validate("apiextensions.k8s.io")`,
    46  			expectValue: types.OptionalNone,
    47  		},
    48  		{
    49  			name:        "example_usage_qualifiedName",
    50  			expr:        `format.qualifiedName().validate("apiextensions.k8s.io/v1beta1")`,
    51  			expectValue: types.OptionalNone,
    52  		},
    53  		{
    54  			name:        "example_usage_dns1123LabelPrefix",
    55  			expr:        `format.dns1123LabelPrefix().validate("my-label-prefix-")`,
    56  			expectValue: types.OptionalNone,
    57  		},
    58  		{
    59  			name:        "example_usage_dns1123SubdomainPrefix",
    60  			expr:        `format.dns1123SubdomainPrefix().validate("mysubdomain.prefix.-")`,
    61  			expectValue: types.OptionalNone,
    62  		},
    63  		{
    64  			name:        "example_usage_dns1035LabelPrefix",
    65  			expr:        `format.dns1035LabelPrefix().validate("my-label-prefix-")`,
    66  			expectValue: types.OptionalNone,
    67  		},
    68  		{
    69  			name:        "example_usage_uri",
    70  			expr:        `format.uri().validate("http://example.com")`,
    71  			expectValue: types.OptionalNone,
    72  		},
    73  		{
    74  			name:        "example_usage_uuid",
    75  			expr:        `format.uuid().validate("123e4567-e89b-12d3-a456-426614174000")`,
    76  			expectValue: types.OptionalNone,
    77  		},
    78  		{
    79  			name:        "example_usage_byte",
    80  			expr:        `format.byte().validate("aGVsbG8=")`,
    81  			expectValue: types.OptionalNone,
    82  		},
    83  		{
    84  			name:        "example_usage_date",
    85  			expr:        `format.date().validate("2021-01-01")`,
    86  			expectValue: types.OptionalNone,
    87  		},
    88  		{
    89  			name:        "example_usage_datetime",
    90  			expr:        `format.datetime().validate("2021-01-01T00:00:00Z")`,
    91  			expectValue: types.OptionalNone,
    92  		},
    93  		{
    94  			name:        "dns1123Label",
    95  			expr:        `format.dns1123Label().validate("contains a space")`,
    96  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{"a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"})),
    97  		},
    98  		{
    99  			name:        "dns1123Subdomain",
   100  			expr:        `format.dns1123Subdomain().validate("contains a space")`,
   101  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{`a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`})),
   102  		},
   103  		{
   104  			name:        "dns1035Label",
   105  			expr:        `format.dns1035Label().validate("contains a space")`,
   106  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{`a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`})),
   107  		},
   108  		{
   109  			name:        "qualifiedName",
   110  			expr:        `format.qualifiedName().validate("contains a space")`,
   111  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{`name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`})),
   112  		},
   113  		{
   114  			name:        "dns1123LabelPrefix",
   115  			expr:        `format.dns1123LabelPrefix().validate("contains a space-")`,
   116  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{"a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"})),
   117  		},
   118  		{
   119  			name:        "dns1123SubdomainPrefix",
   120  			expr:        `format.dns1123SubdomainPrefix().validate("contains a space-")`,
   121  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{`a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`})),
   122  		},
   123  		{
   124  			name:        "dns1035LabelPrefix",
   125  			expr:        `format.dns1035LabelPrefix().validate("contains a space-")`,
   126  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{`a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`})),
   127  		},
   128  		{
   129  			name:        "dns1123Label_Success",
   130  			expr:        `format.dns1123Label().validate("my-label-name")`,
   131  			expectValue: types.OptionalNone,
   132  		},
   133  		{
   134  			name:        "dns1123Subdomain_Success",
   135  			expr:        `format.dns1123Subdomain().validate("example.com")`,
   136  			expectValue: types.OptionalNone,
   137  		},
   138  		{
   139  			name:        "dns1035Label_Success",
   140  			expr:        `format.dns1035Label().validate("my-label-name")`,
   141  			expectValue: types.OptionalNone,
   142  		},
   143  		{
   144  			name:        "qualifiedName_Success",
   145  			expr:        `format.qualifiedName().validate("my.name")`,
   146  			expectValue: types.OptionalNone,
   147  		},
   148  		{
   149  			// byte is base64
   150  			name:        "byte_success",
   151  			expr:        `format.byte().validate("aGVsbG8=")`,
   152  			expectValue: types.OptionalNone,
   153  		},
   154  		{
   155  			// byte is base64
   156  			name:        "byte_failure",
   157  			expr:        `format.byte().validate("aGVsbG8")`,
   158  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{"invalid base64"})),
   159  		},
   160  		{
   161  			name: "date_success",
   162  			expr: `format.date().validate("2020-01-01")`,
   163  			// date is a valid date
   164  			expectValue: types.OptionalNone,
   165  		},
   166  		{
   167  			name:        "date_failure",
   168  			expr:        `format.date().validate("2020-01-32")`,
   169  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{"invalid date"})),
   170  		},
   171  		{
   172  			name: "datetime_success",
   173  			expr: `format.datetime().validate("2020-01-01T00:00:00Z")`,
   174  			// datetime is a valid date
   175  			expectValue: types.OptionalNone,
   176  		},
   177  		{
   178  			name:        "datetime_failure",
   179  			expr:        `format.datetime().validate("2020-01-32T00:00:00Z")`,
   180  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{"invalid datetime"})),
   181  		},
   182  		{
   183  			name:        "unknown_format",
   184  			expr:        `format.named("unknown").hasValue()`,
   185  			expectValue: types.False,
   186  		},
   187  		{
   188  			name:        "labelValue_success",
   189  			expr:        `format.labelValue().validate("my-cool-label-Value")`,
   190  			expectValue: types.OptionalNone,
   191  		},
   192  		{
   193  			name: "labelValue_failure",
   194  			expr: `format.labelValue().validate("my-cool-label-Value!!\n\n!!!")`,
   195  			expectValue: types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, []string{
   196  				"a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue',  or 'my_value',  or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')",
   197  			})),
   198  		},
   199  	}
   200  
   201  	// Also test format names and comparisons of all constants
   202  	for keyLHS := range library.ConstantFormats {
   203  		cases = append(cases, testcase{
   204  			name:        "lookup and comparison",
   205  			expr:        fmt.Sprintf(`format.named("%s").hasValue()`, keyLHS),
   206  			expectValue: types.True,
   207  		}, testcase{
   208  			name:        "comparison with lookup succeeds",
   209  			expr:        fmt.Sprintf(`format.named("%s").value() == format.%s()`, keyLHS, keyLHS),
   210  			expectValue: types.True,
   211  		})
   212  
   213  		for keyRHS := range library.ConstantFormats {
   214  			if keyLHS == keyRHS {
   215  				continue
   216  			}
   217  			cases = append(cases, testcase{
   218  				name:        fmt.Sprintf("compare_%s_%s", keyLHS, keyRHS),
   219  				expr:        fmt.Sprintf(`format.%s() == format.%s()`, keyLHS, keyRHS),
   220  				expectValue: types.False,
   221  			})
   222  		}
   223  	}
   224  
   225  	for _, tc := range cases {
   226  		t.Run(tc.name, func(t *testing.T) {
   227  			testQuantity(t, tc.expr, tc.expectValue, tc.expectedRuntimeErr, tc.expectedCompileErr)
   228  		})
   229  	}
   230  }