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

     1  package github
     2  
     3  import (
     4  	"path/filepath"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/replicatedhq/libyaml"
     9  	"github.com/replicatedhq/ship/pkg/api"
    10  	"github.com/replicatedhq/ship/pkg/constants"
    11  	"github.com/replicatedhq/ship/pkg/templates"
    12  	"github.com/replicatedhq/ship/pkg/test-mocks/state"
    13  	"github.com/replicatedhq/ship/pkg/testing/logger"
    14  	"github.com/spf13/viper"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func Test_getDestPath(t *testing.T) {
    19  	type args struct {
    20  		githubPath string
    21  		asset      api.GitHubAsset
    22  	}
    23  	tests := []struct {
    24  		name    string
    25  		args    args
    26  		want    string
    27  		wantErr bool
    28  	}{
    29  		{
    30  			name: "basic file",
    31  			args: args{
    32  				githubPath: "README.md",
    33  				asset: api.GitHubAsset{
    34  					Path:      "README.md",
    35  					StripPath: "",
    36  					AssetShared: api.AssetShared{
    37  						Dest: "./",
    38  					},
    39  				},
    40  			},
    41  			want:    "README.md",
    42  			wantErr: false,
    43  		},
    44  		{
    45  			name: "file in subdir",
    46  			args: args{
    47  				githubPath: "subdir/README.md",
    48  				asset: api.GitHubAsset{
    49  					Path:      "subdir/",
    50  					StripPath: "",
    51  					AssetShared: api.AssetShared{
    52  						Dest: "./",
    53  					},
    54  				},
    55  			},
    56  			want:    "subdir/README.md",
    57  			wantErr: false,
    58  		},
    59  		{
    60  			name: "file in subdir with dest dir",
    61  			args: args{
    62  				githubPath: "subdir/README.md",
    63  				asset: api.GitHubAsset{
    64  					Path:      "subdir/",
    65  					StripPath: "",
    66  					AssetShared: api.AssetShared{
    67  						Dest: "./dest",
    68  					},
    69  				},
    70  			},
    71  			want:    "dest/subdir/README.md",
    72  			wantErr: false,
    73  		},
    74  		{
    75  			name: "file in stripped subdir with dest dir",
    76  			args: args{
    77  				githubPath: "subdir/README.md",
    78  				asset: api.GitHubAsset{
    79  					Path:      "subdir/",
    80  					StripPath: "true",
    81  					AssetShared: api.AssetShared{
    82  						Dest: "./dest",
    83  					},
    84  				},
    85  			},
    86  			want:    "dest/README.md",
    87  			wantErr: false,
    88  		},
    89  		{
    90  			name: "literal file in stripped subdir with dest dir",
    91  			args: args{
    92  				githubPath: "dir/subdir/README.md",
    93  				asset: api.GitHubAsset{
    94  					Path:      "dir/subdir/README.md",
    95  					StripPath: "true",
    96  					AssetShared: api.AssetShared{
    97  						Dest: "dest",
    98  					},
    99  				},
   100  			},
   101  			want:    "dest/README.md",
   102  			wantErr: false,
   103  		},
   104  		{
   105  			name: "file in stripped subdir that lacks a trailing slash with dest dir",
   106  			args: args{
   107  				githubPath: "dir/subdir/README.md",
   108  				asset: api.GitHubAsset{
   109  					Path:      "dir/subdir",
   110  					StripPath: "true",
   111  					AssetShared: api.AssetShared{
   112  						Dest: "dest",
   113  					},
   114  				},
   115  			},
   116  			want:    "dest/README.md",
   117  			wantErr: false,
   118  		},
   119  		{
   120  			name: "templated dest dir",
   121  			args: args{
   122  				githubPath: "dir/subdir/README.md",
   123  				asset: api.GitHubAsset{
   124  					Path:      "dir/subdir",
   125  					StripPath: "false",
   126  					AssetShared: api.AssetShared{
   127  						Dest: "dest{{repl Add 1 1}}",
   128  					},
   129  				},
   130  			},
   131  			want:    "dest2/dir/subdir/README.md",
   132  			wantErr: false,
   133  		},
   134  		{
   135  			name: "templated stripPath (eval to true)",
   136  			args: args{
   137  				githubPath: "dir/subdir/README.md",
   138  				asset: api.GitHubAsset{
   139  					Path:      "dir/subdir",
   140  					StripPath: `{{repl ParseBool "true"}}`,
   141  					AssetShared: api.AssetShared{
   142  						Dest: "dest",
   143  					},
   144  				},
   145  			},
   146  			want:    "dest/README.md",
   147  			wantErr: false,
   148  		},
   149  		{
   150  			name: "templated stripPath (eval to false)",
   151  			args: args{
   152  				githubPath: "dir/subdir/README.md",
   153  				asset: api.GitHubAsset{
   154  					Path:      "dir/subdir",
   155  					StripPath: `{{repl ParseBool "false"}}`,
   156  					AssetShared: api.AssetShared{
   157  						Dest: "dest",
   158  					},
   159  				},
   160  			},
   161  			want:    "dest/dir/subdir/README.md",
   162  			wantErr: false,
   163  		},
   164  		{
   165  			name: "strip path of root dir file",
   166  			args: args{
   167  				githubPath: "README.md",
   168  				asset: api.GitHubAsset{
   169  					Path:      "",
   170  					StripPath: "true",
   171  					AssetShared: api.AssetShared{
   172  						Dest: "dest",
   173  					},
   174  				},
   175  			},
   176  			want:    "dest/README.md",
   177  			wantErr: false,
   178  		},
   179  		{
   180  			name: "not a valid template function (dest)",
   181  			args: args{
   182  				githubPath: "README.md",
   183  				asset: api.GitHubAsset{
   184  					Path:      "",
   185  					StripPath: "true",
   186  					AssetShared: api.AssetShared{
   187  						Dest: "{{repl NotATemplateFunction }}",
   188  					},
   189  				},
   190  			},
   191  			want:    "",
   192  			wantErr: true,
   193  		},
   194  		{
   195  			name: "not a valid template function (stripPath)",
   196  			args: args{
   197  				githubPath: "README.md",
   198  				asset: api.GitHubAsset{
   199  					Path:      "",
   200  					StripPath: "{{repl NotATemplateFunction }}",
   201  					AssetShared: api.AssetShared{
   202  						Dest: "dest",
   203  					},
   204  				},
   205  			},
   206  			want:    "",
   207  			wantErr: true,
   208  		},
   209  		{
   210  			name: "file in root",
   211  			args: args{
   212  				githubPath: "subdir/README.md",
   213  				asset: api.GitHubAsset{
   214  					Path:      "",
   215  					StripPath: "",
   216  					AssetShared: api.AssetShared{
   217  						Dest: "/bin/runc",
   218  					},
   219  				},
   220  			},
   221  			want:    "",
   222  			wantErr: true,
   223  		},
   224  		{
   225  			name: "file in parent dir",
   226  			args: args{
   227  				githubPath: "subdir/README.md",
   228  				asset: api.GitHubAsset{
   229  					Path:      "abc/",
   230  					StripPath: "",
   231  					AssetShared: api.AssetShared{
   232  						Dest: "../../../bin/runc",
   233  					},
   234  				},
   235  			},
   236  			want:    "",
   237  			wantErr: true,
   238  		},
   239  	}
   240  	for _, tt := range tests {
   241  		t.Run(tt.name, func(t *testing.T) {
   242  			req := require.New(t)
   243  
   244  			testLogger := &logger.TestLogger{T: t}
   245  			v := viper.New()
   246  			bb := templates.NewBuilderBuilder(testLogger, v, &state.MockManager{})
   247  			builder, err := bb.FullBuilder(api.ReleaseMetadata{}, []libyaml.ConfigGroup{}, map[string]interface{}{})
   248  			req.NoError(err)
   249  
   250  			got, err := getDestPath(tt.args.githubPath, tt.args.asset, builder)
   251  			if !tt.wantErr {
   252  				req.NoErrorf(err, "getDestPath(%s, %+v, builder) error = %v", tt.args.githubPath, tt.args.asset, err)
   253  			} else {
   254  				req.Error(err)
   255  			}
   256  
   257  			// convert the returned file to forwardslash format before testing - otherwise this test fails when the separator isn't '/'
   258  			req.Equal(tt.want, filepath.ToSlash(got))
   259  		})
   260  	}
   261  }
   262  
   263  func Test_getDestPathNoProxy(t *testing.T) {
   264  	tests := []struct {
   265  		name       string
   266  		asset      api.GitHubAsset
   267  		want       string
   268  		wantErr    bool
   269  		renderRoot string
   270  	}{
   271  		{
   272  			name: "basic file",
   273  			asset: api.GitHubAsset{
   274  				Path:      "README.md",
   275  				StripPath: "",
   276  				AssetShared: api.AssetShared{
   277  					Dest: "./",
   278  				},
   279  			},
   280  			want:    "installer/README.md",
   281  			wantErr: false,
   282  		},
   283  		{
   284  			name:       "basic file, no installer prefix",
   285  			renderRoot: "./",
   286  			asset: api.GitHubAsset{
   287  				Path:      "README.md",
   288  				StripPath: "",
   289  				AssetShared: api.AssetShared{
   290  					Dest: "./",
   291  				},
   292  			},
   293  			want:    "README.md",
   294  			wantErr: false,
   295  		},
   296  		{
   297  			name:       "basic file, customer installer prefix",
   298  			renderRoot: "my-installer",
   299  			asset: api.GitHubAsset{
   300  				Path:      "README.md",
   301  				StripPath: "",
   302  				AssetShared: api.AssetShared{
   303  					Dest: "./",
   304  				},
   305  			},
   306  			want:    "my-installer/README.md",
   307  			wantErr: false,
   308  		},
   309  		{
   310  			name: "file in subdir",
   311  			asset: api.GitHubAsset{
   312  				Path:      "subdir/README.md",
   313  				StripPath: "",
   314  				AssetShared: api.AssetShared{
   315  					Dest: "./",
   316  				},
   317  			},
   318  			want:    "installer/subdir/README.md",
   319  			wantErr: false,
   320  		},
   321  		{
   322  			name: "file in subdir with dest dir",
   323  			asset: api.GitHubAsset{
   324  				Path:      "subdir/README.md",
   325  				StripPath: "",
   326  				AssetShared: api.AssetShared{
   327  					Dest: "./dest",
   328  				},
   329  			},
   330  			want:    "installer/dest/subdir/README.md",
   331  			wantErr: false,
   332  		},
   333  		{
   334  			name: "file in stripped subdir with dest dir",
   335  			asset: api.GitHubAsset{
   336  				Path:      "subdir/README.md",
   337  				StripPath: "true",
   338  				AssetShared: api.AssetShared{
   339  					Dest: "./dest",
   340  				},
   341  			},
   342  			want:    "installer/dest/README.md",
   343  			wantErr: false,
   344  		},
   345  		{
   346  			name: "literal file in stripped subdir with dest dir",
   347  			asset: api.GitHubAsset{
   348  				Path:      "dir/subdir/README.md",
   349  				StripPath: "true",
   350  				AssetShared: api.AssetShared{
   351  					Dest: "dest",
   352  				},
   353  			},
   354  			want:    "installer/dest/README.md",
   355  			wantErr: false,
   356  		},
   357  		{
   358  			name: "templated dest dir",
   359  			asset: api.GitHubAsset{
   360  				Path:      "dir/subdir/README.md",
   361  				StripPath: "false",
   362  				AssetShared: api.AssetShared{
   363  					Dest: "dest{{repl Add 1 1}}",
   364  				},
   365  			},
   366  			want:    "installer/dest2/dir/subdir/README.md",
   367  			wantErr: false,
   368  		},
   369  		{
   370  			name: "templated stripPath (eval to true)",
   371  			asset: api.GitHubAsset{
   372  				Path:      "dir/subdir/README.md",
   373  				StripPath: `{{repl ParseBool "true"}}`,
   374  				AssetShared: api.AssetShared{
   375  					Dest: "dest",
   376  				},
   377  			},
   378  			want:    "installer/dest/README.md",
   379  			wantErr: false,
   380  		},
   381  		{
   382  			name: "templated stripPath (eval to false)",
   383  			asset: api.GitHubAsset{
   384  				Path:      "dir/subdir/README.md",
   385  				StripPath: `{{repl ParseBool "false"}}`,
   386  				AssetShared: api.AssetShared{
   387  					Dest: "dest",
   388  				},
   389  			},
   390  			want:    "installer/dest/dir/subdir/README.md",
   391  			wantErr: false,
   392  		},
   393  		{
   394  			name: "strip path of root dir file",
   395  			asset: api.GitHubAsset{
   396  				Path:      "README.md",
   397  				StripPath: "true",
   398  				AssetShared: api.AssetShared{
   399  					Dest: "dest",
   400  				},
   401  			},
   402  			want:    "installer/dest/README.md",
   403  			wantErr: false,
   404  		},
   405  		{
   406  			name: "not a valid template function (dest)",
   407  			asset: api.GitHubAsset{
   408  				Path:      "README.md",
   409  				StripPath: "true",
   410  				AssetShared: api.AssetShared{
   411  					Dest: "{{repl NotATemplateFunction }}",
   412  				},
   413  			},
   414  			want:    "",
   415  			wantErr: true,
   416  		},
   417  		{
   418  			name: "not a valid template function (stripPath)",
   419  			asset: api.GitHubAsset{
   420  				Path:      "README.md",
   421  				StripPath: "{{repl NotATemplateFunction }}",
   422  				AssetShared: api.AssetShared{
   423  					Dest: "dest",
   424  				},
   425  			},
   426  			want:    "",
   427  			wantErr: true,
   428  		},
   429  	}
   430  	for _, tt := range tests {
   431  		t.Run(tt.name, func(t *testing.T) {
   432  			req := require.New(t)
   433  
   434  			testLogger := &logger.TestLogger{T: t}
   435  			v := viper.New()
   436  			bb := templates.NewBuilderBuilder(testLogger, v, &state.MockManager{})
   437  			builder, err := bb.FullBuilder(api.ReleaseMetadata{}, []libyaml.ConfigGroup{}, map[string]interface{}{})
   438  			req.NoError(err)
   439  
   440  			if tt.renderRoot == "" {
   441  				tt.renderRoot = constants.InstallerPrefixPath
   442  			}
   443  			got, err := (&LocalRenderer{Logger: testLogger}).getDestPathNoProxy(tt.asset, builder, tt.renderRoot)
   444  			if !tt.wantErr {
   445  				req.NoError(err)
   446  			} else {
   447  				req.Error(err)
   448  			}
   449  
   450  			// convert the returned file to forwardslash format before testing - otherwise this test fails when the separator isn't '/'
   451  			req.Equal(tt.want, filepath.ToSlash(got))
   452  		})
   453  	}
   454  }
   455  
   456  func Test_filterGithubContents(t *testing.T) {
   457  	type args struct {
   458  		githubContents []api.GithubContent
   459  		asset          api.GitHubAsset
   460  	}
   461  	tests := []struct {
   462  		name string
   463  		args args
   464  		want []api.GithubFile
   465  	}{
   466  		{
   467  			name: "has slash prefix and suffix",
   468  			args: args{
   469  				githubContents: []api.GithubContent{{
   470  					Path:  "subdir",
   471  					Files: []api.GithubFile{{Name: "1"}},
   472  				}},
   473  				asset: api.GitHubAsset{
   474  					Path: "/subdir/",
   475  				},
   476  			},
   477  			want: []api.GithubFile{{Name: "1"}},
   478  		},
   479  		{
   480  			name: "is root",
   481  			args: args{
   482  				githubContents: []api.GithubContent{{
   483  					Path:  "/",
   484  					Files: []api.GithubFile{{Name: "1"}},
   485  				}},
   486  				asset: api.GitHubAsset{
   487  					Path: "/",
   488  				},
   489  			},
   490  			want: []api.GithubFile{{Name: "1"}},
   491  		},
   492  	}
   493  	for _, tt := range tests {
   494  		t.Run(tt.name, func(t *testing.T) {
   495  			if got := filterGithubContents(tt.args.githubContents, tt.args.asset); !reflect.DeepEqual(got, tt.want) {
   496  				t.Errorf("filterGithubContent() = %v, want %v", got, tt.want)
   497  			}
   498  		})
   499  	}
   500  }
   501  
   502  func Test_createUpstreamURL(t *testing.T) {
   503  	tests := []struct {
   504  		name         string
   505  		asset        api.GitHubAsset
   506  		configGroups []libyaml.ConfigGroup
   507  		want         string
   508  	}{
   509  		{
   510  			name: "no templated values",
   511  			asset: api.GitHubAsset{
   512  				Repo: "org/repo",
   513  				Ref:  "some-ref",
   514  				Path: "a-path",
   515  			},
   516  			configGroups: []libyaml.ConfigGroup{},
   517  			want:         "github.com/org/repo/tree/some-ref/a-path",
   518  		},
   519  		{
   520  			name: "templated repo, ref, and path",
   521  			asset: api.GitHubAsset{
   522  				Repo: "{{repl ConfigOption \"drink\"}}",
   523  				Ref:  "{{repl ConfigOption \"fruity\"}}",
   524  				Path: "{{repl ConfigOption \"cinammon\"}}",
   525  			},
   526  			configGroups: []libyaml.ConfigGroup{
   527  				{
   528  					Name: "cereal",
   529  					Items: []*libyaml.ConfigItem{
   530  						{
   531  							Name:  "drink",
   532  							Value: "org/repo",
   533  						},
   534  						{
   535  							Name:  "fruity",
   536  							Value: "pomegranate",
   537  						},
   538  						{
   539  							Name:  "cinammon",
   540  							Value: "horchata",
   541  						},
   542  					},
   543  				},
   544  			},
   545  			want: "github.com/org/repo/tree/pomegranate/horchata",
   546  		},
   547  		{
   548  			name: "templated path to file",
   549  			asset: api.GitHubAsset{
   550  				Repo: "org/repo",
   551  				Ref:  "{{repl ConfigOption \"fruity\"}}",
   552  				Path: "{{repl ConfigOption \"hotdog\"}}",
   553  			},
   554  			configGroups: []libyaml.ConfigGroup{
   555  				{
   556  					Name: "cereal",
   557  					Items: []*libyaml.ConfigItem{
   558  						{
   559  							Name:  "drink",
   560  							Value: "org/repo",
   561  						},
   562  						{
   563  							Name:  "fruity",
   564  							Value: "pomegranate",
   565  						},
   566  						{
   567  							Name:  "hotdog",
   568  							Value: "/some/another/file.yml",
   569  						},
   570  					},
   571  				},
   572  			},
   573  			want: "github.com/org/repo/blob/pomegranate/some/another/file.yml",
   574  		},
   575  		{
   576  			name: "templated values with empty ref defaults to master",
   577  			asset: api.GitHubAsset{
   578  				Repo: "{{repl ConfigOption \"drink\"}}",
   579  				Ref:  "{{repl ConfigOption \"fruity\"}}",
   580  				Path: "{{repl ConfigOption \"hotdog\"}}",
   581  			},
   582  			configGroups: []libyaml.ConfigGroup{
   583  				{
   584  					Name: "cereal",
   585  					Items: []*libyaml.ConfigItem{
   586  						{
   587  							Name:  "drink",
   588  							Value: "another/some-other",
   589  						},
   590  						{
   591  							Name:  "fruity",
   592  							Value: "",
   593  						},
   594  						{
   595  							Name:  "hotdog",
   596  							Value: "/going/places/ship.yml",
   597  						},
   598  					},
   599  				},
   600  			},
   601  			want: "github.com/another/some-other/blob/master/going/places/ship.yml",
   602  		},
   603  	}
   604  	for _, tt := range tests {
   605  		req := require.New(t)
   606  		t.Run(tt.name, func(t *testing.T) {
   607  			v := viper.New()
   608  			testLogger := &logger.TestLogger{T: t}
   609  			bb := templates.NewBuilderBuilder(testLogger, v, &state.MockManager{})
   610  			builder, err := bb.FullBuilder(api.ReleaseMetadata{}, tt.configGroups, map[string]interface{}{})
   611  			req.NoError(err)
   612  
   613  			got, err := createUpstreamURL(tt.asset, builder)
   614  			req.NoError(err)
   615  			req.Equal(tt.want, got)
   616  		})
   617  	}
   618  }