github.com/splunk/dan1-qbec@v0.7.3/internal/eval/eval_test.go (about)

     1  /*
     2     Copyright 2019 Splunk Inc.
     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 eval
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/splunk/qbec/internal/model"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestEvalParams(t *testing.T) {
    28  	paramsMap, err := Params("testdata/params.libsonnet", Context{
    29  		Env:       "dev",
    30  		Tag:       "t1",
    31  		DefaultNs: "foobar",
    32  		Verbose:   true,
    33  	})
    34  	require.Nil(t, err)
    35  	a := assert.New(t)
    36  	comps, ok := paramsMap["components"].(map[string]interface{})
    37  	require.True(t, ok)
    38  	base, ok := comps["base"].(map[string]interface{})
    39  	require.True(t, ok)
    40  	a.EqualValues("dev", base["env"])
    41  	a.EqualValues("foobar", base["ns"])
    42  	a.EqualValues("t1", base["tag"])
    43  }
    44  
    45  func TestEvalParamsNegative(t *testing.T) {
    46  	_, err := Params("testdata/params.invalid.libsonnet", Context{Env: "dev"})
    47  	require.NotNil(t, err)
    48  	require.Contains(t, err.Error(), "end of file")
    49  
    50  	_, err = Params("testdata/params.non-object.libsonnet", Context{Env: "dev"})
    51  	require.NotNil(t, err)
    52  	require.Contains(t, err.Error(), "cannot unmarshal array")
    53  }
    54  
    55  func TestEvalComponents(t *testing.T) {
    56  	objs, err := Components([]model.Component{
    57  		{
    58  			Name: "b",
    59  			File: "testdata/components/b.yaml",
    60  		},
    61  		{
    62  			Name: "c",
    63  			File: "testdata/components/c.jsonnet",
    64  		},
    65  		{
    66  			Name: "a",
    67  			File: "testdata/components/a.json",
    68  		},
    69  	}, Context{Env: "dev", Verbose: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"})
    70  	require.Nil(t, err)
    71  	require.Equal(t, 3, len(objs))
    72  	a := assert.New(t)
    73  
    74  	obj := objs[0]
    75  	a.Equal("a", obj.Component())
    76  	a.Equal("dev", obj.Environment())
    77  	a.Equal("", obj.GroupVersionKind().Group)
    78  	a.Equal("v1", obj.GroupVersionKind().Version)
    79  	a.Equal("ConfigMap", obj.GroupVersionKind().Kind)
    80  	a.Equal("", obj.GetNamespace())
    81  	a.Equal("json-config-map", obj.GetName())
    82  	a.Equal("service2", obj.ToUnstructured().GetAnnotations()["team"])
    83  	a.Equal("#svc2", obj.ToUnstructured().GetAnnotations()["slack"])
    84  
    85  	obj = objs[1]
    86  	a.Equal("b", obj.Component())
    87  	a.Equal("dev", obj.Environment())
    88  	a.Equal("yaml-config-map", obj.GetName())
    89  	a.Equal("service2", obj.ToUnstructured().GetAnnotations()["team"])
    90  	a.Equal("#svc2", obj.ToUnstructured().GetAnnotations()["slack"])
    91  
    92  	obj = objs[2]
    93  	a.Equal("c", obj.Component())
    94  	a.Equal("dev", obj.Environment())
    95  	a.Equal("jsonnet-config-map", obj.GetName())
    96  	a.Equal("service2", obj.ToUnstructured().GetAnnotations()["team"])
    97  	a.Equal("#svc2", obj.ToUnstructured().GetAnnotations()["slack"])
    98  }
    99  
   100  func TestEvalComponentsClean(t *testing.T) {
   101  	objs, err := Components([]model.Component{
   102  		{
   103  			Name: "a",
   104  			File: "testdata/components/a.json",
   105  		},
   106  	}, Context{Env: "dev", CleanMode: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"})
   107  	require.Nil(t, err)
   108  	require.Equal(t, 1, len(objs))
   109  	a := assert.New(t)
   110  
   111  	obj := objs[0]
   112  	a.Equal("a", obj.Component())
   113  	a.Equal("dev", obj.Environment())
   114  	a.Equal("", obj.GroupVersionKind().Group)
   115  	a.Equal("v1", obj.GroupVersionKind().Version)
   116  	a.Equal("ConfigMap", obj.GroupVersionKind().Kind)
   117  	a.Equal("", obj.GetNamespace())
   118  	a.Equal("json-config-map", obj.GetName())
   119  	a.Equal("", obj.ToUnstructured().GetAnnotations()["team"])
   120  	a.Equal("", obj.ToUnstructured().GetAnnotations()["slack"])
   121  }
   122  
   123  func TestEvalComponentsEdges(t *testing.T) {
   124  	goodComponents := []model.Component{
   125  		{Name: "g1", File: "testdata/good-components/g1.jsonnet"},
   126  		{Name: "g2", File: "testdata/good-components/g2.jsonnet"},
   127  		{Name: "g3", File: "testdata/good-components/g3.jsonnet"},
   128  		{Name: "g4", File: "testdata/good-components/g4.jsonnet"},
   129  		{Name: "g5", File: "testdata/good-components/g5.jsonnet"},
   130  	}
   131  	goodAssert := func(t *testing.T, ret []model.K8sLocalObject, err error) {
   132  		require.NotNil(t, err)
   133  	}
   134  	tests := []struct {
   135  		name        string
   136  		components  []model.Component
   137  		asserter    func(*testing.T, []model.K8sLocalObject, error)
   138  		concurrency int
   139  	}{
   140  		{
   141  			name: "no components",
   142  			asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) {
   143  				require.Nil(t, err)
   144  				assert.Equal(t, 0, len(ret))
   145  			},
   146  		},
   147  		{
   148  			name:       "single bad",
   149  			components: []model.Component{{Name: "e1", File: "testdata/bad-components/e1.jsonnet"}},
   150  			asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) {
   151  				require.NotNil(t, err)
   152  				assert.Contains(t, err.Error(), "evaluate 'e1'")
   153  			},
   154  		},
   155  		{
   156  			name: "two bad",
   157  			components: []model.Component{
   158  				{Name: "e1", File: "testdata/bad-components/e1.jsonnet"},
   159  				{Name: "e2", File: "testdata/bad-components/e2.jsonnet"},
   160  			},
   161  			asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) {
   162  				require.NotNil(t, err)
   163  				assert.Contains(t, err.Error(), "evaluate 'e1'")
   164  				assert.Contains(t, err.Error(), "evaluate 'e2'")
   165  			},
   166  		},
   167  		{
   168  			name: "many bad",
   169  			components: []model.Component{
   170  				{Name: "e1", File: "testdata/bad-components/e1.jsonnet"},
   171  				{Name: "e2", File: "testdata/bad-components/e2.jsonnet"},
   172  				{Name: "e3", File: "testdata/bad-components/e3.jsonnet"},
   173  				{Name: "e4", File: "testdata/bad-components/e4.jsonnet"},
   174  				{Name: "e5", File: "testdata/bad-components/e5.jsonnet"},
   175  			},
   176  			asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) {
   177  				require.NotNil(t, err)
   178  				assert.Contains(t, err.Error(), "... and 2 more errors")
   179  			},
   180  		},
   181  		{
   182  			name: "bad file",
   183  			components: []model.Component{
   184  				{Name: "e1", File: "testdata/bad-components/XXX.jsonnet"},
   185  			},
   186  			asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) {
   187  				require.NotNil(t, err)
   188  				assert.Contains(t, err.Error(), "no such file")
   189  			},
   190  		},
   191  		{
   192  			name:        "negative concurrency",
   193  			components:  goodComponents,
   194  			asserter:    goodAssert,
   195  			concurrency: -10,
   196  		},
   197  		{
   198  			name:        "zero concurrency",
   199  			components:  goodComponents,
   200  			asserter:    goodAssert,
   201  			concurrency: 0,
   202  		},
   203  		{
   204  			name:        "4 concurrency",
   205  			components:  goodComponents,
   206  			asserter:    goodAssert,
   207  			concurrency: 4,
   208  		},
   209  		{
   210  			name:        "one concurrency",
   211  			components:  goodComponents,
   212  			asserter:    goodAssert,
   213  			concurrency: 1,
   214  		},
   215  		{
   216  			name:        "million concurrency",
   217  			components:  goodComponents,
   218  			asserter:    goodAssert,
   219  			concurrency: 1000000,
   220  		},
   221  	}
   222  	for _, test := range tests {
   223  		t.Run(test.name, func(t *testing.T) {
   224  			ret, err := evalComponents(test.components, Context{
   225  				Env:         "dev",
   226  				Concurrency: test.concurrency,
   227  			}, postProc{})
   228  			test.asserter(t, ret, err)
   229  		})
   230  	}
   231  }
   232  
   233  func TestEvalComponentsBadJson(t *testing.T) {
   234  	_, err := Components([]model.Component{
   235  		{
   236  			Name: "bad",
   237  			File: "testdata/components/bad.json",
   238  		},
   239  	}, Context{Env: "dev"})
   240  	require.NotNil(t, err)
   241  	require.Contains(t, err.Error(), "invalid character")
   242  }
   243  
   244  func TestEvalComponentsBadPosProcessor(t *testing.T) {
   245  	_, err := Components([]model.Component{
   246  		{
   247  			Name: "bad",
   248  			File: "testdata/components/good.json",
   249  		},
   250  	}, Context{Env: "dev", PostProcessFile: "foo/bar.jsonnet"})
   251  	require.NotNil(t, err)
   252  	require.Contains(t, err.Error(), "read post-eval file:")
   253  }
   254  
   255  func TestEvalComponentsBadYaml(t *testing.T) {
   256  	_, err := Components([]model.Component{
   257  		{
   258  			Name: "bad",
   259  			File: "testdata/components/bad.yaml",
   260  		},
   261  	}, Context{Env: "dev"})
   262  	require.NotNil(t, err)
   263  	require.Contains(t, err.Error(), "did not find expected node content")
   264  }
   265  
   266  func TestEvalComponentsBadObjects(t *testing.T) {
   267  	_, err := Components([]model.Component{
   268  		{
   269  			Name: "bad",
   270  			File: "testdata/components/bad-objects.yaml",
   271  		},
   272  	}, Context{Env: "dev"})
   273  	require.NotNil(t, err)
   274  	require.Contains(t, err.Error(), `unexpected type for object (string) at path "$[0].foo"`)
   275  }
   276  
   277  func TestEvalPostProcessor(t *testing.T) {
   278  	obj := map[string]interface{}{
   279  		"apiVersion": "v1",
   280  		"kind":       "ConfigMap",
   281  		"metadata": map[string]interface{}{
   282  			"name": "cm",
   283  		},
   284  		"data": map[string]interface{}{
   285  			"foo": "bar",
   286  		},
   287  	}
   288  	tests := []struct {
   289  		name     string
   290  		code     string
   291  		asserter func(t *testing.T, ret map[string]interface{}, err error)
   292  	}{
   293  		{
   294  			name: "add annotation",
   295  			code: `function (object) object + { metadata +: { annotations +:{ slack: '#crash' }}}`,
   296  			asserter: func(t *testing.T, ret map[string]interface{}, err error) {
   297  				require.Nil(t, err)
   298  				ann := ret["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["slack"]
   299  				assert.Equal(t, "#crash", ann)
   300  			},
   301  		},
   302  		{
   303  			name: "return scalar",
   304  			code: `function (object) "boo"`,
   305  			asserter: func(t *testing.T, ret map[string]interface{}, err error) {
   306  				require.NotNil(t, err)
   307  				assert.Equal(t, `post-eval did not return an object, "boo"`+"\n", err.Error())
   308  			},
   309  		},
   310  		{
   311  			name: "return array",
   312  			code: `function (object) [ object ]`,
   313  			asserter: func(t *testing.T, ret map[string]interface{}, err error) {
   314  				require.NotNil(t, err)
   315  				assert.Contains(t, err.Error(), `post-eval did not return an object, [`)
   316  			},
   317  		},
   318  		{
   319  			name: "return k8s list",
   320  			code: `function (object) { apiVersion: "v1", kind: "List", items: [ object ] }`,
   321  			asserter: func(t *testing.T, ret map[string]interface{}, err error) {
   322  				require.NotNil(t, err)
   323  				assert.Contains(t, err.Error(), `post-eval did not return a K8s object,`)
   324  			},
   325  		},
   326  		{
   327  			name: "bad code",
   328  			code: `function (object) object2`,
   329  			asserter: func(t *testing.T, ret map[string]interface{}, err error) {
   330  				require.NotNil(t, err)
   331  				assert.Contains(t, err.Error(), `post-eval object: pp.jsonnet:1`)
   332  			},
   333  		},
   334  		{
   335  			name: "bad tla",
   336  			code: `function (o) o`,
   337  			asserter: func(t *testing.T, ret map[string]interface{}, err error) {
   338  				require.NotNil(t, err)
   339  				assert.Contains(t, err.Error(), `post-eval object: RUNTIME ERROR: function has no parameter object`)
   340  			},
   341  		},
   342  	}
   343  
   344  	for _, test := range tests {
   345  		t.Run(test.name, func(t *testing.T) {
   346  			ctx := Context{Env: "dev"}
   347  			pp := postProc{ctx: ctx, code: test.code, file: "pp.jsonnet"}
   348  			ret, err := pp.run(obj)
   349  			test.asserter(t, ret, err)
   350  		})
   351  	}
   352  }