Merge pull request #10362 from hoffm/delete-autolinks

feat: Add support for deleting autolink references
This commit is contained in:
Tyler McGoffin 2025-02-07 15:52:03 -08:00 committed by GitHub
commit 55579582e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 602 additions and 48 deletions

View file

@ -3,6 +3,7 @@ package autolink
import (
"github.com/MakeNowJust/heredoc"
cmdCreate "github.com/cli/cli/v2/pkg/cmd/repo/autolink/create"
cmdDelete "github.com/cli/cli/v2/pkg/cmd/repo/autolink/delete"
cmdList "github.com/cli/cli/v2/pkg/cmd/repo/autolink/list"
cmdView "github.com/cli/cli/v2/pkg/cmd/repo/autolink/view"
"github.com/cli/cli/v2/pkg/cmdutil"
@ -26,6 +27,7 @@ func NewCmdAutolink(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(cmdList.NewCmdList(f, nil))
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
cmd.AddCommand(cmdView.NewCmdView(f, nil))
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
return cmd
}

View file

@ -112,7 +112,7 @@ func createRun(opts *createOptions) error {
"%s Created repository autolink %s on %s\n",
cs.SuccessIconWithColor(cs.Green),
cs.Cyanf("%d", autolink.ID),
ghrepo.FullName(repo))
cs.Bold(ghrepo.FullName(repo)))
return nil
}

View file

