github.com/caseydavenport/controller-tools@v0.2.6-0.20200519183242-e8a18b1a6750/pkg/crd/flatten_type_test.go (about) 1 /* 2 Copyright 2019 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 crd_test 18 19 import ( 20 "fmt" 21 22 . "github.com/onsi/ginkgo" 23 . "github.com/onsi/gomega" 24 apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 26 "golang.org/x/tools/go/packages" 27 "sigs.k8s.io/controller-tools/pkg/crd" 28 "sigs.k8s.io/controller-tools/pkg/loader" 29 ) 30 31 var _ = Describe("General Schema Flattening", func() { 32 var fl *crd.Flattener 33 34 var ( 35 // just enough so we don't panic 36 rootPkg = &loader.Package{Package: &packages.Package{PkgPath: "root"}} 37 otherPkg = &loader.Package{Package: &packages.Package{PkgPath: "other"}} 38 39 rootType = crd.TypeIdent{Name: "RootType", Package: rootPkg} 40 subtypeWithRefs = crd.TypeIdent{Name: "SubtypeWithRefs", Package: rootPkg} 41 leafType = crd.TypeIdent{Name: "LeafType", Package: otherPkg} 42 ) 43 44 BeforeEach(func() { 45 fl = &crd.Flattener{ 46 Parser: &crd.Parser{ 47 Schemata: map[crd.TypeIdent]apiext.JSONSchemaProps{}, 48 PackageOverrides: map[string]crd.PackageOverride{ 49 "root": func(_ *crd.Parser, _ *loader.Package) {}, 50 "other": func(_ *crd.Parser, _ *loader.Package) {}, 51 }, 52 }, 53 LookupReference: func(ref string, contextPkg *loader.Package) (crd.TypeIdent, error) { 54 typ, pkgName, err := crd.RefParts(ref) 55 if err != nil { 56 return crd.TypeIdent{}, err 57 } 58 59 // cheat and just treat these as global 60 switch pkgName { 61 case "": 62 return crd.TypeIdent{Name: typ, Package: contextPkg}, nil 63 case "root": 64 return crd.TypeIdent{Name: typ, Package: rootPkg}, nil 65 case "other": 66 return crd.TypeIdent{Name: typ, Package: otherPkg}, nil 67 default: 68 return crd.TypeIdent{}, fmt.Errorf("unknown package %q", pkgName) 69 } 70 71 }, 72 } 73 }) 74 75 It("should flatten a hierarchy of references", func() { 76 By("setting up a series of types RootType --> SubtypeWithRef --> LeafType") 77 toSubtype := crd.TypeRefLink("", subtypeWithRefs.Name) 78 toLeaf := crd.TypeRefLink("other", leafType.Name) 79 fl.Parser.Schemata = map[crd.TypeIdent]apiext.JSONSchemaProps{ 80 rootType: apiext.JSONSchemaProps{ 81 Properties: map[string]apiext.JSONSchemaProps{ 82 "refProp": apiext.JSONSchemaProps{Ref: &toSubtype}, 83 }, 84 }, 85 subtypeWithRefs: apiext.JSONSchemaProps{ 86 AdditionalProperties: &apiext.JSONSchemaPropsOrBool{ 87 Schema: &apiext.JSONSchemaProps{ 88 Ref: &toLeaf, 89 }, 90 }, 91 }, 92 leafType: apiext.JSONSchemaProps{ 93 Type: "string", 94 Pattern: "^[abc]$", 95 }, 96 } 97 98 By("flattening the type hierarchy") 99 outSchema := fl.FlattenType(rootType) 100 Expect(rootPkg.Errors).To(HaveLen(0)) 101 Expect(otherPkg.Errors).To(HaveLen(0)) 102 103 By("verifying that it was flattened to have no references") 104 Expect(outSchema).To(Equal(&apiext.JSONSchemaProps{ 105 Properties: map[string]apiext.JSONSchemaProps{ 106 "refProp": apiext.JSONSchemaProps{ 107 AllOf: []apiext.JSONSchemaProps{ 108 { 109 AdditionalProperties: &apiext.JSONSchemaPropsOrBool{ 110 Schema: &apiext.JSONSchemaProps{ 111 AllOf: []apiext.JSONSchemaProps{ 112 {Type: "string", Pattern: "^[abc]$"}, 113 {}, 114 }, 115 }, 116 }, 117 }, 118 {}, 119 }, 120 }, 121 }, 122 })) 123 }) 124 125 It("should preserve the properties of each separate use of a type without modifying the cache", func() { 126 By("setting up a series of types RootType --> LeafType with 3 uses") 127 defOne := int64(1) 128 defThree := int64(3) 129 toLeaf := crd.TypeRefLink("other", leafType.Name) 130 fl.Parser.Schemata = map[crd.TypeIdent]apiext.JSONSchemaProps{ 131 rootType: apiext.JSONSchemaProps{ 132 Properties: map[string]apiext.JSONSchemaProps{ 133 "useWithOtherPattern": apiext.JSONSchemaProps{ 134 Ref: &toLeaf, 135 Pattern: "^[cde]$", 136 Description: "has other pattern", 137 }, 138 "useWithMinLen": apiext.JSONSchemaProps{ 139 Ref: &toLeaf, 140 MinLength: &defOne, 141 Description: "has min len", 142 }, 143 "useWithMaxLen": apiext.JSONSchemaProps{ 144 Ref: &toLeaf, 145 MaxLength: &defThree, 146 Description: "has max len", 147 }, 148 }, 149 }, 150 leafType: apiext.JSONSchemaProps{ 151 Type: "string", 152 Pattern: "^[abc]$", 153 }, 154 } 155 156 By("flattening the type hierarchy") 157 outSchema := fl.FlattenType(rootType) 158 Expect(rootPkg.Errors).To(HaveLen(0)) 159 Expect(otherPkg.Errors).To(HaveLen(0)) 160 161 By("verifying that each use has its own properties set in allof branches") 162 Expect(outSchema).To(Equal(&apiext.JSONSchemaProps{ 163 Properties: map[string]apiext.JSONSchemaProps{ 164 "useWithOtherPattern": apiext.JSONSchemaProps{ 165 AllOf: []apiext.JSONSchemaProps{ 166 {Type: "string", Pattern: "^[abc]$"}, 167 {Pattern: "^[cde]$"}, 168 }, 169 Description: "has other pattern", 170 }, 171 "useWithMinLen": apiext.JSONSchemaProps{ 172 AllOf: []apiext.JSONSchemaProps{ 173 {Type: "string", Pattern: "^[abc]$"}, 174 {MinLength: &defOne}, 175 }, 176 Description: "has min len", 177 }, 178 "useWithMaxLen": apiext.JSONSchemaProps{ 179 AllOf: []apiext.JSONSchemaProps{ 180 {Type: "string", Pattern: "^[abc]$"}, 181 {MaxLength: &defThree}, 182 }, 183 Description: "has max len", 184 }, 185 }, 186 })) 187 }) 188 189 It("should copy over documentation for each use of a type", func() { 190 By("setting up a series of types RootType --> LeafType with 3 doc-only uses") 191 toLeaf := crd.TypeRefLink("other", leafType.Name) 192 fl.Parser.Schemata = map[crd.TypeIdent]apiext.JSONSchemaProps{ 193 rootType: apiext.JSONSchemaProps{ 194 Properties: map[string]apiext.JSONSchemaProps{ 195 "hasTitle": apiext.JSONSchemaProps{ 196 Ref: &toLeaf, 197 Description: "has title", 198 Title: "some title", 199 }, 200 "hasExample": apiext.JSONSchemaProps{ 201 Ref: &toLeaf, 202 Description: "has example", 203 Example: &apiext.JSON{Raw: []byte("[42]")}, 204 }, 205 "hasExternalDocs": apiext.JSONSchemaProps{ 206 Ref: &toLeaf, 207 Description: "has external docs", 208 ExternalDocs: &apiext.ExternalDocumentation{ 209 Description: "somewhere else", 210 URL: "https://example.com", // RFC 2606 211 }, 212 }, 213 }, 214 }, 215 leafType: apiext.JSONSchemaProps{ 216 Type: "string", 217 Pattern: "^[abc]$", 218 }, 219 } 220 221 By("flattening the type hierarchy") 222 outSchema := fl.FlattenType(rootType) 223 Expect(rootPkg.Errors).To(HaveLen(0)) 224 Expect(otherPkg.Errors).To(HaveLen(0)) 225 226 By("verifying that each use has its own properties set in allof branches") 227 Expect(outSchema).To(Equal(&apiext.JSONSchemaProps{ 228 Properties: map[string]apiext.JSONSchemaProps{ 229 "hasTitle": apiext.JSONSchemaProps{ 230 AllOf: []apiext.JSONSchemaProps{{Type: "string", Pattern: "^[abc]$"}, {}}, 231 Description: "has title", 232 Title: "some title", 233 }, 234 "hasExample": apiext.JSONSchemaProps{ 235 AllOf: []apiext.JSONSchemaProps{{Type: "string", Pattern: "^[abc]$"}, {}}, 236 Description: "has example", 237 Example: &apiext.JSON{Raw: []byte("[42]")}, 238 }, 239 "hasExternalDocs": apiext.JSONSchemaProps{ 240 AllOf: []apiext.JSONSchemaProps{{Type: "string", Pattern: "^[abc]$"}, {}}, 241 Description: "has external docs", 242 ExternalDocs: &apiext.ExternalDocumentation{ 243 Description: "somewhere else", 244 URL: "https://example.com", // RFC 2606 245 }, 246 }, 247 }, 248 })) 249 }) 250 251 It("should ignore schemata that aren't references, but continue flattening", func() { 252 By("setting up a series of types RootType --> LeafType with non-ref properties") 253 toLeaf := crd.TypeRefLink("other", leafType.Name) 254 toSubtype := crd.TypeRefLink("", subtypeWithRefs.Name) 255 fl.Parser.Schemata = map[crd.TypeIdent]apiext.JSONSchemaProps{ 256 rootType: apiext.JSONSchemaProps{ 257 Properties: map[string]apiext.JSONSchemaProps{ 258 "isRef": apiext.JSONSchemaProps{ 259 Ref: &toSubtype, 260 }, 261 "notRef": apiext.JSONSchemaProps{ 262 Type: "int", 263 }, 264 }, 265 }, 266 subtypeWithRefs: apiext.JSONSchemaProps{ 267 Properties: map[string]apiext.JSONSchemaProps{ 268 "leafRef": apiext.JSONSchemaProps{ 269 Ref: &toLeaf, 270 }, 271 "alsoNotRef": apiext.JSONSchemaProps{ 272 Type: "bool", 273 }, 274 }, 275 }, 276 leafType: apiext.JSONSchemaProps{ 277 Type: "string", 278 Pattern: "^[abc]$", 279 }, 280 } 281 282 By("flattening the type hierarchy") 283 outSchema := fl.FlattenType(rootType) 284 Expect(rootPkg.Errors).To(HaveLen(0)) 285 Expect(otherPkg.Errors).To(HaveLen(0)) 286 287 By("verifying that each use has its own properties set in allof branches") 288 Expect(outSchema).To(Equal(&apiext.JSONSchemaProps{ 289 Properties: map[string]apiext.JSONSchemaProps{ 290 "isRef": apiext.JSONSchemaProps{ 291 AllOf: []apiext.JSONSchemaProps{ 292 { 293 Properties: map[string]apiext.JSONSchemaProps{ 294 "leafRef": apiext.JSONSchemaProps{ 295 AllOf: []apiext.JSONSchemaProps{ 296 {Type: "string", Pattern: "^[abc]$"}, {}, 297 }, 298 }, 299 "alsoNotRef": apiext.JSONSchemaProps{ 300 Type: "bool", 301 }, 302 }, 303 }, 304 {}, 305 }, 306 }, 307 "notRef": apiext.JSONSchemaProps{ 308 Type: "int", 309 }, 310 }, 311 })) 312 313 }) 314 })