github.com/oam-dev/kubevela@v1.9.11/pkg/definition/definition_test.go (about)

     1  /*
     2  Copyright 2021 The KubeVela 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 definition
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    32  	"sigs.k8s.io/yaml"
    33  
    34  	common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    36  	addonutils "github.com/oam-dev/kubevela/pkg/utils/addon"
    37  	"github.com/oam-dev/kubevela/pkg/utils/common"
    38  	"github.com/oam-dev/kubevela/pkg/utils/filters"
    39  )
    40  
    41  func TestDefinitionBasicFunctions(t *testing.T) {
    42  	c := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
    43  	def := &Definition{Unstructured: unstructured.Unstructured{}}
    44  	def.SetAnnotations(map[string]string{
    45  		UserPrefix + "annotation": "annotation",
    46  		"other":                   "other",
    47  	})
    48  	def.SetLabels(map[string]string{
    49  		UserPrefix + "label": "label",
    50  		"other":              "other",
    51  	})
    52  	def.SetName("test-trait")
    53  	def.SetGVK("TraitDefinition")
    54  	def.SetOwnerReferences([]v1.OwnerReference{{
    55  		Name: addonutils.Addon2AppName("test-addon"),
    56  	}})
    57  	if _type := def.GetType(); _type != "trait" {
    58  		t.Fatalf("set gvk invalid, expected trait got %s", _type)
    59  	}
    60  	if err := def.SetType("abc"); err == nil {
    61  		t.Fatalf("set type should failed due to invalid type, but got no error")
    62  	}
    63  	def.Object["spec"] = GetDefinitionDefaultSpec("TraitDefinition")
    64  	_ = unstructured.SetNestedField(def.Object, "patch: metadata: labels: \"KubeVela-test\": parameter.tag\nparameter: tag: string\n", "spec", "schematic", "cue", "template")
    65  	cueString, err := def.ToCUEString()
    66  	if err != nil {
    67  		t.Fatalf("unexpected error when getting to cue: %v", err)
    68  	}
    69  	trait := &v1beta1.TraitDefinition{}
    70  	s, _ := json.Marshal(def.Object)
    71  	_ = json.Unmarshal(s, trait)
    72  	if err = c.Create(context.Background(), trait); err != nil {
    73  		t.Fatalf("unexpected error when creating new definition with fake client: %v", err)
    74  	}
    75  	if err = def.FromCUEString("abc:]{xa}", nil); err == nil {
    76  		t.Fatalf("should encounter invalid cue string but not found error")
    77  	}
    78  	if err = def.FromCUEString(cueString+"abc: {xa}", nil); err == nil {
    79  		t.Fatalf("should encounter invalid cue string but not found error")
    80  	}
    81  	parts := strings.Split(cueString, "template: ")
    82  	if err = def.FromCUEString(parts[0], nil); err == nil {
    83  		t.Fatalf("should encounter no template found error but not found error")
    84  	}
    85  	if err = def.FromCUEString("template:"+parts[1], nil); err == nil {
    86  		t.Fatalf("should encounter no metadata found error but not found error")
    87  	}
    88  	if err = def.FromCUEString("import \"strconv\"\n"+cueString, nil); err != nil {
    89  		t.Fatalf("should not encounter cue compile error due to useless import")
    90  	}
    91  	if err = def.FromCUEString("abc: {}\n"+cueString, nil); err == nil {
    92  		t.Fatalf("should encounter duplicated object name error but not found error")
    93  	}
    94  	if err = def.FromCUEString(strings.Replace(cueString, "\"trait\"", "\"tr\"", 1), nil); err == nil {
    95  		t.Fatalf("should encounter invalid type error but not found error")
    96  	}
    97  	if err = def.FromCUEString(cueString, nil); err != nil {
    98  		t.Fatalf("unexpected error when setting from cue: %v", err)
    99  	}
   100  	if _cueString, err := def.ToCUEString(); err != nil {
   101  		t.Fatalf("failed to generate cue string: %v", err)
   102  	} else if _cueString != cueString {
   103  		t.Fatalf("the bidirectional conversion of cue string is not idempotent")
   104  	}
   105  	templateString, _, _ := unstructured.NestedString(def.Object, DefinitionTemplateKeys...)
   106  	_ = unstructured.SetNestedField(def.Object, "import \"strconv\"\n"+templateString, DefinitionTemplateKeys...)
   107  	if s, err := def.ToCUEString(); err != nil {
   108  		t.Fatalf("failed to generate cue string: %v", err)
   109  	} else if !strings.Contains(s, "import \"strconv\"\n") {
   110  		t.Fatalf("definition ToCUEString missed import, val: %v", s)
   111  	}
   112  	def = &Definition{}
   113  	if err = def.FromCUEString(cueString, nil); err != nil {
   114  		t.Fatalf("unexpected error when setting from cue for empty def: %v", err)
   115  	}
   116  
   117  	// test other definition default spec
   118  	_ = GetDefinitionDefaultSpec("ComponentDefinition")
   119  	_ = GetDefinitionDefaultSpec("WorkloadDefinition")
   120  	_ = ValidDefinitionTypes()
   121  
   122  	if _, err = SearchDefinition(c, "", ""); err != nil {
   123  		t.Fatalf("failed to search definition: %v", err)
   124  	}
   125  	if _, err = SearchDefinition(c, "trait", "default"); err != nil {
   126  		t.Fatalf("failed to search definition: %v", err)
   127  	}
   128  	res, err := SearchDefinition(c, "", "", filters.ByOwnerAddon("test-addon"))
   129  	if err != nil {
   130  		t.Fatalf("failed to search definition: %v", err)
   131  	}
   132  	if len(res) < 1 {
   133  		t.Fatalf("failed to search definition with addon filter applied: %s", "no result returned")
   134  	}
   135  	res, err = SearchDefinition(c, "", "", filters.ByName("test-trait"), filters.ByOwnerAddon("test-addon"))
   136  	if err != nil {
   137  		t.Fatalf("failed to search definition: %v", err)
   138  	}
   139  	if len(res) < 1 {
   140  		t.Fatalf("failed to search definition with addon filter applied: %s", "no result returned")
   141  	}
   142  	res, err = SearchDefinition(c, "", "", filters.ByOwnerAddon("this-is-a-non-existent-addon"))
   143  	if err != nil {
   144  		t.Fatalf("failed to search definition: %v", err)
   145  	}
   146  	if len(res) >= 1 {
   147  		t.Fatalf("failed to search definition with addon filter applied: %s", "too many results returned")
   148  	}
   149  }
   150  
   151  func TestDefinitionRevisionSearch(t *testing.T) {
   152  	c := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
   153  
   154  	var err error
   155  
   156  	// Load test DefinitionRevisions files into client
   157  	testFiles, err := os.ReadDir("testdata")
   158  	assert.NoError(t, err, "read testdata failed")
   159  	for _, file := range testFiles {
   160  		if !strings.HasSuffix(file.Name(), ".yaml") {
   161  			continue
   162  		}
   163  		content, err := os.ReadFile(filepath.Join("testdata", file.Name()))
   164  		assert.NoError(t, err)
   165  		def := &v1beta1.DefinitionRevision{}
   166  		err = yaml.Unmarshal(content, def)
   167  		assert.NoError(t, err)
   168  		err = c.Create(context.TODO(), def)
   169  		assert.NoError(t, err, "cannot create "+file.Name())
   170  	}
   171  
   172  	var defrevs []v1beta1.DefinitionRevision
   173  
   174  	// Read with no conditions, should at least have 4 defrevs
   175  	defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "", "", "", 0)
   176  	assert.NoError(t, err)
   177  	assert.Equal(t, true, len(defrevs) >= 4)
   178  
   179  	// Restrict namespace
   180  	defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-custom-ns", "", "", 0)
   181  	assert.NoError(t, err)
   182  	assert.Equal(t, 1, len(defrevs))
   183  
   184  	// Restrict type
   185  	defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-ns", "", common2.ComponentType, 0)
   186  	assert.NoError(t, err)
   187  	assert.Equal(t, 2, len(defrevs))
   188  
   189  	// Restrict revision
   190  	defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-ns", "", "", 1)
   191  	assert.NoError(t, err)
   192  	assert.Equal(t, 2, len(defrevs))
   193  
   194  	// Restrict name
   195  	defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-ns", "webservice", "", 1)
   196  	assert.NoError(t, err)
   197  	assert.Equal(t, 1, len(defrevs))
   198  
   199  	// Test GetDefinitionFromDefinitionRevision
   200  	defrev := defrevs[0]
   201  
   202  	// Simulate ComponentDefinition
   203  	defrev.Spec.DefinitionType = common2.ComponentType
   204  	_, err = GetDefinitionFromDefinitionRevision(&defrev)
   205  	assert.NoError(t, err)
   206  
   207  	// Simulate TraitDefinition
   208  	defrev.Spec.DefinitionType = common2.TraitType
   209  	_, err = GetDefinitionFromDefinitionRevision(&defrev)
   210  	assert.NoError(t, err)
   211  
   212  	// Simulate PolicyDefinition
   213  	defrev.Spec.DefinitionType = common2.PolicyType
   214  	_, err = GetDefinitionFromDefinitionRevision(&defrev)
   215  	assert.NoError(t, err)
   216  
   217  	// Simulate WorkflowStepDefinition
   218  	defrev.Spec.DefinitionType = common2.WorkflowStepType
   219  	_, err = GetDefinitionFromDefinitionRevision(&defrev)
   220  	assert.NoError(t, err)
   221  }
   222  
   223  func TestValidateSpec(t *testing.T) {
   224  	testcases := map[string]struct {
   225  		Input  string
   226  		Type   string
   227  		HasErr bool
   228  	}{
   229  		"comp": {
   230  			Input: `{"podSpecPath": "a"}`,
   231  			Type:  "component",
   232  		},
   233  		"trait": {
   234  			Input: `{"appliesToWorkloads":["deployments"]}`,
   235  			Type:  "trait",
   236  		},
   237  		"workflow-step": {
   238  			Input: `{"definitionRef":{"name":"v"}}`,
   239  			Type:  "workflow-step",
   240  		},
   241  		"bad-policy": {
   242  			Input:  `{"definitionRef":{"invalid":5}}`,
   243  			Type:   "policy",
   244  			HasErr: true,
   245  		},
   246  		"unknown": {
   247  			Input:  `{}`,
   248  			Type:   "unknown",
   249  			HasErr: false,
   250  		},
   251  	}
   252  	for name, tt := range testcases {
   253  		t.Run(name, func(t *testing.T) {
   254  			spec := map[string]interface{}{}
   255  			require.NoError(t, json.Unmarshal([]byte(tt.Input), &spec))
   256  			err := validateSpec(spec, tt.Type)
   257  			if tt.HasErr {
   258  				require.Error(t, err)
   259  			} else {
   260  				require.NoError(t, err)
   261  			}
   262  		})
   263  	}
   264  }