k8s.io/apiserver@v0.31.1/pkg/cel/openapi/compiling_test.go (about)

     1  /*
     2  Copyright 2023 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 openapi
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/google/cel-go/cel"
    23  	"github.com/google/cel-go/common/types"
    24  	"github.com/google/cel-go/interpreter"
    25  
    26  	"k8s.io/apimachinery/pkg/util/version"
    27  	apiservercel "k8s.io/apiserver/pkg/cel"
    28  	"k8s.io/apiserver/pkg/cel/common"
    29  	"k8s.io/apiserver/pkg/cel/environment"
    30  	"k8s.io/kube-openapi/pkg/validation/spec"
    31  )
    32  
    33  func TestMultipleTypes(t *testing.T) {
    34  	env, err := buildTestEnv()
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	for _, tc := range []struct {
    39  		expression         string
    40  		expectCompileError bool
    41  		expectEvalResult   bool
    42  	}{
    43  		{
    44  			expression:       "foo.foo == bar.bar",
    45  			expectEvalResult: true,
    46  		},
    47  		{
    48  			expression:         "foo.bar == 'value'",
    49  			expectCompileError: true,
    50  		},
    51  		{
    52  			expression:       "foo.foo == 'value'",
    53  			expectEvalResult: true,
    54  		},
    55  		{
    56  			expression:       "bar.bar == 'value'",
    57  			expectEvalResult: true,
    58  		},
    59  		{
    60  			expression:       "foo.common + bar.common <= 2",
    61  			expectEvalResult: false, // 3 > 2
    62  		},
    63  		{
    64  			expression:         "foo.confusion == bar.confusion",
    65  			expectCompileError: true,
    66  		},
    67  	} {
    68  		t.Run(tc.expression, func(t *testing.T) {
    69  			ast, issues := env.Compile(tc.expression)
    70  			if issues != nil {
    71  				if tc.expectCompileError {
    72  					return
    73  				}
    74  				t.Fatalf("compile error: %v", issues)
    75  			}
    76  			if issues != nil {
    77  				t.Fatal(issues)
    78  			}
    79  			p, err := env.Program(ast)
    80  			if err != nil {
    81  				t.Fatal(err)
    82  			}
    83  			ret, _, err := p.Eval(&simpleActivation{
    84  				foo: map[string]any{"foo": "value", "common": 1, "confusion": "114514"},
    85  				bar: map[string]any{"bar": "value", "common": 2, "confusion": 114514},
    86  			})
    87  			if err != nil {
    88  				t.Fatal(err)
    89  			}
    90  			if ret.Type() != types.BoolType {
    91  				t.Errorf("bad result type: %v", ret.Type())
    92  			}
    93  			if res := ret.Value().(bool); tc.expectEvalResult != res {
    94  				t.Errorf("expectEvalResult expression evaluates to %v, got %v", tc.expectEvalResult, res)
    95  			}
    96  		})
    97  	}
    98  
    99  }
   100  
   101  // buildTestEnv sets up an environment that contains two variables, "foo" and
   102  // "bar".
   103  // foo is an object with a string field "foo", an integer field "common", and a string field "confusion"
   104  // bar is an object with a string field "bar", an integer field "common", and an integer field "confusion"
   105  func buildTestEnv() (*cel.Env, error) {
   106  	fooType := common.SchemaDeclType(simpleMapSchema("foo", spec.StringProperty()), true).MaybeAssignTypeName("fooType")
   107  	barType := common.SchemaDeclType(simpleMapSchema("bar", spec.Int64Property()), true).MaybeAssignTypeName("barType")
   108  
   109  	env, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true).Extend(
   110  		environment.VersionedOptions{
   111  			IntroducedVersion: version.MajorMinor(1, 26),
   112  			EnvOptions: []cel.EnvOption{
   113  				cel.Variable("foo", fooType.CelType()),
   114  				cel.Variable("bar", barType.CelType()),
   115  			},
   116  			DeclTypes: []*apiservercel.DeclType{
   117  				fooType,
   118  				barType,
   119  			},
   120  		},
   121  	)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	return env.Env(environment.NewExpressions)
   126  }
   127  
   128  func simpleMapSchema(fieldName string, confusionSchema *spec.Schema) common.Schema {
   129  	return &Schema{Schema: &spec.Schema{
   130  		SchemaProps: spec.SchemaProps{
   131  			Type: []string{"object"},
   132  			Properties: map[string]spec.Schema{
   133  				fieldName:   *spec.StringProperty(),
   134  				"common":    *spec.Int64Property(),
   135  				"confusion": *confusionSchema,
   136  			},
   137  		},
   138  	}}
   139  }
   140  
   141  type simpleActivation struct {
   142  	foo any
   143  	bar any
   144  }
   145  
   146  func (a *simpleActivation) ResolveName(name string) (interface{}, bool) {
   147  	switch name {
   148  	case "foo":
   149  		return a.foo, true
   150  	case "bar":
   151  		return a.bar, true
   152  	default:
   153  		return nil, false
   154  	}
   155  }
   156  
   157  func (a *simpleActivation) Parent() interpreter.Activation {
   158  	return nil
   159  }