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

     1  package create
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/MakeNowJust/heredoc"
    15  	"github.com/ungtb10d/cli/v2/internal/browser"
    16  	"github.com/ungtb10d/cli/v2/internal/config"
    17  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    18  	"github.com/ungtb10d/cli/v2/internal/run"
    19  	prShared "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared"
    20  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    21  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    22  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    23  	"github.com/ungtb10d/cli/v2/pkg/prompt"
    24  	"github.com/ungtb10d/cli/v2/test"
    25  	"github.com/google/shlex"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func TestNewCmdCreate(t *testing.T) {
    31  	tmpFile := filepath.Join(t.TempDir(), "my-body.md")
    32  	err := os.WriteFile(tmpFile, []byte("a body from file"), 0600)
    33  	require.NoError(t, err)
    34  
    35  	tests := []struct {
    36  		name      string
    37  		tty       bool
    38  		stdin     string
    39  		cli       string
    40  		wantsErr  bool
    41  		wantsOpts CreateOptions
    42  	}{
    43  		{
    44  			name:     "empty non-tty",
    45  			tty:      false,
    46  			cli:      "",
    47  			wantsErr: true,
    48  		},
    49  		{
    50  			name:     "only title non-tty",
    51  			tty:      false,
    52  			cli:      "-t mytitle",
    53  			wantsErr: true,
    54  		},
    55  		{
    56  			name:     "empty tty",
    57  			tty:      true,
    58  			cli:      "",
    59  			wantsErr: false,
    60  			wantsOpts: CreateOptions{
    61  				Title:       "",
    62  				Body:        "",
    63  				RecoverFile: "",
    64  				WebMode:     false,
    65  				Interactive: true,
    66  			},
    67  		},
    68  		{
    69  			name:     "body from stdin",
    70  			tty:      false,
    71  			stdin:    "this is on standard input",
    72  			cli:      "-t mytitle -F -",
    73  			wantsErr: false,
    74  			wantsOpts: CreateOptions{
    75  				Title:       "mytitle",
    76  				Body:        "this is on standard input",
    77  				RecoverFile: "",
    78  				WebMode:     false,
    79  				Interactive: false,
    80  			},
    81  		},
    82  		{
    83  			name:     "body from file",
    84  			tty:      false,
    85  			cli:      fmt.Sprintf("-t mytitle -F '%s'", tmpFile),
    86  			wantsErr: false,
    87  			wantsOpts: CreateOptions{
    88  				Title:       "mytitle",
    89  				Body:        "a body from file",
    90  				RecoverFile: "",
    91  				WebMode:     false,
    92  				Interactive: false,
    93  			},
    94  		},
    95  	}
    96  	for _, tt := range tests {
    97  		t.Run(tt.name, func(t *testing.T) {
    98  			ios, stdin, stdout, stderr := iostreams.Test()
    99  			if tt.stdin != "" {
   100  				_, _ = stdin.WriteString(tt.stdin)
   101  			} else if tt.tty {
   102  				ios.SetStdinTTY(true)
   103  				ios.SetStdoutTTY(true)
   104  			}
   105  
   106  			f := &cmdutil.Factory{
   107  				IOStreams: ios,
   108  			}
   109  
   110  			var opts *CreateOptions
   111  			cmd := NewCmdCreate(f, func(o *CreateOptions) error {
   112  				opts = o
   113  				return nil
   114  			})
   115  
   116  			args, err := shlex.Split(tt.cli)
   117  			require.NoError(t, err)
   118  			cmd.SetArgs(args)
   119  			cmd.SetOut(io.Discard)
   120  			cmd.SetErr(io.Discard)
   121  			_, err = cmd.ExecuteC()
   122  			if tt.wantsErr {
   123  				assert.Error(t, err)
   124  				return
   125  			} else {
   126  				require.NoError(t, err)
   127  			}
   128  
   129  			assert.Equal(t, "", stdout.String())
   130  			assert.Equal(t, "", stderr.String())
   131  
   132  			assert.Equal(t, tt.wantsOpts.Body, opts.Body)
   133  			assert.Equal(t, tt.wantsOpts.Title, opts.Title)
   134  			assert.Equal(t, tt.wantsOpts.RecoverFile, opts.RecoverFile)
   135  			assert.Equal(t, tt.wantsOpts.WebMode, opts.WebMode)
   136  			assert.Equal(t, tt.wantsOpts.Interactive, opts.Interactive)
   137  		})
   138  	}
   139  }
   140  
   141  func Test_createRun(t *testing.T) {
   142  	tests := []struct {
   143  		name        string
   144  		opts        CreateOptions
   145  		httpStubs   func(*httpmock.Registry)
   146  		wantsStdout string
   147  		wantsStderr string
   148  		wantsBrowse string
   149  		wantsErr    string
   150  	}{
   151  		{
   152  			name: "no args",
   153  			opts: CreateOptions{
   154  				WebMode: true,
   155  			},
   156  			wantsBrowse: "https://github.com/OWNER/REPO/issues/new",
   157  			wantsStderr: "Opening github.com/OWNER/REPO/issues/new in your browser.\n",
   158  		},
   159  		{
   160  			name: "title and body",
   161  			opts: CreateOptions{
   162  				WebMode: true,
   163  				Title:   "myissue",
   164  				Body:    "hello cli",
   165  			},
   166  			wantsBrowse: "https://github.com/OWNER/REPO/issues/new?body=hello+cli&title=myissue",
   167  			wantsStderr: "Opening github.com/OWNER/REPO/issues/new in your browser.\n",
   168  		},
   169  		{
   170  			name: "assignee",
   171  			opts: CreateOptions{
   172  				WebMode:   true,
   173  				Assignees: []string{"monalisa"},
   174  			},
   175  			wantsBrowse: "https://github.com/OWNER/REPO/issues/new?assignees=monalisa&body=",
   176  			wantsStderr: "Opening github.com/OWNER/REPO/issues/new in your browser.\n",
   177  		},
   178  		{
   179  			name: "@me",
   180  			opts: CreateOptions{
   181  				WebMode:   true,
   182  				Assignees: []string{"@me"},
   183  			},
   184  			httpStubs: func(r *httpmock.Registry) {
   185  				r.Register(
   186  					httpmock.GraphQL(`query UserCurrent\b`),
   187  					httpmock.StringResponse(`
   188  					{ "data": {
   189  						"viewer": { "login": "MonaLisa" }
   190  					} }`))
   191  			},
   192  			wantsBrowse: "https://github.com/OWNER/REPO/issues/new?assignees=MonaLisa&body=",
   193  			wantsStderr: "Opening github.com/OWNER/REPO/issues/new in your browser.\n",
   194  		},
   195  		{
   196  			name: "project",
   197  			opts: CreateOptions{
   198  				WebMode:  true,
   199  				Projects: []string{"cleanup"},
   200  			},
   201  			httpStubs: func(r *httpmock.Registry) {
   202  				r.Register(
   203  					httpmock.GraphQL(`query RepositoryProjectList\b`),
   204  					httpmock.StringResponse(`
   205  					{ "data": { "repository": { "projects": {
   206  						"nodes": [
   207  							{ "name": "Cleanup", "id": "CLEANUPID", "resourcePath": "/OWNER/REPO/projects/1" }
   208  						],
   209  						"pageInfo": { "hasNextPage": false }
   210  					} } } }`))
   211  				r.Register(
   212  					httpmock.GraphQL(`query OrganizationProjectList\b`),
   213  					httpmock.StringResponse(`
   214  					{ "data": { "organization": { "projects": {
   215  						"nodes": [
   216  							{ "name": "Triage", "id": "TRIAGEID", "resourcePath": "/orgs/ORG/projects/1"  }
   217  						],
   218  						"pageInfo": { "hasNextPage": false }
   219  					} } } }`))
   220  			},
   221  			wantsBrowse: "https://github.com/OWNER/REPO/issues/new?body=&projects=OWNER%2FREPO%2F1",
   222  			wantsStderr: "Opening github.com/OWNER/REPO/issues/new in your browser.\n",
   223  		},
   224  		{
   225  			name: "has templates",
   226  			opts: CreateOptions{
   227  				WebMode: true,
   228  			},
   229  			httpStubs: func(r *httpmock.Registry) {
   230  				r.Register(
   231  					httpmock.GraphQL(`query IssueTemplates\b`),
   232  					httpmock.StringResponse(`
   233  					{ "data": { "repository": { "issueTemplates": [
   234  						{ "name": "Bug report",
   235  							"body": "Does not work :((" },
   236  						{ "name": "Submit a request",
   237  							"body": "I have a suggestion for an enhancement" }
   238  					] } } }`),
   239  				)
   240  			},
   241  			wantsBrowse: "https://github.com/OWNER/REPO/issues/new/choose",
   242  			wantsStderr: "Opening github.com/OWNER/REPO/issues/new/choose in your browser.\n",
   243  		},
   244  		{
   245  			name: "too long body",
   246  			opts: CreateOptions{
   247  				WebMode: true,
   248  				Body:    strings.Repeat("A", 9216),
   249  			},
   250  			wantsErr: "cannot open in browser: maximum URL length exceeded",
   251  		},
   252  	}
   253  	for _, tt := range tests {
   254  		t.Run(tt.name, func(t *testing.T) {
   255  			httpReg := &httpmock.Registry{}
   256  			defer httpReg.Verify(t)
   257  			if tt.httpStubs != nil {
   258  				tt.httpStubs(httpReg)
   259  			}
   260  
   261  			ios, _, stdout, stderr := iostreams.Test()
   262  			ios.SetStdoutTTY(true)
   263  			opts := &tt.opts
   264  			opts.IO = ios
   265  			opts.HttpClient = func() (*http.Client, error) {
   266  				return &http.Client{Transport: httpReg}, nil
   267  			}
   268  			opts.BaseRepo = func() (ghrepo.Interface, error) {
   269  				return ghrepo.New("OWNER", "REPO"), nil
   270  			}
   271  			browser := &browser.Stub{}
   272  			opts.Browser = browser
   273  
   274  			err := createRun(opts)
   275  			if tt.wantsErr == "" {
   276  				require.NoError(t, err)
   277  			} else {
   278  				assert.EqualError(t, err, tt.wantsErr)
   279  				return
   280  			}
   281  
   282  			assert.Equal(t, tt.wantsStdout, stdout.String())
   283  			assert.Equal(t, tt.wantsStderr, stderr.String())
   284  			browser.Verify(t, tt.wantsBrowse)
   285  		})
   286  	}
   287  }
   288  
   289  /*** LEGACY TESTS ***/
   290  
   291  func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
   292  	return runCommandWithRootDirOverridden(rt, isTTY, cli, "")
   293  }
   294  
   295  func runCommandWithRootDirOverridden(rt http.RoundTripper, isTTY bool, cli string, rootDir string) (*test.CmdOut, error) {
   296  	ios, _, stdout, stderr := iostreams.Test()
   297  	ios.SetStdoutTTY(isTTY)
   298  	ios.SetStdinTTY(isTTY)
   299  	ios.SetStderrTTY(isTTY)
   300  
   301  	browser := &browser.Stub{}
   302  	factory := &cmdutil.Factory{
   303  		IOStreams: ios,
   304  		HttpClient: func() (*http.Client, error) {
   305  			return &http.Client{Transport: rt}, nil
   306  		},
   307  		Config: func() (config.Config, error) {
   308  			return config.NewBlankConfig(), nil
   309  		},
   310  		BaseRepo: func() (ghrepo.Interface, error) {
   311  			return ghrepo.New("OWNER", "REPO"), nil
   312  		},
   313  		Browser: browser,
   314  	}
   315  
   316  	cmd := NewCmdCreate(factory, func(opts *CreateOptions) error {
   317  		opts.RootDirOverride = rootDir
   318  		return createRun(opts)
   319  	})
   320  
   321  	argv, err := shlex.Split(cli)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	cmd.SetArgs(argv)
   326  
   327  	cmd.SetIn(&bytes.Buffer{})
   328  	cmd.SetOut(io.Discard)
   329  	cmd.SetErr(io.Discard)
   330  
   331  	_, err = cmd.ExecuteC()
   332  	return &test.CmdOut{
   333  		OutBuf:     stdout,
   334  		ErrBuf:     stderr,
   335  		BrowsedURL: browser.BrowsedURL(),
   336  	}, err
   337  }
   338  
   339  func TestIssueCreate(t *testing.T) {
   340  	http := &httpmock.Registry{}
   341  	defer http.Verify(t)
   342  
   343  	http.Register(
   344  		httpmock.GraphQL(`query RepositoryInfo\b`),
   345  		httpmock.StringResponse(`
   346  			{ "data": { "repository": {
   347  				"id": "REPOID",
   348  				"hasIssuesEnabled": true
   349  			} } }`),
   350  	)
   351  	http.Register(
   352  		httpmock.GraphQL(`mutation IssueCreate\b`),
   353  		httpmock.GraphQLMutation(`
   354  				{ "data": { "createIssue": { "issue": {
   355  					"URL": "https://github.com/OWNER/REPO/issues/12"
   356  				} } } }`,
   357  			func(inputs map[string]interface{}) {
   358  				assert.Equal(t, inputs["repositoryId"], "REPOID")
   359  				assert.Equal(t, inputs["title"], "hello")
   360  				assert.Equal(t, inputs["body"], "cash rules everything around me")
   361  			}),
   362  	)
   363  
   364  	output, err := runCommand(http, true, `-t hello -b "cash rules everything around me"`)
   365  	if err != nil {
   366  		t.Errorf("error running command `issue create`: %v", err)
   367  	}
   368  
   369  	assert.Equal(t, "https://github.com/OWNER/REPO/issues/12\n", output.String())
   370  }
   371  
   372  func TestIssueCreate_recover(t *testing.T) {
   373  	http := &httpmock.Registry{}
   374  	defer http.Verify(t)
   375  
   376  	http.Register(
   377  		httpmock.GraphQL(`query RepositoryInfo\b`),
   378  		httpmock.StringResponse(`
   379  			{ "data": { "repository": {
   380  				"id": "REPOID",
   381  				"hasIssuesEnabled": true
   382  			} } }`))
   383  	http.Register(
   384  		httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
   385  		httpmock.StringResponse(`
   386  		{ "data": {
   387  			"u000": { "login": "MonaLisa", "id": "MONAID" },
   388  			"repository": {
   389  				"l000": { "name": "bug", "id": "BUGID" },
   390  				"l001": { "name": "TODO", "id": "TODOID" }
   391  			}
   392  		} }
   393  		`))
   394  	http.Register(
   395  		httpmock.GraphQL(`mutation IssueCreate\b`),
   396  		httpmock.GraphQLMutation(`
   397  		{ "data": { "createIssue": { "issue": {
   398  			"URL": "https://github.com/OWNER/REPO/issues/12"
   399  		} } } }
   400  	`, func(inputs map[string]interface{}) {
   401  			assert.Equal(t, "recovered title", inputs["title"])
   402  			assert.Equal(t, "recovered body", inputs["body"])
   403  			assert.Equal(t, []interface{}{"BUGID", "TODOID"}, inputs["labelIds"])
   404  		}))
   405  
   406  	//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
   407  	as := prompt.NewAskStubber(t)
   408  
   409  	as.StubPrompt("Title").AnswerDefault()
   410  	as.StubPrompt("Body").AnswerDefault()
   411  	as.StubPrompt("What's next?").AnswerWith("Submit")
   412  
   413  	tmpfile, err := os.CreateTemp(t.TempDir(), "testrecover*")
   414  	assert.NoError(t, err)
   415  	defer tmpfile.Close()
   416  
   417  	state := prShared.IssueMetadataState{
   418  		Title:  "recovered title",
   419  		Body:   "recovered body",
   420  		Labels: []string{"bug", "TODO"},
   421  	}
   422  
   423  	data, err := json.Marshal(state)
   424  	assert.NoError(t, err)
   425  
   426  	_, err = tmpfile.Write(data)
   427  	assert.NoError(t, err)
   428  
   429  	args := fmt.Sprintf("--recover '%s'", tmpfile.Name())
   430  
   431  	output, err := runCommandWithRootDirOverridden(http, true, args, "")
   432  	if err != nil {
   433  		t.Errorf("error running command `issue create`: %v", err)
   434  	}
   435  
   436  	assert.Equal(t, "https://github.com/OWNER/REPO/issues/12\n", output.String())
   437  }
   438  
   439  func TestIssueCreate_nonLegacyTemplate(t *testing.T) {
   440  	http := &httpmock.Registry{}
   441  	defer http.Verify(t)
   442  
   443  	http.Register(
   444  		httpmock.GraphQL(`query RepositoryInfo\b`),
   445  		httpmock.StringResponse(`
   446  			{ "data": { "repository": {
   447  				"id": "REPOID",
   448  				"hasIssuesEnabled": true
   449  			} } }`),
   450  	)
   451  	http.Register(
   452  		httpmock.GraphQL(`query IssueTemplates\b`),
   453  		httpmock.StringResponse(`
   454  			{ "data": { "repository": { "issueTemplates": [
   455  				{ "name": "Bug report",
   456  				  "body": "Does not work :((" },
   457  				{ "name": "Submit a request",
   458  				  "body": "I have a suggestion for an enhancement" }
   459  			] } } }`),
   460  	)
   461  	http.Register(
   462  		httpmock.GraphQL(`mutation IssueCreate\b`),
   463  		httpmock.GraphQLMutation(`
   464  			{ "data": { "createIssue": { "issue": {
   465  				"URL": "https://github.com/OWNER/REPO/issues/12"
   466  			} } } }`,
   467  			func(inputs map[string]interface{}) {
   468  				assert.Equal(t, inputs["repositoryId"], "REPOID")
   469  				assert.Equal(t, inputs["title"], "hello")
   470  				assert.Equal(t, inputs["body"], "I have a suggestion for an enhancement")
   471  			}),
   472  	)
   473  
   474  	//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
   475  	as := prompt.NewAskStubber(t)
   476  
   477  	as.StubPrompt("Choose a template").AnswerWith("Submit a request")
   478  	as.StubPrompt("Body").AnswerDefault()
   479  	as.StubPrompt("What's next?").
   480  		AssertOptions([]string{"Submit", "Continue in browser", "Cancel"}).
   481  		AnswerWith("Submit")
   482  
   483  	output, err := runCommandWithRootDirOverridden(http, true, `-t hello`, "./fixtures/repoWithNonLegacyIssueTemplates")
   484  	if err != nil {
   485  		t.Errorf("error running command `issue create`: %v", err)
   486  	}
   487  
   488  	assert.Equal(t, "https://github.com/OWNER/REPO/issues/12\n", output.String())
   489  	assert.Equal(t, "", output.BrowsedURL)
   490  }
   491  
   492  func TestIssueCreate_continueInBrowser(t *testing.T) {
   493  	http := &httpmock.Registry{}
   494  	defer http.Verify(t)
   495  
   496  	http.Register(
   497  		httpmock.GraphQL(`query RepositoryInfo\b`),
   498  		httpmock.StringResponse(`
   499  			{ "data": { "repository": {
   500  				"id": "REPOID",
   501  				"hasIssuesEnabled": true
   502  			} } }`),
   503  	)
   504  
   505  	//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
   506  	as := prompt.NewAskStubber(t)
   507  
   508  	as.StubPrompt("Title").AnswerWith("hello")
   509  	as.StubPrompt("What's next?").AnswerWith("Continue in browser")
   510  
   511  	_, cmdTeardown := run.Stub()
   512  	defer cmdTeardown(t)
   513  
   514  	output, err := runCommand(http, true, `-b body`)
   515  	if err != nil {
   516  		t.Errorf("error running command `issue create`: %v", err)
   517  	}
   518  
   519  	assert.Equal(t, "", output.String())
   520  	assert.Equal(t, heredoc.Doc(`
   521  
   522  		Creating issue in OWNER/REPO
   523  
   524  		Opening github.com/OWNER/REPO/issues/new in your browser.
   525  	`), output.Stderr())
   526  	assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?body=body&title=hello", output.BrowsedURL)
   527  }
   528  
   529  func TestIssueCreate_metadata(t *testing.T) {
   530  	http := &httpmock.Registry{}
   531  	defer http.Verify(t)
   532  
   533  	http.StubRepoInfoResponse("OWNER", "REPO", "main")
   534  	http.Register(
   535  		httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
   536  		httpmock.StringResponse(`
   537  		{ "data": {
   538  			"u000": { "login": "MonaLisa", "id": "MONAID" },
   539  			"repository": {
   540  				"l000": { "name": "bug", "id": "BUGID" },
   541  				"l001": { "name": "TODO", "id": "TODOID" }
   542  			}
   543  		} }
   544  		`))
   545  	http.Register(
   546  		httpmock.GraphQL(`query RepositoryMilestoneList\b`),
   547  		httpmock.StringResponse(`
   548  		{ "data": { "repository": { "milestones": {
   549  			"nodes": [
   550  				{ "title": "GA", "id": "GAID" },
   551  				{ "title": "Big One.oh", "id": "BIGONEID" }
   552  			],
   553  			"pageInfo": { "hasNextPage": false }
   554  		} } } }
   555  		`))
   556  	http.Register(
   557  		httpmock.GraphQL(`query RepositoryProjectList\b`),
   558  		httpmock.StringResponse(`
   559  		{ "data": { "repository": { "projects": {
   560  			"nodes": [
   561  				{ "name": "Cleanup", "id": "CLEANUPID" },
   562  				{ "name": "Roadmap", "id": "ROADMAPID" }
   563  			],
   564  			"pageInfo": { "hasNextPage": false }
   565  		} } } }
   566  		`))
   567  	http.Register(
   568  		httpmock.GraphQL(`query OrganizationProjectList\b`),
   569  		httpmock.StringResponse(`
   570  		{	"data": { "organization": null },
   571  			"errors": [{
   572  				"type": "NOT_FOUND",
   573  				"path": [ "organization" ],
   574  				"message": "Could not resolve to an Organization with the login of 'OWNER'."
   575  			}]
   576  		}
   577  		`))
   578  	http.Register(
   579  		httpmock.GraphQL(`mutation IssueCreate\b`),
   580  		httpmock.GraphQLMutation(`
   581  		{ "data": { "createIssue": { "issue": {
   582  			"URL": "https://github.com/OWNER/REPO/issues/12"
   583  		} } } }
   584  	`, func(inputs map[string]interface{}) {
   585  			assert.Equal(t, "TITLE", inputs["title"])
   586  			assert.Equal(t, "BODY", inputs["body"])
   587  			assert.Equal(t, []interface{}{"MONAID"}, inputs["assigneeIds"])
   588  			assert.Equal(t, []interface{}{"BUGID", "TODOID"}, inputs["labelIds"])
   589  			assert.Equal(t, []interface{}{"ROADMAPID"}, inputs["projectIds"])
   590  			assert.Equal(t, "BIGONEID", inputs["milestoneId"])
   591  			if v, ok := inputs["userIds"]; ok {
   592  				t.Errorf("did not expect userIds: %v", v)
   593  			}
   594  			if v, ok := inputs["teamIds"]; ok {
   595  				t.Errorf("did not expect teamIds: %v", v)
   596  			}
   597  		}))
   598  
   599  	output, err := runCommand(http, true, `-t TITLE -b BODY -a monalisa -l bug -l todo -p roadmap -m 'big one.oh'`)
   600  	if err != nil {
   601  		t.Errorf("error running command `issue create`: %v", err)
   602  	}
   603  
   604  	assert.Equal(t, "https://github.com/OWNER/REPO/issues/12\n", output.String())
   605  }
   606  
   607  func TestIssueCreate_disabledIssues(t *testing.T) {
   608  	http := &httpmock.Registry{}
   609  	defer http.Verify(t)
   610  
   611  	http.Register(
   612  		httpmock.GraphQL(`query RepositoryInfo\b`),
   613  		httpmock.StringResponse(`
   614  			{ "data": { "repository": {
   615  				"id": "REPOID",
   616  				"hasIssuesEnabled": false
   617  			} } }`),
   618  	)
   619  
   620  	_, err := runCommand(http, true, `-t heres -b johnny`)
   621  	if err == nil || err.Error() != "the 'OWNER/REPO' repository has disabled issues" {
   622  		t.Errorf("error running command `issue create`: %v", err)
   623  	}
   624  }
   625  
   626  func TestIssueCreate_AtMeAssignee(t *testing.T) {
   627  	http := &httpmock.Registry{}
   628  	defer http.Verify(t)
   629  
   630  	http.Register(
   631  		httpmock.GraphQL(`query UserCurrent\b`),
   632  		httpmock.StringResponse(`
   633  		{ "data": {
   634  			"viewer": { "login": "MonaLisa" }
   635  		} }
   636  		`),
   637  	)
   638  	http.Register(
   639  		httpmock.GraphQL(`query RepositoryInfo\b`),
   640  		httpmock.StringResponse(`
   641  		{ "data": { "repository": {
   642  			"id": "REPOID",
   643  			"hasIssuesEnabled": true
   644  		} } }
   645  	`))
   646  	http.Register(
   647  		httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
   648  		httpmock.StringResponse(`
   649  		{ "data": {
   650  			"u000": { "login": "MonaLisa", "id": "MONAID" },
   651  			"u001": { "login": "SomeOneElse", "id": "SOMEID" },
   652  			"repository": {
   653  				"l000": { "name": "bug", "id": "BUGID" },
   654  				"l001": { "name": "TODO", "id": "TODOID" }
   655  			}
   656  		} }
   657  		`),
   658  	)
   659  	http.Register(
   660  		httpmock.GraphQL(`mutation IssueCreate\b`),
   661  		httpmock.GraphQLMutation(`
   662  		{ "data": { "createIssue": { "issue": {
   663  			"URL": "https://github.com/OWNER/REPO/issues/12"
   664  		} } } }
   665  	`, func(inputs map[string]interface{}) {
   666  			assert.Equal(t, "hello", inputs["title"])
   667  			assert.Equal(t, "cash rules everything around me", inputs["body"])
   668  			assert.Equal(t, []interface{}{"MONAID", "SOMEID"}, inputs["assigneeIds"])
   669  		}))
   670  
   671  	output, err := runCommand(http, true, `-a @me -a someoneelse -t hello -b "cash rules everything around me"`)
   672  	if err != nil {
   673  		t.Errorf("error running command `issue create`: %v", err)
   674  	}
   675  
   676  	assert.Equal(t, "https://github.com/OWNER/REPO/issues/12\n", output.String())
   677  }