github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/config/resolve/api_test.go (about)

     1  package resolve
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/go-test/deep"
    13  	"github.com/replicatedhq/libyaml"
    14  	"github.com/replicatedhq/ship/pkg/api"
    15  	_ "github.com/replicatedhq/ship/pkg/lifecycle/render/config/resolve/test-cases/api"
    16  	"github.com/replicatedhq/ship/pkg/templates"
    17  	"github.com/replicatedhq/ship/pkg/testing/logger"
    18  	"github.com/spf13/viper"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	yaml "gopkg.in/yaml.v3"
    22  )
    23  
    24  type apiTestcase struct {
    25  	Name        string
    26  	Error       bool
    27  	Config      []libyaml.ConfigGroup
    28  	ViperConfig map[string]interface{}   `yaml:"viper_config"`
    29  	Responses   responsesJSON            `yaml:"responses"`
    30  	LiveValues  []map[string]interface{} `yaml:"input"`
    31  	State       map[string]interface{}   `yaml:"state"`
    32  }
    33  
    34  type responsesJSON struct {
    35  	JSON string `yaml:"json"`
    36  }
    37  
    38  type configValuesTestCase struct {
    39  	dependencies map[string][]string
    40  	input        map[string]interface{}
    41  	results      map[string]interface{}
    42  	prefix       string
    43  	suffix       string
    44  
    45  	Name string
    46  }
    47  
    48  type configGroupHiddenTestCase struct {
    49  	Config        libyaml.ConfigGroup
    50  	ExpectedValue bool
    51  	ExpectErr     bool
    52  
    53  	Name string
    54  }
    55  
    56  type configItemRequiredTestCase struct {
    57  	Config        *libyaml.ConfigItem
    58  	ExpectedValue *ValidationError
    59  	ExpectErr     bool
    60  
    61  	Name string
    62  }
    63  
    64  type configTestCase struct {
    65  	Config        []libyaml.ConfigGroup
    66  	ExpectedValue []*ValidationError
    67  	ExpectErr     bool
    68  
    69  	Name string
    70  }
    71  
    72  func TestAPIResolver(t *testing.T) {
    73  	ctx := context.Background()
    74  
    75  	tests := loadAPITestCases(t, filepath.Join("test-cases", "api"))
    76  
    77  	for _, test := range tests {
    78  		t.Run(test.Name, func(t *testing.T) {
    79  			req := require.New(t)
    80  			v := viper.New()
    81  			testLogger := &logger.TestLogger{T: t}
    82  
    83  			builderBuilder := &templates.BuilderBuilder{
    84  				Logger: testLogger,
    85  				Viper:  v,
    86  			}
    87  
    88  			resolver := &APIConfigRenderer{
    89  				Logger:         testLogger,
    90  				Viper:          v,
    91  				BuilderBuilder: builderBuilder,
    92  			}
    93  
    94  			release := &api.Release{
    95  				Spec: api.Spec{
    96  					Config: api.Config{
    97  						V1: test.Config,
    98  					},
    99  				},
   100  			}
   101  
   102  			func() {
   103  				if test.LiveValues == nil {
   104  					test.LiveValues = make([]map[string]interface{}, 0)
   105  				}
   106  				if test.State == nil {
   107  					test.State = make(map[string]interface{})
   108  				}
   109  
   110  				var resolvedConfig []libyaml.ConfigGroup
   111  				var err error
   112  
   113  				if len(test.LiveValues) == 0 {
   114  					resolvedConfig, err = resolver.ResolveConfig(ctx, release, test.State, make(map[string]interface{}), true)
   115  				} else {
   116  					// simulate multiple inputs
   117  					for _, liveValues := range test.LiveValues {
   118  						resolvedConfig, err = resolver.ResolveConfig(ctx, release, test.State, liveValues, false)
   119  					}
   120  				}
   121  
   122  				if test.Error {
   123  					req.True(err != nil, "Expected this api call to return an error")
   124  				} else {
   125  					req.NoError(err)
   126  				}
   127  
   128  				marshalled, err := json.MarshalIndent(resolvedConfig, "", "    ")
   129  				req.NoError(err)
   130  
   131  				areSame := areSameJSON(t, marshalled, []byte(test.Responses.JSON))
   132  
   133  				var expected []libyaml.ConfigGroup
   134  				err = json.Unmarshal([]byte(test.Responses.JSON), &expected)
   135  				assert.NoError(t, err)
   136  				req.NoError(err)
   137  
   138  				diff := deep.Equal(resolvedConfig, expected)
   139  
   140  				req.True(areSame, "%v", strings.Join(diff, "\n"))
   141  			}()
   142  		})
   143  	}
   144  }
   145  
   146  func TestResolveConfigValuesMap(t *testing.T) {
   147  	tests := []configValuesTestCase{
   148  		{
   149  			dependencies: map[string][]string{
   150  				"alpha": {},
   151  
   152  				"bravo": {"alpha"},
   153  			},
   154  			input:   map[string]interface{}{"alpha": "abc"},
   155  			results: map[string]interface{}{"alpha": "abc", "bravo": "abc"},
   156  			Name:    "basic_dependency",
   157  		},
   158  		{
   159  			dependencies: map[string][]string{
   160  				"alpha":   {},
   161  				"bravo":   {"alpha"},
   162  				"charlie": {"bravo"},
   163  			},
   164  			input:   map[string]interface{}{"alpha": "abc"},
   165  			results: map[string]interface{}{"alpha": "abc", "bravo": "(abc)", "charlie": "((abc))"},
   166  			prefix:  "(",
   167  			suffix:  ")",
   168  			Name:    "basic_chain",
   169  		},
   170  		{
   171  			dependencies: map[string][]string{
   172  				"alpha":   {},
   173  				"bravo":   {},
   174  				"charlie": {"alpha", "bravo"},
   175  			},
   176  			input:   map[string]interface{}{"alpha": "abc", "bravo": "xyz"},
   177  			results: map[string]interface{}{"alpha": "abc", "bravo": "xyz", "charlie": "(abcxyz)"},
   178  			prefix:  "(",
   179  			suffix:  ")",
   180  			Name:    "basic_2deps",
   181  		},
   182  		{
   183  			dependencies: map[string][]string{
   184  				"alpha":   {},
   185  				"bravo":   {},
   186  				"charlie": {"alpha", "bravo"},
   187  				"delta":   {"charlie"},
   188  			},
   189  			input:   map[string]interface{}{"alpha": "abc", "bravo": "xyz"},
   190  			results: map[string]interface{}{"alpha": "abc", "bravo": "xyz", "charlie": "(abcxyz)", "delta": "((abcxyz))"},
   191  			prefix:  "(",
   192  			suffix:  ")",
   193  			Name:    "basic_Y_shape",
   194  		},
   195  		{
   196  			dependencies: map[string][]string{
   197  				"alpha":   {},
   198  				"bravo":   {"alpha"},
   199  				"charlie": {"alpha"},
   200  				"delta":   {"bravo", "charlie"},
   201  			},
   202  			input:   map[string]interface{}{"alpha": "abc"},
   203  			results: map[string]interface{}{"alpha": "abc", "bravo": "(abc)", "charlie": "(abc)", "delta": "((abc)(abc))"},
   204  			prefix:  "(",
   205  			suffix:  ")",
   206  			Name:    "basic_◇_shape",
   207  		},
   208  	}
   209  
   210  	for _, test := range tests {
   211  		t.Run(test.Name, func(t *testing.T) {
   212  			req := require.New(t)
   213  
   214  			//build a config to test
   215  			groups := buildTestConfigGroups(test.dependencies, test.prefix, test.suffix, false)
   216  
   217  			testLogger := &logger.TestLogger{T: t}
   218  			v := viper.New()
   219  			builderBuilder := &templates.BuilderBuilder{
   220  				Logger: testLogger,
   221  				Viper:  v,
   222  			}
   223  			renderer := &APIConfigRenderer{
   224  				Logger:         testLogger,
   225  				Viper:          v,
   226  				BuilderBuilder: builderBuilder,
   227  			}
   228  			output, err := renderer.resolveConfigValuesMap(api.ReleaseMetadata{}, test.input, groups)
   229  			req.NoError(err)
   230  
   231  			req.Equal(test.results, output)
   232  		})
   233  	}
   234  }
   235  
   236  func areSameJSON(t *testing.T, s1, s2 []byte) bool {
   237  	var o1 interface{}
   238  	var o2 interface{}
   239  
   240  	err := json.Unmarshal(s1, &o1)
   241  	require.NoError(t, err, string(s1))
   242  
   243  	err = json.Unmarshal(s2, &o2)
   244  	require.NoError(t, err, string(s2))
   245  
   246  	return reflect.DeepEqual(o1, o2)
   247  }
   248  
   249  func loadAPITestCases(t *testing.T, path string) []apiTestcase {
   250  	files, err := ioutil.ReadDir(path)
   251  	assert.NoError(t, err)
   252  
   253  	tests := make([]apiTestcase, 0)
   254  
   255  	for _, file := range files {
   256  		if filepath.Ext(filepath.Join(path, file.Name())) != ".yml" {
   257  			continue
   258  		}
   259  
   260  		contents, err := ioutil.ReadFile(filepath.Join(path, file.Name()))
   261  		assert.NoError(t, err)
   262  
   263  		test := make([]apiTestcase, 0)
   264  		err = yaml.Unmarshal(contents, &test)
   265  		assert.NoError(t, err)
   266  
   267  		tests = append(tests, test...)
   268  	}
   269  
   270  	return tests
   271  }
   272  
   273  func TestHiddenConfigGroup(t *testing.T) {
   274  	tests := []configGroupHiddenTestCase{
   275  		{
   276  			Config:        libyaml.ConfigGroup{},
   277  			ExpectedValue: true,
   278  			Name:          "empty test",
   279  		},
   280  		{
   281  			Config: libyaml.ConfigGroup{
   282  				Name: "one hidden item = hidden group",
   283  				Items: []*libyaml.ConfigItem{
   284  					{
   285  						Name:   "alpha",
   286  						Hidden: true,
   287  					},
   288  				},
   289  			},
   290  			ExpectedValue: true,
   291  			Name:          "one item, hidden => hidden group",
   292  		},
   293  		{
   294  			Config: libyaml.ConfigGroup{
   295  				Name: "two hidden items = hidden group",
   296  				Items: []*libyaml.ConfigItem{
   297  					{
   298  						Name:   "alpha",
   299  						Hidden: true,
   300  					},
   301  					{
   302  						Name:   "beta",
   303  						Hidden: true,
   304  					},
   305  				},
   306  			},
   307  			ExpectedValue: true,
   308  			Name:          "two items, both hidden => hidden group",
   309  		},
   310  		{
   311  			Config: libyaml.ConfigGroup{
   312  				Name: "one item, not hidden",
   313  				Items: []*libyaml.ConfigItem{
   314  					{
   315  						Name:   "alpha",
   316  						Hidden: false,
   317  					},
   318  				},
   319  			},
   320  			ExpectedValue: false,
   321  			Name:          "one item, not hidden => NOT hidden group",
   322  		},
   323  		{
   324  			Config: libyaml.ConfigGroup{
   325  				Name: "two items, one hidden",
   326  				Items: []*libyaml.ConfigItem{
   327  					{
   328  						Name:   "alpha",
   329  						Hidden: true,
   330  					},
   331  					{
   332  						Name:   "beta",
   333  						Hidden: false,
   334  					},
   335  				},
   336  			},
   337  			ExpectedValue: false,
   338  			Name:          "two items, one hidden => NOT hidden group",
   339  		},
   340  	}
   341  
   342  	for _, test := range tests {
   343  		t.Run(test.Name, func(t *testing.T) {
   344  			req := require.New(t)
   345  
   346  			val := configGroupIsHidden(test.Config)
   347  
   348  			req.Equal(test.ExpectedValue, val)
   349  		})
   350  	}
   351  }
   352  
   353  func TestValidateConfigItem(t *testing.T) {
   354  	tests := []configItemRequiredTestCase{
   355  		{
   356  			Config:        &libyaml.ConfigItem{},
   357  			ExpectedValue: (*ValidationError)(nil),
   358  			Name:          "empty test",
   359  		},
   360  		{
   361  			Config: &libyaml.ConfigItem{
   362  
   363  				Name:     "alpha",
   364  				Title:    "alpha value",
   365  				Required: true,
   366  				Value:    "",
   367  				Default:  "",
   368  			},
   369  
   370  			ExpectedValue: &ValidationError{
   371  				Message:   "Config item alpha is required",
   372  				Name:      "alpha",
   373  				ErrorCode: "MISSING_REQUIRED_VALUE",
   374  			},
   375  			Name: "basic fail",
   376  		},
   377  		{
   378  			Config: &libyaml.ConfigItem{
   379  
   380  				Name:     "alpha",
   381  				Title:    "alpha value",
   382  				Required: false,
   383  				Value:    "",
   384  				Default:  "",
   385  			},
   386  
   387  			ExpectedValue: (*ValidationError)(nil),
   388  			Name:          "basic pass",
   389  		},
   390  		{
   391  			Config: &libyaml.ConfigItem{
   392  
   393  				Name:     "alpha",
   394  				Title:    "alpha value",
   395  				Required: true,
   396  				Value:    "value",
   397  				Default:  "",
   398  			},
   399  
   400  			ExpectedValue: (*ValidationError)(nil),
   401  			Name:          "pass due to value",
   402  		},
   403  		{
   404  			Config: &libyaml.ConfigItem{
   405  
   406  				Name:     "alpha",
   407  				Title:    "alpha value",
   408  				Required: true,
   409  				Value:    "",
   410  				Default:  "default",
   411  			},
   412  			ExpectedValue: (*ValidationError)(nil),
   413  			Name:          "pass due to default",
   414  		},
   415  		{
   416  			Config: &libyaml.ConfigItem{
   417  
   418  				Name:     "alpha",
   419  				Title:    "alpha value",
   420  				Required: true,
   421  				Hidden:   true,
   422  				Value:    "",
   423  				Default:  "",
   424  			},
   425  			ExpectedValue: (*ValidationError)(nil),
   426  			Name:          "pass due to hidden",
   427  		},
   428  		{
   429  			Config: &libyaml.ConfigItem{
   430  
   431  				Name:     "alpha",
   432  				Title:    "alpha value",
   433  				Required: true,
   434  				ReadOnly: true,
   435  				Value:    "",
   436  				Default:  "",
   437  			},
   438  			ExpectedValue: (*ValidationError)(nil),
   439  			Name:          "pass due to readonly set",
   440  		},
   441  		{
   442  			Config: &libyaml.ConfigItem{
   443  
   444  				Name:     "alpha",
   445  				Title:    "alpha value",
   446  				Type:     "label",
   447  				Required: true,
   448  				Value:    "",
   449  				Default:  "",
   450  			},
   451  
   452  			ExpectedValue: (*ValidationError)(nil),
   453  			Name:          "pass due to readonly type",
   454  		},
   455  	}
   456  
   457  	for _, test := range tests {
   458  		t.Run(test.Name, func(t *testing.T) {
   459  			req := require.New(t)
   460  
   461  			val := validateConfigItem(test.Config)
   462  
   463  			req.Equal(test.ExpectedValue, val)
   464  		})
   465  	}
   466  }
   467  
   468  func TestValidateConfig(t *testing.T) {
   469  	tests := []configTestCase{
   470  		{
   471  			Config:        []libyaml.ConfigGroup{},
   472  			ExpectedValue: ([]*ValidationError)(nil),
   473  			Name:          "empty test",
   474  		},
   475  		{
   476  			Config: []libyaml.ConfigGroup{
   477  				{
   478  					Name: "testing",
   479  					Items: []*libyaml.ConfigItem{
   480  						{
   481  							Name:     "alpha",
   482  							Title:    "alpha value",
   483  							Required: false,
   484  							Value:    "",
   485  							Default:  "",
   486  						},
   487  					},
   488  				},
   489  			},
   490  			ExpectedValue: ([]*ValidationError)(nil),
   491  			Name:          "one group one item, not required",
   492  		},
   493  		{
   494  			Config: []libyaml.ConfigGroup{
   495  				{
   496  					Name: "testing",
   497  					Items: []*libyaml.ConfigItem{
   498  						{
   499  							Name:     "alpha",
   500  							Title:    "alpha value",
   501  							Required: true,
   502  							Value:    "",
   503  							Default:  "",
   504  						},
   505  					},
   506  				},
   507  			},
   508  			ExpectedValue: []*ValidationError{
   509  				{
   510  					Message:   "Config item alpha is required",
   511  					Name:      "alpha",
   512  					ErrorCode: "MISSING_REQUIRED_VALUE",
   513  				},
   514  			},
   515  			Name: "one group one item, required, no value",
   516  		},
   517  		{
   518  			Config: []libyaml.ConfigGroup{
   519  				{
   520  					Name: "testing",
   521  					Items: []*libyaml.ConfigItem{
   522  						{
   523  							Name:     "alpha",
   524  							Title:    "alpha value",
   525  							Required: true,
   526  							Value:    "abc",
   527  							Default:  "",
   528  						},
   529  					},
   530  				},
   531  			},
   532  			ExpectedValue: ([]*ValidationError)(nil),
   533  			Name:          "one group one item, required, value",
   534  		},
   535  		{
   536  			Config: []libyaml.ConfigGroup{
   537  				{
   538  					Name: "testing",
   539  					Items: []*libyaml.ConfigItem{
   540  						{
   541  							Name:     "alpha",
   542  							Title:    "alpha value",
   543  							Required: false,
   544  							Value:    "",
   545  							Default:  "",
   546  							Hidden:   true,
   547  						},
   548  					},
   549  				},
   550  			},
   551  			ExpectedValue: ([]*ValidationError)(nil),
   552  			Name:          "one group one item, not required, hidden, no value",
   553  		},
   554  		{
   555  			Config: []libyaml.ConfigGroup{
   556  				{
   557  					Name: "testing",
   558  					Items: []*libyaml.ConfigItem{
   559  						{
   560  							Name:     "alpha",
   561  							Title:    "alpha value",
   562  							Required: true,
   563  							Value:    "",
   564  							Default:  "",
   565  							Hidden:   true,
   566  						},
   567  					},
   568  				},
   569  			},
   570  			ExpectedValue: ([]*ValidationError)(nil),
   571  			Name:          "one group one item, required, not hidden, no value",
   572  		},
   573  		{
   574  			Config: []libyaml.ConfigGroup{
   575  				{
   576  					Name: "testing",
   577  					Items: []*libyaml.ConfigItem{
   578  						{
   579  							Name:     "alpha",
   580  							Title:    "alpha value",
   581  							Required: true,
   582  							Value:    "",
   583  							Default:  "",
   584  							Hidden:   false,
   585  						},
   586  					},
   587  				},
   588  			},
   589  			ExpectedValue: []*ValidationError{
   590  				{
   591  					Message:   "Config item alpha is required",
   592  					Name:      "alpha",
   593  					ErrorCode: "MISSING_REQUIRED_VALUE",
   594  				},
   595  			},
   596  			Name: "one group one item, required, not hidden, no value",
   597  		},
   598  		{
   599  			Config: []libyaml.ConfigGroup{
   600  				{
   601  					Name: "testing",
   602  					Items: []*libyaml.ConfigItem{
   603  						{
   604  							Name:     "alpha",
   605  							Title:    "alpha value",
   606  							Required: true,
   607  							Value:    "abc",
   608  							Default:  "",
   609  							Hidden:   false,
   610  						},
   611  					},
   612  				},
   613  			},
   614  			ExpectedValue: ([]*ValidationError)(nil),
   615  			Name:          "one group one item, required, not hidden, value",
   616  		},
   617  		{
   618  			Config: []libyaml.ConfigGroup{
   619  				{
   620  					Name: "testing",
   621  					Items: []*libyaml.ConfigItem{
   622  						{
   623  							Name:     "alpha",
   624  							Title:    "alpha value",
   625  							Required: false,
   626  							Value:    "",
   627  							Default:  "",
   628  							Hidden:   false,
   629  						},
   630  					},
   631  				},
   632  			},
   633  			ExpectedValue: ([]*ValidationError)(nil),
   634  			Name:          "one group one item, not required, not hidden, no value",
   635  		},
   636  		{
   637  			Config: []libyaml.ConfigGroup{
   638  				{
   639  					Name: "testing",
   640  					Items: []*libyaml.ConfigItem{
   641  						{
   642  							Name:     "alpha",
   643  							Title:    "alpha value",
   644  							Required: true,
   645  							Value:    "abc",
   646  							Default:  "",
   647  							Hidden:   true,
   648  						},
   649  					},
   650  				},
   651  			},
   652  			ExpectedValue: ([]*ValidationError)(nil),
   653  			Name:          "one group one item, required, hidden, value",
   654  		},
   655  		{
   656  			Config: []libyaml.ConfigGroup{
   657  				{
   658  					Name: "testing",
   659  					Items: []*libyaml.ConfigItem{
   660  						{
   661  							Name:     "alpha",
   662  							Required: false,
   663  							Value:    "",
   664  							Default:  "",
   665  						},
   666  						{
   667  							Name:     "beta",
   668  							Required: false,
   669  							Value:    "",
   670  							Default:  "",
   671  						},
   672  					},
   673  				},
   674  			},
   675  			ExpectedValue: ([]*ValidationError)(nil),
   676  			Name:          "one group two items",
   677  		},
   678  		{
   679  			Config: []libyaml.ConfigGroup{
   680  				{
   681  					Name: "testing",
   682  					Items: []*libyaml.ConfigItem{
   683  						{
   684  							Name:     "alpha",
   685  							Required: true,
   686  							Value:    "",
   687  							Default:  "",
   688  						},
   689  						{
   690  							Name:     "beta",
   691  							Required: true,
   692  							Value:    "",
   693  							Default:  "",
   694  						},
   695  					},
   696  				},
   697  			},
   698  			ExpectedValue: []*ValidationError{
   699  				{
   700  					Message:   "Config item alpha is required",
   701  					Name:      "alpha",
   702  					ErrorCode: "MISSING_REQUIRED_VALUE",
   703  				},
   704  				{
   705  					Message:   "Config item beta is required",
   706  					Name:      "beta",
   707  					ErrorCode: "MISSING_REQUIRED_VALUE",
   708  				},
   709  			},
   710  			Name: "one group two items, required",
   711  		},
   712  		{
   713  			Config: []libyaml.ConfigGroup{
   714  				{
   715  					Name: "testing",
   716  					Items: []*libyaml.ConfigItem{
   717  						{
   718  							Name:     "alpha",
   719  							Required: true,
   720  							Value:    "abc",
   721  							Default:  "",
   722  						},
   723  						{
   724  							Name:     "beta",
   725  							Required: true,
   726  							Value:    "",
   727  							Default:  "",
   728  						},
   729  					},
   730  				},
   731  			},
   732  			ExpectedValue: []*ValidationError{
   733  				{
   734  					Message:   "Config item beta is required",
   735  					Name:      "beta",
   736  					ErrorCode: "MISSING_REQUIRED_VALUE",
   737  				},
   738  			},
   739  			Name: "one group two items, required",
   740  		},
   741  		{
   742  			Config: []libyaml.ConfigGroup{
   743  				{
   744  					Name: "testing",
   745  					Items: []*libyaml.ConfigItem{
   746  						{
   747  							Name:     "alpha",
   748  							Required: true,
   749  							Value:    "abc",
   750  							Default:  "",
   751  						},
   752  						{
   753  							Name:     "beta",
   754  							Required: true,
   755  							Value:    "xyz",
   756  							Default:  "",
   757  						},
   758  					},
   759  				},
   760  			},
   761  			ExpectedValue: ([]*ValidationError)(nil),
   762  			Name:          "one group two items, required",
   763  		},
   764  	}
   765  
   766  	for _, test := range tests {
   767  		t.Run(test.Name, func(t *testing.T) {
   768  			req := require.New(t)
   769  
   770  			val := ValidateConfig(test.Config)
   771  
   772  			req.Equal(test.ExpectedValue, val)
   773  		})
   774  	}
   775  }