Release 2.5.1 (#271)

* feat: Support for custom authentication provider functions (#270)
* feat: Support for adding "draft" notes to the review, and publishing them, either individually or all at once. Addresses feature request #223.
* feat: Lets users select + checkout a merge request directly within Neovim, without exiting to the terminal
* fix: Checks that the remote feature branch exists and is up-to-date before creating a MR, starting a review, or opening the MR summary (#278)
* docs: We require some state from Diffview, this shows how to load that state prior to installing w/ Packer. Fixes #94.

This is a #MINOR release.

---------

Co-authored-by: Jakub F. Bortlík <jakub.bortlik@proton.me>
Co-authored-by: sunfuze <sunfuze.1989@gmail.com>
Co-authored-by: Patrick Pichler <mail@patrickpichler.dev>
This commit is contained in:
Harrison (Harry) Cramer
2024-04-22 16:56:27 -04:00
committed by GitHub
parent f10c4ebb8f
commit cf6ccddce3
42 changed files with 2830 additions and 1149 deletions

View File

@@ -23,7 +23,7 @@ func updateAssigneesErr(pid interface{}, mergeRequest int, opt *gitlab.UpdateMer
func TestAssigneeHandler(t *testing.T) {
t.Run("Updates assignees", func(t *testing.T) {
request := makeRequest(t, http.MethodPut, "/mr/assignee", AssigneeUpdateRequest{Ids: []int{1, 2}})
server, _ := createRouterAndApi(fakeClient{updateMergeRequestFn: updateAssignees})
server, _ := createRouterAndApi(fakeClient{updateMergeRequest: updateAssignees})
data := serveRequest(t, server, request, AssigneeUpdateResponse{})
assert(t, data.SuccessResponse.Message, "Assignees updated")
assert(t, data.SuccessResponse.Status, http.StatusOK)
@@ -31,7 +31,7 @@ func TestAssigneeHandler(t *testing.T) {
t.Run("Disallows non-PUT method", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/assignee", nil)
server, _ := createRouterAndApi(fakeClient{updateMergeRequestFn: updateAssignees})
server, _ := createRouterAndApi(fakeClient{updateMergeRequest: updateAssignees})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Status, http.StatusMethodNotAllowed)
assert(t, data.Details, "Invalid request type")
@@ -40,7 +40,7 @@ func TestAssigneeHandler(t *testing.T) {
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodPut, "/mr/assignee", AssigneeUpdateRequest{Ids: []int{1, 2}})
server, _ := createRouterAndApi(fakeClient{updateMergeRequestFn: updateAssigneesErr})
server, _ := createRouterAndApi(fakeClient{updateMergeRequest: updateAssigneesErr})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Status, http.StatusInternalServerError)
assert(t, data.Message, "Could not modify merge request assignees")
@@ -49,7 +49,7 @@ func TestAssigneeHandler(t *testing.T) {
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodPut, "/mr/assignee", AssigneeUpdateRequest{Ids: []int{1, 2}})
server, _ := createRouterAndApi(fakeClient{updateMergeRequestFn: updateAssigneesNon200})
server, _ := createRouterAndApi(fakeClient{updateMergeRequest: updateAssigneesNon200})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Status, http.StatusSeeOther)
assert(t, data.Message, "Could not modify merge request assignees")

View File

@@ -40,6 +40,7 @@ type Client struct {
*gitlab.LabelsService
*gitlab.AwardEmojiService
*gitlab.UsersService
*gitlab.DraftNotesService
}
/* initGitlabClient parses and validates the project settings and initializes the Gitlab client. */
@@ -116,6 +117,7 @@ func initGitlabClient() (error, *Client) {
LabelsService: client.Labels,
AwardEmojiService: client.AwardEmoji,
UsersService: client.Users,
DraftNotesService: client.DraftNotes,
}
}

View File

@@ -1,7 +1,6 @@
package main
import (
"crypto/sha1"
"encoding/json"
"fmt"
"io"
@@ -11,28 +10,8 @@ import (
)
type PostCommentRequest struct {
Comment string `json:"comment"`
FileName string `json:"file_name"`
NewLine *int `json:"new_line,omitempty"`
OldLine *int `json:"old_line,omitempty"`
HeadCommitSHA string `json:"head_commit_sha"`
BaseCommitSHA string `json:"base_commit_sha"`
StartCommitSHA string `json:"start_commit_sha"`
Type string `json:"type"`
LineRange *LineRange `json:"line_range,omitempty"`
}
/* LineRange represents the range of a note. */
type LineRange struct {
StartRange *LinePosition `json:"start"`
EndRange *LinePosition `json:"end"`
}
/* LinePosition represents a position in a line range. Unlike the Gitlab struct, this does not contain LineCode with a sha1 of the filename */
type LinePosition struct {
Type string `json:"type"`
OldLine int `json:"old_line"`
NewLine int `json:"new_line"`
Comment string `json:"comment"`
PositionData
}
type DeleteCommentRequest struct {
@@ -53,6 +32,15 @@ type CommentResponse struct {
Discussion *gitlab.Discussion `json:"discussion"`
}
/* CommentWithPosition is a comment with an (optional) position data value embedded in it. The position data will be non-nil for range-based comments. */
type CommentWithPosition struct {
PositionData PositionData
}
func (comment CommentWithPosition) GetPositionData() PositionData {
return comment.PositionData
}
/* commentHandler creates, edits, and deletes discussions (comments, multi-line comments) */
func (a *api) commentHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
@@ -133,46 +121,10 @@ func (a *api) postComment(w http.ResponseWriter, r *http.Request) {
/* If we are leaving a comment on a line, leave position. Otherwise,
we are leaving a note (unlinked comment) */
var friendlyName = "Note"
if postCommentRequest.FileName != "" {
friendlyName = "Comment"
opt.Position = &gitlab.PositionOptions{
PositionType: &postCommentRequest.Type,
StartSHA: &postCommentRequest.StartCommitSHA,
HeadSHA: &postCommentRequest.HeadCommitSHA,
BaseSHA: &postCommentRequest.BaseCommitSHA,
NewPath: &postCommentRequest.FileName,
OldPath: &postCommentRequest.FileName,
NewLine: postCommentRequest.NewLine,
OldLine: postCommentRequest.OldLine,
}
if postCommentRequest.LineRange != nil {
friendlyName = "Multiline Comment"
shaFormat := "%x_%d_%d"
startFilenameSha := fmt.Sprintf(
shaFormat,
sha1.Sum([]byte(postCommentRequest.FileName)),
postCommentRequest.LineRange.StartRange.OldLine,
postCommentRequest.LineRange.StartRange.NewLine,
)
endFilenameSha := fmt.Sprintf(
shaFormat,
sha1.Sum([]byte(postCommentRequest.FileName)),
postCommentRequest.LineRange.EndRange.OldLine,
postCommentRequest.LineRange.EndRange.NewLine,
)
opt.Position.LineRange = &gitlab.LineRangeOptions{
Start: &gitlab.LinePositionOptions{
Type: &postCommentRequest.LineRange.StartRange.Type,
LineCode: &startFilenameSha,
},
End: &gitlab.LinePositionOptions{
Type: &postCommentRequest.LineRange.EndRange.Type,
LineCode: &endFilenameSha,
},
}
}
if postCommentRequest.FileName != "" {
commentWithPositionData := CommentWithPosition{postCommentRequest.PositionData}
opt.Position = buildCommentPosition(commentWithPositionData)
}
discussion, res, err := a.client.CreateMergeRequestDiscussion(a.projectInfo.ProjectId, a.projectInfo.MergeId, &opt)
@@ -190,7 +142,7 @@ func (a *api) postComment(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
response := CommentResponse{
SuccessResponse: SuccessResponse{
Message: fmt.Sprintf("%s created successfully", friendlyName),
Message: "Comment created successfully",
Status: http.StatusOK,
},
Comment: discussion.Notes[0],

82
cmd/comment_helpers.go Normal file
View File

@@ -0,0 +1,82 @@
package main
import (
"crypto/sha1"
"fmt"
"github.com/xanzy/go-gitlab"
)
/* LinePosition represents a position in a line range. Unlike the Gitlab struct, this does not contain LineCode with a sha1 of the filename */
type LinePosition struct {
Type string `json:"type"`
OldLine int `json:"old_line"`
NewLine int `json:"new_line"`
}
/* LineRange represents the range of a note. */
type LineRange struct {
StartRange *LinePosition `json:"start"`
EndRange *LinePosition `json:"end"`
}
/* PositionData represents the position of a comment or note (relative to a file diff) */
type PositionData struct {
FileName string `json:"file_name"`
NewLine *int `json:"new_line,omitempty"`
OldLine *int `json:"old_line,omitempty"`
HeadCommitSHA string `json:"head_commit_sha"`
BaseCommitSHA string `json:"base_commit_sha"`
StartCommitSHA string `json:"start_commit_sha"`
Type string `json:"type"`
LineRange *LineRange `json:"line_range,omitempty"`
}
/* RequestWithPosition is an interface that abstracts the handling of position data for a comment or a draft comment */
type RequestWithPosition interface {
GetPositionData() PositionData
}
/* buildCommentPosition takes a comment or draft comment request and builds the position data necessary for a location-based comment */
func buildCommentPosition(commentWithPositionData RequestWithPosition) *gitlab.PositionOptions {
positionData := commentWithPositionData.GetPositionData()
opt := &gitlab.PositionOptions{
PositionType: &positionData.Type,
StartSHA: &positionData.StartCommitSHA,
HeadSHA: &positionData.HeadCommitSHA,
BaseSHA: &positionData.BaseCommitSHA,
NewPath: &positionData.FileName,
OldPath: &positionData.FileName,
NewLine: positionData.NewLine,
OldLine: positionData.OldLine,
}
if positionData.LineRange != nil {
shaFormat := "%x_%d_%d"
startFilenameSha := fmt.Sprintf(
shaFormat,
sha1.Sum([]byte(positionData.FileName)),
positionData.LineRange.StartRange.OldLine,
positionData.LineRange.StartRange.NewLine,
)
endFilenameSha := fmt.Sprintf(
shaFormat,
sha1.Sum([]byte(positionData.FileName)),
positionData.LineRange.EndRange.OldLine,
positionData.LineRange.EndRange.NewLine,
)
opt.LineRange = &gitlab.LineRangeOptions{
Start: &gitlab.LinePositionOptions{
Type: &positionData.LineRange.StartRange.Type,
LineCode: &startFilenameSha,
},
End: &gitlab.LinePositionOptions{
Type: &positionData.LineRange.EndRange.Type,
LineCode: &endFilenameSha,
},
}
}
return opt
}

View File

@@ -25,12 +25,16 @@ func TestPostComment(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/comment", PostCommentRequest{})
server, _ := createRouterAndApi(fakeClient{createMergeRequestDiscussion: createMergeRequestDiscussion})
data := serveRequest(t, server, request, CommentResponse{})
assert(t, data.SuccessResponse.Message, "Note created successfully")
assert(t, data.SuccessResponse.Message, "Comment created successfully")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})
t.Run("Creates a new comment", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/comment", PostCommentRequest{FileName: "some_file.txt"})
request := makeRequest(t, http.MethodPost, "/mr/comment", PostCommentRequest{
PositionData: PositionData{
FileName: "some_file.txt",
},
})
server, _ := createRouterAndApi(fakeClient{createMergeRequestDiscussion: createMergeRequestDiscussion})
data := serveRequest(t, server, request, CommentResponse{})
assert(t, data.SuccessResponse.Message, "Comment created successfully")
@@ -39,15 +43,17 @@ func TestPostComment(t *testing.T) {
t.Run("Creates a new multiline comment", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/comment", PostCommentRequest{
FileName: "some_file.txt",
LineRange: &LineRange{
StartRange: &LinePosition{}, /* These would have real data */
EndRange: &LinePosition{},
PositionData: PositionData{
FileName: "some_file.txt",
LineRange: &LineRange{
StartRange: &LinePosition{}, /* These would have real data */
EndRange: &LinePosition{},
},
},
})
server, _ := createRouterAndApi(fakeClient{createMergeRequestDiscussion: createMergeRequestDiscussion})
data := serveRequest(t, server, request, CommentResponse{})
assert(t, data.SuccessResponse.Message, "Multiline Comment created successfully")
assert(t, data.SuccessResponse.Message, "Comment created successfully")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})

306
cmd/draft_notes.go Normal file
View File

@@ -0,0 +1,306 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"github.com/xanzy/go-gitlab"
)
/* The data coming from the client when creating a draft note is the same,
as when they are creating a normal comment, but the Gitlab
endpoints + resources we handle are different */
type PostDraftNoteRequest struct {
Comment string `json:"comment"`
PositionData
}
type UpdateDraftNoteRequest struct {
Note string `json:"note"`
Position gitlab.PositionOptions
}
type DraftNotePublishRequest struct {
Note int `json:"note,omitempty"`
PublishAll bool `json:"publish_all"`
}
type DraftNoteResponse struct {
SuccessResponse
DraftNote *gitlab.DraftNote `json:"draft_note"`
}
type ListDraftNotesResponse struct {
SuccessResponse
DraftNotes []*gitlab.DraftNote `json:"draft_notes"`
}
/* DraftNoteWithPosition is a draft comment with an (optional) position data value embedded in it. The position data will be non-nil for range-based draft comments. */
type DraftNoteWithPosition struct {
PositionData PositionData
}
func (draftNote DraftNoteWithPosition) GetPositionData() PositionData {
return draftNote.PositionData
}
/* draftNoteHandler creates, edits, and deletes draft notes */
func (a *api) draftNoteHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
a.listDraftNotes(w, r)
case http.MethodPost:
a.postDraftNote(w, r)
case http.MethodPatch:
a.updateDraftNote(w, r)
case http.MethodDelete:
a.deleteDraftNote(w, r)
default:
w.Header().Set("Access-Control-Allow-Methods", fmt.Sprintf("%s, %s, %s, %s", http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodGet))
handleError(w, InvalidRequestError{}, "Expected DELETE, GET, POST or PATCH", http.StatusMethodNotAllowed)
}
}
func (a *api) draftNotePublisher(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method != http.MethodPost {
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
handleError(w, InvalidRequestError{}, "Expected POST", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
handleError(w, err, "Could not read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
var draftNotePublishRequest DraftNotePublishRequest
err = json.Unmarshal(body, &draftNotePublishRequest)
if err != nil {
handleError(w, err, "Could not read JSON from request", http.StatusBadRequest)
return
}
var res *gitlab.Response
if draftNotePublishRequest.PublishAll {
res, err = a.client.PublishAllDraftNotes(a.projectInfo.ProjectId, a.projectInfo.MergeId)
} else {
if draftNotePublishRequest.Note == 0 {
handleError(w, errors.New("No ID provided"), "Must provide Note ID", http.StatusBadRequest)
return
}
res, err = a.client.PublishDraftNote(a.projectInfo.ProjectId, a.projectInfo.MergeId, draftNotePublishRequest.Note)
}
if err != nil {
handleError(w, err, "Could not publish draft note(s)", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{endpoint: "/mr/draft_notes/publish"}, "Could not publish dfaft note", res.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
response := SuccessResponse{
Message: "Draft note(s) published",
Status: http.StatusOK,
}
err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
}
}
/* postDraftNote creates a draft note */
func (a *api) postDraftNote(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
handleError(w, err, "Could not read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
var postDraftNoteRequest PostDraftNoteRequest
err = json.Unmarshal(body, &postDraftNoteRequest)
if err != nil {
handleError(w, err, "Could not unmarshal data from request body", http.StatusBadRequest)
return
}
opt := gitlab.CreateDraftNoteOptions{
Note: &postDraftNoteRequest.Comment,
// TODO: Support posting replies as drafts and rendering draft replies in the discussion tree
// instead of the notes tree
// InReplyToDiscussionID *string `url:"in_reply_to_discussion_id,omitempty" json:"in_reply_to_discussion_id,omitempty"`
}
if postDraftNoteRequest.FileName != "" {
draftNoteWithPosition := DraftNoteWithPosition{postDraftNoteRequest.PositionData}
opt.Position = buildCommentPosition(draftNoteWithPosition)
}
draftNote, res, err := a.client.CreateDraftNote(a.projectInfo.ProjectId, a.projectInfo.MergeId, &opt)
if err != nil {
handleError(w, err, "Could not create draft note", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{endpoint: "/mr/draft_notes/"}, "Could not create draft note", res.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
response := DraftNoteResponse{
SuccessResponse: SuccessResponse{
Message: "Draft note created successfully",
Status: http.StatusOK,
},
DraftNote: draftNote,
}
err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
}
}
/* deleteDraftNote deletes a draft note */
func (a *api) deleteDraftNote(w http.ResponseWriter, r *http.Request) {
suffix := strings.TrimPrefix(r.URL.Path, "/mr/draft_notes/")
id, err := strconv.Atoi(suffix)
if err != nil {
handleError(w, err, "Could not parse draft note ID", http.StatusBadRequest)
return
}
res, err := a.client.DeleteDraftNote(a.projectInfo.ProjectId, a.projectInfo.MergeId, id)
if err != nil {
handleError(w, err, "Could not delete draft note", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{endpoint: "/mr/draft_notes/"}, "Could not delete draft note", res.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
response := SuccessResponse{
Message: "Draft note deleted",
Status: http.StatusOK,
}
err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
}
}
/* updateDraftNote edits the text of a draft comment */
func (a *api) updateDraftNote(w http.ResponseWriter, r *http.Request) {
suffix := strings.TrimPrefix(r.URL.Path, "/mr/draft_notes/")
id, err := strconv.Atoi(suffix)
if err != nil {
handleError(w, err, "Could not parse draft note ID", http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
handleError(w, err, "Could not read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
var updateDraftNoteRequest UpdateDraftNoteRequest
err = json.Unmarshal(body, &updateDraftNoteRequest)
if err != nil {
handleError(w, err, "Could not unmarshal data from request body", http.StatusBadRequest)
return
}
if updateDraftNoteRequest.Note == "" {
handleError(w, errors.New("Draft note text missing"), "Must provide draft note text", http.StatusBadRequest)
return
}
opt := gitlab.UpdateDraftNoteOptions{
Note: &updateDraftNoteRequest.Note,
Position: &updateDraftNoteRequest.Position,
}
draftNote, res, err := a.client.UpdateDraftNote(a.projectInfo.ProjectId, a.projectInfo.MergeId, id, &opt)
if err != nil {
handleError(w, err, "Could not update draft note", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{endpoint: "/mr/draft_notes/"}, "Could not update draft note", res.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
response := DraftNoteResponse{
SuccessResponse: SuccessResponse{
Message: "Draft note updated",
Status: http.StatusOK,
},
DraftNote: draftNote,
}
err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
}
}
/* listDraftNotes lists all draft notes for the currently authenticated user */
func (a *api) listDraftNotes(w http.ResponseWriter, r *http.Request) {
opt := gitlab.ListDraftNotesOptions{}
draftNotes, res, err := a.client.ListDraftNotes(a.projectInfo.ProjectId, a.projectInfo.MergeId, &opt)
if err != nil {
handleError(w, err, "Could not get draft notes", http.StatusInternalServerError)
return
}
if res.StatusCode >= 300 {
handleError(w, GenericError{endpoint: "/mr/draft/comment"}, "Could not get draft notes", res.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
response := ListDraftNotesResponse{
SuccessResponse: SuccessResponse{
Message: "Draft notes fetched successfully",
Status: http.StatusOK,
},
DraftNotes: draftNotes,
}
err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
}
}

197
cmd/draft_notes_test.go Normal file
View File

@@ -0,0 +1,197 @@
package main
import (
"errors"
"net/http"
"testing"
"github.com/xanzy/go-gitlab"
)
func listDraftNotes(pid interface{}, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error) {
return []*gitlab.DraftNote{}, makeResponse(http.StatusOK), nil
}
func listDraftNotesErr(pid interface{}, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error) {
return nil, makeResponse(http.StatusInternalServerError), errors.New("Some error")
}
func TestListDraftNotes(t *testing.T) {
t.Run("Lists all draft notes", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
server, _ := createRouterAndApi(fakeClient{listDraftNotes: listDraftNotes})
data := serveRequest(t, server, request, ListDraftNotesResponse{})
assert(t, data.SuccessResponse.Message, "Draft notes fetched successfully")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})
t.Run("Handles error", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
server, _ := createRouterAndApi(fakeClient{listDraftNotes: listDraftNotesErr})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not get draft notes")
assert(t, data.Status, http.StatusInternalServerError)
assert(t, data.Details, "Some error")
})
}
func createDraftNote(pid interface{}, mergeRequestIID int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
return &gitlab.DraftNote{}, makeResponse(http.StatusOK), nil
}
func createDraftNoteErr(pid interface{}, mergeRequestIID int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
return nil, makeResponse(http.StatusInternalServerError), errors.New("Some error")
}
func TestPostDraftNote(t *testing.T) {
t.Run("Posts new draft note", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", PostDraftNoteRequest{})
server, _ := createRouterAndApi(fakeClient{createDraftNote: createDraftNote})
data := serveRequest(t, server, request, DraftNoteResponse{})
assert(t, data.SuccessResponse.Message, "Draft note created successfully")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})
t.Run("Handles errors on draft note creation", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", PostDraftNoteRequest{})
server, _ := createRouterAndApi(fakeClient{createDraftNote: createDraftNoteErr})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not create draft note")
assert(t, data.Status, http.StatusInternalServerError)
assert(t, data.Details, "Some error")
})
}
func deleteDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return makeResponse(http.StatusOK), nil
}
func deleteDraftNoteErr(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return makeResponse(http.StatusInternalServerError), errors.New("Something went wrong")
}
func TestDeleteDraftNote(t *testing.T) {
t.Run("Deletes draft note", func(t *testing.T) {
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/3", nil)
server, _ := createRouterAndApi(fakeClient{deleteDraftNote: deleteDraftNote})
data := serveRequest(t, server, request, SuccessResponse{})
assert(t, data.Message, "Draft note deleted")
assert(t, data.Status, http.StatusOK)
})
t.Run("Handles error", func(t *testing.T) {
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/3", nil)
server, _ := createRouterAndApi(fakeClient{deleteDraftNote: deleteDraftNoteErr})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not delete draft note")
assert(t, data.Status, http.StatusInternalServerError)
})
t.Run("Handles bad ID", func(t *testing.T) {
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/abc", nil)
server, _ := createRouterAndApi(fakeClient{deleteDraftNote: deleteDraftNote})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not parse draft note ID")
assert(t, data.Status, http.StatusBadRequest)
})
}
func updateDraftNote(pid interface{}, mergeRequest int, note int, opt *gitlab.UpdateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
return &gitlab.DraftNote{}, makeResponse(http.StatusOK), nil
}
func TestEditDraftNote(t *testing.T) {
t.Run("Edits draft note", func(t *testing.T) {
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/3", UpdateDraftNoteRequest{Note: "Some new note", Position: gitlab.PositionOptions{}})
server, _ := createRouterAndApi(fakeClient{updateDraftNote: updateDraftNote})
data := serveRequest(t, server, request, SuccessResponse{})
assert(t, data.Message, "Draft note updated")
assert(t, data.Status, http.StatusOK)
})
t.Run("Handles bad ID", func(t *testing.T) {
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/abc", nil)
server, _ := createRouterAndApi(fakeClient{updateDraftNote: updateDraftNote})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not parse draft note ID")
assert(t, data.Status, http.StatusBadRequest)
})
t.Run("Handles empty note", func(t *testing.T) {
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/3", UpdateDraftNoteRequest{Note: ""})
server, _ := createRouterAndApi(fakeClient{updateDraftNote: updateDraftNote})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Must provide draft note text")
assert(t, data.Status, http.StatusBadRequest)
})
}
func publishDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return makeResponse(http.StatusOK), nil
}
func publishDraftNoteErr(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return nil, errors.New("Some error")
}
func TestPublishDraftNote(t *testing.T) {
t.Run("Should publish a draft note", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{Note: 3, PublishAll: false})
server, _ := createRouterAndApi(fakeClient{
publishDraftNote: publishDraftNote,
})
data := serveRequest(t, server, request, SuccessResponse{})
assert(t, data.Message, "Draft note(s) published")
assert(t, data.Status, http.StatusOK)
})
t.Run("Handles bad/missing ID", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{PublishAll: false})
server, _ := createRouterAndApi(fakeClient{publishDraftNote: publishDraftNote})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Must provide Note ID")
assert(t, data.Status, http.StatusBadRequest)
})
t.Run("Handles error", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{PublishAll: false, Note: 3})
server, _ := createRouterAndApi(fakeClient{publishDraftNote: publishDraftNoteErr})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not publish draft note(s)")
assert(t, data.Status, http.StatusInternalServerError)
})
}
func publishAllDraftNotes(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return makeResponse(http.StatusOK), nil
}
func publishAllDraftNotesErr(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return nil, errors.New("Some error")
}
func TestPublishAllDraftNotes(t *testing.T) {
t.Run("Should publish all draft notes", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{PublishAll: true})
server, _ := createRouterAndApi(fakeClient{
publishAllDraftNotes: publishAllDraftNotes,
})
data := serveRequest(t, server, request, SuccessResponse{})
assert(t, data.Message, "Draft note(s) published")
assert(t, data.Status, http.StatusOK)
})
t.Run("Should handle an error", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{PublishAll: true})
server, _ := createRouterAndApi(fakeClient{
publishAllDraftNotes: publishAllDraftNotesErr,
})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Could not publish draft note(s)")
assert(t, data.Status, http.StatusInternalServerError)
})
}

View File

@@ -23,7 +23,7 @@ func getInfoErr(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestsO
func TestInfoHandler(t *testing.T) {
t.Run("Returns normal information", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
server, _ := createRouterAndApi(fakeClient{getMergeRequestFn: getInfo})
server, _ := createRouterAndApi(fakeClient{getMergeRequest: getInfo})
data := serveRequest(t, server, request, InfoResponse{})
assert(t, data.Info.Title, "Some Title")
assert(t, data.SuccessResponse.Message, "Merge requests retrieved")
@@ -32,21 +32,21 @@ func TestInfoHandler(t *testing.T) {
t.Run("Disallows non-GET method", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/info", nil)
server, _ := createRouterAndApi(fakeClient{getMergeRequestFn: getInfo})
server, _ := createRouterAndApi(fakeClient{getMergeRequest: getInfo})
data := serveRequest(t, server, request, ErrorResponse{})
checkBadMethod(t, *data, http.MethodGet)
})
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
server, _ := createRouterAndApi(fakeClient{getMergeRequestFn: getInfoErr})
server, _ := createRouterAndApi(fakeClient{getMergeRequest: getInfoErr})
data := serveRequest(t, server, request, ErrorResponse{})
checkErrorFromGitlab(t, *data, "Could not get project info")
})
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
server, _ := createRouterAndApi(fakeClient{getMergeRequestFn: getInfoNon200})
server, _ := createRouterAndApi(fakeClient{getMergeRequest: getInfoNon200})
data := serveRequest(t, server, request, ErrorResponse{})
checkNon200(t, *data, "Could not get project info", "/mr/info")
})

View File

@@ -5,9 +5,10 @@ import (
)
func main() {
log.SetFlags(0)
gitInfo, err := extractGitInfo(RefreshProjectInfo, GetProjectUrlFromNativeGitCmd, GetCurrentBranchNameFromNativeGitCmd)
if err != nil {
log.Fatalf("Failure initializing plugin with `git` commands: %v", err)
log.Fatalf("Failure initializing plugin: %v", err)
}
err, client := initGitlabClient()

55
cmd/merge_requests.go Normal file
View File

@@ -0,0 +1,55 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/xanzy/go-gitlab"
)
type ListMergeRequestResponse struct {
SuccessResponse
MergeRequests []*gitlab.MergeRequest `json:"merge_requests"`
}
func (a *api) mergeRequestsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
handleError(w, InvalidRequestError{}, "Expected GET", http.StatusMethodNotAllowed)
return
}
options := gitlab.ListProjectMergeRequestsOptions{
Scope: gitlab.Ptr("all"),
State: gitlab.Ptr("opened"),
}
mergeRequests, _, err := a.client.ListProjectMergeRequests(a.projectInfo.ProjectId, &options)
if err != nil {
handleError(w, fmt.Errorf("Failed to list merge requests: %w", err), "Failed to list merge requests", http.StatusInternalServerError)
return
}
if len(mergeRequests) == 0 {
handleError(w, errors.New("No merge requests found"), "No merge requests found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusOK)
response := ListMergeRequestResponse{
SuccessResponse: SuccessResponse{
Message: "Merge requests fetched successfully",
Status: http.StatusOK,
},
MergeRequests: mergeRequests,
}
err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
}
}

View File

@@ -0,0 +1,45 @@
package main
import (
"errors"
"net/http"
"testing"
"github.com/xanzy/go-gitlab"
)
func listProjectMergeRequests200(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
return []*gitlab.MergeRequest{{ID: 1}}, &gitlab.Response{}, nil
}
func listProjectMergeRequestsEmpty(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
return []*gitlab.MergeRequest{}, &gitlab.Response{}, nil
}
func listProjectMergeRequestsErr(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
return nil, nil, errors.New("Some error")
}
func TestMergeRequestHandler(t *testing.T) {
t.Run("Should fetch merge requests", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/merge_requests", nil)
server, _ := createRouterAndApi(fakeClient{listProjectMergeRequests: listProjectMergeRequests200})
data := serveRequest(t, server, request, ListMergeRequestResponse{})
assert(t, data.Message, "Merge requests fetched successfully")
assert(t, data.Status, http.StatusOK)
})
t.Run("Should handle an error", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/merge_requests", nil)
server, _ := createRouterAndApi(fakeClient{listProjectMergeRequests: listProjectMergeRequestsErr})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "Failed to list merge requests")
assert(t, data.Status, http.StatusInternalServerError)
})
t.Run("Should handle not having any merge requests with 404", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/merge_requests", nil)
server, _ := createRouterAndApi(fakeClient{listProjectMergeRequests: listProjectMergeRequestsEmpty})
data := serveRequest(t, server, request, ErrorResponse{})
assert(t, data.Message, "No merge requests found")
assert(t, data.Status, http.StatusNotFound)
})
}

