feat: Add support for creating autolink references
This commit is contained in:
parent
d6dba93586
commit
6cf2e9ee3e
11 changed files with 616 additions and 29 deletions
|
|
@ -2,6 +2,7 @@ package autolink
|
|||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
cmdCreate "github.com/cli/cli/v2/pkg/cmd/repo/autolink/create"
|
||||
cmdList "github.com/cli/cli/v2/pkg/cmd/repo/autolink/list"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -24,6 +25,7 @@ func NewCmdAutolink(f *cmdutil.Factory) *cobra.Command {
|
|||
cmdutil.EnableRepoOverride(cmd, f)
|
||||
|
||||
cmd.AddCommand(cmdList.NewCmdList(f, nil))
|
||||
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
139
pkg/cmd/repo/autolink/create/create.go
Normal file
139
pkg/cmd/repo/autolink/create/create.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser.Browser
|
||||
AutolinkClient AutolinkCreateClient
|
||||
IO *iostreams.IOStreams
|
||||
Exporter cmdutil.Exporter
|
||||
|
||||
KeyPrefix string
|
||||
URLTemplate string
|
||||
Numeric bool
|
||||
}
|
||||
|
||||
type AutolinkCreateClient interface {
|
||||
Create(repo ghrepo.Interface, request AutolinkCreateRequest) (*domain.Autolink, error)
|
||||
}
|
||||
|
||||
func NewCmdCreate(f *cmdutil.Factory, runF func(*createOptions) error) *cobra.Command {
|
||||
opts := &createOptions{
|
||||
Browser: f.Browser,
|
||||
IO: f.IOStreams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <keyPrefix> <urlTemplate>",
|
||||
Short: "Create a new autolink reference",
|
||||
Long: heredoc.Docf(`
|
||||
Create a new autolink reference for a repository.
|
||||
|
||||
Autolinks automatically generate links to external resources when they appear in an issue, pull request, or commit.
|
||||
|
||||
The %[1]skeyPrefix%[1]s specifies the prefix that will generate a link when it is appended by certain characters.
|
||||
|
||||
The %[1]surlTemplate%[1]s specifies the target URL that will be generated when the keyPrefix is found. The urlTemplate must contain %[1]s<num>%[1]s for the reference number. %[1]s<num>%[1]s matches different characters depending on the whether the autolink is specified as numeric or alphanumeric.
|
||||
|
||||
By default, the command will create an alphanumeric autolink. This means that the %[1]s<num>%[1]s in the %[1]surlTemplate%[1]s will match alphanumeric characters %[1]sA-Z%[1]s (case insensitive), %[1]s0-9%[1]s, and %[1]s-%[1]s. To create a numeric autolink, use the %[1]s--numeric%[1]s flag. Numeric autolinks only match against numeric characters. If the template contains multiple instances of %[1]s<num>%[1]s, only the first will be replaced.
|
||||
|
||||
If you are using a shell that applies special meaning to angle brackets, you you will need to need to escape these characters in the %[1]surlTemplate%[1]s or place quotation marks around this argument.
|
||||
|
||||
Only repository administrators can create autolinks.
|
||||
`, "`"),
|
||||
Example: heredoc.Doc(`
|
||||
# Create an alphanumeric autolink to example.com for the key prefix "TICKET-". This will generate a link to https://example.com/TICKET?query=123abc when "TICKET-123abc" appears.
|
||||
$ gh repo autolink create TICKET- https://example.com/TICKET?query=<num>
|
||||
|
||||
# Create a numeric autolink to example.com for the key prefix "STORY-". This will generate a link to https://example.com/STORY?id=123 when "STORY-123" appears.
|
||||
$ gh repo autolink create STORY- https://example.com/STORY?id=<num> --numeric
|
||||
|
||||
`),
|
||||
Args: cmdutil.ExactArgs(2, "Cannot create autolink: keyPrefix and urlTemplate arguments are both required"),
|
||||
Aliases: []string{"new"},
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
httpClient, err := f.HttpClient()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.AutolinkClient = &AutolinkCreator{HTTPClient: httpClient}
|
||||
opts.KeyPrefix = args[0]
|
||||
opts.URLTemplate = args[1]
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return createRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.Numeric, "numeric", "n", false, "Mark autolink as non-alphanumeric")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createRun(opts *createOptions) error {
|
||||
repo, err := opts.BaseRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
isAlphanumeric := !opts.Numeric
|
||||
|
||||
request := AutolinkCreateRequest{
|
||||
KeyPrefix: opts.KeyPrefix,
|
||||
URLTemplate: opts.URLTemplate,
|
||||
IsAlphanumeric: isAlphanumeric,
|
||||
}
|
||||
|
||||
autolink, err := opts.AutolinkClient.Create(repo, request)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %w", cs.Red("error creating autolink:"), err)
|
||||
}
|
||||
|
||||
msg := successMsg(autolink, repo, cs)
|
||||
fmt.Fprint(opts.IO.Out, msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func successMsg(autolink *domain.Autolink, repo ghrepo.Interface, cs *iostreams.ColorScheme) string {
|
||||
autolinkType := "Numeric"
|
||||
if autolink.IsAlphanumeric {
|
||||
autolinkType = "Alphanumeric"
|
||||
}
|
||||
|
||||
createdMsg := fmt.Sprintf(
|
||||
"%s %s autolink created in %s (id: %d)",
|
||||
cs.SuccessIconWithColor(cs.Green),
|
||||
autolinkType,
|
||||
ghrepo.FullName(repo),
|
||||
autolink.ID,
|
||||
)
|
||||
|
||||
autolinkMapMsg := cs.Bluef(
|
||||
" %s%s → %s",
|
||||
autolink.KeyPrefix,
|
||||
"<num>",
|
||||
autolink.URLTemplate,
|
||||
)
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n", createdMsg, autolinkMapMsg)
|
||||
}
|
||||
197
pkg/cmd/repo/autolink/create/create_test.go
Normal file
197
pkg/cmd/repo/autolink/create/create_test.go
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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/pkg/cmd/repo/autolink/domain"
|
||||
"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 TestNewCmdCreate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
output createOptions
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "no argument",
|
||||
input: "",
|
||||
wantErr: true,
|
||||
errMsg: "Cannot create autolink: keyPrefix and urlTemplate arguments are both required",
|
||||
},
|
||||
{
|
||||
name: "one argument",
|
||||
input: "TEST-",
|
||||
wantErr: true,
|
||||
errMsg: "Cannot create autolink: keyPrefix and urlTemplate arguments are both required",
|
||||
},
|
||||
{
|
||||
name: "two argument",
|
||||
input: "TICKET- https://example.com/TICKET?query=<num>",
|
||||
output: createOptions{
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "numeric flag",
|
||||
input: "TICKET- https://example.com/TICKET?query=<num> --numeric",
|
||||
output: createOptions{
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
Numeric: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
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 *createOptions
|
||||
cmd := NewCmdCreate(f, func(opts *createOptions) 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.KeyPrefix, gotOpts.KeyPrefix)
|
||||
assert.Equal(t, tt.output.URLTemplate, gotOpts.URLTemplate)
|
||||
assert.Equal(t, tt.output.Numeric, gotOpts.Numeric)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubAutoLinkCreator struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (g stubAutoLinkCreator) Create(repo ghrepo.Interface, request AutolinkCreateRequest) (*domain.Autolink, error) {
|
||||
if g.err != nil {
|
||||
return nil, g.err
|
||||
}
|
||||
|
||||
return &domain.Autolink{
|
||||
ID: 1,
|
||||
KeyPrefix: request.KeyPrefix,
|
||||
URLTemplate: request.URLTemplate,
|
||||
IsAlphanumeric: request.IsAlphanumeric,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type testAutolinkClientCreateError struct{}
|
||||
|
||||
func (e testAutolinkClientCreateError) Error() string {
|
||||
return "autolink client create error"
|
||||
}
|
||||
|
||||
func TestCreateRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *createOptions
|
||||
stubCreator stubAutoLinkCreator
|
||||
expectedErr error
|
||||
errMsg string
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "success, alphanumeric",
|
||||
opts: &createOptions{
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
},
|
||||
stubCreator: stubAutoLinkCreator{},
|
||||
wantStdout: heredoc.Doc(`
|
||||
✓ Alphanumeric autolink created in OWNER/REPO (id: 1)
|
||||
TICKET-<num> → https://example.com/TICKET?query=<num>
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "success, numeric",
|
||||
opts: &createOptions{
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
Numeric: true,
|
||||
},
|
||||
stubCreator: stubAutoLinkCreator{},
|
||||
wantStdout: heredoc.Doc(`
|
||||
✓ Numeric autolink created in OWNER/REPO (id: 1)
|
||||
TICKET-<num> → https://example.com/TICKET?query=<num>
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "client error",
|
||||
opts: &createOptions{
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
},
|
||||
stubCreator: stubAutoLinkCreator{err: testAutolinkClientCreateError{}},
|
||||
expectedErr: testAutolinkClientCreateError{},
|
||||
errMsg: fmt.Sprint("error creating autolink: ", testAutolinkClientCreateError{}.Error()),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
|
||||
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.AutolinkClient = &tt.stubCreator
|
||||
err := createRun(opts)
|
||||
|
||||
if tt.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, tt.expectedErr)
|
||||
assert.Equal(t, err.Error(), tt.errMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if tt.wantStdout != "" {
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
}
|
||||
|
||||
if tt.wantStderr != "" {
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
83
pkg/cmd/repo/autolink/create/http.go
Normal file
83
pkg/cmd/repo/autolink/create/http.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
)
|
||||
|
||||
type AutolinkCreator struct {
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type AutolinkCreateRequest struct {
|
||||
IsAlphanumeric bool `json:"is_alphanumeric"`
|
||||
KeyPrefix string `json:"key_prefix"`
|
||||
URLTemplate string `json:"url_template"`
|
||||
}
|
||||
|
||||
func (a *AutolinkCreator) Create(repo ghrepo.Interface, request AutolinkCreateRequest) (*domain.Autolink, error) {
|
||||
path := fmt.Sprintf("repos/%s/%s/autolinks", repo.RepoOwner(), repo.RepoName())
|
||||
url := ghinstance.RESTPrefix(repo.RepoHost()) + path
|
||||
|
||||
requestByte, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestBody := bytes.NewReader(requestByte)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, url, requestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := a.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// if resp.StatusCode != http.StatusCreated {
|
||||
// return nil, api.HandleHTTPError(resp)
|
||||
// }
|
||||
|
||||
err = handleAutolinkCreateError(resp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var autolink domain.Autolink
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&autolink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &autolink, nil
|
||||
}
|
||||
|
||||
func handleAutolinkCreateError(resp *http.Response) error {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusCreated:
|
||||
return nil
|
||||
case http.StatusNotFound:
|
||||
err := api.HandleHTTPError(resp)
|
||||
var httpErr api.HTTPError
|
||||
if errors.As(err, &httpErr) {
|
||||
httpErr.Message = "Must have admin rights to Repository."
|
||||
return httpErr
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return api.HandleHTTPError(resp)
|
||||
}
|
||||
}
|
||||
155
pkg/cmd/repo/autolink/create/http_test.go
Normal file
155
pkg/cmd/repo/autolink/create/http_test.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAutoLinkCreator_Create(t *testing.T) {
|
||||
repo := ghrepo.New("OWNER", "REPO")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req AutolinkCreateRequest
|
||||
stubStatus int
|
||||
stubRespJSON string
|
||||
|
||||
expectedAutolink *domain.Autolink
|
||||
expectErr bool
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "201 successful creation",
|
||||
req: AutolinkCreateRequest{
|
||||
IsAlphanumeric: true,
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
},
|
||||
stubStatus: http.StatusCreated,
|
||||
stubRespJSON: `{
|
||||
"id": 1,
|
||||
"is_alphanumeric": true,
|
||||
"key_prefix": "TICKET-",
|
||||
"url_template": "https://example.com/TICKET?query=<num>"
|
||||
}`,
|
||||
expectedAutolink: &domain.Autolink{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
IsAlphanumeric: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "422 URL template not valid URL",
|
||||
req: AutolinkCreateRequest{
|
||||
IsAlphanumeric: true,
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "foo/<num>",
|
||||
},
|
||||
stubStatus: http.StatusUnprocessableEntity,
|
||||
stubRespJSON: `{
|
||||
"message": "Validation Failed",
|
||||
"errors": [
|
||||
{
|
||||
"resource": "KeyLink",
|
||||
"code": "custom",
|
||||
"field": "url_template",
|
||||
"message": "url_template must be an absolute URL"
|
||||
}
|
||||
],
|
||||
"documentation_url": "https://docs.github.com/rest/repos/autolinks#create-an-autolink-reference-for-a-repository",
|
||||
"status": "422"
|
||||
}`,
|
||||
expectErr: true,
|
||||
expectedErrMsg: heredoc.Doc(`
|
||||
HTTP 422: Validation Failed (https://api.github.com/repos/OWNER/REPO/autolinks)
|
||||
url_template must be an absolute URL`),
|
||||
},
|
||||
{
|
||||
name: "404 repo not found",
|
||||
req: AutolinkCreateRequest{
|
||||
IsAlphanumeric: true,
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
},
|
||||
stubStatus: http.StatusNotFound,
|
||||
stubRespJSON: `{
|
||||
"message": "Not Found",
|
||||
"documentation_url": "https://docs.github.com/rest/repos/autolinks#create-an-autolink-reference-for-a-repository",
|
||||
"status": "404"
|
||||
}`,
|
||||
expectErr: true,
|
||||
expectedErrMsg: "HTTP 404: Must have admin rights to Repository. (https://api.github.com/repos/OWNER/REPO/autolinks)",
|
||||
},
|
||||
{
|
||||
name: "422 URL template missing <num>",
|
||||
req: AutolinkCreateRequest{
|
||||
IsAlphanumeric: true,
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET",
|
||||
},
|
||||
stubStatus: http.StatusUnprocessableEntity,
|
||||
stubRespJSON: `{"message":"Validation Failed","errors":[{"resource":"KeyLink","code":"custom","field":"url_template","message":"url_template is missing a <num> token"}],"documentation_url":"https://docs.github.com/rest/repos/autolinks#create-an-autolink-reference-for-a-repository","status":"422"}`,
|
||||
expectErr: true,
|
||||
expectedErrMsg: heredoc.Doc(`
|
||||
HTTP 422: Validation Failed (https://api.github.com/repos/OWNER/REPO/autolinks)
|
||||
url_template is missing a <num> token`),
|
||||
},
|
||||
{
|
||||
name: "422 already exists",
|
||||
req: AutolinkCreateRequest{
|
||||
IsAlphanumeric: true,
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
},
|
||||
stubStatus: http.StatusUnprocessableEntity,
|
||||
stubRespJSON: `{"message":"Validation Failed","errors":[{"resource":"KeyLink","code":"already_exists","field":"key_prefix"}],"documentation_url":"https://docs.github.com/rest/repos/autolinks#create-an-autolink-reference-for-a-repository","status":"422"}`,
|
||||
expectErr: true,
|
||||
expectedErrMsg: heredoc.Doc(`
|
||||
HTTP 422: Validation Failed (https://api.github.com/repos/OWNER/REPO/autolinks)
|
||||
KeyLink.key_prefix already exists`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
reg.Register(
|
||||
httpmock.REST(
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("repos/%s/%s/autolinks", repo.RepoOwner(), repo.RepoName())),
|
||||
httpmock.RESTPayload(tt.stubStatus, tt.stubRespJSON,
|
||||
func(payload map[string]interface{}) {
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"is_alphanumeric": tt.req.IsAlphanumeric,
|
||||
"key_prefix": tt.req.KeyPrefix,
|
||||
"url_template": tt.req.URLTemplate,
|
||||
}, payload)
|
||||
},
|
||||
),
|
||||
)
|
||||
defer reg.Verify(t)
|
||||
|
||||
autolinkCreator := &AutolinkCreator{
|
||||
HTTPClient: &http.Client{Transport: reg},
|
||||
}
|
||||
|
||||
autolink, err := autolinkCreator.Create(repo, tt.req)
|
||||
|
||||
if tt.expectErr {
|
||||
require.EqualError(t, err, tt.expectedErrMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedAutolink, autolink)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
14
pkg/cmd/repo/autolink/domain/autolink.go
Normal file
14
pkg/cmd/repo/autolink/domain/autolink.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package domain
|
||||
|
||||
import "github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
||||
type Autolink struct {
|
||||
ID int `json:"id"`
|
||||
IsAlphanumeric bool `json:"is_alphanumeric"`
|
||||
KeyPrefix string `json:"key_prefix"`
|
||||
URLTemplate string `json:"url_template"`
|
||||
}
|
||||
|
||||
func (a *Autolink) ExportData(fields []string) map[string]interface{} {
|
||||
return cmdutil.StructExportData(a, fields)
|
||||
}
|
||||
|
|
@ -8,16 +8,17 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
)
|
||||
|
||||
type AutolinkLister struct {
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func (a *AutolinkLister) List(repo ghrepo.Interface) ([]autolink, error) {
|
||||
func (a *AutolinkLister) List(repo ghrepo.Interface) ([]domain.Autolink, error) {
|
||||
path := fmt.Sprintf("repos/%s/%s/autolinks", repo.RepoOwner(), repo.RepoName())
|
||||
url := ghinstance.RESTPrefix(repo.RepoHost()) + path
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -33,7 +34,7 @@ func (a *AutolinkLister) List(repo ghrepo.Interface) ([]autolink, error) {
|
|||
} else if resp.StatusCode > 299 {
|
||||
return nil, api.HandleHTTPError(resp)
|
||||
}
|
||||
var autolinks []autolink
|
||||
var autolinks []domain.Autolink
|
||||
err = json.NewDecoder(resp.Body).Decode(&autolinks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -15,19 +16,19 @@ func TestAutoLinkLister_List(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
repo ghrepo.Interface
|
||||
resp []autolink
|
||||
resp []domain.Autolink
|
||||
status int
|
||||
}{
|
||||
{
|
||||
name: "no autolinks",
|
||||
repo: ghrepo.New("OWNER", "REPO"),
|
||||
resp: []autolink{},
|
||||
resp: []domain.Autolink{},
|
||||
status: 200,
|
||||
},
|
||||
{
|
||||
name: "two autolinks",
|
||||
repo: ghrepo.New("OWNER", "REPO"),
|
||||
resp: []autolink{
|
||||
resp: []domain.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
IsAlphanumeric: true,
|
||||
|
|
@ -54,7 +55,7 @@ func TestAutoLinkLister_List(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
reg.Register(
|
||||
httpmock.REST("GET", fmt.Sprintf("repos/%s/%s/autolinks", tt.repo.RepoOwner(), tt.repo.RepoName())),
|
||||
httpmock.REST(http.MethodGet, fmt.Sprintf("repos/%s/%s/autolinks", tt.repo.RepoOwner(), tt.repo.RepoName())),
|
||||
httpmock.StatusJSONResponse(tt.status, tt.resp),
|
||||
)
|
||||
defer reg.Verify(t)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/tableprinter"
|
||||
"github.com/cli/cli/v2/internal/text"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -21,29 +22,18 @@ var autolinkFields = []string{
|
|||
"urlTemplate",
|
||||
}
|
||||
|
||||
type autolink struct {
|
||||
ID int `json:"id"`
|
||||
IsAlphanumeric bool `json:"is_alphanumeric"`
|
||||
KeyPrefix string `json:"key_prefix"`
|
||||
URLTemplate string `json:"url_template"`
|
||||
}
|
||||
|
||||
func (s *autolink) ExportData(fields []string) map[string]interface{} {
|
||||
return cmdutil.StructExportData(s, fields)
|
||||
}
|
||||
|
||||
type listOptions struct {
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser.Browser
|
||||
AutolinkClient AutolinkClient
|
||||
AutolinkClient AutolinkListClient
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Exporter cmdutil.Exporter
|
||||
WebMode bool
|
||||
}
|
||||
|
||||
type AutolinkClient interface {
|
||||
List(repo ghrepo.Interface) ([]autolink, error)
|
||||
type AutolinkListClient interface {
|
||||
List(repo ghrepo.Interface) ([]domain.Autolink, error)
|
||||
}
|
||||
|
||||
func NewCmdList(f *cmdutil.Factory, runF func(*listOptions) error) *cobra.Command {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/repo/autolink/domain"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/jsonfieldstest"
|
||||
|
|
@ -96,11 +97,11 @@ func TestNewCmdList(t *testing.T) {
|
|||
}
|
||||
|
||||
type stubAutoLinkLister struct {
|
||||
autolinks []autolink
|
||||
autolinks []domain.Autolink
|
||||
err error
|
||||
}
|
||||
|
||||
func (g stubAutoLinkLister) List(repo ghrepo.Interface) ([]autolink, error) {
|
||||
func (g stubAutoLinkLister) List(repo ghrepo.Interface) ([]domain.Autolink, error) {
|
||||
return g.autolinks, g.err
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +126,7 @@ func TestListRun(t *testing.T) {
|
|||
opts: &listOptions{},
|
||||
isTTY: true,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{
|
||||
autolinks: []domain.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
|
|
@ -161,7 +162,7 @@ func TestListRun(t *testing.T) {
|
|||
},
|
||||
isTTY: true,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{
|
||||
autolinks: []domain.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
|
|
@ -184,7 +185,7 @@ func TestListRun(t *testing.T) {
|
|||
opts: &listOptions{},
|
||||
isTTY: false,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{
|
||||
autolinks: []domain.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
|
|
@ -210,7 +211,7 @@ func TestListRun(t *testing.T) {
|
|||
opts: &listOptions{},
|
||||
isTTY: true,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{},
|
||||
autolinks: []domain.Autolink{},
|
||||
},
|
||||
expectedErr: cmdutil.NewNoResultsError("no autolinks found in OWNER/REPO"),
|
||||
wantStderr: "",
|
||||
|
|
@ -220,7 +221,7 @@ func TestListRun(t *testing.T) {
|
|||
opts: &listOptions{},
|
||||
isTTY: true,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{},
|
||||
autolinks: []domain.Autolink{},
|
||||
err: testAutolinkClientListError{},
|
||||
},
|
||||
expectedErr: testAutolinkClientListError{},
|
||||
|
|
|
|||
|
|
@ -188,7 +188,11 @@ func RESTPayload(responseStatus int, responseBody string, cb func(payload map[st
|
|||
return nil, err
|
||||
}
|
||||
cb(bodyData)
|
||||
return httpResponse(responseStatus, req, bytes.NewBufferString(responseBody)), nil
|
||||
|
||||
header := http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
return httpResponseWithHeader(responseStatus, req, bytes.NewBufferString(responseBody), header), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue