Repository: PagerDuty/go-pagerduty
Pull request #246 (2020-10-09 23:30:32)

Add Support For Change Events
This PR implements the newly released [Change Events](https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#try-it-out) feature. PR #241 seems to have taken a better approach on dealing with V2 Event endpoints in general, so if we can get some consensus on that approach I'd be more than happy to refactor that bit of my implementation.



Patch:
diff --git a/change_events.go b/change_events.go
new file mode 100644
index 0000000..08a63f8
--- /dev/null
+++ b/change_events.go
@@ -0,0 +1,75 @@
+package pagerduty
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+)
+
+const ChangeEventPath = "/v2/change/enqueue"
+
+// ChangeEvent represents a ChangeEvent's request parameters
+// https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#parameters
+type ChangeEvent struct {
+	RoutingKey string  `json:"routing_key"`
+	Payload    Payload `json:"payload"`
+	Links      []Link  `json:"links"`
+}
+
+// Payload ChangeEvent Payload
+// https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#example-request-payload
+type Payload struct {
+	Source        string                 `json:"source"`
+	Summary       string                 `json:"summary"`
+	Timestamp     string                 `json:"timestamp"`
+	CustomDetails map[string]interface{} `json:"custom_details"`
+}
+
+// Link represents a single link in a ChangeEvent
+// https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#the-links-property
+type Link struct {
+	Href string `json:"href"`
+	Text string `json:"text"`
+}
+
+// ChangeEventResponse is the json response body for an event
+type ChangeEventResponse struct {
+	Status  string   `json:"status,omitempty"`
+	Message string   `json:"message,omitempty"`
+	Errors  []string `json:"errors,omitempty"`
+}
+
+// CreateChangeEvent Sends PagerDuty a single ChangeEvent to record
+// The v2EventsAPIEndpoint parameter must be set on the client
+// Documentation can be found at https://developer.pagerduty.com/docs/events-api-v2/send-change-events
+func (c *Client) CreateChangeEvent(e ChangeEvent) (*ChangeEventResponse, error) {
+	if c.v2EventsAPIEndpoint == "" {
+		return nil, errors.New("v2EventsAPIEndpoint field must be set on Client")
+	}
+
+	headers := make(map[string]string)
+
+	data, err := json.Marshal(e)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.doWithEndpoint(
+		c.v2EventsAPIEndpoint,
+		"POST",
+		ChangeEventPath,
+		false,
+		bytes.NewBuffer(data),
+		&headers,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	var eventResponse ChangeEventResponse
+	if err := json.NewDecoder(resp.Body).Decode(&eventResponse); err != nil {
+		return nil, err
+	}
+
+	return &eventResponse, nil
+}


Test patch:
diff --git a/change_events_test.go b/change_events_test.go
new file mode 100644
index 0000000..46b3e49
--- /dev/null
+++ b/change_events_test.go
@@ -0,0 +1,115 @@
+package pagerduty
+
+import (
+	"io/ioutil"
+	"net/http"
+	"testing"
+)
+
+const (
+	expectedChangeCreatePayload = `{"routing_key":"a0000000aa0000a0a000aa0a0a0aa000","payload":{"source":"Test runner",` +
+		`"summary":"Summary can't be blank","timestamp":"2020-10-19T03:06:16.318Z",` +
+		`"custom_details":{"DetailKey1":"DetailValue1","DetailKey2":"DetailValue2"}},` +
+		`"links":[{"href":"https://acme.pagerduty.dev/build/2","text":"View more details in Acme!"},` +
+		`{"href":"https://acme2.pagerduty.dev/build/2","text":"View more details in Acme2!"}]}`
+)
+
+func TestChangeEvent_Create(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(
+		"/v2/change/enqueue", func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, "POST")
+			w.WriteHeader(http.StatusAccepted)
+			w.Write([]byte(`{"message": "Change event processed", "status": "success"}`))
+		},
+	)
+
+	var client = &Client{
+		v2EventsAPIEndpoint: server.URL,
+		apiEndpoint:         server.URL,
+		authToken:           "foo",
+		HTTPClient:          defaultHTTPClient,
+	}
+
+	want := ChangeEventResponse{
+		Status:  "success",
+		Message: "Change event processed",
+	}
+
+	eventDetails := map[string]interface{}{"DetailKey1": "DetailValue1", "DetailKey2": "DetailValue2"}
+	ce := ChangeEvent{
+		RoutingKey: "a0000000aa0000a0a000aa0a0a0aa000",
+		Payload: Payload{
+			Source:        "Test runner",
+			Summary:       "Summary can't be blank",
+			Timestamp:     "2020-10-19T03:06:16.318Z",
+			CustomDetails: eventDetails,
+		},
+		Links: []Link{
+			{
+				Href: "https://acme.pagerduty.dev/build/2",
+				Text: "View more details in Acme!",
+			},
+			{
+				Href: "https://acme2.pagerduty.dev/build/2",
+				Text: "View more details in Acme2!",
+			},
+		},
+	}
+
+	res, err := client.CreateChangeEvent(ce)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	testEqual(t, want, *res)
+}
+
+func TestChangeEvent_CreateWithPayloadVerification(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(
+		"/v2/change/enqueue", func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, "POST")
+			body, err := ioutil.ReadAll(r.Body)
+			if err != nil {
+				t.Fatal(err)
+			}
+			testEqual(t, expectedChangeCreatePayload, string(body))
+		},
+	)
+
+	var client = &Client{
+		v2EventsAPIEndpoint: server.URL,
+		apiEndpoint:         server.URL,
+		authToken:           "foo",
+		HTTPClient:          defaultHTTPClient,
+	}
+
+	eventDetails := map[string]interface{}{"DetailKey1": "DetailValue1", "DetailKey2": "DetailValue2"}
+	ce := ChangeEvent{
+		RoutingKey: "a0000000aa0000a0a000aa0a0a0aa000",
+		Payload: Payload{
+			Source:        "Test runner",
+			Summary:       "Summary can't be blank",
+			Timestamp:     "2020-10-19T03:06:16.318Z",
+			CustomDetails: eventDetails,
+		},
+		Links: []Link{
+			{
+				Href: "https://acme.pagerduty.dev/build/2",
+				Text: "View more details in Acme!",
+			},
+			{
+				Href: "https://acme2.pagerduty.dev/build/2",
+				Text: "View more details in Acme2!",
+			},
+		},
+	}
+
+	_, _ = client.CreateChangeEvent(ce)
+
+}