Merge pull request #10180 from hoffm/create-autolinks
feat: Add support for creating autolink references
This commit is contained in:
commit
b0cd1fb655
11 changed files with 594 additions and 37 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"
|
||||
|
|
@ -12,18 +13,17 @@ func NewCmdAutolink(f *cmdutil.Factory) *cobra.Command {
|
|||
Use: "autolink <command>",
|
||||
Short: "Manage autolink references",
|
||||
Long: heredoc.Docf(`
|
||||
Work with GitHub autolink references.
|
||||
|
||||
GitHub autolinks require admin access to configure and can be found at
|
||||
https://github.com/{owner}/{repo}/settings/key_links.
|
||||
Use %[1]sgh repo autolink list --web%[1]s to open this page for the current repository.
|
||||
|
||||
For more information about GitHub autolinks, see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-autolinks-to-reference-external-resources
|
||||
`, "`"),
|
||||
Autolinks link issues, pull requests, commit messages, and release descriptions to external third-party services.
|
||||
|
||||
Autolinks require %[1]sadmin%[1]s role to view or manage.
|
||||
|
||||
For more information, see <https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-autolinks-to-reference-external-resources>
|
||||
`, "`"),
|
||||
}
|
||||
cmdutil.EnableRepoOverride(cmd, f)
|
||||
|
||||
cmd.AddCommand(cmdList.NewCmdList(f, nil))
|
||||
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
118
pkg/cmd/repo/autolink/create/create.go
Normal file
118
pkg/cmd/repo/autolink/create/create.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
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/shared"
|
||||
"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) (*shared.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.
|
||||
|
||||
The %[1]skeyPrefix%[1]s argument specifies the prefix that will generate a link when it is appended by certain characters.
|
||||
|
||||
The %[1]surlTemplate%[1]s argument specifies the target URL that will be generated when the keyPrefix is found, which
|
||||
must contain %[1]s<num>%[1]s variable for the reference number.
|
||||
|
||||
By default, autolinks are alphanumeric with %[1]s--numeric%[1]s flag used to create a numeric autolink.
|
||||
|
||||
The %[1]s<num>%[1]s variable behavior differs depending on whether the autolink is alphanumeric or numeric:
|
||||
|
||||
- alphanumeric: matches %[1]sA-Z%[1]s (case insensitive), %[1]s0-9%[1]s, and %[1]s-%[1]s
|
||||
- numeric: matches %[1]s0-9%[1]s
|
||||
|
||||
If the template contains multiple instances of %[1]s<num>%[1]s, only the first will be replaced.
|
||||
`, "`"),
|
||||
Example: heredoc.Doc(`
|
||||
# Create an alphanumeric autolink to example.com for the key prefix "TICKET-".
|
||||
# Generates https://example.com/TICKET?query=123abc from "TICKET-123abc".
|
||||
$ gh repo autolink create TICKET- "https://example.com/TICKET?query=<num>"
|
||||
|
||||
# Create a numeric autolink to example.com for the key prefix "STORY-".
|
||||
# Generates https://example.com/STORY?id=123 from "STORY-123".
|
||||
$ 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 numeric")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createRun(opts *createOptions) error {
|
||||
repo, err := opts.BaseRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := AutolinkCreateRequest{
|
||||
KeyPrefix: opts.KeyPrefix,
|
||||
URLTemplate: opts.URLTemplate,
|
||||
IsAlphanumeric: !opts.Numeric,
|
||||
}
|
||||
|
||||
autolink, err := opts.AutolinkClient.Create(repo, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating autolink: %w", err)
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintf(opts.IO.Out,
|
||||
"%s Created repository autolink %d on %s\n",
|
||||
cs.SuccessIconWithColor(cs.Green),
|
||||
autolink.ID,
|
||||
ghrepo.FullName(repo))
|
||||
|
||||
return nil
|
||||
}
|
||||
190
pkg/cmd/repo/autolink/create/create_test.go
Normal file
190
pkg/cmd/repo/autolink/create/create_test.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"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 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) (*shared.Autolink, error) {
|
||||
if g.err != nil {
|
||||
return nil, g.err
|
||||
}
|
||||
|
||||
return &shared.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: "✓ Created repository autolink 1 on OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
name: "success, numeric",
|
||||
opts: &createOptions{
|
||||
KeyPrefix: "TICKET-",
|
||||
URLTemplate: "https://example.com/TICKET?query=<num>",
|
||||
Numeric: true,
|
||||
},
|
||||
stubCreator: stubAutoLinkCreator{},
|
||||
wantStdout: "✓ Created repository autolink 1 on OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
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, tt.errMsg, err.Error())
|
||||
} 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/shared"
|
||||
)
|
||||
|
||||
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) (*shared.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 shared.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/shared"
|
||||
"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 *shared.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: &shared.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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/shared"
|
||||
)
|
||||
|
||||
type AutolinkLister struct {
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func (a *AutolinkLister) List(repo ghrepo.Interface) ([]autolink, error) {
|
||||
func (a *AutolinkLister) List(repo ghrepo.Interface) ([]shared.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 []shared.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/shared"
|
||||
"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 []shared.Autolink
|
||||
status int
|
||||
}{
|
||||
{
|
||||
name: "no autolinks",
|
||||
repo: ghrepo.New("OWNER", "REPO"),
|
||||
resp: []autolink{},
|
||||
resp: []shared.Autolink{},
|
||||
status: 200,
|
||||
},
|
||||
{
|
||||
name: "two autolinks",
|
||||
repo: ghrepo.New("OWNER", "REPO"),
|
||||
resp: []autolink{
|
||||
resp: []shared.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/shared"
|
||||
"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) ([]shared.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/shared"
|
||||
"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 []shared.Autolink
|
||||
err error
|
||||
}
|
||||
|
||||
func (g stubAutoLinkLister) List(repo ghrepo.Interface) ([]autolink, error) {
|
||||
func (g stubAutoLinkLister) List(repo ghrepo.Interface) ([]shared.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: []shared.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
|
|
@ -161,7 +162,7 @@ func TestListRun(t *testing.T) {
|
|||
},
|
||||
isTTY: true,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{
|
||||
autolinks: []shared.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
|
|
@ -184,7 +185,7 @@ func TestListRun(t *testing.T) {
|
|||
opts: &listOptions{},
|
||||
isTTY: false,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{
|
||||
autolinks: []shared.Autolink{
|
||||
{
|
||||
ID: 1,
|
||||
KeyPrefix: "TICKET-",
|
||||
|
|
@ -210,7 +211,7 @@ func TestListRun(t *testing.T) {
|
|||
opts: &listOptions{},
|
||||
isTTY: true,
|
||||
stubLister: stubAutoLinkLister{
|
||||
autolinks: []autolink{},
|
||||
autolinks: []shared.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: []shared.Autolink{},
|
||||
err: testAutolinkClientListError{},
|
||||
},
|
||||
expectedErr: testAutolinkClientListError{},
|
||||
|
|
|
|||
14
pkg/cmd/repo/autolink/shared/autolink.go
Normal file
14
pkg/cmd/repo/autolink/shared/autolink.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package shared
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -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