github.com/2lambda123/git-lfs@v2.5.2+incompatible/lfsapi/ssh_test.go (about) 1 package lfsapi 2 3 import ( 4 "errors" 5 "net/url" 6 "path/filepath" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func TestSSHCacheResolveFromCache(t *testing.T) { 15 ssh := newFakeResolver() 16 cache := withSSHCache(ssh).(*sshCache) 17 cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{ 18 Href: "cache", 19 createdAt: time.Now(), 20 } 21 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 22 23 e := Endpoint{ 24 SshUserAndHost: "userandhost", 25 SshPort: "1", 26 SshPath: "path", 27 } 28 29 res, err := cache.Resolve(e, "post") 30 assert.Nil(t, err) 31 assert.Equal(t, "cache", res.Href) 32 } 33 34 func TestSSHCacheResolveFromCacheWithFutureExpiresAt(t *testing.T) { 35 ssh := newFakeResolver() 36 cache := withSSHCache(ssh).(*sshCache) 37 cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{ 38 Href: "cache", 39 ExpiresAt: time.Now().Add(time.Duration(1) * time.Hour), 40 createdAt: time.Now(), 41 } 42 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 43 44 e := Endpoint{ 45 SshUserAndHost: "userandhost", 46 SshPort: "1", 47 SshPath: "path", 48 } 49 50 res, err := cache.Resolve(e, "post") 51 assert.Nil(t, err) 52 assert.Equal(t, "cache", res.Href) 53 } 54 55 func TestSSHCacheResolveFromCacheWithFutureExpiresIn(t *testing.T) { 56 ssh := newFakeResolver() 57 cache := withSSHCache(ssh).(*sshCache) 58 cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{ 59 Href: "cache", 60 ExpiresIn: 60 * 60, 61 createdAt: time.Now(), 62 } 63 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 64 65 e := Endpoint{ 66 SshUserAndHost: "userandhost", 67 SshPort: "1", 68 SshPath: "path", 69 } 70 71 res, err := cache.Resolve(e, "post") 72 assert.Nil(t, err) 73 assert.Equal(t, "cache", res.Href) 74 } 75 76 func TestSSHCacheResolveFromCacheWithPastExpiresAt(t *testing.T) { 77 ssh := newFakeResolver() 78 cache := withSSHCache(ssh).(*sshCache) 79 cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{ 80 Href: "cache", 81 ExpiresAt: time.Now().Add(time.Duration(-1) * time.Hour), 82 createdAt: time.Now(), 83 } 84 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 85 86 e := Endpoint{ 87 SshUserAndHost: "userandhost", 88 SshPort: "1", 89 SshPath: "path", 90 } 91 92 res, err := cache.Resolve(e, "post") 93 assert.Nil(t, err) 94 assert.Equal(t, "real", res.Href) 95 } 96 97 func TestSSHCacheResolveFromCacheWithPastExpiresIn(t *testing.T) { 98 ssh := newFakeResolver() 99 cache := withSSHCache(ssh).(*sshCache) 100 cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{ 101 Href: "cache", 102 ExpiresIn: -60 * 60, 103 createdAt: time.Now(), 104 } 105 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 106 107 e := Endpoint{ 108 SshUserAndHost: "userandhost", 109 SshPort: "1", 110 SshPath: "path", 111 } 112 113 res, err := cache.Resolve(e, "post") 114 assert.Nil(t, err) 115 assert.Equal(t, "real", res.Href) 116 } 117 118 func TestSSHCacheResolveFromCacheWithAmbiguousExpirationInfo(t *testing.T) { 119 ssh := newFakeResolver() 120 cache := withSSHCache(ssh).(*sshCache) 121 cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{ 122 Href: "cache", 123 ExpiresIn: 60 * 60, 124 ExpiresAt: time.Now().Add(-1 * time.Hour), 125 createdAt: time.Now(), 126 } 127 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 128 129 e := Endpoint{ 130 SshUserAndHost: "userandhost", 131 SshPort: "1", 132 SshPath: "path", 133 } 134 135 res, err := cache.Resolve(e, "post") 136 assert.Nil(t, err) 137 assert.Equal(t, "cache", res.Href) 138 } 139 140 func TestSSHCacheResolveWithoutError(t *testing.T) { 141 ssh := newFakeResolver() 142 cache := withSSHCache(ssh).(*sshCache) 143 144 assert.Equal(t, 0, len(cache.endpoints)) 145 146 ssh.responses["userandhost"] = sshAuthResponse{Href: "real"} 147 148 e := Endpoint{ 149 SshUserAndHost: "userandhost", 150 SshPort: "1", 151 SshPath: "path", 152 } 153 154 res, err := cache.Resolve(e, "post") 155 assert.Nil(t, err) 156 assert.Equal(t, "real", res.Href) 157 158 assert.Equal(t, 1, len(cache.endpoints)) 159 cacheres, ok := cache.endpoints["userandhost//1//path//post"] 160 assert.True(t, ok) 161 assert.NotNil(t, cacheres) 162 assert.Equal(t, "real", cacheres.Href) 163 164 delete(ssh.responses, "userandhost") 165 res2, err := cache.Resolve(e, "post") 166 assert.Nil(t, err) 167 assert.Equal(t, "real", res2.Href) 168 } 169 170 func TestSSHCacheResolveWithError(t *testing.T) { 171 ssh := newFakeResolver() 172 cache := withSSHCache(ssh).(*sshCache) 173 174 assert.Equal(t, 0, len(cache.endpoints)) 175 176 ssh.responses["userandhost"] = sshAuthResponse{Message: "resolve error", Href: "real"} 177 178 e := Endpoint{ 179 SshUserAndHost: "userandhost", 180 SshPort: "1", 181 SshPath: "path", 182 } 183 184 res, err := cache.Resolve(e, "post") 185 assert.NotNil(t, err) 186 assert.Equal(t, "real", res.Href) 187 188 assert.Equal(t, 0, len(cache.endpoints)) 189 delete(ssh.responses, "userandhost") 190 res2, err := cache.Resolve(e, "post") 191 assert.Nil(t, err) 192 assert.Equal(t, "", res2.Href) 193 } 194 195 func newFakeResolver() *fakeResolver { 196 return &fakeResolver{responses: make(map[string]sshAuthResponse)} 197 } 198 199 type fakeResolver struct { 200 responses map[string]sshAuthResponse 201 } 202 203 func (r *fakeResolver) Resolve(e Endpoint, method string) (sshAuthResponse, error) { 204 res := r.responses[e.SshUserAndHost] 205 var err error 206 if len(res.Message) > 0 { 207 err = errors.New(res.Message) 208 } 209 210 res.createdAt = time.Now() 211 212 return res, err 213 } 214 215 func TestSSHGetLFSExeAndArgs(t *testing.T) { 216 cli, err := NewClient(nil) 217 require.Nil(t, err) 218 219 endpoint := cli.Endpoints.Endpoint("download", "") 220 endpoint.SshUserAndHost = "user@foo.com" 221 endpoint.SshPath = "user/repo" 222 223 exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), endpoint, "GET") 224 assert.Equal(t, "ssh", exe) 225 assert.Equal(t, []string{ 226 "--", 227 "user@foo.com", 228 "git-lfs-authenticate user/repo download", 229 }, args) 230 231 exe, args = sshGetLFSExeAndArgs(cli.OSEnv(), endpoint, "HEAD") 232 assert.Equal(t, "ssh", exe) 233 assert.Equal(t, []string{ 234 "--", 235 "user@foo.com", 236 "git-lfs-authenticate user/repo download", 237 }, args) 238 239 // this is going by endpoint.Operation, implicitly set by Endpoint() on L15. 240 exe, args = sshGetLFSExeAndArgs(cli.OSEnv(), endpoint, "POST") 241 assert.Equal(t, "ssh", exe) 242 assert.Equal(t, []string{ 243 "--", 244 "user@foo.com", 245 "git-lfs-authenticate user/repo download", 246 }, args) 247 248 endpoint.Operation = "upload" 249 exe, args = sshGetLFSExeAndArgs(cli.OSEnv(), endpoint, "POST") 250 assert.Equal(t, "ssh", exe) 251 assert.Equal(t, []string{ 252 "--", 253 "user@foo.com", 254 "git-lfs-authenticate user/repo upload", 255 }, args) 256 } 257 258 func TestSSHGetExeAndArgsSsh(t *testing.T) { 259 cli, err := NewClient(NewContext(nil, map[string]string{ 260 "GIT_SSH_COMMAND": "", 261 "GIT_SSH": "", 262 }, nil)) 263 require.Nil(t, err) 264 265 endpoint := cli.Endpoints.Endpoint("download", "") 266 endpoint.SshUserAndHost = "user@foo.com" 267 268 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 269 assert.Equal(t, "ssh", exe) 270 assert.Equal(t, []string{"--", "user@foo.com"}, args) 271 } 272 273 func TestSSHGetExeAndArgsSshCustomPort(t *testing.T) { 274 cli, err := NewClient(NewContext(nil, map[string]string{ 275 "GIT_SSH_COMMAND": "", 276 "GIT_SSH": "", 277 }, nil)) 278 require.Nil(t, err) 279 280 endpoint := cli.Endpoints.Endpoint("download", "") 281 endpoint.SshUserAndHost = "user@foo.com" 282 endpoint.SshPort = "8888" 283 284 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 285 assert.Equal(t, "ssh", exe) 286 assert.Equal(t, []string{"-p", "8888", "--", "user@foo.com"}, args) 287 } 288 289 func TestSSHGetExeAndArgsPlink(t *testing.T) { 290 plink := filepath.Join("Users", "joebloggs", "bin", "plink.exe") 291 292 cli, err := NewClient(NewContext(nil, map[string]string{ 293 "GIT_SSH_COMMAND": "", 294 "GIT_SSH": plink, 295 }, nil)) 296 require.Nil(t, err) 297 298 endpoint := cli.Endpoints.Endpoint("download", "") 299 endpoint.SshUserAndHost = "user@foo.com" 300 301 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 302 assert.Equal(t, plink, exe) 303 assert.Equal(t, []string{"user@foo.com"}, args) 304 } 305 306 func TestSSHGetExeAndArgsPlinkCustomPort(t *testing.T) { 307 plink := filepath.Join("Users", "joebloggs", "bin", "plink") 308 309 cli, err := NewClient(NewContext(nil, map[string]string{ 310 "GIT_SSH_COMMAND": "", 311 "GIT_SSH": plink, 312 }, nil)) 313 require.Nil(t, err) 314 315 endpoint := cli.Endpoints.Endpoint("download", "") 316 endpoint.SshUserAndHost = "user@foo.com" 317 endpoint.SshPort = "8888" 318 319 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 320 assert.Equal(t, plink, exe) 321 assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args) 322 } 323 324 func TestSSHGetExeAndArgsTortoisePlink(t *testing.T) { 325 plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe") 326 327 cli, err := NewClient(NewContext(nil, map[string]string{ 328 "GIT_SSH_COMMAND": "", 329 "GIT_SSH": plink, 330 }, nil)) 331 require.Nil(t, err) 332 333 endpoint := cli.Endpoints.Endpoint("download", "") 334 endpoint.SshUserAndHost = "user@foo.com" 335 336 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 337 assert.Equal(t, plink, exe) 338 assert.Equal(t, []string{"-batch", "user@foo.com"}, args) 339 } 340 341 func TestSSHGetExeAndArgsTortoisePlinkCustomPort(t *testing.T) { 342 plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink") 343 344 cli, err := NewClient(NewContext(nil, map[string]string{ 345 "GIT_SSH_COMMAND": "", 346 "GIT_SSH": plink, 347 }, nil)) 348 require.Nil(t, err) 349 350 endpoint := cli.Endpoints.Endpoint("download", "") 351 endpoint.SshUserAndHost = "user@foo.com" 352 endpoint.SshPort = "8888" 353 354 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 355 assert.Equal(t, plink, exe) 356 assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args) 357 } 358 359 func TestSSHGetExeAndArgsSshCommandPrecedence(t *testing.T) { 360 cli, err := NewClient(NewContext(nil, map[string]string{ 361 "GIT_SSH_COMMAND": "sshcmd", 362 "GIT_SSH": "bad", 363 }, nil)) 364 require.Nil(t, err) 365 366 endpoint := cli.Endpoints.Endpoint("download", "") 367 endpoint.SshUserAndHost = "user@foo.com" 368 369 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 370 assert.Equal(t, "sshcmd", exe) 371 assert.Equal(t, []string{"user@foo.com"}, args) 372 } 373 374 func TestSSHGetExeAndArgsSshCommandArgs(t *testing.T) { 375 cli, err := NewClient(NewContext(nil, map[string]string{ 376 "GIT_SSH_COMMAND": "sshcmd --args 1", 377 }, nil)) 378 require.Nil(t, err) 379 380 endpoint := cli.Endpoints.Endpoint("download", "") 381 endpoint.SshUserAndHost = "user@foo.com" 382 383 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 384 assert.Equal(t, "sshcmd", exe) 385 assert.Equal(t, []string{"--args", "1", "user@foo.com"}, args) 386 } 387 388 func TestSSHGetExeAndArgsSshCommandArgsWithMixedQuotes(t *testing.T) { 389 cli, err := NewClient(NewContext(nil, map[string]string{ 390 "GIT_SSH_COMMAND": "sshcmd foo 'bar \"baz\"'", 391 }, nil)) 392 require.Nil(t, err) 393 394 endpoint := cli.Endpoints.Endpoint("download", "") 395 endpoint.SshUserAndHost = "user@foo.com" 396 397 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 398 assert.Equal(t, "sshcmd", exe) 399 assert.Equal(t, []string{"foo", `bar "baz"`, "user@foo.com"}, args) 400 } 401 402 func TestSSHGetExeAndArgsSshCommandCustomPort(t *testing.T) { 403 cli, err := NewClient(NewContext(nil, map[string]string{ 404 "GIT_SSH_COMMAND": "sshcmd", 405 }, nil)) 406 require.Nil(t, err) 407 408 endpoint := cli.Endpoints.Endpoint("download", "") 409 endpoint.SshUserAndHost = "user@foo.com" 410 endpoint.SshPort = "8888" 411 412 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 413 assert.Equal(t, "sshcmd", exe) 414 assert.Equal(t, []string{"-p", "8888", "user@foo.com"}, args) 415 } 416 417 func TestSSHGetLFSExeAndArgsWithCustomSSH(t *testing.T) { 418 cli, err := NewClient(NewContext(nil, map[string]string{ 419 "GIT_SSH": "not-ssh", 420 }, nil)) 421 require.Nil(t, err) 422 423 u, err := url.Parse("ssh://git@host.com:12345/repo") 424 require.Nil(t, err) 425 426 e := endpointFromSshUrl(u) 427 t.Logf("ENDPOINT: %+v", e) 428 assert.Equal(t, "12345", e.SshPort) 429 assert.Equal(t, "git@host.com", e.SshUserAndHost) 430 assert.Equal(t, "repo", e.SshPath) 431 432 exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), e, "GET") 433 assert.Equal(t, "not-ssh", exe) 434 assert.Equal(t, []string{"-p", "12345", "git@host.com", "git-lfs-authenticate repo download"}, args) 435 } 436 437 func TestSSHGetLFSExeAndArgsInvalidOptionsAsHost(t *testing.T) { 438 cli, err := NewClient(nil) 439 require.Nil(t, err) 440 441 u, err := url.Parse("ssh://-oProxyCommand=gnome-calculator/repo") 442 require.Nil(t, err) 443 assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host) 444 445 e := endpointFromSshUrl(u) 446 t.Logf("ENDPOINT: %+v", e) 447 assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshUserAndHost) 448 assert.Equal(t, "repo", e.SshPath) 449 450 exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), e, "GET") 451 assert.Equal(t, "ssh", exe) 452 assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator", "git-lfs-authenticate repo download"}, args) 453 } 454 455 func TestSSHGetLFSExeAndArgsInvalidOptionsAsHostWithCustomSSH(t *testing.T) { 456 cli, err := NewClient(NewContext(nil, map[string]string{ 457 "GIT_SSH": "not-ssh", 458 }, nil)) 459 require.Nil(t, err) 460 461 u, err := url.Parse("ssh://--oProxyCommand=gnome-calculator/repo") 462 require.Nil(t, err) 463 assert.Equal(t, "--oProxyCommand=gnome-calculator", u.Host) 464 465 e := endpointFromSshUrl(u) 466 t.Logf("ENDPOINT: %+v", e) 467 assert.Equal(t, "--oProxyCommand=gnome-calculator", e.SshUserAndHost) 468 assert.Equal(t, "repo", e.SshPath) 469 470 exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), e, "GET") 471 assert.Equal(t, "not-ssh", exe) 472 assert.Equal(t, []string{"oProxyCommand=gnome-calculator", "git-lfs-authenticate repo download"}, args) 473 } 474 475 func TestSSHGetExeAndArgsInvalidOptionsAsHost(t *testing.T) { 476 cli, err := NewClient(nil) 477 require.Nil(t, err) 478 479 u, err := url.Parse("ssh://-oProxyCommand=gnome-calculator") 480 require.Nil(t, err) 481 assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host) 482 483 e := endpointFromSshUrl(u) 484 t.Logf("ENDPOINT: %+v", e) 485 assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshUserAndHost) 486 assert.Equal(t, "", e.SshPath) 487 488 exe, args := sshGetExeAndArgs(cli.OSEnv(), e) 489 assert.Equal(t, "ssh", exe) 490 assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator"}, args) 491 } 492 493 func TestSSHGetExeAndArgsInvalidOptionsAsPath(t *testing.T) { 494 cli, err := NewClient(nil) 495 require.Nil(t, err) 496 497 u, err := url.Parse("ssh://git@git-host.com/-oProxyCommand=gnome-calculator") 498 require.Nil(t, err) 499 assert.Equal(t, "git-host.com", u.Host) 500 501 e := endpointFromSshUrl(u) 502 t.Logf("ENDPOINT: %+v", e) 503 assert.Equal(t, "git@git-host.com", e.SshUserAndHost) 504 assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshPath) 505 506 exe, args := sshGetExeAndArgs(cli.OSEnv(), e) 507 assert.Equal(t, "ssh", exe) 508 assert.Equal(t, []string{"--", "git@git-host.com"}, args) 509 } 510 511 func TestParseBareSSHUrl(t *testing.T) { 512 e := endpointFromBareSshUrl("git@git-host.com:repo.git") 513 t.Logf("endpoint: %+v", e) 514 assert.Equal(t, "git@git-host.com", e.SshUserAndHost) 515 assert.Equal(t, "repo.git", e.SshPath) 516 517 e = endpointFromBareSshUrl("git@git-host.com/should-be-a-colon.git") 518 t.Logf("endpoint: %+v", e) 519 assert.Equal(t, "", e.SshUserAndHost) 520 assert.Equal(t, "", e.SshPath) 521 522 e = endpointFromBareSshUrl("-oProxyCommand=gnome-calculator") 523 t.Logf("endpoint: %+v", e) 524 assert.Equal(t, "", e.SshUserAndHost) 525 assert.Equal(t, "", e.SshPath) 526 527 e = endpointFromBareSshUrl("git@git-host.com:-oProxyCommand=gnome-calculator") 528 t.Logf("endpoint: %+v", e) 529 assert.Equal(t, "git@git-host.com", e.SshUserAndHost) 530 assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SshPath) 531 } 532 533 func TestSSHGetExeAndArgsPlinkCommand(t *testing.T) { 534 plink := filepath.Join("Users", "joebloggs", "bin", "plink.exe") 535 536 cli, err := NewClient(NewContext(nil, map[string]string{ 537 "GIT_SSH_COMMAND": plink, 538 }, nil)) 539 require.Nil(t, err) 540 541 endpoint := cli.Endpoints.Endpoint("download", "") 542 endpoint.SshUserAndHost = "user@foo.com" 543 544 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 545 assert.Equal(t, plink, exe) 546 assert.Equal(t, []string{"user@foo.com"}, args) 547 } 548 549 func TestSSHGetExeAndArgsPlinkCommandCustomPort(t *testing.T) { 550 plink := filepath.Join("Users", "joebloggs", "bin", "plink") 551 552 cli, err := NewClient(NewContext(nil, map[string]string{ 553 "GIT_SSH_COMMAND": plink, 554 }, nil)) 555 require.Nil(t, err) 556 557 endpoint := cli.Endpoints.Endpoint("download", "") 558 endpoint.SshUserAndHost = "user@foo.com" 559 endpoint.SshPort = "8888" 560 561 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 562 assert.Equal(t, plink, exe) 563 assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args) 564 } 565 566 func TestSSHGetExeAndArgsTortoisePlinkCommand(t *testing.T) { 567 plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe") 568 569 cli, err := NewClient(NewContext(nil, map[string]string{ 570 "GIT_SSH_COMMAND": plink, 571 }, nil)) 572 require.Nil(t, err) 573 574 endpoint := cli.Endpoints.Endpoint("download", "") 575 endpoint.SshUserAndHost = "user@foo.com" 576 577 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 578 assert.Equal(t, plink, exe) 579 assert.Equal(t, []string{"-batch", "user@foo.com"}, args) 580 } 581 582 func TestSSHGetExeAndArgsTortoisePlinkCommandCustomPort(t *testing.T) { 583 plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink") 584 585 cli, err := NewClient(NewContext(nil, map[string]string{ 586 "GIT_SSH_COMMAND": plink, 587 }, nil)) 588 require.Nil(t, err) 589 590 endpoint := cli.Endpoints.Endpoint("download", "") 591 endpoint.SshUserAndHost = "user@foo.com" 592 endpoint.SshPort = "8888" 593 594 exe, args := sshGetExeAndArgs(cli.OSEnv(), endpoint) 595 assert.Equal(t, plink, exe) 596 assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args) 597 }