@ -92,11 +92,11 @@ func TestNewCmdCreate(t *testing.T) {
}
}
type stubAutoLinkCreator struct {
type stubAutolinkCreator struct {
err error
}
func (g stubAutoLinkCreator) Create(repo ghrepo.Interface, request AutolinkCreateRequest) (*shared.Autolink, error) {
func (g stubAutolinkCreator) Create(repo ghrepo.Interface, request AutolinkCreateRequest) (*shared.Autolink, error) {
if g.err != nil {
return nil, g.err
}
@ -119,7 +119,7 @@ func TestCreateRun(t *testing.T) {
tests := []struct {
name string
opts *createOptions
stubCreator stubAutoLinkCreator
stubCreator stubAutolinkCreator
expectedErr error
errMsg string
wantStdout string
@ -131,7 +131,7 @@ func TestCreateRun(t *testing.T) {
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
},
stubCreator: stubAutoLinkCreator{},
stubCreator: stubAutolinkCreator{},
wantStdout: "✓ Created repository autolink 1 on OWNER/REPO\n",
},
{
@ -141,7 +141,7 @@ func TestCreateRun(t *testing.T) {
URLTemplate: "https://example.com/TICKET?query=<num>",
Numeric: true,
},
stubCreator: stubAutoLinkCreator{},
stubCreator: stubAutolinkCreator{},
wantStdout: "✓ Created repository autolink 1 on OWNER/REPO\n",
},
{
@ -150,7 +150,7 @@ func TestCreateRun(t *testing.T) {
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
},
stubCreator: stubAutoLinkCreator{err: testAutolinkClientCreateError{}},
stubCreator: stubAutolinkCreator{err: testAutolinkClientCreateError{}},
expectedErr: testAutolinkClientCreateError{},
errMsg: fmt.Sprint("error creating autolink: ", testAutolinkClientCreateError{}.Error()),
},

View file

@ -45,10 +45,6 @@ func (a *AutolinkCreator) Create(repo ghrepo.Interface, request AutolinkCreateRe
defer resp.Body.Close()
// if resp.StatusCode != http.StatusCreated {
// return nil, api.HandleHTTPError(resp)
// }
err = handleAutolinkCreateError(resp)
if err != nil {

View file

@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestAutoLinkCreator_Create(t *testing.T) {
func TestAutolinkCreator_Create(t *testing.T) {
repo := ghrepo.New("OWNER", "REPO")
tests := []struct {

View file

@ -0,0 +1,107 @@
package delete
import (
"fmt"
"github.com/cli/cli/v2/internal/browser"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/view"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/spf13/cobra"
)
type deleteOptions struct {
BaseRepo func() (ghrepo.Interface, error)
Browser browser.Browser
AutolinkDeleteClient AutolinkDeleteClient
AutolinkViewClient view.AutolinkViewClient
IO *iostreams.IOStreams
ID string
Confirmed bool
Prompter prompter.Prompter
}
type AutolinkDeleteClient interface {
Delete(repo ghrepo.Interface, id string) error
}
func NewCmdDelete(f *cmdutil.Factory, runF func(*deleteOptions) error) *cobra.Command {
opts := &deleteOptions{
Browser: f.Browser,
IO: f.IOStreams,
Prompter: f.Prompter,
}
cmd := &cobra.Command{
Use: "delete <id>",
Short: "Delete an autolink reference",
Long: "Delete an autolink reference for a repository.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.BaseRepo = f.BaseRepo
httpClient, err := f.HttpClient()
if err != nil {
return err
}
opts.AutolinkDeleteClient = &AutolinkDeleter{HTTPClient: httpClient}
opts.AutolinkViewClient = &view.AutolinkViewer{HTTPClient: httpClient}
opts.ID = args[0]
if !opts.IO.CanPrompt() && !opts.Confirmed {
return cmdutil.FlagErrorf("--yes required when not running interactively")
}
if runF != nil {
return runF(opts)
}
return deleteRun(opts)
},
}
cmd.Flags().BoolVar(&opts.Confirmed, "yes", false, "Confirm deletion without prompting")
return cmd
}
func deleteRun(opts *deleteOptions) error {
repo, err := opts.BaseRepo()
if err != nil {
return err
}
out := opts.IO.Out
cs := opts.IO.ColorScheme()
autolink, err := opts.AutolinkViewClient.View(repo, opts.ID)
if err != nil {
return fmt.Errorf("%s %w", cs.Red("error deleting autolink:"), err)
}
if opts.IO.CanPrompt() && !opts.Confirmed {
fmt.Fprintf(out, "Autolink %s has key prefix %s.\n", cs.Cyan(opts.ID), autolink.KeyPrefix)
err := opts.Prompter.ConfirmDeletion(autolink.KeyPrefix)
if err != nil {
return err
}
}
err = opts.AutolinkDeleteClient.Delete(repo, opts.ID)
if err != nil {
return err
}
if opts.IO.IsStdoutTTY() {
fmt.Fprintf(out, "%s Autolink %s deleted from %s\n", cs.SuccessIcon(), cs.Cyan(opts.ID), cs.Bold(ghrepo.FullName(repo)))
}
return nil
}

View file

@ -0,0 +1,335 @@
package delete
import (
"bytes"
"errors"
"net/http"
"testing"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/internal/browser"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewCmdDelete(t *testing.T) {
tests := []struct {
name string
input string
output deleteOptions
isTTY bool
wantErr bool
errMsg string
}{
{
name: "no argument",
input: "",
isTTY: true,
wantErr: true,
errMsg: "accepts 1 arg(s), received 0",
},
{
name: "id provided",
input: "123",
isTTY: true,
output: deleteOptions{ID: "123"},
},
{
name: "yes flag",
input: "123 --yes",
isTTY: true,
output: deleteOptions{ID: "123", Confirmed: true},
},
{
name: "non-TTY",
input: "123 --yes",
isTTY: false,
output: deleteOptions{ID: "123", Confirmed: true},
},
{
name: "non-TTY missing yes flag",
input: "123",
isTTY: false,
wantErr: true,
errMsg: "--yes required when not running interactively",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ios, _, _, _ := iostreams.Test()
ios.SetStdinTTY(tt.isTTY)
ios.SetStdoutTTY(tt.isTTY)
ios.SetStderrTTY(tt.isTTY)
f := &cmdutil.Factory{
IOStreams: ios,
}
f.HttpClient = func() (*http.Client, error) {
return &http.Client{}, nil
}
argv, err := shlex.Split(tt.input)
require.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()
if tt.wantErr {
require.EqualError(t, err, tt.errMsg)
} else {
require.NoError(t, err)
assert.Equal(t, tt.output.ID, gotOpts.ID)
assert.Equal(t, tt.output.Confirmed, gotOpts.Confirmed)
}
})
}
}
type stubAutolinkDeleter struct {
err error
}
func (d *stubAutolinkDeleter) Delete(repo ghrepo.Interface, id string) error {
return d.err
}
type stubAutolinkViewer struct {
autolink *shared.Autolink
err error
}
func (g stubAutolinkViewer) View(repo ghrepo.Interface, id string) (*shared.Autolink, error) {
return g.autolink, g.err
}
var errTestPrompt = errors.New("prompt error")
var errTestAutolinkClientView = errors.New("autolink client view error")
var errTestAutolinkClientDelete = errors.New("autolink client delete error")
func TestDeleteRun(t *testing.T) {
tests := []struct {
name string
opts *deleteOptions
isTTY bool
stubDeleter stubAutolinkDeleter
stubViewer stubAutolinkViewer
prompterStubs func(*prompter.PrompterMock)
wantStdout string
expectedErr error
expectedErrMsg string
}{
{
name: "delete",
opts: &deleteOptions{
ID: "123",
},
isTTY: true,
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 123,
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
IsAlphanumeric: true,
},
},
stubDeleter: stubAutolinkDeleter{
err: nil,
},
prompterStubs: func(pm *prompter.PrompterMock) {
pm.ConfirmDeletionFunc = func(_ string) error {
return nil
}
},
wantStdout: heredoc.Doc(`
Autolink 123 has key prefix TICKET-.
Autolink 123 deleted from OWNER/REPO
`),
},
{
name: "delete with confirm flag",
opts: &deleteOptions{
ID: "123",
Confirmed: true,
},
isTTY: true,
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 123,
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
IsAlphanumeric: true,
},
},
stubDeleter: stubAutolinkDeleter{},
wantStdout: "✓ Autolink 123 deleted from OWNER/REPO\n",
},
{
name: "confirmation fails",
opts: &deleteOptions{
ID: "123",
},
isTTY: true,
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 123,
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
IsAlphanumeric: true,
},
},
stubDeleter: stubAutolinkDeleter{},
prompterStubs: func(pm *prompter.PrompterMock) {
pm.ConfirmDeletionFunc = func(_ string) error {
return errTestPrompt
}
},
expectedErr: errTestPrompt,
expectedErrMsg: errTestPrompt.Error(),
wantStdout: "Autolink 123 has key prefix TICKET-.\n",
},
{
name: "view error",
opts: &deleteOptions{
ID: "123",
},
isTTY: true,
stubViewer: stubAutolinkViewer{
err: errTestAutolinkClientView,
},
stubDeleter: stubAutolinkDeleter{},
expectedErr: errTestAutolinkClientView,
expectedErrMsg: "error deleting autolink: autolink client view error",
},
{
name: "delete error",
opts: &deleteOptions{
ID: "123",
},
isTTY: true,
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 123,
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
IsAlphanumeric: true,
},
},
stubDeleter: stubAutolinkDeleter{
err: errTestAutolinkClientDelete,
},
prompterStubs: func(pm *prompter.PrompterMock) {
pm.ConfirmDeletionFunc = func(_ string) error {
return nil
}
},
expectedErr: errTestAutolinkClientDelete,
expectedErrMsg: errTestAutolinkClientDelete.Error(),
wantStdout: "Autolink 123 has key prefix TICKET-.\n",
},
{
name: "no TTY",
opts: &deleteOptions{
ID: "123",
Confirmed: true,
},
isTTY: false,
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 123,
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
IsAlphanumeric: true,
}},
stubDeleter: stubAutolinkDeleter{},
wantStdout: "",
},
{
name: "no TTY view error",
opts: &deleteOptions{
ID: "123",
},
isTTY: false,
stubViewer: stubAutolinkViewer{
err: errTestAutolinkClientView,
},
stubDeleter: stubAutolinkDeleter{},
expectedErr: errTestAutolinkClientView,
expectedErrMsg: "error deleting autolink: autolink client view error",
},
{
name: "no TTY delete error",
opts: &deleteOptions{
ID: "123",
Confirmed: true,
},
isTTY: false,
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 123,
KeyPrefix: "TICKET-",
URLTemplate: "https://example.com/TICKET?query=<num>",
IsAlphanumeric: true,
},
},
stubDeleter: stubAutolinkDeleter{
err: errTestAutolinkClientDelete,
},
expectedErr: errTestAutolinkClientDelete,
expectedErrMsg: errTestAutolinkClientDelete.Error(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ios, _, stdout, _ := iostreams.Test()
ios.SetStdinTTY(tt.isTTY)
ios.SetStdoutTTY(tt.isTTY)
opts := tt.opts
opts.IO = ios
opts.Browser = &browser.Stub{}
opts.IO = ios
opts.BaseRepo = func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }
opts.AutolinkDeleteClient = &tt.stubDeleter
opts.AutolinkViewClient = &tt.stubViewer
pm := &prompter.PrompterMock{}
if tt.prompterStubs != nil {
tt.prompterStubs(pm)
}
tt.opts.Prompter = pm
err := deleteRun(opts)
if tt.expectedErr != nil {
require.Error(t, err, "expected error but got none")
assert.ErrorIs(t, err, tt.expectedErr, "unexpected error")
assert.Equal(t, tt.expectedErrMsg, err.Error(), "unexpected error message")
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
})
}
}

