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 }