k8s.io/client-go@v0.31.1/tools/clientcmd/validation_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package clientcmd 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "strings" 24 "testing" 25 26 utiltesting "k8s.io/client-go/util/testing" 27 28 utilerrors "k8s.io/apimachinery/pkg/util/errors" 29 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 30 ) 31 32 func TestConfirmUsableBadInfoButOkConfig(t *testing.T) { 33 config := clientcmdapi.NewConfig() 34 config.Clusters["missing ca"] = &clientcmdapi.Cluster{ 35 Server: "anything", 36 CertificateAuthority: "missing", 37 } 38 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 39 Username: "anything", 40 Token: "here", 41 } 42 config.Contexts["dirty"] = &clientcmdapi.Context{ 43 Cluster: "missing ca", 44 AuthInfo: "error", 45 } 46 config.Clusters["clean"] = &clientcmdapi.Cluster{ 47 Server: "anything", 48 } 49 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 50 Token: "here", 51 } 52 config.Contexts["clean"] = &clientcmdapi.Context{ 53 Cluster: "clean", 54 AuthInfo: "clean", 55 } 56 57 badValidation := configValidationTest{ 58 config: config, 59 expectedErrorSubstring: []string{"unable to read certificate-authority"}, 60 } 61 okTest := configValidationTest{ 62 config: config, 63 } 64 65 okTest.testConfirmUsable("clean", t) 66 badValidation.testConfig(t) 67 } 68 69 func TestConfirmUsableMissingObjects(t *testing.T) { 70 config := clientcmdapi.NewConfig() 71 config.Clusters["kind-cluster"] = &clientcmdapi.Cluster{ 72 Server: "anything", 73 } 74 config.AuthInfos["kind-user"] = &clientcmdapi.AuthInfo{ 75 Token: "any-value", 76 } 77 config.Contexts["missing-user"] = &clientcmdapi.Context{ 78 Cluster: "kind-cluster", 79 AuthInfo: "garbage", 80 } 81 config.Contexts["missing-cluster"] = &clientcmdapi.Context{ 82 Cluster: "garbage", 83 AuthInfo: "kind-user", 84 } 85 86 missingUser := configValidationTest{ 87 config: config, 88 expectedErrorSubstring: []string{ 89 `user "garbage" was not found for context "missing-user"`, 90 }, 91 } 92 missingUser.testConfirmUsable("missing-user", t) 93 missingUser.testConfig(t) 94 95 missingCluster := configValidationTest{ 96 config: config, 97 expectedErrorSubstring: []string{ 98 `cluster "garbage" was not found for context "missing-cluster"`, 99 }, 100 } 101 missingCluster.testConfirmUsable("missing-cluster", t) 102 missingCluster.testConfig(t) 103 } 104 105 func TestConfirmUsableBadInfoConfig(t *testing.T) { 106 config := clientcmdapi.NewConfig() 107 config.Clusters["missing ca"] = &clientcmdapi.Cluster{ 108 Server: "anything", 109 CertificateAuthority: "missing", 110 } 111 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 112 Username: "anything", 113 Token: "here", 114 } 115 config.Contexts["first"] = &clientcmdapi.Context{ 116 Cluster: "missing ca", 117 AuthInfo: "error", 118 } 119 test := configValidationTest{ 120 config: config, 121 expectedErrorSubstring: []string{"unable to read certificate-authority"}, 122 } 123 124 test.testConfirmUsable("first", t) 125 } 126 127 func TestConfirmUsableEmptyConfig(t *testing.T) { 128 config := clientcmdapi.NewConfig() 129 test := configValidationTest{ 130 config: config, 131 expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"}, 132 } 133 134 test.testConfirmUsable("", t) 135 } 136 137 func TestConfirmUsableMissingConfig(t *testing.T) { 138 config := clientcmdapi.NewConfig() 139 test := configValidationTest{ 140 config: config, 141 expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"}, 142 } 143 144 test.testConfirmUsable("not-here", t) 145 } 146 147 func TestValidateEmptyConfig(t *testing.T) { 148 config := clientcmdapi.NewConfig() 149 test := configValidationTest{ 150 config: config, 151 expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"}, 152 } 153 154 test.testConfig(t) 155 } 156 157 func TestValidateMissingCurrentContextConfig(t *testing.T) { 158 config := clientcmdapi.NewConfig() 159 config.CurrentContext = "anything" 160 test := configValidationTest{ 161 config: config, 162 expectedErrorSubstring: []string{"context was not found for specified "}, 163 } 164 165 test.testConfig(t) 166 } 167 168 func TestIsContextNotFound(t *testing.T) { 169 config := clientcmdapi.NewConfig() 170 config.CurrentContext = "anything" 171 172 err := Validate(*config) 173 if !IsContextNotFound(err) { 174 t.Errorf("Expected context not found, but got %v", err) 175 } 176 if !IsConfigurationInvalid(err) { 177 t.Errorf("Expected configuration invalid, but got %v", err) 178 } 179 } 180 181 func TestIsEmptyConfig(t *testing.T) { 182 config := clientcmdapi.NewConfig() 183 184 err := Validate(*config) 185 if !IsEmptyConfig(err) { 186 t.Errorf("Expected context not found, but got %v", err) 187 } 188 if !IsConfigurationInvalid(err) { 189 t.Errorf("Expected configuration invalid, but got %v", err) 190 } 191 } 192 193 func TestIsConfigurationInvalid(t *testing.T) { 194 if newErrConfigurationInvalid([]error{}) != nil { 195 t.Errorf("unexpected error") 196 } 197 if newErrConfigurationInvalid([]error{ErrNoContext}) == ErrNoContext { 198 t.Errorf("unexpected error") 199 } 200 if newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext}) == nil { 201 t.Errorf("unexpected error") 202 } 203 if !IsConfigurationInvalid(newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext})) { 204 t.Errorf("unexpected error") 205 } 206 } 207 208 func TestValidateMissingReferencesConfig(t *testing.T) { 209 config := clientcmdapi.NewConfig() 210 config.CurrentContext = "anything" 211 config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"} 212 test := configValidationTest{ 213 config: config, 214 expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""}, 215 } 216 217 test.testContext("anything", t) 218 test.testConfig(t) 219 } 220 221 func TestValidateEmptyContext(t *testing.T) { 222 config := clientcmdapi.NewConfig() 223 config.CurrentContext = "anything" 224 config.Contexts["anything"] = &clientcmdapi.Context{} 225 test := configValidationTest{ 226 config: config, 227 expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""}, 228 } 229 230 test.testContext("anything", t) 231 test.testConfig(t) 232 } 233 234 func TestValidateEmptyContextName(t *testing.T) { 235 config := clientcmdapi.NewConfig() 236 config.CurrentContext = "anything" 237 config.Contexts[""] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"} 238 test := configValidationTest{ 239 config: config, 240 expectedErrorSubstring: []string{"empty context name", "is not allowed"}, 241 } 242 243 test.testContext("", t) 244 test.testConfig(t) 245 } 246 247 func TestValidateEmptyClusterInfo(t *testing.T) { 248 config := clientcmdapi.NewConfig() 249 config.Clusters["empty"] = clientcmdapi.NewCluster() 250 test := configValidationTest{ 251 config: config, 252 expectedErrorSubstring: []string{"cluster has no server defined"}, 253 } 254 255 test.testCluster("empty", t) 256 test.testConfig(t) 257 } 258 259 func TestValidateClusterInfoErrEmptyCluster(t *testing.T) { 260 cluster := clientcmdapi.NewCluster() 261 errs := validateClusterInfo("", *cluster) 262 263 if len(errs) != 1 { 264 t.Fatalf("unexpected errors: %v", errs) 265 } 266 if errs[0] != ErrEmptyCluster { 267 t.Errorf("unexpected error: %v", errs[0]) 268 } 269 } 270 271 func TestValidateMissingCAFileClusterInfo(t *testing.T) { 272 config := clientcmdapi.NewConfig() 273 config.Clusters["missing ca"] = &clientcmdapi.Cluster{ 274 Server: "anything", 275 CertificateAuthority: "missing", 276 } 277 test := configValidationTest{ 278 config: config, 279 expectedErrorSubstring: []string{"unable to read certificate-authority"}, 280 } 281 282 test.testCluster("missing ca", t) 283 test.testConfig(t) 284 } 285 286 func TestValidateCleanClusterInfo(t *testing.T) { 287 config := clientcmdapi.NewConfig() 288 config.Clusters["clean"] = &clientcmdapi.Cluster{ 289 Server: "anything", 290 } 291 test := configValidationTest{ 292 config: config, 293 } 294 295 test.testCluster("clean", t) 296 test.testConfig(t) 297 } 298 299 func TestValidateCleanWithCAClusterInfo(t *testing.T) { 300 tempFile, _ := os.CreateTemp("", "") 301 defer utiltesting.CloseAndRemove(t, tempFile) 302 303 config := clientcmdapi.NewConfig() 304 config.Clusters["clean"] = &clientcmdapi.Cluster{ 305 Server: "anything", 306 CertificateAuthority: tempFile.Name(), 307 } 308 test := configValidationTest{ 309 config: config, 310 } 311 312 test.testCluster("clean", t) 313 test.testConfig(t) 314 } 315 316 func TestValidateEmptyAuthInfo(t *testing.T) { 317 config := clientcmdapi.NewConfig() 318 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{} 319 test := configValidationTest{ 320 config: config, 321 } 322 323 test.testAuthInfo("error", t) 324 test.testConfig(t) 325 } 326 327 func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) { 328 config := clientcmdapi.NewConfig() 329 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 330 ClientCertificate: "missing", 331 ClientKey: "missing", 332 } 333 test := configValidationTest{ 334 config: config, 335 expectedErrorSubstring: []string{"unable to read client-cert", "unable to read client-key"}, 336 } 337 338 test.testAuthInfo("error", t) 339 test.testConfig(t) 340 } 341 342 func TestValidateCertDataOverridesFiles(t *testing.T) { 343 tempFile, _ := os.CreateTemp("", "") 344 defer utiltesting.CloseAndRemove(t, tempFile) 345 346 config := clientcmdapi.NewConfig() 347 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 348 ClientCertificate: tempFile.Name(), 349 ClientCertificateData: []byte("certdata"), 350 ClientKey: tempFile.Name(), 351 ClientKeyData: []byte("keydata"), 352 } 353 test := configValidationTest{ 354 config: config, 355 expectedErrorSubstring: []string{"client-cert-data and client-cert are both specified", "client-key-data and client-key are both specified"}, 356 } 357 358 test.testAuthInfo("clean", t) 359 test.testConfig(t) 360 } 361 362 func TestValidateCleanCertFilesAuthInfo(t *testing.T) { 363 tempFile, _ := os.CreateTemp("", "") 364 defer utiltesting.CloseAndRemove(t, tempFile) 365 366 config := clientcmdapi.NewConfig() 367 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 368 ClientCertificate: tempFile.Name(), 369 ClientKey: tempFile.Name(), 370 } 371 test := configValidationTest{ 372 config: config, 373 } 374 375 test.testAuthInfo("clean", t) 376 test.testConfig(t) 377 } 378 379 func TestValidateCleanTokenAuthInfo(t *testing.T) { 380 config := clientcmdapi.NewConfig() 381 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 382 Token: "any-value", 383 } 384 test := configValidationTest{ 385 config: config, 386 } 387 388 test.testAuthInfo("clean", t) 389 test.testConfig(t) 390 } 391 392 func TestValidateMultipleMethodsAuthInfo(t *testing.T) { 393 config := clientcmdapi.NewConfig() 394 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 395 Token: "token", 396 Username: "username", 397 } 398 test := configValidationTest{ 399 config: config, 400 expectedErrorSubstring: []string{"more than one authentication method", "token", "basicAuth"}, 401 } 402 403 test.testAuthInfo("error", t) 404 test.testConfig(t) 405 } 406 407 func TestValidateAuthInfoExec(t *testing.T) { 408 config := clientcmdapi.NewConfig() 409 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 410 Exec: &clientcmdapi.ExecConfig{ 411 Command: "/bin/example", 412 APIVersion: "clientauthentication.k8s.io/v1alpha1", 413 Args: []string{"hello", "world"}, 414 Env: []clientcmdapi.ExecEnvVar{ 415 {Name: "foo", Value: "bar"}, 416 }, 417 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 418 }, 419 } 420 test := configValidationTest{ 421 config: config, 422 } 423 424 test.testAuthInfo("user", t) 425 test.testConfig(t) 426 } 427 428 func TestValidateAuthInfoExecNoVersion(t *testing.T) { 429 config := clientcmdapi.NewConfig() 430 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 431 Exec: &clientcmdapi.ExecConfig{ 432 Command: "/bin/example", 433 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 434 }, 435 } 436 test := configValidationTest{ 437 config: config, 438 expectedErrorSubstring: []string{ 439 "apiVersion must be specified for user to use exec authentication plugin", 440 }, 441 } 442 443 test.testAuthInfo("user", t) 444 test.testConfig(t) 445 } 446 447 func TestValidateAuthInfoExecNoCommand(t *testing.T) { 448 config := clientcmdapi.NewConfig() 449 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 450 Exec: &clientcmdapi.ExecConfig{ 451 APIVersion: "clientauthentication.k8s.io/v1alpha1", 452 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 453 }, 454 } 455 test := configValidationTest{ 456 config: config, 457 expectedErrorSubstring: []string{ 458 "command must be specified for user to use exec authentication plugin", 459 }, 460 } 461 462 test.testAuthInfo("user", t) 463 test.testConfig(t) 464 } 465 466 func TestValidateAuthInfoExecWithAuthProvider(t *testing.T) { 467 config := clientcmdapi.NewConfig() 468 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 469 AuthProvider: &clientcmdapi.AuthProviderConfig{ 470 Name: "oidc", 471 }, 472 Exec: &clientcmdapi.ExecConfig{ 473 Command: "/bin/example", 474 APIVersion: "clientauthentication.k8s.io/v1alpha1", 475 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 476 }, 477 } 478 test := configValidationTest{ 479 config: config, 480 expectedErrorSubstring: []string{ 481 "authProvider cannot be provided in combination with an exec plugin for user", 482 }, 483 } 484 485 test.testAuthInfo("user", t) 486 test.testConfig(t) 487 } 488 489 func TestValidateAuthInfoExecNoEnv(t *testing.T) { 490 config := clientcmdapi.NewConfig() 491 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 492 Exec: &clientcmdapi.ExecConfig{ 493 Command: "/bin/example", 494 APIVersion: "clientauthentication.k8s.io/v1alpha1", 495 Env: []clientcmdapi.ExecEnvVar{ 496 {Name: "foo", Value: ""}, 497 }, 498 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 499 }, 500 } 501 test := configValidationTest{ 502 config: config, 503 } 504 505 test.testAuthInfo("user", t) 506 test.testConfig(t) 507 } 508 509 func TestValidateAuthInfoExecInteractiveModeMissing(t *testing.T) { 510 config := clientcmdapi.NewConfig() 511 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 512 Exec: &clientcmdapi.ExecConfig{ 513 Command: "/bin/example", 514 APIVersion: "clientauthentication.k8s.io/v1alpha1", 515 }, 516 } 517 test := configValidationTest{ 518 config: config, 519 expectedErrorSubstring: []string{ 520 "interactiveMode must be specified for user to use exec authentication plugin", 521 }, 522 } 523 524 test.testAuthInfo("user", t) 525 test.testConfig(t) 526 } 527 528 func TestValidateAuthInfoExecInteractiveModeInvalid(t *testing.T) { 529 config := clientcmdapi.NewConfig() 530 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 531 Exec: &clientcmdapi.ExecConfig{ 532 Command: "/bin/example", 533 APIVersion: "clientauthentication.k8s.io/v1alpha1", 534 InteractiveMode: "invalid", 535 }, 536 } 537 test := configValidationTest{ 538 config: config, 539 expectedErrorSubstring: []string{ 540 `invalid interactiveMode for user: "invalid"`, 541 }, 542 } 543 544 test.testAuthInfo("user", t) 545 test.testConfig(t) 546 } 547 548 func TestValidateAuthInfoImpersonateUser(t *testing.T) { 549 config := clientcmdapi.NewConfig() 550 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 551 Impersonate: "user", 552 } 553 test := configValidationTest{ 554 config: config, 555 } 556 test.testAuthInfo("user", t) 557 test.testConfig(t) 558 } 559 560 func TestValidateAuthInfoImpersonateEverything(t *testing.T) { 561 config := clientcmdapi.NewConfig() 562 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 563 Impersonate: "user", 564 ImpersonateUID: "abc123", 565 ImpersonateGroups: []string{"group-1", "group-2"}, 566 ImpersonateUserExtra: map[string][]string{"key": {"val1", "val2"}}, 567 } 568 test := configValidationTest{ 569 config: config, 570 } 571 test.testAuthInfo("user", t) 572 test.testConfig(t) 573 } 574 575 func TestValidateAuthInfoImpersonateGroupsWithoutUserInvalid(t *testing.T) { 576 config := clientcmdapi.NewConfig() 577 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 578 ImpersonateGroups: []string{"group-1", "group-2"}, 579 } 580 test := configValidationTest{ 581 config: config, 582 expectedErrorSubstring: []string{ 583 `requesting uid, groups or user-extra for user without impersonating a user`, 584 }, 585 } 586 test.testAuthInfo("user", t) 587 test.testConfig(t) 588 } 589 590 func TestValidateAuthInfoImpersonateExtraWithoutUserInvalid(t *testing.T) { 591 config := clientcmdapi.NewConfig() 592 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 593 ImpersonateUserExtra: map[string][]string{"key": {"val1", "val2"}}, 594 } 595 test := configValidationTest{ 596 config: config, 597 expectedErrorSubstring: []string{ 598 `requesting uid, groups or user-extra for user without impersonating a user`, 599 }, 600 } 601 test.testAuthInfo("user", t) 602 test.testConfig(t) 603 } 604 605 func TestValidateAuthInfoImpersonateUIDWithoutUserInvalid(t *testing.T) { 606 config := clientcmdapi.NewConfig() 607 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 608 ImpersonateUID: "abc123", 609 } 610 test := configValidationTest{ 611 config: config, 612 expectedErrorSubstring: []string{ 613 `requesting uid, groups or user-extra for user without impersonating a user`, 614 }, 615 } 616 test.testAuthInfo("user", t) 617 test.testConfig(t) 618 } 619 620 type configValidationTest struct { 621 config *clientcmdapi.Config 622 expectedErrorSubstring []string 623 } 624 625 func (c configValidationTest) testContext(contextName string, t *testing.T) { 626 errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config) 627 628 if len(c.expectedErrorSubstring) != 0 { 629 if len(errs) == 0 { 630 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 631 } 632 for _, curr := range c.expectedErrorSubstring { 633 if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) { 634 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs)) 635 } 636 } 637 638 } else { 639 if len(errs) != 0 { 640 t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs)) 641 } 642 } 643 } 644 645 func (c configValidationTest) testConfirmUsable(contextName string, t *testing.T) { 646 err := ConfirmUsable(*c.config, contextName) 647 648 if len(c.expectedErrorSubstring) != 0 { 649 if err == nil { 650 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 651 } else { 652 for _, curr := range c.expectedErrorSubstring { 653 if err != nil && !strings.Contains(err.Error(), curr) { 654 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err) 655 } 656 } 657 } 658 } else { 659 if err != nil { 660 t.Errorf("Unexpected error: %v", err) 661 } 662 } 663 } 664 665 func (c configValidationTest) testConfig(t *testing.T) { 666 err := Validate(*c.config) 667 668 if len(c.expectedErrorSubstring) != 0 { 669 if err == nil { 670 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 671 } else { 672 for _, curr := range c.expectedErrorSubstring { 673 if err != nil && !strings.Contains(err.Error(), curr) { 674 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err) 675 } 676 } 677 if !IsConfigurationInvalid(err) { 678 t.Errorf("all errors should be configuration invalid: %v", err) 679 } 680 } 681 } else { 682 if err != nil { 683 t.Errorf("Unexpected error: %v", err) 684 } 685 } 686 } 687 688 func (c configValidationTest) testCluster(clusterName string, t *testing.T) { 689 errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName]) 690 691 if len(c.expectedErrorSubstring) != 0 { 692 if len(errs) == 0 { 693 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 694 } 695 for _, curr := range c.expectedErrorSubstring { 696 if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) { 697 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs)) 698 } 699 } 700 701 } else { 702 if len(errs) != 0 { 703 t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs)) 704 } 705 } 706 } 707 708 func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) { 709 errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName]) 710 711 if len(c.expectedErrorSubstring) != 0 { 712 if len(errs) == 0 { 713 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 714 } 715 for _, curr := range c.expectedErrorSubstring { 716 if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) { 717 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs)) 718 } 719 } 720 721 } else { 722 if len(errs) != 0 { 723 t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs)) 724 } 725 } 726 } 727 728 type alwaysMatchingError struct{} 729 730 func (_ alwaysMatchingError) Error() string { 731 return "error" 732 } 733 734 func (_ alwaysMatchingError) Is(_ error) bool { 735 return true 736 } 737 738 type someError struct{ msg string } 739 740 func (se someError) Error() string { 741 if se.msg != "" { 742 return se.msg 743 } 744 return "err" 745 } 746 747 func TestErrConfigurationInvalidWithErrorsIs(t *testing.T) { 748 testCases := []struct { 749 name string 750 err error 751 matchAgainst error 752 expectMatch bool 753 }{{ 754 name: "no match", 755 err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")}, 756 matchAgainst: fmt.Errorf("no entry %s", "here"), 757 }, { 758 name: "match via .Is()", 759 err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}}, 760 matchAgainst: errors.New("unauthorized"), 761 expectMatch: true, 762 }, { 763 name: "match via equality", 764 err: errConfigurationInvalid{errors.New("err"), someError{}}, 765 matchAgainst: someError{}, 766 expectMatch: true, 767 }, { 768 name: "match via nested aggregate", 769 err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}}, 770 matchAgainst: someError{}, 771 expectMatch: true, 772 }, { 773 name: "match via wrapped aggregate", 774 err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}), 775 matchAgainst: someError{}, 776 expectMatch: true, 777 }} 778 779 for _, tc := range testCases { 780 t.Run(tc.name, func(t *testing.T) { 781 result := errors.Is(tc.err, tc.matchAgainst) 782 if result != tc.expectMatch { 783 t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result) 784 } 785 }) 786 } 787 } 788 789 type accessTrackingError struct { 790 wasAccessed bool 791 } 792 793 func (accessTrackingError) Error() string { 794 return "err" 795 } 796 797 func (ate *accessTrackingError) Is(_ error) bool { 798 ate.wasAccessed = true 799 return true 800 } 801 802 var _ error = &accessTrackingError{} 803 804 func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) { 805 errC := errConfigurationInvalid{&accessTrackingError{}, &accessTrackingError{}} 806 _ = errors.Is(errC, &accessTrackingError{}) 807 808 var numAccessed int 809 for _, err := range errC { 810 if ate := err.(*accessTrackingError); ate.wasAccessed { 811 numAccessed++ 812 } 813 } 814 if numAccessed != 1 { 815 t.Errorf("expected exactly one error to get accessed, got %d", numAccessed) 816 } 817 }