diff --git a/pkg/cmd/gist/delete/delete.go b/pkg/cmd/gist/delete/delete.go new file mode 100644 index 000000000..03ddc6e46 --- /dev/null +++ b/pkg/cmd/gist/delete/delete.go @@ -0,0 +1,88 @@ +package delete + +import ( + "fmt" + "github.com/cli/cli/api" + "github.com/cli/cli/internal/ghinstance" + "github.com/cli/cli/pkg/cmd/gist/shared" + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/iostreams" + "github.com/spf13/cobra" + "net/http" + "strings" +) + +type DeleteOptions struct { + IO *iostreams.IOStreams + HttpClient func() (*http.Client, error) + + Selector string +} + +func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command { + opts := DeleteOptions{ + IO: f.IOStreams, + HttpClient: f.HttpClient, + } + + cmd := &cobra.Command{ + Use: "delete { | }", + Short: "Delete a gist", + Args: cobra.ExactArgs(1), + RunE: func(c *cobra.Command, args []string) error { + opts.Selector = args[0] + if runF != nil { + return runF(&opts) + } + return deleteRun(&opts) + }, + } + return cmd +} + +func deleteRun(opts *DeleteOptions) error { + gistID := opts.Selector + + if strings.Contains(gistID, "/") { + id, err := shared.GistIDFromURL(gistID) + if err != nil { + return err + } + gistID = id + } + client, err := opts.HttpClient() + if err != nil { + return err + } + + apiClient := api.NewClientFromHTTP(client) + + gist, err := shared.GetGist(client, ghinstance.OverridableDefault(), gistID) + if err != nil { + return err + } + username, err := api.CurrentLoginName(apiClient, ghinstance.OverridableDefault()) + if err != nil { + return err + } + + if username != gist.Owner.Login { + return fmt.Errorf("You do not own this gist.") + } + + err = deleteGist(apiClient, ghinstance.OverridableDefault(), gistID) + if err != nil { + return err + } + + return nil +} + +func deleteGist(apiClient *api.Client, hostname string, gistID string) error { + path := "gists/" + gistID + err := apiClient.REST(hostname, "DELETE", path, nil, nil) + if err != nil { + return err + } + return nil +} diff --git a/pkg/cmd/gist/delete/delete_test.go b/pkg/cmd/gist/delete/delete_test.go new file mode 100644 index 000000000..682a92d50 --- /dev/null +++ b/pkg/cmd/gist/delete/delete_test.go @@ -0,0 +1,156 @@ +package delete + +import ( + "bytes" + "github.com/cli/cli/pkg/cmd/gist/shared" + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/httpmock" + "github.com/cli/cli/pkg/iostreams" + "github.com/cli/cli/pkg/prompt" + "github.com/google/shlex" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func TestNewCmdDelete(t *testing.T) { + tests := []struct { + name string + cli string + wants DeleteOptions + }{ + { + name: "valid selector", + cli: "123", + wants: DeleteOptions{ + Selector: "123", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &cmdutil.Factory{} + + argv, err := shlex.Split(tt.cli) + assert.NoError(t, err) + var gotOpts *DeleteOptions + cmd := NewCmdDelete(f, func(opts *DeleteOptions) error { + gotOpts = opts + return nil + }) + + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + + _, err = cmd.ExecuteC() + assert.NoError(t, err) + + assert.Equal(t, tt.wants.Selector, gotOpts.Selector) + }) + } +} + +func Test_deleteRun(t *testing.T) { + tests := []struct { + name string + opts *DeleteOptions + gist *shared.Gist + httpStubs func(*httpmock.Registry) + askStubs func(*prompt.AskStubber) + nontty bool + wantErr bool + wantStderr string + wantParams map[string]interface{} + }{ + { + name: "no such gist", + wantErr: true, + }, { + name: "another user's gist", + gist: &shared.Gist{ + ID: "1234", + Files: map[string]*shared.GistFile{ + "cicada.txt": { + Filename: "cicada.txt", + Content: "bwhiizzzbwhuiiizzzz", + Type: "text/plain", + }, + }, + Owner: &shared.GistOwner{Login: "octocat2"}, + }, + wantErr: true, + wantStderr: "You do not own this gist.", + }, { + name: "successfully delete", + gist: &shared.Gist{ + ID: "1234", + Files: map[string]*shared.GistFile{ + "cicada.txt": { + Filename: "cicada.txt", + Content: "bwhiizzzbwhuiiizzzz", + Type: "text/plain", + }, + }, + Owner: &shared.GistOwner{Login: "octocat"}, + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register(httpmock.REST("DELETE", "gists/1234"), + httpmock.StatusStringResponse(200, "{}")) + }, + wantErr: false, + }, + } + + for _, tt := range tests { + reg := &httpmock.Registry{} + if tt.gist == nil { + reg.Register(httpmock.REST("GET", "gists/1234"), + httpmock.StatusStringResponse(404, "Not Found")) + } else { + reg.Register(httpmock.REST("GET", "gists/1234"), + httpmock.JSONResponse(tt.gist)) + reg.Register(httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`)) + } + + if tt.httpStubs != nil { + tt.httpStubs(reg) + } + + as, teardown := prompt.InitAskStubber() + defer teardown() + if tt.askStubs != nil { + tt.askStubs(as) + } + + if tt.opts == nil { + tt.opts = &DeleteOptions{} + } + + tt.opts.HttpClient = func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + } + io, _, _, _ := iostreams.Test() + io.SetStdoutTTY(!tt.nontty) + io.SetStdinTTY(!tt.nontty) + tt.opts.IO = io + tt.opts.Selector = "1234" + + t.Run(tt.name, func(t *testing.T) { + err := deleteRun(tt.opts) + reg.Verify(t) + if tt.wantErr { + assert.Error(t, err) + if tt.wantStderr != "" { + assert.EqualError(t, err, tt.wantStderr) + } + return + } + assert.NoError(t, err) + + }) + } +} diff --git a/pkg/cmd/gist/gist.go b/pkg/cmd/gist/gist.go index 9a4d42b87..0abfc8591 100644 --- a/pkg/cmd/gist/gist.go +++ b/pkg/cmd/gist/gist.go @@ -3,6 +3,7 @@ package gist import ( "github.com/MakeNowJust/heredoc" gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create" + gistDeleteCmd "github.com/cli/cli/pkg/cmd/gist/delete" gistEditCmd "github.com/cli/cli/pkg/cmd/gist/edit" gistListCmd "github.com/cli/cli/pkg/cmd/gist/list" gistViewCmd "github.com/cli/cli/pkg/cmd/gist/view" @@ -29,6 +30,7 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(gistListCmd.NewCmdList(f, nil)) cmd.AddCommand(gistViewCmd.NewCmdView(f, nil)) cmd.AddCommand(gistEditCmd.NewCmdEdit(f, nil)) + cmd.AddCommand(gistDeleteCmd.NewCmdDelete(f, nil)) return cmd }