gist delete prompt for confirmation

This commit is contained in:
danochoa 2025-01-03 12:51:09 -06:00
parent 61ffbe157d
commit f87d9f98f1
2 changed files with 97 additions and 23 deletions

View file

@ -6,6 +6,7 @@ import (
"net/http"
"strings"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/gh"
"github.com/cli/cli/v2/internal/prompter"
@ -21,7 +22,8 @@ type DeleteOptions struct {
HttpClient func() (*http.Client, error)
Prompter prompter.Prompter
Selector string
Selector string
Confirmed bool
}
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
@ -33,9 +35,23 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
}
cmd := &cobra.Command{
Use: "delete {<id> | <url>}",
Use: "delete [<id> | <url>]",
Short: "Delete a gist",
Args: cobra.MaximumNArgs(1),
Long: heredoc.Docf(`
Delete a GitHub gist.
To delete a gist interactively, use %[1]sgh gist delete%[1]s with no arguments.
To delete a gist non-interactively, supply the gist id or url.
`, "`"),
Example: heredoc.Doc(`
# delete a gist interactively
gh gist delete
# delete a gist non-interactively
gh gist delete 1234
`),
Args: cobra.MaximumNArgs(1),
RunE: func(c *cobra.Command, args []string) error {
if len(args) == 1 {
opts.Selector = args[0]
@ -46,6 +62,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
return deleteRun(&opts)
},
}
cmd.Flags().BoolVar(&opts.Confirmed, "yes", false, "confirm deletion without prompting")
return cmd
}
@ -83,9 +100,16 @@ func deleteRun(opts *DeleteOptions) error {
return nil
}
}
if !opts.Confirmed {
err = opts.Prompter.ConfirmDeletion("delete")
if err != nil {
return cmdutil.CancelError
}
}
apiClient := api.NewClientFromHTTP(client)
if err := deleteGist(apiClient, host, gistID); err != nil {
if err := deleteGist(apiClient, host, gistID, opts); err != nil {
if errors.Is(err, shared.NotFoundErr) {
return fmt.Errorf("unable to delete gist %s: either the gist is not found or it is not owned by you", gistID)
}
@ -95,7 +119,7 @@ func deleteRun(opts *DeleteOptions) error {
return nil
}
func deleteGist(apiClient *api.Client, hostname string, gistID string) error {
func deleteGist(apiClient *api.Client, hostname string, gistID string, opts *DeleteOptions) error {
path := "gists/" + gistID
err := apiClient.REST(hostname, "DELETE", path, nil, nil)
if err != nil {
@ -105,5 +129,12 @@ func deleteGist(apiClient *api.Client, hostname string, gistID string) error {
}
return err
}
if opts.IO.IsStdoutTTY() {
cs := opts.IO.ColorScheme()
fmt.Fprintf(opts.IO.Out,
"%s Deleted gist %s\n",
cs.SuccessIcon(),
gistID)
}
return nil
}

View file

@ -37,6 +37,13 @@ func TestNewCmdDelete(t *testing.T) {
Selector: "",
},
},
{
name: "yes flag",
cli: "123 --yes",
wants: DeleteOptions{
Selector: "123",
},
},
}
for _, tt := range tests {
@ -67,7 +74,7 @@ func TestNewCmdDelete(t *testing.T) {
func Test_deleteRun(t *testing.T) {
tests := []struct {
name string
opts DeleteOptions
opts *DeleteOptions
httpStubs func(*httpmock.Registry)
mockGistList bool
wantErr bool
@ -76,21 +83,23 @@ func Test_deleteRun(t *testing.T) {
}{
{
name: "successfully delete",
opts: DeleteOptions{
Selector: "1234",
opts: &DeleteOptions{
Selector: "1234",
Confirmed: false,
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "gists/1234"),
httpmock.StatusStringResponse(200, "{}"))
},
wantErr: false,
wantStdout: "",
wantStdout: "✓ Deleted gist 1234\n",
wantStderr: "",
},
{
name: "successfully delete with prompt",
opts: DeleteOptions{
Selector: "",
opts: &DeleteOptions{
Selector: "",
Confirmed: false,
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "gists/1234"),
@ -98,13 +107,43 @@ func Test_deleteRun(t *testing.T) {
},
mockGistList: true,
wantErr: false,
wantStdout: "",
wantStdout: "✓ Deleted gist 1234\n",
wantStderr: "",
},
{
name: "successfully delete with --yes",
opts: &DeleteOptions{
Selector: "1234",
Confirmed: true,
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "gists/1234"),
httpmock.StatusStringResponse(200, "{}"))
},
wantErr: false,
wantStdout: "✓ Deleted gist 1234\n",
wantStderr: "",
},
{
name: "successfully delete with prompt and --yes",
opts: &DeleteOptions{
Selector: "",
Confirmed: true,
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "gists/1234"),
httpmock.StatusStringResponse(200, "{}"))
},
mockGistList: true,
wantErr: false,
wantStdout: "✓ Deleted gist 1234\n",
wantStderr: "",
},
{
name: "not found",
opts: DeleteOptions{
Selector: "1234",
opts: &DeleteOptions{
Selector: "1234",
Confirmed: false,
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("DELETE", "gists/1234"),
@ -112,14 +151,18 @@ func Test_deleteRun(t *testing.T) {
},
wantErr: true,
wantStdout: "",
wantStderr: "",
wantStderr: "unable to delete gist 1234: either the gist is not found or it is not owned by you",
},
}
for _, tt := range tests {
reg := &httpmock.Registry{}
if tt.httpStubs != nil {
tt.httpStubs(reg)
tt.httpStubs(reg)
pm := prompter.NewMockPrompter(t)
if !tt.opts.Confirmed {
pm.RegisterConfirmDeletion("Type delete to confirm deletion:", func(_ string) error {
return nil
})
}
if tt.mockGistList {
@ -141,12 +184,11 @@ func Test_deleteRun(t *testing.T) {
)),
)
pm := prompter.NewMockPrompter(t)
pm.RegisterSelect("Select a gist", []string{"cool.txt about 6 hours ago"}, func(_, _ string, opts []string) (int, error) {
return 0, nil
})
tt.opts.Prompter = pm
}
tt.opts.Prompter = pm
tt.opts.HttpClient = func() (*http.Client, error) {
return &http.Client{Transport: reg}, nil
@ -154,13 +196,13 @@ func Test_deleteRun(t *testing.T) {
tt.opts.Config = func() (gh.Config, error) {
return config.NewBlankConfig(), nil
}
ios, _, _, _ := iostreams.Test()
ios.SetStdoutTTY(false)
ios, _, stdout, stderr := iostreams.Test()
ios.SetStdoutTTY(true)
ios.SetStdinTTY(false)
tt.opts.IO = ios
t.Run(tt.name, func(t *testing.T) {
err := deleteRun(&tt.opts)
err := deleteRun(tt.opts)
reg.Verify(t)
if tt.wantErr {
assert.Error(t, err)
@ -170,7 +212,8 @@ func Test_deleteRun(t *testing.T) {
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wantStdout, stdout.String())
assert.Equal(t, tt.wantStderr, stderr.String())
})
}
}