Repository: cli/cli
Pull request #8448 (2023-12-11 15:10:31)

Update multi-account migration error msg
This PR updates the multi-account migration error message when a username is unable to be retrieved to now include a link to an issue in the repo with remediation steps for that specific error. I did not feel this change warranted a new test, but happy to add one if others feel differently. 

Additionally, I fixed up some `auth login` tests that were reading the local config file causing failures if the user running the tests was only logged into a GHES instance.


Patch:
diff --git a/internal/config/migration/multi_account.go b/internal/config/migration/multi_account.go
index d4e1e7a67..eeae8c6f3 100644
--- a/internal/config/migration/multi_account.go
+++ b/internal/config/migration/multi_account.go
@@ -120,7 +120,8 @@ func (m MultiAccount) Do(c *config.Config) error {
 
 		username, err := getUsername(c, hostname, tokenSource.token, m.Transport)
 		if err != nil {
-			return CowardlyRefusalError{fmt.Errorf("couldn't get user name for %q: %w", hostname, err)}
+			issueURL := "https://github.com/cli/cli/issues/8441"
+			return CowardlyRefusalError{fmt.Errorf("couldn't get user name for %q please visit %s for help: %w", hostname, issueURL, err)}
 		}
 
 		if err := migrateConfig(c, hostname, username); err != nil {
diff --git a/pkg/cmd/run/shared/shared.go b/pkg/cmd/run/shared/shared.go
index 20846d60f..6c76714fd 100644
--- a/pkg/cmd/run/shared/shared.go
+++ b/pkg/cmd/run/shared/shared.go
@@ -186,9 +186,9 @@ func (r *Run) ExportData(fields []string) map[string]interface{} {
 						"number":     s.Number,
 					})
 				}
-				var completedAt *time.Time
+				var completedAt time.Time
 				if !j.CompletedAt.IsZero() {
-					completedAt = &j.CompletedAt
+					completedAt = j.CompletedAt
 				}
 				jobs = append(jobs, map[string]interface{}{
 					"databaseId":  j.ID,
@@ -200,8 +200,8 @@ func (r *Run) ExportData(fields []string) map[string]interface{} {
 					"completedAt": completedAt,
 					"url":         j.URL,
 				})
-				data[f] = jobs
 			}
+			data[f] = jobs
 		default:
 			sf := fieldByName(v, f)
 			data[f] = sf.Interface()


Test patch:
diff --git a/pkg/cmd/auth/login/login_test.go b/pkg/cmd/auth/login/login_test.go
index 60c6f6f24..c5f5f8480 100644
--- a/pkg/cmd/auth/login/login_test.go
+++ b/pkg/cmd/auth/login/login_test.go
@@ -42,17 +42,16 @@ func Test_NewCmdLogin(t *testing.T) {
 		wantsErr    bool
 	}{
 		{
-			name:        "nontty, with-token",
-			stdin:       "abc123\n",
-			cli:         "--with-token",
-			defaultHost: "github.com",
+			name:  "nontty, with-token",
+			stdin: "abc123\n",
+			cli:   "--with-token",
 			wants: LoginOptions{
 				Hostname: "github.com",
 				Token:    "abc123",
 			},
 		},
 		{
-			name:        "nontty, Enterprise host",
+			name:        "nontty, with-token, enterprise default host",
 			stdin:       "abc123\n",
 			cli:         "--with-token",
 			defaultHost: "git.example.com",
@@ -209,6 +208,11 @@ func Test_NewCmdLogin(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			// Make sure there is a default host set so that
+			// the local configuration file never read from.
+			if tt.defaultHost == "" {
+				tt.defaultHost = "github.com"
+			}
 			t.Setenv("GH_HOST", tt.defaultHost)
 
 			ios, stdin, _, _ := iostreams.Test()
diff --git a/pkg/cmd/run/shared/shared_test.go b/pkg/cmd/run/shared/shared_test.go
index 5d7768207..815a6556c 100644
--- a/pkg/cmd/run/shared/shared_test.go
+++ b/pkg/cmd/run/shared/shared_test.go
@@ -1,8 +1,10 @@
 package shared
 
 import (
+	"bytes"
 	"encoding/json"
 	"net/http"
+	"strings"
 	"testing"
 	"time"
 
@@ -11,6 +13,7 @@ import (
 	"github.com/cli/cli/v2/internal/ghrepo"
 	"github.com/cli/cli/v2/pkg/httpmock"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestPreciseAgo(t *testing.T) {
@@ -104,3 +107,97 @@ func TestRun_Duration(t *testing.T) {
 		})
 	}
 }
+
+func TestRunExportData(t *testing.T) {
+	oldestStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:13Z")
+	oldestCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:16Z")
+	newestStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:55Z")
+	newestCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:23:16Z")
+
+	tests := []struct {
+		name   string
+		fields []string
+		run    Run
+		output string
+	}{
+		{
+			name:   "exports workflow run's single job",
+			fields: []string{"jobs"},
+			run: Run{
+				Jobs: []Job{
+					{
+						ID:         123456,
+						Status:     "completed",
+						Conclusion: "success",
+						Name:       "macos",
+						Steps: []Step{
+							{
+								Name:       "Checkout",
+								Status:     "completed",
+								Conclusion: "success",
+								Number:     1,
+							},
+						},
+						StartedAt:   oldestStartedAt,
+						CompletedAt: oldestCompletedAt,
+						URL:         "https://example.com/OWNER/REPO/actions/runs/123456",
+					},
+				},
+			},
+			output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"conclusion":"success","name":"Checkout","number":1,"status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"}]}`,
+		},
+		{
+			name:   "exports workflow run's multiple jobs",
+			fields: []string{"jobs"},
+			run: Run{
+				Jobs: []Job{
+					{
+						ID:         123456,
+						Status:     "completed",
+						Conclusion: "success",
+						Name:       "macos",
+						Steps: []Step{
+							{
+								Name:       "Checkout",
+								Status:     "completed",
+								Conclusion: "success",
+								Number:     1,
+							},
+						},
+						StartedAt:   oldestStartedAt,
+						CompletedAt: oldestCompletedAt,
+						URL:         "https://example.com/OWNER/REPO/actions/runs/123456",
+					},
+					{
+						ID:         234567,
+						Status:     "completed",
+						Conclusion: "error",
+						Name:       "windows",
+						Steps: []Step{
+							{
+								Name:       "Checkout",
+								Status:     "completed",
+								Conclusion: "error",
+								Number:     2,
+							},
+						},
+						StartedAt:   newestStartedAt,
+						CompletedAt: newestCompletedAt,
+						URL:         "https://example.com/OWNER/REPO/actions/runs/234567",
+					},
+				},
+			},
+			output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"conclusion":"success","name":"Checkout","number":1,"status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"},{"completedAt":"2022-07-20T11:23:16Z","conclusion":"error","databaseId":234567,"name":"windows","startedAt":"2022-07-20T11:20:55Z","status":"completed","steps":[{"conclusion":"error","name":"Checkout","number":2,"status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/234567"}]}`,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			exported := tt.run.ExportData(tt.fields)
+			buf := bytes.Buffer{}
+			enc := json.NewEncoder(&buf)
+			require.NoError(t, enc.Encode(exported))
+			assert.Equal(t, tt.output, strings.TrimSpace(buf.String()))
+		})
+	}
+}