github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/issue/develop/develop_test.go (about)

     1  package develop
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"testing"
     7  
     8  	"github.com/ungtb10d/cli/v2/context"
     9  	"github.com/ungtb10d/cli/v2/git"
    10  	"github.com/ungtb10d/cli/v2/internal/config"
    11  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    12  	"github.com/ungtb10d/cli/v2/internal/run"
    13  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    14  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    15  	"github.com/ungtb10d/cli/v2/pkg/prompt"
    16  	"github.com/ungtb10d/cli/v2/test"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func Test_developRun(t *testing.T) {
    21  	featureEnabledPayload := `{
    22  		"data": {
    23  			"LinkedBranch": {
    24  				"fields": [
    25  					{
    26  						"name": "id"
    27  					},
    28  					{
    29  						"name": "ref"
    30  					}
    31  				]
    32  			}
    33  		}
    34  	}`
    35  
    36  	featureDisabledPayload := `{ "data": { "LinkedBranch": null } }`
    37  
    38  	tests := []struct {
    39  		name           string
    40  		setup          func(*DevelopOptions, *testing.T) func()
    41  		cmdStubs       func(*run.CommandStubber)
    42  		runStubs       func(*run.CommandStubber)
    43  		remotes        map[string]string
    44  		askStubs       func(*prompt.AskStubber) // TODO eventually migrate to PrompterMock
    45  		httpStubs      func(*httpmock.Registry, *testing.T)
    46  		expectedOut    string
    47  		expectedErrOut string
    48  		expectedBrowse string
    49  		wantErr        string
    50  		tty            bool
    51  	}{
    52  		{name: "list branches for an issue",
    53  			setup: func(opts *DevelopOptions, t *testing.T) func() {
    54  				opts.IssueSelector = "42"
    55  				opts.List = true
    56  				return func() {}
    57  			},
    58  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
    59  				reg.Register(
    60  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
    61  					httpmock.StringResponse(featureEnabledPayload),
    62  				)
    63  				reg.Register(
    64  					httpmock.GraphQL(`query BranchIssueReferenceListLinkedBranches\b`),
    65  					httpmock.GraphQLQuery(`{
    66  						"data": {
    67  							"repository": {
    68  								"issue": {
    69  									"linkedBranches": {
    70  										"edges": [
    71  										{
    72  											"node": {
    73  												"ref": {
    74  													"name": "foo"
    75  												}
    76  											}
    77  										},
    78  										{
    79  											"node": {
    80  												"ref": {
    81  													"name": "bar"
    82  												}
    83  											}
    84  										}
    85  									]
    86  								}
    87  							}
    88  						}
    89  					}
    90  					}
    91  					`, func(query string, inputs map[string]interface{}) {
    92  						assert.Equal(t, float64(42), inputs["issueNumber"])
    93  						assert.Equal(t, "OWNER", inputs["repositoryOwner"])
    94  						assert.Equal(t, "REPO", inputs["repositoryName"])
    95  					}))
    96  			},
    97  			expectedOut: "foo\nbar\n",
    98  		},
    99  		{name: "list branches for an issue in tty",
   100  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   101  				opts.IssueSelector = "42"
   102  				opts.List = true
   103  				return func() {}
   104  			},
   105  			tty: true,
   106  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   107  				reg.Register(
   108  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   109  					httpmock.StringResponse(featureEnabledPayload),
   110  				)
   111  				reg.Register(
   112  					httpmock.GraphQL(`query BranchIssueReferenceListLinkedBranches\b`),
   113  					httpmock.GraphQLQuery(`{
   114  						"data": {
   115  							"repository": {
   116  								"issue": {
   117  									"linkedBranches": {
   118  										"edges": [
   119  										{
   120  											"node": {
   121  												"ref": {
   122  													"name": "foo",
   123  													"repository": {
   124  														"url": "http://github.localhost/OWNER/REPO"
   125  													}
   126  												}
   127  											}
   128  										},
   129  										{
   130  											"node": {
   131  												"ref": {
   132  													"name": "bar",
   133  													"repository": {
   134  														"url": "http://github.localhost/OWNER/OTHER-REPO"
   135  													}
   136  												}
   137  											}
   138  										}
   139  									]
   140  								}
   141  							}
   142  						}
   143  					}
   144  					}
   145  					`, func(query string, inputs map[string]interface{}) {
   146  						assert.Equal(t, float64(42), inputs["issueNumber"])
   147  						assert.Equal(t, "OWNER", inputs["repositoryOwner"])
   148  						assert.Equal(t, "REPO", inputs["repositoryName"])
   149  					}))
   150  			},
   151  			expectedOut: "\nShowing linked branches for OWNER/REPO#42\n\nfoo  http://github.localhost/OWNER/REPO/tree/foo\nbar  http://github.localhost/OWNER/OTHER-REPO/tree/bar\n",
   152  		},
   153  		{name: "list branches for an issue providing an issue url",
   154  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   155  				opts.IssueSelector = "https://github.com/cli/test-repo/issues/42"
   156  				opts.List = true
   157  				return func() {}
   158  			},
   159  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   160  				reg.Register(
   161  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   162  					httpmock.StringResponse(featureEnabledPayload),
   163  				)
   164  				reg.Register(
   165  					httpmock.GraphQL(`query BranchIssueReferenceListLinkedBranches\b`),
   166  					httpmock.GraphQLQuery(`{
   167  						"data": {
   168  							"repository": {
   169  								"issue": {
   170  									"linkedBranches": {
   171  										"edges": [
   172  										{
   173  											"node": {
   174  												"ref": {
   175  													"name": "foo"
   176  												}
   177  											}
   178  										},
   179  										{
   180  											"node": {
   181  												"ref": {
   182  													"name": "bar"
   183  												}
   184  											}
   185  										}
   186  									]
   187  								}
   188  							}
   189  						}
   190  					}
   191  					}
   192  					`, func(query string, inputs map[string]interface{}) {
   193  						assert.Equal(t, float64(42), inputs["issueNumber"])
   194  						assert.Equal(t, "cli", inputs["repositoryOwner"])
   195  						assert.Equal(t, "test-repo", inputs["repositoryName"])
   196  					}))
   197  			},
   198  			expectedOut: "foo\nbar\n",
   199  		},
   200  		{name: "list branches for an issue providing an issue repo",
   201  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   202  				opts.IssueSelector = "42"
   203  				opts.IssueRepoSelector = "cli/test-repo"
   204  				opts.List = true
   205  				return func() {}
   206  			},
   207  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   208  				reg.Register(
   209  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   210  					httpmock.StringResponse(featureEnabledPayload),
   211  				)
   212  				reg.Register(
   213  					httpmock.GraphQL(`query BranchIssueReferenceListLinkedBranches\b`),
   214  					httpmock.GraphQLQuery(`{
   215  						"data": {
   216  							"repository": {
   217  								"issue": {
   218  									"linkedBranches": {
   219  										"edges": [
   220  										{
   221  											"node": {
   222  												"ref": {
   223  													"name": "foo"
   224  												}
   225  											}
   226  										},
   227  										{
   228  											"node": {
   229  												"ref": {
   230  													"name": "bar"
   231  												}
   232  											}
   233  										}
   234  									]
   235  								}
   236  							}
   237  						}
   238  					}
   239  					}
   240  					`, func(query string, inputs map[string]interface{}) {
   241  						assert.Equal(t, float64(42), inputs["issueNumber"])
   242  						assert.Equal(t, "cli", inputs["repositoryOwner"])
   243  						assert.Equal(t, "test-repo", inputs["repositoryName"])
   244  					}))
   245  			},
   246  			expectedOut: "foo\nbar\n",
   247  		},
   248  		{name: "list branches for an issue providing an issue url and specifying the same repo works",
   249  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   250  				opts.IssueSelector = "https://github.com/cli/test-repo/issues/42"
   251  				opts.IssueRepoSelector = "cli/test-repo"
   252  				opts.List = true
   253  				return func() {}
   254  			},
   255  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   256  				reg.Register(
   257  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   258  					httpmock.StringResponse(featureEnabledPayload),
   259  				)
   260  				reg.Register(
   261  					httpmock.GraphQL(`query BranchIssueReferenceListLinkedBranches\b`),
   262  					httpmock.GraphQLQuery(`{
   263  					"data": {
   264  						"repository": {
   265  							"issue": {
   266  								"linkedBranches": {
   267  									"edges": [
   268  									{
   269  										"node": {
   270  											"ref": {
   271  												"name": "foo"
   272  											}
   273  										}
   274  									},
   275  									{
   276  										"node": {
   277  											"ref": {
   278  												"name": "bar"
   279  											}
   280  										}
   281  									}
   282  								]
   283  							}
   284  						}
   285  					}
   286  				}
   287  				}
   288  				`, func(query string, inputs map[string]interface{}) {
   289  						assert.Equal(t, float64(42), inputs["issueNumber"])
   290  						assert.Equal(t, "cli", inputs["repositoryOwner"])
   291  						assert.Equal(t, "test-repo", inputs["repositoryName"])
   292  					}))
   293  			},
   294  			expectedOut: "foo\nbar\n",
   295  		},
   296  		{name: "list branches for an issue providing an issue url and specifying a different repo returns an error",
   297  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   298  				opts.IssueSelector = "https://github.com/cli/test-repo/issues/42"
   299  				opts.IssueRepoSelector = "cli/other"
   300  				opts.List = true
   301  				return func() {}
   302  			},
   303  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   304  				reg.Register(
   305  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   306  					httpmock.StringResponse(featureEnabledPayload),
   307  				)
   308  			},
   309  			wantErr: "issue repo in url cli/test-repo does not match the repo from --issue-repo cli/other",
   310  		},
   311  		{name: "returns an error when the feature isn't enabled in the GraphQL API",
   312  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   313  				opts.IssueSelector = "https://github.com/cli/test-repo/issues/42"
   314  				opts.List = true
   315  				return func() {}
   316  			},
   317  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   318  				reg.Register(
   319  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   320  					httpmock.StringResponse(featureDisabledPayload),
   321  				)
   322  			},
   323  			wantErr: "the `gh issue develop` command is not currently available",
   324  		},
   325  		{name: "develop new branch with a name provided",
   326  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   327  				opts.Name = "my-branch"
   328  				opts.BaseBranch = "main"
   329  				opts.IssueSelector = "123"
   330  				return func() {}
   331  			},
   332  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   333  				reg.Register(
   334  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   335  					httpmock.StringResponse(featureEnabledPayload),
   336  				)
   337  				reg.Register(
   338  					httpmock.GraphQL(`query RepositoryInfo\b`),
   339  					httpmock.StringResponse(`
   340  						{ "data": { "repository": {
   341  							"id": "REPOID",
   342  							"hasIssuesEnabled": true
   343  						} } }`),
   344  				)
   345  				reg.Register(
   346  					httpmock.GraphQL(`query IssueByNumber\b`),
   347  					httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"id": "yar", "number":123, "title":"my issue"} }}}`))
   348  				reg.Register(
   349  					httpmock.GraphQL(`query BranchIssueReferenceFindBaseOid\b`),
   350  					httpmock.StringResponse(`{"data":{"repository":{"ref":{"target":{"oid":"123"}}}}}`))
   351  
   352  				reg.Register(
   353  					httpmock.GraphQL(`(?s)mutation CreateLinkedBranch\b.*issueId: \$issueId,\s+name: \$name,\s+oid: \$oid,`),
   354  					httpmock.GraphQLQuery(`{ "data": { "createLinkedBranch": { "linkedBranch": {"id": "2", "ref": {"name": "my-branch"} } } } }`,
   355  						func(query string, inputs map[string]interface{}) {
   356  							assert.Equal(t, "REPOID", inputs["repositoryId"])
   357  							assert.Equal(t, "my-branch", inputs["name"])
   358  							assert.Equal(t, "yar", inputs["issueId"])
   359  						}),
   360  				)
   361  
   362  			},
   363  			expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
   364  		},
   365  		{name: "develop new branch without a name provided omits the param from the mutation",
   366  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   367  				opts.Name = ""
   368  				opts.BaseBranch = "main"
   369  				opts.IssueSelector = "123"
   370  				return func() {}
   371  			},
   372  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   373  				reg.Register(
   374  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   375  					httpmock.StringResponse(featureEnabledPayload),
   376  				)
   377  				reg.Register(
   378  					httpmock.GraphQL(`query RepositoryInfo\b`),
   379  					httpmock.StringResponse(`
   380  						{ "data": { "repository": {
   381  							"id": "REPOID",
   382  							"hasIssuesEnabled": true
   383  						} } }`),
   384  				)
   385  				reg.Register(
   386  					httpmock.GraphQL(`query IssueByNumber\b`),
   387  					httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"id": "yar", "number":123, "title":"my issue"} }}}`))
   388  				reg.Register(
   389  					httpmock.GraphQL(`query BranchIssueReferenceFindBaseOid\b`),
   390  					httpmock.StringResponse(`{"data":{"repository":{"ref":{"target":{"oid":"123"}}}}}`))
   391  
   392  				reg.Register(
   393  					httpmock.GraphQL(`(?s)mutation CreateLinkedBranch\b.*\$oid: GitObjectID!, \$repositoryId:.*issueId: \$issueId,\s+oid: \$oid,`),
   394  					httpmock.GraphQLQuery(`{ "data": { "createLinkedBranch": { "linkedBranch": {"id": "2", "ref": {"name": "my-issue-1"} } } } }`,
   395  						func(query string, inputs map[string]interface{}) {
   396  							assert.Equal(t, "REPOID", inputs["repositoryId"])
   397  							assert.Equal(t, "", inputs["name"])
   398  							assert.Equal(t, "yar", inputs["issueId"])
   399  						}),
   400  				)
   401  
   402  			},
   403  			expectedOut: "github.com/OWNER/REPO/tree/my-issue-1\n",
   404  		},
   405  		{name: "develop providing an issue url and specifying a different repo returns an error",
   406  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   407  				opts.IssueSelector = "https://github.com/cli/test-repo/issues/42"
   408  				opts.IssueRepoSelector = "cli/other"
   409  				return func() {}
   410  			},
   411  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   412  				reg.Register(
   413  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   414  					httpmock.StringResponse(featureEnabledPayload),
   415  				)
   416  				reg.Register(
   417  					httpmock.GraphQL(`query RepositoryInfo\b`),
   418  					httpmock.StringResponse(`
   419  						{ "data": { "repository": {
   420  							"id": "REPOID",
   421  							"hasIssuesEnabled": true
   422  						} } }`),
   423  				)
   424  			},
   425  			wantErr: "issue repo in url cli/test-repo does not match the repo from --issue-repo cli/other",
   426  		},
   427  		{name: "develop new branch with checkout when the branch exists locally",
   428  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   429  				opts.Name = "my-branch"
   430  				opts.BaseBranch = "main"
   431  				opts.IssueSelector = "123"
   432  				opts.Checkout = true
   433  				return func() {}
   434  			},
   435  			remotes: map[string]string{
   436  				"origin": "OWNER/REPO",
   437  			},
   438  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   439  				reg.Register(
   440  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   441  					httpmock.StringResponse(featureEnabledPayload),
   442  				)
   443  				reg.Register(
   444  					httpmock.GraphQL(`query RepositoryInfo\b`),
   445  					httpmock.StringResponse(`
   446  						{ "data": { "repository": {
   447  							"id": "REPOID",
   448  							"hasIssuesEnabled": true
   449  						} } }`),
   450  				)
   451  				reg.Register(
   452  					httpmock.GraphQL(`query IssueByNumber\b`),
   453  					httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"id": "yar", "number":123, "title":"my issue"} }}}`))
   454  				reg.Register(
   455  					httpmock.GraphQL(`query BranchIssueReferenceFindBaseOid\b`),
   456  					httpmock.StringResponse(`{"data":{"repository":{"ref":{"target":{"oid":"123"}}}}}`))
   457  
   458  				reg.Register(
   459  					httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
   460  					httpmock.GraphQLQuery(`{ "data": { "createLinkedBranch": { "linkedBranch": {"id": "2", "ref": {"name": "my-branch"} } } } }`,
   461  						func(query string, inputs map[string]interface{}) {
   462  							assert.Equal(t, "REPOID", inputs["repositoryId"])
   463  							assert.Equal(t, "my-branch", inputs["name"])
   464  							assert.Equal(t, "yar", inputs["issueId"])
   465  						}),
   466  				)
   467  
   468  			},
   469  			runStubs: func(cs *run.CommandStubber) {
   470  				cs.Register(`git rev-parse --verify refs/heads/my-branch`, 0, "")
   471  				cs.Register(`git checkout my-branch`, 0, "")
   472  				cs.Register(`git pull --ff-only origin my-branch`, 0, "")
   473  			},
   474  			expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
   475  		},
   476  		{name: "develop new branch with checkout when the branch does not exist locally",
   477  			setup: func(opts *DevelopOptions, t *testing.T) func() {
   478  				opts.Name = "my-branch"
   479  				opts.BaseBranch = "main"
   480  				opts.IssueSelector = "123"
   481  				opts.Checkout = true
   482  				return func() {}
   483  			},
   484  			remotes: map[string]string{
   485  				"origin": "OWNER/REPO",
   486  			},
   487  			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
   488  				reg.Register(
   489  					httpmock.GraphQL(`query LinkedBranch_fields\b`),
   490  					httpmock.StringResponse(featureEnabledPayload),
   491  				)
   492  				reg.Register(
   493  					httpmock.GraphQL(`query RepositoryInfo\b`),
   494  					httpmock.StringResponse(`
   495  						{ "data": { "repository": {
   496  							"id": "REPOID",
   497  							"hasIssuesEnabled": true
   498  						} } }`),
   499  				)
   500  				reg.Register(
   501  					httpmock.GraphQL(`query IssueByNumber\b`),
   502  					httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"id": "yar", "number":123, "title":"my issue"} }}}`))
   503  				reg.Register(
   504  					httpmock.GraphQL(`query BranchIssueReferenceFindBaseOid\b`),
   505  					httpmock.StringResponse(`{"data":{"repository":{"ref":{"target":{"oid":"123"}}}}}`))
   506  
   507  				reg.Register(
   508  					httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
   509  					httpmock.GraphQLQuery(`{ "data": { "createLinkedBranch": { "linkedBranch": {"id": "2", "ref": {"name": "my-branch"} } } } }`,
   510  						func(query string, inputs map[string]interface{}) {
   511  							assert.Equal(t, "REPOID", inputs["repositoryId"])
   512  							assert.Equal(t, "my-branch", inputs["name"])
   513  							assert.Equal(t, "yar", inputs["issueId"])
   514  						}),
   515  				)
   516  
   517  			},
   518  			runStubs: func(cs *run.CommandStubber) {
   519  				cs.Register(`git rev-parse --verify refs/heads/my-branch`, 1, "")
   520  				cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
   521  				cs.Register(`git checkout -b my-branch --track origin/my-branch`, 0, "")
   522  				cs.Register(`git pull --ff-only origin my-branch`, 0, "")
   523  			},
   524  			expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
   525  		},
   526  	}
   527  	for _, tt := range tests {
   528  		t.Run(tt.name, func(t *testing.T) {
   529  			reg := &httpmock.Registry{}
   530  			defer reg.Verify(t)
   531  			if tt.httpStubs != nil {
   532  				tt.httpStubs(reg, t)
   533  			}
   534  
   535  			opts := DevelopOptions{}
   536  
   537  			ios, _, stdout, stderr := iostreams.Test()
   538  
   539  			ios.SetStdoutTTY(tt.tty)
   540  			ios.SetStdinTTY(tt.tty)
   541  			ios.SetStderrTTY(tt.tty)
   542  			opts.IO = ios
   543  
   544  			opts.BaseRepo = func() (ghrepo.Interface, error) {
   545  				return ghrepo.New("OWNER", "REPO"), nil
   546  			}
   547  			opts.HttpClient = func() (*http.Client, error) {
   548  				return &http.Client{Transport: reg}, nil
   549  			}
   550  			opts.Config = func() (config.Config, error) {
   551  				return config.NewBlankConfig(), nil
   552  			}
   553  
   554  			opts.Remotes = func() (context.Remotes, error) {
   555  				if len(tt.remotes) == 0 {
   556  					return nil, errors.New("no remotes")
   557  				}
   558  				var remotes context.Remotes
   559  				for name, repo := range tt.remotes {
   560  					r, err := ghrepo.FromFullName(repo)
   561  					if err != nil {
   562  						return remotes, err
   563  					}
   564  					remotes = append(remotes, &context.Remote{
   565  						Remote: &git.Remote{Name: name},
   566  						Repo:   r,
   567  					})
   568  				}
   569  				return remotes, nil
   570  			}
   571  
   572  			opts.GitClient = &git.Client{GitPath: "some/path/git"}
   573  
   574  			cmdStubs, cmdTeardown := run.Stub()
   575  			defer cmdTeardown(t)
   576  			if tt.runStubs != nil {
   577  				tt.runStubs(cmdStubs)
   578  			}
   579  
   580  			cleanSetup := func() {}
   581  			if tt.setup != nil {
   582  				cleanSetup = tt.setup(&opts, t)
   583  			}
   584  			defer cleanSetup()
   585  
   586  			var err error
   587  			if opts.List {
   588  				err = developRunList(&opts)
   589  			} else {
   590  
   591  				err = developRunCreate(&opts)
   592  			}
   593  			output := &test.CmdOut{
   594  				OutBuf: stdout,
   595  				ErrBuf: stderr,
   596  			}
   597  			if tt.wantErr != "" {
   598  				assert.EqualError(t, err, tt.wantErr)
   599  			} else {
   600  				assert.NoError(t, err)
   601  				assert.Equal(t, tt.expectedOut, output.String())
   602  				assert.Equal(t, tt.expectedErrOut, output.Stderr())
   603  			}
   604  		})
   605  	}
   606  }