diff --git a/api/queries_pr.go b/api/queries_pr.go index 29342521f8c..c068c81f7a8 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -468,10 +468,11 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter createPullRequest(input: $input) { pullRequest { id + number url } } - }` + }` inputParams := map[string]interface{}{ "repositoryId": repo.ID, diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 1a3eba7d882..53e56effc0e 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -73,9 +73,12 @@ type CreateOptions struct { MaintainerCanModify bool Template string - DryRun bool + DryRun bool + Exporter cmdutil.Exporter } +var createOutputFields = []string{"id", "url", "number"} + // creationRefs is an interface that provides the necessary information for creating a pull request in the API. // Upcasting to concrete implementations can provide further context on other operations (forking and pushing). type creationRefs interface { @@ -331,6 +334,10 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co return cmdutil.FlagErrorf("`--dry-run` is not supported when using `--web`") } + if opts.DryRun && opts.Exporter != nil { + return cmdutil.FlagErrorf("`--dry-run` is not supported with `--json`") + } + if runF != nil { return runF(opts) } @@ -359,6 +366,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co fl.StringVar(&opts.RecoverFile, "recover", "", "Recover input from a failed run of create") fl.StringVarP(&opts.Template, "template", "T", "", "Template `file` to use as starting body text") fl.BoolVar(&opts.DryRun, "dry-run", false, "Print details instead of creating the PR. May still push git changes.") + cmdutil.AddJSONAndJQFlags(cmd, &opts.Exporter, createOutputFields) _ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "base", "head") @@ -1065,7 +1073,7 @@ func submitPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadataS opts.IO.StartProgressIndicator() pr, err := api.CreatePullRequest(client, ctx.PRRefs.BaseRepo(), params) opts.IO.StopProgressIndicator() - if pr != nil { + if pr != nil && opts.Exporter == nil { fmt.Fprintln(opts.IO.Out, pr.URL) } if err != nil { @@ -1074,6 +1082,9 @@ func submitPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadataS } return fmt.Errorf("pull request create failed: %w", err) } + if opts.Exporter != nil { + return opts.Exporter.Write(opts.IO, pr) + } return nil } diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 5bad889b675..8c0b02f491a 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -24,12 +24,21 @@ import ( "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" + "github.com/cli/cli/v2/pkg/jsonfieldstest" "github.com/cli/cli/v2/test" "github.com/google/shlex" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestJSONFields(t *testing.T) { + jsonfieldstest.ExpectCommandToSupportJSONFields(t, NewCmdCreate, []string{ + "id", + "number", + "url", + }) +} + func TestNewCmdCreate(t *testing.T) { tmpFile := filepath.Join(t.TempDir(), "my-body.md") err := os.WriteFile(tmpFile, []byte("a body from file"), 0600) @@ -203,6 +212,24 @@ func TestNewCmdCreate(t *testing.T) { cli: "--web --dry-run", wantsErr: true, }, + { + name: "dry-run and json", + tty: false, + cli: "--title mytitle --body '' --dry-run --json id", + wantsErr: true, + }, + { + name: "jq without json", + tty: false, + cli: "--title mytitle --body '' --jq .number", + wantsErr: true, + }, + { + name: "web and json", + tty: false, + cli: "--title mytitle --body '' --web --json id", + wantsErr: true, + }, { name: "editor by cli", tty: true, @@ -364,6 +391,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } }`, func(input map[string]interface{}) { @@ -384,6 +412,145 @@ func Test_createRun(t *testing.T) { }, expectedOut: "https://github.com/OWNER/REPO/pull/12\n", }, + { + name: "nontty json output", + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { "data": { "createPullRequest": { "pullRequest": { + "ID": "PR_kwDOA1KnGc6WELLE", + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } }`, + func(input map[string]interface{}) { + assert.Equal(t, "REPOID", input["repositoryId"]) + assert.Equal(t, "my title", input["title"]) + assert.Equal(t, "my body", input["body"]) + })) + }, + setup: func(opts *CreateOptions, t *testing.T) func() { + opts.TitleProvided = true + opts.BodyProvided = true + opts.Title = "my title" + opts.Body = "my body" + opts.HeadBranch = "feature" + exporter := cmdutil.NewJSONExporter() + exporter.SetFields(createOutputFields) + opts.Exporter = exporter + return func() {} + }, + expectedOut: "{\"id\":\"PR_kwDOA1KnGc6WELLE\",\"number\":12,\"url\":\"https://github.com/OWNER/REPO/pull/12\"}\n", + }, + { + name: "tty json output", + tty: true, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { "data": { "createPullRequest": { "pullRequest": { + "ID": "PR_kwDOA1KnGc6WELLE", + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } }`, + func(input map[string]interface{}) {})) + }, + setup: func(opts *CreateOptions, t *testing.T) func() { + opts.TitleProvided = true + opts.BodyProvided = true + opts.Title = "my title" + opts.Body = "my body" + opts.HeadBranch = "feature" + exporter := cmdutil.NewJSONExporter() + exporter.SetFields(createOutputFields) + opts.Exporter = exporter + return func() {} + }, + expectedOut: "{\"id\":\"PR_kwDOA1KnGc6WELLE\",\"number\":12,\"url\":\"https://github.com/OWNER/REPO/pull/12\"}\n", + expectedErrOut: "\nCreating pull request for feature into master in OWNER/REPO\n\n", + }, + { + name: "json output with jq filter", + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { "data": { "createPullRequest": { "pullRequest": { + "ID": "PR_kwDOA1KnGc6WELLE", + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } }`, + func(input map[string]interface{}) {})) + }, + setup: func(opts *CreateOptions, t *testing.T) func() { + opts.TitleProvided = true + opts.BodyProvided = true + opts.Title = "my title" + opts.Body = "my body" + opts.HeadBranch = "feature" + exporter := cmdutil.NewJSONExporter() + exporter.SetFields(createOutputFields) + exporter.SetFilter(".number") + opts.Exporter = exporter + return func() {} + }, + expectedOut: "12\n", + }, + { + name: "json output with pr template", + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { "data": { "createPullRequest": { "pullRequest": { + "ID": "PR_kwDOA1KnGc6WELLE", + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } }`, + func(input map[string]interface{}) { + assert.Equal(t, "my title", input["title"]) + })) + }, + setup: func(opts *CreateOptions, t *testing.T) func() { + opts.TitleProvided = true + opts.BodyProvided = true + opts.Title = "my title" + opts.Body = "my body" + opts.HeadBranch = "feature" + opts.Template = "bug_fix.md" + exporter := cmdutil.NewJSONExporter() + exporter.SetFields(createOutputFields) + opts.Exporter = exporter + return func() {} + }, + expectedOut: "{\"id\":\"PR_kwDOA1KnGc6WELLE\",\"number\":12,\"url\":\"https://github.com/OWNER/REPO/pull/12\"}\n", + }, + { + name: "json output does not print URL", + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } }`, + func(input map[string]interface{}) {})) + }, + setup: func(opts *CreateOptions, t *testing.T) func() { + opts.TitleProvided = true + opts.BodyProvided = true + opts.Title = "my title" + opts.Body = "my body" + opts.HeadBranch = "feature" + exporter := cmdutil.NewJSONExporter() + exporter.SetFields([]string{"number"}) + opts.Exporter = exporter + return func() {} + }, + expectedOut: "{\"number\":12}\n", + }, { name: "same head and base branch should error", setup: func(opts *CreateOptions, t *testing.T) func() { @@ -512,7 +679,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } }`, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) assert.Equal(t, "my title", input["title"].(string)) @@ -559,7 +727,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } }`, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) assert.Equal(t, "my title", input["title"].(string)) @@ -608,7 +777,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { "id": "PullRequest#1", - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) @@ -670,7 +840,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { assert.Equal(t, false, input["maintainerCanModify"].(bool)) @@ -727,7 +898,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" }}}}`, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) assert.Equal(t, "master", input["baseRefName"].(string)) @@ -789,7 +961,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } }`, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) assert.Equal(t, "master", input["baseRefName"].(string)) @@ -820,7 +993,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) @@ -867,7 +1041,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { assert.Equal(t, "my title", input["title"].(string)) @@ -944,7 +1119,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { "id": "NEWPULLID", - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(inputs map[string]interface{}) { assert.Equal(t, "TITLE", inputs["title"]) @@ -1099,7 +1275,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { assert.Equal(t, true, input["draft"].(bool)) @@ -1161,6 +1338,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { @@ -1289,6 +1467,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } } @@ -1325,6 +1504,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } } @@ -1361,6 +1541,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } } @@ -1383,6 +1564,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } } @@ -1437,7 +1619,8 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) @@ -1468,6 +1651,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } }`, func(input map[string]interface{}) { @@ -1504,6 +1688,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12", "id": "NEWPULLID" } } } }`, @@ -1540,6 +1725,7 @@ func Test_createRun(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12", "id": "NEWPULLID" } } } }`, @@ -1861,6 +2047,7 @@ func Test_createRun_GHES(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12", "id": "NEWPULLID" } } } }`, @@ -1924,6 +2111,7 @@ func Test_createRun_GHES(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12", "id": "NEWPULLID" } } } }`, @@ -2059,6 +2247,7 @@ func Test_createRun_GHES(t *testing.T) { httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { "id": "NEWPULLID", + "Number": 12, "URL": "https://github.com/OWNER/REPO/pull/12" } } } } `, @@ -2205,7 +2394,8 @@ func TestRemoteGuessing(t *testing.T) { httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` { "data": { "createPullRequest": { "pullRequest": { - "URL": "https://github.com/OWNER/REPO/pull/12" + "Number": 12, + "URL": "https://github.com/OWNER/REPO/pull/12" } } } }`, func(input map[string]interface{}) { assert.Equal(t, "REPOID", input["repositoryId"].(string)) assert.Equal(t, "master", input["baseRefName"].(string)) diff --git a/pkg/cmdutil/json_flags.go b/pkg/cmdutil/json_flags.go index 579d385520e..d0427a12eda 100644 --- a/pkg/cmdutil/json_flags.go +++ b/pkg/cmdutil/json_flags.go @@ -29,7 +29,7 @@ func AddJSONFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) { addJqFlag(f, "q") addTemplateFlag(f, "t") - setupJsonFlags(cmd, exportTarget, fields) + setupJsonFlags(cmd, exportTarget, fields, true) } func AddJSONFlagsWithoutShorthand(cmd *cobra.Command, exportTarget *Exporter, fields []string) { @@ -38,7 +38,18 @@ func AddJSONFlagsWithoutShorthand(cmd *cobra.Command, exportTarget *Exporter, fi addJqFlag(f, "") addTemplateFlag(f, "") - setupJsonFlags(cmd, exportTarget, fields) + setupJsonFlags(cmd, exportTarget, fields, true) +} + +// AddJSONAndJQFlags adds --json and --jq flags, but not --template. Shorthands are also omitted. +// +// Meant to be used in cases where --template (or -t) is already taken. +func AddJSONAndJQFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) { + f := cmd.Flags() + addJsonFlag(f) + addJqFlag(f, "") + + setupJsonFlags(cmd, exportTarget, fields, false) } func addJsonFlag(f *pflag.FlagSet) { @@ -51,7 +62,7 @@ func addTemplateFlag(f *pflag.FlagSet, shorthand string) { f.StringP("template", shorthand, "", "Format JSON output using a Go template; see \"gh help formatting\"") } -func setupJsonFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) { +func setupJsonFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string, withTemplateFlag bool) { _ = cmd.RegisterFlagCompletionFunc("json", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var results []string @@ -77,7 +88,7 @@ func setupJsonFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) return err } } - if export, err := checkJSONFlags(c); err == nil { + if export, err := checkJSONFlags(c, withTemplateFlag); err == nil { if export == nil { *exportTarget = nil } else { @@ -118,13 +129,19 @@ func setupJsonFlags(cmd *cobra.Command, exportTarget *Exporter, fields []string) cmd.Annotations["help:json-fields"] = strings.Join(fields, ",") } -func checkJSONFlags(cmd *cobra.Command) (*jsonExporter, error) { +func checkJSONFlags(cmd *cobra.Command, withTemplateFlag bool) (*jsonExporter, error) { f := cmd.Flags() jsonFlag := f.Lookup("json") jqFlag := f.Lookup("jq") - tplFlag := f.Lookup("template") webFlag := f.Lookup("web") + var tplFlag *pflag.Flag + var templateValue string + if withTemplateFlag { + tplFlag = f.Lookup("template") + templateValue = tplFlag.Value.String() + } + if jsonFlag.Changed { if webFlag != nil && webFlag.Changed { return nil, errors.New("cannot use `--web` with `--json`") @@ -133,11 +150,11 @@ func checkJSONFlags(cmd *cobra.Command) (*jsonExporter, error) { return &jsonExporter{ fields: jv.GetSlice(), filter: jqFlag.Value.String(), - template: tplFlag.Value.String(), + template: templateValue, }, nil } else if jqFlag.Changed { return nil, errors.New("cannot use `--jq` without specifying `--json`") - } else if tplFlag.Changed { + } else if tplFlag != nil && tplFlag.Changed { return nil, errors.New("cannot use `--template` without specifying `--json`") } return nil, nil @@ -219,6 +236,10 @@ func (e *jsonExporter) SetFields(fields []string) { e.fields = fields } +func (e *jsonExporter) SetFilter(filter string) { + e.filter = filter +} + // Write serializes data into JSON output written to w. If the object passed as data implements exportable, // or if data is a map or slice of exportable object, ExportData() will be called on each object to obtain // raw data for serialization. diff --git a/pkg/cmdutil/json_flags_test.go b/pkg/cmdutil/json_flags_test.go index ee089960b6b..03c70f7c08e 100644 --- a/pkg/cmdutil/json_flags_test.go +++ b/pkg/cmdutil/json_flags_test.go @@ -157,6 +157,87 @@ func TestAddJSONFlagsWithoutShorthand(t *testing.T) { } } +func TestAddJSONAndJQFlags(t *testing.T) { + tests := []struct { + name string + fields []string + args []string + addTplFlag bool + wantsExport *jsonExporter + wantsError string + }{ + { + name: "adds json and jq flags only", + fields: []string{"id", "url", "number"}, + args: []string{"--json", "id,url"}, + wantsExport: &jsonExporter{ + fields: []string{"id", "url"}, + filter: "", + template: "", + }, + }, + { + name: "with jq filter", + fields: []string{"id", "url", "number"}, + args: []string{"--json", "number", "--jq", ".number"}, + wantsExport: &jsonExporter{ + fields: []string{"number"}, + filter: ".number", + template: "", + }, + }, + { + name: "host template flag coexists with json", + fields: []string{"id", "url", "number"}, + args: []string{"--json", "number", "--template", "pull_request_template.md"}, + addTplFlag: true, + wantsExport: &jsonExporter{ + fields: []string{"number"}, + filter: "", + template: "", + }, + }, + { + name: "cannot use jq without json", + fields: []string{"id", "url", "number"}, + args: []string{"--jq", ".number"}, + wantsError: "cannot use `--jq` without specifying `--json`", + }, + { + name: "invalid json field", + fields: []string{"id", "url", "number"}, + args: []string{"--json", "bogus"}, + wantsError: "Unknown JSON field: \"bogus\"\nAvailable fields:\n id\n number\n url", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &cobra.Command{Run: func(*cobra.Command, []string) {}} + if tt.addTplFlag { + // Simulate a command that already has --template for a different purpose. + cmd.Flags().StringP("template", "T", "", "Template file") + } + var exporter Exporter + AddJSONAndJQFlags(cmd, &exporter, tt.fields) + cmd.SetArgs(tt.args) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + _, err := cmd.ExecuteC() + if tt.wantsError == "" { + require.NoError(t, err) + } else { + assert.EqualError(t, err, tt.wantsError) + return + } + if tt.wantsExport == nil { + assert.Nil(t, exporter) + } else { + assert.Equal(t, tt.wantsExport, exporter) + } + }) + } +} + // TestAddJSONFlagsSetsAnnotations asserts that `AddJSONFlags` function adds the // appropriate annotation to the command, which could later be used by doc // generator functions.