oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/credentials/internal/config/config_test.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package config 17 18 import ( 19 "encoding/json" 20 "errors" 21 "os" 22 "path/filepath" 23 "reflect" 24 "testing" 25 26 "oras.land/oras-go/v2/registry/remote/auth" 27 "oras.land/oras-go/v2/registry/remote/credentials/internal/config/configtest" 28 ) 29 30 func TestLoad_badPath(t *testing.T) { 31 tempDir := t.TempDir() 32 33 tests := []struct { 34 name string 35 configPath string 36 wantErr bool 37 }{ 38 { 39 name: "Path is a directory", 40 configPath: tempDir, 41 wantErr: true, 42 }, 43 { 44 name: "Empty file name", 45 configPath: filepath.Join(tempDir, ""), 46 wantErr: true, 47 }, 48 } 49 for _, tt := range tests { 50 t.Run(tt.name, func(t *testing.T) { 51 _, err := Load(tt.configPath) 52 if (err != nil) != tt.wantErr { 53 t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr) 54 return 55 } 56 }) 57 } 58 } 59 60 func TestLoad_badFormat(t *testing.T) { 61 tests := []struct { 62 name string 63 configPath string 64 wantErr bool 65 }{ 66 { 67 name: "Bad JSON format", 68 configPath: "../../testdata/bad_config", 69 wantErr: true, 70 }, 71 { 72 name: "Invalid auths format", 73 configPath: "../../testdata/invalid_auths_config.json", 74 wantErr: true, 75 }, 76 { 77 name: "No auths field", 78 configPath: "../../testdata/no_auths_config.json", 79 wantErr: false, 80 }, 81 } 82 for _, tt := range tests { 83 t.Run(tt.name, func(t *testing.T) { 84 _, err := Load(tt.configPath) 85 if (err != nil) != tt.wantErr { 86 t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr) 87 return 88 } 89 }) 90 } 91 } 92 93 func TestConfig_GetCredential_validConfig(t *testing.T) { 94 cfg, err := Load("../../testdata/valid_auths_config.json") 95 if err != nil { 96 t.Fatal("Load() error =", err) 97 } 98 99 tests := []struct { 100 name string 101 serverAddress string 102 want auth.Credential 103 wantErr bool 104 }{ 105 { 106 name: "Username and password", 107 serverAddress: "registry1.example.com", 108 want: auth.Credential{ 109 Username: "username", 110 Password: "password", 111 }, 112 }, 113 { 114 name: "Identity token", 115 serverAddress: "registry2.example.com", 116 want: auth.Credential{ 117 RefreshToken: "identity_token", 118 }, 119 }, 120 { 121 name: "Registry token", 122 serverAddress: "registry3.example.com", 123 want: auth.Credential{ 124 AccessToken: "registry_token", 125 }, 126 }, 127 { 128 name: "Username and password, identity token and registry token", 129 serverAddress: "registry4.example.com", 130 want: auth.Credential{ 131 Username: "username", 132 Password: "password", 133 RefreshToken: "identity_token", 134 AccessToken: "registry_token", 135 }, 136 }, 137 { 138 name: "Empty credential", 139 serverAddress: "registry5.example.com", 140 want: auth.EmptyCredential, 141 }, 142 { 143 name: "Username and password, no auth", 144 serverAddress: "registry6.example.com", 145 want: auth.Credential{ 146 Username: "username", 147 Password: "password", 148 }, 149 }, 150 { 151 name: "Auth overriding Username and password", 152 serverAddress: "registry7.example.com", 153 want: auth.Credential{ 154 Username: "username", 155 Password: "password", 156 }, 157 }, 158 { 159 name: "Not in auths", 160 serverAddress: "foo.example.com", 161 want: auth.EmptyCredential, 162 }, 163 { 164 name: "No record", 165 serverAddress: "registry999.example.com", 166 want: auth.EmptyCredential, 167 }, 168 } 169 for _, tt := range tests { 170 t.Run(tt.name, func(t *testing.T) { 171 got, err := cfg.GetCredential(tt.serverAddress) 172 if (err != nil) != tt.wantErr { 173 t.Errorf("Config.GetCredential() error = %v, wantErr %v", err, tt.wantErr) 174 return 175 } 176 if !reflect.DeepEqual(got, tt.want) { 177 t.Errorf("Config.GetCredential() = %v, want %v", got, tt.want) 178 } 179 }) 180 } 181 } 182 183 func TestConfig_GetCredential_legacyConfig(t *testing.T) { 184 cfg, err := Load("../../testdata/legacy_auths_config.json") 185 if err != nil { 186 t.Fatal("Load() error =", err) 187 } 188 189 tests := []struct { 190 name string 191 serverAddress string 192 want auth.Credential 193 wantErr bool 194 }{ 195 { 196 name: "Regular address matched", 197 serverAddress: "registry1.example.com", 198 want: auth.Credential{ 199 Username: "username1", 200 Password: "password1", 201 }, 202 }, 203 { 204 name: "Another entry for the same address matched", 205 serverAddress: "https://registry1.example.com/", 206 want: auth.Credential{ 207 Username: "foo", 208 Password: "bar", 209 }, 210 }, 211 { 212 name: "Address with different scheme unmached", 213 serverAddress: "http://registry1.example.com/", 214 want: auth.EmptyCredential, 215 }, 216 { 217 name: "Address with http prefix matched", 218 serverAddress: "registry2.example.com", 219 want: auth.Credential{ 220 Username: "username2", 221 Password: "password2", 222 }, 223 }, 224 { 225 name: "Address with https prefix matched", 226 serverAddress: "registry3.example.com", 227 want: auth.Credential{ 228 Username: "username3", 229 Password: "password3", 230 }, 231 }, 232 { 233 name: "Address with http prefix and / suffix matched", 234 serverAddress: "registry4.example.com", 235 want: auth.Credential{ 236 Username: "username4", 237 Password: "password4", 238 }, 239 }, 240 { 241 name: "Address with https prefix and / suffix matched", 242 serverAddress: "registry5.example.com", 243 want: auth.Credential{ 244 Username: "username5", 245 Password: "password5", 246 }, 247 }, 248 { 249 name: "Address with https prefix and path suffix matched", 250 serverAddress: "registry6.example.com", 251 want: auth.Credential{ 252 Username: "username6", 253 Password: "password6", 254 }, 255 }, 256 } 257 for _, tt := range tests { 258 t.Run(tt.name, func(t *testing.T) { 259 got, err := cfg.GetCredential(tt.serverAddress) 260 if (err != nil) != tt.wantErr { 261 t.Errorf("Config.GetCredential() error = %v, wantErr %v", err, tt.wantErr) 262 return 263 } 264 if !reflect.DeepEqual(got, tt.want) { 265 t.Errorf("Config.GetCredential() = %v, want %v", got, tt.want) 266 } 267 }) 268 } 269 } 270 271 func TestConfig_GetCredential_invalidConfig(t *testing.T) { 272 cfg, err := Load("../../testdata/invalid_auths_entry_config.json") 273 if err != nil { 274 t.Fatal("Load() error =", err) 275 } 276 277 tests := []struct { 278 name string 279 serverAddress string 280 want auth.Credential 281 wantErr bool 282 }{ 283 { 284 name: "Invalid auth encode", 285 serverAddress: "registry1.example.com", 286 want: auth.EmptyCredential, 287 wantErr: true, 288 }, 289 { 290 name: "Invalid auths format", 291 serverAddress: "registry2.example.com", 292 want: auth.EmptyCredential, 293 wantErr: true, 294 }, 295 { 296 name: "Invalid type", 297 serverAddress: "registry3.example.com", 298 want: auth.EmptyCredential, 299 wantErr: true, 300 }, 301 } 302 for _, tt := range tests { 303 t.Run(tt.name, func(t *testing.T) { 304 got, err := cfg.GetCredential(tt.serverAddress) 305 if (err != nil) != tt.wantErr { 306 t.Errorf("Config.GetCredential() error = %v, wantErr %v", err, tt.wantErr) 307 return 308 } 309 if !reflect.DeepEqual(got, tt.want) { 310 t.Errorf("Config.GetCredential() = %v, want %v", got, tt.want) 311 } 312 }) 313 } 314 } 315 316 func TestConfig_GetCredential_emptyConfig(t *testing.T) { 317 cfg, err := Load("../../testdata/empty_config.json") 318 if err != nil { 319 t.Fatal("Load() error =", err) 320 } 321 322 tests := []struct { 323 name string 324 serverAddress string 325 want auth.Credential 326 wantErr error 327 }{ 328 { 329 name: "Not found", 330 serverAddress: "registry.example.com", 331 want: auth.EmptyCredential, 332 wantErr: nil, 333 }, 334 } 335 for _, tt := range tests { 336 t.Run(tt.name, func(t *testing.T) { 337 got, err := cfg.GetCredential(tt.serverAddress) 338 if !errors.Is(err, tt.wantErr) { 339 t.Errorf("Config.GetCredential() error = %v, wantErr %v", err, tt.wantErr) 340 return 341 } 342 if !reflect.DeepEqual(got, tt.want) { 343 t.Errorf("Config.GetCredential() = %v, want %v", got, tt.want) 344 } 345 }) 346 } 347 } 348 349 func TestConfig_GetCredential_notExistConfig(t *testing.T) { 350 cfg, err := Load("whatever") 351 if err != nil { 352 t.Fatal("Load() error =", err) 353 } 354 355 tests := []struct { 356 name string 357 serverAddress string 358 want auth.Credential 359 wantErr error 360 }{ 361 { 362 name: "Not found", 363 serverAddress: "registry.example.com", 364 want: auth.EmptyCredential, 365 wantErr: nil, 366 }, 367 } 368 for _, tt := range tests { 369 t.Run(tt.name, func(t *testing.T) { 370 got, err := cfg.GetCredential(tt.serverAddress) 371 if !errors.Is(err, tt.wantErr) { 372 t.Errorf("Config.GetCredential() error = %v, wantErr %v", err, tt.wantErr) 373 return 374 } 375 if !reflect.DeepEqual(got, tt.want) { 376 t.Errorf("Config.GetCredential() = %v, want %v", got, tt.want) 377 } 378 }) 379 } 380 } 381 382 func TestConfig_PutCredential_notExistConfig(t *testing.T) { 383 tempDir := t.TempDir() 384 configPath := filepath.Join(tempDir, "config.json") 385 386 cfg, err := Load(configPath) 387 if err != nil { 388 t.Fatal("Load() error =", err) 389 } 390 391 server := "test.example.com" 392 cred := auth.Credential{ 393 Username: "username", 394 Password: "password", 395 RefreshToken: "refresh_token", 396 AccessToken: "access_token", 397 } 398 399 // test put 400 if err := cfg.PutCredential(server, cred); err != nil { 401 t.Fatalf("Config.PutCredential() error = %v", err) 402 } 403 404 // verify config file 405 configFile, err := os.Open(configPath) 406 if err != nil { 407 t.Fatalf("failed to open config file: %v", err) 408 } 409 defer configFile.Close() 410 411 var testCfg configtest.Config 412 if err := json.NewDecoder(configFile).Decode(&testCfg); err != nil { 413 t.Fatalf("failed to decode config file: %v", err) 414 } 415 want := configtest.Config{ 416 AuthConfigs: map[string]configtest.AuthConfig{ 417 server: { 418 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 419 IdentityToken: "refresh_token", 420 RegistryToken: "access_token", 421 }, 422 }, 423 } 424 if !reflect.DeepEqual(testCfg, want) { 425 t.Errorf("Decoded config = %v, want %v", testCfg, want) 426 } 427 428 // verify get 429 got, err := cfg.GetCredential(server) 430 if err != nil { 431 t.Fatalf("Config.GetCredential() error = %v", err) 432 } 433 if want := cred; !reflect.DeepEqual(got, want) { 434 t.Errorf("Config.GetCredential() = %v, want %v", got, want) 435 } 436 } 437 438 func TestConfig_PutCredential_addNew(t *testing.T) { 439 tempDir := t.TempDir() 440 configPath := filepath.Join(tempDir, "config.json") 441 // prepare test content 442 server1 := "registry1.example.com" 443 cred1 := auth.Credential{ 444 Username: "username", 445 Password: "password", 446 RefreshToken: "refresh_token", 447 AccessToken: "access_token", 448 } 449 450 testCfg := configtest.Config{ 451 AuthConfigs: map[string]configtest.AuthConfig{ 452 server1: { 453 SomeAuthField: "whatever", 454 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 455 IdentityToken: cred1.RefreshToken, 456 RegistryToken: cred1.AccessToken, 457 }, 458 }, 459 SomeConfigField: 123, 460 } 461 jsonStr, err := json.Marshal(testCfg) 462 if err != nil { 463 t.Fatalf("failed to marshal config: %v", err) 464 } 465 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 466 t.Fatalf("failed to write config file: %v", err) 467 } 468 469 // test put 470 cfg, err := Load(configPath) 471 if err != nil { 472 t.Fatal("Load() error =", err) 473 } 474 server2 := "registry2.example.com" 475 cred2 := auth.Credential{ 476 Username: "username_2", 477 Password: "password_2", 478 RefreshToken: "refresh_token_2", 479 AccessToken: "access_token_2", 480 } 481 if err := cfg.PutCredential(server2, cred2); err != nil { 482 t.Fatalf("Config.PutCredential() error = %v", err) 483 } 484 485 // verify config file 486 configFile, err := os.Open(configPath) 487 if err != nil { 488 t.Fatalf("failed to open config file: %v", err) 489 } 490 defer configFile.Close() 491 var gotCfg configtest.Config 492 if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil { 493 t.Fatalf("failed to decode config file: %v", err) 494 } 495 wantTestCfg := configtest.Config{ 496 AuthConfigs: map[string]configtest.AuthConfig{ 497 server1: { 498 SomeAuthField: "whatever", 499 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 500 IdentityToken: cred1.RefreshToken, 501 RegistryToken: cred1.AccessToken, 502 }, 503 server2: { 504 Auth: "dXNlcm5hbWVfMjpwYXNzd29yZF8y", 505 IdentityToken: "refresh_token_2", 506 RegistryToken: "access_token_2", 507 }, 508 }, 509 SomeConfigField: testCfg.SomeConfigField, 510 } 511 if !reflect.DeepEqual(gotCfg, wantTestCfg) { 512 t.Errorf("Decoded config = %v, want %v", gotCfg, wantTestCfg) 513 } 514 515 // verify get 516 got, err := cfg.GetCredential(server1) 517 if err != nil { 518 t.Fatalf("Config.GetCredential() error = %v", err) 519 } 520 if want := cred1; !reflect.DeepEqual(got, want) { 521 t.Errorf("Config.GetCredential(%s) = %v, want %v", server1, got, want) 522 } 523 524 got, err = cfg.GetCredential(server2) 525 if err != nil { 526 t.Fatalf("Config.GetCredential() error = %v", err) 527 } 528 if want := cred2; !reflect.DeepEqual(got, want) { 529 t.Errorf("Config.GetCredential(%s) = %v, want %v", server2, got, want) 530 } 531 } 532 533 func TestConfig_PutCredential_updateOld(t *testing.T) { 534 tempDir := t.TempDir() 535 configPath := filepath.Join(tempDir, "config.json") 536 537 // prepare test content 538 server := "registry.example.com" 539 testCfg := configtest.Config{ 540 AuthConfigs: map[string]configtest.AuthConfig{ 541 server: { 542 SomeAuthField: "whatever", 543 Username: "foo", 544 Password: "bar", 545 IdentityToken: "refresh_token", 546 }, 547 }, 548 SomeConfigField: 123, 549 } 550 jsonStr, err := json.Marshal(testCfg) 551 if err != nil { 552 t.Fatalf("failed to marshal config: %v", err) 553 } 554 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 555 t.Fatalf("failed to write config file: %v", err) 556 } 557 558 // test put 559 cfg, err := Load(configPath) 560 if err != nil { 561 t.Fatal("Load() error =", err) 562 } 563 cred := auth.Credential{ 564 Username: "username", 565 Password: "password", 566 AccessToken: "access_token", 567 } 568 if err := cfg.PutCredential(server, cred); err != nil { 569 t.Fatalf("Config.PutCredential() error = %v", err) 570 } 571 572 // verify config file 573 configFile, err := os.Open(configPath) 574 if err != nil { 575 t.Fatalf("failed to open config file: %v", err) 576 } 577 defer configFile.Close() 578 var gotCfg configtest.Config 579 if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil { 580 t.Fatalf("failed to decode config file: %v", err) 581 } 582 wantCfg := configtest.Config{ 583 AuthConfigs: map[string]configtest.AuthConfig{ 584 server: { 585 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 586 RegistryToken: "access_token", 587 }, 588 }, 589 SomeConfigField: testCfg.SomeConfigField, 590 } 591 if !reflect.DeepEqual(gotCfg, wantCfg) { 592 t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg) 593 } 594 595 // verify get 596 got, err := cfg.GetCredential(server) 597 if err != nil { 598 t.Fatalf("Config.GetCredential() error = %v", err) 599 } 600 if want := cred; !reflect.DeepEqual(got, want) { 601 t.Errorf("Config.GetCredential(%s) = %v, want %v", server, got, want) 602 } 603 } 604 605 func TestConfig_DeleteCredential(t *testing.T) { 606 tempDir := t.TempDir() 607 configPath := filepath.Join(tempDir, "config.json") 608 609 // prepare test content 610 server1 := "registry1.example.com" 611 cred1 := auth.Credential{ 612 Username: "username", 613 Password: "password", 614 RefreshToken: "refresh_token", 615 AccessToken: "access_token", 616 } 617 server2 := "registry2.example.com" 618 cred2 := auth.Credential{ 619 Username: "username_2", 620 Password: "password_2", 621 RefreshToken: "refresh_token_2", 622 AccessToken: "access_token_2", 623 } 624 625 testCfg := configtest.Config{ 626 AuthConfigs: map[string]configtest.AuthConfig{ 627 server1: { 628 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 629 IdentityToken: cred1.RefreshToken, 630 RegistryToken: cred1.AccessToken, 631 }, 632 server2: { 633 Auth: "dXNlcm5hbWVfMjpwYXNzd29yZF8y", 634 IdentityToken: "refresh_token_2", 635 RegistryToken: "access_token_2", 636 }, 637 }, 638 SomeConfigField: 123, 639 } 640 jsonStr, err := json.Marshal(testCfg) 641 if err != nil { 642 t.Fatalf("failed to marshal config: %v", err) 643 } 644 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 645 t.Fatalf("failed to write config file: %v", err) 646 } 647 648 cfg, err := Load(configPath) 649 if err != nil { 650 t.Fatal("Load() error =", err) 651 } 652 // test get 653 got, err := cfg.GetCredential(server1) 654 if err != nil { 655 t.Fatalf("FileStore.GetCredential() error = %v", err) 656 } 657 if want := cred1; !reflect.DeepEqual(got, want) { 658 t.Errorf("FileStore.GetCredential(%s) = %v, want %v", server1, got, want) 659 } 660 got, err = cfg.GetCredential(server2) 661 if err != nil { 662 t.Fatalf("FileStore.GetCredential() error = %v", err) 663 } 664 if want := cred2; !reflect.DeepEqual(got, want) { 665 t.Errorf("FileStore.Get(%s) = %v, want %v", server2, got, want) 666 } 667 668 // test delete 669 if err := cfg.DeleteCredential(server1); err != nil { 670 t.Fatalf("Config.DeleteCredential() error = %v", err) 671 } 672 673 // verify config file 674 configFile, err := os.Open(configPath) 675 if err != nil { 676 t.Fatalf("failed to open config file: %v", err) 677 } 678 defer configFile.Close() 679 var gotTestCfg configtest.Config 680 if err := json.NewDecoder(configFile).Decode(&gotTestCfg); err != nil { 681 t.Fatalf("failed to decode config file: %v", err) 682 } 683 wantTestCfg := configtest.Config{ 684 AuthConfigs: map[string]configtest.AuthConfig{ 685 server2: testCfg.AuthConfigs[server2], 686 }, 687 SomeConfigField: testCfg.SomeConfigField, 688 } 689 if !reflect.DeepEqual(gotTestCfg, wantTestCfg) { 690 t.Errorf("Decoded config = %v, want %v", gotTestCfg, wantTestCfg) 691 } 692 693 // test get again 694 got, err = cfg.GetCredential(server1) 695 if err != nil { 696 t.Fatalf("Config.GetCredential() error = %v", err) 697 } 698 if want := auth.EmptyCredential; !reflect.DeepEqual(got, want) { 699 t.Errorf("Config.GetCredential(%s) = %v, want %v", server1, got, want) 700 } 701 got, err = cfg.GetCredential(server2) 702 if err != nil { 703 t.Fatalf("Config.GetCredential() error = %v", err) 704 } 705 if want := cred2; !reflect.DeepEqual(got, want) { 706 t.Errorf("Config.GetCredential(%s) = %v, want %v", server2, got, want) 707 } 708 } 709 710 func TestConfig_DeleteCredential_lastConfig(t *testing.T) { 711 tempDir := t.TempDir() 712 configPath := filepath.Join(tempDir, "config.json") 713 714 // prepare test content 715 server := "registry1.example.com" 716 cred := auth.Credential{ 717 Username: "username", 718 Password: "password", 719 RefreshToken: "refresh_token", 720 AccessToken: "access_token", 721 } 722 723 testCfg := configtest.Config{ 724 AuthConfigs: map[string]configtest.AuthConfig{ 725 server: { 726 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 727 IdentityToken: cred.RefreshToken, 728 RegistryToken: cred.AccessToken, 729 }, 730 }, 731 SomeConfigField: 123, 732 } 733 jsonStr, err := json.Marshal(testCfg) 734 if err != nil { 735 t.Fatalf("failed to marshal config: %v", err) 736 } 737 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 738 t.Fatalf("failed to write config file: %v", err) 739 } 740 741 cfg, err := Load(configPath) 742 if err != nil { 743 t.Fatal("Load() error =", err) 744 } 745 // test get 746 got, err := cfg.GetCredential(server) 747 if err != nil { 748 t.Fatalf("Config.GetCredential() error = %v", err) 749 } 750 if want := cred; !reflect.DeepEqual(got, want) { 751 t.Errorf("Config.GetCredential(%s) = %v, want %v", server, got, want) 752 } 753 754 // test delete 755 if err := cfg.DeleteCredential(server); err != nil { 756 t.Fatalf("Config.DeleteCredential() error = %v", err) 757 } 758 759 // verify config file 760 configFile, err := os.Open(configPath) 761 if err != nil { 762 t.Fatalf("failed to open config file: %v", err) 763 } 764 defer configFile.Close() 765 var gotTestCfg configtest.Config 766 if err := json.NewDecoder(configFile).Decode(&gotTestCfg); err != nil { 767 t.Fatalf("failed to decode config file: %v", err) 768 } 769 wantTestCfg := configtest.Config{ 770 AuthConfigs: map[string]configtest.AuthConfig{}, 771 SomeConfigField: testCfg.SomeConfigField, 772 } 773 if !reflect.DeepEqual(gotTestCfg, wantTestCfg) { 774 t.Errorf("Decoded config = %v, want %v", gotTestCfg, wantTestCfg) 775 } 776 777 // test get again 778 got, err = cfg.GetCredential(server) 779 if err != nil { 780 t.Fatalf("Config.GetCredential() error = %v", err) 781 } 782 if want := auth.EmptyCredential; !reflect.DeepEqual(got, want) { 783 t.Errorf("Config.GetCredential(%s) = %v, want %v", server, got, want) 784 } 785 } 786 787 func TestConfig_DeleteCredential_notExistRecord(t *testing.T) { 788 tempDir := t.TempDir() 789 configPath := filepath.Join(tempDir, "config.json") 790 791 // prepare test content 792 server := "registry1.example.com" 793 cred := auth.Credential{ 794 Username: "username", 795 Password: "password", 796 RefreshToken: "refresh_token", 797 AccessToken: "access_token", 798 } 799 testCfg := configtest.Config{ 800 AuthConfigs: map[string]configtest.AuthConfig{ 801 server: { 802 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 803 IdentityToken: cred.RefreshToken, 804 RegistryToken: cred.AccessToken, 805 }, 806 }, 807 SomeConfigField: 123, 808 } 809 jsonStr, err := json.Marshal(testCfg) 810 if err != nil { 811 t.Fatalf("failed to marshal config: %v", err) 812 } 813 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 814 t.Fatalf("failed to write config file: %v", err) 815 } 816 817 cfg, err := Load(configPath) 818 if err != nil { 819 t.Fatal("Load() error =", err) 820 } 821 // test get 822 got, err := cfg.GetCredential(server) 823 if err != nil { 824 t.Fatalf("Config.GetCredential() error = %v", err) 825 } 826 if want := cred; !reflect.DeepEqual(got, want) { 827 t.Errorf("Config.GetCredential(%s) = %v, want %v", server, got, want) 828 } 829 830 // test delete 831 if err := cfg.DeleteCredential("test.example.com"); err != nil { 832 t.Fatalf("Config.DeleteCredential() error = %v", err) 833 } 834 835 // verify config file 836 configFile, err := os.Open(configPath) 837 if err != nil { 838 t.Fatalf("failed to open config file: %v", err) 839 } 840 defer configFile.Close() 841 var gotTestCfg configtest.Config 842 if err := json.NewDecoder(configFile).Decode(&gotTestCfg); err != nil { 843 t.Fatalf("failed to decode config file: %v", err) 844 } 845 wantTestCfg := configtest.Config{ 846 AuthConfigs: map[string]configtest.AuthConfig{ 847 server: testCfg.AuthConfigs[server], 848 }, 849 SomeConfigField: testCfg.SomeConfigField, 850 } 851 if !reflect.DeepEqual(gotTestCfg, wantTestCfg) { 852 t.Errorf("Decoded config = %v, want %v", gotTestCfg, wantTestCfg) 853 } 854 855 // test get again 856 got, err = cfg.GetCredential(server) 857 if err != nil { 858 t.Fatalf("Config.GetCredential() error = %v", err) 859 } 860 if want := cred; !reflect.DeepEqual(got, want) { 861 t.Errorf("Config.GetCredential(%s) = %v, want %v", server, got, want) 862 } 863 } 864 865 func TestConfig_DeleteCredential_notExistConfig(t *testing.T) { 866 tempDir := t.TempDir() 867 configPath := filepath.Join(tempDir, "config.json") 868 869 cfg, err := Load(configPath) 870 if err != nil { 871 t.Fatal("Load() error =", err) 872 } 873 874 server := "test.example.com" 875 // test delete 876 if err := cfg.DeleteCredential(server); err != nil { 877 t.Fatalf("Config.DeleteCredential() error = %v", err) 878 } 879 880 // verify config file is not created 881 _, err = os.Stat(configPath) 882 if wantErr := os.ErrNotExist; !errors.Is(err, wantErr) { 883 t.Errorf("Stat(%s) error = %v, wantErr %v", configPath, err, wantErr) 884 } 885 } 886 887 func TestConfig_GetCredentialHelper(t *testing.T) { 888 cfg, err := Load("../../testdata/credHelpers_config.json") 889 if err != nil { 890 t.Fatal("Load() error =", err) 891 } 892 893 tests := []struct { 894 name string 895 serverAddress string 896 want string 897 }{ 898 { 899 name: "Get cred helper: registry_helper1", 900 serverAddress: "registry1.example.com", 901 want: "registry1-helper", 902 }, 903 { 904 name: "Get cred helper: registry_helper2", 905 serverAddress: "registry2.example.com", 906 want: "registry2-helper", 907 }, 908 { 909 name: "Empty cred helper configured", 910 serverAddress: "registry3.example.com", 911 want: "", 912 }, 913 { 914 name: "No cred helper configured", 915 serverAddress: "whatever.example.com", 916 want: "", 917 }, 918 } 919 for _, tt := range tests { 920 t.Run(tt.name, func(t *testing.T) { 921 if got := cfg.GetCredentialHelper(tt.serverAddress); got != tt.want { 922 t.Errorf("Config.GetCredentialHelper() = %v, want %v", got, tt.want) 923 } 924 }) 925 } 926 } 927 928 func TestConfig_CredentialsStore(t *testing.T) { 929 tests := []struct { 930 name string 931 configPath string 932 want string 933 }{ 934 { 935 name: "creds store configured", 936 configPath: "../../testdata/credsStore_config.json", 937 want: "teststore", 938 }, 939 { 940 name: "No creds store configured", 941 configPath: "../../testdata/credsHelpers_config.json", 942 want: "", 943 }, 944 } 945 for _, tt := range tests { 946 t.Run(tt.name, func(t *testing.T) { 947 cfg, err := Load(tt.configPath) 948 if err != nil { 949 t.Fatal("Load() error =", err) 950 } 951 if got := cfg.CredentialsStore(); got != tt.want { 952 t.Errorf("Config.CredentialsStore() = %v, want %v", got, tt.want) 953 } 954 }) 955 } 956 } 957 958 func TestConfig_SetCredentialsStore(t *testing.T) { 959 // prepare test content 960 tempDir := t.TempDir() 961 configPath := filepath.Join(tempDir, "config.json") 962 testCfg := configtest.Config{ 963 SomeConfigField: 123, 964 } 965 jsonStr, err := json.Marshal(testCfg) 966 if err != nil { 967 t.Fatalf("failed to marshal config: %v", err) 968 } 969 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 970 t.Fatalf("failed to write config file: %v", err) 971 } 972 973 // test SetCredentialsStore 974 cfg, err := Load(configPath) 975 if err != nil { 976 t.Fatal("Load() error =", err) 977 } 978 credsStore := "testStore" 979 if err := cfg.SetCredentialsStore(credsStore); err != nil { 980 t.Fatal("Config.SetCredentialsStore() error =", err) 981 } 982 983 // verify 984 if got := cfg.credentialsStore; got != credsStore { 985 t.Errorf("Config.credentialsStore = %v, want %v", got, credsStore) 986 } 987 // verify config file 988 configFile, err := os.Open(configPath) 989 if err != nil { 990 t.Fatalf("failed to open config file: %v", err) 991 } 992 var gotTestCfg1 configtest.Config 993 if err := json.NewDecoder(configFile).Decode(&gotTestCfg1); err != nil { 994 t.Fatalf("failed to decode config file: %v", err) 995 } 996 if err := configFile.Close(); err != nil { 997 t.Fatal("failed to close config file:", err) 998 } 999 1000 wantTestCfg1 := configtest.Config{ 1001 AuthConfigs: make(map[string]configtest.AuthConfig), 1002 CredentialsStore: credsStore, 1003 SomeConfigField: testCfg.SomeConfigField, 1004 } 1005 if !reflect.DeepEqual(gotTestCfg1, wantTestCfg1) { 1006 t.Errorf("Decoded config = %v, want %v", gotTestCfg1, wantTestCfg1) 1007 } 1008 1009 // test SetCredentialsStore: set as empty 1010 if err := cfg.SetCredentialsStore(""); err != nil { 1011 t.Fatal("Config.SetCredentialsStore() error =", err) 1012 } 1013 // verify 1014 if got := cfg.credentialsStore; got != "" { 1015 t.Errorf("Config.credentialsStore = %v, want empty", got) 1016 } 1017 // verify config file 1018 configFile, err = os.Open(configPath) 1019 if err != nil { 1020 t.Fatalf("failed to open config file: %v", err) 1021 } 1022 var gotTestCfg2 configtest.Config 1023 if err := json.NewDecoder(configFile).Decode(&gotTestCfg2); err != nil { 1024 t.Fatalf("failed to decode config file: %v", err) 1025 } 1026 if err := configFile.Close(); err != nil { 1027 t.Fatal("failed to close config file:", err) 1028 } 1029 1030 wantTestCfg2 := configtest.Config{ 1031 AuthConfigs: make(map[string]configtest.AuthConfig), 1032 SomeConfigField: testCfg.SomeConfigField, 1033 } 1034 if !reflect.DeepEqual(gotTestCfg2, wantTestCfg2) { 1035 t.Errorf("Decoded config = %v, want %v", gotTestCfg2, wantTestCfg2) 1036 } 1037 } 1038 1039 func TestConfig_IsAuthConfigured(t *testing.T) { 1040 tempDir := t.TempDir() 1041 1042 tests := []struct { 1043 name string 1044 fileName string 1045 shouldCreateFile bool 1046 cfg configtest.Config 1047 want bool 1048 }{ 1049 { 1050 name: "not existing file", 1051 fileName: "config.json", 1052 shouldCreateFile: false, 1053 cfg: configtest.Config{}, 1054 want: false, 1055 }, 1056 { 1057 name: "no auth", 1058 fileName: "config.json", 1059 shouldCreateFile: true, 1060 cfg: configtest.Config{ 1061 SomeConfigField: 123, 1062 }, 1063 want: false, 1064 }, 1065 { 1066 name: "empty auths exist", 1067 fileName: "empty_auths.json", 1068 shouldCreateFile: true, 1069 cfg: configtest.Config{ 1070 AuthConfigs: map[string]configtest.AuthConfig{}, 1071 }, 1072 want: false, 1073 }, 1074 { 1075 name: "auths exist, but no credential", 1076 fileName: "no_cred_auths.json", 1077 shouldCreateFile: true, 1078 cfg: configtest.Config{ 1079 AuthConfigs: map[string]configtest.AuthConfig{ 1080 "test.example.com": {}, 1081 }, 1082 }, 1083 want: true, 1084 }, 1085 { 1086 name: "auths exist", 1087 fileName: "auths.json", 1088 shouldCreateFile: true, 1089 cfg: configtest.Config{ 1090 AuthConfigs: map[string]configtest.AuthConfig{ 1091 "test.example.com": { 1092 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 1093 }, 1094 }, 1095 }, 1096 want: true, 1097 }, 1098 { 1099 name: "credsStore exists", 1100 fileName: "credsStore.json", 1101 shouldCreateFile: true, 1102 cfg: configtest.Config{ 1103 CredentialsStore: "teststore", 1104 }, 1105 want: true, 1106 }, 1107 { 1108 name: "empty credHelpers exist", 1109 fileName: "empty_credsStore.json", 1110 shouldCreateFile: true, 1111 cfg: configtest.Config{ 1112 CredentialHelpers: map[string]string{}, 1113 }, 1114 want: false, 1115 }, 1116 { 1117 name: "credHelpers exist", 1118 fileName: "credsStore.json", 1119 shouldCreateFile: true, 1120 cfg: configtest.Config{ 1121 CredentialHelpers: map[string]string{ 1122 "test.example.com": "testhelper", 1123 }, 1124 }, 1125 want: true, 1126 }, 1127 { 1128 name: "all exist", 1129 fileName: "credsStore.json", 1130 shouldCreateFile: true, 1131 cfg: configtest.Config{ 1132 SomeConfigField: 123, 1133 AuthConfigs: map[string]configtest.AuthConfig{ 1134 "test.example.com": {}, 1135 }, 1136 CredentialsStore: "teststore", 1137 CredentialHelpers: map[string]string{ 1138 "test.example.com": "testhelper", 1139 }, 1140 }, 1141 want: true, 1142 }, 1143 } 1144 for _, tt := range tests { 1145 t.Run(tt.name, func(t *testing.T) { 1146 // prepare test content 1147 configPath := filepath.Join(tempDir, tt.fileName) 1148 if tt.shouldCreateFile { 1149 jsonStr, err := json.Marshal(tt.cfg) 1150 if err != nil { 1151 t.Fatalf("failed to marshal config: %v", err) 1152 } 1153 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 1154 t.Fatalf("failed to write config file: %v", err) 1155 } 1156 } 1157 1158 cfg, err := Load(configPath) 1159 if err != nil { 1160 t.Fatal("Load() error =", err) 1161 } 1162 if got := cfg.IsAuthConfigured(); got != tt.want { 1163 t.Errorf("IsAuthConfigured() = %v, want %v", got, tt.want) 1164 } 1165 }) 1166 } 1167 } 1168 1169 func TestConfig_saveFile(t *testing.T) { 1170 tempDir := t.TempDir() 1171 tests := []struct { 1172 name string 1173 fileName string 1174 shouldCreateFile bool 1175 oldCfg configtest.Config 1176 newCfg configtest.Config 1177 wantCfg configtest.Config 1178 }{ 1179 { 1180 name: "set credsStore in a non-existing file", 1181 fileName: "config.json", 1182 oldCfg: configtest.Config{}, 1183 newCfg: configtest.Config{ 1184 CredentialsStore: "teststore", 1185 }, 1186 wantCfg: configtest.Config{ 1187 AuthConfigs: make(map[string]configtest.AuthConfig), 1188 CredentialsStore: "teststore", 1189 }, 1190 shouldCreateFile: false, 1191 }, 1192 { 1193 name: "set credsStore in empty file", 1194 fileName: "empty.json", 1195 oldCfg: configtest.Config{}, 1196 newCfg: configtest.Config{ 1197 CredentialsStore: "teststore", 1198 }, 1199 wantCfg: configtest.Config{ 1200 AuthConfigs: make(map[string]configtest.AuthConfig), 1201 CredentialsStore: "teststore", 1202 }, 1203 shouldCreateFile: true, 1204 }, 1205 { 1206 name: "set credsStore in a no-auth-configured file", 1207 fileName: "empty.json", 1208 oldCfg: configtest.Config{ 1209 SomeConfigField: 123, 1210 }, 1211 newCfg: configtest.Config{ 1212 CredentialsStore: "teststore", 1213 }, 1214 wantCfg: configtest.Config{ 1215 SomeConfigField: 123, 1216 AuthConfigs: make(map[string]configtest.AuthConfig), 1217 CredentialsStore: "teststore", 1218 }, 1219 shouldCreateFile: true, 1220 }, 1221 { 1222 name: "Set credsStore and credHelpers in an auth-configured file", 1223 fileName: "auth_configured.json", 1224 oldCfg: configtest.Config{ 1225 SomeConfigField: 123, 1226 AuthConfigs: map[string]configtest.AuthConfig{ 1227 "registry1.example.com": { 1228 SomeAuthField: "something", 1229 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 1230 }, 1231 }, 1232 CredentialsStore: "oldstore", 1233 CredentialHelpers: map[string]string{ 1234 "registry2.example.com": "testhelper", 1235 }, 1236 }, 1237 newCfg: configtest.Config{ 1238 AuthConfigs: make(map[string]configtest.AuthConfig), 1239 SomeConfigField: 123, 1240 CredentialsStore: "newstore", 1241 CredentialHelpers: map[string]string{ 1242 "xxx": "yyy", 1243 }, 1244 }, 1245 wantCfg: configtest.Config{ 1246 SomeConfigField: 123, 1247 AuthConfigs: map[string]configtest.AuthConfig{ 1248 "registry1.example.com": { 1249 SomeAuthField: "something", 1250 Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", 1251 }, 1252 }, 1253 CredentialsStore: "newstore", 1254 CredentialHelpers: map[string]string{ 1255 "registry2.example.com": "testhelper", // cred helpers will not be updated 1256 }, 1257 }, 1258 shouldCreateFile: true, 1259 }, 1260 } 1261 for _, tt := range tests { 1262 t.Run(tt.name, func(t *testing.T) { 1263 // prepare test content 1264 configPath := filepath.Join(tempDir, tt.fileName) 1265 if tt.shouldCreateFile { 1266 jsonStr, err := json.Marshal(tt.oldCfg) 1267 if err != nil { 1268 t.Fatalf("failed to marshal config: %v", err) 1269 } 1270 if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { 1271 t.Fatalf("failed to write config file: %v", err) 1272 } 1273 } 1274 1275 cfg, err := Load(configPath) 1276 if err != nil { 1277 t.Fatal("Load() error =", err) 1278 } 1279 cfg.credentialsStore = tt.newCfg.CredentialsStore 1280 cfg.credentialHelpers = tt.newCfg.CredentialHelpers 1281 if err := cfg.saveFile(); err != nil { 1282 t.Fatal("saveFile() error =", err) 1283 } 1284 1285 // verify config file 1286 configFile, err := os.Open(configPath) 1287 if err != nil { 1288 t.Fatalf("failed to open config file: %v", err) 1289 } 1290 defer configFile.Close() 1291 var gotCfg configtest.Config 1292 if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil { 1293 t.Fatalf("failed to decode config file: %v", err) 1294 } 1295 if !reflect.DeepEqual(gotCfg, tt.wantCfg) { 1296 t.Errorf("Decoded config = %v, want %v", gotCfg, tt.wantCfg) 1297 } 1298 }) 1299 } 1300 } 1301 1302 func Test_encodeAuth(t *testing.T) { 1303 tests := []struct { 1304 name string 1305 username string 1306 password string 1307 want string 1308 }{ 1309 { 1310 name: "Username and password", 1311 username: "username", 1312 password: "password", 1313 want: "dXNlcm5hbWU6cGFzc3dvcmQ=", 1314 }, 1315 { 1316 name: "Username only", 1317 username: "username", 1318 password: "", 1319 want: "dXNlcm5hbWU6", 1320 }, 1321 { 1322 name: "Password only", 1323 username: "", 1324 password: "password", 1325 want: "OnBhc3N3b3Jk", 1326 }, 1327 { 1328 name: "Empty username and empty password", 1329 username: "", 1330 password: "", 1331 want: "", 1332 }, 1333 } 1334 for _, tt := range tests { 1335 t.Run(tt.name, func(t *testing.T) { 1336 if got := encodeAuth(tt.username, tt.password); got != tt.want { 1337 t.Errorf("encodeAuth() = %v, want %v", got, tt.want) 1338 } 1339 }) 1340 } 1341 } 1342 1343 func Test_decodeAuth(t *testing.T) { 1344 tests := []struct { 1345 name string 1346 authStr string 1347 username string 1348 password string 1349 wantErr bool 1350 }{ 1351 { 1352 name: "Valid base64", 1353 authStr: "dXNlcm5hbWU6cGFzc3dvcmQ=", // username:password 1354 username: "username", 1355 password: "password", 1356 }, 1357 { 1358 name: "Valid base64, username only", 1359 authStr: "dXNlcm5hbWU6", // username: 1360 username: "username", 1361 }, 1362 { 1363 name: "Valid base64, password only", 1364 authStr: "OnBhc3N3b3Jk", // :password 1365 password: "password", 1366 }, 1367 { 1368 name: "Valid base64, bad format", 1369 authStr: "d2hhdGV2ZXI=", // whatever 1370 username: "", 1371 password: "", 1372 wantErr: true, 1373 }, 1374 { 1375 name: "Invalid base64", 1376 authStr: "whatever", 1377 username: "", 1378 password: "", 1379 wantErr: true, 1380 }, 1381 { 1382 name: "Empty string", 1383 authStr: "", 1384 username: "", 1385 password: "", 1386 wantErr: false, 1387 }, 1388 } 1389 for _, tt := range tests { 1390 t.Run(tt.name, func(t *testing.T) { 1391 gotUsername, gotPassword, err := decodeAuth(tt.authStr) 1392 if (err != nil) != tt.wantErr { 1393 t.Errorf("decodeAuth() error = %v, wantErr %v", err, tt.wantErr) 1394 return 1395 } 1396 if gotUsername != tt.username { 1397 t.Errorf("decodeAuth() got = %v, want %v", gotUsername, tt.username) 1398 } 1399 if gotPassword != tt.password { 1400 t.Errorf("decodeAuth() got1 = %v, want %v", gotPassword, tt.password) 1401 } 1402 }) 1403 } 1404 } 1405 1406 func Test_toHostname(t *testing.T) { 1407 tests := []struct { 1408 name string 1409 addr string 1410 want string 1411 }{ 1412 { 1413 addr: "http://test.example.com", 1414 want: "test.example.com", 1415 }, 1416 { 1417 addr: "http://test.example.com/", 1418 want: "test.example.com", 1419 }, 1420 { 1421 addr: "http://test.example.com/foo/bar", 1422 want: "test.example.com", 1423 }, 1424 { 1425 addr: "https://test.example.com", 1426 want: "test.example.com", 1427 }, 1428 { 1429 addr: "https://test.example.com/", 1430 want: "test.example.com", 1431 }, 1432 { 1433 addr: "http://test.example.com/foo/bar", 1434 want: "test.example.com", 1435 }, 1436 { 1437 addr: "test.example.com", 1438 want: "test.example.com", 1439 }, 1440 { 1441 addr: "test.example.com/foo/bar/", 1442 want: "test.example.com", 1443 }, 1444 } 1445 for _, tt := range tests { 1446 t.Run(tt.name, func(t *testing.T) { 1447 if got := toHostname(tt.addr); got != tt.want { 1448 t.Errorf("toHostname() = %v, want %v", got, tt.want) 1449 } 1450 }) 1451 } 1452 } 1453 1454 func TestConfig_Path(t *testing.T) { 1455 mockedPath := "/path/to/config.json" 1456 config := Config{ 1457 path: mockedPath, 1458 } 1459 if got := config.Path(); got != mockedPath { 1460 t.Errorf("Config.Path() = %v, want %v", got, mockedPath) 1461 } 1462 }