Repository: PagerDuty/go-pagerduty
Pull request #241 (2020-09-11 13:39:52)

Event v2 client
Implements ManageEvent for the pagerduty Client - so we can mock it out and test it.


Patch:
diff --git a/client.go b/client.go
index 9e418f9..68fe571 100644
--- a/client.go
+++ b/client.go
@@ -12,7 +12,8 @@ import (
 )
 
 const (
-	apiEndpoint = "https://api.pagerduty.com"
+	apiEndpoint         = "https://api.pagerduty.com"
+	v2EventsAPIEndpoint = "https://events.pagerduty.com"
 )
 
 // The type of authentication to use with the API client
@@ -96,8 +97,9 @@ var defaultHTTPClient HTTPClient = newDefaultHTTPClient()
 
 // Client wraps http client
 type Client struct {
-	authToken   string
-	apiEndpoint string
+	authToken           string
+	apiEndpoint         string
+	v2EventsAPIEndpoint string
 
 	// Authentication type to use for API
 	authType authType
@@ -111,10 +113,11 @@ type Client struct {
 // NewClient creates an API client using an account/user API token
 func NewClient(authToken string, options ...ClientOptions) *Client {
 	client := Client{
-		authToken:   authToken,
-		apiEndpoint: apiEndpoint,
-		authType:    apiToken,
-		HTTPClient:  defaultHTTPClient,
+		authToken:           authToken,
+		apiEndpoint:         apiEndpoint,
+		v2EventsAPIEndpoint: v2EventsAPIEndpoint,
+		authType:            apiToken,
+		HTTPClient:          defaultHTTPClient,
 	}
 
 	for _, opt := range options {
@@ -139,6 +142,13 @@ func WithAPIEndpoint(endpoint string) ClientOptions {
 	}
 }
 
+// WithV2EventsAPIEndpoint allows for a custom V2 Events API endpoint to be passed into the client
+func WithV2EventsAPIEndpoint(endpoint string) ClientOptions {
+	return func(c *Client) {
+		c.v2EventsAPIEndpoint = endpoint
+	}
+}
+
 // WithOAuth allows for an OAuth token to be passed into the the client
 func WithOAuth() ClientOptions {
 	return func(c *Client) {
@@ -174,29 +184,36 @@ func (c *Client) get(path string) (*http.Response, error) {
 	return c.do("GET", path, nil, nil)
 }
 
-func (c *Client) do(method, path string, body io.Reader, headers *map[string]string) (*http.Response, error) {
-	endpoint := c.apiEndpoint + path
-	req, _ := http.NewRequest(method, endpoint, body)
+// needed where pagerduty use a different endpoint for certain actions (eg: v2 events)
+func (c *Client) doWithEndpoint(endpoint, method, path string, authRequired bool, body io.Reader, headers *map[string]string) (*http.Response, error) {
+	req, _ := http.NewRequest(method, endpoint+path, body)
 	req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2")
 	if headers != nil {
 		for k, v := range *headers {
 			req.Header.Set(k, v)
 		}
 	}
-	req.Header.Set("User-Agent", "go-pagerduty/"+Version)
-	req.Header.Set("Content-Type", "application/json")
 
-	switch c.authType {
-	case oauthToken:
-		req.Header.Set("Authorization", "Bearer "+c.authToken)
-	default:
-		req.Header.Set("Authorization", "Token token="+c.authToken)
+	if authRequired {
+		switch c.authType {
+		case oauthToken:
+			req.Header.Set("Authorization", "Bearer "+c.authToken)
+		default:
+			req.Header.Set("Authorization", "Token token="+c.authToken)
+		}
 	}
 
+	req.Header.Set("User-Agent", "go-pagerduty/"+Version)
+	req.Header.Set("Content-Type", "application/json")
+
 	resp, err := c.HTTPClient.Do(req)
 	return c.checkResponse(resp, err)
 }
 
+func (c *Client) do(method, path string, body io.Reader, headers *map[string]string) (*http.Response, error) {
+	return c.doWithEndpoint(c.apiEndpoint, method, path, true, body, headers)
+}
+
 func (c *Client) decodeJSON(resp *http.Response, payload interface{}) error {
 	defer resp.Body.Close()
 	decoder := json.NewDecoder(resp.Body)
diff --git a/event_v2.go b/event_v2.go
index 4e5bc4a..852e24e 100644
--- a/event_v2.go
+++ b/event_v2.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 )
 
-// Event includes the incident/alert details
+// V2Event includes the incident/alert details
 type V2Event struct {
 	RoutingKey string        `json:"routing_key"`
 	Action     string        `json:"event_action"`
@@ -20,7 +20,7 @@ type V2Event struct {
 	Payload    *V2Payload    `json:"payload,omitempty"`
 }
 
-// Payload represents the individual event details for an event
+// V2Payload represents the individual event details for an event
 type V2Payload struct {
 	Summary   string      `json:"summary"`
 	Source    string      `json:"source"`
@@ -32,7 +32,7 @@ type V2Payload struct {
 	Details   interface{} `json:"custom_details,omitempty"`
 }
 
-// Response is the json response body for an event
+// V2EventResponse is the json response body for an event
 type V2EventResponse struct {
 	Status   string   `json:"status,omitempty"`
 	DedupKey string   `json:"dedup_key,omitempty"`
@@ -69,3 +69,20 @@ func ManageEvent(e V2Event) (*V2EventResponse, error) {
 	}
 	return &eventResponse, nil
 }
+
+// ManageEvent handles the trigger, acknowledge, and resolve methods for an event
+func (c *Client) ManageEvent(e *V2Event) (*V2EventResponse, error) {
+	headers := make(map[string]string)
+
+	data, err := json.Marshal(e)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := c.doWithEndpoint(c.v2EventsAPIEndpoint, "POST", "/v2/enqueue", false, bytes.NewBuffer(data), &headers)
+	if err != nil {
+		return nil, err
+	}
+	result := &V2EventResponse{}
+	err = json.NewDecoder(resp.Body).Decode(result)
+	return result, err
+}


Test patch:
diff --git a/event_v2_test.go b/event_v2_test.go
new file mode 100644
index 0000000..d9ff4c4
--- /dev/null
+++ b/event_v2_test.go
@@ -0,0 +1,30 @@
+package pagerduty
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestEventV2_ManageEvent(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/v2/enqueue", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		w.Write([]byte(`{"status": "ok", "dedup_key": "yes", "message": "ok"}`))
+	})
+	var client = &Client{v2EventsAPIEndpoint: server.URL, apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	evt := &V2Event{
+		RoutingKey: "abc123",
+	}
+	res, err := client.ManageEvent(evt)
+	if err != nil {
+		t.Fatal(err)
+	}
+	want := &V2EventResponse{
+		Status:   "ok",
+		DedupKey: "yes",
+		Message:  "ok",
+	}
+	testEqual(t, want, res)
+}