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:
committed by
GitHub
parent
f10c4ebb8f
commit
cf6ccddce3
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
82
cmd/comment_helpers.go
Normal 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
|
||||
}
|
||||
@@ -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
306
cmd/draft_notes.go
Normal 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
197
cmd/draft_notes_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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
55
cmd/merge_requests.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
45
cmd/merge_requests_test.go
Normal file
45
cmd/merge_requests_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
61
cmd/test.go
61
cmd/test.go
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user