github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/helm/template_test.go (about)

     1  package helm
     2  
     3  import (
     4  	"os"
     5  	"path"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/emosbaugh/yaml"
    10  	"github.com/golang/mock/gomock"
    11  	"github.com/replicatedhq/libyaml"
    12  	"github.com/replicatedhq/ship/pkg/api"
    13  	"github.com/replicatedhq/ship/pkg/constants"
    14  	"github.com/replicatedhq/ship/pkg/lifecycle/render/root"
    15  	"github.com/replicatedhq/ship/pkg/process"
    16  	state2 "github.com/replicatedhq/ship/pkg/state"
    17  	"github.com/replicatedhq/ship/pkg/templates"
    18  	"github.com/replicatedhq/ship/pkg/test-mocks/helm"
    19  	"github.com/replicatedhq/ship/pkg/test-mocks/state"
    20  	"github.com/replicatedhq/ship/pkg/testing/logger"
    21  	"github.com/spf13/afero"
    22  	"github.com/spf13/viper"
    23  	"github.com/stretchr/testify/require"
    24  	"k8s.io/helm/pkg/chartutil"
    25  )
    26  
    27  func TestLocalTemplater(t *testing.T) {
    28  	tests := []struct {
    29  		name                string
    30  		describe            string
    31  		expectError         string
    32  		helmOpts            []string
    33  		helmValues          map[string]interface{}
    34  		expectedHelmValues  []string
    35  		templateContext     map[string]interface{}
    36  		channelName         string
    37  		expectedChannelName string
    38  		ontemplate          func(req *require.Assertions, mockFs afero.Afero) func(chartRoot string, args []string) error
    39  		state               *state2.State
    40  		requirements        *chartutil.Requirements
    41  		repoAdd             []string
    42  		namespace           string
    43  	}{
    44  		{
    45  			name:        "helm test proper args",
    46  			describe:    "test that helm is invoked with the proper args. The subprocess will fail if its not called with the args set in EXPECT_HELM_ARGV",
    47  			expectError: "",
    48  		},
    49  		{
    50  			name:        "helm with set value",
    51  			describe:    "ensure any helm.helm_opts are forwarded down to the call to `helm template`",
    52  			expectError: "",
    53  			helmOpts:    []string{"--set", "service.clusterIP=10.3.9.2"},
    54  		},
    55  		{
    56  			name:        "helm with subcharts",
    57  			describe:    "ensure any helm.helm_opts are forwarded down to the call to `helm template`",
    58  			expectError: "",
    59  			helmOpts:    []string{"--set", "service.clusterIP=10.3.9.2"},
    60  			ontemplate: func(req *require.Assertions, mockFs afero.Afero) func(chartRoot string, args []string) error {
    61  				return func(chartRoot string, args []string) error {
    62  					mockFolderPathToCreate := path.Join(constants.ShipPathInternalTmp, "chartrendered", "frobnitz", "templates")
    63  					req.NoError(mockFs.MkdirAll(mockFolderPathToCreate, 0755))
    64  					mockChartsPathToCreate := path.Join(constants.ShipPathInternalTmp, "chartrendered", "frobnitz", "charts")
    65  					req.NoError(mockFs.MkdirAll(mockChartsPathToCreate, 0755))
    66  					return nil
    67  				}
    68  			},
    69  		},
    70  		{
    71  			name:        "helm values from asset value",
    72  			describe:    "ensure any helm.helm_opts are forwarded down to the call to `helm template`",
    73  			expectError: "",
    74  			helmValues: map[string]interface{}{
    75  				"service.clusterIP": "10.3.9.2",
    76  			},
    77  			expectedHelmValues: []string{
    78  				"--set", "service.clusterIP=10.3.9.2",
    79  			},
    80  		},
    81  		{
    82  			name:        "helm replaces spacial characters in ",
    83  			expectError: "",
    84  			helmValues: map[string]interface{}{
    85  				"service.clusterIP": "10.3.9.2",
    86  			},
    87  			expectedHelmValues: []string{
    88  				"--set", "service.clusterIP=10.3.9.2",
    89  			},
    90  			channelName:         "1-2-3---------frobnitz",
    91  			expectedChannelName: "1-2-3---------frobnitz",
    92  		},
    93  		{
    94  			name:        "helm templates values from context",
    95  			expectError: "",
    96  			helmValues: map[string]interface{}{
    97  				"service.clusterIP": "{{repl ConfigOption \"cluster_ip\"}}",
    98  			},
    99  			templateContext: map[string]interface{}{
   100  				"cluster_ip": "10.3.9.2",
   101  			},
   102  			expectedHelmValues: []string{
   103  				"--set", "service.clusterIP=10.3.9.2",
   104  			},
   105  			channelName:         "1-2-3---------frobnitz",
   106  			expectedChannelName: "1-2-3---------frobnitz",
   107  		},
   108  		{
   109  			name:        "helm values from asset value with incubator requirement",
   110  			describe:    "calls helm repo add",
   111  			expectError: "",
   112  			helmValues: map[string]interface{}{
   113  				"service.clusterIP": "10.3.9.2",
   114  			},
   115  			expectedHelmValues: []string{
   116  				"--set", "service.clusterIP=10.3.9.2",
   117  			},
   118  			requirements: &chartutil.Requirements{
   119  				Dependencies: []*chartutil.Dependency{
   120  					{
   121  						Repository: "https://kubernetes-charts-incubator.storage.googleapis.com/",
   122  					},
   123  				},
   124  			},
   125  			repoAdd: []string{"kubernetes-charts-incubator", "https://kubernetes-charts-incubator.storage.googleapis.com/"},
   126  		},
   127  		{
   128  			name:        "helm template with namespace in state",
   129  			describe:    "template uses namespace from state",
   130  			expectError: "",
   131  			state: &state2.State{
   132  				V1: &state2.V1{
   133  					Namespace: "test-namespace",
   134  				},
   135  			},
   136  			namespace: "test-namespace",
   137  		},
   138  	}
   139  	for _, test := range tests {
   140  		t.Run(test.name, func(t *testing.T) {
   141  			req := require.New(t)
   142  
   143  			mc := gomock.NewController(t)
   144  			testLogger := &logger.TestLogger{T: t}
   145  			mockState := state.NewMockManager(mc)
   146  			mockCommands := helm.NewMockCommands(mc)
   147  			memMapFs := afero.MemMapFs{}
   148  			mockFs := afero.Afero{Fs: &memMapFs}
   149  			tpl := &LocalTemplater{
   150  				Commands:       mockCommands,
   151  				Logger:         testLogger,
   152  				FS:             mockFs,
   153  				BuilderBuilder: templates.NewBuilderBuilder(testLogger, viper.New(), &state.MockManager{}),
   154  				Viper:          viper.New(),
   155  				StateManager:   mockState,
   156  				process:        process.Process{Logger: testLogger},
   157  			}
   158  
   159  			channelName := "frobnitz"
   160  			expectedChannelName := "frobnitz"
   161  			if test.channelName != "" {
   162  				channelName = test.channelName
   163  			}
   164  			if test.expectedChannelName != "" {
   165  				expectedChannelName = test.expectedChannelName
   166  			}
   167  
   168  			if test.templateContext == nil {
   169  				test.templateContext = map[string]interface{}{}
   170  			}
   171  
   172  			if test.state == nil {
   173  				mockState.EXPECT().CachedState().Return(state2.State{
   174  					V1: &state2.V1{
   175  						HelmValues:  "we fake",
   176  						ReleaseName: channelName,
   177  					},
   178  				}, nil)
   179  			} else {
   180  				testState := *test.state
   181  				testState.V1.ReleaseName = channelName
   182  				mockState.EXPECT().CachedState().Return(testState, nil)
   183  			}
   184  
   185  			chartRoot := "/tmp/chartroot"
   186  			optionAndValuesArgs := append(
   187  				test.helmOpts,
   188  				test.expectedHelmValues...,
   189  			)
   190  
   191  			if test.requirements != nil {
   192  				requirementsB, err := yaml.Marshal(test.requirements)
   193  				req.NoError(err)
   194  				err = mockFs.WriteFile(path.Join(chartRoot, "requirements.yaml"), requirementsB, 0755)
   195  				req.NoError(err)
   196  			}
   197  
   198  			templateArgs := append(
   199  				[]string{
   200  					"--output-dir", ".ship/tmp/chartrendered",
   201  					"--name", expectedChannelName,
   202  				},
   203  				optionAndValuesArgs...,
   204  			)
   205  
   206  			if len(test.namespace) > 0 {
   207  				templateArgs = addArgIfNotPresent(templateArgs, "--namespace", test.namespace)
   208  			} else {
   209  				templateArgs = addArgIfNotPresent(templateArgs, "--namespace", "default")
   210  			}
   211  
   212  			mockCommands.EXPECT().Init().Return(nil)
   213  			if test.requirements != nil {
   214  				absTempHelmHome, err := filepath.Abs(constants.InternalTempHelmHome)
   215  				req.NoError(err)
   216  				mockCommands.EXPECT().RepoAdd(test.repoAdd[0], test.repoAdd[1], absTempHelmHome)
   217  
   218  				requirementsB, err := mockFs.ReadFile(filepath.Join(chartRoot, "requirements.yaml"))
   219  				req.NoError(err)
   220  				chartRequirements := chartutil.Requirements{}
   221  				err = yaml.Unmarshal(requirementsB, &chartRequirements)
   222  				req.NoError(err)
   223  
   224  				mockCommands.EXPECT().MaybeDependencyUpdate(chartRoot, chartRequirements).Return(nil)
   225  			} else {
   226  				mockCommands.EXPECT().MaybeDependencyUpdate(chartRoot, chartutil.Requirements{}).Return(nil)
   227  			}
   228  
   229  			if test.ontemplate != nil {
   230  				mockCommands.EXPECT().Template(chartRoot, templateArgs).DoAndReturn(test.ontemplate(req, mockFs))
   231  			} else {
   232  
   233  				mockCommands.EXPECT().Template(chartRoot, templateArgs).DoAndReturn(func(rootDir string, args []string) error {
   234  					mockFolderPathToCreate := path.Join(constants.ShipPathInternalTmp, "chartrendered", expectedChannelName, "templates")
   235  					req.NoError(mockFs.MkdirAll(mockFolderPathToCreate, 0755))
   236  					return nil
   237  				})
   238  			}
   239  
   240  			err := tpl.Template(
   241  				"/tmp/chartroot",
   242  				root.Fs{
   243  					Afero:    mockFs,
   244  					RootPath: "",
   245  				},
   246  				api.HelmAsset{
   247  					AssetShared: api.AssetShared{
   248  						Dest: "k8s/",
   249  					},
   250  					HelmOpts: test.helmOpts,
   251  					Values:   test.helmValues,
   252  				},
   253  				api.ReleaseMetadata{
   254  					Semver:      "1.0.0",
   255  					ChannelName: channelName,
   256  					ShipAppMetadata: api.ShipAppMetadata{
   257  						Name: expectedChannelName,
   258  					},
   259  				},
   260  				[]libyaml.ConfigGroup{},
   261  				test.templateContext,
   262  			)
   263  
   264  			t.Logf("checking error %v", err)
   265  			if test.expectError == "" {
   266  				req.NoError(err)
   267  			} else {
   268  				req.Error(err, "expected error "+test.expectError)
   269  				req.Equal(test.expectError, err.Error())
   270  			}
   271  
   272  		})
   273  	}
   274  }
   275  
   276  func TestTryRemoveKustomizeBasePath(t *testing.T) {
   277  	tests := []struct {
   278  		name        string
   279  		describe    string
   280  		baseDir     string
   281  		expectError bool
   282  	}{
   283  		{
   284  			name:        "base exists",
   285  			describe:    "ensure base is removed and removeAll doesn't error",
   286  			baseDir:     constants.KustomizeBasePath,
   287  			expectError: false,
   288  		},
   289  		{
   290  			name:        "base does not exist",
   291  			describe:    "missing base, ensure removeAll doesn't error",
   292  			baseDir:     "",
   293  			expectError: false,
   294  		},
   295  	}
   296  
   297  	for _, test := range tests {
   298  		t.Run(test.name, func(t *testing.T) {
   299  			req := require.New(t)
   300  
   301  			testLogger := &logger.TestLogger{T: t}
   302  
   303  			fakeFS := afero.Afero{Fs: afero.NewMemMapFs()}
   304  
   305  			// create the base directory
   306  			err := fakeFS.MkdirAll(path.Join(test.baseDir, "myCoolManifest.yaml"), 0777)
   307  			req.NoError(err)
   308  
   309  			// verify path actually exists
   310  			successfulMkdirAll, err := fakeFS.DirExists(path.Join(test.baseDir, "myCoolManifest.yaml"))
   311  			req.True(successfulMkdirAll)
   312  			req.NoError(err)
   313  
   314  			ft := &LocalTemplater{
   315  				FS:     fakeFS,
   316  				Logger: testLogger,
   317  			}
   318  
   319  			removeErr := ft.FS.RemoveAll(constants.KustomizeBasePath)
   320  
   321  			if test.expectError {
   322  				req.Error(removeErr)
   323  			} else {
   324  				if dirExists, existErr := ft.FS.DirExists(constants.KustomizeBasePath); dirExists {
   325  					req.NoError(existErr)
   326  					// if dir exists, we expect tryRemoveKustomizeBasePath to have err'd
   327  					req.Error(removeErr)
   328  				} else {
   329  					// if dir does not exist, we expect tryRemoveKustomizeBasePath to have succeeded without err'ing
   330  					req.NoError(removeErr)
   331  				}
   332  			}
   333  		})
   334  	}
   335  }
   336  
   337  func Test_addArgIfNotPresent(t *testing.T) {
   338  	type args struct {
   339  		existingArgs []string
   340  		newArg       string
   341  		newDefault   string
   342  	}
   343  	tests := []struct {
   344  		name string
   345  		args args
   346  		want []string
   347  	}{
   348  		{
   349  			name: "empty",
   350  			args: args{
   351  				existingArgs: []string{},
   352  				newArg:       "--test",
   353  				newDefault:   "newDefault",
   354  			},
   355  			want: []string{"--test", "newDefault"},
   356  		},
   357  		{
   358  			name: "not present, not empty",
   359  			args: args{
   360  				existingArgs: []string{"--notTest", "notDefault"},
   361  				newArg:       "--test",
   362  				newDefault:   "newDefault",
   363  			},
   364  			want: []string{"--notTest", "notDefault", "--test", "newDefault"},
   365  		},
   366  		{
   367  			name: "present",
   368  			args: args{
   369  				existingArgs: []string{"--test", "notDefault"},
   370  				newArg:       "--test",
   371  				newDefault:   "newDefault",
   372  			},
   373  			want: []string{"--test", "notDefault"},
   374  		},
   375  		{
   376  			name: "present with others",
   377  			args: args{
   378  				existingArgs: []string{"--notTest", "notDefault", "--test", "alsoNotDefault"},
   379  				newArg:       "--test",
   380  				newDefault:   "newDefault",
   381  			},
   382  			want: []string{"--notTest", "notDefault", "--test", "alsoNotDefault"},
   383  		},
   384  		{
   385  			name: "present as substring",
   386  			args: args{
   387  				existingArgs: []string{"--notTest", "notDefault", "abc--test", "alsoNotDefault"},
   388  				newArg:       "--test",
   389  				newDefault:   "newDefault",
   390  			},
   391  			want: []string{"--notTest", "notDefault", "abc--test", "alsoNotDefault", "--test", "newDefault"},
   392  		},
   393  	}
   394  	for _, tt := range tests {
   395  		t.Run(tt.name, func(t *testing.T) {
   396  			req := require.New(t)
   397  
   398  			got := addArgIfNotPresent(tt.args.existingArgs, tt.args.newArg, tt.args.newDefault)
   399  
   400  			req.Equal(tt.want, got)
   401  		})
   402  	}
   403  }
   404  
   405  func Test_validateGeneratedFiles(t *testing.T) {
   406  
   407  	type file struct {
   408  		contents string
   409  		path     string
   410  	}
   411  	tests := []struct {
   412  		name        string
   413  		inputFiles  []file
   414  		dir         string
   415  		outputFiles []file
   416  	}{
   417  		{
   418  			name:        "no_files",
   419  			dir:         "",
   420  			inputFiles:  []file{},
   421  			outputFiles: []file{},
   422  		},
   423  		{
   424  			name: "irrelevant_files",
   425  			dir:  "test",
   426  			inputFiles: []file{
   427  				{
   428  					path:     "outside",
   429  					contents: `irrelevant`,
   430  				},
   431  				{
   432  					path: "test/inside",
   433  					contents: `irrelevant
   434  `,
   435  				},
   436  			},
   437  			outputFiles: []file{
   438  				{
   439  					path:     "outside",
   440  					contents: `irrelevant`,
   441  				},
   442  				{
   443  					path: "test/inside",
   444  					contents: `irrelevant
   445  `,
   446  				},
   447  			},
   448  		},
   449  		{
   450  			name: "relevant_args_files",
   451  			dir:  "test",
   452  			inputFiles: []file{
   453  				{
   454  					path:     "test/something.yaml",
   455  					contents: `  args: {}`,
   456  				},
   457  				{
   458  					path:     "test/missingArgs.yaml",
   459  					contents: `  args:`,
   460  				},
   461  				{
   462  					path: "test/notMissingMultilineArgs.yaml",
   463  					contents: `
   464    args:
   465      something
   466    args:
   467    - something`,
   468  				},
   469  				{
   470  					path: "test/missingMultilineArgs.yaml",
   471  					contents: `
   472    args:
   473    something:`,
   474  				},
   475  			},
   476  			outputFiles: []file{
   477  				{
   478  					path:     "test/something.yaml",
   479  					contents: `  args: {}`,
   480  				},
   481  				{
   482  					path:     "test/missingArgs.yaml",
   483  					contents: `  args: []`,
   484  				},
   485  				{
   486  					path: "test/notMissingMultilineArgs.yaml",
   487  					contents: `
   488    args:
   489      something
   490    args:
   491    - something`,
   492  				},
   493  				{
   494  					path: "test/missingMultilineArgs.yaml",
   495  					contents: `
   496    args: []
   497    something:`,
   498  				},
   499  			},
   500  		},
   501  		{
   502  			name: "relevant_env_files",
   503  			dir:  "test",
   504  			inputFiles: []file{
   505  				{
   506  					path:     "test/something.yaml",
   507  					contents: `  env: []`,
   508  				},
   509  				{
   510  					path:     "test/missingEnv.yaml",
   511  					contents: `  env:`,
   512  				},
   513  				{
   514  					path: "test/notMissingMultilineEnv.yaml",
   515  					contents: `
   516    env:
   517      something
   518    env:
   519    - something`,
   520  				},
   521  				{
   522  					path: "test/missingMultilineEnv.yaml",
   523  					contents: `
   524    env:
   525    something:`,
   526  				},
   527  			},
   528  			outputFiles: []file{
   529  				{
   530  					path:     "test/something.yaml",
   531  					contents: `  env: []`,
   532  				},
   533  				{
   534  					path:     "test/missingEnv.yaml",
   535  					contents: `  env: []`,
   536  				},
   537  				{
   538  					path: "test/notMissingMultilineEnv.yaml",
   539  					contents: `
   540    env:
   541      something
   542    env:
   543    - something`,
   544  				},
   545  				{
   546  					path: "test/missingMultilineEnv.yaml",
   547  					contents: `
   548    env: []
   549    something:`,
   550  				},
   551  			},
   552  		},
   553  		{
   554  			name: "relevant_value_files",
   555  			dir:  "test",
   556  			inputFiles: []file{
   557  				{
   558  					path:     "test/something.yaml",
   559  					contents: `  value: {}`,
   560  				},
   561  				{
   562  					path:     "test/missingValue.yaml",
   563  					contents: `  value:`,
   564  				},
   565  			},
   566  			outputFiles: []file{
   567  				{
   568  					path:     "test/something.yaml",
   569  					contents: `  value: {}`,
   570  				},
   571  				{
   572  					path:     "test/missingValue.yaml",
   573  					contents: `  value: ""`,
   574  				},
   575  			},
   576  		},
   577  		{
   578  			name: "blank lines",
   579  			dir:  "test",
   580  			inputFiles: []file{
   581  				{
   582  					path: "test/blank_line_env.yaml",
   583  					contents: `
   584    env:
   585  
   586      item
   587  `,
   588  				},
   589  				{
   590  					path: "test/blank_line_args.yaml",
   591  					contents: `
   592    args:
   593  
   594      item
   595  `,
   596  				},
   597  			},
   598  			outputFiles: []file{
   599  				{
   600  					path: "test/blank_line_env.yaml",
   601  					contents: `
   602    env:
   603  
   604      item
   605  `,
   606  				},
   607  				{
   608  					path: "test/blank_line_args.yaml",
   609  					contents: `
   610    args:
   611  
   612      item
   613  `,
   614  				},
   615  			},
   616  		},
   617  		{
   618  			name: "comment lines",
   619  			dir:  "test",
   620  			inputFiles: []file{
   621  				{
   622  					path: "test/comment_line_env.yaml",
   623  					contents: `
   624    env:
   625      #item
   626  
   627    env:
   628    #item
   629      item2
   630  `,
   631  				},
   632  				{
   633  					path: "test/comment_line_args.yaml",
   634  					contents: `
   635    args:
   636      #item
   637  
   638    args:
   639    #item
   640      item2
   641  `,
   642  				},
   643  			},
   644  			outputFiles: []file{
   645  				{
   646  					path: "test/comment_line_env.yaml",
   647  					contents: `
   648    env: []
   649      #item
   650  
   651    env:
   652    #item
   653      item2
   654  `,
   655  				},
   656  				{
   657  					path: "test/comment_line_args.yaml",
   658  					contents: `
   659    args: []
   660      #item
   661  
   662    args:
   663    #item
   664      item2
   665  `,
   666  				},
   667  			},
   668  		},
   669  		{
   670  			name: "null values",
   671  			dir:  "test",
   672  			inputFiles: []file{
   673  				{
   674  					path: "test/null_values.yaml",
   675  					contents: `
   676    value: null
   677      #item
   678  
   679    value:
   680      null
   681  
   682    value:
   683      value: null
   684  `,
   685  				},
   686  			},
   687  			outputFiles: []file{
   688  				{
   689  					path: "test/null_values.yaml",
   690  					contents: `
   691    value: ""
   692      #item
   693  
   694    value:
   695      null
   696  
   697    value:
   698      value: ""
   699  `,
   700  				},
   701  			},
   702  		},
   703  		{
   704  			name: "templated values",
   705  			dir:  "test",
   706  			inputFiles: []file{
   707  				{
   708  					path: "test/null_values.yaml",
   709  					contents: `
   710    value:
   711  
   712  {{ template }}
   713  
   714    value:
   715    {{ template }}
   716  
   717    value:
   718      value: {{ template }}
   719  `,
   720  				},
   721  			},
   722  			outputFiles: []file{
   723  				{
   724  					path: "test/null_values.yaml",
   725  					contents: `
   726    value:
   727  
   728  {{ template }}
   729  
   730    value:
   731    {{ template }}
   732  
   733    value:
   734      value: {{ template }}
   735  `,
   736  				},
   737  			},
   738  		},
   739  		{
   740  			name: "everything",
   741  			dir:  "test",
   742  			inputFiles: []file{
   743  				{
   744  					path: "test/everything.yaml",
   745  					contents: `
   746    args:
   747    env:
   748    volumes:
   749    value:
   750    value: null
   751    initContainers:
   752  `,
   753  				},
   754  			},
   755  			outputFiles: []file{
   756  				{
   757  					path: "test/everything.yaml",
   758  					contents: `
   759    args: []
   760    env: []
   761    volumes: []
   762    value: ""
   763    value: ""
   764    initContainers: []
   765  `,
   766  				},
   767  			},
   768  		},
   769  	}
   770  	for _, tt := range tests {
   771  		t.Run(tt.name, func(t *testing.T) {
   772  			req := require.New(t)
   773  
   774  			testLogger := &logger.TestLogger{T: t}
   775  
   776  			fakeFS := afero.Afero{Fs: afero.NewMemMapFs()}
   777  			lt := &LocalTemplater{
   778  				FS:     fakeFS,
   779  				Logger: testLogger,
   780  			}
   781  
   782  			// add inputFiles to fakeFS
   783  			for _, file := range tt.inputFiles {
   784  				req.NoError(fakeFS.WriteFile(file.path, []byte(file.contents), os.FileMode(777)))
   785  			}
   786  
   787  			req.NoError(lt.validateGeneratedFiles(fakeFS, tt.dir))
   788  
   789  			// check outputFiles from fakeFS
   790  			for _, file := range tt.outputFiles {
   791  				contents, err := fakeFS.ReadFile(file.path)
   792  				req.NoError(err)
   793  				req.Equal(file.contents, string(contents), "expected %s contents to be equal", file.path)
   794  			}
   795  		})
   796  	}
   797  }
   798  
   799  func TestLocalTemplater_writeStateHelmValuesTo(t *testing.T) {
   800  	tests := []struct {
   801  		name                 string
   802  		dest                 string
   803  		defaultValuesPath    string
   804  		defaultValuesContent string
   805  	}{
   806  		{
   807  			name:              "simple",
   808  			dest:              "some/values.yaml",
   809  			defaultValuesPath: "random/values.yaml",
   810  			defaultValuesContent: `
   811  something: maybe
   812  `,
   813  		},
   814  	}
   815  	for _, tt := range tests {
   816  		req := require.New(t)
   817  		t.Run(tt.name, func(t *testing.T) {
   818  			mc := gomock.NewController(t)
   819  			mockState := state.NewMockManager(mc)
   820  			mockFs := afero.Afero{Fs: afero.NewMemMapFs()}
   821  			err := mockFs.WriteFile(tt.defaultValuesPath, []byte(tt.defaultValuesContent), 0755)
   822  			req.NoError(err)
   823  
   824  			mockState.EXPECT().CachedState().Return(state2.State{V1: &state2.V1{}}, nil)
   825  			f := &LocalTemplater{
   826  				Logger:       &logger.TestLogger{T: t},
   827  				FS:           mockFs,
   828  				StateManager: mockState,
   829  			}
   830  			err = f.writeStateHelmValuesTo(tt.dest, tt.defaultValuesPath)
   831  			req.NoError(err)
   832  
   833  			readFileB, err := mockFs.ReadFile(tt.dest)
   834  			req.NoError(err)
   835  			req.Equal(tt.defaultValuesContent, string(readFileB))
   836  		})
   837  	}
   838  }