View file

@ -0,0 +1,37 @@
package delete
import (
"fmt"
"net/http"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghinstance"
"github.com/cli/cli/v2/internal/ghrepo"
)
type AutolinkDeleter struct {
HTTPClient *http.Client
}
func (a *AutolinkDeleter) Delete(repo ghrepo.Interface, id string) error {
path := fmt.Sprintf("repos/%s/%s/autolinks/%s", repo.RepoOwner(), repo.RepoName(), id)
url := ghinstance.RESTPrefix(repo.RepoHost()) + path
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := a.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("error deleting autolink: HTTP 404: Perhaps you are missing admin rights to the repository? (https://api.github.com/%s)", path)
} else if resp.StatusCode > 299 {
return api.HandleHTTPError(resp)
}
return nil
}

View file

@ -0,0 +1,73 @@
package delete
import (
"fmt"
"net/http"
"testing"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/stretchr/testify/require"
)
func TestAutolinkDeleter_Delete(t *testing.T) {
repo := ghrepo.New("OWNER", "REPO")
tests := []struct {
name string
id string
stubStatus int
stubRespJSON string
expectErr bool
expectedErrMsg string
}{
{
name: "204 successful delete",
id: "123",
stubStatus: http.StatusNoContent,
},
{
name: "404 repo or autolink not found",
id: "123",
stubStatus: http.StatusNotFound,
stubRespJSON: `{}`, // API response not used in output
expectErr: true,
expectedErrMsg: "error deleting autolink: HTTP 404: Perhaps you are missing admin rights to the repository? (https://api.github.com/repos/OWNER/REPO/autolinks/123)",
},
{
name: "500 unexpected error",
id: "123",
stubRespJSON: `{"messsage": "arbitrary error"}`,
stubStatus: http.StatusInternalServerError,
expectErr: true,
expectedErrMsg: "HTTP 500 (https://api.github.com/repos/OWNER/REPO/autolinks/123)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reg := &httpmock.Registry{}
reg.Register(
httpmock.REST(
http.MethodDelete,
fmt.Sprintf("repos/%s/%s/autolinks/%s", repo.RepoOwner(), repo.RepoName(), tt.id),
),
httpmock.StatusJSONResponse(tt.stubStatus, tt.stubRespJSON),
)
defer reg.Verify(t)
autolinkDeleter := &AutolinkDeleter{
HTTPClient: &http.Client{Transport: reg},
}
err := autolinkDeleter.Delete(repo, tt.id)
if tt.expectErr {
require.EqualError(t, err, tt.expectedErrMsg)
} else {
require.NoError(t, err)
}
})
}
}

