...

Source file src/k8s.io/kubectl/pkg/explain/v2/templates/plaintext_test.go

Documentation: k8s.io/kubectl/pkg/explain/v2/templates

     1  /*
     2  Copyright 2022 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 templates_test
    18  
    19  import (
    20  	"bytes"
    21  	_ "embed"
    22  	"encoding/json"
    23  	"fmt"
    24  	"strings"
    25  	"testing"
    26  	"text/template"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/stretchr/testify/require"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    32  	"k8s.io/kube-openapi/pkg/spec3"
    33  	"k8s.io/kube-openapi/pkg/validation/spec"
    34  	v2 "k8s.io/kubectl/pkg/explain/v2"
    35  )
    36  
    37  var (
    38  	//go:embed plaintext.tmpl
    39  	plaintextSource string
    40  
    41  	//go:embed apiextensions.k8s.io_v1.json
    42  	apiextensionsJSON string
    43  
    44  	//go:embed batch.k8s.io_v1.json
    45  	batchJSON string
    46  
    47  	apiExtensionsV1OpenAPI map[string]interface{} = func() map[string]interface{} {
    48  		var res map[string]interface{}
    49  		utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
    50  		return res
    51  	}()
    52  
    53  	apiExtensionsV1OpenAPIWithoutListVerb map[string]interface{} = func() map[string]interface{} {
    54  		var res map[string]interface{}
    55  		utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
    56  		paths := res["paths"].(map[string]interface{})
    57  		delete(paths, "/apis/apiextensions.k8s.io/v1/customresourcedefinitions")
    58  		return res
    59  	}()
    60  
    61  	apiExtensionsV1OpenAPISpec spec3.OpenAPI = func() spec3.OpenAPI {
    62  		var res spec3.OpenAPI
    63  		utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
    64  		return res
    65  	}()
    66  
    67  	batchV1OpenAPI map[string]interface{} = func() map[string]interface{} {
    68  		var res map[string]interface{}
    69  		utilruntime.Must(json.Unmarshal([]byte(batchJSON), &res))
    70  		return res
    71  	}()
    72  
    73  	batchV1OpenAPIWithoutListVerb map[string]interface{} = func() map[string]interface{} {
    74  		var res map[string]interface{}
    75  		utilruntime.Must(json.Unmarshal([]byte(batchJSON), &res))
    76  		paths := res["paths"].(map[string]interface{})
    77  		delete(paths, "/apis/batch/v1/jobs")
    78  		delete(paths, "/apis/batch/v1/namespaces/{namespace}/jobs")
    79  
    80  		delete(paths, "/apis/batch/v1/cronjobs")
    81  		delete(paths, "/apis/batch/v1/namespaces/{namespace}/cronjobs/{name}")
    82  		return res
    83  	}()
    84  )
    85  
    86  type testCase struct {
    87  	// test case name
    88  	Name string
    89  	// if empty uses main template
    90  	Subtemplate string
    91  	// context to render withi
    92  	Context any
    93  	// checks to perform on rendered output
    94  	Checks []check
    95  }
    96  
    97  type check interface {
    98  	doCheck(output string, err error) error
    99  }
   100  
   101  type checkError string
   102  
   103  func (c checkError) doCheck(output string, err error) error {
   104  	if !strings.Contains(err.Error(), "error: "+string(c)) {
   105  		return fmt.Errorf("expected error: '%v' in string:\n%v", string(c), err)
   106  	}
   107  	return nil
   108  }
   109  
   110  type checkContains string
   111  
   112  func (c checkContains) doCheck(output string, err error) error {
   113  	if !strings.Contains(output, string(c)) {
   114  		return fmt.Errorf("expected substring: '%v' in string:\n%v", string(c), output)
   115  	}
   116  	return nil
   117  }
   118  
   119  type checkEquals string
   120  
   121  func (c checkEquals) doCheck(output string, err error) error {
   122  	if output != string(c) {
   123  		return fmt.Errorf("output is not equal to expectation:\n%v", cmp.Diff(string(c), output))
   124  	}
   125  	return nil
   126  }
   127  
   128  func MapDict[K comparable, V any, N any](accum map[K]V, mapper func(V) N) map[K]N {
   129  	res := make(map[K]N, len(accum))
   130  	for k, v := range accum {
   131  		res[k] = mapper(v)
   132  	}
   133  	return res
   134  }
   135  
   136  func ReduceDict[K comparable, V any, N any](val map[K]V, accum N, mapper func(N, K, V) N) N {
   137  	for k, v := range val {
   138  		accum = mapper(accum, k, v)
   139  	}
   140  	return accum
   141  }
   142  
   143  func TestPlaintext(t *testing.T) {
   144  	testcases := []testCase{
   145  		{
   146  			// Test case where resource being rendered is not found in OpenAPI schema
   147  			Name: "ResourceNotFound",
   148  			Context: v2.TemplateContext{
   149  				Document:  apiExtensionsV1OpenAPI,
   150  				GVR:       schema.GroupVersionResource{},
   151  				FieldPath: nil,
   152  				Recursive: false,
   153  			},
   154  			Checks: []check{
   155  				checkError("GVR (/, Resource=) not found in OpenAPI schema"),
   156  			},
   157  		},
   158  		{
   159  			// Test basic ability to find a GVR and print its description
   160  			Name: "SchemaFound",
   161  			Context: v2.TemplateContext{
   162  				Document: apiExtensionsV1OpenAPI,
   163  				GVR: schema.GroupVersionResource{
   164  					Group:    "apiextensions.k8s.io",
   165  					Version:  "v1",
   166  					Resource: "customresourcedefinitions",
   167  				},
   168  				FieldPath: nil,
   169  				Recursive: false,
   170  			},
   171  			Checks: []check{
   172  				checkContains("CustomResourceDefinition represents a resource that should be exposed"),
   173  			},
   174  		},
   175  		{
   176  			// Test basic ability to find a namespaced GVR and print its description
   177  			Name: "SchemaFoundNamespaced",
   178  			Context: v2.TemplateContext{
   179  				Document: batchV1OpenAPI,
   180  				GVR: schema.GroupVersionResource{
   181  					Group:    "batch",
   182  					Version:  "v1",
   183  					Resource: "jobs",
   184  				},
   185  				FieldPath: nil,
   186  				Recursive: false,
   187  			},
   188  			Checks: []check{
   189  				checkContains("Job represents the configuration of a single job"),
   190  			},
   191  		},
   192  		{
   193  			// Test basic ability to find a GVR without a list verb and print its description
   194  			Name: "SchemaFoundWithoutListVerb",
   195  			Context: v2.TemplateContext{
   196  				Document: apiExtensionsV1OpenAPIWithoutListVerb,
   197  				GVR: schema.GroupVersionResource{
   198  					Group:    "apiextensions.k8s.io",
   199  					Version:  "v1",
   200  					Resource: "customresourcedefinitions",
   201  				},
   202  				FieldPath: nil,
   203  				Recursive: false,
   204  			},
   205  			Checks: []check{
   206  				checkContains("CustomResourceDefinition represents a resource that should be exposed"),
   207  			},
   208  		},
   209  		{
   210  			// Test basic ability to find a namespaced GVR without a list verb and print its description
   211  			Name: "SchemaFoundNamespacedWithoutListVerb",
   212  			Context: v2.TemplateContext{
   213  				Document: batchV1OpenAPIWithoutListVerb,
   214  				GVR: schema.GroupVersionResource{
   215  					Group:    "batch",
   216  					Version:  "v1",
   217  					Resource: "jobs",
   218  				},
   219  				FieldPath: nil,
   220  				Recursive: false,
   221  			},
   222  			Checks: []check{
   223  				checkContains("Job represents the configuration of a single job"),
   224  			},
   225  		},
   226  		{
   227  			// Test basic ability to find a namespaced GVR without a top level list verb and print its description
   228  			Name: "SchemaFoundNamespacedWithoutTopLevelListVerb",
   229  			Context: v2.TemplateContext{
   230  				Document: batchV1OpenAPIWithoutListVerb,
   231  				GVR: schema.GroupVersionResource{
   232  					Group:    "batch",
   233  					Version:  "v1",
   234  					Resource: "cronjobs",
   235  				},
   236  				FieldPath: nil,
   237  				Recursive: false,
   238  			},
   239  			Checks: []check{
   240  				checkContains("CronJob represents the configuration of a single cron job"),
   241  			},
   242  		},
   243  		{
   244  			// Test that shows trying to find a non-existent field path of an existing
   245  			// schema
   246  			Name: "SchemaFieldPathNotFound",
   247  			Context: v2.TemplateContext{
   248  				Document: apiExtensionsV1OpenAPI,
   249  				GVR: schema.GroupVersionResource{
   250  					Group:    "apiextensions.k8s.io",
   251  					Version:  "v1",
   252  					Resource: "customresourcedefinitions",
   253  				},
   254  				FieldPath: []string{"does", "not", "exist"},
   255  				Recursive: false,
   256  			},
   257  			Checks: []check{
   258  				checkError(`field "exist" does not exist`),
   259  			},
   260  		},
   261  		{
   262  			// Test traversing a single level for scalar field path
   263  			Name: "SchemaFieldPathShallow",
   264  			Context: v2.TemplateContext{
   265  				Document: apiExtensionsV1OpenAPI,
   266  				GVR: schema.GroupVersionResource{
   267  					Group:    "apiextensions.k8s.io",
   268  					Version:  "v1",
   269  					Resource: "customresourcedefinitions",
   270  				},
   271  				FieldPath: []string{"kind"},
   272  				Recursive: false,
   273  			},
   274  			Checks: []check{
   275  				checkContains("FIELD: kind <string>"),
   276  			},
   277  		},
   278  		{
   279  			// Test traversing a multiple levels for scalar field path
   280  			Name: "SchemaFieldPathDeep",
   281  			Context: v2.TemplateContext{
   282  				Document: apiExtensionsV1OpenAPI,
   283  				GVR: schema.GroupVersionResource{
   284  					Group:    "apiextensions.k8s.io",
   285  					Version:  "v1",
   286  					Resource: "customresourcedefinitions",
   287  				},
   288  				FieldPath: []string{"spec", "names", "singular"},
   289  				Recursive: false,
   290  			},
   291  			Checks: []check{
   292  				checkContains("FIELD: singular <string>"),
   293  			},
   294  		},
   295  		{
   296  			// Test traversing a multiple levels for scalar field path
   297  			// through an array field
   298  			Name: "SchemaFieldPathViaList",
   299  			Context: v2.TemplateContext{
   300  				Document: apiExtensionsV1OpenAPI,
   301  				GVR: schema.GroupVersionResource{
   302  					Group:    "apiextensions.k8s.io",
   303  					Version:  "v1",
   304  					Resource: "customresourcedefinitions",
   305  				},
   306  				FieldPath: []string{"spec", "versions", "name"},
   307  				Recursive: false,
   308  			},
   309  			Checks: []check{
   310  				checkContains("FIELD: name <string>"),
   311  			},
   312  		},
   313  		{
   314  			// Test traversing a multiple levels for scalar field path
   315  			// through a map[string]T field.
   316  			Name: "SchemaFieldPathViaMap",
   317  			Context: v2.TemplateContext{
   318  				Document: apiExtensionsV1OpenAPI,
   319  				GVR: schema.GroupVersionResource{
   320  					Group:    "apiextensions.k8s.io",
   321  					Version:  "v1",
   322  					Resource: "customresourcedefinitions",
   323  				},
   324  				FieldPath: []string{"spec", "versions", "schema", "openAPIV3Schema", "properties", "default"},
   325  				Recursive: false,
   326  			},
   327  			Checks: []check{
   328  				checkContains("default is a default value for undefined object fields"),
   329  			},
   330  		},
   331  		{
   332  			// Shows that walking through a recursively specified schema is A-OK!
   333  			Name: "SchemaFieldPathRecursive",
   334  			Context: v2.TemplateContext{
   335  				Document: apiExtensionsV1OpenAPI,
   336  				GVR: schema.GroupVersionResource{
   337  					Group:    "apiextensions.k8s.io",
   338  					Version:  "v1",
   339  					Resource: "customresourcedefinitions",
   340  				},
   341  				FieldPath: []string{"spec", "versions", "schema", "openAPIV3Schema", "properties", "properties", "properties", "properties", "properties", "default"},
   342  				Recursive: false,
   343  			},
   344  			Checks: []check{
   345  				checkContains("default is a default value for undefined object fields"),
   346  			},
   347  		},
   348  		{
   349  			// Shows that all fields are included
   350  			Name: "SchemaAllFields",
   351  			Context: v2.TemplateContext{
   352  				Document: apiExtensionsV1OpenAPI,
   353  				GVR: schema.GroupVersionResource{
   354  					Group:    "apiextensions.k8s.io",
   355  					Version:  "v1",
   356  					Resource: "customresourcedefinitions",
   357  				},
   358  				FieldPath: []string{"spec", "versions", "schema"},
   359  				Recursive: false,
   360  			},
   361  			Checks: ReduceDict(apiExtensionsV1OpenAPISpec.Components.Schemas["io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceValidation"].Properties, []check{}, func(checks []check, k string, v spec.Schema) []check {
   362  				return append(checks, checkContains(k), checkContains(v.Description))
   363  			}),
   364  		},
   365  		{
   366  			// Shows that all fields are included
   367  			Name: "SchemaAllFieldsRecursive",
   368  			Context: v2.TemplateContext{
   369  				Document: apiExtensionsV1OpenAPI,
   370  				GVR: schema.GroupVersionResource{
   371  					Group:    "apiextensions.k8s.io",
   372  					Version:  "v1",
   373  					Resource: "customresourcedefinitions",
   374  				},
   375  				FieldPath: []string{"spec", "versions", "schema"},
   376  				Recursive: true,
   377  			},
   378  			Checks: ReduceDict(apiExtensionsV1OpenAPISpec.Components.Schemas["io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceValidation"].Properties, []check{}, func(checks []check, k string, v spec.Schema) []check {
   379  				return append(checks, checkContains(k))
   380  			}),
   381  		},
   382  		{
   383  			// Shows that the typeguess template works with scalars
   384  			Name:        "Scalar",
   385  			Subtemplate: "typeGuess",
   386  			Context: map[string]any{
   387  				"schema": map[string]any{
   388  					"type": "string",
   389  				},
   390  			},
   391  			Checks: []check{
   392  				checkEquals("string"),
   393  			},
   394  		},
   395  		{
   396  			// Show that a ref to a primitive type uses the referred type's type
   397  			Name:        "PrimitiveRef",
   398  			Subtemplate: "typeGuess",
   399  			Context: map[string]any{
   400  				"schema": map[string]any{
   401  					"description": "a cool field",
   402  					"$ref":        "#/components/schemas/v1.Time",
   403  				},
   404  				"Document": map[string]any{
   405  					"components": map[string]any{
   406  						"schemas": map[string]any{
   407  							"v1.Time": map[string]any{
   408  								"type":   "string",
   409  								"format": "date-time",
   410  							},
   411  						},
   412  					},
   413  				},
   414  			},
   415  			Checks: []check{
   416  				checkEquals("string"),
   417  			},
   418  		},
   419  		{
   420  			// Shows that the typeguess template behaves correctly given an
   421  			// array with unknown items
   422  			Name:        "ArrayUnknown",
   423  			Subtemplate: "typeGuess",
   424  			Context: map[string]any{
   425  				"schema": map[string]any{
   426  					"description": "a cool field",
   427  					"type":        "array",
   428  				},
   429  			},
   430  			Checks: []check{
   431  				checkEquals("array"),
   432  			},
   433  		},
   434  		{
   435  			// Shows that the typeguess puts Object tpye in title case
   436  			Name:        "ObjectTitle",
   437  			Subtemplate: "typeGuess",
   438  			Context: map[string]any{
   439  				"schema": map[string]any{
   440  					"description": "a cool field",
   441  					"type":        "object",
   442  				},
   443  			},
   444  			Checks: []check{
   445  				checkEquals("Object"),
   446  			},
   447  		},
   448  		{
   449  			// Shows that the typeguess template works with scalars
   450  			Name:        "ArrayOfScalar",
   451  			Subtemplate: "typeGuess",
   452  			Context: map[string]any{
   453  				"schema": map[string]any{
   454  					"description": "a cool field",
   455  					"type":        "array",
   456  					"items": map[string]any{
   457  						"type": "number",
   458  					},
   459  				},
   460  			},
   461  			Checks: []check{
   462  				checkEquals("[]number"),
   463  			},
   464  		},
   465  		{
   466  			// Shows that the typeguess template works with arrays containing
   467  			// a items which are a schema specified by a single-element allOf
   468  			// pointing to a $ref
   469  			Name:        "ArrayOfAllOfRef",
   470  			Subtemplate: "typeGuess",
   471  			Context: map[string]any{
   472  				"schema": map[string]any{
   473  					"description": "a cool field",
   474  					"type":        "array",
   475  					"items": map[string]any{
   476  						"type": "object",
   477  						"allOf": []map[string]any{
   478  							{
   479  								"$ref": "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceValidation",
   480  							},
   481  						},
   482  					},
   483  				},
   484  			},
   485  			Checks: []check{
   486  				checkEquals("[]CustomResourceValidation"),
   487  			},
   488  		},
   489  		{
   490  			// Shows that the typeguess template works with arrays containing
   491  			// a items which are a schema pointing to a $ref
   492  			Name:        "ArrayOfRef",
   493  			Subtemplate: "typeGuess",
   494  			Context: map[string]any{
   495  				"schema": map[string]any{
   496  					"description": "a cool field",
   497  					"type":        "array",
   498  					"items": map[string]any{
   499  						"type": "object",
   500  						"$ref": "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceValidation",
   501  					},
   502  				},
   503  			},
   504  			Checks: []check{
   505  				checkEquals("[]CustomResourceValidation"),
   506  			},
   507  		},
   508  		{
   509  			// Shows that the typeguess template works with arrays of maps of scalars
   510  			Name:        "ArrayOfMap",
   511  			Subtemplate: "typeGuess",
   512  			Context: map[string]any{
   513  				"schema": map[string]any{
   514  					"description": "a cool field",
   515  					"type":        "array",
   516  					"items": map[string]any{
   517  						"type": "object",
   518  						"additionalProperties": map[string]any{
   519  							"type": "string",
   520  						},
   521  					},
   522  				},
   523  			},
   524  			Checks: []check{
   525  				checkEquals("[]map[string]string"),
   526  			},
   527  		},
   528  		{
   529  			// Shows that the typeguess template works with maps of arrays of scalars
   530  			Name:        "MapOfArrayOfScalar",
   531  			Subtemplate: "typeGuess",
   532  			Context: map[string]any{
   533  				"schema": map[string]any{
   534  					"description": "a cool field",
   535  					"type":        "object",
   536  					"additionalProperties": map[string]any{
   537  						"type": "array",
   538  						"items": map[string]any{
   539  							"type": "string",
   540  						},
   541  					},
   542  				},
   543  			},
   544  			Checks: []check{
   545  				checkEquals("map[string][]string"),
   546  			},
   547  		},
   548  		{
   549  			// Shows that the typeguess template works with maps of ref types
   550  			Name:        "MapOfRef",
   551  			Subtemplate: "typeGuess",
   552  			Context: map[string]any{
   553  				"schema": map[string]any{
   554  					"description": "a cool field",
   555  					"type":        "object",
   556  					"additionalProperties": map[string]any{
   557  						"type": "string",
   558  						"allOf": []map[string]any{
   559  							{
   560  								"$ref": "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceValidation",
   561  							},
   562  						},
   563  					},
   564  				},
   565  			},
   566  			Checks: []check{
   567  				checkEquals("map[string]CustomResourceValidation"),
   568  			},
   569  		},
   570  		{
   571  			// Shows that the typeguess template prints `Object` if there
   572  			// is absolutely no type information
   573  			Name:        "Unknown",
   574  			Subtemplate: "typeGuess",
   575  			Context: map[string]any{
   576  				"schema": map[string]any{
   577  					"description": "a cool field",
   578  				},
   579  			},
   580  			Checks: []check{
   581  				checkEquals("Object"),
   582  			},
   583  		},
   584  		{
   585  			Name:        "Required",
   586  			Subtemplate: "fieldDetail",
   587  			Context: map[string]any{
   588  				"schema": map[string]any{
   589  					"type":        "object",
   590  					"description": "a description that should not be printed",
   591  					"properties": map[string]any{
   592  						"thefield": map[string]any{
   593  							"type":        "string",
   594  							"description": "a description that should not be printed",
   595  						},
   596  					},
   597  					"required": []string{"thefield"},
   598  				},
   599  				"name":  "thefield",
   600  				"short": true,
   601  			},
   602  			Checks: []check{
   603  				checkEquals("thefield\t<string> -required-\n"),
   604  			},
   605  		},
   606  		{
   607  			Name:        "Description",
   608  			Subtemplate: "fieldDetail",
   609  			Context: map[string]any{
   610  				"schema": map[string]any{
   611  					"type":        "object",
   612  					"description": "a description that should not be printed",
   613  					"properties": map[string]any{
   614  						"thefield": map[string]any{
   615  							"type":        "string",
   616  							"description": "a description that should be printed",
   617  						},
   618  					},
   619  					"required": []string{"thefield"},
   620  				},
   621  				"name":  "thefield",
   622  				"short": false,
   623  			},
   624  			Checks: []check{
   625  				checkEquals("thefield\t<string> -required-\n  a description that should be printed\n\n"),
   626  			},
   627  		},
   628  		{
   629  			Name:        "Indent",
   630  			Subtemplate: "fieldDetail",
   631  			Context: map[string]any{
   632  				"schema": map[string]any{
   633  					"type":        "object",
   634  					"description": "a description that should not be printed",
   635  					"properties": map[string]any{
   636  						"thefield": map[string]any{
   637  							"type":        "string",
   638  							"description": "a description that should not be printed",
   639  						},
   640  					},
   641  					"required": []string{"thefield"},
   642  				},
   643  				"name":  "thefield",
   644  				"short": true,
   645  				"level": 5,
   646  			},
   647  			Checks: []check{
   648  				checkEquals("          thefield\t<string> -required-\n"),
   649  			},
   650  		},
   651  		{
   652  			// show that extractEnum can skip empty enum slice
   653  			Name:        "extractEmptyEnum",
   654  			Subtemplate: "extractEnum",
   655  			Context: map[string]any{
   656  				"schema": map[string]any{
   657  					"type":        "string",
   658  					"description": "a description that should not be printed",
   659  					"enum":        []any{},
   660  				},
   661  			},
   662  			Checks: []check{
   663  				checkEquals(""),
   664  			},
   665  		},
   666  		{
   667  			// show that extractEnum can extract any enum slice and style it uppercase
   668  			Name:        "extractEnumSimpleForm",
   669  			Subtemplate: "extractEnum",
   670  			Context: map[string]any{
   671  				"schema": map[string]any{
   672  					"type":        "string",
   673  					"description": "a description that should not be printed",
   674  					"enum":        []any{0, 1, 2, 3},
   675  				},
   676  				"isLongView": true,
   677  			},
   678  			Checks: []check{
   679  				checkEquals("ENUM:\n    0\n    1\n    2\n    3"),
   680  			},
   681  		},
   682  		{
   683  			// show that extractEnum can extract any enum slice and style it lowercase
   684  			Name:        "extractEnumLongFormWithIndent",
   685  			Subtemplate: "extractEnum",
   686  			Context: map[string]any{
   687  				"schema": map[string]any{
   688  					"type":        "string",
   689  					"description": "a description that should not be printed",
   690  					"enum":        []any{0, 1, 2, 3},
   691  				},
   692  				"isLongView":   false,
   693  				"indentAmount": 2,
   694  			},
   695  			Checks: []check{
   696  				checkEquals("\n  enum: 0, 1, 2, 3"),
   697  			},
   698  		},
   699  		{
   700  			// show that extractEnum can extract any enum slice and style it with truncated enums
   701  			Name:        "extractEnumLongFormWithLimitAndIndent",
   702  			Subtemplate: "extractEnum",
   703  			Context: map[string]any{
   704  				"schema": map[string]any{
   705  					"type":        "string",
   706  					"description": "a description that should not be printed",
   707  					"enum":        []any{0, 1, 2, 3},
   708  				},
   709  				"isLongView":   false,
   710  				"limit":        2,
   711  				"indentAmount": 2,
   712  			},
   713  			Checks: []check{
   714  				checkEquals("\n  enum: 0, 1, ...."),
   715  			},
   716  		},
   717  		{
   718  			// show that extractEnum can extract any enum slice and style it with truncated enums
   719  			Name:        "extractEnumSimpleFormWithLimitAndIndent",
   720  			Subtemplate: "extractEnum",
   721  			Context: map[string]any{
   722  				"schema": map[string]any{
   723  					"type":        "string",
   724  					"description": "a description that should not be printed",
   725  					"enum":        []any{0, 1, 2, 3},
   726  				},
   727  				"isLongView":   true,
   728  				"limit":        2,
   729  				"indentAmount": 2,
   730  			},
   731  			Checks: []check{
   732  				checkEquals("ENUM:\n    0\n    1, ...."),
   733  			},
   734  		},
   735  		{
   736  			// show that extractEnum can extract any enum slice and style it with empty string
   737  			Name:        "extractEnumSimpleFormEmptyString",
   738  			Subtemplate: "extractEnum",
   739  			Context: map[string]any{
   740  				"schema": map[string]any{
   741  					"type":        "string",
   742  					"description": "a description that should not be printed",
   743  					"enum":        []any{"Block", "File", ""},
   744  				},
   745  				"isLongView": true,
   746  			},
   747  			Checks: []check{
   748  				checkEquals("ENUM:\n    Block\n    File\n    \"\""),
   749  			},
   750  		},
   751  	}
   752  
   753  	tmpl, err := v2.WithBuiltinTemplateFuncs(template.New("")).Parse(plaintextSource)
   754  	require.NoError(t, err)
   755  
   756  	for _, tcase := range testcases {
   757  		testName := tcase.Name
   758  		if len(tcase.Subtemplate) > 0 {
   759  			testName = tcase.Subtemplate + "/" + testName
   760  		}
   761  
   762  		t.Run(testName, func(t *testing.T) {
   763  			buf := bytes.NewBuffer(nil)
   764  
   765  			var outputErr error
   766  			if len(tcase.Subtemplate) == 0 {
   767  				outputErr = tmpl.Execute(buf, tcase.Context)
   768  			} else {
   769  				outputErr = tmpl.ExecuteTemplate(buf, tcase.Subtemplate, tcase.Context)
   770  			}
   771  
   772  			output := buf.String()
   773  			for _, check := range tcase.Checks {
   774  				err = check.doCheck(output, outputErr)
   775  
   776  				if err != nil {
   777  					t.Log("test failed on output:\n" + output)
   778  					require.NoError(t, err)
   779  				}
   780  			}
   781  		})
   782  	}
   783  }
   784  

View as plain text