diff --git a/.gitignore b/.gitignore index d69b87f6..d9592b47 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,3 @@ # Dependency directories (remove the comment below to include it) # vendor/ .idea/ -jira/examples/ -/jira/examples/ diff --git a/jira/examples/issue/comment/add/add.go b/jira/examples/issue/comment/add/add.go new file mode 100644 index 00000000..ded4c5a7 --- /dev/null +++ b/jira/examples/issue/comment/add/add.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "github.com/ctreminiom/go-atlassian/jira" + "log" + "os" +) + +func main() { + + var ( + host = os.Getenv("HOST") + mail = os.Getenv("MAIL") + token = os.Getenv("TOKEN") + ) + + atlassian, err := jira.New(nil, host) + if err != nil { + return + } + + atlassian.Auth.SetBasicAuth(mail, token) + atlassian.Auth.SetUserAgent("curl/7.54.0") + + commentBody := jira.CommentNodeScheme{} + commentBody.Version = 1 + commentBody.Type = "doc" + + commentBody.AppendNode(&jira.CommentNodeScheme{ + Type: "paragraph", + Content: []*jira.CommentNodeScheme{ + { + Type: "text", + Text: "Carlos Test", + }, + { + Type: "emoji", + Attrs: map[string]interface{}{ + "shortName": ":grin", + "id": "1f601", + "text": "😁", + }, + }, + { + Type: "text", + Text: " ", + }, + }, + }) + + newComment, response, err := atlassian.Issue.Comment.Add(context.Background(), "KP-2", "role", "Administrators", &commentBody, nil) + if err != nil { + if response != nil { + log.Println("Response HTTP Response", string(response.BodyAsBytes)) + } + log.Fatal(err) + } + + log.Println("Response HTTP Code", response.StatusCode) + log.Println("HTTP Endpoint Used", response.Endpoint) + + log.Println(newComment.ID) +} diff --git a/jira/issueComment.go b/jira/issueComment.go index 28b2f760..7a6de017 100644 --- a/jira/issueComment.go +++ b/jira/issueComment.go @@ -160,3 +160,84 @@ func (c *CommentService) Delete(ctx context.Context, issueKeyOrID, commentID str return } + +func (c *CommentService) Add(ctx context.Context, issueKeyOrID, visibilityType, visibilityValue string, body *CommentNodeScheme, expands []string) (result *CommentScheme, response *Response, err error) { + + if len(issueKeyOrID) == 0 { + return nil, nil, fmt.Errorf("error, please provide a valid issueKeyOrID value") + } + + if body == nil { + return nil, nil, fmt.Errorf("error, please provide a valid CommentNodeScheme pointer") + } + + var commentPayload = map[string]interface{}{} + commentPayload["body"] = body + + var visibilityPayload = map[string]interface{}{} + visibilityPayload["type"] = visibilityType + visibilityPayload["value"] = visibilityValue + + commentPayload["visibility"] = visibilityPayload + + params := url.Values{} + var expand string + for index, value := range expands { + + if index == 0 { + expand = value + continue + } + + expand += "," + value + } + + if len(expand) != 0 { + params.Add("expand", expand) + } + + var endpoint string + if len(params.Encode()) != 0 { + endpoint = fmt.Sprintf("rest/api/3/issue/%v/comment?%v", issueKeyOrID, params.Encode()) + } else { + endpoint = fmt.Sprintf("rest/api/3/issue/%v/comment", issueKeyOrID) + } + + request, err := c.client.newRequest(ctx, http.MethodPost, endpoint, &commentPayload) + if err != nil { + return + } + + request.Header.Set("Accept", "application/json") + request.Header.Set("Content-Type", "application/json") + + response, err = c.client.Do(request) + if err != nil { + return + } + + result = new(CommentScheme) + if err = json.Unmarshal(response.BodyAsBytes, &result); err != nil { + return + } + + return +} + +type CommentNodeScheme struct { + Version int `json:"version,omitempty"` + Type string `json:"type,omitempty"` + Content []*CommentNodeScheme `json:"content,omitempty"` + Text string `json:"text,omitempty"` + Attrs map[string]interface{} `json:"attrs,omitempty"` + Marks []*MarkScheme `json:"marks,omitempty"` +} + +func (n *CommentNodeScheme) AppendNode(node *CommentNodeScheme) { + n.Content = append(n.Content, node) +} + +type MarkScheme struct { + Type string `json:"type"` + Attrs map[string]interface{} `json:"attrs"` +} diff --git a/jira/issueComment_test.go b/jira/issueComment_test.go index 340c735f..380e6578 100644 --- a/jira/issueComment_test.go +++ b/jira/issueComment_test.go @@ -293,6 +293,28 @@ func TestCommentService_Delete(t *testing.T) { wantHTTPCodeReturn: http.StatusBadRequest, wantErr: true, }, + + { + name: "DeleteIssueCommentWhenTheContextIsNil", + issueKeyOrID: "DUMMY-3", + commentID: "10001", + wantHTTPMethod: http.MethodDelete, + endpoint: "/rest/api/3/issue/DUMMY-3/comment/10001", + context: nil, + wantHTTPCodeReturn: http.StatusNoContent, + wantErr: true, + }, + + { + name: "DeleteIssueCommentWhenTheEndpointIsEmpty", + issueKeyOrID: "DUMMY-3", + commentID: "10001", + wantHTTPMethod: http.MethodDelete, + endpoint: "", + context: context.Background(), + wantHTTPCodeReturn: http.StatusNoContent, + wantErr: true, + }, } for _, testCase := range testCases { @@ -540,3 +562,259 @@ func TestCommentService_Get(t *testing.T) { } } + +func TestCommentService_Add(t *testing.T) { + + commentBody := CommentNodeScheme{} + commentBody.Version = 1 + commentBody.Type = "doc" + + commentBody.AppendNode(&CommentNodeScheme{ + Type: "paragraph", + Content: []*CommentNodeScheme{ + { + Type: "text", + Text: "Carlos Test", + Marks: []*MarkScheme{ + { + Type: "strong", + }, + }, + }, + { + Type: "emoji", + Attrs: map[string]interface{}{ + "shortName": ":grin", + "id": "1f601", + "text": "😁", + }, + }, + { + Type: "text", + Text: " ", + }, + }, + }) + + testCases := []struct { + name string + issueKeyOrID, visibilityType, visibilityValue string + body *CommentNodeScheme + expands []string + mockFile string + wantHTTPMethod string + endpoint string + context context.Context + wantHTTPCodeReturn int + wantErr bool + }{ + { + name: "AddIssueCommentWhenTheParametersAreCorrect", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: []string{"renderedBody", "comment.id"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody%2Ccomment.id", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: false, + }, + + { + name: "AddIssueCommentWhenTheResponseBodyHasADifferentFormat", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: []string{"renderedBody"}, + mockFile: "./mocks/empty_json.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: true, + }, + + { + name: "AddIssueCommentWhenTheIssueKeyIsNotSet", + issueKeyOrID: "", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: []string{"renderedBody"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: true, + }, + + { + name: "AddIssueCommentWhenTheCommentBodyIsNotSet", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: nil, + expands: []string{"renderedBody"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: true, + }, + + { + name: "AddIssueCommentWhenTheVisibilityIsNotSet", + issueKeyOrID: "DUMMY-3", + visibilityType: "", + visibilityValue: "", + body: &commentBody, + expands: []string{"renderedBody"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: false, + }, + + { + name: "AddIssueCommentWhenTheExpandsAreNotSet", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: nil, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: true, + }, + + { + name: "AddIssueCommentWhenTheRequestMethodIsIncorrect", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: []string{"renderedBody"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodDelete, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusCreated, + wantErr: true, + }, + + { + name: "AddIssueCommentWhenTheStatusCodeIsIncorrect", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: []string{"renderedBody"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: context.Background(), + wantHTTPCodeReturn: http.StatusBadRequest, + wantErr: true, + }, + + { + name: "AddIssueCommentWhenTheContextIsNil", + issueKeyOrID: "DUMMY-3", + visibilityType: "role", + visibilityValue: "Administrators", + body: &commentBody, + expands: []string{"renderedBody"}, + mockFile: "./mocks/get-issue-comment-by-id.json", + wantHTTPMethod: http.MethodPost, + endpoint: "/rest/api/3/issue/DUMMY-3/comment?expand=renderedBody", + context: nil, + wantHTTPCodeReturn: http.StatusCreated, + wantErr: true, + }, + } + + for _, testCase := range testCases { + + t.Run(testCase.name, func(t *testing.T) { + + //Init a new HTTP mock server + mockOptions := mockServerOptions{ + Endpoint: testCase.endpoint, + MockFilePath: testCase.mockFile, + MethodAccepted: testCase.wantHTTPMethod, + ResponseCodeWanted: testCase.wantHTTPCodeReturn, + } + + mockServer, err := startMockServer(&mockOptions) + if err != nil { + t.Fatal(err) + } + + defer mockServer.Close() + + //Init the library instance + mockClient, err := startMockClient(mockServer.URL) + if err != nil { + t.Fatal(err) + } + + i := &CommentService{client: mockClient} + + gotResult, gotResponse, err := i.Add(testCase.context, + testCase.issueKeyOrID, testCase.visibilityType, + testCase.visibilityValue, testCase.body, + testCase.expands) + + if testCase.wantErr { + + if err != nil { + t.Logf("error returned: %v", err.Error()) + } + + assert.Error(t, err) + } else { + + assert.NoError(t, err) + assert.NotEqual(t, gotResponse, nil) + assert.NotEqual(t, gotResult, nil) + + apiEndpoint, err := url.Parse(gotResponse.Endpoint) + if err != nil { + t.Fatal(err) + } + + var endpointToAssert string + + if apiEndpoint.Query().Encode() != "" { + endpointToAssert = fmt.Sprintf("%v?%v", apiEndpoint.Path, apiEndpoint.Query().Encode()) + } else { + endpointToAssert = apiEndpoint.Path + } + + t.Logf("HTTP Endpoint Wanted: %v, HTTP Endpoint Returned: %v", testCase.endpoint, endpointToAssert) + assert.Equal(t, testCase.endpoint, endpointToAssert) + + t.Log("-------------------------") + t.Logf("Comment ID: %v", gotResult.ID) + t.Logf("Comment Created: %v", gotResult.Created) + t.Logf("Comment Author EmailAddress: %v", gotResult.Author.EmailAddress) + t.Logf("Comment Author AccountID: %v", gotResult.Author.AccountID) + t.Log("------------------------- \n") + + } + }) + + } + +}