package jira import ( "encoding/json" "fmt" "github.com/google/go-cmp/cmp" "io" "io/ioutil" "net/http" "reflect" "strings" "testing" "time" "github.com/trivago/tgo/tcontainer" ) func TestIssueService_Get_Success(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) issue, _, err := testClient.Issue.Get("10002", nil) if issue == nil { t.Error("Expected issue. Issue is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_Get_WithQuerySuccess(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002?expand=foo") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) opt := &GetQueryOptions{ Expand: "foo", } issue, _, err := testClient.Issue.Get("10002", opt) if issue == nil { t.Error("Expected issue. Issue is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_Create(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) i := &Issue{ Fields: &IssueFields{ Description: "example bug report", }, } issue, _, err := testClient.Issue.Create(i) if issue == nil { t.Error("Expected issue. Issue is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_CreateThenGet(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue") w.WriteHeader(http.StatusCreated) io.Copy(w, r.Body) }) i := &Issue{ Fields: &IssueFields{ Description: "example bug report", Created: Time(time.Now()), }, } issue, _, err := testClient.Issue.Create(i) if issue == nil { t.Error("Expected issue. Issue is nil") } if err != nil { t.Errorf("Error given: %s", err) } testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") bytes, err := json.Marshal(issue) if err != nil { t.Errorf("Error marshaling issue: %s", err) } _, err = w.Write(bytes) if err != nil { t.Errorf("Error writing response: %s", err) } }) issue2, _, err := testClient.Issue.Get("10002", nil) if issue2 == nil { t.Error("Expected issue. Issue is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_Update(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") w.WriteHeader(http.StatusNoContent) }) i := &Issue{ Key: "PROJ-9001", Fields: &IssueFields{ Description: "example bug report", }, } issue, _, err := testClient.Issue.Update(i) if issue == nil { t.Error("Expected issue. Issue is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_UpdateIssue(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/PROJ-9001", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") testRequestURL(t, r, "/rest/api/2/issue/PROJ-9001") w.WriteHeader(http.StatusNoContent) }) jID := "PROJ-9001" i := make(map[string]interface{}) fields := make(map[string]interface{}) i["fields"] = fields resp, err := testClient.Issue.UpdateIssue(jID, i) if resp == nil { t.Error("Expected resp. resp is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_AddComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue/10000/comment") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}`) }) c := &Comment{ Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", Visibility: CommentVisibility{ Type: "role", Value: "Administrators", }, } comment, _, err := testClient.Issue.AddComment("10000", c) if comment == nil { t.Error("Expected Comment. Comment is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_UpdateComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10001","id":"10001","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}`) }) c := &Comment{ ID: "10001", Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.", Visibility: CommentVisibility{ Type: "role", Value: "Administrators", }, } comment, _, err := testClient.Issue.UpdateComment("10000", c) if comment == nil { t.Error("Expected Comment. Comment is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_DeleteComment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/comment/10001", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "DELETE") testRequestURL(t, r, "/rest/api/2/issue/10000/comment/10001") w.WriteHeader(http.StatusNoContent) fmt.Fprint(w, `{}`) }) err := testClient.Issue.DeleteComment("10000", "10001") if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_AddWorklogRecord(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/worklog", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue/10000/worklog") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2018-02-14T22:14:46.003+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2018-02-14T22:14:46.003+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}`) }) r := &WorklogRecord{ TimeSpent: "1h", } record, _, err := testClient.Issue.AddWorklogRecord("10000", r) if record == nil { t.Error("Expected Record. Record is nil") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_AddLink(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issueLink", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issueLink") w.WriteHeader(http.StatusOK) }) il := &IssueLink{ Type: IssueLinkType{ Name: "Duplicate", }, InwardIssue: &Issue{ Key: "HSP-1", }, OutwardIssue: &Issue{ Key: "MKY-1", }, Comment: &Comment{ Body: "Linked related issue!", Visibility: CommentVisibility{ Type: "group", Value: "jira-software-users", }, }, } resp, err := testClient.Issue.AddLink(il) if resp == nil { t.Error("Expected response. Response is nil") } if resp.StatusCode != 200 { t.Errorf("Expected Status code 200. Given %d", resp.StatusCode) } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_Get_Fields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) issue, _, err := testClient.Issue.Get("10002", nil) if issue == nil { t.Error("Expected issue. Issue is nil") } if !reflect.DeepEqual(issue.Fields.Labels, []string{"test"}) { t.Error("Expected labels for the returned issue") } if len(issue.Fields.Comments.Comments) != 1 { t.Errorf("Expected one comment, %v found", len(issue.Fields.Comments.Comments)) } if issue.Fields.Epic == nil { t.Error("Epic expected but not found") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_Get_RenderedFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) }) issue, _, err := testClient.Issue.Get("10002", nil) if issue == nil { t.Error("Expected issue. Issue is nil") } if issue.RenderedFields.Updated != "2 hours ago" { t.Error("Expected updated to equla '2 hours ago' for rendered field") } if len(issue.RenderedFields.Comments.Comments) != 1 { t.Errorf("Expected one comment, %v found", len(issue.RenderedFields.Comments.Comments)) } comment := issue.RenderedFields.Comments.Comments[0] if comment.Body != "This is HTML" { t.Errorf("Wrong comment body returned in RenderedField. Got %s", comment.Body) } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_DownloadAttachment(t *testing.T) { var testAttachment = "Here is an attachment" setup() defer teardown() testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/secure/attachment/10000/") w.WriteHeader(http.StatusOK) w.Write([]byte(testAttachment)) }) resp, err := testClient.Issue.DownloadAttachment("10000") if resp == nil { t.Error("Expected response. Response is nil") } defer resp.Body.Close() attachment, err := ioutil.ReadAll(resp.Body) if err != nil { t.Error("Expected attachment text", err) } if string(attachment) != testAttachment { t.Errorf("Expecting an attachment: %s", string(attachment)) } if resp.StatusCode != 200 { t.Errorf("Expected Status code 200. Given %d", resp.StatusCode) } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_DownloadAttachment_BadStatus(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/secure/attachment/", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/secure/attachment/10000/") w.WriteHeader(http.StatusForbidden) }) resp, err := testClient.Issue.DownloadAttachment("10000") if resp == nil { t.Error("Expected response. Response is nil") } defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Errorf("Expected Status code %d. Given %d", http.StatusForbidden, resp.StatusCode) } if err == nil { t.Errorf("Error expected") } } func TestIssueService_PostAttachment(t *testing.T) { var testAttachment = "Here is an attachment" setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") status := http.StatusOK file, _, err := r.FormFile("file") if err != nil { status = http.StatusNotAcceptable } if file == nil { status = http.StatusNoContent } else { // Read the file into memory data, err := ioutil.ReadAll(file) if err != nil { status = http.StatusInternalServerError } if string(data) != testAttachment { status = http.StatusNotAcceptable } w.WriteHeader(status) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) file.Close() } }) reader := strings.NewReader(testAttachment) issue, resp, err := testClient.Issue.PostAttachment("10000", reader, "attachment") if issue == nil { t.Error("Expected response. Response is nil") } if resp.StatusCode != 200 { t.Errorf("Expected Status code 200. Given %d", resp.StatusCode) } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_PostAttachment_NoResponse(t *testing.T) { var testAttachment = "Here is an attachment" setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) }) reader := strings.NewReader(testAttachment) _, _, err := testClient.Issue.PostAttachment("10000", reader, "attachment") if err == nil { t.Errorf("Error expected: %s", err) } } func TestIssueService_PostAttachment_NoFilename(t *testing.T) { var testAttachment = "Here is an attachment" setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) reader := strings.NewReader(testAttachment) _, _, err := testClient.Issue.PostAttachment("10000", reader, "") if err != nil { t.Errorf("Error expected: %s", err) } } func TestIssueService_PostAttachment_NoAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10000/attachments", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, "/rest/api/2/issue/10000/attachments") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `[{"self":"http://jira/jira/rest/api/2/attachment/228924","id":"228924","filename":"example.jpg","author":{"self":"http://jira/jira/rest/api/2/user?username=test","name":"test","emailAddress":"test@test.com","avatarUrls":{"16x16":"http://jira/jira/secure/useravatar?size=small&avatarId=10082","48x48":"http://jira/jira/secure/useravatar?avatarId=10082"},"displayName":"Tester","active":true},"created":"2016-05-24T00:25:17.000-0700","size":32280,"mimeType":"image/jpeg","content":"http://jira/jira/secure/attachment/228924/example.jpg","thumbnail":"http://jira/jira/secure/thumbnail/228924/_thumb_228924.png"}]`) }) _, _, err := testClient.Issue.PostAttachment("10000", nil, "attachment") if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_DeleteAttachment(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/attachment/10054", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "DELETE") testRequestURL(t, r, "/rest/api/2/attachment/10054") w.WriteHeader(http.StatusNoContent) fmt.Fprint(w, `{}`) }) resp, err := testClient.Issue.DeleteAttachment("10054") if resp.StatusCode != 204 { t.Error("Expected attachment not deleted.") if resp.StatusCode == 403 { t.Error("User not permitted to delete attachment") } if resp.StatusCode == 404 { t.Error("Attachment not found") } } else { t.Log("Attachment deleted") } if err != nil { t.Errorf("Error given: %s", err) } else { t.Log("No error") } } func TestIssueService_Search(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/search?jql=something&startAt=1&maxResults=40&expand=foo") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`) }) opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"} _, resp, err := testClient.Issue.Search("something", opt) if resp == nil { t.Errorf("Response given: %+v", resp) } if err != nil { t.Errorf("Error given: %s", err) } if resp.StartAt != 1 { t.Errorf("StartAt should populate with 1, %v given", resp.StartAt) } if resp.MaxResults != 40 { t.Errorf("StartAt should populate with 40, %v given", resp.MaxResults) } if resp.Total != 6 { t.Errorf("StartAt should populate with 6, %v given", resp.Total) } } func TestIssueService_Search_WithoutPaging(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/search?jql=something") w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 50,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`) }) _, resp, err := testClient.Issue.Search("something", nil) if resp == nil { t.Errorf("Response given: %+v", resp) } if err != nil { t.Errorf("Error given: %s", err) } if resp.StartAt != 0 { t.Errorf("StartAt should populate with 0, %v given", resp.StartAt) } if resp.MaxResults != 50 { t.Errorf("StartAt should populate with 50, %v given", resp.MaxResults) } if resp.Total != 6 { t.Errorf("StartAt should populate with 6, %v given", resp.Total) } } func TestIssueService_SearchPages(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") if r.URL.String() == "/rest/api/2/search?jql=something&startAt=1&maxResults=2&expand=foo&validateQuery=warn" { w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 2,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`) return } else if r.URL.String() == "/rest/api/2/search?jql=something&startAt=3&maxResults=2&expand=foo&validateQuery=warn" { w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 3,"maxResults": 2,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`) return } else if r.URL.String() == "/rest/api/2/search?jql=something&startAt=5&maxResults=2&expand=foo&validateQuery=warn" { w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 5,"maxResults": 2,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}}]}`) return } t.Errorf("Unexpected URL: %v", r.URL) }) opt := &SearchOptions{StartAt: 1, MaxResults: 2, Expand: "foo", ValidateQuery: "warn"} issues := make([]Issue, 0) err := testClient.Issue.SearchPages("something", opt, func(issue Issue) error { issues = append(issues, issue) return nil }) if err != nil { t.Errorf("Error given: %s", err) } if len(issues) != 5 { t.Errorf("Expected 5 issues, %v given", len(issues)) } } func TestIssueService_GetCustomFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":"test","watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) issue, _, err := testClient.Issue.GetCustomFields("10002") if err != nil { t.Errorf("Error given: %s", err) } if issue == nil { t.Error("Expected Customfields") } cf := issue["customfield_123"] if cf != "test" { t.Error("Expected \"test\" for custom field") } } func TestIssueService_GetComplexCustomFields(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"customfield_123":{"self":"http://www.example.com/jira/rest/api/2/customFieldOption/123","value":"test","id":"123"},"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{}}`) }) issue, _, err := testClient.Issue.GetCustomFields("10002") if err != nil { t.Errorf("Error given: %s", err) } if issue == nil { t.Error("Expected Customfields") } cf := issue["customfield_123"] if cf != "test" { t.Error("Expected \"test\" for custom field") } } func TestIssueService_GetTransitions(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/issue/123/transitions" raw, err := ioutil.ReadFile("./mocks/transitions.json") if err != nil { t.Error(err.Error()) } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, testAPIEndpoint) fmt.Fprint(w, string(raw)) }) transitions, _, err := testClient.Issue.GetTransitions("123") if err != nil { t.Errorf("Got error: %v", err) } if transitions == nil { t.Error("Expected transition list. Got nil.") } if len(transitions) != 2 { t.Errorf("Expected 2 transitions. Got %d", len(transitions)) } if transitions[0].Fields["summary"].Required != false { t.Errorf("First transition summary field should not be required") } } func TestIssueService_DoTransition(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/issue/123/transitions" transitionID := "22" testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) var payload CreateTransitionPayload err := decoder.Decode(&payload) if err != nil { t.Errorf("Got error: %v", err) } if payload.Transition.ID != transitionID { t.Errorf("Expected %s to be in payload, got %s instead", transitionID, payload.Transition.ID) } }) _, err := testClient.Issue.DoTransition("123", transitionID) if err != nil { t.Errorf("Got error: %v", err) } } func TestIssueService_DoTransitionWithPayload(t *testing.T) { setup() defer teardown() testAPIEndpoint := "/rest/api/2/issue/123/transitions" transitionID := "22" customPayload := map[string]interface{}{ "update": map[string]interface{}{ "comment": []map[string]interface{}{ { "add": map[string]string{ "body": "Hello World", }, }, }, }, "transition": TransitionPayload{ ID: transitionID, }, } testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testRequestURL(t, r, testAPIEndpoint) decoder := json.NewDecoder(r.Body) payload := map[string]interface{}{} err := decoder.Decode(&payload) if err != nil { t.Errorf("Got error: %v", err) } contains := func(key string) bool { _, ok := payload[key] return ok } if !contains("update") || !contains("transition") { t.Fatalf("Excpected update, transition to be in payload, got %s instead", payload) } transition, ok := payload["transition"].(map[string]interface{}) if !ok { t.Fatalf("Excpected transition to be in payload, got %s instead", payload["transition"]) } if transition["id"].(string) != transitionID { t.Errorf("Expected %s to be in payload, got %s instead", transitionID, transition["id"]) } }) _, err := testClient.Issue.DoTransitionWithPayload("123", customPayload) if err != nil { t.Errorf("Got error: %v", err) } } func TestIssueFields_TestMarshalJSON_PopulateUnknownsSuccess(t *testing.T) { data := `{ "customfield_123":"test", "description":"example bug report", "project":{ "self":"http://www.example.com/jira/rest/api/2/project/EX", "id":"10000", "key":"EX", "name":"Example", "avatarUrls":{ "48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000", "24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000", "16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000", "32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000" }, "projectCategory":{ "self":"http://www.example.com/jira/rest/api/2/projectCategory/10000", "id":"10000", "name":"FIRST", "description":"First Project Category" } }, "issuelinks":[ { "id":"10001", "type":{ "id":"10000", "name":"Dependent", "inward":"depends on", "outward":"is depended by" }, "outwardIssue":{ "id":"10004L", "key":"PRJ-2", "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2", "fields":{ "status":{ "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", "name":"Open" } } } }, { "id":"10002", "type":{ "id":"10000", "name":"Dependent", "inward":"depends on", "outward":"is depended by" }, "inwardIssue":{ "id":"10004", "key":"PRJ-3", "self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3", "fields":{ "status":{ "iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png", "name":"Open" } } } } ] }` i := new(IssueFields) err := json.Unmarshal([]byte(data), i) if err != nil { t.Errorf("Expected nil error, received %s", err) } if len(i.Unknowns) != 1 { t.Errorf("Expected 1 unknown field to be present, received %d", len(i.Unknowns)) } if i.Description != "example bug report" { t.Errorf("Expected description to be \"%s\", received \"%s\"", "example bug report", i.Description) } } func TestIssueFields_MarshalJSON_OmitsEmptyFields(t *testing.T) { i := &IssueFields{ Description: "blahblah", Type: IssueType{ Name: "Story", }, Labels: []string{"aws-docker"}, } rawdata, err := json.Marshal(i) if err != nil { t.Errorf("Expected nil err, received %s", err) } // convert json to map and see if unset keys are there issuef := tcontainer.NewMarshalMap() err = json.Unmarshal(rawdata, &issuef) if err != nil { t.Errorf("Expected nil err, received %s", err) } _, err = issuef.Int("issuetype/avatarId") if err == nil { t.Error("Expected non nil error, received nil") } // verify that the field that should be there, is. name, err := issuef.String("issuetype/name") if err != nil { t.Errorf("Expected nil err, received %s", err) } if name != "Story" { t.Errorf("Expected Story, received %s", name) } } func TestIssueFields_MarshalJSON_Success(t *testing.T) { i := &IssueFields{ Description: "example bug report", Unknowns: tcontainer.MarshalMap{ "customfield_123": "test", }, Project: Project{ Self: "http://www.example.com/jira/rest/api/2/project/EX", ID: "10000", Key: "EX", }, AffectsVersions: []*AffectsVersion{ &AffectsVersion{ ID: "10705", Name: "2.1.0-rc3", Self: "http://www.example.com/jira/rest/api/2/version/10705", ReleaseDate: "2018-09-30", }, }, } bytes, err := json.Marshal(i) if err != nil { t.Errorf("Expected nil err, received %s", err) } received := new(IssueFields) // the order of json might be different. so unmarshal it again and compare objects err = json.Unmarshal(bytes, received) if err != nil { t.Errorf("Expected nil err, received %s", err) } if !reflect.DeepEqual(i, received) { t.Errorf("Received object different from expected. Expected %+v, received %+v", i, received) } } func TestInitIssueWithMetaAndFields_Success(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["summary"] = map[string]interface{}{ "name": "Summary", "schema": map[string]interface{}{ "type": "string", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedSummary := "Issue Summary" fieldConfig := map[string]string{ "Summary": "Issue Summary", } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } gotSummary, found := issue.Fields.Unknowns["summary"] if !found { t.Errorf("Expected summary to be set in issue. Not set.") } if gotSummary != expectedSummary { t.Errorf("Expected %s received %s", expectedSummary, gotSummary) } } func TestInitIssueWithMetaAndFields_ArrayValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["component"] = map[string]interface{}{ "name": "Component/s", "schema": map[string]interface{}{ "type": "array", "items": "component", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedComponent := "Jira automation" fieldConfig := map[string]string{ "Component/s": expectedComponent, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } c, isArray := issue.Fields.Unknowns["component"].([]Component) if isArray == false { t.Error("Expected array, non array object received") } if len(c) != 1 { t.Errorf("Expected received array to be of length 1. Got %d", len(c)) } gotComponent := c[0].Name if err != nil { t.Errorf("Expected err to be nil, received %s", err) } if gotComponent != expectedComponent { t.Errorf("Expected %s received %s", expectedComponent, gotComponent) } } func TestInitIssueWithMetaAndFields_DateValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["created"] = map[string]interface{}{ "name": "Created", "schema": map[string]interface{}{ "type": "date", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedCreated := "19 oct 2012" fieldConfig := map[string]string{ "Created": expectedCreated, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } gotCreated, err := issue.Fields.Unknowns.String("created") if err != nil { t.Errorf("Expected err to be nil, received %s", err) } if gotCreated != expectedCreated { t.Errorf("Expected %s received %s", expectedCreated, gotCreated) } } func TestInitIssueWithMetaAndFields_UserValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["assignee"] = map[string]interface{}{ "name": "Assignee", "schema": map[string]interface{}{ "type": "user", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedAssignee := "jdoe" fieldConfig := map[string]string{ "Assignee": expectedAssignee, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } a, _ := issue.Fields.Unknowns.Value("assignee") gotAssignee := a.(User).Name if gotAssignee != expectedAssignee { t.Errorf("Expected %s received %s", expectedAssignee, gotAssignee) } } func TestInitIssueWithMetaAndFields_ProjectValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["project"] = map[string]interface{}{ "name": "Project", "schema": map[string]interface{}{ "type": "project", }, } metaIssueType := MetaIssueType{ Fields: fields, } setProject := "somewhere" fieldConfig := map[string]string{ "Project": setProject, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } a, _ := issue.Fields.Unknowns.Value("project") gotProject := a.(Project).Name if gotProject != metaProject.Name { t.Errorf("Expected %s received %s", metaProject.Name, gotProject) } } func TestInitIssueWithMetaAndFields_PriorityValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["priority"] = map[string]interface{}{ "name": "Priority", "schema": map[string]interface{}{ "type": "priority", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedPriority := "Normal" fieldConfig := map[string]string{ "Priority": expectedPriority, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } a, _ := issue.Fields.Unknowns.Value("priority") gotPriority := a.(Priority).Name if gotPriority != expectedPriority { t.Errorf("Expected %s received %s", expectedPriority, gotPriority) } } func TestInitIssueWithMetaAndFields_SelectList(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["someitem"] = map[string]interface{}{ "name": "A Select Item", "schema": map[string]interface{}{ "type": "option", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedVal := "Value" fieldConfig := map[string]string{ "A Select Item": expectedVal, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } a, _ := issue.Fields.Unknowns.Value("someitem") gotVal := a.(Option).Value if gotVal != expectedVal { t.Errorf("Expected %s received %s", expectedVal, gotVal) } } func TestInitIssueWithMetaAndFields_IssuetypeValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["issuetype"] = map[string]interface{}{ "name": "Issue type", "schema": map[string]interface{}{ "type": "issuetype", }, } metaIssueType := MetaIssueType{ Fields: fields, } expectedIssuetype := "Bug" fieldConfig := map[string]string{ "Issue type": expectedIssuetype, } issue, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err != nil { t.Errorf("Expected nil error, received %s", err) } a, _ := issue.Fields.Unknowns.Value("issuetype") gotIssuetype := a.(IssueType).Name if gotIssuetype != expectedIssuetype { t.Errorf("Expected %s received %s", expectedIssuetype, gotIssuetype) } } func TestInitIssueWithmetaAndFields_FailureWithUnknownValueType(t *testing.T) { metaProject := MetaProject{ Name: "Engineering - Dept", Id: "ENG", } fields := tcontainer.NewMarshalMap() fields["issuetype"] = map[string]interface{}{ "name": "Issue type", "schema": map[string]interface{}{ "type": "randomType", }, } metaIssueType := MetaIssueType{ Fields: fields, } fieldConfig := map[string]string{ "Issue tyoe": "sometype", } _, err := InitIssueWithMetaAndFields(&metaProject, &metaIssueType, fieldConfig) if err == nil { t.Error("Expected non nil error, received nil") } } func TestIssueService_Delete(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "DELETE") testRequestURL(t, r, "/rest/api/2/issue/10002") w.WriteHeader(http.StatusNoContent) fmt.Fprint(w, `{}`) }) resp, err := testClient.Issue.Delete("10002") if resp.StatusCode != 204 { t.Error("Expected issue not deleted.") } if err != nil { t.Errorf("Error given: %s", err) } } func getTime(original time.Time) *Time { jiraTime := Time(original) return &jiraTime } func TestIssueService_GetWorklogs(t *testing.T) { setup() defer teardown() tt := []struct { name string response string issueId string uri string worklog *Worklog err error option *AddWorklogQueryOptions }{ { name: "simple worklog", response: `{"startAt": 1,"maxResults": 40,"total": 1,"worklogs": [{"id": "3","self": "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent": "1h","timeSpentSeconds": 3600,"issueId":"10002"}]}`, issueId: "10002", uri: "/rest/api/2/issue/%s/worklog", worklog: &Worklog{ StartAt: 1, MaxResults: 40, Total: 1, Worklogs: []WorklogRecord{ { Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", Author: &User{ Self: "http://www.example.com/jira/rest/api/2/user?username=fred", Name: "fred", DisplayName: "Fred F. User", }, UpdateAuthor: &User{ Self: "http://www.example.com/jira/rest/api/2/user?username=fred", Name: "fred", DisplayName: "Fred F. User", }, Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), TimeSpent: "1h", TimeSpentSeconds: 3600, ID: "3", IssueID: "10002", }, }, }, }, { name: "expanded worklog", response: `{"startAt":1,"maxResults":40,"total":1,"worklogs":[{"id":"3","self":"http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent":"1h","timeSpentSeconds":3600,"issueId":"10002","properties":[{"key":"foo","value":{"bar":"baz"}}]}]}`, issueId: "10002", uri: "/rest/api/2/issue/%s/worklog?expand=properties", worklog: &Worklog{ StartAt: 1, MaxResults: 40, Total: 1, Worklogs: []WorklogRecord{ { Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", Author: &User{ Self: "http://www.example.com/jira/rest/api/2/user?username=fred", Name: "fred", DisplayName: "Fred F. User", }, UpdateAuthor: &User{ Self: "http://www.example.com/jira/rest/api/2/user?username=fred", Name: "fred", DisplayName: "Fred F. User", }, Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), TimeSpent: "1h", TimeSpentSeconds: 3600, ID: "3", IssueID: "10002", Properties: []EntityProperty{ { Key: "foo", Value: map[string]interface{}{ "bar": "baz", }, }, }, }, }, }, option: &AddWorklogQueryOptions{Expand: "properties"}, }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { uri := fmt.Sprintf(tc.uri, tc.issueId) testMux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, uri) _, _ = fmt.Fprint(w, tc.response) }) var worklog *Worklog var err error if tc.option != nil { worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId, WithQueryOptions(tc.option)) } else { worklog, _, err = testClient.Issue.GetWorklogs(tc.issueId) } if err != nil && !cmp.Equal(err, tc.err) { t.Errorf("unexpected error: %v", err) } if !cmp.Equal(worklog, tc.worklog) { t.Errorf("unexpected worklog structure: %s", cmp.Diff(worklog, tc.worklog)) } }) } } func TestIssueService_GetWatchers(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/watchers", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002/watchers") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]}`) }) testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/user?username=fred") fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred", "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`) }) watchers, _, err := testClient.Issue.GetWatchers("10002") if err != nil { t.Errorf("Error given: %s", err) return } if watchers == nil { t.Error("Expected watchers. Watchers is nil") return } if len(*watchers) != 1 { t.Errorf("Expected 1 watcher, got: %d", len(*watchers)) return } if (*watchers)[0].Name != "fred" { t.Error("Expected watcher name fred") } } func TestIssueService_UpdateAssignee(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002/assignee", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") testRequestURL(t, r, "/rest/api/2/issue/10002/assignee") w.WriteHeader(http.StatusNoContent) }) resp, err := testClient.Issue.UpdateAssignee("10002", &User{ Name: "test-username", }) if resp.StatusCode != 204 { t.Error("Expected issue not re-assigned.") } if err != nil { t.Errorf("Error given: %s", err) } } func TestIssueService_Get_Fields_Changelog(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"expand":"changelog","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","changelog":{"startAt": 0,"maxResults": 1, "total": 1, "histories": [{"id": "10002", "author": {"self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "key": "fred", "emailAddress": "fred@example.com", "avatarUrls": {"48x48": "http://www.example.com/secure/useravatar?ownerId=fred&avatarId=33072", "24x24": "http://www.example.com/secure/useravatar?size=small&ownerId=fred&avatarId=33072", "16x16": "http://www.example.com/secure/useravatar?size=xsmall&ownerId=fred&avatarId=33072", "32x32": "http://www.example.com/secure/useravatar?size=medium&ownerId=fred&avatarId=33072"},"displayName":"Fred","active": true,"timeZone":"Australia/Sydney"},"created":"2018-06-20T16:50:35.000+0300","items":[{"field":"Rank","fieldtype":"custom","from":"","fromString":"","to":"","toString":"Ranked higher"}]}]}}`) }) issue, _, _ := testClient.Issue.Get("10002", &GetQueryOptions{Expand: "changelog"}) if issue == nil { t.Error("Expected issue. Issue is nil") } if len(issue.Changelog.Histories) != 1 { t.Errorf("Expected one history item, %v found", len(issue.Changelog.Histories)) } if issue.Changelog.Histories[0].Created != "2018-06-20T16:50:35.000+0300" { t.Errorf("Expected created time of history item 2018-06-20T16:50:35.000+0300, %v got", issue.Changelog.Histories[0].Created) } tm, _ := time.Parse("2006-01-02T15:04:05.999-0700", "2018-06-20T16:50:35.000+0300") if ct, _ := issue.Changelog.Histories[0].CreatedTime(); !tm.Equal(ct) { t.Errorf("Expected CreatedTime func return %v time, %v got", tm, ct) } } func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { setup() defer teardown() testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testRequestURL(t, r, "/rest/api/2/issue/10002") fmt.Fprint(w, `{"fields":{"versions":[{"self":"http://www.example.com/jira/rest/api/2/version/10705","id":"10705","description":"test description","name":"2.1.0-rc3","archived":false,"released":false,"releaseDate":"2018-09-30"}]}}`) }) issue, _, err := testClient.Issue.Get("10002", nil) if issue == nil { t.Error("Expected issue. Issue is nil") } if !reflect.DeepEqual(issue.Fields.AffectsVersions, []*AffectsVersion{ { ID: "10705", Name: "2.1.0-rc3", Self: "http://www.example.com/jira/rest/api/2/version/10705", ReleaseDate: "2018-09-30", Released: false, Archived: false, Description: "test description", }, }) { t.Error("Expected AffectsVersions for the returned issue") } if err != nil { t.Errorf("Error given: %s", err) } }