cli/commands/cmdutils/cmdutils_test.go

1382 lines
32 KiB
Go

package cmdutils
import (
"errors"
"fmt"
"testing"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"github.com/acarl005/stripansi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xanzy/go-gitlab"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"gitlab.com/gitlab-org/cli/pkg/git"
"gitlab.com/gitlab-org/cli/pkg/prompt"
)
func Test_ParseAssignees(t *testing.T) {
testCases := []struct {
name string
input []string
wantAdd []string
wantRemove []string
wantReplace []string
}{
{
name: "simple replace",
input: []string{"foo"},
wantAdd: []string{},
wantRemove: []string{},
wantReplace: []string{"foo"},
},
{
name: "only add",
input: []string{"+foo"},
wantAdd: []string{"foo"},
wantRemove: []string{},
wantReplace: []string{},
},
{
name: "only remove",
input: []string{"-foo", "!bar"},
wantAdd: []string{},
wantRemove: []string{"foo", "bar"},
wantReplace: []string{},
},
{
name: "only replace",
input: []string{"baz"},
wantAdd: []string{},
wantRemove: []string{},
wantReplace: []string{"baz"},
},
{
name: "add and remove",
input: []string{"+qux", "-foo", "!bar"},
wantAdd: []string{"qux"},
wantRemove: []string{"foo", "bar"},
wantReplace: []string{},
},
{
name: "add and replace",
input: []string{"+foo", "bar"},
wantAdd: []string{"foo"},
wantRemove: []string{},
wantReplace: []string{"bar"},
},
{
name: "remove and replace",
input: []string{"-foo", "bar", "!baz"},
wantAdd: []string{},
wantRemove: []string{"foo", "baz"},
wantReplace: []string{"bar"},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
uaGot := ParseAssignees(tC.input)
assert.ElementsMatch(t, uaGot.ToAdd, tC.wantAdd)
assert.ElementsMatch(t, uaGot.ToRemove, tC.wantRemove)
assert.ElementsMatch(t, uaGot.ToReplace, tC.wantReplace)
})
}
}
func Test_VerifyAssignees(t *testing.T) {
testCases := []struct {
name string
input UserAssignments
want string // expected error message
}{
{
name: "empty, no errors",
input: UserAssignments{
ToAdd: []string{},
ToRemove: []string{},
ToReplace: []string{},
},
},
{
name: "simple addition, no errors",
input: UserAssignments{
ToAdd: []string{"foo"},
ToRemove: []string{},
ToReplace: []string{},
},
},
{
name: "simple removal, no errors",
input: UserAssignments{
ToAdd: []string{},
ToRemove: []string{"foo"},
ToReplace: []string{},
},
},
{
name: "simple replace, no errors",
input: UserAssignments{
ToAdd: []string{},
ToRemove: []string{},
ToReplace: []string{"foo"},
},
},
{
name: "add and removal with multiple elements, no errors",
input: UserAssignments{
ToAdd: []string{"foo", "bar", "baz"},
ToRemove: []string{"qux", "quux", "quz"},
ToReplace: []string{},
},
},
{
name: "multi replace, no errors",
input: UserAssignments{
ToAdd: []string{},
ToRemove: []string{},
ToReplace: []string{"foo", "bar"},
},
},
{
name: "replace with add, error",
input: UserAssignments{
ToAdd: []string{"bar"},
ToRemove: []string{},
ToReplace: []string{"foo"},
},
want: "mixing relative (+,!,-) and absolute assignments is forbidden",
},
{
name: "replace with remove, error",
input: UserAssignments{
ToAdd: []string{},
ToRemove: []string{"baz"},
ToReplace: []string{"foo"},
},
want: "mixing relative (+,!,-) and absolute assignments is forbidden",
},
{
name: "overlapping add and removal element, error",
input: UserAssignments{
ToAdd: []string{"foo"},
ToRemove: []string{"foo"},
ToReplace: []string{},
},
want: `1 element "foo" present in both add and remove which is forbidden`,
},
{
name: "overlapping add and removal elements, error",
input: UserAssignments{
ToAdd: []string{"foo", "bar", "baz"},
ToRemove: []string{"foo", "baz"},
ToReplace: []string{},
},
want: `2 elements "foo baz" present in both add and remove which is forbidden`,
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
err := tC.input.VerifyAssignees()
if tC.want == "" {
if err != nil {
t.Errorf("VerifyAssignees() unexpected error = %s", err)
}
} else {
if tC.want != err.Error() {
t.Errorf("VerifyAssignees() expected = %s, got = %s", tC.want, err.Error())
}
}
})
}
}
func Test_UsersFromReplaces(t *testing.T) {
testCases := []struct {
name string
users []*gitlab.User
expectedIDs []int
expectedAction []string
}{
{
name: "nothingness",
users: []*gitlab.User{},
expectedIDs: []int{},
expectedAction: []string{},
},
{
name: "single user named foo",
users: []*gitlab.User{
{ID: 1, Username: "foo"},
},
expectedIDs: []int{1},
expectedAction: []string{`assigned to "@foo"`},
},
{
name: "multiple users named foo, bar and baz",
users: []*gitlab.User{
{ID: 1, Username: "foo"},
{ID: 3, Username: "bar"},
{ID: 7, Username: "baz"},
},
expectedIDs: []int{1, 3, 7},
expectedAction: []string{`assigned to "@foo @bar @baz"`},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
ua := UserAssignments{}
api.UsersByNames = func(apiClient *gitlab.Client, names []string) ([]*gitlab.User, error) {
return tC.users, nil
}
var gotAction []string
gotIDs, gotAction, err := ua.UsersFromReplaces(&gitlab.Client{}, gotAction)
if err != nil {
t.Errorf("UsersFromReplaces() unexpected error = %s", err)
}
assert.ElementsMatch(t, *gotIDs, tC.expectedIDs)
assert.ElementsMatch(t, gotAction, tC.expectedAction)
})
}
}
func Test_UserAssignmentsAPIFailure(t *testing.T) {
want := "failed to get users by their names" // Error message we want
ua := UserAssignments{
ToAdd: []string{"foo"},
} // Fill `ToAdd` so `cmdutils.UsersFromAddRemove()` reaches the api call
var err error
api.UsersByNames = func(apiClient *gitlab.Client, names []string) ([]*gitlab.User, error) {
return nil, fmt.Errorf("failed to get users by their names")
}
apiClient := gitlab.Client{} // Empty Client, it won't be used, just to satisfy the function signature
_, _, err = ua.UsersFromReplaces(&apiClient, nil)
if err == nil {
t.Errorf("UsersFromReplaces() expected error to not be nil")
}
if want != err.Error() {
t.Errorf("UsersFromReplace() expected error = %s, got = %v", want, err)
}
_, _, err = ua.UsersFromAddRemove(nil, nil, &apiClient, nil)
if err == nil {
t.Errorf("UsersFromReplaces() expected error to not be nil")
}
if want != err.Error() {
t.Errorf("UsersFromReplace() expected error = %s, got = %v", want, err)
}
}
func Test_UsersFromAddRemove(t *testing.T) {
testCases := []struct {
name string
users []*gitlab.User // Mock *gitlab.User received from api.UsersByNames
merge []*gitlab.BasicUser // Mock `.Assignee field` from a merge request
issue []*gitlab.IssueAssignee // Mock `.Assignee field` from an issue
expectedIDs []int
expectedAction []string
ua UserAssignments
wantErr string
}{
{
name: "add foo (issue and merge request)",
users: []*gitlab.User{
{
ID: 1,
Username: "foo",
},
},
expectedIDs: []int{1},
expectedAction: []string{`assigned "@foo"`},
ua: UserAssignments{ToAdd: []string{"foo"}},
},
{
name: "add foo, bar and baz (issue and merge request)",
users: []*gitlab.User{
{
ID: 1,
Username: "foo",
},
{
ID: 235,
Username: "bar",
},
{
ID: 1500,
Username: "baz",
},
},
expectedIDs: []int{1, 235, 1500},
expectedAction: []string{`assigned "@foo @bar @baz"`},
ua: UserAssignments{ToAdd: []string{"foo", "bar", "baz"}},
},
{
name: "remove foo (issue)",
users: []*gitlab.User{},
issue: []*gitlab.IssueAssignee{
{
ID: 1,
Username: "foo",
},
},
expectedIDs: []int{0},
expectedAction: []string{`unassigned "@foo"`},
ua: UserAssignments{ToRemove: []string{"foo"}},
},
{
name: "remove foo and baz out of foo, bar and baz (issue)",
users: []*gitlab.User{},
issue: []*gitlab.IssueAssignee{
{
ID: 1,
Username: "foo",
},
{
ID: 2,
Username: "bar",
},
{
ID: 3,
Username: "baz",
},
},
expectedIDs: []int{2},
expectedAction: []string{`unassigned "@foo @baz"`},
ua: UserAssignments{ToRemove: []string{"foo", "baz"}},
},
{
name: "remove foo out of foo and baz and add bar (issue)",
users: []*gitlab.User{
{
ID: 100,
Username: "bar",
},
},
issue: []*gitlab.IssueAssignee{
{
ID: 1,
Username: "foo",
},
{
ID: 500,
Username: "baz",
},
},
expectedIDs: []int{500, 100},
expectedAction: []string{
`unassigned "@foo"`,
`assigned "@bar"`,
},
ua: UserAssignments{
ToAdd: []string{"bar"},
ToRemove: []string{"foo"},
},
},
{
name: "remove foo (merge request)",
users: []*gitlab.User{},
merge: []*gitlab.BasicUser{
{
ID: 1,
Username: "foo",
},
},
expectedIDs: []int{0},
expectedAction: []string{`unassigned "@foo"`},
ua: UserAssignments{ToRemove: []string{"foo"}},
},
{
name: "remove foo and baz out of foo, bar and baz (merge request)",
users: []*gitlab.User{},
merge: []*gitlab.BasicUser{
{
ID: 1,
Username: "foo",
},
{
ID: 2,
Username: "bar",
},
{
ID: 3,
Username: "baz",
},
},
expectedIDs: []int{2},
expectedAction: []string{`unassigned "@foo @baz"`},
ua: UserAssignments{ToRemove: []string{"foo", "baz"}},
},
{
name: "remove foo out of foo and baz and add bar (merge request)",
users: []*gitlab.User{
{
ID: 100,
Username: "bar",
},
},
merge: []*gitlab.BasicUser{
{
ID: 1,
Username: "foo",
},
{
ID: 500,
Username: "baz",
},
},
expectedIDs: []int{500, 100},
expectedAction: []string{
`unassigned "@foo"`,
`assigned "@bar"`,
},
ua: UserAssignments{
ToAdd: []string{"bar"},
ToRemove: []string{"foo"},
},
},
{
name: "try to pass both issue and merge request users",
issue: []*gitlab.IssueAssignee{
{
ID: 1,
Username: "foo",
},
},
merge: []*gitlab.BasicUser{
{
ID: 5,
Username: "bar",
},
},
wantErr: "issueAssignes and mergeRequestAssignes can't both not be nil",
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
api.UsersByNames = func(_ *gitlab.Client, _ []string) ([]*gitlab.User, error) {
return tC.users, nil
}
var gotAction []string
gotIDs, gotAction, err := tC.ua.UsersFromAddRemove(tC.issue, tC.merge, &gitlab.Client{}, gotAction)
if err != nil {
if tC.wantErr != "" && tC.wantErr != err.Error() {
t.Errorf("UsersFromAddRemove() expected error = %s, got = %s", tC.wantErr, err)
} else if tC.wantErr == "" {
t.Errorf("UsersFromAddRemove() unexpected error = %s", err)
}
}
assert.ElementsMatch(t, *gotIDs, tC.expectedIDs)
assert.ElementsMatch(t, gotAction, tC.expectedAction)
})
}
}
func Test_ParseMilestoneTitleIsID(t *testing.T) {
title := "1"
expectedMilestoneID := 1
// Override function to return an error, it should never reach this
api.ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
return nil, fmt.Errorf("We shouldn't have reached here")
}
got, err := ParseMilestone(&gitlab.Client{}, glrepo.New("foo", "bar"), title)
if err != nil {
t.Errorf("ParseMilestone() unexpected error = %s", err)
}
if got != expectedMilestoneID {
t.Errorf("ParseMilestone() got = %d, expected = %d", got, expectedMilestoneID)
}
}
func Test_ParseMilestoneAPIFail(t *testing.T) {
title := "AsLongAsItDoesn'tConvertToInt"
want := "api call failed in api.MilestoneByTitle()"
// Override function to return an error simulating an API call failure
api.ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
return nil, fmt.Errorf("api call failed in api.MilestoneByTitle()")
}
_, err := ParseMilestone(&gitlab.Client{}, glrepo.New("foo", "bar"), title)
if err == nil {
t.Errorf("ParseMilestone() expected error")
}
if want != err.Error() {
t.Errorf("ParseMilestone() expected error = %s, got error = %s", want, err)
}
}
func Test_ParseMilestoneTitleToID(t *testing.T) {
milestoneTitle := "kind: testing"
expectedID := 3
// Override function so it returns the correct milestone
api.ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
return &gitlab.Milestone{
Title: "kind: testing",
ID: 3,
},
nil
}
got, err := ParseMilestone(&gitlab.Client{}, glrepo.New("foo", "bar"), milestoneTitle)
if err != nil {
t.Errorf("ParseMilestone() unexpected error = %s", err)
}
if got != expectedID {
t.Errorf("ParseMilestone() expected = %d, got = %d", expectedID, got)
}
}
func Test_PickMetadata(t *testing.T) {
const (
labelsLabel = "labels"
assigneeLabel = "assignees"
milestoneLabel = "milestones"
)
testCases := []struct {
name string
values []string
expected []Action
}{
{
name: "nothing picked",
},
{
name: "labels",
values: []string{labelsLabel},
expected: []Action{AddLabelAction},
},
{
name: "assignees",
values: []string{assigneeLabel},
expected: []Action{AddAssigneeAction},
},
{
name: "milestone",
values: []string{milestoneLabel},
expected: []Action{AddMilestoneAction},
},
{
name: "labels and assignees",
values: []string{labelsLabel, assigneeLabel},
expected: []Action{AddLabelAction, AddAssigneeAction},
},
{
name: "labels and milestone",
values: []string{labelsLabel, milestoneLabel},
expected: []Action{AddLabelAction, AddMilestoneAction},
},
{
name: "assignees and milestone",
values: []string{assigneeLabel, milestoneLabel},
expected: []Action{AddAssigneeAction, AddMilestoneAction},
},
{
name: "labels, assignees and milestone",
values: []string{labelsLabel, assigneeLabel, milestoneLabel},
expected: []Action{AddLabelAction, AddAssigneeAction, AddMilestoneAction},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "metadata",
Value: tC.values,
},
})
got, err := PickMetadata()
if err != nil {
t.Errorf("PickMetadata() unexpected error = %s", err)
}
assert.ElementsMatch(t, got, tC.expected)
})
}
t.Run("Prompt fails", func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "metadata",
Value: errors.New("meant to fail"),
},
})
got, err := PickMetadata()
assert.Nil(t, got)
assert.EqualError(t, err, "could not prompt: meant to fail")
})
}
func Test_AssigneesPrompt(t *testing.T) {
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
testCases := []struct {
name string
choice []string
mock []*gitlab.ProjectMember
output []string
minimumAccessLevel int
expectedStdErr string
expectedError string
}{
{
name: "nothing",
},
{
name: "reporter",
choice: []string{"foo (reporter)"},
output: []string{"foo"},
minimumAccessLevel: 20,
mock: []*gitlab.ProjectMember{
{
Username: "foo",
AccessLevel: gitlab.AccessLevelValue(20),
},
},
},
{
name: "reporter-developer",
choice: []string{"foo (reporter)", "bar (developer)"},
output: []string{"foo", "bar"},
minimumAccessLevel: 20,
mock: []*gitlab.ProjectMember{
{
Username: "foo",
AccessLevel: gitlab.AccessLevelValue(20),
},
{
Username: "bar",
AccessLevel: gitlab.AccessLevelValue(30),
},
},
},
{
name: "reporter-developer-maintainer",
choice: []string{"foo (reporter)", "bar (developer)", "baz (maintainer)"},
output: []string{"foo", "bar", "baz"},
minimumAccessLevel: 20,
mock: []*gitlab.ProjectMember{
{
Username: "foo",
AccessLevel: gitlab.AccessLevelValue(20),
},
{
Username: "bar",
AccessLevel: gitlab.AccessLevelValue(30),
},
{
Username: "baz",
AccessLevel: gitlab.AccessLevelValue(40),
},
},
},
{
name: "no-members",
minimumAccessLevel: 10,
mock: []*gitlab.ProjectMember{},
expectedStdErr: "Couldn't fetch any members with minimum permission level 10\n",
},
{
name: "no-valid-members",
minimumAccessLevel: 50,
mock: []*gitlab.ProjectMember{
{
Username: "foo",
AccessLevel: gitlab.AccessLevelValue(40),
},
},
expectedStdErr: "Couldn't fetch any members with minimum permission level 50\n",
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
api.ListProjectMembers = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectMembersOptions) ([]*gitlab.ProjectMember, error) {
return tC.mock, nil
}
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "assignees",
Value: tC.choice,
},
})
var got []string
io, _, _, stderr := iostreams.Test()
err := AssigneesPrompt(&got, &gitlab.Client{}, repoRemote, io, tC.minimumAccessLevel)
if tC.expectedError != "" {
assert.EqualError(t, err, tC.expectedError)
} else {
assert.NoError(t, err)
}
if tC.expectedStdErr != "" {
outErr := stripansi.Strip(stderr.String())
assert.Equal(t, tC.expectedStdErr, outErr)
}
assert.ElementsMatch(t, got, tC.output)
})
}
t.Run("Prompt fails", func(t *testing.T) {
var got []string
api.ListProjectMembers = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectMembersOptions) ([]*gitlab.ProjectMember, error) {
return []*gitlab.ProjectMember{
{
Username: "foo",
AccessLevel: gitlab.AccessLevelValue(20),
},
}, nil
}
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "assignees",
Value: errors.New("meant to fail"),
},
})
err := AssigneesPrompt(&got, &gitlab.Client{}, repoRemote, nil, 20)
assert.Empty(t, got)
assert.EqualError(t, err, "meant to fail")
})
t.Run("API Failed", func(t *testing.T) {
var got []string
api.ListProjectMembers = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectMembersOptions) ([]*gitlab.ProjectMember, error) {
return nil, errors.New("meant to fail")
}
err := AssigneesPrompt(&got, &gitlab.Client{}, repoRemote, nil, 20)
assert.Empty(t, got)
assert.EqualError(t, err, "meant to fail")
})
t.Run("respect-flags", func(t *testing.T) {
got := []string{"foo"}
api.ListProjectMembers = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectMembersOptions) ([]*gitlab.ProjectMember, error) {
return []*gitlab.ProjectMember{
{
Username: "foo",
AccessLevel: gitlab.AccessLevelValue(20),
},
{
Username: "bar",
AccessLevel: gitlab.AccessLevelValue(30),
},
}, nil
}
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "assignees",
Value: []string{"bar (developer)"},
},
})
err := AssigneesPrompt(&got, &gitlab.Client{}, repoRemote, nil, 20)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{"foo", "bar"}, got)
})
}
func Test_MilestonesPrompt(t *testing.T) {
mockMilestones := []*api.Milestone{
{
Title: "New Release",
ID: 5,
},
{
Title: "Really big feature",
ID: 240,
},
{
Title: "Get rid of low quality Code",
ID: 650,
},
}
// Override API.ListMilestones so it doesn't make any network calls
api.ListAllMilestones = func(_ *gitlab.Client, _ interface{}, _ *api.ListMilestonesOptions) ([]*api.Milestone, error) {
return mockMilestones, nil
}
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
testCases := []struct {
name string
input string // Selected milestone
expectedID int // expected global ID from the milestone
}{
{
name: "match",
input: "New Release",
expectedID: 5,
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "milestone",
Value: tC.input,
},
})
var got int
var io iostreams.IOStreams
err := MilestonesPrompt(&got, &gitlab.Client{}, repoRemote, &io)
if err != nil {
t.Errorf("MilestonesPrompt() unexpected error = %s", err)
}
if got != 0 && got != tC.expectedID {
t.Errorf("MilestonesPrompt() expected = %d, got = %d", got, tC.expectedID)
}
})
}
t.Run("Prompt fails", func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "milestone",
Value: errors.New("meant to fail"),
},
})
var got int
var io iostreams.IOStreams
err := MilestonesPrompt(&got, &gitlab.Client{}, repoRemote, &io)
assert.Empty(t, got)
assert.EqualError(t, err, "meant to fail")
})
}
func Test_MilestonesPromptNoPrompts(t *testing.T) {
// Override api.ListMilestones so it returns an empty slice, we are testing if MilestonesPrompt()
// will print the correct message to `stderr` when it tries to get the list of Milestones in a
// project but the project has no milestones
api.ListAllMilestones = func(_ *gitlab.Client, _ interface{}, _ *api.ListMilestonesOptions) ([]*api.Milestone, error) {
return []*api.Milestone{}, nil
}
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
var got int
io, _, _, stderr := iostreams.Test()
err := MilestonesPrompt(&got, &gitlab.Client{}, repoRemote, io)
if err != nil {
t.Errorf("MilestonesPrompt() unexpected error = %s", err)
}
assert.Equal(t, "There are no active milestones in this project\n", stderr.String())
}
func TestMilestonesPromptFailures(t *testing.T) {
// Override api.ListMilestones so it returns an error, we are testing to see if error
// handling from the usage of api.ListMilestones is correct
api.ListAllMilestones = func(_ *gitlab.Client, _ interface{}, _ *api.ListMilestonesOptions) ([]*api.Milestone, error) {
return nil, errors.New("api.ListMilestones() failed")
}
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
var got int
io, _, _, _ := iostreams.Test()
err := MilestonesPrompt(&got, &gitlab.Client{}, repoRemote, io)
if err == nil {
t.Error("MilestonesPrompt() expected error")
}
assert.Equal(t, "api.ListMilestones() failed", err.Error())
}
func Test_IDsFromUsers(t *testing.T) {
testCases := []struct {
name string
users []*gitlab.User // Mock of the gitlab.User object
IDs []int // IDs we expect from the users
}{
{
name: "no users",
},
{
name: "one user",
users: []*gitlab.User{
{
ID: 1,
},
},
IDs: []int{1},
},
{
name: "multiple users",
users: []*gitlab.User{
{
ID: 3,
},
{
ID: 6,
},
{
ID: 2,
},
{
ID: 51,
},
{
ID: 32,
},
{
ID: 87,
},
{
ID: 210,
},
{
ID: 6493,
},
{
ID: 50132,
},
},
IDs: []int{
50132,
6493,
210,
87,
32,
51,
2,
3,
6,
},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
got := IDsFromUsers(tC.users)
assert.ElementsMatch(t, *got, tC.IDs)
})
}
}
func Test_LabelsPromptAPIFail(t *testing.T) {
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
api.ListLabels = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListLabelsOptions) ([]*gitlab.Label, error) {
return nil, errors.New("API call failed")
}
var got []string
err := LabelsPrompt(&got, &gitlab.Client{}, repoRemote)
assert.Nil(t, got)
assert.EqualError(t, err, "API call failed")
}
func Test_LabelsPromptPromptsFail(t *testing.T) {
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
t.Run("MultiSelect", func(t *testing.T) {
// Return a list with at least one value so we hit the MultiSelect path
api.ListLabels = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListLabelsOptions) ([]*gitlab.Label, error) {
return []*gitlab.Label{
{
Name: "foo",
},
}, nil
}
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "labels",
Value: errors.New("MultiSelect prompt failed"),
},
})
var got []string
err := LabelsPrompt(&got, &gitlab.Client{}, repoRemote)
assert.Nil(t, got)
assert.EqualError(t, err, "MultiSelect prompt failed")
})
t.Run("AskQuestionWithInput", func(t *testing.T) {
// Return an empty list so we hit the AskQuestionWithInput prompt path
api.ListLabels = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListLabelsOptions) ([]*gitlab.Label, error) {
return []*gitlab.Label{}, nil
}
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "labels",
Value: errors.New("AskQuestionWithInput prompt failed"),
},
})
var got []string
err := LabelsPrompt(&got, &gitlab.Client{}, repoRemote)
assert.Nil(t, got)
assert.EqualError(t, err, "AskQuestionWithInput prompt failed")
})
}
func Test_LabelsPromptMultiSelect(t *testing.T) {
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
api.ListLabels = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListLabelsOptions) ([]*gitlab.Label, error) {
return []*gitlab.Label{
{
Name: "foo",
},
{
Name: "bar",
},
{
Name: "baz",
},
{
Name: "qux",
},
{
Name: "quux",
},
{
Name: "quz",
},
}, nil
}
testCases := []struct {
name string
input []string
labels []string // Can be set to have initial labels
expected []string // expected labels
}{
{
name: "simple",
input: []string{"foo", "bar"},
expected: []string{"foo", "bar"},
},
{
name: "respect-defined-labels",
input: []string{"foo"},
labels: []string{"bar"},
expected: []string{"foo", "bar"},
},
{
name: "nothing",
},
{
name: "nothing-but-respect-already-defined",
labels: []string{"qux"},
expected: []string{"qux"},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "labels",
Value: tC.input,
},
})
err := LabelsPrompt(&tC.labels, &gitlab.Client{}, repoRemote)
assert.NoError(t, err)
assert.ElementsMatch(t, tC.labels, tC.expected)
})
}
}
func Test_LabelsPromptAskQuestionWithInput(t *testing.T) {
// mock glrepo.Remote object
repo := glrepo.New("foo", "bar")
remote := &git.Remote{
Name: "test",
Resolved: "base",
}
repoRemote := &glrepo.Remote{
Remote: remote,
Repo: repo,
}
api.ListLabels = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListLabelsOptions) ([]*gitlab.Label, error) {
return []*gitlab.Label{}, nil
}
testCases := []struct {
name string
input string
labels []string // Can be set to have initial labels
expected []string // expected labels
}{
{
name: "simple",
input: "foo,bar",
expected: []string{"foo", "bar"},
},
{
name: "respect-defined-labels",
input: "foo",
labels: []string{"bar"},
expected: []string{"foo", "bar"},
},
{
name: "nothing",
},
{
name: "nothing-but-respect-already-defined",
labels: []string{"qux"},
expected: []string{"qux"},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "labels",
Value: tC.input,
},
})
err := LabelsPrompt(&tC.labels, &gitlab.Client{}, repoRemote)
assert.NoError(t, err)
assert.ElementsMatch(t, tC.labels, tC.expected)
})
}
}
func Test_ConfirmSubmission(t *testing.T) {
const (
submitLabel = "Submit"
previewLabel = "Continue in browser"
addMetadataLabel = "Add metadata"
cancelLabel = "Cancel"
)
t.Run("success", func(t *testing.T) {
testCases := []struct {
name string
input string
allowPreview bool
allowAddMetadata bool
output Action
}{
{
name: "submit",
input: submitLabel,
output: SubmitAction,
},
{
name: "preview",
input: previewLabel,
allowPreview: true,
output: PreviewAction,
},
{
name: "Add Metadata",
input: addMetadataLabel,
allowAddMetadata: true,
output: AddMetadataAction,
},
{
name: "cancel",
input: cancelLabel,
output: CancelAction,
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: tC.input,
},
})
var got Action
got, err := ConfirmSubmission(tC.allowPreview, tC.allowAddMetadata)
assert.NoError(t, err)
assert.Equal(t, tC.output, got)
})
}
})
t.Run("failed", func(t *testing.T) {
testCases := []struct {
name string
input interface{}
output string
}{
{
name: "prompt",
input: errors.New("no terminal"),
output: "could not prompt: no terminal",
},
{
name: "invalid value",
input: "does not exist",
output: "invalid value: does not exist",
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: tC.input,
},
})
var got Action
got, err := ConfirmSubmission(false, false)
assert.Equal(t, Action(-1), got)
assert.EqualError(t, err, tC.output)
})
}
})
}
func TestListGitLabTemplates(t *testing.T) {
tests := []struct {
name string
give string
wantTemplates []string
wantErr bool
}{
{
name: "Get all the issues templates",
give: "issue_templates",
wantTemplates: []string{"Bug", "Feature Request"},
},
{
name: "Get all the issues templates",
give: "merge_request_templates",
wantTemplates: []string{"Default"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
git.ToplevelDir = func() (string, error) { return "../../test/testdata", nil }
gotTemplates, gotErr := ListGitLabTemplates(test.give)
require.Equal(t, test.wantErr, (gotErr != nil))
assert.EqualValues(t, test.wantTemplates, gotTemplates, "Templates got didn't match")
})
}
}