View File

@@ -8,11 +8,11 @@ import (
"github.com/xanzy/go-gitlab"
)
func acceptAndMergeFn(pid interface{}, mergeRequest int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
func acceptMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
return &gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil
}
func acceptAndMergeFnErr(pid interface{}, mergeRequest int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
func acceptMergeRequestErr(pid interface{}, mergeRequest int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
return nil, nil, errors.New("Some error from Gitlab")
}
@@ -23,7 +23,7 @@ func acceptAndMergeNon200(pid interface{}, mergeRequest int, opt *gitlab.AcceptM
func TestAcceptAndMergeHandler(t *testing.T) {
t.Run("Accepts and merges a merge request", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/merge", AcceptMergeRequestRequest{})
server, _ := createRouterAndApi(fakeClient{acceptAndMergeFn: acceptAndMergeFn})
server, _ := createRouterAndApi(fakeClient{acceptMergeRequest: acceptMergeRequest})
data := serveRequest(t, server, request, SuccessResponse{})
assert(t, data.Message, "MR merged successfully")
assert(t, data.Status, http.StatusOK)
@@ -31,21 +31,21 @@ func TestAcceptAndMergeHandler(t *testing.T) {
t.Run("Disallows non-POST methods", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/mr/merge", AcceptMergeRequestRequest{})
server, _ := createRouterAndApi(fakeClient{acceptAndMergeFn: acceptAndMergeFn})
server, _ := createRouterAndApi(fakeClient{acceptMergeRequest: acceptMergeRequest})
data := serveRequest(t, server, request, ErrorResponse{})
checkBadMethod(t, *data, http.MethodPost)
})
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/merge", AcceptMergeRequestRequest{})
server, _ := createRouterAndApi(fakeClient{acceptAndMergeFn: acceptAndMergeFnErr})
server, _ := createRouterAndApi(fakeClient{acceptMergeRequest: acceptMergeRequestErr})
data := serveRequest(t, server, request, ErrorResponse{})
checkErrorFromGitlab(t, *data, "Could not merge MR")
})
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/mr/merge", AcceptMergeRequestRequest{})
server, _ := createRouterAndApi(fakeClient{acceptAndMergeFn: acceptAndMergeNon200})
server, _ := createRouterAndApi(fakeClient{acceptMergeRequest: acceptAndMergeNon200})
data := serveRequest(t, server, request, ErrorResponse{})
checkNon200(t, *data, "Could not merge MR", "/mr/merge")
})

