github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/config/credentials/native_store_test.go (about)

     1  package credentials
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/docker/cli/cli/config/types"
    11  	"github.com/docker/docker-credential-helpers/client"
    12  	"github.com/docker/docker-credential-helpers/credentials"
    13  	"github.com/pkg/errors"
    14  	"gotest.tools/v3/assert"
    15  	is "gotest.tools/v3/assert/cmp"
    16  )
    17  
    18  const (
    19  	validServerAddress   = "https://index.docker.io/v1"
    20  	validServerAddress2  = "https://example.com:5002"
    21  	invalidServerAddress = "https://foobar.example.com"
    22  	missingCredsAddress  = "https://missing.docker.io/v1"
    23  )
    24  
    25  var errCommandExited = errors.Errorf("exited 1")
    26  
    27  // mockCommand simulates interactions between the docker client and a remote
    28  // credentials helper.
    29  // Unit tests inject this mocked command into the remote to control execution.
    30  type mockCommand struct {
    31  	arg   string
    32  	input io.Reader
    33  }
    34  
    35  // Output returns responses from the remote credentials helper.
    36  // It mocks those responses based in the input in the mock.
    37  func (m *mockCommand) Output() ([]byte, error) {
    38  	in, err := io.ReadAll(m.input)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	inS := string(in)
    43  
    44  	switch m.arg {
    45  	case "erase":
    46  		switch inS {
    47  		case validServerAddress:
    48  			return nil, nil
    49  		default:
    50  			return []byte("program failed"), errCommandExited
    51  		}
    52  	case "get":
    53  		switch inS {
    54  		case validServerAddress:
    55  			return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
    56  		case validServerAddress2:
    57  			return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
    58  		case missingCredsAddress:
    59  			return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
    60  		case invalidServerAddress:
    61  			return []byte("program failed"), errCommandExited
    62  		}
    63  	case "store":
    64  		var c credentials.Credentials
    65  		err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
    66  		if err != nil {
    67  			return []byte("program failed"), errCommandExited
    68  		}
    69  		switch c.ServerURL {
    70  		case validServerAddress:
    71  			return nil, nil
    72  		default:
    73  			return []byte("program failed"), errCommandExited
    74  		}
    75  	case "list":
    76  		return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
    77  	}
    78  
    79  	return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
    80  }
    81  
    82  // Input sets the input to send to a remote credentials helper.
    83  func (m *mockCommand) Input(in io.Reader) {
    84  	m.input = in
    85  }
    86  
    87  func mockCommandFn(args ...string) client.Program {
    88  	return &mockCommand{
    89  		arg: args[0],
    90  	}
    91  }
    92  
    93  func TestNativeStoreAddCredentials(t *testing.T) {
    94  	f := newStore(make(map[string]types.AuthConfig))
    95  	s := &nativeStore{
    96  		programFunc: mockCommandFn,
    97  		fileStore:   NewFileStore(f),
    98  	}
    99  	auth := types.AuthConfig{
   100  		Username:      "foo",
   101  		Password:      "bar",
   102  		Email:         "foo@example.com",
   103  		ServerAddress: validServerAddress,
   104  	}
   105  	err := s.Store(auth)
   106  	assert.NilError(t, err)
   107  	assert.Check(t, is.Len(f.GetAuthConfigs(), 1))
   108  
   109  	actual, ok := f.GetAuthConfigs()[validServerAddress]
   110  	assert.Check(t, ok)
   111  	expected := types.AuthConfig{
   112  		Email:         auth.Email,
   113  		ServerAddress: auth.ServerAddress,
   114  	}
   115  	assert.Check(t, is.DeepEqual(expected, actual))
   116  }
   117  
   118  func TestNativeStoreAddInvalidCredentials(t *testing.T) {
   119  	f := newStore(make(map[string]types.AuthConfig))
   120  	s := &nativeStore{
   121  		programFunc: mockCommandFn,
   122  		fileStore:   NewFileStore(f),
   123  	}
   124  	err := s.Store(types.AuthConfig{
   125  		Username:      "foo",
   126  		Password:      "bar",
   127  		Email:         "foo@example.com",
   128  		ServerAddress: invalidServerAddress,
   129  	})
   130  	assert.ErrorContains(t, err, "program failed")
   131  	assert.Check(t, is.Len(f.GetAuthConfigs(), 0))
   132  }
   133  
   134  func TestNativeStoreGet(t *testing.T) {
   135  	f := newStore(map[string]types.AuthConfig{
   136  		validServerAddress: {
   137  			Email: "foo@example.com",
   138  		},
   139  	})
   140  	s := &nativeStore{
   141  		programFunc: mockCommandFn,
   142  		fileStore:   NewFileStore(f),
   143  	}
   144  	actual, err := s.Get(validServerAddress)
   145  	assert.NilError(t, err)
   146  
   147  	expected := types.AuthConfig{
   148  		Username:      "foo",
   149  		Password:      "bar",
   150  		Email:         "foo@example.com",
   151  		ServerAddress: validServerAddress,
   152  	}
   153  	assert.Check(t, is.DeepEqual(expected, actual))
   154  }
   155  
   156  func TestNativeStoreGetIdentityToken(t *testing.T) {
   157  	f := newStore(map[string]types.AuthConfig{
   158  		validServerAddress2: {
   159  			Email: "foo@example2.com",
   160  		},
   161  	})
   162  
   163  	s := &nativeStore{
   164  		programFunc: mockCommandFn,
   165  		fileStore:   NewFileStore(f),
   166  	}
   167  	actual, err := s.Get(validServerAddress2)
   168  	assert.NilError(t, err)
   169  
   170  	expected := types.AuthConfig{
   171  		IdentityToken: "abcd1234",
   172  		Email:         "foo@example2.com",
   173  		ServerAddress: validServerAddress2,
   174  	}
   175  	assert.Check(t, is.DeepEqual(expected, actual))
   176  }
   177  
   178  func TestNativeStoreGetAll(t *testing.T) {
   179  	f := newStore(map[string]types.AuthConfig{
   180  		validServerAddress: {
   181  			Email: "foo@example.com",
   182  		},
   183  	})
   184  
   185  	s := &nativeStore{
   186  		programFunc: mockCommandFn,
   187  		fileStore:   NewFileStore(f),
   188  	}
   189  	as, err := s.GetAll()
   190  	assert.NilError(t, err)
   191  	assert.Check(t, is.Len(as, 2))
   192  
   193  	if as[validServerAddress].Username != "foo" {
   194  		t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
   195  	}
   196  	if as[validServerAddress].Password != "bar" {
   197  		t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
   198  	}
   199  	if as[validServerAddress].IdentityToken != "" {
   200  		t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
   201  	}
   202  	if as[validServerAddress].Email != "foo@example.com" {
   203  		t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
   204  	}
   205  	if as[validServerAddress2].Username != "" {
   206  		t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
   207  	}
   208  	if as[validServerAddress2].Password != "" {
   209  		t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
   210  	}
   211  	if as[validServerAddress2].IdentityToken != "abcd1234" {
   212  		t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
   213  	}
   214  	if as[validServerAddress2].Email != "" {
   215  		t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
   216  	}
   217  }
   218  
   219  func TestNativeStoreGetMissingCredentials(t *testing.T) {
   220  	f := newStore(map[string]types.AuthConfig{
   221  		validServerAddress: {
   222  			Email: "foo@example.com",
   223  		},
   224  	})
   225  
   226  	s := &nativeStore{
   227  		programFunc: mockCommandFn,
   228  		fileStore:   NewFileStore(f),
   229  	}
   230  	_, err := s.Get(missingCredsAddress)
   231  	assert.NilError(t, err)
   232  }
   233  
   234  func TestNativeStoreGetInvalidAddress(t *testing.T) {
   235  	f := newStore(map[string]types.AuthConfig{
   236  		validServerAddress: {
   237  			Email: "foo@example.com",
   238  		},
   239  	})
   240  
   241  	s := &nativeStore{
   242  		programFunc: mockCommandFn,
   243  		fileStore:   NewFileStore(f),
   244  	}
   245  	_, err := s.Get(invalidServerAddress)
   246  	assert.ErrorContains(t, err, "program failed")
   247  }
   248  
   249  func TestNativeStoreErase(t *testing.T) {
   250  	f := newStore(map[string]types.AuthConfig{
   251  		validServerAddress: {
   252  			Email: "foo@example.com",
   253  		},
   254  	})
   255  
   256  	s := &nativeStore{
   257  		programFunc: mockCommandFn,
   258  		fileStore:   NewFileStore(f),
   259  	}
   260  	err := s.Erase(validServerAddress)
   261  	assert.NilError(t, err)
   262  	assert.Check(t, is.Len(f.GetAuthConfigs(), 0))
   263  }
   264  
   265  func TestNativeStoreEraseInvalidAddress(t *testing.T) {
   266  	f := newStore(map[string]types.AuthConfig{
   267  		validServerAddress: {
   268  			Email: "foo@example.com",
   269  		},
   270  	})
   271  
   272  	s := &nativeStore{
   273  		programFunc: mockCommandFn,
   274  		fileStore:   NewFileStore(f),
   275  	}
   276  	err := s.Erase(invalidServerAddress)
   277  	assert.ErrorContains(t, err, "program failed")
   278  }