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  })