Skip to content

Commit

Permalink
Add tests and types for merge requests
Browse files Browse the repository at this point in the history
This adds tests for parsing the response for a single merge request, and
for a list of merge requests.

In addition, the list options are annotated with types that make their
semantics and usage rather clearer, with the aim of guiding users to the
correct values without constraining them.
  • Loading branch information
alexkalderimis committed Aug 21, 2019
1 parent d3b6947 commit 25897e4
Show file tree
Hide file tree
Showing 6 changed files with 2,173 additions and 50 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/xanzy/go-gitlab

require (
github.com/google/go-querystring v1.0.0
github.com/stretchr/testify v1.3.0
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand All @@ -19,3 +21,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
125 changes: 76 additions & 49 deletions merge_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type MergeRequest struct {
SourceBranch string `json:"source_branch"`
ProjectID int `json:"project_id"`
Title string `json:"title"`
State string `json:"state"`
State MergeRequestState `json:"state"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
Upvotes int `json:"upvotes"`
Expand Down Expand Up @@ -93,20 +93,16 @@ type MergeRequest struct {
RebaseInProgress bool `json:"rebase_in_progress"`
ApprovalsBeforeMerge int `json:"approvals_before_merge"`
Reference string `json:"reference"`
TaskCompletionStatus struct {
Count int `json:"count"`
CompletedCount int `json:"completed_count"`
} `json:"task_completion_status"`
}

// MergeRequestUser represents a GitLab User record within a MergeRequest
//
// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html
type MergeRequestUser struct {
ID int `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
State string `json:"state"`
CreatedAt *time.Time `json:"created_at"`
AvatarURL string `json:"avatar_url"`
WebURL string `json:"web_url"`
}
type MergeRequestUser = BasicUser

func (m MergeRequest) String() string {
return Stringify(m)
Expand Down Expand Up @@ -140,26 +136,57 @@ func (m MergeRequestDiffVersion) String() string {
// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests
type ListMergeRequestsOptions struct {
ListOptions
State *string `url:"state,omitempty" json:"state,omitempty"`
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
View *string `url:"view,omitempty" json:"view,omitempty"`
Labels Labels `url:"labels,omitempty" json:"labels,omitempty"`
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
SourceBranch *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
Search *string `url:"search,omitempty" json:"search,omitempty"`
In *string `url:"in,omitempty" json:"in,omitempty"`
WIP *string `url:"wip,omitempty" json:"wip,omitempty"`
}
State *MergeRequestState `url:"state,omitempty" json:"state,omitempty"`
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
Sort *SortDirection `url:"sort,omitempty" json:"sort,omitempty"`
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
View *MergeRequestView `url:"view,omitempty" json:"view,omitempty"`
Labels Labels `url:"labels,omitempty" json:"labels,omitempty"`
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
Scope *Scope `url:"scope,omitempty" json:"scope,omitempty"`
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
SourceBranch *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
Search *string `url:"search,omitempty" json:"search,omitempty"`
In *SearchDomain `url:"in,omitempty" json:"in,omitempty"`
WIP *YesNo `url:"wip,omitempty" json:"wip,omitempty"`
}

type MergeRequestView = string
type Scope = string
type SortDirection = string
type MergeRequestState = string
type SearchDomain = string
type YesNo = string

// The values options that take known strings
const (
AssignedToMe Scope = "assigned_to_me"
CreatedByMe Scope = "created_by_me"
All Scope = "all"

SimpleView MergeRequestView = "simple"

Asc SortDirection = "asc"
Desc SortDirection = "desc"

Opened MergeRequestState = "opened"
Closed MergeRequestState = "closed"
Locked MergeRequestState = "locked"
Merged MergeRequestState = "merged"

InTitle SearchDomain = "title"
InDescription SearchDomain = "description"
InTitleOrDescription SearchDomain = "title,description"

Yes YesNo = "yes"
No YesNo = "no"
)

// ListMergeRequests gets all merge requests. The state parameter can be used
// to get only merge requests with a given state (opened, closed, or merged)
Expand Down Expand Up @@ -241,25 +268,25 @@ func (s *MergeRequestsService) ListGroupMergeRequests(gid interface{}, opt *List
// https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests
type ListProjectMergeRequestsOptions struct {
ListOptions
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"`
State *string `url:"state,omitempty" json:"state,omitempty"`
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
View *string `url:"view,omitempty" json:"view,omitempty"`
Labels Labels `url:"labels,omitempty" json:"labels,omitempty"`
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
SourceBranch *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
Search *string `url:"search,omitempty" json:"search,omitempty"`
WIP *string `url:"wip,omitempty" json:"wip,omitempty"`
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"`
State *MergeRequestState `url:"state,omitempty" json:"state,omitempty"`
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
Sort *SortDirection `url:"sort,omitempty" json:"sort,omitempty"`
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
View *MergeRequestView `url:"view,omitempty" json:"view,omitempty"`
Labels Labels `url:"labels,omitempty" json:"labels,omitempty"`
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
Scope *Scope `url:"scope,omitempty" json:"scope,omitempty"`
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
SourceBranch *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
Search *SearchDomain `url:"search,omitempty" json:"search,omitempty"`
WIP *YesNo `url:"wip,omitempty" json:"wip,omitempty"`
}

// ListProjectMergeRequests gets all merge requests for this project.
Expand Down
158 changes: 158 additions & 0 deletions merge_requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package gitlab

import (
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
ajk = MergeRequestUser{
ID: 3614858,
Name: "Alex Kalderimis",
Username: "alexkalderimis",
State: "active",
AvatarURL: "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3614858/avatar.png",
WebURL: "https://gitlab.com/alexkalderimis",
}
tk = MergeRequestUser{
ID: 2535118,
Name: "Thong Kuah",
Username: "tkuah",
State: "active",
AvatarURL: "https://secure.gravatar.com/avatar/f7b51bdd49a4914d29504d7ff4c3f7b9?s=80&d=identicon",
WebURL: "https://gitlab.com/tkuah",
}
getOpts = GetMergeRequestsOptions{}
labels = []string{
"GitLab Enterprise Edition",
"backend",
"database",
"database::reviewed",
"design management",
"feature",
"frontend",
"group::knowledge",
"missed:12.1",
}
pipelineBasic = PipelineInfo{
ID: 77056819,
SHA: "8e0b45049b6253b8984cde9241830d2851168142",
Ref: "delete-designs-v2",
Status: "success",
WebURL: "https://gitlab.com/gitlab-org/gitlab-ee/pipelines/77056819",
}
pipelineCreation = time.Date(2019, 8, 19, 9, 50, 58, 157000000, time.UTC)
pipelineStarted = time.Date(2019, 8, 19, 9, 51, 6, 545000000, time.UTC)
pipelineFinished = time.Date(2019, 8, 19, 19, 22, 29, 632000000, time.UTC)
pipelineUpdate = time.Date(2019, 8, 19, 19, 22, 29, 647000000, time.UTC)
pipelineDetailed = Pipeline{
ID: 77056819,
SHA: "8e0b45049b6253b8984cde9241830d2851168142",
Ref: "delete-designs-v2",
Status: "success",
WebURL: "https://gitlab.com/gitlab-org/gitlab-ee/pipelines/77056819",
BeforeSHA: "3fe568caacb261b63090886f5b879ca0d9c6f4c3",
Tag: false,
User: &ajk,
CreatedAt: &pipelineCreation,
UpdatedAt: &pipelineUpdate,
StartedAt: &pipelineStarted,
FinishedAt: &pipelineFinished,
Duration: 4916,
Coverage: "82.68",
DetailedStatus: &DetailedStatus{
Icon: "status_warning",
Text: "passed",
Label: "passed with warnings",
Group: "success-with-warnings",
Tooltip: "passed",
HasDetails: true,
DetailsPath: "/gitlab-org/gitlab-ee/pipelines/77056819",
Favicon: "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
},
}
)

func TestGetMergeRequest(t *testing.T) {
mux, server, client := setup()
defer teardown(server)

path := "/api/v4/projects/namespace/name/merge_requests/123"

mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
mustWriteHTTPResponse(t, w, "testdata/get_merge_request.json")
})

mergeRequest, _, err := client.MergeRequests.GetMergeRequest("namespace/name", 123, &getOpts)

require.NoError(t, err)

require.Equal(t, mergeRequest.ID, 33092005)
require.Equal(t, mergeRequest.SHA, "8e0b45049b6253b8984cde9241830d2851168142")
require.Equal(t, mergeRequest.IID, 14656)
require.Equal(t, mergeRequest.Reference, "!14656")
require.Equal(t, mergeRequest.ProjectID, 278964)
require.Equal(t, mergeRequest.SourceBranch, "delete-designs-v2")
require.Equal(t, mergeRequest.TaskCompletionStatus.Count, 9)
require.Equal(t, mergeRequest.TaskCompletionStatus.CompletedCount, 8)
require.Equal(t, mergeRequest.Title, "Add deletion support for designs")
require.Equal(t, mergeRequest.Description,
"## What does this MR do?\r\n\r\nThis adds the capability to destroy/hide designs.")
require.Equal(t, mergeRequest.WebURL,
"https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14656")
require.Equal(t, mergeRequest.MergeStatus, "can_be_merged")
require.Equal(t, mergeRequest.Author, &ajk)
require.Equal(t, mergeRequest.Assignee, &tk)
require.Equal(t, mergeRequest.Assignees, []*MergeRequestUser{&tk})
require.Equal(t, mergeRequest.Labels, labels)
require.Equal(t, mergeRequest.Squash, true)
require.Equal(t, mergeRequest.UserNotesCount, 245)
require.Equal(t, mergeRequest.Pipeline, &pipelineBasic)
require.Equal(t, mergeRequest.HeadPipeline, &pipelineDetailed)
mrCreation := time.Date(2019, 7, 11, 22, 34, 43, 500000000, time.UTC)
require.Equal(t, mergeRequest.CreatedAt, &mrCreation)
mrUpdate := time.Date(2019, 8, 20, 9, 9, 56, 690000000, time.UTC)
require.Equal(t, mergeRequest.UpdatedAt, &mrUpdate)
}

func TestListProjectMergeRequests(t *testing.T) {
mux, server, client := setup()
defer teardown(server)

path := "/api/v4/projects/278964/merge_requests"

mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
mustWriteHTTPResponse(t, w, "testdata/get_merge_requests.json")
})
opts := ListProjectMergeRequestsOptions{}

