|
6 | 6 | "debug/pe" |
7 | 7 | "encoding/binary" |
8 | 8 | "errors" |
| 9 | + "fmt" |
9 | 10 | "net/http" |
10 | 11 | "os" |
11 | 12 | "path/filepath" |
@@ -400,6 +401,71 @@ func TestDoctorDoesNotCallAnUnreadableStoreAMissingLogin(t *testing.T) { |
400 | 401 | } |
401 | 402 | } |
402 | 403 |
|
| 404 | +func TestDoctorAuthenticationPreservesLaterStoreTimeouts(t *testing.T) { |
| 405 | + for _, failure := range []string{"authentication read", "refresh read", "refresh write"} { |
| 406 | + t.Run(failure, func(t *testing.T) { |
| 407 | + app, _ := setupDoctorTestApp(t, "12345") |
| 408 | + t.Setenv("BASECAMP_TOKEN", "") |
| 409 | + creds := &auth.Credentials{ |
| 410 | + AccessToken: "test-token", RefreshToken: "test-refresh", |
| 411 | + OAuthType: "bc5", ExpiresAt: time.Now().Add(time.Hour).Unix(), |
| 412 | + } |
| 413 | + if failure != "authentication read" { |
| 414 | + creds.ExpiresAt = time.Now().Add(-time.Hour).Unix() |
| 415 | + } |
| 416 | + require.NoError(t, app.Auth.GetStore().Save(app.Auth.CredentialKey(), creds)) |
| 417 | + initial, unreadable := checkStoredCredentials(context.Background(), app, false) |
| 418 | + require.False(t, unreadable) |
| 419 | + require.Equal(t, "pass", initial.Status) |
| 420 | + |
| 421 | + operation := "read" |
| 422 | + if failure == "refresh write" { |
| 423 | + operation = "write" |
| 424 | + } |
| 425 | + timeout := fmt.Errorf("keyring %s timed out: %w; set BASECAMP_NO_KEYRING=1", operation, context.DeadlineExceeded) |
| 426 | + loads, refreshes := 0, 0 |
| 427 | + load := func(context.Context, string) (*auth.Credentials, error) { |
| 428 | + loads++ |
| 429 | + if failure == "authentication read" || (failure == "refresh read" && loads == 2) { |
| 430 | + return nil, timeout |
| 431 | + } |
| 432 | + return creds, nil |
| 433 | + } |
| 434 | + refresh := func(ctx context.Context) error { |
| 435 | + refreshes++ |
| 436 | + if _, err := load(ctx, app.Auth.CredentialKey()); err != nil { |
| 437 | + return err |
| 438 | + } |
| 439 | + return timeout // The refresh read succeeded, but saving its result timed out. |
| 440 | + } |
| 441 | + check := checkAuthenticationWith(context.Background(), app, false, load, refresh) |
| 442 | + assert.Equal(t, "fail", check.Status) |
| 443 | + assert.Contains(t, check.Message, "keyring "+operation+" timed out") |
| 444 | + assert.Contains(t, check.Message, "BASECAMP_NO_KEYRING=1") |
| 445 | + assert.NotContains(t, check.Hint, app.Auth.LoginCommand()) |
| 446 | + assert.Empty(t, buildDoctorBreadcrumbs([]Check{initial, check}, app.Auth.LoginCommand())) |
| 447 | + if failure == "authentication read" { |
| 448 | + assert.Equal(t, 1, loads) |
| 449 | + assert.Zero(t, refreshes) |
| 450 | + } else { |
| 451 | + assert.Equal(t, 2, loads) |
| 452 | + assert.Equal(t, 1, refreshes) |
| 453 | + } |
| 454 | + }) |
| 455 | + } |
| 456 | +} |
| 457 | + |
| 458 | +func TestDoctorAuthenticationBreadcrumbRequiresLoginRemedy(t *testing.T) { |
| 459 | + for _, status := range []string{"fail", "warn"} { |
| 460 | + check := Check{Name: "Authentication", Status: status, Hint: "Try again later"} |
| 461 | + assert.Empty(t, buildDoctorBreadcrumbs([]Check{check}, "basecamp auth login")) |
| 462 | + } |
| 463 | + check := Check{Name: "Authentication", Status: "fail", Hint: "Run: basecamp auth login"} |
| 464 | + crumbs := buildDoctorBreadcrumbs([]Check{check}, "basecamp auth login") |
| 465 | + require.Len(t, crumbs, 1) |
| 466 | + assert.Equal(t, "basecamp auth login", crumbs[0].Cmd) |
| 467 | +} |
| 468 | + |
403 | 469 | func TestDoctorLegacyKeyringProbeIsBounded(t *testing.T) { |
404 | 470 | original := legacyKeyringGet |
405 | 471 | t.Cleanup(func() { legacyKeyringGet = original }) |
|
0 commit comments