mirror of
https://github.com/interviewstreet/go-jira.git
synced 2025-06-12 23:37:31 +02:00
Merge branch 'master' of https://github.com/andygrunwald/go-jira
# Conflicts: # issue.go
This commit is contained in:
commit
c75a2ca567
@ -71,6 +71,9 @@ func main() {
|
|||||||
|
|
||||||
### Authenticate with session cookie
|
### Authenticate with session cookie
|
||||||
|
|
||||||
|
Some actions require an authenticated user.
|
||||||
|
Here is an example with a session cookie authentification.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -102,6 +105,8 @@ func main() {
|
|||||||
|
|
||||||
### Call a not implemented API endpoint
|
### 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/).
|
Lets get all public projects of [Atlassian`s JIRA instance](https://jira.atlassian.com/).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -36,6 +36,10 @@ func TestAcquireSessionCookie_Fail(t *testing.T) {
|
|||||||
if res == true {
|
if res == true {
|
||||||
t.Error("Expected error, but result was 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) {
|
func TestAcquireSessionCookie_Success(t *testing.T) {
|
||||||
@ -65,4 +69,18 @@ func TestAcquireSessionCookie_Success(t *testing.T) {
|
|||||||
if res == false {
|
if res == false {
|
||||||
t.Error("Expected result was true. Got 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
94
issue.go
94
issue.go
@ -1,7 +1,10 @@
|
|||||||
package jira
|
package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,6 +29,19 @@ type Issue struct {
|
|||||||
Fields *IssueFields `json:"fields,omitempty"`
|
Fields *IssueFields `json:"fields,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attachment represents a JIRA attachment
|
||||||
|
type Attachment struct {
|
||||||
|
Self string `json:"self,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Filename string `json:"filename,omitempty"`
|
||||||
|
Author *Assignee `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.
|
// IssueFields represents single fields of a JIRA issue.
|
||||||
// Every JIRA issue has several fields attached.
|
// Every JIRA issue has several fields attached.
|
||||||
type IssueFields struct {
|
type IssueFields struct {
|
||||||
@ -38,7 +54,6 @@ type IssueFields struct {
|
|||||||
// * "aggregatetimeoriginalestimate": null,
|
// * "aggregatetimeoriginalestimate": null,
|
||||||
// * "timeoriginalestimate": null,
|
// * "timeoriginalestimate": null,
|
||||||
// * "timetracking": {},
|
// * "timetracking": {},
|
||||||
// * "attachment": [],
|
|
||||||
// * "aggregatetimeestimate": null,
|
// * "aggregatetimeestimate": null,
|
||||||
// * "environment": null,
|
// * "environment": null,
|
||||||
// * "duedate": null,
|
// * "duedate": null,
|
||||||
@ -65,6 +80,7 @@ type IssueFields struct {
|
|||||||
FixVersions []*FixVersion `json:"fixVersions,omitempty"`
|
FixVersions []*FixVersion `json:"fixVersions,omitempty"`
|
||||||
Labels []string `json:"labels,omitempty"`
|
Labels []string `json:"labels,omitempty"`
|
||||||
Subtasks []*Subtasks `json:"subtasks,omitempty"`
|
Subtasks []*Subtasks `json:"subtasks,omitempty"`
|
||||||
|
Attachments []*Attachment `json:"attachment,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssueType represents a type of a JIRA issue.
|
// IssueType represents a type of a JIRA issue.
|
||||||
@ -267,14 +283,14 @@ type IssueLinkType struct {
|
|||||||
|
|
||||||
// Comment represents a comment by a person to an issue in JIRA.
|
// Comment represents a comment by a person to an issue in JIRA.
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
Self string `json:"self"`
|
Self string `json:"self,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name,omitempty"`
|
||||||
Author Assignee `json:"author"`
|
Author Assignee `json:"author,omitempty"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body,omitempty"`
|
||||||
UpdateAuthor Assignee `json:"updateAuthor"`
|
UpdateAuthor Assignee `json:"updateAuthor,omitempty"`
|
||||||
Updated string `json:"updated"`
|
Updated string `json:"updated,omitempty"`
|
||||||
Created string `json:"created"`
|
Created string `json:"created,omitempty"`
|
||||||
Visibility CommentVisibility `json:"visibility"`
|
Visibility CommentVisibility `json:"visibility,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixVersion represents a software release in which an issue is fixed.
|
// FixVersion represents a software release in which an issue is fixed.
|
||||||
@ -292,8 +308,8 @@ type FixVersion struct {
|
|||||||
// CommentVisibility represents he visibility of a comment.
|
// CommentVisibility represents he visibility of a comment.
|
||||||
// E.g. Type could be "role" and Value "Administrators"
|
// E.g. Type could be "role" and Value "Administrators"
|
||||||
type CommentVisibility struct {
|
type CommentVisibility struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type,omitempty"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a full representation of the issue for the given issue key.
|
// Get returns a full representation of the issue for the given issue key.
|
||||||
@ -318,6 +334,62 @@ func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
|
|||||||
return issue, resp, nil
|
return issue, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachment, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create creates an issue or a sub-task from a JSON representation.
|
// Create creates an issue or a sub-task from a JSON representation.
|
||||||
// Creating a sub-task is similar to creating a regular issue, with two important differences:
|
// Creating a sub-task is similar to creating a regular issue, with two important differences:
|
||||||
// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
|
// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
|
||||||
|
170
issue_test.go
170
issue_test.go
@ -2,8 +2,10 @@ package jira
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -135,6 +137,174 @@ func TestIssueFields(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(issue.Fields.Labels, []string{"test"}) {
|
if !reflect.DeepEqual(issue.Fields.Labels, []string{"test"}) {
|
||||||
t.Error("Expected labels for the returned issue")
|
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 {
|
if err != nil {
|
||||||
t.Errorf("Error given: %s", err)
|
t.Errorf("Error given: %s", err)
|
||||||
}
|
}
|
||||||
|
41
jira.go
41
jira.go
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
@ -92,6 +91,34 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
|
|||||||
return req, nil
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Do sends an API request and returns the API response.
|
// Do sends an API request and returns the API response.
|
||||||
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
|
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
|
||||||
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||||
@ -118,17 +145,13 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
|||||||
|
|
||||||
// CheckResponse checks the API response for errors, and returns them if present.
|
// 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.
|
// 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.
|
// The caller is responsible to analyze the response body.
|
||||||
// Any other response body will be silently ignored.
|
// The body can contain JSON (if the error is intended) or xml (sometimes JIRA just failes).
|
||||||
func CheckResponse(r *http.Response) error {
|
func CheckResponse(r *http.Response) error {
|
||||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
errorResponse := &ErrorResponse{Response: r}
|
err := fmt.Errorf("Request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode)
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
return err
|
||||||
if err == nil && data != nil {
|
|
||||||
json.Unmarshal(data, errorResponse)
|
|
||||||
}
|
|
||||||
return errorResponse
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user