Repository: ankitpokhrel/jira-cli
Pull request #28 (2020-12-19 12:42:27)

feat(client): Add POST method and request headers


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index aebe4bf..911e74a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -23,7 +23,6 @@ linters:
     - prealloc
     - unparam
     - varcheck
-    - wsl
   fast: false
 linters-settings:
   gofmt:
diff --git a/pkg/jira/board.go b/pkg/jira/board.go
index bca1abf..02f19b8 100644
--- a/pkg/jira/board.go
+++ b/pkg/jira/board.go
@@ -28,7 +28,7 @@ func (c *Client) Boards(project, boardType string) (*BoardResult, error) {
 		path += fmt.Sprintf("&type=%s", boardType)
 	}
 
-	res, err := c.GetV1(context.Background(), path)
+	res, err := c.GetV1(context.Background(), path, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/jira/client.go b/pkg/jira/client.go
index 7ebbd38..316eb93 100644
--- a/pkg/jira/client.go
+++ b/pkg/jira/client.go
@@ -3,6 +3,7 @@ package jira
 import (
 	"context"
 	"fmt"
+	"io"
 	"net"
 	"net/http"
 	"strings"
@@ -26,6 +27,9 @@ var (
 	ErrUnexpectedStatusCode = fmt.Errorf("jira: unexpected status code")
 )
 
+// Header is a key, value pair for request headers.
+type Header map[string]string
+
 // Config is a jira config.
 type Config struct {
 	Server   string
@@ -77,26 +81,39 @@ func WithTimeout(to time.Duration) ClientFunc {
 	}
 }
 
-// Get sends get request to v3 version of the jira api.
-func (c *Client) Get(ctx context.Context, path string) (*http.Response, error) {
-	return c.request(ctx, c.server+baseURLv3+path)
+// Get sends GET request to v3 version of the jira api.
+func (c *Client) Get(ctx context.Context, path string, headers Header) (*http.Response, error) {
+	return c.request(ctx, http.MethodGet, c.server+baseURLv3+path, nil, headers)
 }
 
 // GetV1 sends get request to v1 version of the jira api.
-func (c *Client) GetV1(ctx context.Context, path string) (*http.Response, error) {
-	return c.request(ctx, c.server+baseURLv1+path)
+func (c *Client) GetV1(ctx context.Context, path string, headers Header) (*http.Response, error) {
+	return c.request(ctx, http.MethodGet, c.server+baseURLv1+path, nil, headers)
+}
+
+// Post sends POST request to v3 version of the jira api.
+func (c *Client) Post(ctx context.Context, path string, body io.Reader, headers Header) (*http.Response, error) {
+	res, err := c.request(ctx, http.MethodPost, c.server+baseURLv3+path, body, headers)
+	if err != nil {
+		return res, err
+	}
+	return res, err
 }
 
-func (c *Client) request(ctx context.Context, endpoint string) (*http.Response, error) {
+func (c *Client) request(ctx context.Context, method, endpoint string, body io.Reader, headers Header) (*http.Response, error) {
 	if c.debug {
-		fmt.Printf("Requesting: %s\n", endpoint)
+		fmt.Printf("Requesting %s: %s\n", method, endpoint)
 	}
 
-	req, err := http.NewRequest(http.MethodGet, endpoint, nil)
+	req, err := http.NewRequest(method, endpoint, body)
 	if err != nil {
 		return nil, err
 	}
 
+	for k, v := range headers {
+		req.Header.Set(k, v)
+	}
+
 	req.SetBasicAuth(c.login, c.token)
 
 	res, err := c.transport.RoundTrip(req.WithContext(ctx))
diff --git a/pkg/jira/epic.go b/pkg/jira/epic.go
index 0bdf560..a5770a1 100644
--- a/pkg/jira/epic.go
+++ b/pkg/jira/epic.go
@@ -10,7 +10,7 @@ import (
 
 // Epic fetches epics using the /search endpoint.
 func (c *Client) Epic(jql string) (*SearchResult, error) {
-	res, err := c.Get(context.Background(), "/search?jql="+url.QueryEscape(jql))
+	res, err := c.Get(context.Background(), "/search?jql="+url.QueryEscape(jql), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +39,7 @@ func (c *Client) EpicIssues(key, jql string) (*SearchResult, error) {
 		path += fmt.Sprintf("?jql=%s", url.QueryEscape(jql))
 	}
 
-	res, err := c.GetV1(context.Background(), path)
+	res, err := c.GetV1(context.Background(), path, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/jira/me.go b/pkg/jira/me.go
index ec01af0..90b4597 100644
--- a/pkg/jira/me.go
+++ b/pkg/jira/me.go
@@ -14,7 +14,7 @@ type Me struct {
 
 // Me fetches response from /myself endpoint.
 func (c *Client) Me() (*Me, error) {
-	res, err := c.Get(context.Background(), "/myself")
+	res, err := c.Get(context.Background(), "/myself", nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/jira/project.go b/pkg/jira/project.go
index 54137dd..4c2ac90 100644
--- a/pkg/jira/project.go
+++ b/pkg/jira/project.go
@@ -8,7 +8,7 @@ import (
 
 // Project fetches response from /project endpoint.
 func (c *Client) Project() ([]*Project, error) {
-	res, err := c.Get(context.Background(), "/project?expand=lead")
+	res, err := c.Get(context.Background(), "/project?expand=lead", nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/jira/search.go b/pkg/jira/search.go
index fe612b6..1f3d4b6 100644
--- a/pkg/jira/search.go
+++ b/pkg/jira/search.go
@@ -22,7 +22,7 @@ type SearchResult struct {
 func (c *Client) Search(jql string) (*SearchResult, error) {
 	path := fmt.Sprintf("/search?jql=%s&maxResults=%d", url.QueryEscape(jql), maxResults)
 
-	res, err := c.Get(context.Background(), path)
+	res, err := c.Get(context.Background(), path, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/jira/sprint.go b/pkg/jira/sprint.go
index 7b6e303..0cf6a0c 100644
--- a/pkg/jira/sprint.go
+++ b/pkg/jira/sprint.go
@@ -20,7 +20,11 @@ type SprintResult struct {
 //
 // qp is an additional query parameters in key, value pair format, eg: state=closed.
 func (c *Client) Sprints(boardID int, qp string, startAt, max int) (*SprintResult, error) {
-	res, err := c.GetV1(context.Background(), fmt.Sprintf("/board/%d/sprint?%s&startAt=%d&maxResults=%d", boardID, qp, startAt, max))
+	res, err := c.GetV1(
+		context.Background(),
+		fmt.Sprintf("/board/%d/sprint?%s&startAt=%d&maxResults=%d", boardID, qp, startAt, max),
+		nil,
+	)
 	if err != nil {
 		return nil, err
 	}
@@ -92,7 +96,7 @@ func (c *Client) SprintIssues(boardID, sprintID int, jql string) (*SearchResult,
 		path += fmt.Sprintf("?jql=%s", url.QueryEscape(jql))
 	}
 
-	res, err := c.GetV1(context.Background(), path)
+	res, err := c.GetV1(context.Background(), path, nil)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/pkg/jira/client_test.go b/pkg/jira/client_test.go
index b2f0b8a..7b2ceb7 100644
--- a/pkg/jira/client_test.go
+++ b/pkg/jira/client_test.go
@@ -5,6 +5,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -16,13 +17,16 @@ func TestGet(t *testing.T) {
 		assert.Equal(t, url.Values{
 			"jql": []string{"project=TEST AND status=Done"},
 		}, r.URL.Query())
+		assert.Equal(t, "text/plain", r.Header.Get("Content-Type"))
 
 		w.WriteHeader(200)
 	}))
 	defer server.Close()
 
 	client := NewClient(Config{Server: server.URL}, WithTimeout(3))
-	resp, err := client.Get(context.Background(), "/search?jql=project=TEST%20AND%20status=Done")
+	resp, err := client.Get(context.Background(), "/search?jql=project=TEST%20AND%20status=Done", Header{
+		"Content-Type": "text/plain",
+	})
 
 	assert.NoError(t, err)
 	assert.Equal(t, 200, resp.StatusCode)
@@ -42,10 +46,32 @@ func TestGetV1(t *testing.T) {
 	defer server.Close()
 
 	client := NewClient(Config{Server: server.URL}, WithTimeout(3))
-	resp, err := client.GetV1(context.Background(), "/epic/TEST-1/issue?jql=project=TEST%20AND%20status=Done")
+	resp, err := client.GetV1(context.Background(), "/epic/TEST-1/issue?jql=project=TEST%20AND%20status=Done", nil)
 
 	assert.NoError(t, err)
 	assert.Equal(t, 200, resp.StatusCode)
 
 	_ = resp.Body.Close()
 }
+
+func TestPost(t *testing.T) {
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, "/rest/api/3/issue", r.URL.Path)
+		assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
+		assert.Equal(t, "jira-cli", r.Header.Get("X-Requested-By"))
+
+		w.WriteHeader(201)
+	}))
+	defer server.Close()
+
+	client := NewClient(Config{Server: server.URL}, WithTimeout(3))
+	resp, err := client.Post(context.Background(), "/issue", strings.NewReader("hello"), Header{
+		"Content-Type":   "application/json",
+		"X-Requested-By": "jira-cli",
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, 201, resp.StatusCode)
+
+	_ = resp.Body.Close()
+}