View File

@@ -134,6 +134,8 @@ func createRouterAndApi(client ClientInterface, optFuncs ...optFunc) (*http.Serv
m.HandleFunc("/mr/label", a.withMr(a.labelHandler))
m.HandleFunc("/mr/revoke", a.withMr(a.revokeHandler))
m.HandleFunc("/mr/awardable/note/", a.withMr(a.emojiNoteHandler))
m.HandleFunc("/mr/draft_notes/", a.withMr(a.draftNoteHandler))
m.HandleFunc("/mr/draft_notes/publish", a.withMr(a.draftNotePublisher))
m.HandleFunc("/pipeline", a.pipelineHandler)
m.HandleFunc("/pipeline/trigger/", a.pipelineHandler)
@@ -143,6 +145,7 @@ func createRouterAndApi(client ClientInterface, optFuncs ...optFunc) (*http.Serv
m.HandleFunc("/job", a.jobHandler)
m.HandleFunc("/project/members", a.projectMembersHandler)
m.HandleFunc("/shutdown", a.shutdownHandler)
m.HandleFunc("/merge_requests", a.mergeRequestsHandler)
m.Handle("/ping", http.HandlerFunc(pingHandler))

View File

@@ -19,10 +19,10 @@ The FakeHandlerClient is used to create a fake gitlab client for testing our han
type fakeClient struct {
createMrFn func(pid interface{}, opt *gitlab.CreateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
getMergeRequestFn func(pid interface{}, mergeRequestIID int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
updateMergeRequestFn func(pid interface{}, mergeRequestIID int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
acceptAndMergeFn func(pid interface{}, mergeRequestIID int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
unapprorveMergeRequestFn func(pid interface{}, mergeRequestIID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
getMergeRequest func(pid interface{}, mergeRequestIID int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
updateMergeRequest func(pid interface{}, mergeRequestIID int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
acceptMergeRequest func(pid interface{}, mergeRequestIID int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
unapproveMergeRequest func(pid interface{}, mergeRequestIID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
uploadFile func(pid interface{}, content io.Reader, filename string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectFile, *gitlab.Response, error)
getMergeRequestDiffVersions func(pid interface{}, mergeRequestIID int, opt *gitlab.GetMergeRequestDiffVersionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequestDiffVersion, *gitlab.Response, error)
approveMergeRequest func(pid interface{}, mergeRequestIID int, opt *gitlab.ApproveMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequestApprovals, *gitlab.Response, error)
@@ -41,6 +41,13 @@ type fakeClient struct {
listMergeRequestAwardEmojiOnNote func(pid interface{}, mergeRequestIID, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error)
deleteMergeRequestAwardEmojiOnNote func(pid interface{}, mergeRequestIID, noteID, awardID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
currentUser func(options ...gitlab.RequestOptionFunc) (*gitlab.User, *gitlab.Response, error)
createDraftNote func(pid interface{}, mergeRequestIID int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error)
listDraftNotes func(pid interface{}, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error)
deleteDraftNote func(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
updateDraftNote func(pid interface{}, mergeRequest int, note int, opt *gitlab.UpdateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error)
publishAllDraftNotes func(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
publishDraftNote func(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
listProjectMergeRequests func(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error)
}
type Author struct {
@@ -58,19 +65,19 @@ func (f fakeClient) CreateMergeRequest(pid interface{}, opt *gitlab.CreateMergeR
}
func (f fakeClient) AcceptMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
return f.acceptAndMergeFn(pid, mergeRequestIID, opt, options...)
return f.acceptMergeRequest(pid, mergeRequestIID, opt, options...)
}
func (f fakeClient) GetMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
return f.getMergeRequestFn(pid, mergeRequestIID, opt, options...)
return f.getMergeRequest(pid, mergeRequestIID, opt, options...)
}
func (f fakeClient) UpdateMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
return f.updateMergeRequestFn(pid, mergeRequestIID, opt, options...)
return f.updateMergeRequest(pid, mergeRequestIID, opt, options...)
}
func (f fakeClient) UnapproveMergeRequest(pid interface{}, mergeRequestIID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return f.unapprorveMergeRequestFn(pid, mergeRequestIID, options...)
return f.unapproveMergeRequest(pid, mergeRequestIID, options...)
}
func (f fakeClient) UploadFile(pid interface{}, content io.Reader, filename string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectFile, *gitlab.Response, error) {
@@ -141,19 +148,47 @@ func (f fakeClient) DeleteMergeRequestAwardEmojiOnNote(pid interface{}, mergeReq
return f.deleteMergeRequestAwardEmojiOnNote(pid, mergeRequestIID, noteID, awardID)
}
func (f fakeClient) CreateDraftNote(pid interface{}, mergeRequestIID int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
return f.createDraftNote(pid, mergeRequestIID, opt)
}
func (f fakeClient) ListDraftNotes(pid interface{}, mergeRequestIID int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error) {
return f.listDraftNotes(pid, mergeRequestIID, opt)
}
func (f fakeClient) CurrentUser(options ...gitlab.RequestOptionFunc) (*gitlab.User, *gitlab.Response, error) {
return f.currentUser()
}
/* This middleware function needs to return an ID for the rest of the handlers */
func (f fakeClient) ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
return []*gitlab.MergeRequest{{ID: 1}}, &gitlab.Response{}, nil
}
func (f fakeClient) CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *gitlab.CreateAwardEmojiOptions, options ...gitlab.RequestOptionFunc) (*gitlab.AwardEmoji, *gitlab.Response, error) {
return &gitlab.AwardEmoji{}, &gitlab.Response{}, nil
}
func (f fakeClient) UpdateDraftNote(pid interface{}, mergeRequest int, note int, opt *gitlab.UpdateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
return f.updateDraftNote(pid, mergeRequest, note, opt)
}
func (f fakeClient) DeleteDraftNote(pid interface{}, mergeRequestIID int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return f.deleteDraftNote(pid, mergeRequestIID, note)
}
func (f fakeClient) PublishDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return f.publishDraftNote(pid, mergeRequest, note)
}
func (f fakeClient) PublishAllDraftNotes(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
return f.publishAllDraftNotes(pid, mergeRequest)
}
/* This middleware function needs to return an ID for the rest of the handlers */
func (f fakeClient) ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
if f.listProjectMergeRequests == nil {
return []*gitlab.MergeRequest{{ID: 1}}, &gitlab.Response{}, nil
} else {
return f.listProjectMergeRequests(pid, opt)
}
}
/* The assert function is a helper function used to check two comparables */
func assert[T comparable](t *testing.T, got T, want T) {
t.Helper()

View File

@@ -49,6 +49,12 @@ type ClientInterface interface {
CreateMergeRequestDiscussion(pid interface{}, mergeRequestIID int, opt *gitlab.CreateMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error)
UpdateMergeRequestDiscussionNote(pid interface{}, mergeRequestIID int, discussion string, note int, opt *gitlab.UpdateMergeRequestDiscussionNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error)
DeleteMergeRequestDiscussionNote(pid interface{}, mergeRequestIID int, discussion string, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
CreateDraftNote(pid interface{}, mergeRequestIID int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error)
ListDraftNotes(pid interface{}, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error)
DeleteDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
UpdateDraftNote(pid interface{}, mergeRequest int, note int, opt *gitlab.UpdateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error)
PublishDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
PublishAllDraftNotes(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
AddMergeRequestDiscussionNote(pid interface{}, mergeRequestIID int, discussion string, opt *gitlab.AddMergeRequestDiscussionNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error)
ListAllProjectMembers(pid interface{}, opt *gitlab.ListProjectMembersOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectMember, *gitlab.Response, error)
RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)