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 }