Fix MR Selection, Go Code Refactor (#358)
refactor: Refactors the Go codebase into a more modular and idiomatic approach fix: require selection of specific MR when there are multiple targets for a given source branch feat: Allows for the passing of Gitlab's filter options when choosing an MR, improves MR selection feat: API to choose an MR from a list based on the provided username's involvement as an assignee/reviewer/author This is a #MINOR release
This commit is contained in:
committed by
GitHub
parent
6500ef1f2c
commit
ea2b2b2f5c
5
.github/CONTRIBUTING.md
vendored
5
.github/CONTRIBUTING.md
vendored
@@ -38,11 +38,10 @@ $ go fmt ./...
|
|||||||
$ golangci-lint run
|
$ golangci-lint run
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are writing tests and have added something to the Go client, you can re-generate the mocked client like so:
|
If you are writing tests and have added something to the Go client, you can test with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go install go.uber.org/mock/mockgen@latest # Install the mockgen CLI on your machine
|
$ make test
|
||||||
$ mockgen -source cmd/types.go > cmd/mocks/fake_client.go
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For changes to the Lua codebase: We use <a href="https://github.com/JohnnyMorganz/StyLua">stylua</a> for formatting and <a href="https://github.com/mpeterv/luacheck">luacheck</a> for linting. Run these commands in the root of the repository:
|
For changes to the Lua codebase: We use <a href="https://github.com/JohnnyMorganz/StyLua">stylua</a> for formatting and <a href="https://github.com/mpeterv/luacheck">luacheck</a> for linting. Run these commands in the root of the repository:
|
||||||
|
|||||||
1
.github/workflows/go.yaml
vendored
1
.github/workflows/go.yaml
vendored
@@ -19,6 +19,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: v1.54
|
version: v1.54
|
||||||
only-new-issues: true
|
only-new-issues: true
|
||||||
|
skip-cache: true
|
||||||
go_test:
|
go_test:
|
||||||
name: Test Go 🧪
|
name: Test Go 🧪
|
||||||
needs: [go_lint]
|
needs: [go_lint]
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MergeRequestApprover interface {
|
||||||
|
ApproveMergeRequest(pid interface{}, mr int, opt *gitlab.ApproveMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequestApprovals, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeRequestApproverService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestApprover
|
||||||
|
}
|
||||||
|
|
||||||
/* approveHandler approves a merge request. */
|
/* approveHandler approves a merge request. */
|
||||||
func (a *Api) approveHandler(w http.ResponseWriter, r *http.Request) {
|
func (a mergeRequestApproverService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
||||||
55
cmd/app/approve_test.go
Normal file
55
cmd/app/approve_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeApproverClient struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeApproverClient) ApproveMergeRequest(pid interface{}, mr int, opt *gitlab.ApproveMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequestApprovals, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &gitlab.MergeRequestApprovals{}, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApproveHandler(t *testing.T) {
|
||||||
|
t.Run("Approves merge request", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/approve", nil)
|
||||||
|
client := fakeApproverClient{}
|
||||||
|
svc := mergeRequestApproverService{testProjectData, client}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Approved MR")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Disallows non-POST method", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/approve", nil)
|
||||||
|
client := fakeApproverClient{}
|
||||||
|
svc := mergeRequestApproverService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/approve", nil)
|
||||||
|
client := fakeApproverClient{testBase{errFromGitlab: true}}
|
||||||
|
svc := mergeRequestApproverService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not approve merge request")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/approve", nil)
|
||||||
|
client := fakeApproverClient{testBase{status: http.StatusSeeOther}}
|
||||||
|
svc := mergeRequestApproverService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not approve merge request", "/mr/approve")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -22,8 +22,13 @@ type AssigneesRequestResponse struct {
|
|||||||
Assignees []int `json:"assignees"`
|
Assignees []int `json:"assignees"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type assigneesService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestUpdater
|
||||||
|
}
|
||||||
|
|
||||||
/* assigneesHandler adds or removes assignees from a merge request. */
|
/* assigneesHandler adds or removes assignees from a merge request. */
|
||||||
func (a *Api) assigneesHandler(w http.ResponseWriter, r *http.Request) {
|
func (a assigneesService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPut)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPut)
|
||||||
57
cmd/app/assignee_test.go
Normal file
57
cmd/app/assignee_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeAssigneeClient struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeAssigneeClient) UpdateMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &gitlab.MergeRequest{}, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssigneeHandler(t *testing.T) {
|
||||||
|
var updatePayload = AssigneeUpdateRequest{Ids: []int{1, 2}}
|
||||||
|
|
||||||
|
t.Run("Updates assignees", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPut, "/mr/assignee", updatePayload)
|
||||||
|
client := fakeAssigneeClient{}
|
||||||
|
svc := assigneesService{testProjectData, client}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Assignees updated")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Disallows non-PUT method", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/assignee", nil)
|
||||||
|
client := fakeAssigneeClient{}
|
||||||
|
svc := assigneesService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPut)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPut, "/mr/approve", updatePayload)
|
||||||
|
client := fakeAssigneeClient{testBase{errFromGitlab: true}}
|
||||||
|
svc := assigneesService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not modify merge request assignees")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPut, "/mr/approve", updatePayload)
|
||||||
|
client := fakeAssigneeClient{testBase{status: http.StatusSeeOther}}
|
||||||
|
svc := assigneesService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not modify merge request assignees", "/mr/assignee")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileReader interface {
|
type FileReader interface {
|
||||||
@@ -45,8 +47,18 @@ func (ar attachmentReader) ReadFile(path string) (io.Reader, error) {
|
|||||||
return reader, nil
|
return reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileUploader interface {
|
||||||
|
UploadFile(pid interface{}, content io.Reader, filename string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectFile, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type attachmentService struct {
|
||||||
|
data
|
||||||
|
fileReader FileReader
|
||||||
|
client FileUploader
|
||||||
|
}
|
||||||
|
|
||||||
/* attachmentHandler uploads an attachment (file, image, etc) to Gitlab and returns metadata about the upload. */
|
/* attachmentHandler uploads an attachment (file, image, etc) to Gitlab and returns metadata about the upload. */
|
||||||
func (a *Api) attachmentHandler(w http.ResponseWriter, r *http.Request) {
|
func (a attachmentService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
||||||
64
cmd/app/attachment_test.go
Normal file
64
cmd/app/attachment_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeFileUploaderClient struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeFileUploaderClient) UploadFile(pid interface{}, content io.Reader, filename string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectFile, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gitlab.ProjectFile{}, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeFileReader struct{}
|
||||||
|
|
||||||
|
func (f fakeFileReader) ReadFile(path string) (io.Reader, error) {
|
||||||
|
return &bytes.Reader{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttachmentHandler(t *testing.T) {
|
||||||
|
attachmentTestRequestData := AttachmentRequest{
|
||||||
|
FileName: "some_file_name",
|
||||||
|
FilePath: "some_file_path",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Returns 200-status response after upload", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/attachment", attachmentTestRequestData)
|
||||||
|
svc := attachmentService{testProjectData, fakeFileReader{}, fakeFileUploaderClient{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "File uploaded successfully")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Disallows non-POST method", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/attachment", nil)
|
||||||
|
svc := attachmentService{testProjectData, fakeFileReader{}, fakeFileUploaderClient{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/attachment", attachmentTestRequestData)
|
||||||
|
svc := attachmentService{testProjectData, fakeFileReader{}, fakeFileUploaderClient{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not upload some_file_name to Gitlab")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/attachment", attachmentTestRequestData)
|
||||||
|
svc := attachmentService{testProjectData, fakeFileReader{}, fakeFileUploaderClient{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not upload some_file_name to Gitlab", "/attachment")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
@@ -34,8 +35,8 @@ type Client struct {
|
|||||||
*gitlab.DraftNotesService
|
*gitlab.DraftNotesService
|
||||||
}
|
}
|
||||||
|
|
||||||
/* initGitlabClient parses and validates the project settings and initializes the Gitlab client. */
|
/* NewClient parses and validates the project settings and initializes the Gitlab client. */
|
||||||
func initGitlabClient() (error, *Client) {
|
func NewClient() (error, *Client) {
|
||||||
|
|
||||||
if pluginOptions.GitlabUrl == "" {
|
if pluginOptions.GitlabUrl == "" {
|
||||||
return errors.New("GitLab instance URL cannot be empty"), nil
|
return errors.New("GitLab instance URL cannot be empty"), nil
|
||||||
@@ -86,11 +87,11 @@ func initGitlabClient() (error, *Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* initProjectSettings fetch the project ID using the client */
|
/* InitProjectSettings fetch the project ID using the client */
|
||||||
func initProjectSettings(c *Client, gitInfo GitProjectInfo) (error, *ProjectInfo) {
|
func InitProjectSettings(c *Client, gitInfo git.GitData) (error, *ProjectInfo) {
|
||||||
|
|
||||||
opt := gitlab.GetProjectOptions{}
|
opt := gitlab.GetProjectOptions{}
|
||||||
project, _, err := c.GetProject(gitInfo.projectPath(), &opt)
|
project, _, err := c.GetProject(gitInfo.ProjectPath(), &opt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(fmt.Sprintf("Error getting project at %s", gitInfo.RemoteUrl), err), nil
|
return fmt.Errorf(fmt.Sprintf("Error getting project at %s", gitInfo.RemoteUrl), err), nil
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -41,8 +41,19 @@ func (comment CommentWithPosition) GetPositionData() PositionData {
|
|||||||
return comment.PositionData
|
return comment.PositionData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentManager interface {
|
||||||
|
CreateMergeRequestDiscussion(pid interface{}, mergeRequest int, opt *gitlab.CreateMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error)
|
||||||
|
UpdateMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, note int, opt *gitlab.UpdateMergeRequestDiscussionNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error)
|
||||||
|
DeleteMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type commentService struct {
|
||||||
|
data
|
||||||
|
client CommentManager
|
||||||
|
}
|
||||||
|
|
||||||
/* commentHandler creates, edits, and deletes discussions (comments, multi-line comments) */
|
/* commentHandler creates, edits, and deletes discussions (comments, multi-line comments) */
|
||||||
func (a *Api) commentHandler(w http.ResponseWriter, r *http.Request) {
|
func (a commentService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
@@ -58,7 +69,7 @@ func (a *Api) commentHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* deleteComment deletes a note, multiline comment, or comment, which are all considered discussion notes. */
|
/* deleteComment deletes a note, multiline comment, or comment, which are all considered discussion notes. */
|
||||||
func (a *Api) deleteComment(w http.ResponseWriter, r *http.Request) {
|
func (a commentService) deleteComment(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
||||||
@@ -99,7 +110,7 @@ func (a *Api) deleteComment(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* postComment creates a note, multiline comment, or comment. */
|
/* postComment creates a note, multiline comment, or comment. */
|
||||||
func (a *Api) postComment(w http.ResponseWriter, r *http.Request) {
|
func (a commentService) postComment(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
||||||
@@ -156,7 +167,7 @@ func (a *Api) postComment(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* editComment changes the text of a comment or changes it's resolved status. */
|
/* editComment changes the text of a comment or changes it's resolved status. */
|
||||||
func (a *Api) editComment(w http.ResponseWriter, r *http.Request) {
|
func (a commentService) editComment(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
124
cmd/app/comment_test.go
Normal file
124
cmd/app/comment_test.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeCommentClient struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeCommentClient) CreateMergeRequestDiscussion(pid interface{}, mergeRequest int, opt *gitlab.CreateMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gitlab.Discussion{Notes: []*gitlab.Note{{}}}, resp, err
|
||||||
|
}
|
||||||
|
func (f fakeCommentClient) UpdateMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, note int, opt *gitlab.UpdateMergeRequestDiscussionNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gitlab.Note{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeCommentClient) DeleteMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostComment(t *testing.T) {
|
||||||
|
var testCommentCreationData = PostCommentRequest{Comment: "Some comment"}
|
||||||
|
t.Run("Creates a new note (unlinked comment)", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Comment created successfully")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Creates a new comment", func(t *testing.T) {
|
||||||
|
testCommentCreationData := PostCommentRequest{ // Re-create comment creation data to avoid mutating this variable in other tests
|
||||||
|
Comment: "Some comment",
|
||||||
|
PositionData: PositionData{
|
||||||
|
FileName: "file.txt",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Comment created successfully")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not create discussion")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not create discussion", "/mr/comment")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteComment(t *testing.T) {
|
||||||
|
var testCommentDeletionData = DeleteCommentRequest{NoteId: 3, DiscussionId: "abc123"}
|
||||||
|
t.Run("Deletes a comment", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/comment", testCommentDeletionData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Comment deleted successfully")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/comment", testCommentDeletionData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not delete comment")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/comment", testCommentDeletionData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not delete comment", "/mr/comment")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEditComment(t *testing.T) {
|
||||||
|
var testEditCommentData = EditCommentRequest{Comment: "Some comment", NoteId: 3, DiscussionId: "abc123"}
|
||||||
|
t.Run("Edits a comment", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/comment", testEditCommentData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Comment updated successfully")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/comment", testEditCommentData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not update comment")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/comment", testEditCommentData)
|
||||||
|
svc := commentService{testProjectData, fakeCommentClient{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not update comment", "/mr/comment")
|
||||||
|
})
|
||||||
|
}
|
||||||
23
cmd/app/config.go
Normal file
23
cmd/app/config.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
type PluginOptions struct {
|
||||||
|
GitlabUrl string `json:"gitlab_url"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
AuthToken string `json:"auth_token"`
|
||||||
|
LogPath string `json:"log_path"`
|
||||||
|
Debug struct {
|
||||||
|
Request bool `json:"go_request"`
|
||||||
|
Response bool `json:"go_response"`
|
||||||
|
} `json:"debug"`
|
||||||
|
ChosenTargetBranch *string `json:"chosen_target_branch,omitempty"`
|
||||||
|
ConnectionSettings struct {
|
||||||
|
Insecure bool `json:"insecure"`
|
||||||
|
Remote string `json:"remote"`
|
||||||
|
} `json:"connection_settings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginOptions PluginOptions
|
||||||
|
|
||||||
|
func SetPluginOptions(p PluginOptions) {
|
||||||
|
pluginOptions = p
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -19,8 +19,17 @@ type CreateMrRequest struct {
|
|||||||
TargetProjectID int `json:"forked_project_id,omitempty"`
|
TargetProjectID int `json:"forked_project_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergeRequestCreator interface {
|
||||||
|
CreateMergeRequest(pid interface{}, opt *gitlab.CreateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeRequestCreatorService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestCreator
|
||||||
|
}
|
||||||
|
|
||||||
/* createMr creates a merge request */
|
/* createMr creates a merge request */
|
||||||
func (a *Api) createMr(w http.ResponseWriter, r *http.Request) {
|
func (a mergeRequestCreatorService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
80
cmd/app/create_mr_test.go
Normal file
80
cmd/app/create_mr_test.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMergeCreatorClient struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeMergeCreatorClient) CreateMergeRequest(pid interface{}, opt *gitlab.CreateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &gitlab.MergeRequest{}, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateMr(t *testing.T) {
|
||||||
|
var testCreateMrRequestData = CreateMrRequest{
|
||||||
|
Title: "Some title",
|
||||||
|
Description: "Some description",
|
||||||
|
TargetBranch: "main",
|
||||||
|
DeleteBranch: false,
|
||||||
|
Squash: false,
|
||||||
|
}
|
||||||
|
t.Run("Creates an MR", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/create_mr", testCreateMrRequestData)
|
||||||
|
svc := mergeRequestCreatorService{testProjectData, fakeMergeCreatorClient{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "MR 'Some title' created")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Disallows non-POST methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/create_mr", testCreateMrRequestData)
|
||||||
|
svc := mergeRequestCreatorService{testProjectData, fakeMergeCreatorClient{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/create_mr", testCreateMrRequestData)
|
||||||
|
svc := mergeRequestCreatorService{testProjectData, fakeMergeCreatorClient{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not create MR")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/create_mr", testCreateMrRequestData)
|
||||||
|
svc := mergeRequestCreatorService{testProjectData, fakeMergeCreatorClient{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not create MR", "/create_mr")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles missing titles", func(t *testing.T) {
|
||||||
|
reqData := testCreateMrRequestData
|
||||||
|
reqData.Title = ""
|
||||||
|
request := makeRequest(t, http.MethodPost, "/create_mr", reqData)
|
||||||
|
svc := mergeRequestCreatorService{testProjectData, fakeMergeCreatorClient{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
assert(t, data.Message, "Could not create MR")
|
||||||
|
assert(t, data.Details, "Title cannot be empty")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles missing target branch", func(t *testing.T) {
|
||||||
|
reqData := testCreateMrRequestData
|
||||||
|
reqData.TargetBranch = ""
|
||||||
|
request := makeRequest(t, http.MethodPost, "/create_mr", reqData)
|
||||||
|
svc := mergeRequestCreatorService{testProjectData, fakeMergeCreatorClient{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
assert(t, data.Message, "Could not create MR")
|
||||||
|
assert(t, data.Details, "Target branch cannot be empty")
|
||||||
|
})
|
||||||
|
}
|
||||||
76
cmd/app/draft_note_publisher.go
Normal file
76
cmd/app/draft_note_publisher.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DraftNotePublisher interface {
|
||||||
|
PublishAllDraftNotes(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
||||||
|
PublishDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type draftNotePublisherService struct {
|
||||||
|
data
|
||||||
|
client DraftNotePublisher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a draftNotePublisherService) handler(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
cmd/app/draft_note_publisher_test.go
Normal file
74
cmd/app/draft_note_publisher_test.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeDraftNotePublisher struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDraftNotePublisher) PublishAllDraftNotes(pid interface{}, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
||||||
|
return f.handleGitlabError()
|
||||||
|
}
|
||||||
|
func (f fakeDraftNotePublisher) PublishDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
||||||
|
return f.handleGitlabError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishDraftNote(t *testing.T) {
|
||||||
|
var testDraftNotePublishRequest = DraftNotePublishRequest{Note: 3, PublishAll: false}
|
||||||
|
t.Run("Publishes draft note", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Draft note(s) published")
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-POST method", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
t.Run("Handles bad ID", func(t *testing.T) {
|
||||||
|
badData := testDraftNotePublishRequest
|
||||||
|
badData.Note = 0
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", badData)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
assert(t, data.Message, "Must provide Note ID")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not publish draft note(s)")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishAllDraftNotes(t *testing.T) {
|
||||||
|
var testDraftNotePublishRequest = DraftNotePublishRequest{PublishAll: true}
|
||||||
|
t.Run("Should publish all draft notes", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Draft note(s) published")
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-POST method", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
||||||
|
svc := draftNotePublisherService{testProjectData, fakeDraftNotePublisher{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not publish draft note(s)")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -51,8 +51,20 @@ func (draftNote DraftNoteWithPosition) GetPositionData() PositionData {
|
|||||||
return draftNote.PositionData
|
return draftNote.PositionData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DraftNoteManager interface {
|
||||||
|
ListDraftNotes(pid interface{}, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error)
|
||||||
|
CreateDraftNote(pid interface{}, mergeRequest int, opt *gitlab.CreateDraftNoteOptions, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
type draftNoteService struct {
|
||||||
|
data
|
||||||
|
client DraftNoteManager
|
||||||
|
}
|
||||||
|
|
||||||
/* draftNoteHandler creates, edits, and deletes draft notes */
|
/* draftNoteHandler creates, edits, and deletes draft notes */
|
||||||
func (a *Api) draftNoteHandler(w http.ResponseWriter, r *http.Request) {
|
func (a draftNoteService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
@@ -69,54 +81,29 @@ func (a *Api) draftNoteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) draftNotePublisher(w http.ResponseWriter, r *http.Request) {
|
/* listDraftNotes lists all draft notes for the currently authenticated user */
|
||||||
w.Header().Set("Content-Type", "application/json")
|
func (a draftNoteService) listDraftNotes(w http.ResponseWriter, _ *http.Request) {
|
||||||
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)
|
opt := gitlab.ListDraftNotesOptions{}
|
||||||
if err != nil {
|
draftNotes, res, err := a.client.ListDraftNotes(a.projectInfo.ProjectId, a.projectInfo.MergeId, &opt)
|
||||||
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 {
|
if err != nil {
|
||||||
handleError(w, err, "Could not read JSON from request", http.StatusBadRequest)
|
handleError(w, err, "Could not get draft notes", http.StatusInternalServerError)
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode >= 300 {
|
if res.StatusCode >= 300 {
|
||||||
handleError(w, GenericError{endpoint: "/mr/draft_notes/publish"}, "Could not publish dfaft note", res.StatusCode)
|
handleError(w, GenericError{endpoint: "/mr/draft_notes/"}, "Could not get draft notes", res.StatusCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
response := SuccessResponse{
|
response := ListDraftNotesResponse{
|
||||||
Message: "Draft note(s) published",
|
SuccessResponse: SuccessResponse{
|
||||||
Status: http.StatusOK,
|
Message: "Draft notes fetched successfully",
|
||||||
|
Status: http.StatusOK,
|
||||||
|
},
|
||||||
|
DraftNotes: draftNotes,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(response)
|
err = json.NewEncoder(w).Encode(response)
|
||||||
@@ -126,7 +113,7 @@ func (a *Api) draftNotePublisher(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* postDraftNote creates a draft note */
|
/* postDraftNote creates a draft note */
|
||||||
func (a *Api) postDraftNote(w http.ResponseWriter, r *http.Request) {
|
func (a draftNoteService) postDraftNote(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
||||||
@@ -184,7 +171,7 @@ func (a *Api) postDraftNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* deleteDraftNote deletes a draft note */
|
/* deleteDraftNote deletes a draft note */
|
||||||
func (a *Api) deleteDraftNote(w http.ResponseWriter, r *http.Request) {
|
func (a draftNoteService) deleteDraftNote(w http.ResponseWriter, r *http.Request) {
|
||||||
suffix := strings.TrimPrefix(r.URL.Path, "/mr/draft_notes/")
|
suffix := strings.TrimPrefix(r.URL.Path, "/mr/draft_notes/")
|
||||||
id, err := strconv.Atoi(suffix)
|
id, err := strconv.Atoi(suffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -200,7 +187,7 @@ func (a *Api) deleteDraftNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode >= 300 {
|
if res.StatusCode >= 300 {
|
||||||
handleError(w, GenericError{endpoint: "/mr/draft_notes/"}, "Could not delete draft note", res.StatusCode)
|
handleError(w, GenericError{endpoint: fmt.Sprintf("/mr/draft_notes/%d", id)}, "Could not delete draft note", res.StatusCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +204,7 @@ func (a *Api) deleteDraftNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* updateDraftNote edits the text of a draft comment */
|
/* updateDraftNote edits the text of a draft comment */
|
||||||
func (a *Api) updateDraftNote(w http.ResponseWriter, r *http.Request) {
|
func (a draftNoteService) updateDraftNote(w http.ResponseWriter, r *http.Request) {
|
||||||
suffix := strings.TrimPrefix(r.URL.Path, "/mr/draft_notes/")
|
suffix := strings.TrimPrefix(r.URL.Path, "/mr/draft_notes/")
|
||||||
id, err := strconv.Atoi(suffix)
|
id, err := strconv.Atoi(suffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -258,7 +245,7 @@ func (a *Api) updateDraftNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode >= 300 {
|
if res.StatusCode >= 300 {
|
||||||
handleError(w, GenericError{endpoint: "/mr/draft_notes/"}, "Could not update draft note", res.StatusCode)
|
handleError(w, GenericError{endpoint: fmt.Sprintf("/mr/draft_notes/%d", id)}, "Could not update draft note", res.StatusCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,34 +263,3 @@ func (a *Api) updateDraftNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
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, _ *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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
153
cmd/app/draft_notes_test.go
Normal file
153
cmd/app/draft_notes_test.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeDraftNoteManager struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDraftNoteManager) ListDraftNotes(pid interface{}, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return []*gitlab.DraftNote{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDraftNoteManager) CreateDraftNote(pid interface{}, mergeRequest int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &gitlab.DraftNote{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDraftNoteManager) DeleteDraftNote(pid interface{}, mergeRequest int, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
||||||
|
return f.handleGitlabError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDraftNoteManager) UpdateDraftNote(pid interface{}, mergeRequest int, note int, opt *gitlab.UpdateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &gitlab.DraftNote{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListDraftNotes(t *testing.T) {
|
||||||
|
t.Run("Lists all draft notes", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Draft notes fetched successfully")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not get draft notes")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not get draft notes", "/mr/draft_notes/")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostDraftNote(t *testing.T) {
|
||||||
|
var testPostDraftNoteRequestData = PostDraftNoteRequest{Comment: "Some comment"}
|
||||||
|
t.Run("Posts new draft note", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", testPostDraftNoteRequestData)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Draft note created successfully")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", testPostDraftNoteRequestData)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not create draft note")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", testPostDraftNoteRequestData)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not create draft note", "/mr/draft_notes/")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteDraftNote(t *testing.T) {
|
||||||
|
t.Run("Deletes new draft note", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/3", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Draft note deleted")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/3", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not delete draft note")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/3", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not delete draft note", "/mr/draft_notes/3")
|
||||||
|
})
|
||||||
|
t.Run("Handles bad ID", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodDelete, "/mr/draft_notes/blah", nil)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Could not parse draft note ID")
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEditDraftNote(t *testing.T) {
|
||||||
|
var testUpdateDraftNoteRequest = UpdateDraftNoteRequest{Note: "Some new note"}
|
||||||
|
t.Run("Edits new draft note", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/3", testUpdateDraftNoteRequest)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Draft note updated")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/3", testUpdateDraftNoteRequest)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not update draft note")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/3", testUpdateDraftNoteRequest)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not update draft note", "/mr/draft_notes/3")
|
||||||
|
})
|
||||||
|
t.Run("Handles bad ID", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/blah", testUpdateDraftNoteRequest)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
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) {
|
||||||
|
requestData := testUpdateDraftNoteRequest
|
||||||
|
requestData.Note = ""
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/mr/draft_notes/3", requestData)
|
||||||
|
svc := draftNoteService{testProjectData, fakeDraftNoteManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Must provide draft note text")
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
@@ -39,99 +38,17 @@ type CreateEmojiResponse struct {
|
|||||||
Emoji *gitlab.AwardEmoji
|
Emoji *gitlab.AwardEmoji
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
type EmojiManager interface {
|
||||||
attachEmojisToApi reads the emojis from our external JSON file
|
DeleteMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, awardID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
||||||
and attaches them to the API so that they can be looked up later
|
CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, opt *gitlab.CreateAwardEmojiOptions, options ...gitlab.RequestOptionFunc) (*gitlab.AwardEmoji, *gitlab.Response, error)
|
||||||
*/
|
|
||||||
func attachEmojisToApi(a *Api) error {
|
|
||||||
|
|
||||||
e, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
binPath := path.Dir(e)
|
|
||||||
filePath := fmt.Sprintf("%s/config/emojis.json", binPath)
|
|
||||||
|
|
||||||
reader, err := a.fileReader.ReadFile(filePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Could not find emojis at %s", filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := io.ReadAll(reader)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("Could not read emoji file")
|
|
||||||
}
|
|
||||||
|
|
||||||
var emojiMap EmojiMap
|
|
||||||
err = json.Unmarshal(bytes, &emojiMap)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("Could not unmarshal emojis")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.emojiMap = emojiMap
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
type emojiService struct {
|
||||||
Fetches emojis for a set of notes and comments in parallel and returns a map of note IDs to their emojis.
|
data
|
||||||
Gitlab's API does not allow for fetching notes for an entire discussion thread so we have to do it per-note.
|
client EmojiManager
|
||||||
*/
|
|
||||||
func (a *Api) fetchEmojisForNotesAndComments(noteIDs []int) (map[int][]*gitlab.AwardEmoji, error) {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
emojis := make(map[int][]*gitlab.AwardEmoji)
|
|
||||||
mu := &sync.Mutex{}
|
|
||||||
errs := make(chan error, len(noteIDs))
|
|
||||||
emojiChan := make(chan struct {
|
|
||||||
noteID int
|
|
||||||
emojis []*gitlab.AwardEmoji
|
|
||||||
}, len(noteIDs))
|
|
||||||
|
|
||||||
for _, noteID := range noteIDs {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(noteID int) {
|
|
||||||
defer wg.Done()
|
|
||||||
emojis, _, err := a.client.ListMergeRequestAwardEmojiOnNote(a.projectInfo.ProjectId, a.projectInfo.MergeId, noteID, &gitlab.ListAwardEmojiOptions{})
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
emojiChan <- struct {
|
|
||||||
noteID int
|
|
||||||
emojis []*gitlab.AwardEmoji
|
|
||||||
}{noteID, emojis}
|
|
||||||
}(noteID)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Close the channels when all goroutines finish */
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(errs)
|
|
||||||
close(emojiChan)
|
|
||||||
}()
|
|
||||||
|
|
||||||
/* Collect emojis */
|
|
||||||
for e := range emojiChan {
|
|
||||||
mu.Lock()
|
|
||||||
emojis[e.noteID] = e.emojis
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if any errors occurred */
|
|
||||||
if len(errs) > 0 {
|
|
||||||
for err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return emojis, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) emojiNoteHandler(w http.ResponseWriter, r *http.Request) {
|
func (a emojiService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
@@ -145,7 +62,7 @@ func (a *Api) emojiNoteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* deleteEmojiFromNote deletes an emoji from a note based on the emoji (awardable) ID and the note's ID */
|
/* deleteEmojiFromNote deletes an emoji from a note based on the emoji (awardable) ID and the note's ID */
|
||||||
func (a *Api) deleteEmojiFromNote(w http.ResponseWriter, r *http.Request) {
|
func (a emojiService) deleteEmojiFromNote(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
suffix := strings.TrimPrefix(r.URL.Path, "/mr/awardable/note/")
|
suffix := strings.TrimPrefix(r.URL.Path, "/mr/awardable/note/")
|
||||||
ids := strings.Split(suffix, "/")
|
ids := strings.Split(suffix, "/")
|
||||||
@@ -187,7 +104,7 @@ func (a *Api) deleteEmojiFromNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* postEmojiOnNote adds an emojis to a note based on the note's ID */
|
/* postEmojiOnNote adds an emojis to a note based on the note's ID */
|
||||||
func (a *Api) postEmojiOnNote(w http.ResponseWriter, r *http.Request) {
|
func (a emojiService) postEmojiOnNote(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
handleError(w, err, "Could not read request body", http.StatusBadRequest)
|
||||||
@@ -232,3 +149,38 @@ func (a *Api) postEmojiOnNote(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
attachEmojis reads the emojis from our external JSON file
|
||||||
|
and attaches them to the data so that they can be looked up later
|
||||||
|
*/
|
||||||
|
func attachEmojis(a *data, fr FileReader) error {
|
||||||
|
|
||||||
|
e, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
binPath := path.Dir(e)
|
||||||
|
filePath := fmt.Sprintf("%s/config/emojis.json", binPath)
|
||||||
|
|
||||||
|
reader, err := fr.ReadFile(filePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not find emojis at %s", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Could not read emoji file")
|
||||||
|
}
|
||||||
|
|
||||||
|
var emojiMap EmojiMap
|
||||||
|
err = json.Unmarshal(bytes, &emojiMap)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Could not unmarshal emojis")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.emojiMap = emojiMap
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7,19 +7,27 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitProjectInfo struct {
|
type GitManager interface {
|
||||||
RemoteUrl string
|
RefreshProjectInfo(remote string) error
|
||||||
Namespace string
|
GetProjectUrlFromNativeGitCmd(remote string) (url string, err error)
|
||||||
ProjectName string
|
GetCurrentBranchNameFromNativeGitCmd() (string, error)
|
||||||
BranchName string
|
GetLatestCommitOnRemote(remote string, branchName string) (string, error)
|
||||||
GetLatestCommitOnRemote func(a *Api) (string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GitData struct {
|
||||||
|
RemoteUrl string
|
||||||
|
Namespace string
|
||||||
|
ProjectName string
|
||||||
|
BranchName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Git struct{}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
projectPath returns the Gitlab project full path, which isn't necessarily the same as its name.
|
projectPath returns the Gitlab project full path, which isn't necessarily the same as its name.
|
||||||
See https://docs.gitlab.com/ee/api/rest/index.html#namespaced-path-encoding for more information.
|
See https://docs.gitlab.com/ee/api/rest/index.html#namespaced-path-encoding for more information.
|
||||||
*/
|
*/
|
||||||
func (g GitProjectInfo) projectPath() string {
|
func (g GitData) ProjectPath() string {
|
||||||
return g.Namespace + "/" + g.ProjectName
|
return g.Namespace + "/" + g.ProjectName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +36,15 @@ Extracts information about the current repository and returns
|
|||||||
it to the client for initialization. The current directory must be a valid
|
it to the client for initialization. The current directory must be a valid
|
||||||
Gitlab project and the branch must be a feature branch
|
Gitlab project and the branch must be a feature branch
|
||||||
*/
|
*/
|
||||||
func extractGitInfo(refreshGitInfo func() error, getProjectRemoteUrl func() (string, error), getCurrentBranchName func() (string, error)) (GitProjectInfo, error) {
|
func NewGitData(remote string, g GitManager) (GitData, error) {
|
||||||
err := refreshGitInfo()
|
err := g.RefreshProjectInfo(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GitProjectInfo{}, fmt.Errorf("Could not get latest information from remote: %v", err)
|
return GitData{}, fmt.Errorf("Could not get latest information from remote: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := getProjectRemoteUrl()
|
url, err := g.GetProjectUrlFromNativeGitCmd(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GitProjectInfo{}, fmt.Errorf("Could not get project Url: %v", err)
|
return GitData{}, fmt.Errorf("Could not get project Url: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -54,18 +62,18 @@ func extractGitInfo(refreshGitInfo func() error, getProjectRemoteUrl func() (str
|
|||||||
re := regexp.MustCompile(`(?:^https?:\/\/|^ssh:\/\/|^git@)(?:[^\/:]+)(?::\d+)?[\/:](.*)\/([^\/]+?)(?:\.git)?$`)
|
re := regexp.MustCompile(`(?:^https?:\/\/|^ssh:\/\/|^git@)(?:[^\/:]+)(?::\d+)?[\/:](.*)\/([^\/]+?)(?:\.git)?$`)
|
||||||
matches := re.FindStringSubmatch(url)
|
matches := re.FindStringSubmatch(url)
|
||||||
if len(matches) != 3 {
|
if len(matches) != 3 {
|
||||||
return GitProjectInfo{}, fmt.Errorf("Invalid Git URL format: %s", url)
|
return GitData{}, fmt.Errorf("Invalid Git URL format: %s", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace := matches[1]
|
namespace := matches[1]
|
||||||
projectName := matches[2]
|
projectName := matches[2]
|
||||||
|
|
||||||
branchName, err := getCurrentBranchName()
|
branchName, err := g.GetCurrentBranchNameFromNativeGitCmd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GitProjectInfo{}, fmt.Errorf("Failed to get current branch: %v", err)
|
return GitData{}, fmt.Errorf("Failed to get current branch: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GitProjectInfo{
|
return GitData{
|
||||||
RemoteUrl: url,
|
RemoteUrl: url,
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
ProjectName: projectName,
|
ProjectName: projectName,
|
||||||
@@ -75,7 +83,7 @@ func extractGitInfo(refreshGitInfo func() error, getProjectRemoteUrl func() (str
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Gets the current branch name */
|
/* Gets the current branch name */
|
||||||
func GetCurrentBranchNameFromNativeGitCmd() (res string, e error) {
|
func (g Git) GetCurrentBranchNameFromNativeGitCmd() (res string, e error) {
|
||||||
gitCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
gitCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
|
||||||
output, err := gitCmd.Output()
|
output, err := gitCmd.Output()
|
||||||
@@ -89,8 +97,8 @@ func GetCurrentBranchNameFromNativeGitCmd() (res string, e error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Gets the project SSH or HTTPS url */
|
/* Gets the project SSH or HTTPS url */
|
||||||
func GetProjectUrlFromNativeGitCmd() (string, error) {
|
func (g Git) GetProjectUrlFromNativeGitCmd(remote string) (string, error) {
|
||||||
cmd := exec.Command("git", "remote", "get-url", pluginOptions.ConnectionSettings.Remote)
|
cmd := exec.Command("git", "remote", "get-url", remote)
|
||||||
url, err := cmd.Output()
|
url, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Could not get remote")
|
return "", fmt.Errorf("Could not get remote")
|
||||||
@@ -100,25 +108,22 @@ func GetProjectUrlFromNativeGitCmd() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Pulls down latest commit information from Gitlab */
|
/* Pulls down latest commit information from Gitlab */
|
||||||
func RefreshProjectInfo() error {
|
func (g Git) RefreshProjectInfo(remote string) error {
|
||||||
cmd := exec.Command("git", "fetch", pluginOptions.ConnectionSettings.Remote)
|
cmd := exec.Command("git", "fetch", remote)
|
||||||
_, err := cmd.Output()
|
_, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to run `git fetch %s`: %v", pluginOptions.ConnectionSettings.Remote, err)
|
return fmt.Errorf("Failed to run `git fetch %s`: %v", remote, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func (g Git) GetLatestCommitOnRemote(remote string, branchName string) (string, error) {
|
||||||
The GetLatestCommitOnRemote function is attached during the CreateRouterAndApi call, since it needs to be called every time to get the latest commit.
|
cmd := exec.Command("git", "log", "-1", "--format=%H", fmt.Sprintf("%s/%s", remote, branchName))
|
||||||
*/
|
|
||||||
func GetLatestCommitOnRemote(a *Api) (string, error) {
|
|
||||||
cmd := exec.Command("git", "log", "-1", "--format=%H", fmt.Sprintf("%s/%s", pluginOptions.ConnectionSettings.Remote, a.gitInfo.BranchName))
|
|
||||||
|
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Failed to run `git log -1 --format=%%H " + fmt.Sprintf("%s/%s", pluginOptions.ConnectionSettings.Remote, a.gitInfo.BranchName))
|
return "", fmt.Errorf("Failed to run `git log -1 --format=%%H " + fmt.Sprintf("%s/%s", remote, branchName))
|
||||||
}
|
}
|
||||||
|
|
||||||
commit := strings.TrimSpace(string(out))
|
commit := strings.TrimSpace(string(out))
|
||||||
219
cmd/app/git/git_test.go
Normal file
219
cmd/app/git/git_test.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeGitManager struct {
|
||||||
|
RemoteUrl string
|
||||||
|
BranchName string
|
||||||
|
ProjectName string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) RefreshProjectInfo(remote string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) GetCurrentBranchNameFromNativeGitCmd() (string, error) {
|
||||||
|
return f.BranchName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) GetLatestCommitOnRemote(remote string, branchName string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) GetProjectUrlFromNativeGitCmd(string) (url string, err error) {
|
||||||
|
return f.RemoteUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
desc string
|
||||||
|
branch string
|
||||||
|
projectName string
|
||||||
|
namespace string
|
||||||
|
remote string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractGitInfo_Success(t *testing.T) {
|
||||||
|
testCases := []TestCase{
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH under a single folder",
|
||||||
|
remote: "git@custom-gitlab.com:namespace-1/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH under a single folder without .git extension",
|
||||||
|
remote: "git@custom-gitlab.com:namespace-1/project-name",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH under one nested folder",
|
||||||
|
remote: "git@custom-gitlab.com:namespace-1/namespace-2/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1/namespace-2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH under two nested folders",
|
||||||
|
remote: "git@custom-gitlab.com:namespace-1/namespace-2/namespace-3/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1/namespace-2/namespace-3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH:// under a single folder",
|
||||||
|
remote: "ssh://custom-gitlab.com/namespace-1/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH:// under a single folder without .git extension",
|
||||||
|
remote: "ssh://custom-gitlab.com/namespace-1/project-name",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH:// under two nested folders",
|
||||||
|
remote: "ssh://custom-gitlab.com/namespace-1/namespace-2/namespace-3/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1/namespace-2/namespace-3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in SSH:// and have a custom port",
|
||||||
|
remote: "ssh://custom-gitlab.com:2222/namespace-1/project-name",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in HTTP and under a single folder without .git extension",
|
||||||
|
remote: "http://custom-gitlab.com/namespace-1/project-name",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in HTTPS and under a single folder",
|
||||||
|
remote: "https://custom-gitlab.com/namespace-1/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in HTTPS and under a nested folder",
|
||||||
|
remote: "https://custom-gitlab.com/namespace-1/namespace-2/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1/namespace-2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Project configured in HTTPS and under two nested folders",
|
||||||
|
remote: "https://custom-gitlab.com/namespace-1/namespace-2/namespace-3/project-name.git",
|
||||||
|
branch: "feature/abc",
|
||||||
|
projectName: "project-name",
|
||||||
|
namespace: "namespace-1/namespace-2/namespace-3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tC := range testCases {
|
||||||
|
t.Run(tC.desc, func(t *testing.T) {
|
||||||
|
g := FakeGitManager{
|
||||||
|
Namespace: tC.namespace,
|
||||||
|
ProjectName: tC.projectName,
|
||||||
|
BranchName: tC.branch,
|
||||||
|
RemoteUrl: tC.remote,
|
||||||
|
}
|
||||||
|
data, err := NewGitData(tC.remote, g)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("No error was expected, got %s", err)
|
||||||
|
}
|
||||||
|
if data.RemoteUrl != tC.remote {
|
||||||
|
t.Errorf("\nExpected Remote URL: %s\nActual: %s", tC.remote, data.RemoteUrl)
|
||||||
|
}
|
||||||
|
if data.BranchName != tC.branch {
|
||||||
|
t.Errorf("\nExpected Branch Name: %s\nActual: %s", tC.branch, data.BranchName)
|
||||||
|
}
|
||||||
|
if data.ProjectName != tC.projectName {
|
||||||
|
t.Errorf("\nExpected Project Name: %s\nActual: %s", tC.projectName, data.ProjectName)
|
||||||
|
}
|
||||||
|
if data.Namespace != tC.namespace {
|
||||||
|
t.Errorf("\nExpected Namespace: %s\nActual: %s", tC.namespace, data.Namespace)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FailTestCase struct {
|
||||||
|
desc string
|
||||||
|
errMsg string
|
||||||
|
expectedErr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingUrlManager struct {
|
||||||
|
errMsg string
|
||||||
|
FakeGitManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f failingUrlManager) GetProjectUrlFromNativeGitCmd(string) (string, error) {
|
||||||
|
return "", errors.New(f.errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractGitInfo_FailToGetProjectRemoteUrl(t *testing.T) {
|
||||||
|
tC := FailTestCase{
|
||||||
|
desc: "Error returned by function to get the project remote url",
|
||||||
|
errMsg: "Some error",
|
||||||
|
expectedErr: "Could not get project Url: Some error",
|
||||||
|
}
|
||||||
|
t.Run(tC.desc, func(t *testing.T) {
|
||||||
|
g := failingUrlManager{
|
||||||
|
errMsg: tC.errMsg,
|
||||||
|
}
|
||||||
|
_, err := NewGitData("", g)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error, got none")
|
||||||
|
}
|
||||||
|
if err.Error() != tC.expectedErr {
|
||||||
|
t.Errorf("\nExpected: %s\nActual: %s", tC.expectedErr, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingBranchManager struct {
|
||||||
|
errMsg string
|
||||||
|
FakeGitManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f failingBranchManager) GetCurrentBranchNameFromNativeGitCmd() (string, error) {
|
||||||
|
return "", errors.New(f.errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractGitInfo_FailToGetCurrentBranchName(t *testing.T) {
|
||||||
|
tC := FailTestCase{
|
||||||
|
desc: "Error returned by function to get the project remote url",
|
||||||
|
errMsg: "Some error",
|
||||||
|
expectedErr: "Failed to get current branch: Some error",
|
||||||
|
}
|
||||||
|
t.Run(tC.desc, func(t *testing.T) {
|
||||||
|
g := failingBranchManager{
|
||||||
|
FakeGitManager: FakeGitManager{
|
||||||
|
RemoteUrl: "git@custom-gitlab.com:namespace-1/project-name.git",
|
||||||
|
},
|
||||||
|
errMsg: tC.errMsg,
|
||||||
|
}
|
||||||
|
_, err := NewGitData("", g)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error, got none")
|
||||||
|
}
|
||||||
|
if err.Error() != tC.expectedErr {
|
||||||
|
t.Errorf("\nExpected: %s\nActual: %s", tC.expectedErr, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,8 +12,17 @@ type InfoResponse struct {
|
|||||||
Info *gitlab.MergeRequest `json:"info"`
|
Info *gitlab.MergeRequest `json:"info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergeRequestGetter interface {
|
||||||
|
GetMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type infoService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestGetter
|
||||||
|
}
|
||||||
|
|
||||||
/* infoHandler fetches infomation about the current git project. The data returned here is used in many other API calls */
|
/* infoHandler fetches infomation about the current git project. The data returned here is used in many other API calls */
|
||||||
func (a *Api) infoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a infoService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
49
cmd/app/info_test.go
Normal file
49
cmd/app/info_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMergeRequestGetter struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeMergeRequestGetter) GetMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gitlab.MergeRequest{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoHandler(t *testing.T) {
|
||||||
|
t.Run("Returns normal information", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
|
||||||
|
svc := infoService{testProjectData, fakeMergeRequestGetter{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Merge requests retrieved")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-GET methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/info", nil)
|
||||||
|
svc := infoService{testProjectData, fakeMergeRequestGetter{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodGet)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
|
||||||
|
svc := infoService{testProjectData, fakeMergeRequestGetter{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
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)
|
||||||
|
svc := infoService{testProjectData, fakeMergeRequestGetter{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not get project info", "/mr/info")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JobTraceRequest struct {
|
type JobTraceRequest struct {
|
||||||
@@ -15,8 +18,17 @@ type JobTraceResponse struct {
|
|||||||
File string `json:"file"`
|
File string `json:"file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TraceFileGetter interface {
|
||||||
|
GetTraceFile(pid interface{}, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type traceFileService struct {
|
||||||
|
data
|
||||||
|
client TraceFileGetter
|
||||||
|
}
|
||||||
|
|
||||||
/* jobHandler returns a string that shows the output of a specific job run in a Gitlab pipeline */
|
/* jobHandler returns a string that shows the output of a specific job run in a Gitlab pipeline */
|
||||||
func (a *Api) jobHandler(w http.ResponseWriter, r *http.Request) {
|
func (a traceFileService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
71
cmd/app/job_test.go
Normal file
71
cmd/app/job_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeTraceFileGetter struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTraceFileData(t *testing.T, svc ServiceWithHandler, request *http.Request) JobTraceResponse {
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
svc.handler(res, request)
|
||||||
|
|
||||||
|
var data JobTraceResponse
|
||||||
|
err := json.Unmarshal(res.Body.Bytes(), &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeTraceFileGetter) GetTraceFile(pid interface{}, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
re := bytes.NewReader([]byte("Some data"))
|
||||||
|
return re, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// var jobId = 0
|
||||||
|
func TestJobHandler(t *testing.T) {
|
||||||
|
t.Run("Should read a job trace file", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/job", JobTraceRequest{})
|
||||||
|
client := fakeTraceFileGetter{}
|
||||||
|
svc := traceFileService{testProjectData, client}
|
||||||
|
data := getTraceFileData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Log file read")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.File, "Some data")
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-GET methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/job", JobTraceRequest{})
|
||||||
|
client := fakeTraceFileGetter{}
|
||||||
|
svc := traceFileService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodGet)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/job", JobTraceRequest{})
|
||||||
|
client := fakeTraceFileGetter{testBase{errFromGitlab: true}}
|
||||||
|
svc := traceFileService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not get trace file for job")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/job", JobTraceRequest{})
|
||||||
|
client := fakeTraceFileGetter{testBase{status: http.StatusSeeOther}}
|
||||||
|
svc := traceFileService{testProjectData, client}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not get trace file for job", "/job")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -28,8 +28,18 @@ type LabelsRequestResponse struct {
|
|||||||
Labels []Label `json:"labels"`
|
Labels []Label `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LabelManager interface {
|
||||||
|
UpdateMergeRequest(interface{}, int, *gitlab.UpdateMergeRequestOptions, ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
ListLabels(interface{}, *gitlab.ListLabelsOptions, ...gitlab.RequestOptionFunc) ([]*gitlab.Label, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type labelService struct {
|
||||||
|
data
|
||||||
|
client LabelManager
|
||||||
|
}
|
||||||
|
|
||||||
/* labelsHandler adds or removes labels from a merge request, and returns all labels for the current project */
|
/* labelsHandler adds or removes labels from a merge request, and returns all labels for the current project */
|
||||||
func (a *Api) labelHandler(w http.ResponseWriter, r *http.Request) {
|
func (a labelService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
a.getLabels(w, r)
|
a.getLabels(w, r)
|
||||||
@@ -42,7 +52,7 @@ func (a *Api) labelHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) getLabels(w http.ResponseWriter, r *http.Request) {
|
func (a labelService) getLabels(w http.ResponseWriter, _ *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
labels, res, err := a.client.ListLabels(a.projectInfo.ProjectId, &gitlab.ListLabelsOptions{})
|
labels, res, err := a.client.ListLabels(a.projectInfo.ProjectId, &gitlab.ListLabelsOptions{})
|
||||||
@@ -82,7 +92,7 @@ func (a *Api) getLabels(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) updateLabels(w http.ResponseWriter, r *http.Request) {
|
func (a labelService) updateLabels(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Contains[T comparable](elems []T, v T) bool {
|
||||||
|
for _, s := range elems {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type DiscussionsRequest struct {
|
type DiscussionsRequest struct {
|
||||||
Blacklist []string `json:"blacklist"`
|
Blacklist []string `json:"blacklist"`
|
||||||
}
|
}
|
||||||
@@ -37,11 +47,21 @@ func (n SortableDiscussions) Swap(i, j int) {
|
|||||||
n[i], n[j] = n[j], n[i]
|
n[i], n[j] = n[j], n[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscussionsLister interface {
|
||||||
|
ListMergeRequestDiscussions(pid interface{}, mergeRequest int, opt *gitlab.ListMergeRequestDiscussionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Discussion, *gitlab.Response, error)
|
||||||
|
ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type discussionsListerService struct {
|
||||||
|
data
|
||||||
|
client DiscussionsLister
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
listDiscussionsHandler lists all discusions for a given merge request, both those linked and unlinked to particular points in the code.
|
listDiscussionsHandler lists all discusions for a given merge request, both those linked and unlinked to particular points in the code.
|
||||||
The responses are sorted by date created, and blacklisted users are not included
|
The responses are sorted by date created, and blacklisted users are not included
|
||||||
*/
|
*/
|
||||||
func (a *Api) listDiscussionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a discussionsListerService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
||||||
@@ -86,7 +106,7 @@ func (a *Api) listDiscussionsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
var linkedDiscussions []*gitlab.Discussion
|
var linkedDiscussions []*gitlab.Discussion
|
||||||
|
|
||||||
for _, discussion := range discussions {
|
for _, discussion := range discussions {
|
||||||
if discussion.Notes == nil || len(discussion.Notes) == 0 || Contains(requestBody.Blacklist, discussion.Notes[0].Author.Username) > -1 {
|
if discussion.Notes == nil || len(discussion.Notes) == 0 || Contains(requestBody.Blacklist, discussion.Notes[0].Author.Username) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, note := range discussion.Notes {
|
for _, note := range discussion.Notes {
|
||||||
@@ -136,3 +156,60 @@ func (a *Api) listDiscussionsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fetches emojis for a set of notes and comments in parallel and returns a map of note IDs to their emojis.
|
||||||
|
Gitlab's API does not allow for fetching notes for an entire discussion thread so we have to do it per-note.
|
||||||
|
*/
|
||||||
|
func (a discussionsListerService) fetchEmojisForNotesAndComments(noteIDs []int) (map[int][]*gitlab.AwardEmoji, error) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
emojis := make(map[int][]*gitlab.AwardEmoji)
|
||||||
|
mu := &sync.Mutex{}
|
||||||
|
errs := make(chan error, len(noteIDs))
|
||||||
|
emojiChan := make(chan struct {
|
||||||
|
noteID int
|
||||||
|
emojis []*gitlab.AwardEmoji
|
||||||
|
}, len(noteIDs))
|
||||||
|
|
||||||
|
for _, noteID := range noteIDs {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(noteID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
emojis, _, err := a.client.ListMergeRequestAwardEmojiOnNote(a.projectInfo.ProjectId, a.projectInfo.MergeId, noteID, &gitlab.ListAwardEmojiOptions{})
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emojiChan <- struct {
|
||||||
|
noteID int
|
||||||
|
emojis []*gitlab.AwardEmoji
|
||||||
|
}{noteID, emojis}
|
||||||
|
}(noteID)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close the channels when all goroutines finish */
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errs)
|
||||||
|
close(emojiChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
/* Collect emojis */
|
||||||
|
for e := range emojiChan {
|
||||||
|
mu.Lock()
|
||||||
|
emojis[e.noteID] = e.emojis
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if any errors occurred */
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emojis, nil
|
||||||
|
}
|
||||||
113
cmd/app/list_discussions_test.go
Normal file
113
cmd/app/list_discussions_test.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeDiscussionsLister struct {
|
||||||
|
testBase
|
||||||
|
badEmojiResponse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDiscussionsLister) ListMergeRequestDiscussions(pid interface{}, mergeRequest int, opt *gitlab.ListMergeRequestDiscussionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Discussion, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
newer := now.Add(time.Second * 100)
|
||||||
|
|
||||||
|
type Author struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
State string `json:"state"`
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
WebURL string `json:"web_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
testListDiscussionsResponse := []*gitlab.Discussion{
|
||||||
|
{Notes: []*gitlab.Note{{CreatedAt: &now, Type: "DiffNote", Author: Author{Username: "hcramer"}}}},
|
||||||
|
{Notes: []*gitlab.Note{{CreatedAt: &newer, Type: "DiffNote", Author: Author{Username: "hcramer2"}}}},
|
||||||
|
}
|
||||||
|
return testListDiscussionsResponse, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDiscussionsLister) ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.badEmojiResponse {
|
||||||
|
return nil, nil, errors.New("Some error from emoji service")
|
||||||
|
}
|
||||||
|
return []*gitlab.AwardEmoji{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDiscussionsList(t *testing.T, svc ServiceWithHandler, request *http.Request) DiscussionsResponse {
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
svc.handler(res, request)
|
||||||
|
|
||||||
|
var data DiscussionsResponse
|
||||||
|
err := json.Unmarshal(res.Body.Bytes(), &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListDiscussions(t *testing.T) {
|
||||||
|
t.Run("Returns sorted discussions", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
||||||
|
svc := discussionsListerService{testProjectData, fakeDiscussionsLister{}}
|
||||||
|
data := getDiscussionsList(t, svc, request)
|
||||||
|
assert(t, data.Message, "Discussions retrieved")
|
||||||
|
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
||||||
|
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2") /* Sorting applied */
|
||||||
|
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Uses blacklist to filter unwanted authors", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{"hcramer"}})
|
||||||
|
svc := discussionsListerService{testProjectData, fakeDiscussionsLister{}}
|
||||||
|
data := getDiscussionsList(t, svc, request)
|
||||||
|
assert(t, data.SuccessResponse.Message, "Discussions retrieved")
|
||||||
|
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
||||||
|
assert(t, len(data.Discussions), 1)
|
||||||
|
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2")
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-GET methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/mr/discussions/list", DiscussionsRequest{})
|
||||||
|
svc := discussionsListerService{testProjectData, fakeDiscussionsLister{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
||||||
|
svc := discussionsListerService{testProjectData, fakeDiscussionsLister{testBase: testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not list discussions")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
||||||
|
svc := discussionsListerService{testProjectData, fakeDiscussionsLister{testBase: testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not list discussions", "/mr/discussions/list")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from emoji service", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
||||||
|
svc := discussionsListerService{testProjectData, fakeDiscussionsLister{badEmojiResponse: true}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Could not fetch emojis")
|
||||||
|
assert(t, data.Details, "Some error from emoji service")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,8 +12,17 @@ type ProjectMembersResponse struct {
|
|||||||
ProjectMembers []*gitlab.ProjectMember
|
ProjectMembers []*gitlab.ProjectMember
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProjectMemberLister interface {
|
||||||
|
ListAllProjectMembers(pid interface{}, opt *gitlab.ListProjectMembersOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectMember, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type projectMemberService struct {
|
||||||
|
data
|
||||||
|
client ProjectMemberLister
|
||||||
|
}
|
||||||
|
|
||||||
/* projectMembersHandler returns all members of the current Gitlab project */
|
/* projectMembersHandler returns all members of the current Gitlab project */
|
||||||
func (a *Api) projectMembersHandler(w http.ResponseWriter, r *http.Request) {
|
func (a projectMemberService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
48
cmd/app/members_test.go
Normal file
48
cmd/app/members_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMemberLister struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeMemberLister) ListAllProjectMembers(pid interface{}, opt *gitlab.ListProjectMembersOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectMember, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return []*gitlab.ProjectMember{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMembersHandler(t *testing.T) {
|
||||||
|
t.Run("Returns project members", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/project/members", nil)
|
||||||
|
svc := projectMemberService{testProjectData, fakeMemberLister{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Project members retrieved")
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-GET methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/project/members", nil)
|
||||||
|
svc := projectMemberService{testProjectData, fakeMemberLister{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodGet)
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/project/members", nil)
|
||||||
|
svc := projectMemberService{testProjectData, fakeMemberLister{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not retrieve project members")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/project/members", nil)
|
||||||
|
svc := projectMemberService{testProjectData, fakeMemberLister{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not retrieve project members", "/project/members")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -14,8 +14,17 @@ type AcceptMergeRequestRequest struct {
|
|||||||
DeleteBranch bool `json:"delete_branch"`
|
DeleteBranch bool `json:"delete_branch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergeRequestAccepter interface {
|
||||||
|
AcceptMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeRequestAccepterService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestAccepter
|
||||||
|
}
|
||||||
|
|
||||||
/* acceptAndMergeHandler merges a given merge request into the target branch */
|
/* acceptAndMergeHandler merges a given merge request into the target branch */
|
||||||
func (a *Api) acceptAndMergeHandler(w http.ResponseWriter, r *http.Request) {
|
func (a mergeRequestAccepterService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
50
cmd/app/merge_mr_test.go
Normal file
50
cmd/app/merge_mr_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMergeRequestAccepter struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeMergeRequestAccepter) AcceptMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gitlab.MergeRequest{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcceptAndMergeHandler(t *testing.T) {
|
||||||
|
var testAcceptMergeRequestPayload = AcceptMergeRequestRequest{Squash: false, SquashMessage: "Squash me!", DeleteBranch: false}
|
||||||
|
t.Run("Accepts and merges a merge request", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/merge", testAcceptMergeRequestPayload)
|
||||||
|
svc := mergeRequestAccepterService{testProjectData, fakeMergeRequestAccepter{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "MR merged successfully")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-POST methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPut, "/mr/merge", testAcceptMergeRequestPayload)
|
||||||
|
svc := mergeRequestAccepterService{testProjectData, fakeMergeRequestAccepter{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodPost)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/merge", testAcceptMergeRequestPayload)
|
||||||
|
svc := mergeRequestAccepterService{testProjectData, fakeMergeRequestAccepter{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not merge MR")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/merge", testAcceptMergeRequestPayload)
|
||||||
|
svc := mergeRequestAccepterService{testProjectData, fakeMergeRequestAccepter{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not merge MR", "/mr/merge")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListMergeRequestRequest struct {
|
|
||||||
Label []string `json:"label"`
|
|
||||||
NotLabel []string `json:"notlabel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListMergeRequestResponse struct {
|
type ListMergeRequestResponse struct {
|
||||||
SuccessResponse
|
SuccessResponse
|
||||||
MergeRequests []*gitlab.MergeRequest `json:"merge_requests"`
|
MergeRequests []*gitlab.MergeRequest `json:"merge_requests"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) mergeRequestsHandler(w http.ResponseWriter, r *http.Request) {
|
type MergeRequestLister interface {
|
||||||
|
ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeRequestListerService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestLister
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a mergeRequestListerService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
||||||
@@ -35,24 +38,25 @@ func (a *Api) mergeRequestsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
var listMergeRequestRequest ListMergeRequestRequest
|
var listMergeRequestRequest gitlab.ListProjectMergeRequestsOptions
|
||||||
err = json.Unmarshal(body, &listMergeRequestRequest)
|
err = json.Unmarshal(body, &listMergeRequestRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Could not read JSON from request", http.StatusBadRequest)
|
handleError(w, err, "Could not read JSON from request", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
options := gitlab.ListProjectMergeRequestsOptions{
|
if listMergeRequestRequest.State == nil {
|
||||||
Scope: gitlab.Ptr("all"),
|
listMergeRequestRequest.State = gitlab.Ptr("opened")
|
||||||
State: gitlab.Ptr("opened"),
|
|
||||||
Labels: (*gitlab.LabelOptions)(&listMergeRequestRequest.Label),
|
|
||||||
NotLabels: (*gitlab.LabelOptions)(&listMergeRequestRequest.NotLabel),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeRequests, res, err := a.client.ListProjectMergeRequests(a.projectInfo.ProjectId, &options)
|
if listMergeRequestRequest.Scope == nil {
|
||||||
|
listMergeRequestRequest.Scope = gitlab.Ptr("all")
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeRequests, res, err := a.client.ListProjectMergeRequests(a.projectInfo.ProjectId, &listMergeRequestRequest)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, fmt.Errorf("Failed to list merge requests: %w", err), "Failed to list merge requests", http.StatusInternalServerError)
|
handleError(w, err, "Failed to list merge requests", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
162
cmd/app/merge_requests_by_username.go
Normal file
162
cmd/app/merge_requests_by_username.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MergeRequestListerByUsername interface {
|
||||||
|
ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeRequestListerByUsernameService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestListerByUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
type MergeRequestByUsernameRequest struct {
|
||||||
|
UserId int `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a mergeRequestListerByUsernameService) handler(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 request MergeRequestByUsernameRequest
|
||||||
|
err = json.Unmarshal(body, &request)
|
||||||
|
if err != nil {
|
||||||
|
handleError(w, err, "Could not read JSON from request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Username == "" {
|
||||||
|
handleError(w, errors.New("username is a required payload field"), "username is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.UserId == 0 {
|
||||||
|
handleError(w, errors.New("user_id is a required payload field"), "user_id is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.State == "" {
|
||||||
|
request.State = "opened"
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads := []gitlab.ListProjectMergeRequestsOptions{
|
||||||
|
{
|
||||||
|
AuthorUsername: gitlab.Ptr(request.Username),
|
||||||
|
State: gitlab.Ptr(request.State),
|
||||||
|
Scope: gitlab.Ptr("all"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ReviewerUsername: gitlab.Ptr(request.Username),
|
||||||
|
State: gitlab.Ptr(request.State),
|
||||||
|
Scope: gitlab.Ptr("all"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AssigneeID: gitlab.AssigneeID(request.UserId),
|
||||||
|
State: gitlab.Ptr(request.State),
|
||||||
|
Scope: gitlab.Ptr("all"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiResponse struct {
|
||||||
|
mrs []*gitlab.MergeRequest
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
mrChan := make(chan apiResponse, len(payloads))
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(mrChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, payload := range payloads {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(p gitlab.ListProjectMergeRequestsOptions) {
|
||||||
|
defer wg.Done()
|
||||||
|
mrs, err := a.getMrs(&p)
|
||||||
|
mrChan <- apiResponse{mrs, err}
|
||||||
|
}(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergeRequests []*gitlab.MergeRequest
|
||||||
|
existingIds := make(map[int]bool)
|
||||||
|
var errs []error
|
||||||
|
for res := range mrChan {
|
||||||
|
if res.err != nil {
|
||||||
|
errs = append(errs, res.err)
|
||||||
|
} else {
|
||||||
|
for _, mr := range res.mrs {
|
||||||
|
if !existingIds[mr.ID] {
|
||||||
|
mergeRequests = append(mergeRequests, mr)
|
||||||
|
existingIds[mr.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
combinedErr := ""
|
||||||
|
for _, err := range errs {
|
||||||
|
combinedErr += err.Error() + "; "
|
||||||
|
}
|
||||||
|
handleError(w, errors.New(combinedErr), "An error occurred", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mergeRequests) == 0 {
|
||||||
|
handleError(w, fmt.Errorf("%s did not have any MRs", request.Username), "No MRs found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
response := ListMergeRequestResponse{
|
||||||
|
SuccessResponse: SuccessResponse{
|
||||||
|
Message: fmt.Sprintf("Merge requests fetched for %s", request.Username),
|
||||||
|
Status: http.StatusOK,
|
||||||
|
},
|
||||||
|
MergeRequests: mergeRequests,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewEncoder(w).Encode(response)
|
||||||
|
if err != nil {
|
||||||
|
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a mergeRequestListerByUsernameService) getMrs(payload *gitlab.ListProjectMergeRequestsOptions) ([]*gitlab.MergeRequest, error) {
|
||||||
|
mrs, res, err := a.client.ListProjectMergeRequests(a.projectInfo.ProjectId, payload)
|
||||||
|
if err != nil {
|
||||||
|
return []*gitlab.MergeRequest{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode >= 300 {
|
||||||
|
return []*gitlab.MergeRequest{}, GenericError{endpoint: "/merge_requests_by_username"}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
return mrs, err
|
||||||
|
}
|
||||||
87
cmd/app/merge_requests_by_username_test.go
Normal file
87
cmd/app/merge_requests_by_username_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMergeRequestListerByUsername struct {
|
||||||
|
testBase
|
||||||
|
emptyResponse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeMergeRequestListerByUsername) ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.emptyResponse {
|
||||||
|
return []*gitlab.MergeRequest{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*gitlab.MergeRequest{{IID: 10}}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListMergeRequestByUsername(t *testing.T) {
|
||||||
|
var testListMrsByUsernamePayload = MergeRequestByUsernameRequest{Username: "hcramer", UserId: 1234, State: "opened"}
|
||||||
|
t.Run("Gets merge requests by username", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests_by_username", testListMrsByUsernamePayload)
|
||||||
|
svc := mergeRequestListerByUsernameService{testProjectData, fakeMergeRequestListerByUsername{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Merge requests fetched for hcramer")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle no merge requests", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests_by_username", testListMrsByUsernamePayload)
|
||||||
|
svc := mergeRequestListerByUsernameService{testProjectData, fakeMergeRequestListerByUsername{emptyResponse: true}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "No MRs found")
|
||||||
|
assert(t, data.Details, "hcramer did not have any MRs")
|
||||||
|
assert(t, data.Status, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should require username", func(t *testing.T) {
|
||||||
|
missingUsernamePayload := testListMrsByUsernamePayload
|
||||||
|
missingUsernamePayload.Username = ""
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests_by_username", missingUsernamePayload)
|
||||||
|
svc := mergeRequestListerByUsernameService{testProjectData, fakeMergeRequestListerByUsername{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "username is required")
|
||||||
|
assert(t, data.Details, "username is a required payload field")
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should require User ID for assignee call", func(t *testing.T) {
|
||||||
|
missingUsernamePayload := testListMrsByUsernamePayload
|
||||||
|
missingUsernamePayload.UserId = 0
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests_by_username", missingUsernamePayload)
|
||||||
|
svc := mergeRequestListerByUsernameService{testProjectData, fakeMergeRequestListerByUsername{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "user_id is required")
|
||||||
|
assert(t, data.Details, "user_id is a required payload field")
|
||||||
|
assert(t, data.Status, http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle error from Gitlab", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests_by_username", testListMrsByUsernamePayload)
|
||||||
|
svc := mergeRequestListerByUsernameService{testProjectData, fakeMergeRequestListerByUsername{testBase: testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "An error occurred")
|
||||||
|
assert(t, data.Details, strings.Repeat("Some error from Gitlab; ", 3))
|
||||||
|
assert(t, data.Status, http.StatusInternalServerError)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200 from Gitlab", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests_by_username", testListMrsByUsernamePayload)
|
||||||
|
svc := mergeRequestListerByUsernameService{testProjectData, fakeMergeRequestListerByUsername{testBase: testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "An error occurred")
|
||||||
|
assert(t, data.Details, strings.Repeat("An error occurred on the /merge_requests_by_username endpoint; ", 3))
|
||||||
|
assert(t, data.Status, http.StatusInternalServerError)
|
||||||
|
})
|
||||||
|
}
|
||||||
58
cmd/app/merge_requests_test.go
Normal file
58
cmd/app/merge_requests_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMergeRequestLister struct {
|
||||||
|
testBase
|
||||||
|
emptyResponse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeMergeRequestLister) ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.emptyResponse {
|
||||||
|
return []*gitlab.MergeRequest{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*gitlab.MergeRequest{{IID: 10}}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeRequestHandler(t *testing.T) {
|
||||||
|
var testListMergeRequestsRequest = gitlab.ListProjectMergeRequestsOptions{}
|
||||||
|
t.Run("Should fetch merge requests", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
||||||
|
svc := mergeRequestListerService{testProjectData, fakeMergeRequestLister{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
assert(t, data.Message, "Merge requests fetched successfully")
|
||||||
|
})
|
||||||
|
t.Run("Handles error from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
||||||
|
svc := mergeRequestListerService{testProjectData, fakeMergeRequestLister{testBase: testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Failed to list merge requests")
|
||||||
|
assert(t, data.Status, http.StatusInternalServerError)
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
||||||
|
svc := mergeRequestListerService{testProjectData, fakeMergeRequestLister{testBase: testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Failed to list merge requests", "/merge_requests")
|
||||||
|
assert(t, data.Status, http.StatusSeeOther)
|
||||||
|
})
|
||||||
|
t.Run("Should handle not having any merge requests with 404", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
||||||
|
svc := mergeRequestListerService{testProjectData, fakeMergeRequestLister{emptyResponse: true}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "No merge requests found")
|
||||||
|
assert(t, data.Status, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,11 +27,23 @@ type GetPipelineAndJobsResponse struct {
|
|||||||
Pipeline PipelineWithJobs `json:"latest_pipeline"`
|
Pipeline PipelineWithJobs `json:"latest_pipeline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PipelineManager interface {
|
||||||
|
ListProjectPipelines(pid interface{}, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error)
|
||||||
|
ListPipelineJobs(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error)
|
||||||
|
RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelineService struct {
|
||||||
|
data
|
||||||
|
client PipelineManager
|
||||||
|
gitService git.GitManager
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
pipelineHandler fetches information about the current pipeline, and retriggers a pipeline run. For more detailed information
|
pipelineHandler fetches information about the current pipeline, and retriggers a pipeline run. For more detailed information
|
||||||
about a given job in a pipeline, see the jobHandler function
|
about a given job in a pipeline, see the jobHandler function
|
||||||
*/
|
*/
|
||||||
func (a *Api) pipelineHandler(w http.ResponseWriter, r *http.Request) {
|
func (a pipelineService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
a.GetPipelineAndJobs(w, r)
|
a.GetPipelineAndJobs(w, r)
|
||||||
@@ -44,7 +57,7 @@ func (a *Api) pipelineHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Gets the latest pipeline for a given commit, returns an error if there is no pipeline */
|
/* Gets the latest pipeline for a given commit, returns an error if there is no pipeline */
|
||||||
func (a *Api) GetLastPipeline(commit string) (*gitlab.PipelineInfo, error) {
|
func (a pipelineService) GetLastPipeline(commit string) (*gitlab.PipelineInfo, error) {
|
||||||
|
|
||||||
l := &gitlab.ListProjectPipelinesOptions{
|
l := &gitlab.ListProjectPipelinesOptions{
|
||||||
SHA: gitlab.Ptr(commit),
|
SHA: gitlab.Ptr(commit),
|
||||||
@@ -69,10 +82,10 @@ func (a *Api) GetLastPipeline(commit string) (*gitlab.PipelineInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Gets the latest pipeline and job information for the current branch */
|
/* Gets the latest pipeline and job information for the current branch */
|
||||||
func (a *Api) GetPipelineAndJobs(w http.ResponseWriter, r *http.Request) {
|
func (a pipelineService) GetPipelineAndJobs(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
commit, err := a.gitInfo.GetLatestCommitOnRemote(a)
|
commit, err := a.gitService.GetLatestCommitOnRemote(pluginOptions.ConnectionSettings.Remote, a.gitInfo.BranchName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Error getting commit on remote branch", http.StatusInternalServerError)
|
handleError(w, err, "Error getting commit on remote branch", http.StatusInternalServerError)
|
||||||
@@ -82,7 +95,7 @@ func (a *Api) GetPipelineAndJobs(w http.ResponseWriter, r *http.Request) {
|
|||||||
pipeline, err := a.GetLastPipeline(commit)
|
pipeline, err := a.GetLastPipeline(commit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, fmt.Sprintf("Gitlab failed to get latest pipeline for %s branch", a.gitInfo.BranchName), http.StatusInternalServerError)
|
handleError(w, err, fmt.Sprintf("Failed to get latest pipeline for %s branch", a.gitInfo.BranchName), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +134,7 @@ func (a *Api) GetPipelineAndJobs(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) RetriggerPipeline(w http.ResponseWriter, r *http.Request) {
|
func (a pipelineService) RetriggerPipeline(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
id := strings.TrimPrefix(r.URL.Path, "/pipeline/trigger/")
|
id := strings.TrimPrefix(r.URL.Path, "/pipeline/trigger/")
|
||||||
86
cmd/app/pipeline_test.go
Normal file
86
cmd/app/pipeline_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakePipelineManager struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakePipelineManager) ListProjectPipelines(pid interface{}, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return []*gitlab.PipelineInfo{{ID: 1234}}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakePipelineManager) ListPipelineJobs(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return []*gitlab.Job{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakePipelineManager) RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &gitlab.Pipeline{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPipelineGetter(t *testing.T) {
|
||||||
|
t.Run("Gets all pipeline jobs", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{}, FakeGitManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Pipeline retrieved")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Disallows non-GET, non-POST methods", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPatch, "/pipeline", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{}, FakeGitManager{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkBadMethod(t, data, http.MethodGet, http.MethodPost)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{testBase{errFromGitlab: true}}, FakeGitManager{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Failed to get latest pipeline for some-branch branch")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{testBase: testBase{status: http.StatusSeeOther}}, FakeGitManager{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Failed to get latest pipeline for some-branch branch") // Expected, we treat this as an error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPipelineTrigger(t *testing.T) {
|
||||||
|
t.Run("Retriggers pipeline", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/pipeline/trigger/3", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{}, FakeGitManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Pipeline retriggered")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/pipeline/trigger/3", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{testBase{errFromGitlab: true}}, FakeGitManager{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not retrigger pipeline")
|
||||||
|
})
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/pipeline/trigger/3", nil)
|
||||||
|
svc := pipelineService{testProjectData, fakePipelineManager{testBase: testBase{status: http.StatusSeeOther}}, FakeGitManager{}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not retrigger pipeline", "/pipeline")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -20,8 +20,17 @@ type ReplyResponse struct {
|
|||||||
Note *gitlab.Note `json:"note"`
|
Note *gitlab.Note `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReplyManager interface {
|
||||||
|
AddMergeRequestDiscussionNote(interface{}, int, string, *gitlab.AddMergeRequestDiscussionNoteOptions, ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type replyService struct {
|
||||||
|
data
|
||||||
|
client ReplyManager
|
||||||
|
}
|
||||||
|
|
||||||
/* replyHandler sends a reply to a note or comment */
|
/* replyHandler sends a reply to a note or comment */
|
||||||
func (a *Api) replyHandler(w http.ResponseWriter, r *http.Request) {
|
func (a replyService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
||||||
45
cmd/app/reply_test.go
Normal file
45
cmd/app/reply_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeReplyManager struct {
|
||||||
|
testBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeReplyManager) AddMergeRequestDiscussionNote(interface{}, int, string, *gitlab.AddMergeRequestDiscussionNoteOptions, ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error) {
|
||||||
|
resp, err := f.handleGitlabError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gitlab.Note{}, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplyHandler(t *testing.T) {
|
||||||
|
var testReplyRequest = ReplyRequest{DiscussionId: "abc123", Reply: "Some Reply", IsDraft: false}
|
||||||
|
t.Run("Sends a reply", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/reply", testReplyRequest)
|
||||||
|
svc := replyService{testProjectData, fakeReplyManager{}}
|
||||||
|
data := getSuccessData(t, svc, request)
|
||||||
|
assert(t, data.Message, "Replied to comment")
|
||||||
|
assert(t, data.Status, http.StatusOK)
|
||||||
|
})
|
||||||
|
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/reply", testReplyRequest)
|
||||||
|
svc := replyService{testProjectData, fakeReplyManager{testBase{errFromGitlab: true}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkErrorFromGitlab(t, data, "Could not leave reply")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
||||||
|
request := makeRequest(t, http.MethodPost, "/mr/reply", testReplyRequest)
|
||||||
|
svc := replyService{testProjectData, fakeReplyManager{testBase{status: http.StatusSeeOther}}}
|
||||||
|
data := getFailData(t, svc, request)
|
||||||
|
checkNon200(t, data, "Could not leave reply", "/mr/reply")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -14,8 +14,17 @@ type DiscussionResolveRequest struct {
|
|||||||
Resolved bool `json:"resolved"`
|
Resolved bool `json:"resolved"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscussionResolver interface {
|
||||||
|
ResolveMergeRequestDiscussion(pid interface{}, mergeRequest int, discussion string, opt *gitlab.ResolveMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type discussionsResolutionService struct {
|
||||||
|
data
|
||||||
|
client DiscussionResolver
|
||||||
|
}
|
||||||
|
|
||||||
/* discussionsResolveHandler sets a discussion to be "resolved" or not resolved, depending on the payload */
|
/* discussionsResolveHandler sets a discussion to be "resolved" or not resolved, depending on the payload */
|
||||||
func (a *Api) discussionsResolveHandler(w http.ResponseWriter, r *http.Request) {
|
func (a discussionsResolutionService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPut)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPut)
|
||||||
30
cmd/app/response_types.go
Normal file
30
cmd/app/response_types.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Details string `json:"details"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SuccessResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericError struct {
|
||||||
|
endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e GenericError) Error() string {
|
||||||
|
return fmt.Sprintf("An error occurred on the %s endpoint", e.endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidRequestError struct{}
|
||||||
|
|
||||||
|
func (e InvalidRequestError) Error() string {
|
||||||
|
return "Invalid request type"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -22,8 +22,17 @@ type ReviewersRequestResponse struct {
|
|||||||
Reviewers []int `json:"reviewers"`
|
Reviewers []int `json:"reviewers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergeRequestUpdater interface {
|
||||||
|
UpdateMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type reviewerService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestUpdater
|
||||||
|
}
|
||||||
|
|
||||||
/* reviewersHandler adds or removes reviewers from an MR */
|
/* reviewersHandler adds or removes reviewers from an MR */
|
||||||
func (a *Api) reviewersHandler(w http.ResponseWriter, r *http.Request) {
|
func (a reviewerService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPut)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPut)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,11 +12,20 @@ type RevisionsResponse struct {
|
|||||||
Revisions []*gitlab.MergeRequestDiffVersion
|
Revisions []*gitlab.MergeRequestDiffVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RevisionsGetter interface {
|
||||||
|
GetMergeRequestDiffVersions(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestDiffVersionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequestDiffVersion, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type revisionsService struct {
|
||||||
|
data
|
||||||
|
client RevisionsGetter
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
revisionsHandler gets revision information about the current MR. This data is not used directly but is
|
revisionsHandler gets revision information about the current MR. This data is not used directly but is
|
||||||
a precursor API call for other functionality
|
a precursor API call for other functionality
|
||||||
*/
|
*/
|
||||||
func (a *Api) revisionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a revisionsService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MergeRequestRevoker interface {
|
||||||
|
UnapproveMergeRequest(interface{}, int, ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeRequestRevokerService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestRevoker
|
||||||
|
}
|
||||||
|
|
||||||
/* revokeHandler revokes approval for the current merge request */
|
/* revokeHandler revokes approval for the current merge request */
|
||||||
func (a *Api) revokeHandler(w http.ResponseWriter, r *http.Request) {
|
func (a mergeRequestRevokerService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodPost)
|
||||||
199
cmd/app/server.go
Normal file
199
cmd/app/server.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
|
||||||
|
"github.com/xanzy/go-gitlab"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
startSever starts the server and runs concurrent goroutines
|
||||||
|
to handle potential shutdown requests and incoming HTTP requests.
|
||||||
|
*/
|
||||||
|
func StartServer(client *Client, projectInfo *ProjectInfo, GitInfo git.GitData) {
|
||||||
|
|
||||||
|
s := shutdown{
|
||||||
|
sigCh: make(chan os.Signal, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
fr := attachmentReader{}
|
||||||
|
r := CreateRouter(
|
||||||
|
client,
|
||||||
|
projectInfo,
|
||||||
|
s,
|
||||||
|
func(a *data) error { a.projectInfo = projectInfo; return nil },
|
||||||
|
func(a *data) error { a.gitInfo = &GitInfo; return nil },
|
||||||
|
func(a *data) error { err := attachEmojis(a, fr); return err },
|
||||||
|
)
|
||||||
|
l := createListener()
|
||||||
|
|
||||||
|
server := &http.Server{Handler: r}
|
||||||
|
|
||||||
|
/* Starts the Go server */
|
||||||
|
go func() {
|
||||||
|
err := server.Serve(l)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Server did not respond: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
port := l.Addr().(*net.TCPAddr).Port
|
||||||
|
err := checkServer(port)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Server did not respond: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This print is detected by the Lua code */
|
||||||
|
fmt.Println("Server started on port: ", port)
|
||||||
|
|
||||||
|
/* Handles shutdown requests */
|
||||||
|
s.WatchForShutdown(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CreateRouterAndApi wires up the router and attaches all handlers to their respective routes. It also
|
||||||
|
iterates over all option functions to configure API fields such as the project information and default
|
||||||
|
file reader functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
type data struct {
|
||||||
|
projectInfo *ProjectInfo
|
||||||
|
gitInfo *git.GitData
|
||||||
|
emojiMap EmojiMap
|
||||||
|
}
|
||||||
|
|
||||||
|
type optFunc func(a *data) error
|
||||||
|
|
||||||
|
func CreateRouter(gitlabClient *Client, projectInfo *ProjectInfo, s ShutdownHandler, optFuncs ...optFunc) *http.ServeMux {
|
||||||
|
m := http.NewServeMux()
|
||||||
|
|
||||||
|
d := data{
|
||||||
|
projectInfo: &ProjectInfo{},
|
||||||
|
gitInfo: &git.GitData{},
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mutates the API struct as necessary with configuration functions */
|
||||||
|
for _, optFunc := range optFuncs {
|
||||||
|
err := optFunc(&d)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.HandleFunc("/mr/approve", withMr(mergeRequestApproverService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/comment", withMr(commentService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/merge", withMr(mergeRequestAccepterService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/discussions/list", withMr(discussionsListerService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/discussions/resolve", withMr(discussionsResolutionService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/info", withMr(infoService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/assignee", withMr(assigneesService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/summary", withMr(summaryService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/reviewer", withMr(reviewerService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/revisions", withMr(revisionsService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/reply", withMr(replyService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/label", withMr(labelService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/revoke", withMr(mergeRequestRevokerService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/awardable/note/", withMr(emojiService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/draft_notes/", withMr(draftNoteService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
m.HandleFunc("/mr/draft_notes/publish", withMr(draftNotePublisherService{d, gitlabClient}, d, gitlabClient))
|
||||||
|
|
||||||
|
m.HandleFunc("/pipeline", pipelineService{d, gitlabClient, git.Git{}}.handler)
|
||||||
|
m.HandleFunc("/pipeline/trigger/", pipelineService{d, gitlabClient, git.Git{}}.handler)
|
||||||
|
m.HandleFunc("/users/me", meService{d, gitlabClient}.handler)
|
||||||
|
m.HandleFunc("/attachment", attachmentService{data: d, client: gitlabClient, fileReader: attachmentReader{}}.handler)
|
||||||
|
m.HandleFunc("/create_mr", mergeRequestCreatorService{d, gitlabClient}.handler)
|
||||||
|
m.HandleFunc("/job", traceFileService{d, gitlabClient}.handler)
|
||||||
|
m.HandleFunc("/project/members", projectMemberService{d, gitlabClient}.handler)
|
||||||
|
m.HandleFunc("/merge_requests", mergeRequestListerService{d, gitlabClient}.handler)
|
||||||
|
m.HandleFunc("/merge_requests_by_username", mergeRequestListerByUsernameService{d, gitlabClient}.handler)
|
||||||
|
|
||||||
|
m.HandleFunc("/shutdown", s.shutdownHandler)
|
||||||
|
m.Handle("/ping", http.HandlerFunc(pingHandler))
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to check whether the server has started yet */
|
||||||
|
func pingHandler(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintln(w, "pong")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checkServer pings the server repeatedly for 1 full second after startup in order to notify the plugin that the server is ready */
|
||||||
|
func checkServer(port int) error {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
resp, err := http.Get("http://localhost:" + fmt.Sprintf("%d", port) + "/ping")
|
||||||
|
if resp.StatusCode == 200 && err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("Could not start server!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates a TCP listener on the port specified by the user or a random port */
|
||||||
|
func createListener() (l net.Listener) {
|
||||||
|
addr := fmt.Sprintf("localhost:%d", pluginOptions.Port)
|
||||||
|
l, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceWithHandler interface {
|
||||||
|
handler(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* withMr is a Middlware that gets the current merge request ID and attaches it to the projectInfo */
|
||||||
|
func withMr(svc ServiceWithHandler, c data, client MergeRequestLister) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// If the merge request is already attached, skip the middleware logic
|
||||||
|
if c.projectInfo.MergeId == 0 {
|
||||||
|
options := gitlab.ListProjectMergeRequestsOptions{
|
||||||
|
Scope: gitlab.Ptr("all"),
|
||||||
|
SourceBranch: &c.gitInfo.BranchName,
|
||||||
|
TargetBranch: pluginOptions.ChosenTargetBranch,
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeRequests, _, err := client.ListProjectMergeRequests(c.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 {
|
||||||
|
err := fmt.Errorf("No merge requests found for branch '%s'", c.gitInfo.BranchName)
|
||||||
|
handleError(w, err, "No merge requests found", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mergeRequests) > 1 {
|
||||||
|
err := errors.New("Please call gitlab.choose_merge_request()")
|
||||||
|
handleError(w, err, "Multiple MRs found", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeIdInt := mergeRequests[0].IID
|
||||||
|
c.projectInfo.MergeId = mergeIdInt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the next handler if middleware succeeds
|
||||||
|
svc.handler(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type killer struct{}
|
type killer struct{}
|
||||||
@@ -14,12 +17,33 @@ func (k killer) String() string {
|
|||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShutdownHandler interface {
|
||||||
|
WatchForShutdown(server *http.Server)
|
||||||
|
shutdownHandler(w http.ResponseWriter, r *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type shutdown struct {
|
||||||
|
sigCh chan os.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s shutdown) WatchForShutdown(server *http.Server) {
|
||||||
|
/* Handles shutdown requests */
|
||||||
|
<-s.sigCh
|
||||||
|
err := server.Shutdown(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Server could not shut down gracefully: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ShutdownRequest struct {
|
type ShutdownRequest struct {
|
||||||
Restart bool `json:"restart"`
|
Restart bool `json:"restart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/* shutdownHandler will shutdown the HTTP server and exit the process by signaling to the shutdown channel */
|
/* shutdownHandler will shutdown the HTTP server and exit the process by signaling to the shutdown channel */
|
||||||
func (a *Api) shutdownHandler(w http.ResponseWriter, r *http.Request) {
|
func (s shutdown) shutdownHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.Header().Set("Allow", http.MethodPost)
|
w.Header().Set("Allow", http.MethodPost)
|
||||||
handleError(w, errors.New("Invalid request type"), "That request type is not allowed", http.StatusMethodNotAllowed)
|
handleError(w, errors.New("Invalid request type"), "That request type is not allowed", http.StatusMethodNotAllowed)
|
||||||
@@ -54,6 +78,6 @@ func (a *Api) shutdownHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
||||||
} else {
|
} else {
|
||||||
a.sigCh <- killer{}
|
s.sigCh <- killer{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -18,7 +18,12 @@ type SummaryUpdateResponse struct {
|
|||||||
MergeRequest *gitlab.MergeRequest `json:"mr"`
|
MergeRequest *gitlab.MergeRequest `json:"mr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) summaryHandler(w http.ResponseWriter, r *http.Request) {
|
type summaryService struct {
|
||||||
|
data
|
||||||
|
client MergeRequestUpdater
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a summaryService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -11,13 +11,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
|
||||||
"github.com/xanzy/go-gitlab"
|
"github.com/xanzy/go-gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
The FakeHandlerClient is used to create a fake gitlab client for testing our handlers, where the gitlab APIs are all mocked depending on what is provided during the variable initialization, so that we can simulate different responses from Gitlab
|
|
||||||
*/
|
|
||||||
|
|
||||||
var errorFromGitlab = errors.New("Some error from Gitlab")
|
var errorFromGitlab = errors.New("Some error from Gitlab")
|
||||||
|
|
||||||
/* The assert function is a helper function used to check two comparables */
|
/* The assert function is a helper function used to check two comparables */
|
||||||
@@ -49,31 +46,63 @@ func makeRequest(t *testing.T, method string, endpoint string, body any) *http.R
|
|||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Serves and parses the JSON from an endpoint into the given type */
|
|
||||||
func serveRequest[T any](t *testing.T, s *http.ServeMux, request *http.Request, i T) *T {
|
|
||||||
t.Helper()
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
s.ServeHTTP(recorder, request)
|
|
||||||
result := recorder.Result()
|
|
||||||
decoder := json.NewDecoder(result.Body)
|
|
||||||
err := decoder.Decode(&i)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &i
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make response makes a simple response value with the right status code */
|
/* Make response makes a simple response value with the right status code */
|
||||||
func makeResponse(status int) *gitlab.Response {
|
func makeResponse(status int) *gitlab.Response {
|
||||||
return &gitlab.Response{
|
return &gitlab.Response{
|
||||||
Response: &http.Response{
|
Response: &http.Response{
|
||||||
StatusCode: status,
|
StatusCode: status,
|
||||||
|
Body: http.NoBody,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var testProjectData = data{
|
||||||
|
projectInfo: &ProjectInfo{},
|
||||||
|
gitInfo: &git.GitData{
|
||||||
|
BranchName: "some-branch",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSuccessData(t *testing.T, svc ServiceWithHandler, request *http.Request) SuccessResponse {
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
svc.handler(res, request)
|
||||||
|
|
||||||
|
var data SuccessResponse
|
||||||
|
err := json.Unmarshal(res.Body.Bytes(), &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFailData(t *testing.T, svc ServiceWithHandler, request *http.Request) ErrorResponse {
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
svc.handler(res, request)
|
||||||
|
|
||||||
|
var data ErrorResponse
|
||||||
|
err := json.Unmarshal(res.Body.Bytes(), &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
type testBase struct {
|
||||||
|
errFromGitlab bool
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for easily mocking bad responses or errors from Gitlab
|
||||||
|
func (f *testBase) handleGitlabError() (*gitlab.Response, error) {
|
||||||
|
if f.errFromGitlab {
|
||||||
|
return nil, errorFromGitlab
|
||||||
|
}
|
||||||
|
if f.status == 0 {
|
||||||
|
f.status = 200
|
||||||
|
}
|
||||||
|
return makeResponse(f.status), nil
|
||||||
|
}
|
||||||
|
|
||||||
func checkErrorFromGitlab(t *testing.T, data ErrorResponse, msg string) {
|
func checkErrorFromGitlab(t *testing.T, data ErrorResponse, msg string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
assert(t, data.Status, http.StatusInternalServerError)
|
assert(t, data.Status, http.StatusInternalServerError)
|
||||||
@@ -95,3 +124,26 @@ func checkNon200(t *testing.T, data ErrorResponse, msg, endpoint string) {
|
|||||||
assert(t, data.Message, msg)
|
assert(t, data.Message, msg)
|
||||||
assert(t, data.Details, fmt.Sprintf("An error occurred on the %s endpoint", endpoint))
|
assert(t, data.Details, fmt.Sprintf("An error occurred on the %s endpoint", endpoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FakeGitManager struct {
|
||||||
|
RemoteUrl string
|
||||||
|
BranchName string
|
||||||
|
ProjectName string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) RefreshProjectInfo(remote string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) GetCurrentBranchNameFromNativeGitCmd() (string, error) {
|
||||||
|
return f.BranchName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) GetLatestCommitOnRemote(remote string, branchName string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeGitManager) GetProjectUrlFromNativeGitCmd(string) (url string, err error) {
|
||||||
|
return f.RemoteUrl, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,7 +12,16 @@ type UserResponse struct {
|
|||||||
User *gitlab.User `json:"user"`
|
User *gitlab.User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) meHandler(w http.ResponseWriter, r *http.Request) {
|
type MeGetter interface {
|
||||||
|
CurrentUser(options ...gitlab.RequestOptionFunc) (*gitlab.User, *gitlab.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type meService struct {
|
||||||
|
data
|
||||||
|
client MeGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a meService) handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApproveHandler(t *testing.T) {
|
|
||||||
t.Run("Approves merge request", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ApproveMergeRequest("", mock_main.MergeId, nil, nil).Return(&gitlab.MergeRequestApprovals{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/approve", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, SuccessResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Approved MR")
|
|
||||||
assert(t, data.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-POST method", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ApproveMergeRequest("", mock_main.MergeId, nil, nil).Return(&gitlab.MergeRequestApprovals{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/mr/approve", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkBadMethod(t, *data, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ApproveMergeRequest("", mock_main.MergeId, nil, nil).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/approve", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not approve merge request")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ApproveMergeRequest("", mock_main.MergeId, nil, nil).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/approve", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not approve merge request", "/mr/approve")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var updatePayload = AssigneeUpdateRequest{Ids: []int{1, 2}}
|
|
||||||
|
|
||||||
func TestAssigneeHandler(t *testing.T) {
|
|
||||||
t.Run("Updates assignees", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().UpdateMergeRequest("", mock_main.MergeId, gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/mr/assignee", updatePayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, AssigneeUpdateResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Assignees updated")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-PUT method", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().UpdateMergeRequest("", mock_main.MergeId, gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/assignee", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Status, http.StatusMethodNotAllowed)
|
|
||||||
assert(t, data.Details, "Invalid request type")
|
|
||||||
assert(t, data.Message, "Expected PUT")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().UpdateMergeRequest("", mock_main.MergeId, gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/mr/assignee", updatePayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Status, http.StatusInternalServerError)
|
|
||||||
assert(t, data.Message, "Could not modify merge request assignees")
|
|
||||||
assert(t, data.Details, "Some error from Gitlab")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().UpdateMergeRequest("", mock_main.MergeId, gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/mr/assignee", updatePayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Status, http.StatusSeeOther)
|
|
||||||
assert(t, data.Message, "Could not modify merge request assignees")
|
|
||||||
assert(t, data.Details, "An error occurred on the /mr/assignee endpoint")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
func withMockFileReader(a *Api) error {
|
|
||||||
reader := mock_main.MockAttachmentReader{}
|
|
||||||
a.fileReader = reader
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var reader = bytes.NewReader([]byte{})
|
|
||||||
var attachmentTestRequestData = AttachmentRequest{
|
|
||||||
FileName: "some_file_name",
|
|
||||||
FilePath: "some_file_path",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAttachmentHandler(t *testing.T) {
|
|
||||||
t.Run("Returns 200-status response after upload", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().UploadFile("", reader, attachmentTestRequestData.FileName).Return(&gitlab.ProjectFile{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/attachment", attachmentTestRequestData)
|
|
||||||
router, _ := CreateRouterAndApi(client, withMockFileReader)
|
|
||||||
data := serveRequest(t, router, request, AttachmentResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
assert(t, data.SuccessResponse.Message, "File uploaded successfully")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-POST method", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().UploadFile("", reader, attachmentTestRequestData.FileName).Return(&gitlab.ProjectFile{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/attachment", attachmentTestRequestData)
|
|
||||||
router, _ := CreateRouterAndApi(client, withMockFileReader)
|
|
||||||
data := serveRequest(t, router, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkBadMethod(t, *data, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().UploadFile("", reader, attachmentTestRequestData.FileName).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/attachment", attachmentTestRequestData)
|
|
||||||
router, _ := CreateRouterAndApi(client, withMockFileReader)
|
|
||||||
|
|
||||||
data := serveRequest(t, router, request, ErrorResponse{})
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not upload some_file_name to Gitlab")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().UploadFile("", reader, attachmentTestRequestData.FileName).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/attachment", attachmentTestRequestData)
|
|
||||||
router, _ := CreateRouterAndApi(client, withMockFileReader)
|
|
||||||
|
|
||||||
data := serveRequest(t, router, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not upload some_file_name to Gitlab", "/attachment")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testCommentCreationData = PostCommentRequest{
|
|
||||||
Comment: "Some comment",
|
|
||||||
}
|
|
||||||
|
|
||||||
var testCommentDeletionData = DeleteCommentRequest{
|
|
||||||
NoteId: 3,
|
|
||||||
DiscussionId: "abc123",
|
|
||||||
}
|
|
||||||
|
|
||||||
var testEditCommentData = EditCommentRequest{
|
|
||||||
Comment: "Some comment",
|
|
||||||
NoteId: 3,
|
|
||||||
DiscussionId: "abc123",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPostComment(t *testing.T) {
|
|
||||||
t.Run("Creates a new note (unlinked comment)", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().CreateMergeRequestDiscussion(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(&gitlab.Discussion{Notes: []*gitlab.Note{{}}}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, CommentResponse{})
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// Re-create comment creation data to avoid mutating this variable in other tests
|
|
||||||
testCommentCreationData := PostCommentRequest{
|
|
||||||
Comment: "Some comment",
|
|
||||||
PositionData: PositionData{
|
|
||||||
FileName: "file.txt",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().CreateMergeRequestDiscussion(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(&gitlab.Discussion{Notes: []*gitlab.Note{{}}}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
|
||||||
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, CommentResponse{})
|
|
||||||
assert(t, data.SuccessResponse.Message, "Comment created successfully")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().CreateMergeRequestDiscussion(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(nil, nil, errors.New("Some error from Gitlab"))
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not create discussion")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().CreateMergeRequestDiscussion(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/comment", testCommentCreationData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not create discussion", "/mr/comment")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteComment(t *testing.T) {
|
|
||||||
t.Run("Deletes a comment", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().DeleteMergeRequestDiscussionNote("", mock_main.MergeId, testCommentDeletionData.DiscussionId, testCommentDeletionData.NoteId).Return(makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodDelete, "/mr/comment", testCommentDeletionData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, CommentResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Comment deleted successfully")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().DeleteMergeRequestDiscussionNote("", mock_main.MergeId, testCommentDeletionData.DiscussionId, testCommentDeletionData.NoteId).Return(nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodDelete, "/mr/comment", testCommentDeletionData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not delete comment")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().DeleteMergeRequestDiscussionNote("", mock_main.MergeId, testCommentDeletionData.DiscussionId, testCommentDeletionData.NoteId).Return(makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodDelete, "/mr/comment", testCommentDeletionData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not delete comment", "/mr/comment")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEditComment(t *testing.T) {
|
|
||||||
t.Run("Edits a comment", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
opts := gitlab.UpdateMergeRequestDiscussionNoteOptions{
|
|
||||||
Body: gitlab.Ptr(testEditCommentData.Comment),
|
|
||||||
}
|
|
||||||
client.EXPECT().UpdateMergeRequestDiscussionNote("", mock_main.MergeId, testEditCommentData.DiscussionId, testEditCommentData.NoteId, &opts).Return(&gitlab.Note{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, "/mr/comment", testEditCommentData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, CommentResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Comment updated successfully")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().UpdateMergeRequestDiscussionNote("", mock_main.MergeId, testEditCommentData.DiscussionId, testEditCommentData.NoteId, gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, "/mr/comment", testEditCommentData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not update comment")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().UpdateMergeRequestDiscussionNote("", mock_main.MergeId, testEditCommentData.DiscussionId, testEditCommentData.NoteId, gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, "/mr/comment", testEditCommentData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not update comment", "/mr/comment")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testCreateMrRequestData = CreateMrRequest{
|
|
||||||
Title: "Some title",
|
|
||||||
Description: "Some description",
|
|
||||||
TargetBranch: "main",
|
|
||||||
DeleteBranch: false,
|
|
||||||
Squash: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateMr(t *testing.T) {
|
|
||||||
t.Run("Creates an MR", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().CreateMergeRequest("", gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/create_mr", testCreateMrRequestData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, SuccessResponse{})
|
|
||||||
assert(t, data.Message, "MR 'Some title' created")
|
|
||||||
assert(t, data.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-POST methods", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().CreateMergeRequest("", gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, "/create_mr", testCreateMrRequestData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkBadMethod(t, *data, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().CreateMergeRequest("", gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/create_mr", testCreateMrRequestData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not create MR")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().CreateMergeRequest("", gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/create_mr", testCreateMrRequestData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not create MR", "/create_mr")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles missing titles", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
|
|
||||||
missingTitleRequest := testCreateMrRequestData
|
|
||||||
missingTitleRequest.Title = ""
|
|
||||||
|
|
||||||
client.EXPECT().CreateMergeRequest("", gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/create_mr", missingTitleRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Status, http.StatusBadRequest)
|
|
||||||
assert(t, data.Message, "Could not create MR")
|
|
||||||
assert(t, data.Details, "Title cannot be empty")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles missing target branch", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
|
|
||||||
missingTitleRequest := testCreateMrRequestData
|
|
||||||
missingTitleRequest.TargetBranch = ""
|
|
||||||
|
|
||||||
client.EXPECT().CreateMergeRequest("", gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/create_mr", missingTitleRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Status, http.StatusBadRequest)
|
|
||||||
assert(t, data.Message, "Could not create MR")
|
|
||||||
assert(t, data.Details, "Target branch cannot be empty")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testPostDraftNoteRequestData = PostDraftNoteRequest{
|
|
||||||
Comment: "Some comment",
|
|
||||||
}
|
|
||||||
|
|
||||||
var testUpdateDraftNoteRequest = UpdateDraftNoteRequest{
|
|
||||||
Note: "Some new note",
|
|
||||||
}
|
|
||||||
|
|
||||||
var testDraftNotePublishRequest = DraftNotePublishRequest{
|
|
||||||
Note: 3,
|
|
||||||
PublishAll: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListDraftNotes(t *testing.T) {
|
|
||||||
t.Run("Lists all draft notes", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListDraftNotes("", mock_main.MergeId, gomock.Any()).Return([]*gitlab.DraftNote{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListDraftNotes("", mock_main.MergeId, gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/mr/draft_notes/", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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, errorFromGitlab.Error())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPostDraftNote(t *testing.T) {
|
|
||||||
t.Run("Posts new draft note", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().CreateDraftNote("", mock_main.MergeId, gomock.Any()).Return(&gitlab.DraftNote{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", testPostDraftNoteRequestData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().CreateDraftNote("", mock_main.MergeId, gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/", testPostDraftNoteRequestData)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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, errorFromGitlab.Error())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteDraftNote(t *testing.T) {
|
|
||||||
t.Run("Deletes draft note", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
urlId := 10
|
|
||||||
client.EXPECT().DeleteDraftNote("", mock_main.MergeId, urlId).Return(makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodDelete, fmt.Sprintf("/mr/draft_notes/%d", urlId), nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, DraftNoteResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Draft note deleted")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles error", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
urlId := 10
|
|
||||||
client.EXPECT().DeleteDraftNote("", mock_main.MergeId, urlId).Return(nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodDelete, fmt.Sprintf("/mr/draft_notes/%d", urlId), nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Could not delete draft note")
|
|
||||||
assert(t, data.Status, http.StatusInternalServerError)
|
|
||||||
assert(t, data.Details, errorFromGitlab.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles bad ID", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
urlId := "abc"
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodDelete, fmt.Sprintf("/mr/draft_notes/%s", urlId), nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Could not parse draft note ID")
|
|
||||||
assert(t, data.Status, http.StatusBadRequest)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEditDraftNote(t *testing.T) {
|
|
||||||
t.Run("Edits draft note", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
urlId := 10
|
|
||||||
client.EXPECT().UpdateDraftNote("", mock_main.MergeId, urlId, gomock.Any()).Return(&gitlab.DraftNote{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, fmt.Sprintf("/mr/draft_notes/%d", urlId), testUpdateDraftNoteRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, DraftNoteResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Draft note updated")
|
|
||||||
assert(t, data.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles bad ID", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
urlId := "abc"
|
|
||||||
client.EXPECT().UpdateDraftNote("", mock_main.MergeId, urlId, gomock.Any()).Return(&gitlab.DraftNote{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, fmt.Sprintf("/mr/draft_notes/%s", urlId), testUpdateDraftNoteRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
urlId := 10
|
|
||||||
|
|
||||||
testEmptyUpdateDraftNoteRequest := testUpdateDraftNoteRequest
|
|
||||||
testEmptyUpdateDraftNoteRequest.Note = ""
|
|
||||||
|
|
||||||
client.EXPECT().UpdateDraftNote("", mock_main.MergeId, urlId, gomock.Any()).Return(&gitlab.DraftNote{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, fmt.Sprintf("/mr/draft_notes/%d", urlId), testEmptyUpdateDraftNoteRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Must provide draft note text")
|
|
||||||
assert(t, data.Status, http.StatusBadRequest)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPublishDraftNote(t *testing.T) {
|
|
||||||
t.Run("Should publish a draft note", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().PublishDraftNote("", mock_main.MergeId, testDraftNotePublishRequest.Note).Return(makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
|
|
||||||
// Missing Note ID
|
|
||||||
testDraftNotePublishRequest := DraftNotePublishRequest{
|
|
||||||
PublishAll: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().PublishDraftNote("", mock_main.MergeId, testDraftNotePublishRequest.Note).Return(nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", testDraftNotePublishRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Could not publish draft note(s)")
|
|
||||||
assert(t, data.Status, http.StatusInternalServerError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPublishAllDraftNotes(t *testing.T) {
|
|
||||||
t.Run("Should publish all draft notes", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().PublishAllDraftNotes("", mock_main.MergeId).Return(makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{PublishAll: true})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().PublishAllDraftNotes("", mock_main.MergeId).Return(nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/draft_notes/publish", DraftNotePublishRequest{PublishAll: true})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
assert(t, data.Message, "Could not publish draft note(s)")
|
|
||||||
assert(t, data.Status, http.StatusInternalServerError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
272
cmd/git_test.go
272
cmd/git_test.go
@@ -1,272 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExtractGitInfo_Success(t *testing.T) {
|
|
||||||
getCurrentBranchName := func() (string, error) {
|
|
||||||
return "feature/abc", nil
|
|
||||||
}
|
|
||||||
refreshGitInfo := func() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
testCases := []struct {
|
|
||||||
getProjectRemoteUrl func() (string, error)
|
|
||||||
expected GitProjectInfo
|
|
||||||
desc string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH under a single folder",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "git@custom-gitlab.com:namespace-1/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "git@custom-gitlab.com:namespace-1/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH under a single folder without .git extension",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "git@custom-gitlab.com:namespace-1/project-name", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "git@custom-gitlab.com:namespace-1/project-name",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH under one nested folder",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "git@custom-gitlab.com:namespace-1/namespace-2/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "git@custom-gitlab.com:namespace-1/namespace-2/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1/namespace-2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH under two nested folders",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "git@custom-gitlab.com:namespace-1/namespace-2/namespace-3/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "git@custom-gitlab.com:namespace-1/namespace-2/namespace-3/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1/namespace-2/namespace-3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH:// under a single folder",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "ssh://custom-gitlab.com/namespace-1/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "ssh://custom-gitlab.com/namespace-1/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH:// under a single folder without .git extension",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "ssh://custom-gitlab.com/namespace-1/project-name", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "ssh://custom-gitlab.com/namespace-1/project-name",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH:// under two nested folders",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "ssh://custom-gitlab.com/namespace-1/namespace-2/namespace-3/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "ssh://custom-gitlab.com/namespace-1/namespace-2/namespace-3/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1/namespace-2/namespace-3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in SSH:// and have a custom port",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "ssh://custom-gitlab.com:2222/namespace-1/project-name", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "ssh://custom-gitlab.com:2222/namespace-1/project-name",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in HTTP and under a single folder without .git extension",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "http://custom-gitlab.com/namespace-1/project-name", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "http://custom-gitlab.com/namespace-1/project-name",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in HTTPS and under a single folder",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "https://custom-gitlab.com/namespace-1/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "https://custom-gitlab.com/namespace-1/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in HTTPS and under a nested folder",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "https://custom-gitlab.com/namespace-1/namespace-2/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "https://custom-gitlab.com/namespace-1/namespace-2/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1/namespace-2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Project configured in HTTPS and under two nested folders",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "https://custom-gitlab.com/namespace-1/namespace-2/namespace-3/project-name.git", nil
|
|
||||||
},
|
|
||||||
expected: GitProjectInfo{
|
|
||||||
RemoteUrl: "https://custom-gitlab.com/namespace-1/namespace-2/namespace-3/project-name.git",
|
|
||||||
BranchName: "feature/abc",
|
|
||||||
ProjectName: "project-name",
|
|
||||||
Namespace: "namespace-1/namespace-2/namespace-3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tC := range testCases {
|
|
||||||
t.Run(tC.desc, func(t *testing.T) {
|
|
||||||
actual, err := extractGitInfo(refreshGitInfo, tC.getProjectRemoteUrl, getCurrentBranchName)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("No error was expected, got %s", err)
|
|
||||||
}
|
|
||||||
if actual.RemoteUrl != tC.expected.RemoteUrl {
|
|
||||||
t.Errorf("\nExpected Remote URL: %s\nActual: %s", tC.expected.RemoteUrl, actual.RemoteUrl)
|
|
||||||
}
|
|
||||||
if actual.BranchName != tC.expected.BranchName {
|
|
||||||
t.Errorf("\nExpected Branch Name: %s\nActual: %s", tC.expected.BranchName, actual.BranchName)
|
|
||||||
}
|
|
||||||
if actual.ProjectName != tC.expected.ProjectName {
|
|
||||||
t.Errorf("\nExpected Project Name: %s\nActual: %s", tC.expected.ProjectName, actual.ProjectName)
|
|
||||||
}
|
|
||||||
if actual.Namespace != tC.expected.Namespace {
|
|
||||||
t.Errorf("\nExpected Namespace: %s\nActual: %s", tC.expected.Namespace, actual.Namespace)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractGitInfo_FailToGetProjectRemoteUrl(t *testing.T) {
|
|
||||||
getCurrentBranchName := func() (string, error) {
|
|
||||||
return "feature/abc", nil
|
|
||||||
}
|
|
||||||
refreshGitInfo := func() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
testCases := []struct {
|
|
||||||
getProjectRemoteUrl func() (string, error)
|
|
||||||
expectedErrorMessage string
|
|
||||||
desc string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "Error returned by function to get the project remote url",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "", errors.New("error when getting project remote url")
|
|
||||||
},
|
|
||||||
expectedErrorMessage: "Could not get project Url: error when getting project remote url",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Invalid project remote url",
|
|
||||||
getProjectRemoteUrl: func() (string, error) {
|
|
||||||
return "git@invalid", nil
|
|
||||||
},
|
|
||||||
expectedErrorMessage: "Invalid Git URL format: git@invalid",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tC := range testCases {
|
|
||||||
t.Run(tC.desc, func(t *testing.T) {
|
|
||||||
_, actualErr := extractGitInfo(refreshGitInfo, tC.getProjectRemoteUrl, getCurrentBranchName)
|
|
||||||
if actualErr == nil {
|
|
||||||
t.Errorf("Expected an error, got none")
|
|
||||||
}
|
|
||||||
if actualErr.Error() != tC.expectedErrorMessage {
|
|
||||||
t.Errorf("\nExpected: %s\nActual: %s", tC.expectedErrorMessage, actualErr.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractGitInfo_FailToGetCurrentBranchName(t *testing.T) {
|
|
||||||
expectedErrNestedMsg := "error when getting current branch name"
|
|
||||||
|
|
||||||
refreshGitInfo := func() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, actualErr := extractGitInfo(refreshGitInfo,
|
|
||||||
func() (string, error) {
|
|
||||||
return "git@custom-gitlab.com:namespace/project.git", nil
|
|
||||||
},
|
|
||||||
func() (string, error) {
|
|
||||||
return "", errors.New(expectedErrNestedMsg)
|
|
||||||
})
|
|
||||||
|
|
||||||
if actualErr == nil {
|
|
||||||
t.Errorf("Expected an error, got none")
|
|
||||||
}
|
|
||||||
expectedErr := fmt.Errorf("Failed to get current branch: %s", expectedErrNestedMsg)
|
|
||||||
if actualErr.Error() != expectedErr.Error() {
|
|
||||||
t.Errorf("\nExpected: %s\nActual: %s", expectedErr, actualErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRefreshGitRemote_FailToRefreshRemote(t *testing.T) {
|
|
||||||
expectedErrNestedMsg := "error when fetching commits"
|
|
||||||
_, actualErr := extractGitInfo(
|
|
||||||
func() error {
|
|
||||||
return errors.New(expectedErrNestedMsg)
|
|
||||||
},
|
|
||||||
func() (string, error) {
|
|
||||||
return "git@custom-gitlab.com:namespace/project.git", nil
|
|
||||||
},
|
|
||||||
func() (string, error) {
|
|
||||||
return "feature/abc", nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if actualErr == nil {
|
|
||||||
t.Errorf("Expected an error, got none")
|
|
||||||
}
|
|
||||||
expectedErr := fmt.Errorf("Could not get latest information from remote: %s", expectedErrNestedMsg)
|
|
||||||
if actualErr.Error() != expectedErr.Error() {
|
|
||||||
t.Errorf("\nExpected: %s\nActual: %s", expectedErr, actualErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInfoHandler(t *testing.T) {
|
|
||||||
t.Run("Returns normal information", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().GetMergeRequest("", mock_main.MergeId, &gitlab.GetMergeRequestsOptions{}).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
|
|
||||||
data := serveRequest(t, server, request, InfoResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Merge requests retrieved")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-GET method", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/info", nil)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkBadMethod(t, *data, http.MethodGet)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().GetMergeRequest("", mock_main.MergeId, &gitlab.GetMergeRequestsOptions{}).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().GetMergeRequest("", mock_main.MergeId, &gitlab.GetMergeRequestsOptions{}).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
request := makeRequest(t, http.MethodGet, "/mr/info", nil)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not get project info", "/mr/info")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
var jobId = 0
|
|
||||||
|
|
||||||
func TestJobHandler(t *testing.T) {
|
|
||||||
t.Run("Should read a job trace file", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().GetTraceFile("", jobId).Return(bytes.NewReader([]byte("Some data")), makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/job", JobTraceRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, JobTraceResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Log file read")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
assert(t, data.File, "Some data")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-GET methods", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().GetTraceFile("", jobId).Return(bytes.NewReader([]byte("Some data")), makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/job", JobTraceRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkBadMethod(t, *data, http.MethodGet)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Should handle errors from Gitlab", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().GetTraceFile("", jobId).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/job", JobTraceRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not get trace file for job")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Should handle non-2jobIdjobIds", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().GetTraceFile("", jobId).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/job", JobTraceRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not get trace file for job", "/job")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var now = time.Now()
|
|
||||||
var newer = now.Add(time.Second * 100)
|
|
||||||
|
|
||||||
type Author struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
State string `json:"state"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
WebURL string `json:"web_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var testListDiscussionsResponse = []*gitlab.Discussion{
|
|
||||||
{
|
|
||||||
Notes: []*gitlab.Note{
|
|
||||||
{
|
|
||||||
CreatedAt: &now,
|
|
||||||
Type: "DiffNote",
|
|
||||||
Author: Author{
|
|
||||||
Username: "hcramer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Notes: []*gitlab.Note{
|
|
||||||
{
|
|
||||||
CreatedAt: &newer,
|
|
||||||
Type: "DiffNote",
|
|
||||||
Author: Author{
|
|
||||||
Username: "hcramer2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListDiscussionsHandler(t *testing.T) {
|
|
||||||
t.Run("Returns sorted discussions", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListMergeRequestDiscussions("", mock_main.MergeId, gomock.Any()).Return(testListDiscussionsResponse, makeResponse(http.StatusOK), nil)
|
|
||||||
client.EXPECT().ListMergeRequestAwardEmojiOnNote("", mock_main.MergeId, gomock.Any(), gomock.Any()).Return([]*gitlab.AwardEmoji{}, makeResponse(http.StatusOK), nil).Times(2)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, DiscussionsResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Discussions retrieved")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2") /* Sorting applied */
|
|
||||||
assert(t, data.Discussions[1].Notes[0].Author.Username, "hcramer")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Uses blacklist to filter unwanted authors", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListMergeRequestDiscussions("", mock_main.MergeId, gomock.Any()).Return(testListDiscussionsResponse, makeResponse(http.StatusOK), nil)
|
|
||||||
client.EXPECT().ListMergeRequestAwardEmojiOnNote("", mock_main.MergeId, gomock.Any(), gomock.Any()).Return([]*gitlab.AwardEmoji{}, makeResponse(http.StatusOK), nil).Times(2)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{Blacklist: []string{"hcramer"}})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, DiscussionsResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Discussions retrieved")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
assert(t, len(data.Discussions), 1)
|
|
||||||
assert(t, data.Discussions[0].Notes[0].Author.Username, "hcramer2")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-POST method", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/mr/discussions/list", DiscussionsRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkBadMethod(t, *data, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListMergeRequestDiscussions("", mock_main.MergeId, gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not list discussions")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListMergeRequestDiscussions("", mock_main.MergeId, gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not list discussions", "/mr/discussions/list")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles error from emoji service", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListMergeRequestDiscussions("", mock_main.MergeId, gomock.Any()).Return(testListDiscussionsResponse, makeResponse(http.StatusOK), nil)
|
|
||||||
client.EXPECT().ListMergeRequestAwardEmojiOnNote("", mock_main.MergeId, gomock.Any(), gomock.Any()).Return(nil, nil, errorFromGitlab).Times(2)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/discussions/list", DiscussionsRequest{})
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not fetch emojis")
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
32
cmd/main.go
32
cmd/main.go
@@ -4,47 +4,39 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/harrisoncramer/gitlab.nvim/cmd/app"
|
||||||
|
"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PluginOptions struct {
|
var pluginOptions app.PluginOptions
|
||||||
GitlabUrl string `json:"gitlab_url"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
AuthToken string `json:"auth_token"`
|
|
||||||
LogPath string `json:"log_path"`
|
|
||||||
Debug struct {
|
|
||||||
Request bool `json:"go_request"`
|
|
||||||
Response bool `json:"go_response"`
|
|
||||||
} `json:"debug"`
|
|
||||||
ConnectionSettings struct {
|
|
||||||
Insecure bool `json:"insecure"`
|
|
||||||
Remote string `json:"remote"`
|
|
||||||
} `json:"connection_settings"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var pluginOptions PluginOptions
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(os.Args[1]), &pluginOptions)
|
err := json.Unmarshal([]byte(os.Args[1]), &pluginOptions)
|
||||||
|
app.SetPluginOptions(pluginOptions)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failure parsing plugin settings: %v", err)
|
log.Fatalf("Failure parsing plugin settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gitInfo, err := extractGitInfo(RefreshProjectInfo, GetProjectUrlFromNativeGitCmd, GetCurrentBranchNameFromNativeGitCmd)
|
gitManager := git.Git{}
|
||||||
|
gitData, err := git.NewGitData(pluginOptions.ConnectionSettings.Remote, gitManager)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failure initializing plugin: %v", err)
|
log.Fatalf("Failure initializing plugin: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err, client := initGitlabClient()
|
err, client := app.NewClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize Gitlab client: %v", err)
|
log.Fatalf("Failed to initialize Gitlab client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err, projectInfo := initProjectSettings(client, gitInfo)
|
err, projectInfo := app.InitProjectSettings(client, gitData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize project settings: %v", err)
|
log.Fatalf("Failed to initialize project settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
startServer(client, projectInfo, gitInfo)
|
app.StartServer(client, projectInfo, gitData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMembersHandler(t *testing.T) {
|
|
||||||
t.Run("Returns project members", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().ListAllProjectMembers("", gomock.Any()).Return([]*gitlab.ProjectMember{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/project/members", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ProjectMembersResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Project members retrieved")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-GET method", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/project/members", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkBadMethod(t, *data, http.MethodGet)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().ListAllProjectMembers("", gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/project/members", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not retrieve project members")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().ListAllProjectMembers("", gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/project/members", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not retrieve project members", "/project/members")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testListMergeRequestsRequest = ListMergeRequestRequest{
|
|
||||||
Label: []string{},
|
|
||||||
NotLabel: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeRequestHandler(t *testing.T) {
|
|
||||||
t.Run("Should fetch merge requests", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListProjectMergeRequests("", gomock.Any()).Return([]*gitlab.MergeRequest{
|
|
||||||
{
|
|
||||||
IID: 10,
|
|
||||||
},
|
|
||||||
}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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 from Gitlab", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListProjectMergeRequests("", gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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 a non-200", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListProjectMergeRequests("", gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "Failed to list merge requests")
|
|
||||||
assert(t, data.Status, http.StatusSeeOther)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Should handle not having any merge requests with 404", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().ListProjectMergeRequests("", gomock.Any()).Return([]*gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/merge_requests", testListMergeRequestsRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ListMergeRequestResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "No merge requests found")
|
|
||||||
assert(t, data.Status, http.StatusNotFound)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testAcceptMergeRequestPayload = AcceptMergeRequestRequest{
|
|
||||||
Squash: false,
|
|
||||||
SquashMessage: "Squash me!",
|
|
||||||
DeleteBranch: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAcceptAndMergeHandler(t *testing.T) {
|
|
||||||
t.Run("Accepts and merges a merge request", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AcceptMergeRequest("", mock_main.MergeId, gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/merge", testAcceptMergeRequestPayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, SuccessResponse{})
|
|
||||||
|
|
||||||
assert(t, data.Message, "MR merged successfully")
|
|
||||||
assert(t, data.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-POST methods", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AcceptMergeRequest("", mock_main.MergeId, gomock.Any()).Return(&gitlab.MergeRequest{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPatch, "/mr/merge", testAcceptMergeRequestPayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkBadMethod(t, *data, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AcceptMergeRequest("", mock_main.MergeId, gomock.Any()).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/merge", testAcceptMergeRequestPayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
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) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AcceptMergeRequest("", mock_main.MergeId, gomock.Any()).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/merge", testAcceptMergeRequestPayload)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
|
|
||||||
checkNon200(t, *data, "Could not merge MR", "/mr/merge")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,686 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: cmd/types.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source cmd/types.go
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock_main is a generated GoMock package.
|
|
||||||
package mock_main
|
|
||||||
|
|
||||||
import (
|
|
||||||
bytes "bytes"
|
|
||||||
io "io"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gitlab "github.com/xanzy/go-gitlab"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockClientInterface is a mock of ClientInterface interface.
|
|
||||||
type MockClientInterface struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockClientInterfaceMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClientInterfaceMockRecorder is the mock recorder for MockClientInterface.
|
|
||||||
type MockClientInterfaceMockRecorder struct {
|
|
||||||
mock *MockClientInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockClientInterface creates a new mock instance.
|
|
||||||
func NewMockClientInterface(ctrl *gomock.Controller) *MockClientInterface {
|
|
||||||
mock := &MockClientInterface{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockClientInterfaceMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockClientInterface) EXPECT() *MockClientInterfaceMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptMergeRequest mocks base method.
|
|
||||||
func (m *MockClientInterface) AcceptMergeRequest(pid any, mergeRequestIID int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AcceptMergeRequest", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.MergeRequest)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptMergeRequest indicates an expected call of AcceptMergeRequest.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) AcceptMergeRequest(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptMergeRequest", reflect.TypeOf((*MockClientInterface)(nil).AcceptMergeRequest), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddMergeRequestDiscussionNote mocks base method.
|
|
||||||
func (m *MockClientInterface) AddMergeRequestDiscussionNote(pid any, mergeRequestIID int, discussion string, opt *gitlab.AddMergeRequestDiscussionNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, discussion, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddMergeRequestDiscussionNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Note)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddMergeRequestDiscussionNote indicates an expected call of AddMergeRequestDiscussionNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) AddMergeRequestDiscussionNote(pid, mergeRequestIID, discussion, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, discussion, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMergeRequestDiscussionNote", reflect.TypeOf((*MockClientInterface)(nil).AddMergeRequestDiscussionNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApproveMergeRequest mocks base method.
|
|
||||||
func (m *MockClientInterface) ApproveMergeRequest(pid any, mergeRequestIID int, opt *gitlab.ApproveMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequestApprovals, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ApproveMergeRequest", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.MergeRequestApprovals)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApproveMergeRequest indicates an expected call of ApproveMergeRequest.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ApproveMergeRequest(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApproveMergeRequest", reflect.TypeOf((*MockClientInterface)(nil).ApproveMergeRequest), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDraftNote mocks base method.
|
|
||||||
func (m *MockClientInterface) CreateDraftNote(pid any, mergeRequestIID int, opt *gitlab.CreateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "CreateDraftNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.DraftNote)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDraftNote indicates an expected call of CreateDraftNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) CreateDraftNote(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraftNote", reflect.TypeOf((*MockClientInterface)(nil).CreateDraftNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergeRequest mocks base method.
|
|
||||||
func (m *MockClientInterface) CreateMergeRequest(pid any, opt *gitlab.CreateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "CreateMergeRequest", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.MergeRequest)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergeRequest indicates an expected call of CreateMergeRequest.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) CreateMergeRequest(pid, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequest", reflect.TypeOf((*MockClientInterface)(nil).CreateMergeRequest), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergeRequestAwardEmojiOnNote mocks base method.
|
|
||||||
func (m *MockClientInterface) CreateMergeRequestAwardEmojiOnNote(pid any, mergeRequestIID, noteID int, opt *gitlab.CreateAwardEmojiOptions, options ...gitlab.RequestOptionFunc) (*gitlab.AwardEmoji, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, noteID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "CreateMergeRequestAwardEmojiOnNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.AwardEmoji)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergeRequestAwardEmojiOnNote indicates an expected call of CreateMergeRequestAwardEmojiOnNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) CreateMergeRequestAwardEmojiOnNote(pid, mergeRequestIID, noteID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, noteID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequestAwardEmojiOnNote", reflect.TypeOf((*MockClientInterface)(nil).CreateMergeRequestAwardEmojiOnNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergeRequestDiscussion mocks base method.
|
|
||||||
func (m *MockClientInterface) CreateMergeRequestDiscussion(pid any, mergeRequestIID int, opt *gitlab.CreateMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "CreateMergeRequestDiscussion", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Discussion)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergeRequestDiscussion indicates an expected call of CreateMergeRequestDiscussion.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) CreateMergeRequestDiscussion(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequestDiscussion", reflect.TypeOf((*MockClientInterface)(nil).CreateMergeRequestDiscussion), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentUser mocks base method.
|
|
||||||
func (m *MockClientInterface) CurrentUser(options ...gitlab.RequestOptionFunc) (*gitlab.User, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "CurrentUser", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.User)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentUser indicates an expected call of CurrentUser.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) CurrentUser(options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockClientInterface)(nil).CurrentUser), options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDraftNote mocks base method.
|
|
||||||
func (m *MockClientInterface) DeleteDraftNote(pid any, mergeRequest, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequest, note}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteDraftNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Response)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDraftNote indicates an expected call of DeleteDraftNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) DeleteDraftNote(pid, mergeRequest, note any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequest, note}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDraftNote", reflect.TypeOf((*MockClientInterface)(nil).DeleteDraftNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMergeRequestAwardEmojiOnNote mocks base method.
|
|
||||||
func (m *MockClientInterface) DeleteMergeRequestAwardEmojiOnNote(pid any, mergeRequestIID, noteID, awardID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, noteID, awardID}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteMergeRequestAwardEmojiOnNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Response)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMergeRequestAwardEmojiOnNote indicates an expected call of DeleteMergeRequestAwardEmojiOnNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) DeleteMergeRequestAwardEmojiOnNote(pid, mergeRequestIID, noteID, awardID any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, noteID, awardID}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMergeRequestAwardEmojiOnNote", reflect.TypeOf((*MockClientInterface)(nil).DeleteMergeRequestAwardEmojiOnNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMergeRequestDiscussionNote mocks base method.
|
|
||||||
func (m *MockClientInterface) DeleteMergeRequestDiscussionNote(pid any, mergeRequestIID int, discussion string, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, discussion, note}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteMergeRequestDiscussionNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Response)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMergeRequestDiscussionNote indicates an expected call of DeleteMergeRequestDiscussionNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) DeleteMergeRequestDiscussionNote(pid, mergeRequestIID, discussion, note any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, discussion, note}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMergeRequestDiscussionNote", reflect.TypeOf((*MockClientInterface)(nil).DeleteMergeRequestDiscussionNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMergeRequest mocks base method.
|
|
||||||
func (m *MockClientInterface) GetMergeRequest(pid any, mergeRequestIID int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "GetMergeRequest", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.MergeRequest)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMergeRequest indicates an expected call of GetMergeRequest.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) GetMergeRequest(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequest", reflect.TypeOf((*MockClientInterface)(nil).GetMergeRequest), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMergeRequestDiffVersions mocks base method.
|
|
||||||
func (m *MockClientInterface) GetMergeRequestDiffVersions(pid any, mergeRequestIID int, opt *gitlab.GetMergeRequestDiffVersionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequestDiffVersion, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "GetMergeRequestDiffVersions", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.MergeRequestDiffVersion)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMergeRequestDiffVersions indicates an expected call of GetMergeRequestDiffVersions.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) GetMergeRequestDiffVersions(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequestDiffVersions", reflect.TypeOf((*MockClientInterface)(nil).GetMergeRequestDiffVersions), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTraceFile mocks base method.
|
|
||||||
func (m *MockClientInterface) GetTraceFile(pid any, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, jobID}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "GetTraceFile", varargs...)
|
|
||||||
ret0, _ := ret[0].(*bytes.Reader)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTraceFile indicates an expected call of GetTraceFile.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) GetTraceFile(pid, jobID any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, jobID}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraceFile", reflect.TypeOf((*MockClientInterface)(nil).GetTraceFile), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAllProjectMembers mocks base method.
|
|
||||||
func (m *MockClientInterface) ListAllProjectMembers(pid any, opt *gitlab.ListProjectMembersOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectMember, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListAllProjectMembers", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.ProjectMember)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAllProjectMembers indicates an expected call of ListAllProjectMembers.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListAllProjectMembers(pid, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllProjectMembers", reflect.TypeOf((*MockClientInterface)(nil).ListAllProjectMembers), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDraftNotes mocks base method.
|
|
||||||
func (m *MockClientInterface) ListDraftNotes(pid any, mergeRequest int, opt *gitlab.ListDraftNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.DraftNote, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequest, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListDraftNotes", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.DraftNote)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDraftNotes indicates an expected call of ListDraftNotes.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListDraftNotes(pid, mergeRequest, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequest, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDraftNotes", reflect.TypeOf((*MockClientInterface)(nil).ListDraftNotes), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListLabels mocks base method.
|
|
||||||
func (m *MockClientInterface) ListLabels(pid any, opt *gitlab.ListLabelsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Label, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListLabels", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.Label)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListLabels indicates an expected call of ListLabels.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListLabels(pid, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockClientInterface)(nil).ListLabels), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMergeRequestAwardEmojiOnNote mocks base method.
|
|
||||||
func (m *MockClientInterface) ListMergeRequestAwardEmojiOnNote(pid any, mergeRequestIID, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, noteID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListMergeRequestAwardEmojiOnNote", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.AwardEmoji)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMergeRequestAwardEmojiOnNote indicates an expected call of ListMergeRequestAwardEmojiOnNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListMergeRequestAwardEmojiOnNote(pid, mergeRequestIID, noteID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, noteID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMergeRequestAwardEmojiOnNote", reflect.TypeOf((*MockClientInterface)(nil).ListMergeRequestAwardEmojiOnNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMergeRequestDiscussions mocks base method.
|
|
||||||
func (m *MockClientInterface) ListMergeRequestDiscussions(pid any, mergeRequestIID int, opt *gitlab.ListMergeRequestDiscussionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Discussion, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListMergeRequestDiscussions", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.Discussion)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMergeRequestDiscussions indicates an expected call of ListMergeRequestDiscussions.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListMergeRequestDiscussions(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMergeRequestDiscussions", reflect.TypeOf((*MockClientInterface)(nil).ListMergeRequestDiscussions), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPipelineJobs mocks base method.
|
|
||||||
func (m *MockClientInterface) ListPipelineJobs(pid any, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, pipelineID, opts}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListPipelineJobs", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.Job)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPipelineJobs indicates an expected call of ListPipelineJobs.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListPipelineJobs(pid, pipelineID, opts any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, pipelineID, opts}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPipelineJobs", reflect.TypeOf((*MockClientInterface)(nil).ListPipelineJobs), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListProjectMergeRequests mocks base method.
|
|
||||||
func (m *MockClientInterface) ListProjectMergeRequests(pid any, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListProjectMergeRequests", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.MergeRequest)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListProjectMergeRequests indicates an expected call of ListProjectMergeRequests.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListProjectMergeRequests(pid, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectMergeRequests", reflect.TypeOf((*MockClientInterface)(nil).ListProjectMergeRequests), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListProjectPipelines mocks base method.
|
|
||||||
func (m *MockClientInterface) ListProjectPipelines(pid any, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ListProjectPipelines", varargs...)
|
|
||||||
ret0, _ := ret[0].([]*gitlab.PipelineInfo)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListProjectPipelines indicates an expected call of ListProjectPipelines.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ListProjectPipelines(pid, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectPipelines", reflect.TypeOf((*MockClientInterface)(nil).ListProjectPipelines), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublishAllDraftNotes mocks base method.
|
|
||||||
func (m *MockClientInterface) PublishAllDraftNotes(pid any, mergeRequest int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequest}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "PublishAllDraftNotes", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Response)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublishAllDraftNotes indicates an expected call of PublishAllDraftNotes.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) PublishAllDraftNotes(pid, mergeRequest any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequest}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishAllDraftNotes", reflect.TypeOf((*MockClientInterface)(nil).PublishAllDraftNotes), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublishDraftNote mocks base method.
|
|
||||||
func (m *MockClientInterface) PublishDraftNote(pid any, mergeRequest, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequest, note}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "PublishDraftNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Response)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublishDraftNote indicates an expected call of PublishDraftNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) PublishDraftNote(pid, mergeRequest, note any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequest, note}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishDraftNote", reflect.TypeOf((*MockClientInterface)(nil).PublishDraftNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveMergeRequestDiscussion mocks base method.
|
|
||||||
func (m *MockClientInterface) ResolveMergeRequestDiscussion(pid any, mergeRequestIID int, discussion string, opt *gitlab.ResolveMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, discussion, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "ResolveMergeRequestDiscussion", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Discussion)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveMergeRequestDiscussion indicates an expected call of ResolveMergeRequestDiscussion.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) ResolveMergeRequestDiscussion(pid, mergeRequestIID, discussion, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, discussion, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveMergeRequestDiscussion", reflect.TypeOf((*MockClientInterface)(nil).ResolveMergeRequestDiscussion), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetryPipelineBuild mocks base method.
|
|
||||||
func (m *MockClientInterface) RetryPipelineBuild(pid any, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, pipeline}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "RetryPipelineBuild", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Pipeline)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetryPipelineBuild indicates an expected call of RetryPipelineBuild.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) RetryPipelineBuild(pid, pipeline any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, pipeline}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryPipelineBuild", reflect.TypeOf((*MockClientInterface)(nil).RetryPipelineBuild), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnapproveMergeRequest mocks base method.
|
|
||||||
func (m *MockClientInterface) UnapproveMergeRequest(pid any, mergeRequestIID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UnapproveMergeRequest", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Response)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnapproveMergeRequest indicates an expected call of UnapproveMergeRequest.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) UnapproveMergeRequest(pid, mergeRequestIID any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnapproveMergeRequest", reflect.TypeOf((*MockClientInterface)(nil).UnapproveMergeRequest), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDraftNote mocks base method.
|
|
||||||
func (m *MockClientInterface) UpdateDraftNote(pid any, mergeRequest, note int, opt *gitlab.UpdateDraftNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.DraftNote, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequest, note, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateDraftNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.DraftNote)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDraftNote indicates an expected call of UpdateDraftNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) UpdateDraftNote(pid, mergeRequest, note, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequest, note, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDraftNote", reflect.TypeOf((*MockClientInterface)(nil).UpdateDraftNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMergeRequest mocks base method.
|
|
||||||
func (m *MockClientInterface) UpdateMergeRequest(pid any, mergeRequestIID int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateMergeRequest", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.MergeRequest)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMergeRequest indicates an expected call of UpdateMergeRequest.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) UpdateMergeRequest(pid, mergeRequestIID, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMergeRequest", reflect.TypeOf((*MockClientInterface)(nil).UpdateMergeRequest), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMergeRequestDiscussionNote mocks base method.
|
|
||||||
func (m *MockClientInterface) UpdateMergeRequestDiscussionNote(pid any, mergeRequestIID int, discussion string, note int, opt *gitlab.UpdateMergeRequestDiscussionNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, mergeRequestIID, discussion, note, opt}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateMergeRequestDiscussionNote", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.Note)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMergeRequestDiscussionNote indicates an expected call of UpdateMergeRequestDiscussionNote.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) UpdateMergeRequestDiscussionNote(pid, mergeRequestIID, discussion, note, opt any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, mergeRequestIID, discussion, note, opt}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMergeRequestDiscussionNote", reflect.TypeOf((*MockClientInterface)(nil).UpdateMergeRequestDiscussionNote), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadFile mocks base method.
|
|
||||||
func (m *MockClientInterface) UploadFile(pid any, content io.Reader, filename string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectFile, *gitlab.Response, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{pid, content, filename}
|
|
||||||
for _, a := range options {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UploadFile", varargs...)
|
|
||||||
ret0, _ := ret[0].(*gitlab.ProjectFile)
|
|
||||||
ret1, _ := ret[1].(*gitlab.Response)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadFile indicates an expected call of UploadFile.
|
|
||||||
func (mr *MockClientInterfaceMockRecorder) UploadFile(pid, content, filename any, options ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{pid, content, filename}, options...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadFile", reflect.TypeOf((*MockClientInterface)(nil).UploadFile), varargs...)
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package mock_main
|
|
||||||
|
|
||||||
import (
|
|
||||||
bytes "bytes"
|
|
||||||
io "io"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
gitlab "github.com/xanzy/go-gitlab"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var MergeId = 3
|
|
||||||
|
|
||||||
func NewListMrOptions() *gitlab.ListProjectMergeRequestsOptions {
|
|
||||||
return &gitlab.ListProjectMergeRequestsOptions{
|
|
||||||
Scope: gitlab.Ptr("all"),
|
|
||||||
State: gitlab.Ptr("opened"),
|
|
||||||
SourceBranch: gitlab.Ptr(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make response makes a simple response value with the right status code */
|
|
||||||
func makeResponse(status int) *gitlab.Response {
|
|
||||||
return &gitlab.Response{
|
|
||||||
Response: &http.Response{
|
|
||||||
StatusCode: status,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockOpts struct {
|
|
||||||
MergeId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMockClient(t *testing.T) *MockClientInterface {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
mockObj := NewMockClientInterface(ctrl)
|
|
||||||
return mockObj
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Adds a handler to satisfy the withMrs middleware by returning an MR from that endpoint with the given ID */
|
|
||||||
func WithMr(t *testing.T, m *MockClientInterface) *MockClientInterface {
|
|
||||||
options := gitlab.ListProjectMergeRequestsOptions{
|
|
||||||
Scope: gitlab.Ptr("all"),
|
|
||||||
State: gitlab.Ptr("opened"),
|
|
||||||
SourceBranch: gitlab.Ptr(""),
|
|
||||||
}
|
|
||||||
|
|
||||||
m.EXPECT().ListProjectMergeRequests("", &options).Return([]*gitlab.MergeRequest{{IID: MergeId}}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockAttachmentReader struct{}
|
|
||||||
|
|
||||||
func (mf MockAttachmentReader) ReadFile(path string) (io.Reader, error) {
|
|
||||||
return bytes.NewReader([]byte{}), nil
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testPipelineId = 12435
|
|
||||||
var testPipelineCommit = "abc123"
|
|
||||||
var fakeProjectPipelines = []*gitlab.PipelineInfo{{ID: testPipelineId}}
|
|
||||||
|
|
||||||
/* This helps us stub out git interactions that the server would normally run in the project directory */
|
|
||||||
func withGitInfo(a *Api) error {
|
|
||||||
a.gitInfo.GetLatestCommitOnRemote = func(a *Api) (string, error) {
|
|
||||||
return testPipelineCommit, nil
|
|
||||||
}
|
|
||||||
a.gitInfo.BranchName = "some-feature"
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPipelineHandler(t *testing.T) {
|
|
||||||
t.Run("Gets all pipeline jobs", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().ListProjectPipelines("", gomock.Any()).Return(fakeProjectPipelines, makeResponse(http.StatusOK), nil)
|
|
||||||
client.EXPECT().ListPipelineJobs("", testPipelineId, &gitlab.ListJobsOptions{}).Return([]*gitlab.Job{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
data := serveRequest(t, server, request, GetPipelineAndJobsResponse{})
|
|
||||||
|
|
||||||
assert(t, data.SuccessResponse.Message, "Pipeline retrieved")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-GET, non-POST methods", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
request := makeRequest(t, http.MethodPatch, "/pipeline", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkBadMethod(t, *data, http.MethodGet, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().ListProjectPipelines("", gomock.Any()).Return(fakeProjectPipelines, makeResponse(http.StatusOK), nil)
|
|
||||||
client.EXPECT().ListPipelineJobs("", testPipelineId, &gitlab.ListJobsOptions{}).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not get pipeline jobs")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().ListProjectPipelines("", gomock.Any()).Return(fakeProjectPipelines, makeResponse(http.StatusOK), nil)
|
|
||||||
client.EXPECT().ListPipelineJobs("", testPipelineId, &gitlab.ListJobsOptions{}).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not get pipeline jobs", "/pipeline")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Retriggers pipeline", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().RetryPipelineBuild("", testPipelineId).Return(&gitlab.Pipeline{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, fmt.Sprintf("/pipeline/trigger/%d", testPipelineId), nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, GetPipelineAndJobsResponse{})
|
|
||||||
assert(t, data.SuccessResponse.Message, "Pipeline retriggered")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client on retrigger", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().RetryPipelineBuild("", testPipelineId).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, fmt.Sprintf("/pipeline/trigger/%d", testPipelineId), nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not retrigger pipeline", "/pipeline")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles error from Gitlab client on retrigger", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
client.EXPECT().RetryPipelineBuild("", testPipelineId).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, fmt.Sprintf("/pipeline/trigger/%d", testPipelineId), nil)
|
|
||||||
server, _ := CreateRouterAndApi(client, withGitInfo)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not retrigger pipeline")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
mock_main "gitlab.com/harrisoncramer/gitlab.nvim/cmd/mocks"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testReplyRequest = ReplyRequest{
|
|
||||||
DiscussionId: "abc123",
|
|
||||||
Reply: "Some Reply",
|
|
||||||
IsDraft: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplyHandler(t *testing.T) {
|
|
||||||
t.Run("Sends a reply", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AddMergeRequestDiscussionNote(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
testReplyRequest.DiscussionId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(&gitlab.Note{}, makeResponse(http.StatusOK), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/reply", testReplyRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ReplyResponse{})
|
|
||||||
assert(t, data.SuccessResponse.Message, "Replied to comment")
|
|
||||||
assert(t, data.SuccessResponse.Status, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Disallows non-POST methods", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPut, "/mr/reply", testReplyRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkBadMethod(t, *data, http.MethodPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles errors from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AddMergeRequestDiscussionNote(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
testReplyRequest.DiscussionId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(nil, nil, errorFromGitlab)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/reply", testReplyRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkErrorFromGitlab(t, *data, "Could not leave reply")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
|
|
||||||
client := mock_main.NewMockClient(t)
|
|
||||||
mock_main.WithMr(t, client)
|
|
||||||
client.EXPECT().AddMergeRequestDiscussionNote(
|
|
||||||
"",
|
|
||||||
mock_main.MergeId,
|
|
||||||
testReplyRequest.DiscussionId,
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(nil, makeResponse(http.StatusSeeOther), nil)
|
|
||||||
|
|
||||||
request := makeRequest(t, http.MethodPost, "/mr/reply", testReplyRequest)
|
|
||||||
server, _ := CreateRouterAndApi(client)
|
|
||||||
|
|
||||||
data := serveRequest(t, server, request, ErrorResponse{})
|
|
||||||
checkNon200(t, *data, "Could not leave reply", "/mr/reply")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
222
cmd/server.go
222
cmd/server.go
@@ -1,222 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
startSever starts the server and runs concurrent goroutines
|
|
||||||
to handle potential shutdown requests and incoming HTTP requests.
|
|
||||||
*/
|
|
||||||
func startServer(client *Client, projectInfo *ProjectInfo, gitInfo GitProjectInfo) {
|
|
||||||
|
|
||||||
m, a := CreateRouterAndApi(client,
|
|
||||||
func(a *Api) error {
|
|
||||||
a.projectInfo = projectInfo
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
func(a *Api) error {
|
|
||||||
a.fileReader = attachmentReader{}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
func(a *Api) error {
|
|
||||||
a.gitInfo = &gitInfo
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
func(a *Api) error {
|
|
||||||
err := attachEmojisToApi(a)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
func(a *Api) error {
|
|
||||||
a.gitInfo.GetLatestCommitOnRemote = GetLatestCommitOnRemote
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
l := createListener()
|
|
||||||
server := &http.Server{Handler: m}
|
|
||||||
|
|
||||||
/* Starts the Go server */
|
|
||||||
go func() {
|
|
||||||
err := server.Serve(l)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, http.ErrServerClosed) {
|
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "Server did not respond: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
port := l.Addr().(*net.TCPAddr).Port
|
|
||||||
err := checkServer(port)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Server did not respond: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This print is detected by the Lua code */
|
|
||||||
fmt.Println("Server started on port: ", port)
|
|
||||||
|
|
||||||
/* Handles shutdown requests */
|
|
||||||
<-a.sigCh
|
|
||||||
err = server.Shutdown(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Server could not shut down gracefully: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
} else {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
The Api struct contains common configuration that's accessible to all handlers, such as the gitlab
|
|
||||||
client, the project information, and the channels for signaling error or shutdown requests
|
|
||||||
|
|
||||||
The handlers for different Gitlab operations are are all methods on the Api struct and interact
|
|
||||||
with the client value, which is a go-gitlab client.
|
|
||||||
*/
|
|
||||||
type Api struct {
|
|
||||||
client ClientInterface
|
|
||||||
projectInfo *ProjectInfo
|
|
||||||
gitInfo *GitProjectInfo
|
|
||||||
fileReader FileReader
|
|
||||||
emojiMap EmojiMap
|
|
||||||
sigCh chan os.Signal
|
|
||||||
}
|
|
||||||
|
|
||||||
type optFunc func(a *Api) error
|
|
||||||
|
|
||||||
/*
|
|
||||||
CreateRouterAndApi wires up the router and attaches all handlers to their respective routes. It also
|
|
||||||
iterates over all option functions to configure API fields such as the project information and default
|
|
||||||
file reader functionality
|
|
||||||
*/
|
|
||||||
|
|
||||||
func CreateRouterAndApi(client ClientInterface, optFuncs ...optFunc) (*http.ServeMux, Api) {
|
|
||||||
m := http.NewServeMux()
|
|
||||||
a := Api{
|
|
||||||
client: client,
|
|
||||||
projectInfo: &ProjectInfo{},
|
|
||||||
gitInfo: &GitProjectInfo{},
|
|
||||||
fileReader: nil,
|
|
||||||
emojiMap: EmojiMap{},
|
|
||||||
sigCh: make(chan os.Signal, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mutates the API struct as necessary with configuration functions */
|
|
||||||
for _, optFunc := range optFuncs {
|
|
||||||
err := optFunc(&a)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.HandleFunc("/mr/approve", a.withMr(a.approveHandler))
|
|
||||||
m.HandleFunc("/mr/comment", a.withMr(a.commentHandler))
|
|
||||||
m.HandleFunc("/mr/merge", a.withMr(a.acceptAndMergeHandler))
|
|
||||||
m.HandleFunc("/mr/discussions/list", a.withMr(a.listDiscussionsHandler))
|
|
||||||
m.HandleFunc("/mr/discussions/resolve", a.withMr(a.discussionsResolveHandler))
|
|
||||||
m.HandleFunc("/mr/info", a.withMr(a.infoHandler))
|
|
||||||
m.HandleFunc("/mr/assignee", a.withMr(a.assigneesHandler))
|
|
||||||
m.HandleFunc("/mr/summary", a.withMr(a.summaryHandler))
|
|
||||||
m.HandleFunc("/mr/reviewer", a.withMr(a.reviewersHandler))
|
|
||||||
m.HandleFunc("/mr/revisions", a.withMr(a.revisionsHandler))
|
|
||||||
m.HandleFunc("/mr/reply", a.withMr(a.replyHandler))
|
|
||||||
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)
|
|
||||||
m.HandleFunc("/users/me", a.meHandler)
|
|
||||||
m.HandleFunc("/attachment", a.attachmentHandler)
|
|
||||||
m.HandleFunc("/create_mr", a.createMr)
|
|
||||||
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))
|
|
||||||
|
|
||||||
return m, a
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Used to check whether the server has started yet */
|
|
||||||
func pingHandler(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintln(w, "pong")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* checkServer pings the server repeatedly for 1 full second after startup in order to notify the plugin that the server is ready */
|
|
||||||
func checkServer(port int) error {
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
resp, err := http.Get("http://localhost:" + fmt.Sprintf("%d", port) + "/ping")
|
|
||||||
if resp.StatusCode == 200 && err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Microsecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("Could not start server!")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Creates a TCP listener on the port specified by the user or a random port */
|
|
||||||
func createListener() (l net.Listener) {
|
|
||||||
addr := fmt.Sprintf("localhost:%d", pluginOptions.Port)
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
/* withMr is a Middlware that gets the current merge request ID and attaches it to the projectInfo */
|
|
||||||
func (a *Api) withMr(f func(w http.ResponseWriter, r *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
if a.projectInfo.MergeId != 0 {
|
|
||||||
f(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
options := gitlab.ListProjectMergeRequestsOptions{
|
|
||||||
Scope: gitlab.Ptr("all"),
|
|
||||||
State: gitlab.Ptr("opened"),
|
|
||||||
SourceBranch: &a.gitInfo.BranchName,
|
|
||||||
}
|
|
||||||
|
|
||||||
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, fmt.Errorf("No merge requests found for branch '%s'", a.gitInfo.BranchName), "No merge requests found", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeId := strconv.Itoa(mergeRequests[0].IID)
|
|
||||||
mergeIdInt, err := strconv.Atoi(mergeId)
|
|
||||||
if err != nil {
|
|
||||||
handleError(w, err, "Could not convert merge ID to integer", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a.projectInfo.MergeId = mergeIdInt
|
|
||||||
f(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
cmd/types.go
69
cmd/types.go
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrorResponse struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Details string `json:"details"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SuccessResponse struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenericError struct {
|
|
||||||
endpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e GenericError) Error() string {
|
|
||||||
return fmt.Sprintf("An error occurred on the %s endpoint", e.endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
type InvalidRequestError struct{}
|
|
||||||
|
|
||||||
func (e InvalidRequestError) Error() string {
|
|
||||||
return "Invalid request type"
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The ClientInterface interface implements all the methods that our handlers need */
|
|
||||||
type ClientInterface interface {
|
|
||||||
CreateMergeRequest(pid interface{}, opt *gitlab.CreateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
|
||||||
ListProjectMergeRequests(pid interface{}, opt *gitlab.ListProjectMergeRequestsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequest, *gitlab.Response, error)
|
|
||||||
GetMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
|
||||||
AcceptMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.AcceptMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
|
||||||
UpdateMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
|
|
||||||
UploadFile(pid interface{}, content io.Reader, filename string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectFile, *gitlab.Response, error)
|
|
||||||
GetMergeRequestDiffVersions(pid interface{}, mergeRequestIID int, opt *gitlab.GetMergeRequestDiffVersionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequestDiffVersion, *gitlab.Response, error)
|
|
||||||
ApproveMergeRequest(pid interface{}, mergeRequestIID int, opt *gitlab.ApproveMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequestApprovals, *gitlab.Response, error)
|
|
||||||
UnapproveMergeRequest(pid interface{}, mergeRequestIID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
|
||||||
ListMergeRequestDiscussions(pid interface{}, mergeRequestIID int, opt *gitlab.ListMergeRequestDiscussionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Discussion, *gitlab.Response, error)
|
|
||||||
ResolveMergeRequestDiscussion(pid interface{}, mergeRequestIID int, discussion string, opt *gitlab.ResolveMergeRequestDiscussionOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Discussion, *gitlab.Response, error)
|
|
||||||
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)
|
|
||||||
ListPipelineJobs(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error)
|
|
||||||
ListProjectPipelines(pid interface{}, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error)
|
|
||||||
GetTraceFile(pid interface{}, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error)
|
|
||||||
ListLabels(pid interface{}, opt *gitlab.ListLabelsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Label, *gitlab.Response, error)
|
|
||||||
ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error)
|
|
||||||
CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, opt *gitlab.CreateAwardEmojiOptions, options ...gitlab.RequestOptionFunc) (*gitlab.AwardEmoji, *gitlab.Response, error)
|
|
||||||
DeleteMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID, awardID int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
|
|
||||||
CurrentUser(options ...gitlab.RequestOptionFunc) (*gitlab.User, *gitlab.Response, error)
|
|
||||||
}
|
|
||||||
10
cmd/utils.go
10
cmd/utils.go
@@ -1,10 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
func Contains[T comparable](elems []T, v T) int {
|
|
||||||
for i, s := range elems {
|
|
||||||
if v == s {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
@@ -739,22 +739,39 @@ Choose a merge request from a list of those open in your current project to
|
|||||||
review. This command will automatically check out the feature branch locally
|
review. This command will automatically check out the feature branch locally
|
||||||
and open the reviewer pane (this can be overridden with the `open_reviewer`
|
and open the reviewer pane (this can be overridden with the `open_reviewer`
|
||||||
parameter.
|
parameter.
|
||||||
|
|
||||||
You can also filter merge requests by specifying `label` and `notlabel`
|
You can also filter merge requests by specifying `label` and `notlabel`
|
||||||
parameters.
|
parameters, or any other parameter included in list MRs API.
|
||||||
|
|
||||||
|
By default, the endpoint will return all open merge requests.
|
||||||
>lua
|
>lua
|
||||||
require("gitlab").choose_merge_request()
|
require("gitlab").choose_merge_request()
|
||||||
require("gitlab").choose_merge_request({ open_reviewer = false })
|
require("gitlab").choose_merge_request({ open_reviewer = false })
|
||||||
require("gitlab").choose_merge_request({ label = {"include_mrs_with_label"} })
|
require("gitlab").choose_merge_request({ labels = {"include_mrs_with_label"} })
|
||||||
require("gitlab").choose_merge_request({ notlabel = {"exclude_mrs_with_label"} })
|
require("gitlab").choose_merge_request({ ["[not]labels"] = {"exclude_mrs_with_label"} })
|
||||||
<
|
<
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {opts}: (table|nil) Keyword arguments to configure the checkout.
|
• {opts}: (table|nil)
|
||||||
• {open_reviewer}: (boolean) Whether to open the reviewer after
|
• {open_reviewer}: (boolean) Whether to open the reviewer after
|
||||||
switching branches. True by default.
|
switching branches. True by default.
|
||||||
• {label}: (table<string>) Return merge requests with *including* matching labels
|
• {labels}: (table<string>) Return merge requests with *including* matching labels
|
||||||
• {notlabel}: (table<string>) Return merge requests *excluding*
|
• Etc, see: https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests
|
||||||
matching label
|
|
||||||
|
*gitlab.nvim.choose_merge_request*
|
||||||
|
gitlab.choose_merge_request_by_username({opts}) ~
|
||||||
|
|
||||||
|
Choose a merge request based on the username provided. Like the `choose_merge_request`
|
||||||
|
action, this will automatically check out the branch locally and open the
|
||||||
|
the reviewer pane when the MR is chosen.
|
||||||
|
>lua
|
||||||
|
require("gitlab").choose_merge_request_by_username({ username = "hcramer" })
|
||||||
<
|
<
|
||||||
|
Parameters: ~
|
||||||
|
• {opts}: (table|nil)
|
||||||
|
• {username}: (string) The username of the Gitlab user, must be a
|
||||||
|
member of the current project.
|
||||||
|
• {state} [optional]: (string) The status of the MR, e.g. "opened" or "all"
|
||||||
|
|
||||||
*gitlab.nvim.review*
|
*gitlab.nvim.review*
|
||||||
gitlab.review() ~
|
gitlab.review() ~
|
||||||
|
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -1,23 +1,19 @@
|
|||||||
module gitlab.com/harrisoncramer/gitlab.nvim
|
module github.com/harrisoncramer/gitlab.nvim
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
github.com/xanzy/go-gitlab v0.102.0
|
github.com/xanzy/go-gitlab v0.108.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
|
||||||
golang.org/x/mod v0.11.0 // indirect
|
|
||||||
golang.org/x/net v0.8.0 // indirect
|
golang.org/x/net v0.8.0 // indirect
|
||||||
golang.org/x/oauth2 v0.6.0 // indirect
|
golang.org/x/oauth2 v0.6.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.2.0 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.29.1 // indirect
|
google.golang.org/protobuf v1.29.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
@@ -11,36 +11,28 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
|||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
|
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
|
github.com/xanzy/go-gitlab v0.108.0 h1:IEvEUWFR5G1seslRhJ8gC//INiIUqYXuSUoBd7/gFKE=
|
||||||
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
|
github.com/xanzy/go-gitlab v0.108.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
|
||||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
|
||||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ M.choose_merge_request = function(opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
|
state.chosen_target_branch = choice.target_branch
|
||||||
require("gitlab.server").restart(function()
|
require("gitlab.server").restart(function()
|
||||||
if opts.open_reviewer then
|
if opts.open_reviewer then
|
||||||
require("gitlab").review()
|
require("gitlab").review()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ local project_members = state.dependencies.project_members
|
|||||||
local latest_pipeline = state.dependencies.latest_pipeline
|
local latest_pipeline = state.dependencies.latest_pipeline
|
||||||
local revisions = state.dependencies.revisions
|
local revisions = state.dependencies.revisions
|
||||||
local merge_requests_dep = state.dependencies.merge_requests
|
local merge_requests_dep = state.dependencies.merge_requests
|
||||||
|
local merge_requests_by_username_dep = state.dependencies.merge_requests_by_username
|
||||||
local draft_notes_dep = state.dependencies.draft_notes
|
local draft_notes_dep = state.dependencies.draft_notes
|
||||||
local discussion_data = state.dependencies.discussion_data
|
local discussion_data = state.dependencies.discussion_data
|
||||||
|
|
||||||
@@ -102,6 +103,10 @@ return {
|
|||||||
data = data.data,
|
data = data.data,
|
||||||
print_settings = state.print_settings,
|
print_settings = state.print_settings,
|
||||||
choose_merge_request = async.sequence({ merge_requests_dep }, merge_requests.choose_merge_request),
|
choose_merge_request = async.sequence({ merge_requests_dep }, merge_requests.choose_merge_request),
|
||||||
|
choose_merge_request_by_username = async.sequence(
|
||||||
|
{ project_members, merge_requests_by_username_dep },
|
||||||
|
merge_requests.choose_merge_request
|
||||||
|
),
|
||||||
open_in_browser = async.sequence({ info }, function()
|
open_in_browser = async.sequence({ info }, function()
|
||||||
local web_url = u.get_web_url()
|
local web_url = u.get_web_url()
|
||||||
if web_url ~= nil then
|
if web_url ~= nil then
|
||||||
|
|||||||
@@ -66,6 +66,14 @@ M.open = function()
|
|||||||
u.notify("This merge request has conflicts!", vim.log.levels.WARN)
|
u.notify("This merge request has conflicts!", vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if state.INFO.state == "closed" then
|
||||||
|
u.notify(string.format("This MR was closed on %s", u.format_date(state.INFO.closed_at)), vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
|
||||||
|
if state.INFO.state == "merged" then
|
||||||
|
u.notify(string.format("This MR was merged on %s", u.format_date(state.INFO.merged_at)), vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
|
||||||
if state.settings.discussion_diagnostic ~= nil or state.settings.discussion_sign ~= nil then
|
if state.settings.discussion_diagnostic ~= nil or state.settings.discussion_sign ~= nil then
|
||||||
u.notify(
|
u.notify(
|
||||||
"Diagnostics are now configured as settings.discussion_signs, see :h gitlab.nvim.signs-and-diagnostics",
|
"Diagnostics are now configured as settings.discussion_signs, see :h gitlab.nvim.signs-and-diagnostics",
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ M.start = function(callback)
|
|||||||
debug = state.settings.debug,
|
debug = state.settings.debug,
|
||||||
log_path = state.settings.log_path,
|
log_path = state.settings.log_path,
|
||||||
connection_settings = state.settings.connection_settings,
|
connection_settings = state.settings.connection_settings,
|
||||||
|
chosen_target_branch = state.chosen_target_branch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.chosen_target_branch = nil -- Do not let this interfere with subsequent reviewer.open() calls
|
||||||
|
|
||||||
local settings = vim.json.encode(go_server_settings)
|
local settings = vim.json.encode(go_server_settings)
|
||||||
local command = string.format("%s '%s'", state.settings.bin, settings)
|
local command = string.format("%s '%s'", state.settings.bin, settings)
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ M.shutdown = function(cb)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Restarts the Go server and clears out all gitlab.nvim state
|
---Restarts the Go server and clears out all gitlab.nvim state
|
||||||
M.restart = function(cb)
|
M.restart = function(cb)
|
||||||
if not state.go_server_running then
|
if not state.go_server_running then
|
||||||
vim.notify("The gitlab.nvim server is not running", vim.log.levels.ERROR)
|
vim.notify("The gitlab.nvim server is not running", vim.log.levels.ERROR)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
local git = require("gitlab.git")
|
local git = require("gitlab.git")
|
||||||
local u = require("gitlab.utils")
|
local u = require("gitlab.utils")
|
||||||
|
local List = require("gitlab.utils.list")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.emoji_map = nil
|
M.emoji_map = nil
|
||||||
@@ -43,6 +44,7 @@ end
|
|||||||
--- These are the default settings for the plugin
|
--- These are the default settings for the plugin
|
||||||
M.settings = {
|
M.settings = {
|
||||||
auth_provider = M.default_auth_provider,
|
auth_provider = M.default_auth_provider,
|
||||||
|
file_separator = u.path_separator,
|
||||||
port = nil, -- choose random port
|
port = nil, -- choose random port
|
||||||
debug = {
|
debug = {
|
||||||
go_request = false,
|
go_request = false,
|
||||||
@@ -247,6 +249,10 @@ M.unlinked_discussion_tree = {
|
|||||||
unresolved_expanded = false,
|
unresolved_expanded = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Used to set a specific target when choosing a merge request, due to the fact
|
||||||
|
-- that it's technically possible to have multiple target branches
|
||||||
|
M.chosen_target_branch = nil
|
||||||
|
|
||||||
-- These keymaps are set globally when the plugin is initialized
|
-- These keymaps are set globally when the plugin is initialized
|
||||||
M.set_global_keymaps = function()
|
M.set_global_keymaps = function()
|
||||||
local keymaps = M.settings.keymaps
|
local keymaps = M.settings.keymaps
|
||||||
@@ -381,7 +387,6 @@ end
|
|||||||
---@return Settings
|
---@return Settings
|
||||||
M.merge_settings = function(args)
|
M.merge_settings = function(args)
|
||||||
M.settings = u.merge(M.settings, args)
|
M.settings = u.merge(M.settings, args)
|
||||||
M.settings.file_separator = (u.is_windows() and "\\" or "/")
|
|
||||||
return M.settings
|
return M.settings
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -561,18 +566,35 @@ M.dependencies = {
|
|||||||
refresh = true,
|
refresh = true,
|
||||||
method = "POST",
|
method = "POST",
|
||||||
body = function(opts)
|
body = function(opts)
|
||||||
local listArgs = {
|
if opts then
|
||||||
label = opts and opts.label or {},
|
opts.open_reviewer_field = nil
|
||||||
notlabel = opts and opts.notlabel or {},
|
|
||||||
}
|
|
||||||
for k, v in pairs(listArgs) do
|
|
||||||
listArgs[k] = v
|
|
||||||
end
|
end
|
||||||
return listArgs
|
if opts and opts.notlabel then -- Legacy: Migrate use of notlabel to not[label], per API
|
||||||
|
opts["not[label]"] = opts.notlabel
|
||||||
|
opts.notlabel = nil
|
||||||
|
end
|
||||||
|
return opts or vim.json.decode("{}")
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
merge_requests_by_username = {
|
||||||
|
endpoint = "/merge_requests_by_username",
|
||||||
|
key = "merge_requests",
|
||||||
|
state = "MERGE_REQUESTS",
|
||||||
|
refresh = true,
|
||||||
|
method = "POST",
|
||||||
|
body = function(opts)
|
||||||
|
local members = List.new(M.PROJECT_MEMBERS)
|
||||||
|
local user = members:find(function(usr)
|
||||||
|
return usr.username == opts.username
|
||||||
|
end)
|
||||||
|
if user == nil then
|
||||||
|
error("Invalid payload, user could not be found!")
|
||||||
|
end
|
||||||
|
opts.user_id = user.id
|
||||||
|
return opts
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
discussion_data = {
|
discussion_data = {
|
||||||
-- key is missing here...
|
|
||||||
endpoint = "/mr/discussions/list",
|
endpoint = "/mr/discussions/list",
|
||||||
state = "DISCUSSION_DATA",
|
state = "DISCUSSION_DATA",
|
||||||
refresh = false,
|
refresh = false,
|
||||||
|
|||||||
2
makefile
2
makefile
@@ -7,7 +7,7 @@ compile:
|
|||||||
@cd cmd && go build -o bin && mv bin ../bin
|
@cd cmd && go build -o bin && mv bin ../bin
|
||||||
## test: run golang project tests
|
## test: run golang project tests
|
||||||
test:
|
test:
|
||||||
@cd cmd && go test
|
@cd cmd/app && go test
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
all: help
|
all: help
|
||||||
|
|||||||
Reference in New Issue
Block a user