mirror of
https://github.com/interviewstreet/go-jira.git
synced 2025-02-09 13:36:58 +02:00
Adding tests
This commit is contained in:
commit
31fcf682f6
@ -71,6 +71,9 @@ func main() {
|
||||
|
||||
### Authenticate with session cookie
|
||||
|
||||
Some actions require an authenticated user.
|
||||
Here is an example with a session cookie authentification.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@ -102,6 +105,8 @@ func main() {
|
||||
|
||||
### Call a not implemented API endpoint
|
||||
|
||||
Not all API endpoints of the JIRA API are implemented into *go-jira*.
|
||||
But you can call them anyway:
|
||||
Lets get all public projects of [Atlassian`s JIRA instance](https://jira.atlassian.com/).
|
||||
|
||||
```go
|
||||
|
@ -2,6 +2,7 @@ package jira
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// AuthenticationService handles authentication for the JIRA instance / API.
|
||||
@ -25,6 +26,7 @@ type Session struct {
|
||||
LastFailedLoginTime string `json:"lastFailedLoginTime"`
|
||||
PreviousLoginTime string `json:"previousLoginTime"`
|
||||
} `json:"loginInfo"`
|
||||
Cookies []*http.Cookie
|
||||
}
|
||||
|
||||
// AcquireSessionCookie creates a new session for a user in JIRA.
|
||||
@ -51,6 +53,8 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
||||
|
||||
session := new(Session)
|
||||
resp, err := s.client.Do(req, session)
|
||||
session.Cookies = resp.Cookies()
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). %s", err)
|
||||
}
|
||||
@ -63,6 +67,14 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Authenticated reports if the current Client has an authenticated session with JIRA
|
||||
func (s *AuthenticationService) Authenticated() bool {
|
||||
if s != nil {
|
||||
return s.client.session != nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO Missing API Call GET (Returns information about the currently authenticated user's session)
|
||||
// See https://docs.atlassian.com/jira/REST/latest/#auth/1/session
|
||||
// TODO Missing API Call DELETE (Logs the current user out of JIRA, destroying the existing session, if any.)
|
||||
|
@ -36,6 +36,10 @@ func TestAcquireSessionCookie_Fail(t *testing.T) {
|
||||
if res == true {
|
||||
t.Error("Expected error, but result was true")
|
||||
}
|
||||
|
||||
if testClient.Authentication.Authenticated() != false {
|
||||
t.Error("Expected false, but result was true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquireSessionCookie_Success(t *testing.T) {
|
||||
@ -65,4 +69,18 @@ func TestAcquireSessionCookie_Success(t *testing.T) {
|
||||
if res == false {
|
||||
t.Error("Expected result was true. Got false")
|
||||
}
|
||||
|
||||
if testClient.Authentication.Authenticated() != true {
|
||||
t.Error("Expected true, but result was false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticated_NotInit(t *testing.T) {
|
||||
// Skip setup() because we don't want a fully setup client
|
||||
testClient = new(Client)
|
||||
|
||||
// Test before we've attempted to authenticate
|
||||
if testClient.Authentication.Authenticated() != false {
|
||||
t.Error("Expected false, but result was true")
|
||||
}
|
||||
}
|
||||
|
22
errors.go
22
errors.go
@ -1,22 +0,0 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ErrorResponse reports one or more errors caused by an API request.
|
||||
type ErrorResponse struct {
|
||||
Response *http.Response // HTTP response that caused this error
|
||||
ErrorMessages []string `json:"errorMessages,omitempty"`
|
||||
Errors map[string]string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
if r.Response == nil {
|
||||
return fmt.Sprintf("%v %+v", r.ErrorMessages, r.Errors)
|
||||
}
|
||||
return fmt.Sprintf("%v %v: %d %v %+v",
|
||||
r.Response.Request.Method, r.Response.Request.URL,
|
||||
r.Response.StatusCode, r.ErrorMessages, r.Errors)
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrorResponse_Empty(t *testing.T) {
|
||||
u, _ := url.Parse("https://issues.apache.org/jira/browse/MESOS-5040")
|
||||
r := &http.Response{
|
||||
Request: &http.Request{
|
||||
Method: "POST",
|
||||
URL: u,
|
||||
},
|
||||
StatusCode: 200,
|
||||
}
|
||||
|
||||
mockData := []struct {
|
||||
Response ErrorResponse
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Response: ErrorResponse{},
|
||||
Expected: "[] map[]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
ErrorMessages: []string{"foo", "bar"},
|
||||
},
|
||||
Expected: "[foo bar] map[]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
Errors: map[string]string{"Foo": "Bar"},
|
||||
},
|
||||
Expected: "[] map[Foo:Bar]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
ErrorMessages: []string{"foo", "bar"},
|
||||
Errors: map[string]string{"Foo": "Bar"},
|
||||
},
|
||||
Expected: "[foo bar] map[Foo:Bar]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
Response: r,
|
||||
},
|
||||
Expected: "POST https://issues.apache.org/jira/browse/MESOS-5040: 200 [] map[]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
Response: r,
|
||||
ErrorMessages: []string{"foo", "bar"},
|
||||
},
|
||||
Expected: "POST https://issues.apache.org/jira/browse/MESOS-5040: 200 [foo bar] map[]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
Response: r,
|
||||
Errors: map[string]string{"Foo": "Bar"},
|
||||
},
|
||||
Expected: "POST https://issues.apache.org/jira/browse/MESOS-5040: 200 [] map[Foo:Bar]",
|
||||
},
|
||||
{
|
||||
Response: ErrorResponse{
|
||||
Response: r,
|
||||
ErrorMessages: []string{"foo", "bar"},
|
||||
Errors: map[string]string{"Foo": "Bar"},
|
||||
},
|
||||
Expected: "POST https://issues.apache.org/jira/browse/MESOS-5040: 200 [foo bar] map[Foo:Bar]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range mockData {
|
||||
got := data.Response.Error()
|
||||
if got != data.Expected {
|
||||
t.Errorf("Response is different as expected. Expected \"%s\". Got \"%s\"", data.Expected, got)
|
||||
}
|
||||
}
|
||||
}
|
252
issue.go
252
issue.go
@ -1,7 +1,10 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -29,7 +32,18 @@ type Issue struct {
|
||||
Fields *IssueFields `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
type Issues []*Issue
|
||||
// Attachment represents a JIRA attachment
|
||||
type Attachment struct {
|
||||
Self string `json:"self,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
Author *User `json:"author,omitempty"`
|
||||
Created string `json:"created,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Thumbnail string `json:"thumbnail,omitempty"`
|
||||
}
|
||||
|
||||
// IssueFields represents single fields of a JIRA issue.
|
||||
// Every JIRA issue has several fields attached.
|
||||
@ -43,7 +57,6 @@ type IssueFields struct {
|
||||
// * "aggregatetimeoriginalestimate": null,
|
||||
// * "timeoriginalestimate": null,
|
||||
// * "timetracking": {},
|
||||
// * "attachment": [],
|
||||
// * "aggregatetimeestimate": null,
|
||||
// * "environment": null,
|
||||
// * "duedate": null,
|
||||
@ -54,22 +67,23 @@ type IssueFields struct {
|
||||
Resolutiondate string `json:"resolutiondate,omitempty"`
|
||||
Created string `json:"created,omitempty"`
|
||||
Watches *Watches `json:"watches,omitempty"`
|
||||
Assignee *Assignee `json:"assignee,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Summary string `json:"summary"`
|
||||
Creator *Assignee `json:"Creator,omitempty"`
|
||||
Reporter *Assignee `json:"reporter,omitempty"`
|
||||
Creator *User `json:"Creator,omitempty"`
|
||||
Reporter *User `json:"reporter,omitempty"`
|
||||
Components []*Component `json:"components,omitempty"`
|
||||
Status *Status `json:"status,omitempty"`
|
||||
Progress *Progress `json:"progress,omitempty"`
|
||||
AggregateProgress *Progress `json:"aggregateprogress,omitempty"`
|
||||
WorklogPage *WorklogPage `json:"worklog,omitempty"`
|
||||
Worklog *Worklog `json:"worklog,omitempty"`
|
||||
IssueLinks []*IssueLink `json:"issuelinks,omitempty"`
|
||||
Comments []*Comment `json:"comment.comments,omitempty"`
|
||||
FixVersions []*FixVersion `json:"fixVersions,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
SubTasks Issues `json:"subtasks,omitempty"`
|
||||
Subtasks []*Subtasks `json:"subtasks,omitempty"`
|
||||
Attachments []*Attachment `json:"attachment,omitempty"`
|
||||
}
|
||||
|
||||
// IssueType represents a type of a JIRA issue.
|
||||
@ -81,15 +95,7 @@ type IssueType struct {
|
||||
IconURL string `json:"iconUrl,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Subtask bool `json:"subtask,omitempty"`
|
||||
}
|
||||
|
||||
// Project represents a JIRA Project.
|
||||
type Project struct {
|
||||
Self string `json:"self,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AvatarURLs map[string]string `json:"avatarUrls,omitempty"`
|
||||
AvatarID int `json:"avatarId,omitempty"`
|
||||
}
|
||||
|
||||
// Resolution represents a resolution of a JIRA issue.
|
||||
@ -117,14 +123,24 @@ type Watches struct {
|
||||
IsWatching bool `json:"isWatching,omitempty"`
|
||||
}
|
||||
|
||||
// Assignee represents a user who is this JIRA issue assigned to.
|
||||
type Assignee struct {
|
||||
Self string `json:"self,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
EmailAddress string `json:"emailAddress,omitempty"`
|
||||
AvatarURLs map[string]string `json:"avatarUrls,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
// User represents a user who is this JIRA issue assigned to.
|
||||
type User struct {
|
||||
Self string `json:"self,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
EmailAddress string `json:"emailAddress,omitempty"`
|
||||
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
TimeZone string `json:"timeZone,omitempty"`
|
||||
}
|
||||
|
||||
// AvatarUrls represents different dimensions of avatars / images
|
||||
type AvatarUrls struct {
|
||||
Four8X48 string `json:"48x48,omitempty"`
|
||||
Two4X24 string `json:"24x24,omitempty"`
|
||||
One6X16 string `json:"16x16,omitempty"`
|
||||
Three2X32 string `json:"32x32,omitempty"`
|
||||
}
|
||||
|
||||
// Component represents a "component" of a JIRA issue.
|
||||
@ -163,32 +179,8 @@ type Progress struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type WorklogPage struct {
|
||||
StartAt uint `json:"startAt"`
|
||||
MaxResults uint `json:"maxResults"`
|
||||
Total uint `json:"total"`
|
||||
Worklogs []*Worklog `json:"worklogs"`
|
||||
}
|
||||
|
||||
// Worklog represents the work log of a JIRA issue.
|
||||
// JIRA Wiki: https://confluence.atlassian.com/jira/logging-work-on-an-issue-185729605.html
|
||||
type Worklog struct {
|
||||
ID string `json:"id"`
|
||||
Self string `json:"self"`
|
||||
IssueId string `json:"issueId"`
|
||||
TimeSpent string `json:"timeSpent"`
|
||||
TimeSpentSeconds uint64 `json:"timeSpentSeconds"`
|
||||
Comment string `json:"comment"`
|
||||
Updated JiraTime `json:"updated"`
|
||||
Created JiraTime `json:"created"`
|
||||
Started JiraTime `json:"started"`
|
||||
Author *Assignee `json:"author"`
|
||||
}
|
||||
|
||||
type JiraTime time.Time
|
||||
|
||||
type CustomFields map[string]string
|
||||
|
||||
func (t *JiraTime) UnmarshalJSON(b []byte) error {
|
||||
ti, err := time.Parse("\"2006-01-02T15:04:05.999-0700\"", string(b))
|
||||
if err != nil {
|
||||
@ -198,6 +190,39 @@ func (t *JiraTime) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Worklog represents the work log of a JIRA issue.
|
||||
// One Worklog contains zero or n WorklogRecords
|
||||
// JIRA Wiki: https://confluence.atlassian.com/jira/logging-work-on-an-issue-185729605.html
|
||||
type Worklog struct {
|
||||
StartAt int `json:"startAt"`
|
||||
MaxResults int `json:"maxResults"`
|
||||
Total int `json:"total"`
|
||||
Worklogs []WorklogRecord `json:"worklogs"`
|
||||
}
|
||||
|
||||
// WorklogRecord represents one entry of a Worklog
|
||||
type WorklogRecord struct {
|
||||
Self string `json:"self"`
|
||||
Author User `json:"author"`
|
||||
UpdateAuthor User `json:"updateAuthor"`
|
||||
Comment string `json:"comment"`
|
||||
Created JiraTime `json:"created"`
|
||||
Updated JiraTime `json:"updated"`
|
||||
Started string `json:"started"`
|
||||
TimeSpent string `json:"timeSpent"`
|
||||
TimeSpentSeconds int `json:"timeSpentSeconds"`
|
||||
ID string `json:"id"`
|
||||
IssueID string `json:"issueId"`
|
||||
}
|
||||
|
||||
// Subtasks represents all issues of a parent issue.
|
||||
type Subtasks struct {
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Self string `json:"self"`
|
||||
Fields IssueFields `json:"fields"`
|
||||
}
|
||||
|
||||
// IssueLink represents a link between two issues in JIRA.
|
||||
type IssueLink struct {
|
||||
ID string `json:"id"`
|
||||
@ -220,14 +245,14 @@ type IssueLinkType struct {
|
||||
|
||||
// Comment represents a comment by a person to an issue in JIRA.
|
||||
type Comment struct {
|
||||
Self string `json:"self"`
|
||||
Name string `json:"name"`
|
||||
Author Assignee `json:"author"`
|
||||
Body string `json:"body"`
|
||||
UpdateAuthor Assignee `json:"updateAuthor"`
|
||||
Updated string `json:"updated"`
|
||||
Created string `json:"created"`
|
||||
Visibility CommentVisibility `json:"visibility"`
|
||||
Self string `json:"self,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Author User `json:"author,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
UpdateAuthor User `json:"updateAuthor,omitempty"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
Created string `json:"created,omitempty"`
|
||||
Visibility CommentVisibility `json:"visibility,omitempty"`
|
||||
}
|
||||
|
||||
// FixVersion represents a software release in which an issue is fixed.
|
||||
@ -245,12 +270,8 @@ type FixVersion struct {
|
||||
// CommentVisibility represents he visibility of a comment.
|
||||
// E.g. Type could be "role" and Value "Administrators"
|
||||
type CommentVisibility struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Issues Issues `json:"issues"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Get returns a full representation of the issue for the given issue key.
|
||||
@ -275,35 +296,60 @@ func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
|
||||
return issue, resp, nil
|
||||
}
|
||||
|
||||
|
||||
// Returns a map of customfield_* keys with string values
|
||||
func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *http.Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
|
||||
// DownloadAttachment returns a http.Response of an attachment for a given attachmentID.
|
||||
// The attachment is in the http.Response.Body of the response.
|
||||
// This is an io.ReadCloser.
|
||||
// The caller should close the resp.Body.
|
||||
func (s *IssueService) DownloadAttachment(attachmentID string) (*http.Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// PostAttachment uploads r (io.Reader) as an attachment to a given attachmentID
|
||||
func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachmentName string) (*[]Attachment, *http.Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", attachmentID)
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(b)
|
||||
|
||||
fw, err := writer.CreateFormFile("file", attachmentName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
issue := new(map[string]interface{})
|
||||
resp, err := s.client.Do(req, issue)
|
||||
if r != nil {
|
||||
// Copy the file
|
||||
if _, err = io.Copy(fw, r); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
writer.Close()
|
||||
|
||||
req, err := s.client.NewMultiPartRequest("POST", apiEndpoint, b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
// PostAttachment response returns a JSON array (as multiple attachments can be posted)
|
||||
attachment := new([]Attachment)
|
||||
resp, err := s.client.Do(req, attachment)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
m := *issue
|
||||
f := m["fields"]
|
||||
cf := make(CustomFields)
|
||||
if f == nil {
|
||||
return cf, resp, nil
|
||||
}
|
||||
|
||||
if rec, ok := f.(map[string]interface{}); ok {
|
||||
for key, val := range rec {
|
||||
if strings.Contains(key, "customfield") {
|
||||
cf[key] = fmt.Sprint(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cf, resp, nil
|
||||
return attachment, resp, nil
|
||||
}
|
||||
|
||||
// Create creates an issue or a sub-task from a JSON representation.
|
||||
@ -360,14 +406,50 @@ func (s *IssueService) AddLink(issueLink *IssueLink) (*http.Response, error) {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
type searchResult struct {
|
||||
Issues []Issue `json:"issues"`
|
||||
}
|
||||
|
||||
// Search for tickets
|
||||
// JIRA API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues
|
||||
func (s *IssueService) Search(jql string) (*SearchResult, error) {
|
||||
func (s *IssueService) Search(jql string) ([]Issue, error) {
|
||||
req, err := s.client.NewRequest("GET", "rest/api/2/search?jql="+url.QueryEscape(jql), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp := new(SearchResult)
|
||||
resp := new(searchResult)
|
||||
_, err = s.client.Do(req, resp)
|
||||
return resp, err
|
||||
return resp.Issues, err
|
||||
}
|
||||
|
||||
type CustomFields map[string]string
|
||||
|
||||
// Returns a map of customfield_* keys with string values
|
||||
func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *http.Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
issue := new(map[string]interface{})
|
||||
resp, err := s.client.Do(req, issue)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
m := *issue
|
||||
f := m["fields"]
|
||||
cf := make(CustomFields)
|
||||
if f == nil {
|
||||
return cf, resp, nil
|
||||
}
|
||||
|
||||
if rec, ok := f.(map[string]interface{}); ok {
|
||||
for key, val := range rec {
|
||||
if strings.Contains(key, "customfield") {
|
||||
cf[key] = fmt.Sprint(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cf, resp, nil
|
||||
}
|
||||
|
233
issue_test.go
233
issue_test.go
@ -2,7 +2,10 @@ package jira
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -116,3 +119,233 @@ func TestIssueAddLink(t *testing.T) {
|
||||
t.Errorf("Error given: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueFields(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}]},"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","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")
|
||||
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 err != nil {
|
||||
t.Errorf("Error given: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueDownloadAttachment(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 TestIssueDownloadAttachment_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 TestIssuePostAttachment(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 TestIssuePostAttachment_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 TestIssuePostAttachment_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 TestIssuePostAttachment_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 TestIssue_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")
|
||||
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}}}]}`)
|
||||
})
|
||||
_ ,err := testClient.Issue.Search("something")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error given: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func Test_CustomFields(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","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")
|
||||
}
|
||||
}
|
55
jira.go
55
jira.go
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
@ -24,6 +23,7 @@ type Client struct {
|
||||
// Services used for talking to different parts of the JIRA API.
|
||||
Authentication *AuthenticationService
|
||||
Issue *IssueService
|
||||
Project *ProjectService
|
||||
}
|
||||
|
||||
// NewClient returns a new JIRA API client.
|
||||
@ -49,6 +49,7 @@ func NewClient(httpClient *http.Client, baseURL string) (*Client, error) {
|
||||
}
|
||||
c.Authentication = &AuthenticationService{client: c}
|
||||
c.Issue = &IssueService{client: c}
|
||||
c.Project = &ProjectService{client: c}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@ -83,6 +84,36 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
|
||||
|
||||
// Set session cookie if there is one
|
||||
if c.session != nil {
|
||||
for _, cookie := range c.session.Cookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewMultiPartRequest creates an API request including a multi-part file.
|
||||
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
|
||||
// Relative URLs should always be specified without a preceding slash.
|
||||
// If specified, the value pointed to by buf is a multipart form.
|
||||
func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
|
||||
rel, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := c.baseURL.ResolveReference(rel)
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set required headers
|
||||
req.Header.Set("X-Atlassian-Token", "nocheck")
|
||||
|
||||
// Set session cookie if there is one
|
||||
if c.Authentication.Authenticated() {
|
||||
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", c.session.Session.Name, c.session.Session.Value))
|
||||
}
|
||||
|
||||
@ -97,8 +128,6 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = CheckResponse(resp)
|
||||
if err != nil {
|
||||
// Even though there was an error, we still return the response
|
||||
@ -107,6 +136,8 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
// Open a NewDecoder and defer closing the reader only if there is a provided interface to decode to
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
}
|
||||
|
||||
@ -115,17 +146,19 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||
|
||||
// CheckResponse checks the API response for errors, and returns them if present.
|
||||
// A response is considered an error if it has a status code outside the 200 range.
|
||||
// API error responses are expected to have either no response body, or a JSON response body that maps to ErrorResponse.
|
||||
// Any other response body will be silently ignored.
|
||||
// The caller is responsible to analyze the response body.
|
||||
// The body can contain JSON (if the error is intended) or xml (sometimes JIRA just failes).
|
||||
func CheckResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
errorResponse := &ErrorResponse{Response: r}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err == nil && data != nil {
|
||||
json.Unmarshal(data, errorResponse)
|
||||
}
|
||||
return errorResponse
|
||||
err := fmt.Errorf("Request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetBaseURL will return you the Base URL.
|
||||
// This is the same URL as in the NewClient constructor
|
||||
func (c *Client) GetBaseURL() url.URL {
|
||||
return *c.baseURL
|
||||
}
|
||||
|
45
jira_test.go
45
jira_test.go
@ -215,6 +215,32 @@ func TestDo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDo_HTTPResponse(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
type foo struct {
|
||||
A string
|
||||
}
|
||||
|
||||
testMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if m := "GET"; m != r.Method {
|
||||
t.Errorf("Request method = %v, want %v", r.Method, m)
|
||||
}
|
||||
fmt.Fprint(w, `{"A":"a"}`)
|
||||
})
|
||||
|
||||
req, _ := testClient.NewRequest("GET", "/", nil)
|
||||
res, _ := testClient.Do(req, nil)
|
||||
_, err := ioutil.ReadAll(res.Body)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error on parsing HTTP Response = %v", err.Error())
|
||||
} else if res.StatusCode != 200 {
|
||||
t.Errorf("Response code = %v, want %v", res.StatusCode, 200)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDo_HTTPError(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
@ -251,3 +277,22 @@ func TestDo_RedirectLoop(t *testing.T) {
|
||||
t.Errorf("Expected a URL error; got %+v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBaseURL_WithURL(t *testing.T) {
|
||||
u, err := url.Parse(testJIRAInstanceURL)
|
||||
if err != nil {
|
||||
t.Errorf("URL parsing -> Got an error: %s", err)
|
||||
}
|
||||
|
||||
c, err := NewClient(nil, testJIRAInstanceURL)
|
||||
if err != nil {
|
||||
t.Errorf("Client creation -> Got an error: %s", err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Error("Expected a client. Got none")
|
||||
}
|
||||
|
||||
if b := c.GetBaseURL(); !reflect.DeepEqual(b, *u) {
|
||||
t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b)
|
||||
}
|
||||
}
|
||||
|
9872
mocks/all_projects.json
Normal file
9872
mocks/all_projects.json
Normal file
File diff suppressed because it is too large
Load Diff
411
mocks/project.json
Normal file
411
mocks/project.json
Normal file
@ -0,0 +1,411 @@
|
||||
{
|
||||
"expand": "projectKeys",
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/project/12310505",
|
||||
"id": "12310505",
|
||||
"key": "ABDERA",
|
||||
"description": "The Abdera project is an implementation of the Atom Syndication Format (RFC4287) and the Atom Publishing Protocol specifications published by the IETF Atompub working group.",
|
||||
"lead": {
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/user?username=rooneg",
|
||||
"key": "rooneg",
|
||||
"name": "rooneg",
|
||||
"avatarUrls": {
|
||||
"48x48": "https://issues.apache.org/jira/secure/useravatar?avatarId=10452",
|
||||
"24x24": "https://issues.apache.org/jira/secure/useravatar?size=small&avatarId=10452",
|
||||
"16x16": "https://issues.apache.org/jira/secure/useravatar?size=xsmall&avatarId=10452",
|
||||
"32x32": "https://issues.apache.org/jira/secure/useravatar?size=medium&avatarId=10452"
|
||||
},
|
||||
"displayName": "Garrett Rooney",
|
||||
"active": true
|
||||
},
|
||||
"components": [],
|
||||
"issueTypes": [
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/1",
|
||||
"id": "1",
|
||||
"description": "A problem which impairs or prevents the functions of the product.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/bug.png",
|
||||
"name": "Bug",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/2",
|
||||
"id": "2",
|
||||
"description": "A new feature of the product, which has yet to be developed.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/newfeature.png",
|
||||
"name": "New Feature",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/4",
|
||||
"id": "4",
|
||||
"description": "An improvement or enhancement to an existing feature or task.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/improvement.png",
|
||||
"name": "Improvement",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/6",
|
||||
"id": "6",
|
||||
"description": "A new unit, integration or system test.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/requirement.png",
|
||||
"name": "Test",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/5",
|
||||
"id": "5",
|
||||
"description": "General wishlist item.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/improvement.png",
|
||||
"name": "Wish",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/3",
|
||||
"id": "3",
|
||||
"description": "A task that needs to be done.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/task.png",
|
||||
"name": "Task",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/7",
|
||||
"id": "7",
|
||||
"description": "The sub-task of the issue",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/subtask_alternate.png",
|
||||
"name": "Sub-task",
|
||||
"subtask": true
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/8",
|
||||
"id": "8",
|
||||
"description": "A request for a new JIRA project to be set up",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/jiraman18.gif",
|
||||
"name": "New JIRA Project",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/9",
|
||||
"id": "9",
|
||||
"description": "An RTC request",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/newfeature.png",
|
||||
"name": "RTC",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10",
|
||||
"id": "10",
|
||||
"description": "Challenges made against the Sun Compatibility Test Suite",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/task.png",
|
||||
"name": "TCK Challenge",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/11",
|
||||
"id": "11",
|
||||
"description": "A formal question. Initially added for the Legal JIRA.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/genericissue.png",
|
||||
"name": "Question",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/12",
|
||||
"id": "12",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/genericissue.png",
|
||||
"name": "Temp",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/13",
|
||||
"id": "13",
|
||||
"description": "A place to record back and forth on notions not yet formed enough to make a 'New Feature' or 'Task'",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/genericissue.png",
|
||||
"name": "Brainstorming",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/14",
|
||||
"id": "14",
|
||||
"description": "An overarching type made of sub-tasks",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/genericissue.png",
|
||||
"name": "Umbrella",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/15",
|
||||
"id": "15",
|
||||
"description": "Created by JIRA Agile - do not edit or delete. Issue type for a big user story that needs to be broken down.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/epic.png",
|
||||
"name": "Epic",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/16",
|
||||
"id": "16",
|
||||
"description": "Created by JIRA Agile - do not edit or delete. Issue type for a user story.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/story.png",
|
||||
"name": "Story",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/17",
|
||||
"id": "17",
|
||||
"description": "A technical task.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/task_agile.png",
|
||||
"name": "Technical task",
|
||||
"subtask": true
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/18",
|
||||
"id": "18",
|
||||
"description": "Upgrading a dependency to a newer version",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/improvement.png",
|
||||
"name": "Dependency upgrade",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/19",
|
||||
"id": "19",
|
||||
"description": "A search for a suitable name for an Apache product",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/requirement.png",
|
||||
"name": "Suitable Name Search",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/20",
|
||||
"id": "20",
|
||||
"description": "Documentation or Website",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/documentation.png",
|
||||
"name": "Documentation",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10000",
|
||||
"id": "10000",
|
||||
"description": "Assigned specifically to Contractors by the VP Infra or or other VP/ Board Member.",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/sales.png",
|
||||
"name": "Planned Work",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10100",
|
||||
"id": "10100",
|
||||
"description": "A request for a new Confluence Wiki to be set up",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=23211&avatarType=issuetype",
|
||||
"name": "New Confluence Wiki",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10200",
|
||||
"id": "10200",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21140&avatarType=issuetype",
|
||||
"name": "New Git Repo",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10201",
|
||||
"id": "10201",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "Github Integration",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10202",
|
||||
"id": "10202",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "New TLP ",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10204",
|
||||
"id": "10204",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "New TLP - Common Tasks",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10205",
|
||||
"id": "10205",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21134&avatarType=issuetype",
|
||||
"name": "SVN->GIT Migration",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10206",
|
||||
"id": "10206",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "Blog - New Blog Request",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10207",
|
||||
"id": "10207",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "Blogs - New Blog User Account Request",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10208",
|
||||
"id": "10208",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "Blogs - Access to Existing Blog",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10209",
|
||||
"id": "10209",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "New Bugzilla Project",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10210",
|
||||
"id": "10210",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/secure/viewavatar?size=xsmall&avatarId=21130&avatarType=issuetype",
|
||||
"name": "SVN->GIT Mirroring",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10300",
|
||||
"id": "10300",
|
||||
"description": "For general IT problems and questions. Created by JIRA Service Desk.",
|
||||
"iconUrl": "https://issues.apache.org/jira/servicedesk/issue-type-icons?icon=it-help",
|
||||
"name": "IT Help",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10301",
|
||||
"id": "10301",
|
||||
"description": "For new system accounts or passwords. Created by JIRA Service Desk.",
|
||||
"iconUrl": "https://issues.apache.org/jira/servicedesk/issue-type-icons?icon=access",
|
||||
"name": "Access",
|
||||
"subtask": false
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/issuetype/10400",
|
||||
"id": "10400",
|
||||
"description": "",
|
||||
"iconUrl": "https://issues.apache.org/jira/images/icons/issuetypes/genericissue.png",
|
||||
"name": "Request",
|
||||
"subtask": false
|
||||
}
|
||||
],
|
||||
"url": "http://abdera.apache.org",
|
||||
"assigneeType": "UNASSIGNED",
|
||||
"versions": [
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12312506",
|
||||
"id": "12312506",
|
||||
"name": "0.2.2",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2007-02-19",
|
||||
"userReleaseDate": "19/Feb/07",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12312507",
|
||||
"id": "12312507",
|
||||
"name": "0.3.0",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2007-10-05",
|
||||
"userReleaseDate": "05/Oct/07",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12312825",
|
||||
"id": "12312825",
|
||||
"name": "0.4.0",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2008-04-11",
|
||||
"userReleaseDate": "11/Apr/08",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12313105",
|
||||
"id": "12313105",
|
||||
"name": "1.0",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2010-05-02",
|
||||
"userReleaseDate": "02/May/10",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12314990",
|
||||
"id": "12314990",
|
||||
"name": "1.1",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2010-07-11",
|
||||
"userReleaseDate": "11/Jul/10",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12315922",
|
||||
"id": "12315922",
|
||||
"name": "1.1.1",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2010-12-06",
|
||||
"userReleaseDate": "06/Dec/10",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12316041",
|
||||
"id": "12316041",
|
||||
"name": "1.1.2",
|
||||
"archived": false,
|
||||
"released": true,
|
||||
"releaseDate": "2011-01-15",
|
||||
"userReleaseDate": "15/Jan/11",
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12323557",
|
||||
"id": "12323557",
|
||||
"name": "1.1.3",
|
||||
"archived": false,
|
||||
"released": false,
|
||||
"projectId": 12310505
|
||||
},
|
||||
{
|
||||
"self": "https://issues.apache.org/jira/rest/api/2/version/12316141",
|
||||
"id": "12316141",
|
||||
"name": "1.2",
|
||||
"archived": false,
|
||||
"released": false,
|
||||
"projectId": 12310505
|
||||
}
|
||||
],
|
||||
"name": "Abdera",
|
||||
"roles": {
|
||||
"Service Desk Team": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10251",
|
||||
"Developers": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10050",
|
||||
"Service Desk Customers": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10250",
|
||||
"Contributors": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10010",
|
||||
"PMC": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10011",
|
||||
"Committers": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10001",
|
||||
"Administrators": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10002",
|
||||
"ASF Members": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10150",
|
||||
"Users": "https://issues.apache.org/jira/rest/api/2/project/12310505/role/10040"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"48x48": "https://issues.apache.org/jira/secure/projectavatar?pid=12310505&avatarId=10011",
|
||||
"24x24": "https://issues.apache.org/jira/secure/projectavatar?size=small&pid=12310505&avatarId=10011",
|
||||
"16x16": "https://issues.apache.org/jira/secure/projectavatar?size=xsmall&pid=12310505&avatarId=10011",
|
||||
"32x32": "https://issues.apache.org/jira/secure/projectavatar?size=medium&pid=12310505&avatarId=10011"
|
||||
}
|
||||
}
|
109
project.go
Normal file
109
project.go
Normal file
@ -0,0 +1,109 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ProjectService handles projects for the JIRA instance / API.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project
|
||||
type ProjectService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// ProjectList represent a list of Projects
|
||||
type ProjectList []struct {
|
||||
Expand string `json:"expand"`
|
||||
Self string `json:"self"`
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
AvatarUrls AvatarUrls `json:"avatarUrls"`
|
||||
ProjectTypeKey string `json:"projectTypeKey"`
|
||||
ProjectCategory ProjectCategory `json:"projectCategory,omitempty"`
|
||||
}
|
||||
|
||||
// ProjectCategory represents a single project category
|
||||
type ProjectCategory struct {
|
||||
Self string `json:"self"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// Project represents a JIRA Project.
|
||||
type Project struct {
|
||||
Expand string `json:"expand,omitempty"`
|
||||
Self string `json:"self,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Lead User `json:"lead,omitempty"`
|
||||
Components []ProjectComponent `json:"components,omitempty"`
|
||||
IssueTypes []IssueType `json:"issueTypes,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
AssigneeType string `json:"assigneeType,omitempty"`
|
||||
Versions []interface{} `json:"versions,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Roles struct {
|
||||
Developers string `json:"Developers,omitempty"`
|
||||
} `json:"roles,omitempty"`
|
||||
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty"`
|
||||
ProjectCategory ProjectCategory `json:"projectCategory,omitempty"`
|
||||
}
|
||||
|
||||
// ProjectComponent represents a single component of a project
|
||||
type ProjectComponent struct {
|
||||
Self string `json:"self"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Lead User `json:"lead"`
|
||||
AssigneeType string `json:"assigneeType"`
|
||||
Assignee User `json:"assignee"`
|
||||
RealAssigneeType string `json:"realAssigneeType"`
|
||||
RealAssignee User `json:"realAssignee"`
|
||||
IsAssigneeTypeValid bool `json:"isAssigneeTypeValid"`
|
||||
Project string `json:"project"`
|
||||
ProjectID int `json:"projectId"`
|
||||
}
|
||||
|
||||
// GetList gets all projects form JIRA
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
||||
func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) {
|
||||
apiEndpoint := "rest/api/2/project"
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
projectList := new(ProjectList)
|
||||
resp, err := s.client.Do(req, projectList)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return projectList, resp, nil
|
||||
}
|
||||
|
||||
// Get returns a full representation of the project for the given issue key.
|
||||
// JIRA will attempt to identify the project by the projectIdOrKey path parameter.
|
||||
// This can be an project id, or an project key.
|
||||
//
|
||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
||||
func (s *ProjectService) Get(projectID string) (*Project, *http.Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s", projectID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
project := new(Project)
|
||||
resp, err := s.client.Do(req, project)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return project, resp, nil
|
||||
}
|
80
project_test.go
Normal file
80
project_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProjectGetAll(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
testAPIEdpoint := "/rest/api/2/project"
|
||||
|
||||
raw, err := ioutil.ReadFile("./mocks/all_projects.json")
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testRequestURL(t, r, testAPIEdpoint)
|
||||
fmt.Fprint(w, string(raw))
|
||||
})
|
||||
|
||||
projects, _, err := testClient.Project.GetList()
|
||||
if projects == nil {
|
||||
t.Error("Expected project list. Project list is nil")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Error given: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectGet(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
testAPIEdpoint := "/rest/api/2/project/12310505"
|
||||
|
||||
raw, err := ioutil.ReadFile("./mocks/project.json")
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testRequestURL(t, r, testAPIEdpoint)
|
||||
fmt.Fprint(w, string(raw))
|
||||
})
|
||||
|
||||
projects, _, err := testClient.Project.Get("12310505")
|
||||
if projects == nil {
|
||||
t.Error("Expected project list. Project list is nil")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Error given: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectGet_NoProject(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
testAPIEdpoint := "/rest/api/2/project/99999999"
|
||||
|
||||
testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
testRequestURL(t, r, testAPIEdpoint)
|
||||
fmt.Fprint(w, nil)
|
||||
})
|
||||
|
||||
projects, resp, err := testClient.Project.Get("99999999")
|
||||
if projects != nil {
|
||||
t.Errorf("Expected nil. Got %+v", projects)
|
||||
}
|
||||
|
||||
if resp.Status == "404" {
|
||||
t.Errorf("Expected status 404. Got %s", resp.Status)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Error given: %s", err)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user