diff --git a/api_response.go b/api_response.go index 77b4935..27dd37b 100644 --- a/api_response.go +++ b/api_response.go @@ -410,23 +410,108 @@ type Parent struct { ID int `json:"id"` } +type DiffType string + +const ( + DiffTypeEffective DiffType = "EFFECTIVE" + DiffTypeCommit DiffType = "COMMIT" + DiffTypeRange DiffType = "RANGE" +) + +type LineType string + +const ( + LineTypeAdded LineType = "ADDED" + LineTypeRemoved LineType = "REMOVED" + LineTypeContext LineType = "CONTEXT" +) + +type FileType string + +const ( + FileTypeFrom FileType = "FROM" + FileTypeTo FileType = "TO" +) + type Anchor struct { - DiffType string `json:"diffType,omitempty"` - Line int `json:"line,omitempty"` - LineType string `json:"lineType,omitempty"` - FileType string `json:"fileType,omitempty"` - FromHash string `json:"fromHash,omitempty"` - Path string `json:"path,omitempty"` - SrcPath string `json:"srcPath,omitempty"` - ToHash string `json:"toHash,omitempty"` + DiffType DiffType `json:"diffType,omitempty"` + Line int `json:"line,omitempty"` + LineType LineType `json:"lineType,omitempty"` + FileType FileType `json:"fileType,omitempty"` + FromHash string `json:"fromHash,omitempty"` + Path string `json:"path,omitempty"` + SrcPath string `json:"srcPath,omitempty"` + ToHash string `json:"toHash,omitempty"` } type Comment struct { - Text string `json:"text"` + Text string `json:"text"` Parent *Parent `json:"parent,omitempty"` Anchor *Anchor `json:"anchor,omitempty"` } +type Properties struct { + Key string `json:"key"` +} + +type PermittedOperations struct { + Editable bool `json:"editable"` + Deletable bool `json:"deletable"` +} + +type ActivityComment struct { + Properties Properties `json:"properties"` + ID int `json:"id"` + Version int `json:"version"` + Text string `json:"text"` + Author User `json:"author"` + CreatedDate int64 `json:"createdDate"` + UpdatedDate int64 `json:"updatedDate"` + Comments []ActivityComment `json:"comments"` + PermittedOperations PermittedOperations `json:"permittedOperations"` +} + +type CommitsStats struct { + Commits []Commit `json:"commits"` + Total int `json:"total"` +} + +type Action string + +const ( + ActionCommented Action = "COMMENTED" + ActionRescoped Action = "RESCOPED" + ActionMerged Action = "MERGED" + ActionApproved Action = "APPROVED" + ActionDeclined Action = "DECLINED" + ActionOpened Action = "OPENED" +) + +type Activity struct { + ID int `json:"id"` + CreatedDate int `json:"createdDate"` + User User `json:"user"` + Action Action `json:"action"` + CommentAction string `json:"commentAction"` + Comment ActivityComment `json:"comment"` + CommentAnchor Anchor `json:"commentAnchor"` + FromHash string `json:"fromHash,omitempty"` + PreviousFromHash string `json:"previousFromHash,omitempty"` + PreviousToHash string `json:"previousToHash,omitempty"` + ToHash string `json:"toHash,omitempty"` + Added CommitsStats `json:"added"` + Removed CommitsStats `json:"removed"` +} + +type Activities struct { + NextPageStart int `json:"nextPageStart"` + IsLastPage bool `json:"isLastPage"` + Limit int `json:"limit"` + Size int `json:"size"` + Start int `json:"start"` + Values []Activity `json:"values"` +} + // String converts global permission to its string representation func (p PermissionGlobal) String() string { return string(p) @@ -567,6 +652,13 @@ func GetSearchResultResponse(r *APIResponse) (SearchResult, error) { return h, err } +// GetActivitiesResponse cast Activities results into structure +func GetActivitiesResponse(r *APIResponse) (Activities, error) { + var h Activities + err := json.Unmarshal(r.Payload, &h) + return h, err +} + // NewAPIResponse create new APIResponse from http.Response func NewAPIResponse(r *http.Response) *APIResponse { diff --git a/api_response_test.go b/api_response_test.go index 9d1f198..9102ced 100644 --- a/api_response_test.go +++ b/api_response_test.go @@ -1108,3 +1108,392 @@ func TestGetSearchResultResponse(t *testing.T) { }) } } + +func TestGetActivitiesResponse(t *testing.T) { + type args struct { + r *APIResponse + } + tests := []struct { + name string + args args + want Activities + wantErr bool + }{ + { + name: "Empty list", + args: args{ + r: &APIResponse{ + Payload: []byte(`{ + "size": 0, + "limit": 0, + "isLastPage": false, + "values": [], + "start": 0 +}`), + }, + }, + want: Activities{Values: []Activity{}}, + }, + { + name: "Bad response", + args: args{ + r: &APIResponse{ + Payload: []byte(`[]`), + }, + }, + wantErr: true, + }, + { + name: "Good response", + args: args{ + r: &APIResponse{ + Payload: []byte(`{ + "size": 3, + "limit": 25, + "isLastPage": true, + "values": [ + { + "id": 101, + "createdDate": 1359065920, + "user": { + "name": "jcitizen", + "emailAddress": "jane@example.com", + "id": 101, + "displayName": "Jane Citizen", + "active": true, + "slug": "jcitizen", + "type": "NORMAL" + }, + "action": "COMMENTED", + "commentAction": "ADDED", + "comment": { + "properties": { + "key": "value" + }, + "id": 1, + "version": 1, + "text": "A measured reply.", + "author": { + "name": "jcitizen", + "emailAddress": "jane@example.com", + "id": 101, + "displayName": "Jane Citizen", + "active": true, + "slug": "jcitizen", + "type": "NORMAL" + }, + "createdDate": 1548720847370, + "updatedDate": 1548720847370, + "comments": [ + { + "properties": { + "key": "value" + }, + "id": 1, + "version": 1, + "text": "An insightful comment.", + "author": { + "name": "jcitizen", + "emailAddress": "jane@example.com", + "id": 101, + "displayName": "Jane Citizen", + "active": true, + "slug": "jcitizen", + "type": "NORMAL" + }, + "createdDate": 1548720847365, + "updatedDate": 1548720847365, + "comments": [], + "tasks": [], + "permittedOperations": { + "editable": true, + "deletable": true + } + } + ], + "tasks": [], + "permittedOperations": { + "editable": true, + "deletable": true + } + }, + "commentAnchor": { + "line": 1, + "lineType": "CONTEXT", + "fileType": "FROM", + "path": "path/to/file", + "srcPath": "path/to/file" + } + }, + { + "id": 101, + "createdDate": 1359065920, + "user": { + "name": "jcitizen", + "emailAddress": "jane@example.com", + "id": 101, + "displayName": "Jane Citizen", + "active": true, + "slug": "jcitizen", + "type": "NORMAL" + }, + "action": "RESCOPED", + "fromHash": "abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde", + "previousFromHash": "bcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdea", + "previousToHash": "cdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeab", + "toHash": "ddeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabc", + "added": { + "commits": [ + { + "id": "abcdef0123abcdef4567abcdef8987abcdef6543", + "displayId": "abcdef0123a", + "author": { + "name": "charlie", + "emailAddress": "charlie@example.com" + }, + "authorTimestamp": 1548720847608, + "committer": { + "name": "charlie", + "emailAddress": "charlie@example.com" + }, + "committerTimestamp": 1548720847608, + "message": "WIP on feature 1", + "parents": [ + { + "id": "abcdef0123abcdef4567abcdef8987abcdef6543", + "displayId": "abcdef0" + } + ] + } + ], + "total": 1 + }, + "removed": { + "commits": [ + { + "id": "def0123abcdef4567abcdef8987abcdef6543abc", + "displayId": "def0123abcd", + "author": { + "name": "charlie", + "emailAddress": "charlie@example.com" + }, + "authorTimestamp": 1548720847609, + "committer": { + "name": "charlie", + "emailAddress": "charlie@example.com" + }, + "committerTimestamp": 1548720847609, + "message": "More work on feature 1", + "parents": [ + { + "id": "abcdef0123abcdef4567abcdef8987abcdef6543", + "displayId": "abcdef0" + } + ] + } + ], + "total": 1 + } + }, + { + "id": 101, + "createdDate": 1359085920, + "user": { + "name": "jcitizen", + "emailAddress": "jane@example.com", + "id": 101, + "displayName": "Jane Citizen", + "active": true, + "slug": "jcitizen", + "type": "NORMAL" + }, + "action": "MERGED" + } + ], + "start": 0 +}`), + }, + }, + want: Activities{ + IsLastPage: true, + Limit: 25, + Size: 3, + Values: []Activity{ + { + ID: 101, + CreatedDate: 1359065920, + User: User{ + Name: "jcitizen", + EmailAddress: "jane@example.com", + ID: 101, + DisplayName: "Jane Citizen", + Active: true, + Slug: "jcitizen", + Type: "NORMAL", + }, + Action: "COMMENTED", + CommentAction: "ADDED", + Comment: ActivityComment{ + Properties: Properties{ + Key: "value", + }, + ID: 1, + Version: 1, + Text: "A measured reply.", + Author: User{ + Name: "jcitizen", + EmailAddress: "jane@example.com", + ID: 101, + DisplayName: "Jane Citizen", + Active: true, + Slug: "jcitizen", + Type: "NORMAL", + }, + CreatedDate: 1548720847370, + UpdatedDate: 1548720847370, + Comments: []ActivityComment{ + { + Properties: Properties{ + Key: "value", + }, + ID: 1, + Version: 1, + Text: "An insightful comment.", + Author: User{ + Name: "jcitizen", + EmailAddress: "jane@example.com", + ID: 101, + DisplayName: "Jane Citizen", + Active: true, + Slug: "jcitizen", + Type: "NORMAL", + }, + CreatedDate: 1548720847365, + UpdatedDate: 1548720847365, + Comments: []ActivityComment{}, + PermittedOperations: PermittedOperations{ + Editable: true, + Deletable: true, + }, + }, + }, + PermittedOperations: PermittedOperations{ + Editable: true, + Deletable: true, + }, + }, + CommentAnchor: Anchor{ + Line: 1, + LineType: "CONTEXT", + FileType: "FROM", + Path: "path/to/file", + SrcPath: "path/to/file", + }, + }, + { + ID: 101, + CreatedDate: 1359065920, + User: User{ + Name: "jcitizen", + EmailAddress: "jane@example.com", + ID: 101, + DisplayName: "Jane Citizen", + Active: true, + Slug: "jcitizen", + Type: "NORMAL", + }, + Action: "RESCOPED", + FromHash: "abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde", + PreviousFromHash: "bcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdea", + PreviousToHash: "cdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeab", + ToHash: "ddeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabc", + Added: CommitsStats{ + Commits: []Commit{ + { + ID: "abcdef0123abcdef4567abcdef8987abcdef6543", + DisplayID: "abcdef0123a", + Author: User{ + Name: "charlie", + EmailAddress: "charlie@example.com", + }, + AuthorTimestamp: 1548720847608, + Committer: User{ + Name: "charlie", + EmailAddress: "charlie@example.com", + }, + CommitterTimestamp: 1548720847608, + Message: "WIP on feature 1", + Parents: []struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + }{ + { + ID: "abcdef0123abcdef4567abcdef8987abcdef6543", + DisplayID: "abcdef0", + }, + }, + }, + }, + Total: 1, + }, + Removed: CommitsStats{ + Commits: []Commit{ + { + ID: "def0123abcdef4567abcdef8987abcdef6543abc", + DisplayID: "def0123abcd", + Author: User{ + Name: "charlie", + EmailAddress: "charlie@example.com", + }, + AuthorTimestamp: 1548720847609, + Committer: User{ + Name: "charlie", + EmailAddress: "charlie@example.com", + }, + CommitterTimestamp: 1548720847609, + Message: "More work on feature 1", + Parents: []struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + }{ + { + ID: "abcdef0123abcdef4567abcdef8987abcdef6543", + DisplayID: "abcdef0", + }, + }, + }, + }, + Total: 1, + }, + }, + { + ID: 101, + CreatedDate: 1359085920, + User: User{ + Name: "jcitizen", + EmailAddress: "jane@example.com", + ID: 101, + DisplayName: "Jane Citizen", + Active: true, + Slug: "jcitizen", + Type: "NORMAL", + }, + Action: "MERGED", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetActivitiesResponse(tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("GetActivitiesResponse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetActivitiesResponse() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/default_api.go b/default_api.go index 06bd287..7fb8dc9 100644 --- a/default_api.go +++ b/default_api.go @@ -794,13 +794,18 @@ func (a *DefaultApiService) CreateComment(projectKey, repositorySlug string, com Add a new comment. <p> Comments can be added in a few places by setting different attributes: <p> General pull request comment: <pre> { \"text\": \"An insightful general comment on a pull request.\" } </pre> Reply to a comment: <pre> { \"text\": \"A measured reply.\", \"parent\": { \"id\": 1 } } </pre> General file comment: <pre> { \"text\": \"An insightful general comment on a file.\", \"anchor\": { \"diffType\": \"RANGE\", \"fromHash\": \"6df3858eeb9a53a911cd17e66a9174d44ffb02cd\", \"path\": \"path/to/file\", \"srcPath\": \"path/to/file\", \"toHash\": \"04c7c5c931b9418ca7b66f51fe934d0bd9b2ba4b\" } } </pre> File line comment: <pre> { \"text\": \"A pithy comment on a particular line within a file.\", \"anchor\": { \"diffType\": \"COMMIT\", \"line\": 1, \"lineType\": \"CONTEXT\", \"fileType\": \"FROM\", \"fromHash\": \"6df3858eeb9a53a911cd17e66a9174d44ffb02cd\", \"path\": \"path/to/file\", \"srcPath\": \"path/to/file\", \"toHash\": \"04c7c5c931b9418ca7b66f51fe934d0bd9b2ba4b\" } } </pre> <p> For file and line comments, 'path' refers to the path of the file to which the comment should be applied and 'srcPath' refers to the path the that file used to have (only required for copies and moves). Also, fromHash and toHash refer to the sinceId / untilId (respectively) used to produce the diff on which the comment was added. Finally diffType refers to the type of diff the comment was added on. For backwards compatibility purposes if no diffType is provided and no fromHash/toHash pair is provided the diffType will be resolved to 'EFFECTIVE'. In any other cases the diffType is REQUIRED. <p> For line comments, 'line' refers to the line in the diff that the comment should apply to. 'lineType' refers to the type of diff hunk, which can be: <ul> <li>'ADDED' - for an added line;</li> <li>'REMOVED' - for a removed line; or</li> <li>'CONTEXT' - for a line that was unmodified but is in the vicinity of the diff.</li> </ul> 'fileType' refers to the file of the diff to which the anchor should be attached - which is of relevance when displaying the diff in a side-by-side way. Currently the supported values are: <ul> <li>'FROM' - the source file of the diff</li> <li>'TO' - the destination file of the diff</li> </ul> If the current user is not a participant the user is added as a watcher of the pull request. <p> The authenticated user must have <strong>REPO_READ</strong> permission for the repository that this pull request targets to call this resource. @return */ -func (a *DefaultApiService) CreateComment_1(projectKey, repositorySlug string, pullRequestID int, localVarPostBody interface{}, localVarHTTPContentTypes []string) (*APIResponse, error) { +func (a *DefaultApiService) CreatePullRequestComment(projectKey, repositorySlug string, pullRequestID int, comment Comment, localVarHTTPContentTypes []string) (*APIResponse, error) { var ( localVarHTTPMethod = strings.ToUpper("Post") localVarFileName string localVarFileBytes []byte ) + localVarPostBody, err := json.Marshal(comment) + if err != nil { + return nil, err + } + // create path and map variables localVarPath := a.client.cfg.BasePath + "/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments" localVarPath = strings.Replace(localVarPath, "{"+"projectKey"+"}", fmt.Sprintf("%v", projectKey), -1) @@ -2990,7 +2995,7 @@ func (a *DefaultApiService) GetActivities(projectKey, repositorySlug string, pul return NewAPIResponseWithError(localVarHTTPResponse, bodyBytes, reportError("Status: %v, Body: %s", localVarHTTPResponse.Status, bodyBytes)) } - return NewBitbucketAPIResponse(localVarHTTPResponse) + return NewRawAPIResponse(localVarHTTPResponse) } /* DefaultApiService diff --git a/default_api_test.go b/default_api_test.go index 7c1b0d8..d3c2c29 100644 --- a/default_api_test.go +++ b/default_api_test.go @@ -715,7 +715,7 @@ func TestDefaultApiService_CreateComment(t *testing.T) { } } -func TestDefaultApiService_CreateComment_1(t *testing.T) { +func TestDefaultApiService_CreatePullRequestComment(t *testing.T) { type fields struct { client *APIClient } @@ -723,7 +723,7 @@ func TestDefaultApiService_CreateComment_1(t *testing.T) { projectKey string repositorySlug string pullRequestID int - localVarPostBody interface{} + comment Comment localVarHTTPContentTypes []string } tests := []struct { @@ -734,6 +734,26 @@ func TestDefaultApiService_CreateComment_1(t *testing.T) { wantErr, integrationTest bool }{ {"networkErrorContextExceeded", fields{client: generateConfigFake()}, args{}, &APIResponse{Message: "Post https://stash.domain.com/rest/api/1.0/projects//repos//pull-requests/0/comments: context canceled"}, true, false}, + {"simpleComment", fields{client: generateConfigRealLocalServer()}, + args{projectKey: "PROJ", + repositorySlug: "repo1", + pullRequestID: 1, + comment: Comment{Text: "Simple comment"}, + localVarHTTPContentTypes: []string{"application/json"}, + }, + &APIResponse{Message: `Status: 404 , Body: {errors:[{context:null,message:Pull request 1 does not exist in PROJ/repo1.,exceptionName:com.atlassian.bitbucket.pull.NoSuchPullRequestException}]}`, + Values: map[string]interface{}{ + "errors": []interface{}{ + map[string]interface{}{ + "context": nil, + "message": "Pull request 1 does not exist in PROJ/repo1.", + "exceptionName": "com.atlassian.bitbucket.pull.NoSuchPullRequestException", + }, + }, + }, + }, + true, true, + }, } for _, tt := range tests { if tt.integrationTest != runIntegrationTests { @@ -743,13 +763,16 @@ func TestDefaultApiService_CreateComment_1(t *testing.T) { a := &DefaultApiService{ client: tt.fields.client, } - got, err := a.CreateComment_1(tt.args.projectKey, tt.args.repositorySlug, tt.args.pullRequestID, tt.args.localVarPostBody, tt.args.localVarHTTPContentTypes) + got, err := a.CreatePullRequestComment(tt.args.projectKey, tt.args.repositorySlug, tt.args.pullRequestID, tt.args.comment, tt.args.localVarHTTPContentTypes) if (err != nil) != tt.wantErr { - t.Errorf("DefaultApiService.CreateComment_1() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("DefaultApiService.CreatePullRequestComment() error = %v, wantErr %v", err, tt.wantErr) return } + if got != nil { + got.Response = nil + } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("DefaultApiService.CreateComment_1() = %v, want %v", got, tt.want) + t.Errorf("DefaultApiService.CreatePullRequestComment() = %v, want %v", got, tt.want) } }) }