mergeRequests, _, err := client.MergeRequests.ListProjectMergeRequests(278964, &opts)

require.NoError(t, err)
require.Equal(t, 20, len(mergeRequests))

validStates := []MergeRequestState{Opened, Closed, Locked, Merged}
mergeStatuses := []string{"can_be_merged", "cannot_be_merged"}
allCreatedBefore := time.Date(2019, 8, 21, 0, 0, 0, 0, time.UTC)
allCreatedAfter := time.Date(2019, 8, 17, 0, 0, 0, 0, time.UTC)

for _, mr := range mergeRequests {
require.Equal(t, 278964, mr.ProjectID)
require.Contains(t, validStates, mr.State)
assert.Less(t, mr.CreatedAt.Unix(), allCreatedBefore.Unix())
assert.Greater(t, mr.CreatedAt.Unix(), allCreatedAfter.Unix())
assert.LessOrEqual(t, mr.CreatedAt.Unix(), mr.UpdatedAt.Unix())
assert.LessOrEqual(t, mr.TaskCompletionStatus.CompletedCount, mr.TaskCompletionStatus.Count)
require.Contains(t, mergeStatuses, mr.MergeStatus)
// list requests do not provide these fields:
assert.Nil(t, mr.Pipeline)
assert.Nil(t, mr.HeadPipeline)
assert.Equal(t, "", mr.DiffRefs.HeadSha)
}
}
Loading

0 comments on commit 25897e4

Please sign in to comment.