github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/composer/list_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package composer contains functions for composing components within Jackal packages.
     5  package composer
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"testing"
    12  
    13  	"github.com/Racer159/jackal/src/types"
    14  	"github.com/Racer159/jackal/src/types/extensions"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestNewImportChain(t *testing.T) {
    19  	t.Parallel()
    20  
    21  	type testCase struct {
    22  		name                 string
    23  		head                 types.JackalComponent
    24  		arch                 string
    25  		flavor               string
    26  		expectedErrorMessage string
    27  	}
    28  
    29  	testCases := []testCase{
    30  		{
    31  			name:                 "No Architecture",
    32  			head:                 types.JackalComponent{},
    33  			expectedErrorMessage: "architecture must be provided",
    34  		},
    35  		{
    36  			name: "Circular Import",
    37  			head: types.JackalComponent{
    38  				Import: types.JackalComponentImport{
    39  					Path: ".",
    40  				},
    41  			},
    42  			arch:                 "amd64",
    43  			expectedErrorMessage: "detected circular import chain",
    44  		},
    45  	}
    46  	testPackageName := "test-package"
    47  	for _, testCase := range testCases {
    48  		testCase := testCase
    49  
    50  		t.Run(testCase.name, func(t *testing.T) {
    51  			t.Parallel()
    52  
    53  			_, err := NewImportChain(testCase.head, 0, testPackageName, testCase.arch, testCase.flavor)
    54  			require.Contains(t, err.Error(), testCase.expectedErrorMessage)
    55  		})
    56  	}
    57  }
    58  
    59  func TestCompose(t *testing.T) {
    60  	t.Parallel()
    61  
    62  	type testCase struct {
    63  		name                 string
    64  		ic                   *ImportChain
    65  		returnError          bool
    66  		expectedComposed     types.JackalComponent
    67  		expectedErrorMessage string
    68  	}
    69  
    70  	firstDirectory := "hello"
    71  	secondDirectory := "world"
    72  	finalDirectory := filepath.Join(firstDirectory, secondDirectory)
    73  
    74  	finalDirectoryActionDefault := filepath.Join(firstDirectory, secondDirectory, "today-dc")
    75  	secondDirectoryActionDefault := filepath.Join(firstDirectory, "world-dc")
    76  	firstDirectoryActionDefault := "hello-dc"
    77  
    78  	testCases := []testCase{
    79  		{
    80  			name: "Single Component",
    81  			ic: createChainFromSlice([]types.JackalComponent{
    82  				{
    83  					Name: "no-import",
    84  				},
    85  			}),
    86  			returnError: false,
    87  			expectedComposed: types.JackalComponent{
    88  				Name: "no-import",
    89  			},
    90  		},
    91  		{
    92  			name: "Multiple Components",
    93  			ic: createChainFromSlice([]types.JackalComponent{
    94  				createDummyComponent("hello", firstDirectory, "hello"),
    95  				createDummyComponent("world", secondDirectory, "world"),
    96  				createDummyComponent("today", "", "hello"),
    97  			}),
    98  			returnError: false,
    99  			expectedComposed: types.JackalComponent{
   100  				Name: "import-hello",
   101  				// Files should always be appended with corrected directories
   102  				Files: []types.JackalFile{
   103  					{Source: fmt.Sprintf("%s%stoday.txt", finalDirectory, string(os.PathSeparator))},
   104  					{Source: fmt.Sprintf("%s%sworld.txt", firstDirectory, string(os.PathSeparator))},
   105  					{Source: "hello.txt"},
   106  				},
   107  				// Charts should be merged if names match and appended if not with corrected directories
   108  				Charts: []types.JackalChart{
   109  					{
   110  						Name:      "hello",
   111  						LocalPath: fmt.Sprintf("%s%schart", finalDirectory, string(os.PathSeparator)),
   112  						ValuesFiles: []string{
   113  							fmt.Sprintf("%s%svalues.yaml", finalDirectory, string(os.PathSeparator)),
   114  							"values.yaml",
   115  						},
   116  					},
   117  					{
   118  						Name:      "world",
   119  						LocalPath: fmt.Sprintf("%s%schart", firstDirectory, string(os.PathSeparator)),
   120  						ValuesFiles: []string{
   121  							fmt.Sprintf("%s%svalues.yaml", firstDirectory, string(os.PathSeparator)),
   122  						},
   123  					},
   124  				},
   125  				// Manifests should be merged if names match and appended if not with corrected directories
   126  				Manifests: []types.JackalManifest{
   127  					{
   128  						Name: "hello",
   129  						Files: []string{
   130  							fmt.Sprintf("%s%smanifest.yaml", finalDirectory, string(os.PathSeparator)),
   131  							"manifest.yaml",
   132  						},
   133  					},
   134  					{
   135  						Name: "world",
   136  						Files: []string{
   137  							fmt.Sprintf("%s%smanifest.yaml", firstDirectory, string(os.PathSeparator)),
   138  						},
   139  					},
   140  				},
   141  				// DataInjections should always be appended with corrected directories
   142  				DataInjections: []types.JackalDataInjection{
   143  					{Source: fmt.Sprintf("%s%stoday", finalDirectory, string(os.PathSeparator))},
   144  					{Source: fmt.Sprintf("%s%sworld", firstDirectory, string(os.PathSeparator))},
   145  					{Source: "hello"},
   146  				},
   147  				Actions: types.JackalComponentActions{
   148  					// OnCreate actions should be appended with corrected directories that properly handle default directories
   149  					OnCreate: types.JackalComponentActionSet{
   150  						Defaults: types.JackalComponentActionDefaults{
   151  							Dir: "hello-dc",
   152  						},
   153  						Before: []types.JackalComponentAction{
   154  							{Cmd: "today-bc", Dir: &finalDirectoryActionDefault},
   155  							{Cmd: "world-bc", Dir: &secondDirectoryActionDefault},
   156  							{Cmd: "hello-bc", Dir: &firstDirectoryActionDefault},
   157  						},
   158  						After: []types.JackalComponentAction{
   159  							{Cmd: "today-ac", Dir: &finalDirectoryActionDefault},
   160  							{Cmd: "world-ac", Dir: &secondDirectoryActionDefault},
   161  							{Cmd: "hello-ac", Dir: &firstDirectoryActionDefault},
   162  						},
   163  						OnSuccess: []types.JackalComponentAction{
   164  							{Cmd: "today-sc", Dir: &finalDirectoryActionDefault},
   165  							{Cmd: "world-sc", Dir: &secondDirectoryActionDefault},
   166  							{Cmd: "hello-sc", Dir: &firstDirectoryActionDefault},
   167  						},
   168  						OnFailure: []types.JackalComponentAction{
   169  							{Cmd: "today-fc", Dir: &finalDirectoryActionDefault},
   170  							{Cmd: "world-fc", Dir: &secondDirectoryActionDefault},
   171  							{Cmd: "hello-fc", Dir: &firstDirectoryActionDefault},
   172  						},
   173  					},
   174  					// OnDeploy actions should be appended without corrected directories
   175  					OnDeploy: types.JackalComponentActionSet{
   176  						Defaults: types.JackalComponentActionDefaults{
   177  							Dir: "hello-dd",
   178  						},
   179  						Before: []types.JackalComponentAction{
   180  							{Cmd: "today-bd"},
   181  							{Cmd: "world-bd"},
   182  							{Cmd: "hello-bd"},
   183  						},
   184  						After: []types.JackalComponentAction{
   185  							{Cmd: "today-ad"},
   186  							{Cmd: "world-ad"},
   187  							{Cmd: "hello-ad"},
   188  						},
   189  						OnSuccess: []types.JackalComponentAction{
   190  							{Cmd: "today-sd"},
   191  							{Cmd: "world-sd"},
   192  							{Cmd: "hello-sd"},
   193  						},
   194  						OnFailure: []types.JackalComponentAction{
   195  							{Cmd: "today-fd"},
   196  							{Cmd: "world-fd"},
   197  							{Cmd: "hello-fd"},
   198  						},
   199  					},
   200  					// OnRemove actions should be appended without corrected directories
   201  					OnRemove: types.JackalComponentActionSet{
   202  						Defaults: types.JackalComponentActionDefaults{
   203  							Dir: "hello-dr",
   204  						},
   205  						Before: []types.JackalComponentAction{
   206  							{Cmd: "today-br"},
   207  							{Cmd: "world-br"},
   208  							{Cmd: "hello-br"},
   209  						},
   210  						After: []types.JackalComponentAction{
   211  							{Cmd: "today-ar"},
   212  							{Cmd: "world-ar"},
   213  							{Cmd: "hello-ar"},
   214  						},
   215  						OnSuccess: []types.JackalComponentAction{
   216  							{Cmd: "today-sr"},
   217  							{Cmd: "world-sr"},
   218  							{Cmd: "hello-sr"},
   219  						},
   220  						OnFailure: []types.JackalComponentAction{
   221  							{Cmd: "today-fr"},
   222  							{Cmd: "world-fr"},
   223  							{Cmd: "hello-fr"},
   224  						},
   225  					},
   226  				},
   227  				// Extensions should be appended with corrected directories
   228  				Extensions: extensions.JackalComponentExtensions{
   229  					BigBang: &extensions.BigBang{
   230  						ValuesFiles: []string{
   231  							fmt.Sprintf("%s%svalues.yaml", finalDirectory, string(os.PathSeparator)),
   232  							fmt.Sprintf("%s%svalues.yaml", firstDirectory, string(os.PathSeparator)),
   233  							"values.yaml",
   234  						},
   235  						FluxPatchFiles: []string{
   236  							fmt.Sprintf("%s%spatch.yaml", finalDirectory, string(os.PathSeparator)),
   237  							fmt.Sprintf("%s%spatch.yaml", firstDirectory, string(os.PathSeparator)),
   238  							"patch.yaml",
   239  						},
   240  					},
   241  				},
   242  			},
   243  		},
   244  	}
   245  
   246  	for _, testCase := range testCases {
   247  		testCase := testCase
   248  
   249  		t.Run(testCase.name, func(t *testing.T) {
   250  			t.Parallel()
   251  
   252  			composed, err := testCase.ic.Compose()
   253  			if testCase.returnError {
   254  				require.Contains(t, err.Error(), testCase.expectedErrorMessage)
   255  			} else {
   256  				require.EqualValues(t, &testCase.expectedComposed, composed)
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func TestMerging(t *testing.T) {
   263  	t.Parallel()
   264  
   265  	type testCase struct {
   266  		name           string
   267  		ic             *ImportChain
   268  		existingVars   []types.JackalPackageVariable
   269  		existingConsts []types.JackalPackageConstant
   270  		expectedVars   []types.JackalPackageVariable
   271  		expectedConsts []types.JackalPackageConstant
   272  	}
   273  
   274  	head := Node{
   275  		vars: []types.JackalPackageVariable{
   276  			{
   277  				Name:    "TEST",
   278  				Default: "head",
   279  			},
   280  			{
   281  				Name: "HEAD",
   282  			},
   283  		},
   284  		consts: []types.JackalPackageConstant{
   285  			{
   286  				Name:  "TEST",
   287  				Value: "head",
   288  			},
   289  			{
   290  				Name: "HEAD",
   291  			},
   292  		},
   293  	}
   294  	tail := Node{
   295  		vars: []types.JackalPackageVariable{
   296  			{
   297  				Name:    "TEST",
   298  				Default: "tail",
   299  			},
   300  			{
   301  				Name: "TAIL",
   302  			},
   303  		},
   304  		consts: []types.JackalPackageConstant{
   305  			{
   306  				Name:  "TEST",
   307  				Value: "tail",
   308  			},
   309  			{
   310  				Name: "TAIL",
   311  			},
   312  		},
   313  	}
   314  	head.next = &tail
   315  	tail.prev = &head
   316  	testIC := &ImportChain{head: &head, tail: &tail}
   317  
   318  	testCases := []testCase{
   319  		{
   320  			name: "empty-ic",
   321  			ic:   &ImportChain{},
   322  			existingVars: []types.JackalPackageVariable{
   323  				{
   324  					Name: "TEST",
   325  				},
   326  			},
   327  			existingConsts: []types.JackalPackageConstant{
   328  				{
   329  					Name: "TEST",
   330  				},
   331  			},
   332  			expectedVars: []types.JackalPackageVariable{
   333  				{
   334  					Name: "TEST",
   335  				},
   336  			},
   337  			expectedConsts: []types.JackalPackageConstant{
   338  				{
   339  					Name: "TEST",
   340  				},
   341  			},
   342  		},
   343  		{
   344  			name:           "no-existing",
   345  			ic:             testIC,
   346  			existingVars:   []types.JackalPackageVariable{},
   347  			existingConsts: []types.JackalPackageConstant{},
   348  			expectedVars: []types.JackalPackageVariable{
   349  				{
   350  					Name:    "TEST",
   351  					Default: "head",
   352  				},
   353  				{
   354  					Name: "HEAD",
   355  				},
   356  				{
   357  					Name: "TAIL",
   358  				},
   359  			},
   360  			expectedConsts: []types.JackalPackageConstant{
   361  				{
   362  					Name:  "TEST",
   363  					Value: "head",
   364  				},
   365  				{
   366  					Name: "HEAD",
   367  				},
   368  				{
   369  					Name: "TAIL",
   370  				},
   371  			},
   372  		},
   373  		{
   374  			name: "with-existing",
   375  			ic:   testIC,
   376  			existingVars: []types.JackalPackageVariable{
   377  				{
   378  					Name:    "TEST",
   379  					Default: "existing",
   380  				},
   381  				{
   382  					Name: "EXISTING",
   383  				},
   384  			},
   385  			existingConsts: []types.JackalPackageConstant{
   386  				{
   387  					Name:  "TEST",
   388  					Value: "existing",
   389  				},
   390  				{
   391  					Name: "EXISTING",
   392  				},
   393  			},
   394  			expectedVars: []types.JackalPackageVariable{
   395  				{
   396  					Name:    "TEST",
   397  					Default: "existing",
   398  				},
   399  				{
   400  					Name: "EXISTING",
   401  				},
   402  				{
   403  					Name: "HEAD",
   404  				},
   405  				{
   406  					Name: "TAIL",
   407  				},
   408  			},
   409  			expectedConsts: []types.JackalPackageConstant{
   410  				{
   411  					Name:  "TEST",
   412  					Value: "existing",
   413  				},
   414  				{
   415  					Name: "EXISTING",
   416  				},
   417  				{
   418  					Name: "HEAD",
   419  				},
   420  				{
   421  					Name: "TAIL",
   422  				},
   423  			},
   424  		},
   425  	}
   426  
   427  	for _, testCase := range testCases {
   428  		testCase := testCase
   429  
   430  		t.Run(testCase.name, func(t *testing.T) {
   431  			t.Parallel()
   432  
   433  			mergedVars := testCase.ic.MergeVariables(testCase.existingVars)
   434  			require.EqualValues(t, testCase.expectedVars, mergedVars)
   435  
   436  			mergedConsts := testCase.ic.MergeConstants(testCase.existingConsts)
   437  			require.EqualValues(t, testCase.expectedConsts, mergedConsts)
   438  		})
   439  	}
   440  }
   441  
   442  func createChainFromSlice(components []types.JackalComponent) (ic *ImportChain) {
   443  	ic = &ImportChain{}
   444  	testPackageName := "test-package"
   445  
   446  	if len(components) == 0 {
   447  		return ic
   448  	}
   449  
   450  	ic.append(components[0], 0, testPackageName, ".", nil, nil)
   451  	history := []string{}
   452  
   453  	for idx := 1; idx < len(components); idx++ {
   454  		history = append(history, components[idx-1].Import.Path)
   455  		ic.append(components[idx], idx, testPackageName, filepath.Join(history...), nil, nil)
   456  	}
   457  
   458  	return ic
   459  }
   460  
   461  func createDummyComponent(name, importDir, subName string) types.JackalComponent {
   462  	return types.JackalComponent{
   463  		Name: fmt.Sprintf("import-%s", name),
   464  		Import: types.JackalComponentImport{
   465  			Path: importDir,
   466  		},
   467  		Files: []types.JackalFile{
   468  			{
   469  				Source: fmt.Sprintf("%s.txt", name),
   470  			},
   471  		},
   472  		Charts: []types.JackalChart{
   473  			{
   474  				Name:      subName,
   475  				LocalPath: "chart",
   476  				ValuesFiles: []string{
   477  					"values.yaml",
   478  				},
   479  			},
   480  		},
   481  		Manifests: []types.JackalManifest{
   482  			{
   483  				Name: subName,
   484  				Files: []string{
   485  					"manifest.yaml",
   486  				},
   487  			},
   488  		},
   489  		DataInjections: []types.JackalDataInjection{
   490  			{
   491  				Source: name,
   492  			},
   493  		},
   494  		Actions: types.JackalComponentActions{
   495  			OnCreate: types.JackalComponentActionSet{
   496  				Defaults: types.JackalComponentActionDefaults{
   497  					Dir: name + "-dc",
   498  				},
   499  				Before: []types.JackalComponentAction{
   500  					{Cmd: name + "-bc"},
   501  				},
   502  				After: []types.JackalComponentAction{
   503  					{Cmd: name + "-ac"},
   504  				},
   505  				OnSuccess: []types.JackalComponentAction{
   506  					{Cmd: name + "-sc"},
   507  				},
   508  				OnFailure: []types.JackalComponentAction{
   509  					{Cmd: name + "-fc"},
   510  				},
   511  			},
   512  			OnDeploy: types.JackalComponentActionSet{
   513  				Defaults: types.JackalComponentActionDefaults{
   514  					Dir: name + "-dd",
   515  				},
   516  				Before: []types.JackalComponentAction{
   517  					{Cmd: name + "-bd"},
   518  				},
   519  				After: []types.JackalComponentAction{
   520  					{Cmd: name + "-ad"},
   521  				},
   522  				OnSuccess: []types.JackalComponentAction{
   523  					{Cmd: name + "-sd"},
   524  				},
   525  				OnFailure: []types.JackalComponentAction{
   526  					{Cmd: name + "-fd"},
   527  				},
   528  			},
   529  			OnRemove: types.JackalComponentActionSet{
   530  				Defaults: types.JackalComponentActionDefaults{
   531  					Dir: name + "-dr",
   532  				},
   533  				Before: []types.JackalComponentAction{
   534  					{Cmd: name + "-br"},
   535  				},
   536  				After: []types.JackalComponentAction{
   537  					{Cmd: name + "-ar"},
   538  				},
   539  				OnSuccess: []types.JackalComponentAction{
   540  					{Cmd: name + "-sr"},
   541  				},
   542  				OnFailure: []types.JackalComponentAction{
   543  					{Cmd: name + "-fr"},
   544  				},
   545  			},
   546  		},
   547  		Extensions: extensions.JackalComponentExtensions{
   548  			BigBang: &extensions.BigBang{
   549  				ValuesFiles: []string{
   550  					"values.yaml",
   551  				},
   552  				FluxPatchFiles: []string{
   553  					"patch.yaml",
   554  				},
   555  			},
   556  		},
   557  	}
   558  }