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 }