oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/credentials/store_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 credentials
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"errors"
    22  	"os"
    23  	"path/filepath"
    24  	"reflect"
    25  	"testing"
    26  
    27  	"oras.land/oras-go/v2/registry/remote/auth"
    28  	"oras.land/oras-go/v2/registry/remote/credentials/internal/config/configtest"
    29  )
    30  
    31  type badStore struct{}
    32  
    33  var errBadStore = errors.New("bad store!")
    34  
    35  // Get retrieves credentials from the store for the given server address.
    36  func (s *badStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) {
    37  	return auth.EmptyCredential, errBadStore
    38  }
    39  
    40  // Put saves credentials into the store for the given server address.
    41  func (s *badStore) Put(ctx context.Context, serverAddress string, cred auth.Credential) error {
    42  	return errBadStore
    43  }
    44  
    45  // Delete removes credentials from the store for the given server address.
    46  func (s *badStore) Delete(ctx context.Context, serverAddress string) error {
    47  	return errBadStore
    48  }
    49  
    50  func Test_DynamicStore_IsAuthConfigured(t *testing.T) {
    51  	tempDir := t.TempDir()
    52  
    53  	tests := []struct {
    54  		name             string
    55  		fileName         string
    56  		shouldCreateFile bool
    57  		cfg              configtest.Config
    58  		want             bool
    59  	}{
    60  		{
    61  			name:             "not existing file",
    62  			fileName:         "config.json",
    63  			shouldCreateFile: false,
    64  			cfg:              configtest.Config{},
    65  			want:             false,
    66  		},
    67  		{
    68  			name:             "no auth",
    69  			fileName:         "config.json",
    70  			shouldCreateFile: true,
    71  			cfg: configtest.Config{
    72  				SomeConfigField: 123,
    73  			},
    74  			want: false,
    75  		},
    76  		{
    77  			name:             "empty auths exist",
    78  			fileName:         "empty_auths.json",
    79  			shouldCreateFile: true,
    80  			cfg: configtest.Config{
    81  				AuthConfigs: map[string]configtest.AuthConfig{},
    82  			},
    83  			want: false,
    84  		},
    85  		{
    86  			name:             "auths exist, but no credential",
    87  			fileName:         "no_cred_auths.json",
    88  			shouldCreateFile: true,
    89  			cfg: configtest.Config{
    90  				AuthConfigs: map[string]configtest.AuthConfig{
    91  					"test.example.com": {},
    92  				},
    93  			},
    94  			want: true,
    95  		},
    96  		{
    97  			name:             "auths exist",
    98  			fileName:         "auths.json",
    99  			shouldCreateFile: true,
   100  			cfg: configtest.Config{
   101  				AuthConfigs: map[string]configtest.AuthConfig{
   102  					"test.example.com": {
   103  						Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=",
   104  					},
   105  				},
   106  			},
   107  			want: true,
   108  		},
   109  		{
   110  			name:             "credsStore exists",
   111  			fileName:         "credsStore.json",
   112  			shouldCreateFile: true,
   113  			cfg: configtest.Config{
   114  				CredentialsStore: "teststore",
   115  			},
   116  			want: true,
   117  		},
   118  		{
   119  			name:             "empty credHelpers exist",
   120  			fileName:         "empty_credsStore.json",
   121  			shouldCreateFile: true,
   122  			cfg: configtest.Config{
   123  				CredentialHelpers: map[string]string{},
   124  			},
   125  			want: false,
   126  		},
   127  		{
   128  			name:             "credHelpers exist",
   129  			fileName:         "credsStore.json",
   130  			shouldCreateFile: true,
   131  			cfg: configtest.Config{
   132  				CredentialHelpers: map[string]string{
   133  					"test.example.com": "testhelper",
   134  				},
   135  			},
   136  			want: true,
   137  		},
   138  		{
   139  			name:             "all exist",
   140  			fileName:         "credsStore.json",
   141  			shouldCreateFile: true,
   142  			cfg: configtest.Config{
   143  				SomeConfigField: 123,
   144  				AuthConfigs: map[string]configtest.AuthConfig{
   145  					"test.example.com": {},
   146  				},
   147  				CredentialsStore: "teststore",
   148  				CredentialHelpers: map[string]string{
   149  					"test.example.com": "testhelper",
   150  				},
   151  			},
   152  			want: true,
   153  		},
   154  	}
   155  	for _, tt := range tests {
   156  		t.Run(tt.name, func(t *testing.T) {
   157  			// prepare test content
   158  			configPath := filepath.Join(tempDir, tt.fileName)
   159  			if tt.shouldCreateFile {
   160  				jsonStr, err := json.Marshal(tt.cfg)
   161  				if err != nil {
   162  					t.Fatalf("failed to marshal config: %v", err)
   163  				}
   164  				if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   165  					t.Fatalf("failed to write config file: %v", err)
   166  				}
   167  			}
   168  
   169  			ds, err := NewStore(configPath, StoreOptions{})
   170  			if err != nil {
   171  				t.Fatal("newStore() error =", err)
   172  			}
   173  			if got := ds.IsAuthConfigured(); got != tt.want {
   174  				t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", got, tt.want)
   175  			}
   176  		})
   177  	}
   178  }
   179  
   180  func Test_DynamicStore_authConfigured(t *testing.T) {
   181  	// prepare test content
   182  	tempDir := t.TempDir()
   183  	configPath := filepath.Join(tempDir, "auth_configured.json")
   184  	config := configtest.Config{
   185  		AuthConfigs: map[string]configtest.AuthConfig{
   186  			"xxx": {},
   187  		},
   188  		SomeConfigField: 123,
   189  	}
   190  	jsonStr, err := json.Marshal(config)
   191  	if err != nil {
   192  		t.Fatalf("failed to marshal config: %v", err)
   193  	}
   194  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   195  		t.Fatalf("failed to write config file: %v", err)
   196  	}
   197  
   198  	ds, err := NewStore(configPath, StoreOptions{AllowPlaintextPut: true})
   199  	if err != nil {
   200  		t.Fatal("NewStore() error =", err)
   201  	}
   202  
   203  	// test IsAuthConfigured
   204  	authConfigured := ds.IsAuthConfigured()
   205  	if want := true; authConfigured != want {
   206  		t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want)
   207  	}
   208  
   209  	serverAddr := "test.example.com"
   210  	cred := auth.Credential{
   211  		Username: "username",
   212  		Password: "password",
   213  	}
   214  	ctx := context.Background()
   215  
   216  	// test put
   217  	if err := ds.Put(ctx, serverAddr, cred); err != nil {
   218  		t.Fatal("DynamicStore.Get() error =", err)
   219  	}
   220  	// Put() should not set detected store back to config
   221  	if got := ds.detectedCredsStore; got != "" {
   222  		t.Errorf("ds.detectedCredsStore = %v, want empty", got)
   223  	}
   224  	if got := ds.config.CredentialsStore(); got != "" {
   225  		t.Errorf("ds.config.CredentialsStore() = %v, want empty", got)
   226  	}
   227  
   228  	// test get
   229  	got, err := ds.Get(ctx, serverAddr)
   230  	if err != nil {
   231  		t.Fatal("DynamicStore.Get() error =", err)
   232  	}
   233  	if want := cred; got != want {
   234  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   235  	}
   236  
   237  	// test delete
   238  	err = ds.Delete(ctx, serverAddr)
   239  	if err != nil {
   240  		t.Fatal("DynamicStore.Delete() error =", err)
   241  	}
   242  
   243  	// verify delete
   244  	got, err = ds.Get(ctx, serverAddr)
   245  	if err != nil {
   246  		t.Fatal("DynamicStore.Get() error =", err)
   247  	}
   248  	if want := auth.EmptyCredential; got != want {
   249  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   250  	}
   251  }
   252  
   253  func Test_DynamicStore_authConfigured_DetectDefaultNativeStore(t *testing.T) {
   254  	// prepare test content
   255  	tempDir := t.TempDir()
   256  	configPath := filepath.Join(tempDir, "auth_configured.json")
   257  	config := configtest.Config{
   258  		AuthConfigs: map[string]configtest.AuthConfig{
   259  			"xxx": {},
   260  		},
   261  		SomeConfigField: 123,
   262  	}
   263  	jsonStr, err := json.Marshal(config)
   264  	if err != nil {
   265  		t.Fatalf("failed to marshal config: %v", err)
   266  	}
   267  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   268  		t.Fatalf("failed to write config file: %v", err)
   269  	}
   270  
   271  	opts := StoreOptions{
   272  		AllowPlaintextPut:        true,
   273  		DetectDefaultNativeStore: true,
   274  	}
   275  	ds, err := NewStore(configPath, opts)
   276  	if err != nil {
   277  		t.Fatal("NewStore() error =", err)
   278  	}
   279  
   280  	// test IsAuthConfigured
   281  	authConfigured := ds.IsAuthConfigured()
   282  	if want := true; authConfigured != want {
   283  		t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want)
   284  	}
   285  
   286  	serverAddr := "test.example.com"
   287  	cred := auth.Credential{
   288  		Username: "username",
   289  		Password: "password",
   290  	}
   291  	ctx := context.Background()
   292  
   293  	// test put
   294  	if err := ds.Put(ctx, serverAddr, cred); err != nil {
   295  		t.Fatal("DynamicStore.Get() error =", err)
   296  	}
   297  	// Put() should not set detected store back to config
   298  	if got := ds.detectedCredsStore; got != "" {
   299  		t.Errorf("ds.detectedCredsStore = %v, want empty", got)
   300  	}
   301  	if got := ds.config.CredentialsStore(); got != "" {
   302  		t.Errorf("ds.config.CredentialsStore() = %v, want empty", got)
   303  	}
   304  
   305  	// test get
   306  	got, err := ds.Get(ctx, serverAddr)
   307  	if err != nil {
   308  		t.Fatal("DynamicStore.Get() error =", err)
   309  	}
   310  	if want := cred; got != want {
   311  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   312  	}
   313  
   314  	// test delete
   315  	err = ds.Delete(ctx, serverAddr)
   316  	if err != nil {
   317  		t.Fatal("DynamicStore.Delete() error =", err)
   318  	}
   319  
   320  	// verify delete
   321  	got, err = ds.Get(ctx, serverAddr)
   322  	if err != nil {
   323  		t.Fatal("DynamicStore.Get() error =", err)
   324  	}
   325  	if want := auth.EmptyCredential; got != want {
   326  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   327  	}
   328  }
   329  
   330  func Test_DynamicStore_noAuthConfigured(t *testing.T) {
   331  	// prepare test content
   332  	tempDir := t.TempDir()
   333  	configPath := filepath.Join(tempDir, "no_auth_configured.json")
   334  	cfg := configtest.Config{
   335  		SomeConfigField: 123,
   336  	}
   337  	jsonStr, err := json.Marshal(cfg)
   338  	if err != nil {
   339  		t.Fatalf("failed to marshal config: %v", err)
   340  	}
   341  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   342  		t.Fatalf("failed to write config file: %v", err)
   343  	}
   344  
   345  	ds, err := NewStore(configPath, StoreOptions{AllowPlaintextPut: true})
   346  	if err != nil {
   347  		t.Fatal("NewStore() error =", err)
   348  	}
   349  
   350  	// test IsAuthConfigured
   351  	authConfigured := ds.IsAuthConfigured()
   352  	if want := false; authConfigured != want {
   353  		t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want)
   354  	}
   355  
   356  	serverAddr := "test.example.com"
   357  	cred := auth.Credential{
   358  		Username: "username",
   359  		Password: "password",
   360  	}
   361  	ctx := context.Background()
   362  
   363  	// Get() should not set detected store back to config
   364  	if _, err := ds.Get(ctx, serverAddr); err != nil {
   365  		t.Fatal("DynamicStore.Get() error =", err)
   366  	}
   367  
   368  	// test put
   369  	if err := ds.Put(ctx, serverAddr, cred); err != nil {
   370  		t.Fatal("DynamicStore.Put() error =", err)
   371  	}
   372  	// Put() should not set detected store back to config
   373  	if got := ds.detectedCredsStore; got != "" {
   374  		t.Errorf("ds.detectedCredsStore = %v, want empty", got)
   375  	}
   376  	if got := ds.config.CredentialsStore(); got != "" {
   377  		t.Errorf("ds.config.CredentialsStore() = %v, want empty", got)
   378  	}
   379  
   380  	// test get
   381  	got, err := ds.Get(ctx, serverAddr)
   382  	if err != nil {
   383  		t.Fatal("DynamicStore.Get() error =", err)
   384  	}
   385  	if want := cred; got != want {
   386  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   387  	}
   388  
   389  	// test delete
   390  	err = ds.Delete(ctx, serverAddr)
   391  	if err != nil {
   392  		t.Fatal("DynamicStore.Delete() error =", err)
   393  	}
   394  
   395  	// verify delete
   396  	got, err = ds.Get(ctx, serverAddr)
   397  	if err != nil {
   398  		t.Fatal("DynamicStore.Get() error =", err)
   399  	}
   400  	if want := auth.EmptyCredential; got != want {
   401  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   402  	}
   403  }
   404  
   405  func Test_DynamicStore_noAuthConfigured_DetectDefaultNativeStore(t *testing.T) {
   406  	// prepare test content
   407  	tempDir := t.TempDir()
   408  	configPath := filepath.Join(tempDir, "no_auth_configured.json")
   409  	cfg := configtest.Config{
   410  		SomeConfigField: 123,
   411  	}
   412  	jsonStr, err := json.Marshal(cfg)
   413  	if err != nil {
   414  		t.Fatalf("failed to marshal config: %v", err)
   415  	}
   416  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   417  		t.Fatalf("failed to write config file: %v", err)
   418  	}
   419  
   420  	opts := StoreOptions{
   421  		AllowPlaintextPut:        true,
   422  		DetectDefaultNativeStore: true,
   423  	}
   424  	ds, err := NewStore(configPath, opts)
   425  	if err != nil {
   426  		t.Fatal("NewStore() error =", err)
   427  	}
   428  
   429  	// test IsAuthConfigured
   430  	authConfigured := ds.IsAuthConfigured()
   431  	if want := false; authConfigured != want {
   432  		t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want)
   433  	}
   434  
   435  	serverAddr := "test.example.com"
   436  	cred := auth.Credential{
   437  		Username: "username",
   438  		Password: "password",
   439  	}
   440  	ctx := context.Background()
   441  
   442  	// Get() should set detectedCredsStore only, but should not save it back to config
   443  	if _, err := ds.Get(ctx, serverAddr); err != nil {
   444  		t.Fatal("DynamicStore.Get() error =", err)
   445  	}
   446  	if defaultStore := getDefaultHelperSuffix(); defaultStore != "" {
   447  		if got := ds.detectedCredsStore; got != defaultStore {
   448  			t.Errorf("ds.detectedCredsStore = %v, want %v", got, defaultStore)
   449  		}
   450  	}
   451  	if got := ds.config.CredentialsStore(); got != "" {
   452  		t.Errorf("ds.config.CredentialsStore() = %v, want empty", got)
   453  	}
   454  
   455  	// test put
   456  	if err := ds.Put(ctx, serverAddr, cred); err != nil {
   457  		t.Fatal("DynamicStore.Put() error =", err)
   458  	}
   459  
   460  	// Put() should set the detected store back to config
   461  	if got := ds.config.CredentialsStore(); got != ds.detectedCredsStore {
   462  		t.Errorf("ds.config.CredentialsStore() = %v, want %v", got, ds.detectedCredsStore)
   463  	}
   464  
   465  	// test get
   466  	got, err := ds.Get(ctx, serverAddr)
   467  	if err != nil {
   468  		t.Fatal("DynamicStore.Get() error =", err)
   469  	}
   470  	if want := cred; got != want {
   471  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   472  	}
   473  
   474  	// test delete
   475  	err = ds.Delete(ctx, serverAddr)
   476  	if err != nil {
   477  		t.Fatal("DynamicStore.Delete() error =", err)
   478  	}
   479  
   480  	// verify delete
   481  	got, err = ds.Get(ctx, serverAddr)
   482  	if err != nil {
   483  		t.Fatal("DynamicStore.Get() error =", err)
   484  	}
   485  	if want := auth.EmptyCredential; got != want {
   486  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   487  	}
   488  }
   489  
   490  func Test_DynamicStore_fileStore_AllowPlainTextPut(t *testing.T) {
   491  	// prepare test content
   492  	tempDir := t.TempDir()
   493  	configPath := filepath.Join(tempDir, "config.json")
   494  	serverAddr := "newtest.example.com"
   495  	cred := auth.Credential{
   496  		Username: "username",
   497  		Password: "password",
   498  	}
   499  	ctx := context.Background()
   500  
   501  	cfg := configtest.Config{
   502  		AuthConfigs: map[string]configtest.AuthConfig{
   503  			"test.example.com": {},
   504  		},
   505  		SomeConfigField: 123,
   506  	}
   507  	jsonStr, err := json.Marshal(cfg)
   508  	if err != nil {
   509  		t.Fatalf("failed to marshal config: %v", err)
   510  	}
   511  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   512  		t.Fatalf("failed to write config file: %v", err)
   513  	}
   514  
   515  	// test default option
   516  	ds, err := NewStore(configPath, StoreOptions{})
   517  	if err != nil {
   518  		t.Fatal("NewStore() error =", err)
   519  	}
   520  	err = ds.Put(ctx, serverAddr, cred)
   521  	if wantErr := ErrPlaintextPutDisabled; !errors.Is(err, wantErr) {
   522  		t.Errorf("DynamicStore.Put() error = %v, wantErr %v", err, wantErr)
   523  	}
   524  
   525  	// test AllowPlainTextPut = true
   526  	ds, err = NewStore(configPath, StoreOptions{AllowPlaintextPut: true})
   527  	if err != nil {
   528  		t.Fatal("NewStore() error =", err)
   529  	}
   530  	if err := ds.Put(ctx, serverAddr, cred); err != nil {
   531  		t.Error("DynamicStore.Put() error =", err)
   532  	}
   533  
   534  	// verify config file
   535  	configFile, err := os.Open(configPath)
   536  	if err != nil {
   537  		t.Fatalf("failed to open config file: %v", err)
   538  	}
   539  	defer configFile.Close()
   540  	var gotCfg configtest.Config
   541  	if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil {
   542  		t.Fatalf("failed to decode config file: %v", err)
   543  	}
   544  	wantCfg := configtest.Config{
   545  		AuthConfigs: map[string]configtest.AuthConfig{
   546  			"test.example.com": {},
   547  			serverAddr: {
   548  				Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=",
   549  			},
   550  		},
   551  		SomeConfigField: cfg.SomeConfigField,
   552  	}
   553  	if !reflect.DeepEqual(gotCfg, wantCfg) {
   554  		t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg)
   555  	}
   556  }
   557  
   558  func Test_DynamicStore_getHelperSuffix(t *testing.T) {
   559  	tests := []struct {
   560  		name          string
   561  		configPath    string
   562  		serverAddress string
   563  		want          string
   564  	}{
   565  		{
   566  			name:          "Get cred helper: registry_helper1",
   567  			configPath:    "testdata/credHelpers_config.json",
   568  			serverAddress: "registry1.example.com",
   569  			want:          "registry1-helper",
   570  		},
   571  		{
   572  			name:          "Get cred helper: registry_helper2",
   573  			configPath:    "testdata/credHelpers_config.json",
   574  			serverAddress: "registry2.example.com",
   575  			want:          "registry2-helper",
   576  		},
   577  		{
   578  			name:          "Empty cred helper configured",
   579  			configPath:    "testdata/credHelpers_config.json",
   580  			serverAddress: "registry3.example.com",
   581  			want:          "",
   582  		},
   583  		{
   584  			name:          "No cred helper and creds store configured",
   585  			configPath:    "testdata/credHelpers_config.json",
   586  			serverAddress: "whatever.example.com",
   587  			want:          "",
   588  		},
   589  		{
   590  			name:          "Choose cred helper over creds store",
   591  			configPath:    "testdata/credsStore_config.json",
   592  			serverAddress: "test.example.com",
   593  			want:          "test-helper",
   594  		},
   595  		{
   596  			name:          "No cred helper configured, choose cred store",
   597  			configPath:    "testdata/credsStore_config.json",
   598  			serverAddress: "whatever.example.com",
   599  			want:          "teststore",
   600  		},
   601  	}
   602  	for _, tt := range tests {
   603  		t.Run(tt.name, func(t *testing.T) {
   604  			ds, err := NewStore(tt.configPath, StoreOptions{})
   605  			if err != nil {
   606  				t.Fatal("NewStore() error =", err)
   607  			}
   608  			if got := ds.getHelperSuffix(tt.serverAddress); got != tt.want {
   609  				t.Errorf("DynamicStore.getHelperSuffix() = %v, want %v", got, tt.want)
   610  			}
   611  		})
   612  	}
   613  }
   614  
   615  func Test_DynamicStore_ConfigPath(t *testing.T) {
   616  	path := "../../testdata/credsStore_config.json"
   617  	var err error
   618  	store, err := NewStore(path, StoreOptions{})
   619  	if err != nil {
   620  		t.Fatal("NewFileStore() error =", err)
   621  	}
   622  	got := store.ConfigPath()
   623  	if got != path {
   624  		t.Errorf("Config.GetPath() = %v, want %v", got, path)
   625  	}
   626  }
   627  
   628  func Test_DynamicStore_getStore_nativeStore(t *testing.T) {
   629  	tests := []struct {
   630  		name          string
   631  		configPath    string
   632  		serverAddress string
   633  	}{
   634  		{
   635  			name:          "Cred helper configured for registry1.example.com",
   636  			configPath:    "testdata/credHelpers_config.json",
   637  			serverAddress: "registry1.example.com",
   638  		},
   639  		{
   640  			name:          "Cred helper configured for registry2.example.com",
   641  			configPath:    "testdata/credHelpers_config.json",
   642  			serverAddress: "registry2.example.com",
   643  		},
   644  		{
   645  			name:          "Cred helper configured for test.example.com",
   646  			configPath:    "testdata/credsStore_config.json",
   647  			serverAddress: "test.example.com",
   648  		},
   649  		{
   650  			name:          "No cred helper configured, use creds store",
   651  			configPath:    "testdata/credsStore_config.json",
   652  			serverAddress: "whaterver.example.com",
   653  		},
   654  	}
   655  	for _, tt := range tests {
   656  		t.Run(tt.name, func(t *testing.T) {
   657  			ds, err := NewStore(tt.configPath, StoreOptions{})
   658  			if err != nil {
   659  				t.Fatal("NewStore() error =", err)
   660  			}
   661  			gotStore := ds.getStore(tt.serverAddress)
   662  			if _, ok := gotStore.(*nativeStore); !ok {
   663  				t.Errorf("gotStore is not a native store")
   664  			}
   665  		})
   666  	}
   667  }
   668  
   669  func Test_DynamicStore_getStore_fileStore(t *testing.T) {
   670  	tests := []struct {
   671  		name          string
   672  		configPath    string
   673  		serverAddress string
   674  	}{
   675  		{
   676  			name:          "Empty cred helper configured for registry3.example.com",
   677  			configPath:    "testdata/credHelpers_config.json",
   678  			serverAddress: "registry3.example.com",
   679  		},
   680  		{
   681  			name:          "No cred helper configured",
   682  			configPath:    "testdata/credHelpers_config.json",
   683  			serverAddress: "whatever.example.com",
   684  		},
   685  	}
   686  	for _, tt := range tests {
   687  		t.Run(tt.name, func(t *testing.T) {
   688  			ds, err := NewStore(tt.configPath, StoreOptions{})
   689  			if err != nil {
   690  				t.Fatal("NewStore() error =", err)
   691  			}
   692  			gotStore := ds.getStore(tt.serverAddress)
   693  			gotFS1, ok := gotStore.(*FileStore)
   694  			if !ok {
   695  				t.Errorf("gotStore is not a file store")
   696  			}
   697  
   698  			// get again, the two file stores should be based on the same config instance
   699  			gotStore = ds.getStore(tt.serverAddress)
   700  			gotFS2, ok := gotStore.(*FileStore)
   701  			if !ok {
   702  				t.Errorf("gotStore is not a file store")
   703  			}
   704  			if gotFS1.config != gotFS2.config {
   705  				t.Errorf("gotFS1 and gotFS2 are not based on the same config")
   706  			}
   707  		})
   708  	}
   709  }
   710  
   711  func Test_storeWithFallbacks_Get(t *testing.T) {
   712  	// prepare test content
   713  	server1 := "foo.registry.com"
   714  	cred1 := auth.Credential{
   715  		Username: "username",
   716  		Password: "password",
   717  	}
   718  	server2 := "bar.registry.com"
   719  	cred2 := auth.Credential{
   720  		RefreshToken: "identity_token",
   721  	}
   722  
   723  	primaryStore := &testStore{}
   724  	fallbackStore1 := &testStore{
   725  		storage: map[string]auth.Credential{
   726  			server1: cred1,
   727  		},
   728  	}
   729  	fallbackStore2 := &testStore{
   730  		storage: map[string]auth.Credential{
   731  			server2: cred2,
   732  		},
   733  	}
   734  	sf := NewStoreWithFallbacks(primaryStore, fallbackStore1, fallbackStore2)
   735  	ctx := context.Background()
   736  
   737  	// test Get()
   738  	got1, err := sf.Get(ctx, server1)
   739  	if err != nil {
   740  		t.Fatalf("storeWithFallbacks.Get(%s) error = %v", server1, err)
   741  	}
   742  	if want := cred1; got1 != cred1 {
   743  		t.Errorf("storeWithFallbacks.Get(%s) = %v, want %v", server1, got1, want)
   744  	}
   745  	got2, err := sf.Get(ctx, server2)
   746  	if err != nil {
   747  		t.Fatalf("storeWithFallbacks.Get(%s) error = %v", server2, err)
   748  	}
   749  	if want := cred2; got2 != cred2 {
   750  		t.Errorf("storeWithFallbacks.Get(%s) = %v, want %v", server2, got2, want)
   751  	}
   752  
   753  	// test Get(): no credential found
   754  	got, err := sf.Get(ctx, "whaterver")
   755  	if err != nil {
   756  		t.Fatal("storeWithFallbacks.Get() error =", err)
   757  	}
   758  	if want := auth.EmptyCredential; got != want {
   759  		t.Errorf("storeWithFallbacks.Get() = %v, want %v", got, want)
   760  	}
   761  }
   762  
   763  func Test_storeWithFallbacks_Get_throwError(t *testing.T) {
   764  	badStore := &badStore{}
   765  	goodStore := &testStore{}
   766  	sf := NewStoreWithFallbacks(badStore, goodStore)
   767  	ctx := context.Background()
   768  
   769  	// test Get(): should throw error
   770  	_, err := sf.Get(ctx, "whatever")
   771  	if wantErr := errBadStore; !errors.Is(err, wantErr) {
   772  		t.Errorf("storeWithFallback.Get() error = %v, wantErr %v", err, wantErr)
   773  	}
   774  }
   775  
   776  func Test_storeWithFallbacks_Put(t *testing.T) {
   777  	// prepare test content
   778  	cfg := configtest.Config{
   779  		SomeConfigField: 123,
   780  	}
   781  	jsonStr, err := json.Marshal(cfg)
   782  	if err != nil {
   783  		t.Fatalf("failed to marshal config: %v", err)
   784  	}
   785  	tempDir := t.TempDir()
   786  	configPath := filepath.Join(tempDir, "no_auth_configured.json")
   787  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   788  		t.Fatalf("failed to write config file: %v", err)
   789  	}
   790  	opts := StoreOptions{
   791  		AllowPlaintextPut: true,
   792  	}
   793  	primaryStore, err := NewStore(configPath, opts) // plaintext enabled
   794  	if err != nil {
   795  		t.Fatalf("NewStore(%s) error = %v", configPath, err)
   796  	}
   797  	badStore := &badStore{} // bad store
   798  	sf := NewStoreWithFallbacks(primaryStore, badStore)
   799  	ctx := context.Background()
   800  
   801  	server := "example.registry.com"
   802  	cred := auth.Credential{
   803  		Username: "username",
   804  		Password: "password",
   805  	}
   806  	// test Put()
   807  	if err := sf.Put(ctx, server, cred); err != nil {
   808  		t.Fatal("storeWithFallbacks.Put() error =", err)
   809  	}
   810  	// verify Get()
   811  	got, err := sf.Get(ctx, server)
   812  	if err != nil {
   813  		t.Fatal("storeWithFallbacks.Get() error =", err)
   814  	}
   815  	if want := cred; got != want {
   816  		t.Errorf("storeWithFallbacks.Get() = %v, want %v", got, want)
   817  	}
   818  }
   819  
   820  func Test_storeWithFallbacks_Put_throwError(t *testing.T) {
   821  	badStore := &badStore{}
   822  	goodStore := &testStore{}
   823  	sf := NewStoreWithFallbacks(badStore, goodStore)
   824  	ctx := context.Background()
   825  
   826  	// test Put(): should thrown error
   827  	err := sf.Put(ctx, "whatever", auth.Credential{})
   828  	if wantErr := errBadStore; !errors.Is(err, wantErr) {
   829  		t.Errorf("storeWithFallback.Put() error = %v, wantErr %v", err, wantErr)
   830  	}
   831  }
   832  
   833  func Test_storeWithFallbacks_Delete(t *testing.T) {
   834  	// prepare test content
   835  	server1 := "foo.registry.com"
   836  	cred1 := auth.Credential{
   837  		Username: "username",
   838  		Password: "password",
   839  	}
   840  	server2 := "bar.registry.com"
   841  	cred2 := auth.Credential{
   842  		RefreshToken: "identity_token",
   843  	}
   844  
   845  	primaryStore := &testStore{
   846  		storage: map[string]auth.Credential{
   847  			server1: cred1,
   848  			server2: cred2,
   849  		},
   850  	}
   851  	badStore := &badStore{}
   852  	sf := NewStoreWithFallbacks(primaryStore, badStore)
   853  	ctx := context.Background()
   854  
   855  	// test Delete(): server1
   856  	if err := sf.Delete(ctx, server1); err != nil {
   857  		t.Fatal("storeWithFallback.Delete()")
   858  	}
   859  	// verify primary store
   860  	if want := map[string]auth.Credential{server2: cred2}; !reflect.DeepEqual(primaryStore.storage, want) {
   861  		t.Errorf("primaryStore.storage = %v, want %v", primaryStore.storage, want)
   862  	}
   863  
   864  	// test Delete(): server2
   865  	if err := sf.Delete(ctx, server2); err != nil {
   866  		t.Fatal("storeWithFallback.Delete()")
   867  	}
   868  	// verify primary store
   869  	if want := map[string]auth.Credential{}; !reflect.DeepEqual(primaryStore.storage, want) {
   870  		t.Errorf("primaryStore.storage = %v, want %v", primaryStore.storage, want)
   871  	}
   872  }
   873  
   874  func Test_storeWithFallbacks_Delete_throwError(t *testing.T) {
   875  	badStore := &badStore{}
   876  	goodStore := &testStore{}
   877  	sf := NewStoreWithFallbacks(badStore, goodStore)
   878  	ctx := context.Background()
   879  
   880  	// test Delete(): should throw error
   881  	err := sf.Delete(ctx, "whatever")
   882  	if wantErr := errBadStore; !errors.Is(err, wantErr) {
   883  		t.Errorf("storeWithFallback.Delete() error = %v, wantErr %v", err, wantErr)
   884  	}
   885  }
   886  
   887  func Test_getDockerConfigPath_env(t *testing.T) {
   888  	dir, err := os.Getwd()
   889  	if err != nil {
   890  		t.Fatal("os.Getwd() error =", err)
   891  	}
   892  	t.Setenv("DOCKER_CONFIG", dir)
   893  
   894  	got, err := getDockerConfigPath()
   895  	if err != nil {
   896  		t.Fatal("getDockerConfigPath() error =", err)
   897  	}
   898  	if want := filepath.Join(dir, "config.json"); got != want {
   899  		t.Errorf("getDockerConfigPath() = %v, want %v", got, want)
   900  	}
   901  }
   902  
   903  func Test_getDockerConfigPath_homeDir(t *testing.T) {
   904  	t.Setenv("DOCKER_CONFIG", "")
   905  
   906  	got, err := getDockerConfigPath()
   907  	if err != nil {
   908  		t.Fatal("getDockerConfigPath() error =", err)
   909  	}
   910  	homeDir, err := os.UserHomeDir()
   911  	if err != nil {
   912  		t.Fatal("os.UserHomeDir()")
   913  	}
   914  	if want := filepath.Join(homeDir, ".docker", "config.json"); got != want {
   915  		t.Errorf("getDockerConfigPath() = %v, want %v", got, want)
   916  	}
   917  }
   918  
   919  func TestNewStoreFromDocker(t *testing.T) {
   920  	// prepare test content
   921  	tempDir := t.TempDir()
   922  	configPath := filepath.Join(tempDir, "config.json")
   923  	t.Setenv("DOCKER_CONFIG", tempDir)
   924  
   925  	serverAddr1 := "test.example.com"
   926  	cred1 := auth.Credential{
   927  		Username: "foo",
   928  		Password: "bar",
   929  	}
   930  	config := configtest.Config{
   931  		AuthConfigs: map[string]configtest.AuthConfig{
   932  			serverAddr1: {
   933  				Auth: "Zm9vOmJhcg==",
   934  			},
   935  		},
   936  		SomeConfigField: 123,
   937  	}
   938  	jsonStr, err := json.Marshal(config)
   939  	if err != nil {
   940  		t.Fatalf("failed to marshal config: %v", err)
   941  	}
   942  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   943  		t.Fatalf("failed to write config file: %v", err)
   944  	}
   945  
   946  	ctx := context.Background()
   947  
   948  	ds, err := NewStoreFromDocker(StoreOptions{AllowPlaintextPut: true})
   949  	if err != nil {
   950  		t.Fatal("NewStoreFromDocker() error =", err)
   951  	}
   952  
   953  	// test getting an existing credential
   954  	got, err := ds.Get(ctx, serverAddr1)
   955  	if err != nil {
   956  		t.Fatal("DynamicStore.Get() error =", err)
   957  	}
   958  	if want := cred1; got != want {
   959  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   960  	}
   961  
   962  	// test putting a new credential
   963  	serverAddr2 := "newtest.example.com"
   964  	cred2 := auth.Credential{
   965  		Username: "username",
   966  		Password: "password",
   967  	}
   968  	if err := ds.Put(ctx, serverAddr2, cred2); err != nil {
   969  		t.Fatal("DynamicStore.Get() error =", err)
   970  	}
   971  
   972  	// test getting the new credential
   973  	got, err = ds.Get(ctx, serverAddr2)
   974  	if err != nil {
   975  		t.Fatal("DynamicStore.Get() error =", err)
   976  	}
   977  	if want := cred2; got != want {
   978  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   979  	}
   980  
   981  	// test deleting the old credential
   982  	err = ds.Delete(ctx, serverAddr1)
   983  	if err != nil {
   984  		t.Fatal("DynamicStore.Delete() error =", err)
   985  	}
   986  
   987  	// verify delete
   988  	got, err = ds.Get(ctx, serverAddr1)
   989  	if err != nil {
   990  		t.Fatal("DynamicStore.Get() error =", err)
   991  	}
   992  	if want := auth.EmptyCredential; got != want {
   993  		t.Errorf("DynamicStore.Get() = %v, want %v", got, want)
   994  	}
   995  }