github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/issue/create/create_test.go (about)

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