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 }