Repository: cli/cli
Pull request #7826 (2023-08-11 07:52:16)

add verbose flag to api cmd
## Overview
I added `verbose` flag to api command to output debugging information.

## Explanation
Checking if `GH_DEBUG=api` is set is done in the [NewHTTPClient function](https://github.com/cli/cli/blob/d9ea221b1a8b1af7856ed4896c92f67acbf295da/api/http_client.go#L38-L42).  This `NewHTTPClient` function is called before the `NewCmdApi` function which initializes api command. Therefore, it is necessary to use a slightly cumbersome approach to output debugging information in the same way as `GH_DEBUG=api` with the verbose flag.

My approach is to create the http client again with `LogVerboseHTTP` set to true when the verbose flag is set. `LogVerboseHTTP` is a required field to output debugging information.

There might be better approaches, so let me know if you have any ideas.

## Releated Issue 
- #7809 

andyfeller: @JunNishimura : Thank you for opening this pull request! ✨ Please allow me some time to review these changes and confirm with my team on any feedback. 🙇 
JunNishimura: @andyfeller 
Thanks for the message, and it is fine when you have time to spare!!
andyfeller: **@JunNishimura : How extensive do you envision adding verbose logging to be?**

I ask because I think there is more to adding this support than the current PR touches upon because `gh api` commands require a different `http.Client` instantiation logic:

https://github.com/cli/cli/blob/e4cddb562edc1436258dc1a70cf8c8c2ca08eb29/pkg/cmd/root/root.go#L154-L159

https://github.com/cli/cli/blob/e4cddb562edc1436258dc1a70cf8c8c2ca08eb29/pkg/cmd/root/root.go#L226-L241

So, we need a way that commands like `gh api` can the `cmdutil.Factory` that the `http.Client` needs `LogVerboseHTTP` set differently.

Does that make sense?
JunNishimura: @andyfeller 
> How extensive do you envision adding verbose logging to be?

At this stage, it is assumed that verbose logging will be added only for `gh api` command. However, it is possible that there will be requests to add verbose logging to other commands in the future, so it will be desirable to implement it in a versatile way.

> we need a way that commands like gh api can the cmdutil.Factory that the http.Client needs LogVerboseHTTP set differently.

I assume that the implementation is to add the method to the Factory struct to set LogVerboseHTTP differently, is that correct?

andyfeller: > At this stage, it is assumed that verbose logging will be added only for `gh api` command. However, it is possible that there will be requests to add verbose logging to other commands in the future, so it will be desirable to implement it in a versatile way.

I appreciate you having the same mindset as we have been discussing about how this work might apply to other commands in the future. 🙇 

> I assume that the implementation is to add the method to the Factory struct to set LogVerboseHTTP differently, is that correct?

**Yeah, that is line of thought I was working through.**  

If `api.NewHTTPClient()` assumes `api.HTTPClientOptions.LogVerboseHTTP` is the only thing determining whether verbose logging is needed, then the `func() (*http.Client, error)` functions would need to check the environment for a global setting or something else for a command-specific flag.

Changing the `func() (*http.Client, error)` to accept an argument for `LogVerboseHTTP` would be problematic [given everywhere it is used for generating a `http.Client`](https://github.com/search?q=repo%3Acli%2Fcli%20%22(*http.Client%2C%20error)%22&type=code).  

Instead, the simplest idea I can consider would be:

1. Add `LogVerboseHTTP` to [`cmdutil.Factory` struct](https://github.com/cli/cli/blob/4a57a812f5db9db72812030f19b214c93b18b297/pkg/cmdutil/factory.go#L19-L33)
2. Update [`cmd.factory.httpClientFunc()`](https://github.com/cli/cli/blob/4a57a812f5db9db72812030f19b214c93b18b297/pkg/cmd/factory/default.go#L91-L111) to set `api.HTTPClientOptions.LogVerboseHTTP` based on checking `utils.IsDebugEnabled()` value with `f.LogVerboseHTTP`
3. Update [`root.bareHTTPClient()`](https://github.com/cli/cli/blob/4a57a812f5db9db72812030f19b214c93b18b297/pkg/cmd/root/root.go#L226C87-L241) with the same logic
4. Update [`api.NewHTTPClient()`](https://github.com/cli/cli/blob/4a57a812f5db9db72812030f19b214c93b18b297/api/http_client.go#L29-L42) to only check `opts.HTTPClientOptions.LogVerboseHTTP`

Hopefully this puts us in a place where we can update other commands to signal verbose logging from a flag or globally with only the 2 places we're facilitating `api.HttpClient` generation.

What do you think about that?

_Again, thank you for your patience working through this PR!_ 🫂
JunNishimura: @andyfeller 
Thank you for the detailed explanation. I agree with your idea.

I've followed your ideas and implemented them, but there is one point I would like to ask.

I assign `opts.LogVerboseHTTP` to `factory.LogVerboseHTTP` in RunE (not in apiRun function) as follows. Do you see any issues with this implementation?
https://github.com/cli/cli/blob/5f465c94917f3385ab6b849ae87372d8bbfa9f29/pkg/cmd/api/api.go#L221-L223
samcoe: @andyfeller @JunNishimura Appreciate your work getting this feature implemented. There has been quiet a bit of back and forth on correct ways to implement this due to how connected the uses of `http.Client` are throughout our codebase. As it stands now I feel like this implementation has a bit too far reaching of an impact for the small feature we are trying to add. I have an alternative idea that I would like to implement, it is a bit complicated to describe though, so if no one minds I can push a commit on top of the work already done?
JunNishimura: @samcoe 
Thanks for the message and contribution.

If there is a better approach, I would prefer that. So I am open to adopt your approach👍

Patch:
diff --git a/api/http_client.go b/api/http_client.go
index 53225b139..9f2d59ce3 100644
--- a/api/http_client.go
+++ b/api/http_client.go
@@ -23,6 +23,7 @@ type HTTPClientOptions struct {
 	EnableCache       bool
 	Log               io.Writer
 	LogColorize       bool
+	LogVerboseHTTP    bool
 	SkipAcceptHeaders bool
 }
 
@@ -35,10 +36,15 @@ func NewHTTPClient(opts HTTPClientOptions) (*http.Client, error) {
 		LogIgnoreEnv: true,
 	}
 
-	if debugEnabled, debugValue := utils.IsDebugEnabled(); debugEnabled {
+	debugEnabled, debugValue := utils.IsDebugEnabled()
+	if strings.Contains(debugValue, "api") {
+		opts.LogVerboseHTTP = true
+	}
+
+	if opts.LogVerboseHTTP || debugEnabled {
 		clientOpts.Log = opts.Log
 		clientOpts.LogColorize = opts.LogColorize
-		clientOpts.LogVerboseHTTP = strings.Contains(debugValue, "api")
+		clientOpts.LogVerboseHTTP = opts.LogVerboseHTTP
 	}
 
 	headers := map[string]string{
diff --git a/docs/install_linux.md b/docs/install_linux.md
index 0825c3305..82d3c1a77 100644
--- a/docs/install_linux.md
+++ b/docs/install_linux.md
@@ -197,10 +197,10 @@ kiss b github-cli && kiss i github-cli
 
 ### Nix/NixOS
 
-Nix/NixOS users can install from [nixpkgs](https://search.nixos.org/packages?show=gitAndTools.gh&query=gh&from=0&size=30&sort=relevance&channel=20.03#disabled):
+Nix/NixOS users can install from [nixpkgs](https://search.nixos.org/packages?query=gh&sort=relevance&show=gh):
 
 ```bash
-nix-env -iA nixos.gitAndTools.gh
+nix-env -iA nixos.gh
 ```
 
 ### openSUSE Tumbleweed
diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go
index 3ac83ad6b..38b6278b4 100644
--- a/pkg/cmd/api/api.go
+++ b/pkg/cmd/api/api.go
@@ -30,7 +30,12 @@ import (
 )
 
 type ApiOptions struct {
-	IO *iostreams.IOStreams
+	AppVersion string
+	BaseRepo   func() (ghrepo.Interface, error)
+	Branch     func() (string, error)
+	Config     func() (config.Config, error)
+	HttpClient func() (*http.Client, error)
+	IO         *iostreams.IOStreams
 
 	Hostname            string
 	RequestMethod       string
@@ -47,20 +52,16 @@ type ApiOptions struct {
 	Template            string
 	CacheTTL            time.Duration
 	FilterOutput        string
-
-	Config     func() (config.Config, error)
-	HttpClient func() (*http.Client, error)
-	BaseRepo   func() (ghrepo.Interface, error)
-	Branch     func() (string, error)
+	Verbose             bool
 }
 
 func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command {
 	opts := ApiOptions{
-		IO:         f.IOStreams,
-		Config:     f.Config,
-		HttpClient: f.HttpClient,
+		AppVersion: f.AppVersion,
 		BaseRepo:   f.BaseRepo,
 		Branch:     f.Branch,
+		Config:     f.Config,
+		IO:         f.IOStreams,
 	}
 
 	cmd := &cobra.Command{
@@ -209,7 +210,8 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
 			}
 
 			if err := cmdutil.MutuallyExclusive(
-				"only one of `--template`, `--jq`, or `--silent` may be used",
+				"only one of `--template`, `--jq`, `--silent`, or `--verbose` may be used",
+				opts.Verbose,
 				opts.Silent,
 				opts.FilterOutput != "",
 				opts.Template != "",
@@ -237,6 +239,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
 	cmd.Flags().StringVarP(&opts.Template, "template", "t", "", "Format JSON output using a Go template; see \"gh help formatting\"")
 	cmd.Flags().StringVarP(&opts.FilterOutput, "jq", "q", "", "Query to select values from the response using jq syntax")
 	cmd.Flags().DurationVar(&opts.CacheTTL, "cache", 0, "Cache the response, e.g. \"3600s\", \"60m\", \"1h\"")
+	cmd.Flags().BoolVar(&opts.Verbose, "verbose", false, "Include full HTTP request and response in the output")
 	return cmd
 }
 
@@ -283,12 +286,33 @@ func apiRun(opts *ApiOptions) error {
 		requestHeaders = append(requestHeaders, "Accept: "+previewNamesToMIMETypes(opts.Previews))
 	}
 
-	httpClient, err := opts.HttpClient()
+	cfg, err := opts.Config()
 	if err != nil {
 		return err
 	}
-	if opts.CacheTTL > 0 {
-		httpClient = api.NewCachedHTTPClient(httpClient, opts.CacheTTL)
+
+	if opts.HttpClient == nil {
+		opts.HttpClient = func() (*http.Client, error) {
+			log := opts.IO.ErrOut
+			if opts.Verbose {
+				log = opts.IO.Out
+			}
+			opts := api.HTTPClientOptions{
+				AppVersion:        opts.AppVersion,
+				CacheTTL:          opts.CacheTTL,
+				Config:            cfg.Authentication(),
+				EnableCache:       opts.CacheTTL > 0,
+				Log:               log,
+				LogColorize:       opts.IO.ColorEnabled(),
+				LogVerboseHTTP:    opts.Verbose,
+				SkipAcceptHeaders: true,
+			}
+			return api.NewHTTPClient(opts)
+		}
+	}
+	httpClient, err := opts.HttpClient()
+	if err != nil {
+		return err
 	}
 
 	if !opts.Silent {
@@ -304,10 +328,10 @@ func apiRun(opts *ApiOptions) error {
 	if opts.Silent {
 		bodyWriter = io.Discard
 	}
-
-	cfg, err := opts.Config()
-	if err != nil {
-		return err
+	if opts.Verbose {
+		// httpClient handles output when verbose flag is specified.
+		bodyWriter = io.Discard
+		headersWriter = io.Discard
 	}
 
 	host, _ := cfg.Authentication().DefaultHost()
diff --git a/pkg/cmd/factory/default.go b/pkg/cmd/factory/default.go
index 4ff7a77f7..a24dc20fb 100644
--- a/pkg/cmd/factory/default.go
+++ b/pkg/cmd/factory/default.go
@@ -25,6 +25,7 @@ var ssoURLRE = regexp.MustCompile(`\burl=([^;]+)`)
 
 func New(appVersion string) *cmdutil.Factory {
 	f := &cmdutil.Factory{
+		AppVersion:     appVersion,
 		Config:         configFunc(), // No factory dependencies
 		ExecutableName: "gh",
 	}
diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go
index 8c8420bee..12884198e 100644
--- a/pkg/cmd/root/root.go
+++ b/pkg/cmd/root/root.go
@@ -2,12 +2,10 @@ package root
 
 import (
 	"fmt"
-	"net/http"
 	"os"
 	"strings"
 
 	"github.com/MakeNowJust/heredoc"
-	"github.com/cli/cli/v2/api"
 	actionsCmd "github.com/cli/cli/v2/pkg/cmd/actions"
 	aliasCmd "github.com/cli/cli/v2/pkg/cmd/alias"
 	"github.com/cli/cli/v2/pkg/cmd/alias/shared"
@@ -150,13 +148,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
 	cmd.AddCommand(workflowCmd.NewCmdWorkflow(&repoResolvingCmdFactory))
 	cmd.AddCommand(labelCmd.NewCmdLabel(&repoResolvingCmdFactory))
 	cmd.AddCommand(cacheCmd.NewCmdCache(&repoResolvingCmdFactory))
-
-	// the `api` command should not inherit any extra HTTP headers
-	bareHTTPCmdFactory := *f
-	bareHTTPCmdFactory.HttpClient = bareHTTPClient(f, version)
-	bareHTTPCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(&bareHTTPCmdFactory)
-
-	cmd.AddCommand(apiCmd.NewCmdApi(&bareHTTPCmdFactory, nil))
+	cmd.AddCommand(apiCmd.NewCmdApi(&repoResolvingCmdFactory, nil))
 
 	// Help topics
 	var referenceCmd *cobra.Command
@@ -222,20 +214,3 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
 	referenceCmd.SetHelpFunc(longPager(f.IOStreams))
 	return cmd, nil
 }
-
-func bareHTTPClient(f *cmdutil.Factory, version string) func() (*http.Client, error) {
-	return func() (*http.Client, error) {
-		cfg, err := f.Config()
-		if err != nil {
-			return nil, err
-		}
-		opts := api.HTTPClientOptions{
-			AppVersion:        version,
-			Config:            cfg.Authentication(),
-			Log:               f.IOStreams.ErrOut,
-			LogColorize:       f.IOStreams.ColorEnabled(),
-			SkipAcceptHeaders: true,
-		}
-		return api.NewHTTPClient(opts)
-	}
-}
diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go
index 2d7a73a62..223d30eb2 100644
--- a/pkg/cmdutil/factory.go
+++ b/pkg/cmdutil/factory.go
@@ -17,19 +17,20 @@ import (
 )
 
 type Factory struct {
-	IOStreams *iostreams.IOStreams
-	Prompter  prompter.Prompter
-	Browser   browser.Browser
-	GitClient *git.Client
+	AppVersion     string
+	ExecutableName string
+
+	Browser          browser.Browser
+	ExtensionManager extensions.ExtensionManager
+	GitClient        *git.Client
+	IOStreams        *iostreams.IOStreams
+	Prompter         prompter.Prompter
 
-	HttpClient func() (*http.Client, error)
 	BaseRepo   func() (ghrepo.Interface, error)
-	Remotes    func() (context.Remotes, error)
-	Config     func() (config.Config, error)
 	Branch     func() (string, error)
-
-	ExtensionManager extensions.ExtensionManager
-	ExecutableName   string
+	Config     func() (config.Config, error)
+	HttpClient func() (*http.Client, error)
+	Remotes    func() (context.Remotes, error)
 }
 
 // Executable is the path to the currently invoked binary


Test patch:
diff --git a/api/http_client_test.go b/api/http_client_test.go
index 5bfe0aa05..b1e84e582 100644
--- a/api/http_client_test.go
+++ b/api/http_client_test.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"net/http"
 	"net/http/httptest"
-	"os"
 	"regexp"
 	"strings"
 	"testing"
@@ -19,16 +18,14 @@ import (
 
 func TestNewHTTPClient(t *testing.T) {
 	type args struct {
-		config     tokenGetter
-		appVersion string
-		setAccept  bool
+		config         tokenGetter
+		appVersion     string
+		setAccept      bool
+		logVerboseHTTP bool
 	}
 	tests := []struct {
 		name       string
 		args       args
-		envDebug   string
-		setGhDebug bool
-		envGhDebug string
 		host       string
 		wantHeader map[string]string
 		wantStderr string
@@ -36,9 +33,10 @@ func TestNewHTTPClient(t *testing.T) {
 		{
 			name: "github.com with Accept header",
 			args: args{
-				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
-				appVersion: "v1.2.3",
-				setAccept:  true,
+				config:         tinyConfig{"github.com:oauth_token": "MYTOKEN"},
+				appVersion:     "v1.2.3",
+				setAccept:      true,
+				logVerboseHTTP: false,
 			},
 			host: "github.com",
 			wantHeader: map[string]string{
@@ -51,9 +49,10 @@ func TestNewHTTPClient(t *testing.T) {
 		{
 			name: "github.com no Accept header",
 			args: args{
-				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
-				appVersion: "v1.2.3",
-				setAccept:  false,
+				config:         tinyConfig{"github.com:oauth_token": "MYTOKEN"},
+				appVersion:     "v1.2.3",
+				setAccept:      false,
+				logVerboseHTTP: false,
 			},
 			host: "github.com",
 			wantHeader: map[string]string{
@@ -66,9 +65,10 @@ func TestNewHTTPClient(t *testing.T) {
 		{
 			name: "github.com no authentication token",
 			args: args{
-				config:     tinyConfig{"example.com:oauth_token": "MYTOKEN"},
-				appVersion: "v1.2.3",
-				setAccept:  true,
+				config:         tinyConfig{"example.com:oauth_token": "MYTOKEN"},
+				appVersion:     "v1.2.3",
+				setAccept:      true,
+				logVerboseHTTP: false,
 			},
 			host: "github.com",
 			wantHeader: map[string]string{
@@ -81,45 +81,12 @@ func TestNewHTTPClient(t *testing.T) {
 		{
 			name: "github.com in verbose mode",
 			args: args{
-				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
-				appVersion: "v1.2.3",
-				setAccept:  true,
-			},
-			host:       "github.com",
-			envDebug:   "api",
-			setGhDebug: false,
-			wantHeader: map[string]string{
-				"authorization": "token MYTOKEN",
-				"user-agent":    "GitHub CLI v1.2.3",
-				"accept":        "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
-			},
-			wantStderr: heredoc.Doc(`
-				* Request at <time>
-				* Request to http://<host>:<port>
-				> GET / HTTP/1.1
-				> Host: github.com
-				> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
-				> Authorization: token ████████████████████
-				> Content-Type: application/json; charset=utf-8
-				> Time-Zone: <timezone>
-				> User-Agent: GitHub CLI v1.2.3
-
-				< HTTP/1.1 204 No Content
-				< Date: <time>
-
-				* Request took <duration>
-			`),
-		},
-		{
-			name: "github.com in verbose mode",
-			args: args{
-				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
-				appVersion: "v1.2.3",
-				setAccept:  true,
+				config:         tinyConfig{"github.com:oauth_token": "MYTOKEN"},
+				appVersion:     "v1.2.3",
+				setAccept:      true,
+				logVerboseHTTP: true,
 			},
-			host:       "github.com",
-			envGhDebug: "api",
-			setGhDebug: true,
+			host: "github.com",
 			wantHeader: map[string]string{
 				"authorization": "token MYTOKEN",
 				"user-agent":    "GitHub CLI v1.2.3",
@@ -168,19 +135,13 @@ func TestNewHTTPClient(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			t.Setenv("DEBUG", tt.envDebug)
-			if tt.setGhDebug {
-				t.Setenv("GH_DEBUG", tt.envGhDebug)
-			} else {
-				os.Unsetenv("GH_DEBUG")
-			}
-
 			ios, _, _, stderr := iostreams.Test()
 			client, err := NewHTTPClient(HTTPClientOptions{
 				AppVersion:        tt.args.appVersion,
 				Config:            tt.args.config,
 				Log:               ios.ErrOut,
 				SkipAcceptHeaders: !tt.args.setAccept,
+				LogVerboseHTTP:    tt.args.logVerboseHTTP,
 			})
 			require.NoError(t, err)
 
diff --git a/pkg/cmd/api/api_test.go b/pkg/cmd/api/api_test.go
index 2d43daea7..178bebd86 100644
--- a/pkg/cmd/api/api_test.go
+++ b/pkg/cmd/api/api_test.go
@@ -52,6 +52,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -73,6 +74,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -94,6 +96,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -115,6 +118,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -136,6 +140,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -157,6 +162,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -183,6 +189,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -209,6 +216,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -235,6 +243,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -256,6 +265,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            time.Minute * 5,
 				Template:            "",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -277,6 +287,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "hello {{.name}}",
 				FilterOutput:        "",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -298,6 +309,7 @@ func Test_NewCmdApi(t *testing.T) {
 				CacheTTL:            0,
 				Template:            "",
 				FilterOutput:        ".name",
+				Verbose:             false,
 			},
 			wantsErr: false,
 		},
@@ -316,6 +328,28 @@ func Test_NewCmdApi(t *testing.T) {
 			cli:      "user --jq .foo -t '{{.foo}}'",
 			wantsErr: true,
 		},
+		{
+			name: "with verbose",
+			cli:  "user --verbose",
+			wants: ApiOptions{
+				Hostname:            "",
+				RequestMethod:       "GET",
+				RequestMethodPassed: false,
+				RequestPath:         "user",
+				RequestInputFile:    "",
+				RawFields:           []string(nil),
+				MagicFields:         []string(nil),
+				RequestHeaders:      []string(nil),
+				ShowResponseHeaders: false,
+				Paginate:            false,
+				Silent:              false,
+				CacheTTL:            0,
+				Template:            "",
+				FilterOutput:        "",
+				Verbose:             true,
+			},
+			wantsErr: false,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -352,6 +386,7 @@ func Test_NewCmdApi(t *testing.T) {
 			assert.Equal(t, tt.wants.CacheTTL, opts.CacheTTL)
 			assert.Equal(t, tt.wants.Template, opts.Template)
 			assert.Equal(t, tt.wants.FilterOutput, opts.FilterOutput)
+			assert.Equal(t, tt.wants.Verbose, opts.Verbose)
 		})
 	}
 }