github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/registry/remote/credentials/file_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  func TestNewFileStore_badPath(t *testing.T) {
    32  	tempDir := t.TempDir()
    33  
    34  	tests := []struct {
    35  		name       string
    36  		configPath string
    37  		wantErr    bool
    38  	}{
    39  		{
    40  			name:       "Path is a directory",
    41  			configPath: tempDir,
    42  			wantErr:    true,
    43  		},
    44  		{
    45  			name:       "Empty file name",
    46  			configPath: filepath.Join(tempDir, ""),
    47  			wantErr:    true,
    48  		},
    49  	}
    50  	for _, tt := range tests {
    51  		t.Run(tt.name, func(t *testing.T) {
    52  			_, err := NewFileStore(tt.configPath)
    53  			if (err != nil) != tt.wantErr {
    54  				t.Errorf("NewFileStore() error = %v, wantErr %v", err, tt.wantErr)
    55  				return
    56  			}
    57  		})
    58  	}
    59  }
    60  
    61  func TestNewFileStore_badFormat(t *testing.T) {
    62  	tests := []struct {
    63  		name       string
    64  		configPath string
    65  		wantErr    bool
    66  	}{
    67  		{
    68  			name:       "Bad JSON format",
    69  			configPath: "testdata/bad_config",
    70  			wantErr:    true,
    71  		},
    72  		{
    73  			name:       "Invalid auths format",
    74  			configPath: "testdata/invalid_auths_config.json",
    75  			wantErr:    true,
    76  		},
    77  		{
    78  			name:       "No auths field",
    79  			configPath: "testdata/no_auths_config.json",
    80  			wantErr:    false,
    81  		},
    82  	}
    83  	for _, tt := range tests {
    84  		t.Run(tt.name, func(t *testing.T) {
    85  			_, err := NewFileStore(tt.configPath)
    86  			if (err != nil) != tt.wantErr {
    87  				t.Errorf("NewFileStore() error = %v, wantErr %v", err, tt.wantErr)
    88  				return
    89  			}
    90  		})
    91  	}
    92  }
    93  
    94  func TestFileStore_Get_validConfig(t *testing.T) {
    95  	ctx := context.Background()
    96  	fs, err := NewFileStore("testdata/valid_auths_config.json")
    97  	if err != nil {
    98  		t.Fatal("NewFileStore() error =", err)
    99  	}
   100  
   101  	tests := []struct {
   102  		name          string
   103  		serverAddress string
   104  		want          auth.Credential
   105  		wantErr       bool
   106  	}{
   107  		{
   108  			name:          "Username and password",
   109  			serverAddress: "registry1.example.com",
   110  			want: auth.Credential{
   111  				Username: "username",
   112  				Password: "password",
   113  			},
   114  		},
   115  		{
   116  			name:          "Identity token",
   117  			serverAddress: "registry2.example.com",
   118  			want: auth.Credential{
   119  				RefreshToken: "identity_token",
   120  			},
   121  		},
   122  		{
   123  			name:          "Registry token",
   124  			serverAddress: "registry3.example.com",
   125  			want: auth.Credential{
   126  				AccessToken: "registry_token",
   127  			},
   128  		},
   129  		{
   130  			name:          "Username and password, identity token and registry token",
   131  			serverAddress: "registry4.example.com",
   132  			want: auth.Credential{
   133  				Username:     "username",
   134  				Password:     "password",
   135  				RefreshToken: "identity_token",
   136  				AccessToken:  "registry_token",
   137  			},
   138  		},
   139  		{
   140  			name:          "Empty credential",
   141  			serverAddress: "registry5.example.com",
   142  			want:          auth.EmptyCredential,
   143  		},
   144  		{
   145  			name:          "Username and password, no auth",
   146  			serverAddress: "registry6.example.com",
   147  			want: auth.Credential{
   148  				Username: "username",
   149  				Password: "password",
   150  			},
   151  		},
   152  		{
   153  			name:          "Auth overriding Username and password",
   154  			serverAddress: "registry7.example.com",
   155  			want: auth.Credential{
   156  				Username: "username",
   157  				Password: "password",
   158  			},
   159  		},
   160  		{
   161  			name:          "Not in auths",
   162  			serverAddress: "foo.example.com",
   163  			want:          auth.EmptyCredential,
   164  		},
   165  		{
   166  			name:          "No record",
   167  			serverAddress: "registry999.example.com",
   168  			want:          auth.EmptyCredential,
   169  		},
   170  	}
   171  	for _, tt := range tests {
   172  		t.Run(tt.name, func(t *testing.T) {
   173  			got, err := fs.Get(ctx, tt.serverAddress)
   174  			if (err != nil) != tt.wantErr {
   175  				t.Errorf("FileStore.Get() error = %v, wantErr %v", err, tt.wantErr)
   176  				return
   177  			}
   178  			if !reflect.DeepEqual(got, tt.want) {
   179  				t.Errorf("FileStore.Get() = %v, want %v", got, tt.want)
   180  			}
   181  		})
   182  	}
   183  }
   184  
   185  func TestFileStore_Get_invalidConfig(t *testing.T) {
   186  	ctx := context.Background()
   187  	fs, err := NewFileStore("testdata/invalid_auths_entry_config.json")
   188  	if err != nil {
   189  		t.Fatal("NewFileStore() error =", err)
   190  	}
   191  
   192  	tests := []struct {
   193  		name          string
   194  		serverAddress string
   195  		want          auth.Credential
   196  		wantErr       bool
   197  	}{
   198  		{
   199  			name:          "Invalid auth encode",
   200  			serverAddress: "registry1.example.com",
   201  			want:          auth.EmptyCredential,
   202  			wantErr:       true,
   203  		},
   204  		{
   205  			name:          "Invalid auths format",
   206  			serverAddress: "registry2.example.com",
   207  			want:          auth.EmptyCredential,
   208  			wantErr:       true,
   209  		},
   210  		{
   211  			name:          "Invalid type",
   212  			serverAddress: "registry3.example.com",
   213  			want:          auth.EmptyCredential,
   214  			wantErr:       true,
   215  		},
   216  	}
   217  	for _, tt := range tests {
   218  		t.Run(tt.name, func(t *testing.T) {
   219  			got, err := fs.Get(ctx, tt.serverAddress)
   220  			if (err != nil) != tt.wantErr {
   221  				t.Errorf("FileStore.Get() error = %v, wantErr %v", err, tt.wantErr)
   222  				return
   223  			}
   224  			if !reflect.DeepEqual(got, tt.want) {
   225  				t.Errorf("FileStore.Get() = %v, want %v", got, tt.want)
   226  			}
   227  		})
   228  	}
   229  }
   230  
   231  func TestFileStore_Get_emptyConfig(t *testing.T) {
   232  	ctx := context.Background()
   233  	fs, err := NewFileStore("testdata/empty_config.json")
   234  	if err != nil {
   235  		t.Fatal("NewFileStore() error =", err)
   236  	}
   237  
   238  	tests := []struct {
   239  		name          string
   240  		serverAddress string
   241  		want          auth.Credential
   242  		wantErr       error
   243  	}{
   244  		{
   245  			name:          "Not found",
   246  			serverAddress: "registry.example.com",
   247  			want:          auth.EmptyCredential,
   248  			wantErr:       nil,
   249  		},
   250  	}
   251  	for _, tt := range tests {
   252  		t.Run(tt.name, func(t *testing.T) {
   253  			got, err := fs.Get(ctx, tt.serverAddress)
   254  			if !errors.Is(err, tt.wantErr) {
   255  				t.Errorf("FileStore.Get() error = %v, wantErr %v", err, tt.wantErr)
   256  				return
   257  			}
   258  			if !reflect.DeepEqual(got, tt.want) {
   259  				t.Errorf("FileStore.Get() = %v, want %v", got, tt.want)
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func TestFileStore_Get_notExistConfig(t *testing.T) {
   266  	ctx := context.Background()
   267  	fs, err := NewFileStore("whatever")
   268  	if err != nil {
   269  		t.Fatal("NewFileStore() error =", err)
   270  	}
   271  
   272  	tests := []struct {
   273  		name          string
   274  		serverAddress string
   275  		want          auth.Credential
   276  		wantErr       error
   277  	}{
   278  		{
   279  			name:          "Not found",
   280  			serverAddress: "registry.example.com",
   281  			want:          auth.EmptyCredential,
   282  			wantErr:       nil,
   283  		},
   284  	}
   285  	for _, tt := range tests {
   286  		t.Run(tt.name, func(t *testing.T) {
   287  			got, err := fs.Get(ctx, tt.serverAddress)
   288  			if !errors.Is(err, tt.wantErr) {
   289  				t.Errorf("FileStore.Get() error = %v, wantErr %v", err, tt.wantErr)
   290  				return
   291  			}
   292  			if !reflect.DeepEqual(got, tt.want) {
   293  				t.Errorf("FileStore.Get() = %v, want %v", got, tt.want)
   294  			}
   295  		})
   296  	}
   297  }
   298  
   299  func TestFileStore_Put_notExistConfig(t *testing.T) {
   300  	tempDir := t.TempDir()
   301  	configPath := filepath.Join(tempDir, "config.json")
   302  	ctx := context.Background()
   303  
   304  	fs, err := NewFileStore(configPath)
   305  	if err != nil {
   306  		t.Fatal("NewFileStore() error =", err)
   307  	}
   308  
   309  	server := "test.example.com"
   310  	cred := auth.Credential{
   311  		Username:     "username",
   312  		Password:     "password",
   313  		RefreshToken: "refresh_token",
   314  		AccessToken:  "access_token",
   315  	}
   316  
   317  	// test put
   318  	if err := fs.Put(ctx, server, cred); err != nil {
   319  		t.Fatalf("FileStore.Put() error = %v", err)
   320  	}
   321  
   322  	// verify config file
   323  	configFile, err := os.Open(configPath)
   324  	if err != nil {
   325  		t.Fatalf("failed to open config file: %v", err)
   326  	}
   327  	defer configFile.Close()
   328  
   329  	var cfg configtest.Config
   330  	if err := json.NewDecoder(configFile).Decode(&cfg); err != nil {
   331  		t.Fatalf("failed to decode config file: %v", err)
   332  	}
   333  	want := configtest.Config{
   334  		AuthConfigs: map[string]configtest.AuthConfig{
   335  			server: {
   336  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   337  				IdentityToken: "refresh_token",
   338  				RegistryToken: "access_token",
   339  			},
   340  		},
   341  	}
   342  	if !reflect.DeepEqual(cfg, want) {
   343  		t.Errorf("Decoded config = %v, want %v", cfg, want)
   344  	}
   345  
   346  	// verify get
   347  	got, err := fs.Get(ctx, server)
   348  	if err != nil {
   349  		t.Fatalf("FileStore.Get() error = %v", err)
   350  	}
   351  	if want := cred; !reflect.DeepEqual(got, want) {
   352  		t.Errorf("FileStore.Get() = %v, want %v", got, want)
   353  	}
   354  }
   355  
   356  func TestFileStore_Put_addNew(t *testing.T) {
   357  	tempDir := t.TempDir()
   358  	configPath := filepath.Join(tempDir, "config.json")
   359  	ctx := context.Background()
   360  
   361  	// prepare test content
   362  	server1 := "registry1.example.com"
   363  	cred1 := auth.Credential{
   364  		Username:     "username",
   365  		Password:     "password",
   366  		RefreshToken: "refresh_token",
   367  		AccessToken:  "access_token",
   368  	}
   369  
   370  	cfg := configtest.Config{
   371  		AuthConfigs: map[string]configtest.AuthConfig{
   372  			server1: {
   373  				SomeAuthField: "whatever",
   374  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   375  				IdentityToken: cred1.RefreshToken,
   376  				RegistryToken: cred1.AccessToken,
   377  			},
   378  		},
   379  		SomeConfigField: 123,
   380  	}
   381  	jsonStr, err := json.Marshal(cfg)
   382  	if err != nil {
   383  		t.Fatalf("failed to marshal config: %v", err)
   384  	}
   385  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   386  		t.Fatalf("failed to write config file: %v", err)
   387  	}
   388  
   389  	// test put
   390  	fs, err := NewFileStore(configPath)
   391  	if err != nil {
   392  		t.Fatal("NewFileStore() error =", err)
   393  	}
   394  	server2 := "registry2.example.com"
   395  	cred2 := auth.Credential{
   396  		Username:     "username_2",
   397  		Password:     "password_2",
   398  		RefreshToken: "refresh_token_2",
   399  		AccessToken:  "access_token_2",
   400  	}
   401  	if err := fs.Put(ctx, server2, cred2); err != nil {
   402  		t.Fatalf("FileStore.Put() error = %v", err)
   403  	}
   404  
   405  	// verify config file
   406  	configFile, err := os.Open(configPath)
   407  	if err != nil {
   408  		t.Fatalf("failed to open config file: %v", err)
   409  	}
   410  	defer configFile.Close()
   411  	var gotCfg configtest.Config
   412  	if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil {
   413  		t.Fatalf("failed to decode config file: %v", err)
   414  	}
   415  	wantCfg := configtest.Config{
   416  		AuthConfigs: map[string]configtest.AuthConfig{
   417  			server1: {
   418  				SomeAuthField: "whatever",
   419  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   420  				IdentityToken: cred1.RefreshToken,
   421  				RegistryToken: cred1.AccessToken,
   422  			},
   423  			server2: {
   424  				Auth:          "dXNlcm5hbWVfMjpwYXNzd29yZF8y",
   425  				IdentityToken: "refresh_token_2",
   426  				RegistryToken: "access_token_2",
   427  			},
   428  		},
   429  		SomeConfigField: cfg.SomeConfigField,
   430  	}
   431  	if !reflect.DeepEqual(gotCfg, wantCfg) {
   432  		t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg)
   433  	}
   434  
   435  	// verify get
   436  	got, err := fs.Get(ctx, server1)
   437  	if err != nil {
   438  		t.Fatalf("FileStore.Get() error = %v", err)
   439  	}
   440  	if want := cred1; !reflect.DeepEqual(got, want) {
   441  		t.Errorf("FileStore.Get(%s) = %v, want %v", server1, got, want)
   442  	}
   443  
   444  	got, err = fs.Get(ctx, server2)
   445  	if err != nil {
   446  		t.Fatalf("FileStore.Get() error = %v", err)
   447  	}
   448  	if want := cred2; !reflect.DeepEqual(got, want) {
   449  		t.Errorf("FileStore.Get(%s) = %v, want %v", server2, got, want)
   450  	}
   451  }
   452  
   453  func TestFileStore_Put_updateOld(t *testing.T) {
   454  	tempDir := t.TempDir()
   455  	configPath := filepath.Join(tempDir, "config.json")
   456  	ctx := context.Background()
   457  
   458  	// prepare test content
   459  	server := "registry.example.com"
   460  	cfg := configtest.Config{
   461  		AuthConfigs: map[string]configtest.AuthConfig{
   462  			server: {
   463  				SomeAuthField: "whatever",
   464  				Username:      "foo",
   465  				Password:      "bar",
   466  				IdentityToken: "refresh_token",
   467  			},
   468  		},
   469  		SomeConfigField: 123,
   470  	}
   471  	jsonStr, err := json.Marshal(cfg)
   472  	if err != nil {
   473  		t.Fatalf("failed to marshal config: %v", err)
   474  	}
   475  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   476  		t.Fatalf("failed to write config file: %v", err)
   477  	}
   478  
   479  	// test put
   480  	fs, err := NewFileStore(configPath)
   481  	if err != nil {
   482  		t.Fatal("NewFileStore() error =", err)
   483  	}
   484  	cred := auth.Credential{
   485  		Username:    "username",
   486  		Password:    "password",
   487  		AccessToken: "access_token",
   488  	}
   489  	if err := fs.Put(ctx, server, cred); err != nil {
   490  		t.Fatalf("FileStore.Put() error = %v", err)
   491  	}
   492  
   493  	// verify config file
   494  	configFile, err := os.Open(configPath)
   495  	if err != nil {
   496  		t.Fatalf("failed to open config file: %v", err)
   497  	}
   498  	defer configFile.Close()
   499  	var gotCfg configtest.Config
   500  	if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil {
   501  		t.Fatalf("failed to decode config file: %v", err)
   502  	}
   503  	wantCfg := configtest.Config{
   504  		AuthConfigs: map[string]configtest.AuthConfig{
   505  			server: {
   506  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   507  				RegistryToken: "access_token",
   508  			},
   509  		},
   510  		SomeConfigField: cfg.SomeConfigField,
   511  	}
   512  	if !reflect.DeepEqual(gotCfg, wantCfg) {
   513  		t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg)
   514  	}
   515  
   516  	// verify get
   517  	got, err := fs.Get(ctx, server)
   518  	if err != nil {
   519  		t.Fatalf("FileStore.Get() error = %v", err)
   520  	}
   521  	if want := cred; !reflect.DeepEqual(got, want) {
   522  		t.Errorf("FileStore.Get(%s) = %v, want %v", server, got, want)
   523  	}
   524  }
   525  
   526  func TestFileStore_Put_disablePut(t *testing.T) {
   527  	tempDir := t.TempDir()
   528  	configPath := filepath.Join(tempDir, "config.json")
   529  	ctx := context.Background()
   530  
   531  	fs, err := NewFileStore(configPath)
   532  	if err != nil {
   533  		t.Fatal("NewFileStore() error =", err)
   534  	}
   535  	fs.DisablePut = true
   536  
   537  	server := "test.example.com"
   538  	cred := auth.Credential{
   539  		Username:     "username",
   540  		Password:     "password",
   541  		RefreshToken: "refresh_token",
   542  		AccessToken:  "access_token",
   543  	}
   544  	err = fs.Put(ctx, server, cred)
   545  	if wantErr := ErrPlaintextPutDisabled; !errors.Is(err, wantErr) {
   546  		t.Errorf("FileStore.Put() error = %v, wantErr %v", err, wantErr)
   547  	}
   548  }
   549  
   550  func TestFileStore_Put_usernameContainsColon(t *testing.T) {
   551  	tempDir := t.TempDir()
   552  	configPath := filepath.Join(tempDir, "config.json")
   553  	ctx := context.Background()
   554  
   555  	fs, err := NewFileStore(configPath)
   556  	if err != nil {
   557  		t.Fatal("NewFileStore() error =", err)
   558  	}
   559  	serverAddr := "test.example.com"
   560  	cred := auth.Credential{
   561  		Username: "x:y",
   562  		Password: "z",
   563  	}
   564  	if err := fs.Put(ctx, serverAddr, cred); err == nil {
   565  		t.Fatal("FileStore.Put() error is nil, want", ErrBadCredentialFormat)
   566  	}
   567  }
   568  
   569  func TestFileStore_Put_passwordContainsColon(t *testing.T) {
   570  	tempDir := t.TempDir()
   571  	configPath := filepath.Join(tempDir, "config.json")
   572  	ctx := context.Background()
   573  
   574  	fs, err := NewFileStore(configPath)
   575  	if err != nil {
   576  		t.Fatal("NewFileStore() error =", err)
   577  	}
   578  	serverAddr := "test.example.com"
   579  	cred := auth.Credential{
   580  		Username: "y",
   581  		Password: "y:z",
   582  	}
   583  	if err := fs.Put(ctx, serverAddr, cred); err != nil {
   584  		t.Fatal("FileStore.Put() error =", err)
   585  	}
   586  	got, err := fs.Get(ctx, serverAddr)
   587  	if err != nil {
   588  		t.Fatal("FileStore.Get() error =", err)
   589  	}
   590  	if !reflect.DeepEqual(got, cred) {
   591  		t.Errorf("FileStore.Get() = %v, want %v", got, cred)
   592  	}
   593  }
   594  
   595  func TestFileStore_Delete(t *testing.T) {
   596  	tempDir := t.TempDir()
   597  	configPath := filepath.Join(tempDir, "config.json")
   598  	ctx := context.Background()
   599  
   600  	// prepare test content
   601  	server1 := "registry1.example.com"
   602  	cred1 := auth.Credential{
   603  		Username:     "username",
   604  		Password:     "password",
   605  		RefreshToken: "refresh_token",
   606  		AccessToken:  "access_token",
   607  	}
   608  	server2 := "registry2.example.com"
   609  	cred2 := auth.Credential{
   610  		Username:     "username_2",
   611  		Password:     "password_2",
   612  		RefreshToken: "refresh_token_2",
   613  		AccessToken:  "access_token_2",
   614  	}
   615  
   616  	cfg := configtest.Config{
   617  		AuthConfigs: map[string]configtest.AuthConfig{
   618  			server1: {
   619  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   620  				IdentityToken: cred1.RefreshToken,
   621  				RegistryToken: cred1.AccessToken,
   622  			},
   623  			server2: {
   624  				Auth:          "dXNlcm5hbWVfMjpwYXNzd29yZF8y",
   625  				IdentityToken: "refresh_token_2",
   626  				RegistryToken: "access_token_2",
   627  			},
   628  		},
   629  		SomeConfigField: 123,
   630  	}
   631  	jsonStr, err := json.Marshal(cfg)
   632  	if err != nil {
   633  		t.Fatalf("failed to marshal config: %v", err)
   634  	}
   635  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   636  		t.Fatalf("failed to write config file: %v", err)
   637  	}
   638  
   639  	fs, err := NewFileStore(configPath)
   640  	if err != nil {
   641  		t.Fatal("NewFileStore() error =", err)
   642  	}
   643  	// test get
   644  	got, err := fs.Get(ctx, server1)
   645  	if err != nil {
   646  		t.Fatalf("FileStore.Get() error = %v", err)
   647  	}
   648  	if want := cred1; !reflect.DeepEqual(got, want) {
   649  		t.Errorf("FileStore.Get(%s) = %v, want %v", server1, got, want)
   650  	}
   651  	got, err = fs.Get(ctx, server2)
   652  	if err != nil {
   653  		t.Fatalf("FileStore.Get() error = %v", err)
   654  	}
   655  	if want := cred2; !reflect.DeepEqual(got, want) {
   656  		t.Errorf("FileStore.Get(%s) = %v, want %v", server2, got, want)
   657  	}
   658  
   659  	// test delete
   660  	if err := fs.Delete(ctx, server1); err != nil {
   661  		t.Fatalf("FileStore.Delete() error = %v", err)
   662  	}
   663  
   664  	// verify config file
   665  	configFile, err := os.Open(configPath)
   666  	if err != nil {
   667  		t.Fatalf("failed to open config file: %v", err)
   668  	}
   669  	defer configFile.Close()
   670  	var gotCfg configtest.Config
   671  	if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil {
   672  		t.Fatalf("failed to decode config file: %v", err)
   673  	}
   674  	wantCfg := configtest.Config{
   675  		AuthConfigs: map[string]configtest.AuthConfig{
   676  			server2: cfg.AuthConfigs[server2],
   677  		},
   678  		SomeConfigField: cfg.SomeConfigField,
   679  	}
   680  	if !reflect.DeepEqual(gotCfg, wantCfg) {
   681  		t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg)
   682  	}
   683  
   684  	// test get again
   685  	got, err = fs.Get(ctx, server1)
   686  	if err != nil {
   687  		t.Fatalf("FileStore.Get() error = %v", err)
   688  	}
   689  	if want := auth.EmptyCredential; !reflect.DeepEqual(got, want) {
   690  		t.Errorf("FileStore.Get(%s) = %v, want %v", server1, got, want)
   691  	}
   692  	got, err = fs.Get(ctx, server2)
   693  	if err != nil {
   694  		t.Fatalf("FileStore.Get() error = %v", err)
   695  	}
   696  	if want := cred2; !reflect.DeepEqual(got, want) {
   697  		t.Errorf("FileStore.Get(%s) = %v, want %v", server2, got, want)
   698  	}
   699  }
   700  
   701  func TestFileStore_Delete_lastConfig(t *testing.T) {
   702  	tempDir := t.TempDir()
   703  	configPath := filepath.Join(tempDir, "config.json")
   704  	ctx := context.Background()
   705  
   706  	// prepare test content
   707  	server := "registry1.example.com"
   708  	cred := auth.Credential{
   709  		Username:     "username",
   710  		Password:     "password",
   711  		RefreshToken: "refresh_token",
   712  		AccessToken:  "access_token",
   713  	}
   714  
   715  	cfg := configtest.Config{
   716  		AuthConfigs: map[string]configtest.AuthConfig{
   717  			server: {
   718  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   719  				IdentityToken: cred.RefreshToken,
   720  				RegistryToken: cred.AccessToken,
   721  			},
   722  		},
   723  		SomeConfigField: 123,
   724  	}
   725  	jsonStr, err := json.Marshal(cfg)
   726  	if err != nil {
   727  		t.Fatalf("failed to marshal config: %v", err)
   728  	}
   729  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   730  		t.Fatalf("failed to write config file: %v", err)
   731  	}
   732  
   733  	fs, err := NewFileStore(configPath)
   734  	if err != nil {
   735  		t.Fatal("NewFileStore() error =", err)
   736  	}
   737  	// test get
   738  	got, err := fs.Get(ctx, server)
   739  	if err != nil {
   740  		t.Fatalf("FileStore.Get() error = %v", err)
   741  	}
   742  	if want := cred; !reflect.DeepEqual(got, want) {
   743  		t.Errorf("FileStore.Get(%s) = %v, want %v", server, got, want)
   744  	}
   745  
   746  	// test delete
   747  	if err := fs.Delete(ctx, server); err != nil {
   748  		t.Fatalf("FileStore.Delete() error = %v", err)
   749  	}
   750  
   751  	// verify config file
   752  	configFile, err := os.Open(configPath)
   753  	if err != nil {
   754  		t.Fatalf("failed to open config file: %v", err)
   755  	}
   756  	defer configFile.Close()
   757  	var gotCfg configtest.Config
   758  	if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil {
   759  		t.Fatalf("failed to decode config file: %v", err)
   760  	}
   761  	wantCfg := configtest.Config{
   762  		AuthConfigs:     map[string]configtest.AuthConfig{},
   763  		SomeConfigField: cfg.SomeConfigField,
   764  	}
   765  	if !reflect.DeepEqual(gotCfg, wantCfg) {
   766  		t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg)
   767  	}
   768  
   769  	// test get again
   770  	got, err = fs.Get(ctx, server)
   771  	if err != nil {
   772  		t.Fatalf("FileStore.Get() error = %v", err)
   773  	}
   774  	if want := auth.EmptyCredential; !reflect.DeepEqual(got, want) {
   775  		t.Errorf("FileStore.Get(%s) = %v, want %v", server, got, want)
   776  	}
   777  }
   778  
   779  func TestFileStore_Delete_notExistRecord(t *testing.T) {
   780  	tempDir := t.TempDir()
   781  	configPath := filepath.Join(tempDir, "config.json")
   782  	ctx := context.Background()
   783  
   784  	// prepare test content
   785  	server := "registry1.example.com"
   786  	cred := auth.Credential{
   787  		Username:     "username",
   788  		Password:     "password",
   789  		RefreshToken: "refresh_token",
   790  		AccessToken:  "access_token",
   791  	}
   792  	cfg := configtest.Config{
   793  		AuthConfigs: map[string]configtest.AuthConfig{
   794  			server: {
   795  				Auth:          "dXNlcm5hbWU6cGFzc3dvcmQ=",
   796  				IdentityToken: cred.RefreshToken,
   797  				RegistryToken: cred.AccessToken,
   798  			},
   799  		},
   800  		SomeConfigField: 123,
   801  	}
   802  	jsonStr, err := json.Marshal(cfg)
   803  	if err != nil {
   804  		t.Fatalf("failed to marshal config: %v", err)
   805  	}
   806  	if err := os.WriteFile(configPath, jsonStr, 0666); err != nil {
   807  		t.Fatalf("failed to write config file: %v", err)
   808  	}
   809  
   810  	fs, err := NewFileStore(configPath)
   811  	if err != nil {
   812  		t.Fatal("NewFileStore() error =", err)
   813  	}
   814  	// test get
   815  	got, err := fs.Get(ctx, server)
   816  	if err != nil {
   817  		t.Fatalf("FileStore.Get() error = %v", err)
   818  	}
   819  	if want := cred; !reflect.DeepEqual(got, want) {
   820  		t.Errorf("FileStore.Get(%s) = %v, want %v", server, got, want)
   821  	}
   822  
   823  	// test delete
   824  	if err := fs.Delete(ctx, "test.example.com"); err != nil {
   825  		t.Fatalf("FileStore.Delete() error = %v", err)
   826  	}
   827  
   828  	// verify config file
   829  	configFile, err := os.Open(configPath)
   830  	if err != nil {
   831  		t.Fatalf("failed to open config file: %v", err)
   832  	}
   833  	defer configFile.Close()
   834  	var gotCfg configtest.Config
   835  	if err := json.NewDecoder(configFile).Decode(&gotCfg); err != nil {
   836  		t.Fatalf("failed to decode config file: %v", err)
   837  	}
   838  	wantCfg := configtest.Config{
   839  		AuthConfigs: map[string]configtest.AuthConfig{
   840  			server: cfg.AuthConfigs[server],
   841  		},
   842  		SomeConfigField: cfg.SomeConfigField,
   843  	}
   844  	if !reflect.DeepEqual(gotCfg, wantCfg) {
   845  		t.Errorf("Decoded config = %v, want %v", gotCfg, wantCfg)
   846  	}
   847  
   848  	// test get again
   849  	got, err = fs.Get(ctx, server)
   850  	if err != nil {
   851  		t.Fatalf("FileStore.Get() error = %v", err)
   852  	}
   853  	if want := cred; !reflect.DeepEqual(got, want) {
   854  		t.Errorf("FileStore.Get(%s) = %v, want %v", server, got, want)
   855  	}
   856  }
   857  
   858  func TestFileStore_Delete_notExistConfig(t *testing.T) {
   859  	tempDir := t.TempDir()
   860  	configPath := filepath.Join(tempDir, "config.json")
   861  	ctx := context.Background()
   862  
   863  	fs, err := NewFileStore(configPath)
   864  	if err != nil {
   865  		t.Fatal("NewFileStore() error =", err)
   866  	}
   867  
   868  	server := "test.example.com"
   869  	// test delete
   870  	if err := fs.Delete(ctx, server); err != nil {
   871  		t.Fatalf("FileStore.Delete() error = %v", err)
   872  	}
   873  
   874  	// verify config file is not created
   875  	_, err = os.Stat(configPath)
   876  	if wantErr := os.ErrNotExist; !errors.Is(err, wantErr) {
   877  		t.Errorf("Stat(%s) error = %v, wantErr %v", configPath, err, wantErr)
   878  	}
   879  }
   880  
   881  func Test_validateCredentialFormat(t *testing.T) {
   882  	tests := []struct {
   883  		name    string
   884  		cred    auth.Credential
   885  		wantErr error
   886  	}{
   887  		{
   888  			name: "Username contains colon",
   889  			cred: auth.Credential{
   890  				Username: "x:y",
   891  				Password: "z",
   892  			},
   893  			wantErr: ErrBadCredentialFormat,
   894  		},
   895  		{
   896  			name: "Password contains colon",
   897  			cred: auth.Credential{
   898  				Username: "x",
   899  				Password: "y:z",
   900  			},
   901  		},
   902  	}
   903  	for _, tt := range tests {
   904  		t.Run(tt.name, func(t *testing.T) {
   905  			if err := validateCredentialFormat(tt.cred); !errors.Is(err, tt.wantErr) {
   906  				t.Errorf("validateCredentialFormat() error = %v, wantErr %v", err, tt.wantErr)
   907  			}
   908  		})
   909  	}
   910  }