740 lines
19 KiB
Go
740 lines
19 KiB
Go
package lock
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/internal/prompter"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/httpmock"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/cli/cli/v2/test"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func Test_NewCmdLock(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
args string
|
|
want LockOptions
|
|
wantErr string
|
|
tty bool
|
|
}{
|
|
{
|
|
name: "sets reason",
|
|
args: "--reason off_topic 451",
|
|
want: LockOptions{
|
|
Reason: "off_topic",
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "no args",
|
|
wantErr: "accepts 1 arg(s), received 0",
|
|
},
|
|
{
|
|
name: "no flags",
|
|
args: "451",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "issue number argument",
|
|
args: "451 --repo owner/repo",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "argument is hash prefixed number",
|
|
// Escaping is required here to avoid what I think is shellex treating it as a comment.
|
|
args: "\\#451 --repo owner/repo",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "argument is a URL",
|
|
args: "https://github.com/cli/cli/issues/451",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "argument cannot be parsed to an issue",
|
|
args: "unparseable",
|
|
wantErr: "invalid issue format: \"unparseable\"",
|
|
},
|
|
{
|
|
name: "bad reason",
|
|
args: "--reason bad 451",
|
|
wantErr: "invalid reason bad",
|
|
},
|
|
{
|
|
name: "bad reason tty",
|
|
args: "--reason bad 451",
|
|
tty: true,
|
|
wantErr: "X Invalid reason: bad\n",
|
|
},
|
|
{
|
|
name: "interactive",
|
|
args: "451",
|
|
tty: true,
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
Interactive: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ios, _, _, _ := iostreams.Test()
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStdinTTY(tt.tty)
|
|
ios.SetStderrTTY(tt.tty)
|
|
f := &cmdutil.Factory{
|
|
IOStreams: ios,
|
|
}
|
|
var opts *LockOptions
|
|
cmd := NewCmdLock(f, "issue", func(_ string, o *LockOptions) error {
|
|
opts = o
|
|
return nil
|
|
})
|
|
cmd.PersistentFlags().StringP("repo", "R", "", "")
|
|
|
|
argv, err := shlex.Split(tt.args)
|
|
assert.NoError(t, err)
|
|
|
|
cmd.SetArgs(argv)
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetErr(io.Discard)
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
return
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, tt.want.Reason, opts.Reason)
|
|
assert.Equal(t, tt.want.IssueNumber, opts.IssueNumber)
|
|
assert.Equal(t, tt.want.Interactive, opts.Interactive)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_NewCmdUnlock(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
args string
|
|
want LockOptions
|
|
wantErr string
|
|
tty bool
|
|
}{
|
|
{
|
|
name: "no args",
|
|
wantErr: "accepts 1 arg(s), received 0",
|
|
},
|
|
{
|
|
name: "no flags",
|
|
args: "451",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "issue number argument",
|
|
args: "451 --repo owner/repo",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "argument is hash prefixed number",
|
|
// Escaping is required here to avoid what I think is shellex treating it as a comment.
|
|
args: "\\#451 --repo owner/repo",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "argument is a URL",
|
|
args: "https://github.com/cli/cli/issues/451",
|
|
want: LockOptions{
|
|
IssueNumber: 451,
|
|
},
|
|
},
|
|
{
|
|
name: "argument cannot be parsed to an issue",
|
|
args: "unparseable",
|
|
wantErr: "invalid issue format: \"unparseable\"",
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ios, _, _, _ := iostreams.Test()
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStdinTTY(tt.tty)
|
|
ios.SetStderrTTY(tt.tty)
|
|
f := &cmdutil.Factory{
|
|
IOStreams: ios,
|
|
}
|
|
var opts *LockOptions
|
|
cmd := NewCmdUnlock(f, "issue", func(_ string, o *LockOptions) error {
|
|
opts = o
|
|
return nil
|
|
})
|
|
cmd.PersistentFlags().StringP("repo", "R", "", "")
|
|
|
|
argv, err := shlex.Split(tt.args)
|
|
assert.NoError(t, err)
|
|
|
|
cmd.SetArgs(argv)
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetErr(io.Discard)
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
return
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, tt.want.IssueNumber, opts.IssueNumber)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_runLock(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
opts LockOptions
|
|
promptStubs func(*testing.T, *prompter.PrompterMock)
|
|
httpStubs func(*testing.T, *httpmock.Registry)
|
|
wantOut string
|
|
wantErrOut string
|
|
wantErr string
|
|
tty bool
|
|
state string
|
|
}{
|
|
{
|
|
name: "lock issue nontty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
},
|
|
{
|
|
name: "lock issue tty",
|
|
tty: true,
|
|
opts: LockOptions{
|
|
Interactive: true,
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
},
|
|
state: Lock,
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
promptStubs: func(t *testing.T, pm *prompter.PrompterMock) {
|
|
pm.SelectFunc = func(p, d string, opts []string) (int, error) {
|
|
if p == "Lock reason?" {
|
|
assert.Equal(t, []string{"None", "Off topic", "Resolved", "Spam", "Too heated"}, opts)
|
|
|
|
return prompter.IndexFor(opts, "Too heated")
|
|
}
|
|
|
|
return -1, prompter.NoSuchPromptErr(p)
|
|
}
|
|
},
|
|
wantOut: "✓ Locked as TOO_HEATED: Issue OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "lock issue with explicit reason tty",
|
|
tty: true,
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
Reason: "off_topic",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
wantOut: "✓ Locked as OFF_TOPIC: Issue OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "unlock issue tty",
|
|
tty: true,
|
|
state: Unlock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation UnlockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"unlockLockable": {
|
|
"unlockedRecord": {
|
|
"locked": false }}}}`))
|
|
},
|
|
wantOut: "✓ Unlocked: Issue OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "unlock issue nontty",
|
|
state: Unlock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation UnlockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"unlockLockable": {
|
|
"unlockedRecord": {
|
|
"locked": false }}}}`))
|
|
},
|
|
},
|
|
{
|
|
name: "lock issue with explicit reason nontty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
Reason: "off_topic",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
},
|
|
{
|
|
name: "relock issue tty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
Reason: "off_topic",
|
|
},
|
|
tty: true,
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation UnlockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"unlockLockable": {
|
|
"unlockedRecord": {
|
|
"locked": false }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
promptStubs: func(t *testing.T, pm *prompter.PrompterMock) {
|
|
pm.ConfirmFunc = func(p string, d bool) (bool, error) {
|
|
if p == "Issue OWNER/REPO#451 already locked. Unlock and lock again as OFF_TOPIC?" {
|
|
return true, nil
|
|
}
|
|
|
|
return false, prompter.NoSuchPromptErr(p)
|
|
}
|
|
},
|
|
wantOut: "✓ Locked as OFF_TOPIC: Issue OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "relock issue nontty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "issue",
|
|
Reason: "off_topic",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "Issue" }}}}`))
|
|
},
|
|
wantErr: "already locked",
|
|
},
|
|
|
|
{
|
|
name: "lock pr nontty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
},
|
|
{
|
|
name: "lock pr tty",
|
|
tty: true,
|
|
opts: LockOptions{
|
|
Interactive: true,
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
},
|
|
state: Lock,
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
promptStubs: func(t *testing.T, pm *prompter.PrompterMock) {
|
|
pm.SelectFunc = func(p, d string, opts []string) (int, error) {
|
|
if p == "Lock reason?" {
|
|
return prompter.IndexFor(opts, "Too heated")
|
|
}
|
|
|
|
return -1, prompter.NoSuchPromptErr(p)
|
|
}
|
|
},
|
|
wantOut: "✓ Locked as TOO_HEATED: Pull request OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "lock pr with explicit reason tty",
|
|
tty: true,
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
Reason: "off_topic",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
wantOut: "✓ Locked as OFF_TOPIC: Pull request OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "lock pr with explicit nontty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
Reason: "off_topic",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
},
|
|
{
|
|
name: "unlock pr tty",
|
|
state: Unlock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation UnlockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"unlockLockable": {
|
|
"unlockedRecord": {
|
|
"locked": false }}}}`))
|
|
},
|
|
},
|
|
{
|
|
name: "unlock pr nontty",
|
|
tty: true,
|
|
state: Unlock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation UnlockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"unlockLockable": {
|
|
"unlockedRecord": {
|
|
"locked": false }}}}`))
|
|
},
|
|
wantOut: "✓ Unlocked: Pull request OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "relock pr tty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
Reason: "off_topic",
|
|
},
|
|
tty: true,
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation UnlockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"unlockLockable": {
|
|
"unlockedRecord": {
|
|
"locked": false }}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation LockLockable\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": {
|
|
"lockLockable": {
|
|
"lockedRecord": {
|
|
"locked": true }}}}`))
|
|
},
|
|
promptStubs: func(t *testing.T, pm *prompter.PrompterMock) {
|
|
pm.ConfirmFunc = func(p string, d bool) (bool, error) {
|
|
if p == "Pull request OWNER/REPO#451 already locked. Unlock and lock again as OFF_TOPIC?" {
|
|
return true, nil
|
|
}
|
|
|
|
return false, prompter.NoSuchPromptErr(p)
|
|
}
|
|
},
|
|
wantOut: "✓ Locked as OFF_TOPIC: Pull request OWNER/REPO#451 (traverse the library)\n",
|
|
},
|
|
{
|
|
name: "relock pr nontty",
|
|
state: Lock,
|
|
opts: LockOptions{
|
|
IssueNumber: 451,
|
|
ParentCmd: "pr",
|
|
Reason: "off_topic",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
|
|
"number": 451,
|
|
"locked": true,
|
|
"title": "traverse the library",
|
|
"__typename": "PullRequest" }}}}`))
|
|
},
|
|
wantErr: "already locked",
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
reg := &httpmock.Registry{}
|
|
defer reg.Verify(t)
|
|
if tt.httpStubs != nil {
|
|
tt.httpStubs(t, reg)
|
|
}
|
|
|
|
pm := &prompter.PrompterMock{}
|
|
if tt.promptStubs != nil {
|
|
tt.promptStubs(t, pm)
|
|
}
|
|
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStdinTTY(tt.tty)
|
|
ios.SetStderrTTY(tt.tty)
|
|
|
|
tt.opts.Prompter = pm
|
|
tt.opts.IO = ios
|
|
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
|
return ghrepo.FromFullName("OWNER/REPO")
|
|
}
|
|
tt.opts.HttpClient = func() (*http.Client, error) {
|
|
return &http.Client{Transport: reg}, nil
|
|
}
|
|
|
|
err := lockRun(tt.state, &tt.opts)
|
|
output := &test.CmdOut{
|
|
OutBuf: stdout,
|
|
ErrBuf: stderr,
|
|
}
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wantOut, output.String())
|
|
assert.Equal(t, tt.wantErrOut, output.Stderr())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReasons(t *testing.T) {
|
|
assert.Equal(t, len(reasons), len(reasonsApi))
|
|
|
|
for _, reason := range reasons {
|
|
assert.Equal(t, strings.ToUpper(reason), string(*reasonsMap[reason]))
|
|
}
|
|
}
|