View file

@ -23,7 +23,7 @@ func TestAutolinkLister_List(t *testing.T) {
name: "no autolinks",
repo: ghrepo.New("OWNER", "REPO"),
resp: []shared.Autolink{},
status: 200,
status: http.StatusOK,
},
{
name: "two autolinks",
@ -42,12 +42,12 @@ func TestAutolinkLister_List(t *testing.T) {
URLTemplate: "https://example2.com",
},
},
status: 200,
status: http.StatusOK,
},
{
name: "http error",
repo: ghrepo.New("OWNER", "REPO"),
status: 404,
status: http.StatusNotFound,
},
}
@ -64,7 +64,7 @@ func TestAutolinkLister_List(t *testing.T) {
HTTPClient: &http.Client{Transport: reg},
}
autolinks, err := autolinkLister.List(tt.repo)
if tt.status == 404 {
if tt.status == http.StatusNotFound {
require.Error(t, err)
assert.Equal(t, "error getting autolinks: HTTP 404: Perhaps you are missing admin rights to the repository? (https://api.github.com/repos/OWNER/REPO/autolinks)", err.Error())
} else {

View file

@ -89,8 +89,14 @@ func listRun(opts *listOptions) error {
return err
}
cs := opts.IO.ColorScheme()
if len(autolinks) == 0 {
return cmdutil.NewNoResultsError(fmt.Sprintf("no autolinks found in %s", ghrepo.FullName(repo)))
return cmdutil.NewNoResultsError(
fmt.Sprintf(
"no autolinks found in %s",
cs.Bold(ghrepo.FullName(repo))),
)
}
if opts.Exporter != nil {
@ -98,14 +104,16 @@ func listRun(opts *listOptions) error {
}
if opts.IO.IsStdoutTTY() {
title := listHeader(ghrepo.FullName(repo), len(autolinks))
title := fmt.Sprintf(
"Showing %s in %s",
text.Pluralize(len(autolinks), "autolink reference"),
cs.Bold(ghrepo.FullName(repo)),
)
fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title)
}
tp := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "KEY PREFIX", "URL TEMPLATE", "ALPHANUMERIC"))
cs := opts.IO.ColorScheme()
for _, autolink := range autolinks {
tp.AddField(cs.Cyanf("%d", autolink.ID))
tp.AddField(autolink.KeyPrefix)
@ -116,7 +124,3 @@ func listRun(opts *listOptions) error {
return tp.Render()
}
func listHeader(repoName string, count int) string {
return fmt.Sprintf("Showing %s in %s", text.Pluralize(count, "autolink reference"), repoName)
}

View file

@ -96,12 +96,12 @@ func TestNewCmdList(t *testing.T) {
}
}
type stubAutoLinkLister struct {
type stubAutolinkLister struct {
autolinks []shared.Autolink
err error
}
func (g stubAutoLinkLister) List(repo ghrepo.Interface) ([]shared.Autolink, error) {
func (g stubAutolinkLister) List(repo ghrepo.Interface) ([]shared.Autolink, error) {
return g.autolinks, g.err
}
@ -116,7 +116,7 @@ func TestListRun(t *testing.T) {
name string
opts *listOptions
isTTY bool
stubLister stubAutoLinkLister
stubLister stubAutolinkLister
expectedErr error
wantStdout string
wantStderr string
@ -125,7 +125,7 @@ func TestListRun(t *testing.T) {
name: "list tty",
opts: &listOptions{},
isTTY: true,
stubLister: stubAutoLinkLister{
stubLister: stubAutolinkLister{
autolinks: []shared.Autolink{
{
ID: 1,
@ -161,7 +161,7 @@ func TestListRun(t *testing.T) {
}(),
},
isTTY: true,
stubLister: stubAutoLinkLister{
stubLister: stubAutolinkLister{
autolinks: []shared.Autolink{
{
ID: 1,
@ -184,7 +184,7 @@ func TestListRun(t *testing.T) {
name: "list non-tty",
opts: &listOptions{},
isTTY: false,
stubLister: stubAutoLinkLister{
stubLister: stubAutolinkLister{
autolinks: []shared.Autolink{
{
ID: 1,
@ -210,7 +210,7 @@ func TestListRun(t *testing.T) {
name: "no results",
opts: &listOptions{},
isTTY: true,
stubLister: stubAutoLinkLister{
stubLister: stubAutolinkLister{
autolinks: []shared.Autolink{},
},
expectedErr: cmdutil.NewNoResultsError("no autolinks found in OWNER/REPO"),
@ -220,7 +220,7 @@ func TestListRun(t *testing.T) {
name: "client error",
opts: &listOptions{},
isTTY: true,
stubLister: stubAutoLinkLister{
stubLister: stubAutolinkLister{
autolinks: []shared.Autolink{},
err: testAutolinkClientListError{},
},

View file

@ -30,7 +30,7 @@ func (a *AutolinkViewer) View(repo ghrepo.Interface, id string) (*shared.Autolin
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, fmt.Errorf("HTTP 404: Either no autolink with this ID exists for this repository or you are missing admin rights to the repository. (https://api.github.com/%s)", path)
return nil, fmt.Errorf("HTTP 404: Perhaps you are missing admin rights to the repository? (https://api.github.com/%s)", path)
} else if resp.StatusCode > 299 {
return nil, api.HandleHTTPError(resp)
}

View file

@ -28,7 +28,7 @@ func TestAutolinkViewer_View(t *testing.T) {
{
name: "200 successful alphanumeric view",
id: "123",
stubStatus: 200,
stubStatus: http.StatusOK,
stubRespJSON: `{
"id": 123,
"key_prefix": "TICKET-",
@ -45,7 +45,7 @@ func TestAutolinkViewer_View(t *testing.T) {
{
name: "200 successful numeric view",
id: "123",
stubStatus: 200,
stubStatus: http.StatusOK,
stubRespJSON: `{
"id": 123,
"key_prefix": "TICKET-",
@ -62,14 +62,14 @@ func TestAutolinkViewer_View(t *testing.T) {
{
name: "404 repo or autolink not found",
id: "123",
stubStatus: 404,
stubStatus: http.StatusNotFound,
stubRespJSON: `{
"message": "Not Found",
"documentation_url": "https://docs.github.com/rest/repos/autolinks#get-an-autolink-reference-of-a-repository",
"status": "404"
}`,
expectErr: true,
expectedErrMsg: "HTTP 404: Either no autolink with this ID exists for this repository or you are missing admin rights to the repository. (https://api.github.com/repos/OWNER/REPO/autolinks/123)",
expectedErrMsg: "HTTP 404: Perhaps you are missing admin rights to the repository? (https://api.github.com/repos/OWNER/REPO/autolinks/123)",
},
}
@ -85,11 +85,11 @@ func TestAutolinkViewer_View(t *testing.T) {
)
defer reg.Verify(t)
autolinkCreator := &AutolinkViewer{
autolinkViewer := &AutolinkViewer{
HTTPClient: &http.Client{Transport: reg},
}
autolink, err := autolinkCreator.View(repo, tt.id)
autolink, err := autolinkViewer.View(repo, tt.id)
if tt.expectErr {
require.EqualError(t, err, tt.expectedErrMsg)

View file

@ -78,7 +78,7 @@ func viewRun(opts *viewOptions) error {
return opts.Exporter.Write(opts.IO, autolink)
}
fmt.Fprintf(out, "Autolink in %s\n\n", ghrepo.FullName(repo))
fmt.Fprintf(out, "Autolink in %s\n\n", cs.Bold(ghrepo.FullName(repo)))
fmt.Fprint(out, cs.Bold("ID: "))
fmt.Fprintln(out, cs.Cyanf("%d", autolink.ID))

View file

@ -49,13 +49,12 @@ func TestNewCmdView(t *testing.T) {
{
name: "json flag",
input: "123 --json id",
output: viewOptions{},
output: viewOptions{ID: "123"},
wantExporter: true,
},
{
name: "invalid json flag",
input: "123 --json invalid",
output: viewOptions{},
wantErr: true,
errMsg: "Unknown JSON field: \"invalid\"\nAvailable fields:\n id\n isAlphanumeric\n keyPrefix\n urlTemplate",
},
@ -91,17 +90,18 @@ func TestNewCmdView(t *testing.T) {
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantExporter, gotOpts.Exporter != nil)
assert.Equal(t, tt.output.ID, gotOpts.ID)
}
})
}
}
type stubAutoLinkViewer struct {
type stubAutolinkViewer struct {
autolink *shared.Autolink
err error
}
func (g stubAutoLinkViewer) View(repo ghrepo.Interface, id string) (*shared.Autolink, error) {
func (g stubAutolinkViewer) View(repo ghrepo.Interface, id string) (*shared.Autolink, error) {
return g.autolink, g.err
}
@ -115,7 +115,7 @@ func TestViewRun(t *testing.T) {
tests := []struct {
name string
opts *viewOptions
stubViewer stubAutoLinkViewer
stubViewer stubAutolinkViewer
expectedErr error
wantStdout string
}{
@ -124,7 +124,7 @@ func TestViewRun(t *testing.T) {
opts: &viewOptions{
ID: "1",
},
stubViewer: stubAutoLinkViewer{
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 1,
KeyPrefix: "TICKET-",
@ -150,7 +150,7 @@ func TestViewRun(t *testing.T) {
return exporter
}(),
},
stubViewer: stubAutoLinkViewer{
stubViewer: stubAutolinkViewer{
autolink: &shared.Autolink{
ID: 1,
KeyPrefix: "TICKET-",
@ -163,7 +163,7 @@ func TestViewRun(t *testing.T) {
{
name: "client error",
opts: &viewOptions{},
stubViewer: stubAutoLinkViewer{
stubViewer: stubAutolinkViewer{
autolink: nil,
err: testAutolinkClientViewError{},
},