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