Repository: nicklaw5/helix
Pull request #113 (2021-07-27 06:47:30)

Add Get HypeTrainEvents
- introduce GetHypeTrainEvents [endpoint](https://dev.twitch.tv/docs/api/reference#get-hype-train-events)
- supply accommodating test
- update README.md

NOTES:-
- docs say to use 'cursor' query parameter for pagination, however this did not achieve expected pagination behavior... it appears that actually the undocumented 'after' query parameter strikes again
- do the field names of the structs suffice?


coveralls: ## Pull Request Test Coverage Report for [Build 1070173092](https://coveralls.io/builds/41672468)

* **11** of **11**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.03%**) to **96.639%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/41672468/badge)](https://coveralls.io/builds/41672468) |
| :-- | --: |
| Change from base [Build 1068395473](https://coveralls.io/builds/41656640): |  0.03% |
| Covered Lines: | 1064 |
| Relevant Lines: | 1101 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 1070173092](https://coveralls.io/builds/41672467)

* **11** of **11**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.03%**) to **96.639%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/41672467/badge)](https://coveralls.io/builds/41672467) |
| :-- | --: |
| Change from base [Build 1068395473](https://coveralls.io/builds/41656640): |  0.03% |
| Covered Lines: | 1064 |
| Relevant Lines: | 1101 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/README.md b/README.md
index cb61264..b5267ff 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ If you are looking for the Twitch API docs, see the [Twitch Developer website](h
 - [x] Create / Remove / List EventSub Subscriptions
 - [x] Get Top Games
 - [x] Get Games
-- [ ] Get Hype Train Events
+- [x] Get Hype Train Events
 - [ ] Check AutoMod Status
 - [x] Manage Held AutoMod Messages
 - [ ] Get Banned Events
diff --git a/hype_train.go b/hype_train.go
new file mode 100644
index 0000000..87a7526
--- /dev/null
+++ b/hype_train.go
@@ -0,0 +1,60 @@
+package helix
+
+type HypeTrainContribuition struct {
+	Total int64  `json:"total"`
+	Type  string `json:"type"`
+	User  string `json:"user"`
+}
+
+type HypeTrainEvent struct {
+	ID             string             `json:"id"`
+	EventType      string             `json:"event_type"`
+	EventTimestamp Time               `json:"event_timestamp"`
+	Version        string             `json:"version"`
+	Event          HypeTrainEventData `json:"event_data"`
+}
+
+type HypeTrainEventData struct {
+	ID               string                   `json:"id"`
+	BroadcasterID    string                   `json:"broadcaster_id"`
+	CooldownEndTime  Time                     `json:"cooldown_end_time"`
+	ExpiresAt        Time                     `json:"expires_at"`
+	Goal             int64                    `json:"goal"`
+	LastContribution HypeTrainContribuition   `json:"last_contribution"`
+	Level            int64                    `json:"level"`
+	StartedAt        Time                     `json:"started_at"`
+	TopContributions []HypeTrainContribuition `json:"top_contributions"`
+	Total            int64                    `json:"total"`
+}
+
+type ManyHypeTrainEvents struct {
+	Events     []HypeTrainEvent `json:"data"`
+	Pagination Pagination       `json:"pagination"`
+}
+
+type HypeTrainEventsResponse struct {
+	ResponseCommon
+	Data ManyHypeTrainEvents
+}
+
+type HypeTrainEventsParams struct {
+	BroadcasterID string `query:"broadcaster_id"`
+	After         string `query:"after"`
+	First         int    `query:"first,20"` // Limit 100
+	ID            string `query:"id"`
+}
+
+// Required scope: channel:read:hype_train
+func (c *Client) GetHypeTrainEvents(params *HypeTrainEventsParams) (*HypeTrainEventsResponse, error) {
+	resp, err := c.get("/hypetrain/events", &ManyHypeTrainEvents{}, params)
+	if err != nil {
+		return nil, err
+	}
+
+	events := &HypeTrainEventsResponse{}
+	resp.HydrateResponseCommon(&events.ResponseCommon)
+	events.Data.Events = resp.Data.(*ManyHypeTrainEvents).Events
+	events.Data.Pagination = resp.Data.(*ManyHypeTrainEvents).Pagination
+
+	return events, nil
+}


Test patch:
diff --git a/hype_train_test.go b/hype_train_test.go
new file mode 100644
index 0000000..dfeddbc
--- /dev/null
+++ b/hype_train_test.go
@@ -0,0 +1,88 @@
+package helix
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestGetHypeTrainEvents(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		statusCode  int
+		options     *Options
+		PollsParams *HypeTrainEventsParams
+		respBody    string
+	}{
+		{
+			http.StatusBadRequest,
+			&Options{ClientID: "my-client-id"},
+			&HypeTrainEventsParams{BroadcasterID: ""},
+			`{"error":"Bad Request","status":400,"message":"Missing required parameter \"broadcaster_id\""}`,
+		},
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id"},
+			&HypeTrainEventsParams{BroadcasterID: "121445595"},
+			`{"data":[{"id":"1vsv0LoqZEl1YVRUKehGesQJQFY","event_type":"hypetrain.progression","event_timestamp":"2021-07-27T05:23:06Z","version":"1.0","event_data":{"broadcaster_id":"32043018","cooldown_end_time":"2021-07-27T06:27:11Z","expires_at":"2021-07-27T05:27:11Z","goal":1600,"id":"207ab35e-26f3-4b3a-b4c5-0c9025079906","last_contribution":{"total":100,"type":"BITS","user":"36099963"},"level":1,"started_at":"2021-07-27T05:22:11Z","top_contributions":[{"total":100,"type":"BITS","user":"86228897"},{"total":500,"type":"SUBS","user":"36099963"}],"total":1300}},{"id":"1vsuvsQkf1dFPbOXcxvlS3NiAum","event_type":"hypetrain.progression","event_timestamp":"2021-07-27T05:22:30Z","version":"1.0","event_data":{"broadcaster_id":"32043018","cooldown_end_time":"2021-07-27T06:27:11Z","expires_at":"2021-07-27T05:27:11Z","goal":1600,"id":"207ab35e-26f3-4b3a-b4c5-0c9025079906","last_contribution":{"total":100,"type":"BITS","user":"44526173"},"level":1,"started_at":"2021-07-27T05:22:11Z","top_contributions":[{"total":100,"type":"BITS","user":"86228897"},{"total":500,"type":"SUBS","user":"36099963"}],"total":1200}},{"id":"1vsutOWymap8tfJMTuihbwzBQHC","event_type":"hypetrain.progression","event_timestamp":"2021-07-27T05:22:11Z","version":"1.0","event_data":{"broadcaster_id":"32043018","cooldown_end_time":"2021-07-27T06:27:11Z","expires_at":"2021-07-27T05:27:11Z","goal":1600,"id":"207ab35e-26f3-4b3a-b4c5-0c9025079906","last_contribution":{"total":500,"type":"SUBS","user":"158357643"},"level":1,"started_at":"2021-07-27T05:22:11Z","top_contributions":[{"total":100,"type":"BITS","user":"86228897"},{"total":500,"type":"SUBS","user":"36099963"}],"total":1100}},{"id":"1vsutUYGOAFjhD3U8d0NbG4BbxA","event_type":"hypetrain.progression","event_timestamp":"2021-07-27T05:22:11Z","version":"1.0","event_data":{"broadcaster_id":"32043018","cooldown_end_time":"2021-07-27T06:27:11Z","expires_at":"2021-07-27T05:27:11Z","goal":1600,"id":"207ab35e-26f3-4b3a-b4c5-0c9025079906","last_contribution":{"total":500,"type":"SUBS","user":"158357643"},"level":1,"started_at":"2021-07-27T05:22:11Z","top_contributions":[{"total":500,"type":"SUBS","user":"36099963"}],"total":1100}},{"id":"1vsutTHkvLoRBbRpIPNuu6SGZzd","event_type":"hypetrain.progression","event_timestamp":"2021-07-27T05:22:11Z","version":"1.0","event_data":{"broadcaster_id":"32043018","cooldown_end_time":"2021-07-27T06:27:11Z","expires_at":"2021-07-27T05:27:11Z","goal":1600,"id":"207ab35e-26f3-4b3a-b4c5-0c9025079906","last_contribution":{"total":500,"type":"SUBS","user":"158357643"},"level":1,"started_at":"2021-07-27T05:22:11Z","top_contributions":[{"total":500,"type":"SUBS","user":"36099963"}],"total":1100}}],"pagination":null}`,
+		},
+	}
+
+	for _, testCase := range testCases {
+		c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
+
+		resp, err := c.GetHypeTrainEvents(testCase.PollsParams)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if resp.StatusCode != testCase.statusCode {
+			t.Errorf("expected status code to be %d, got %d", testCase.statusCode, resp.StatusCode)
+		}
+
+		if resp.StatusCode == http.StatusBadRequest {
+			if resp.Error != "Bad Request" {
+				t.Errorf("expected error to be %s, got %s", "Bad Request", resp.Error)
+			}
+
+			if resp.ErrorStatus != http.StatusBadRequest {
+				t.Errorf("expected error status to be %d, got %d", http.StatusBadRequest, resp.ErrorStatus)
+			}
+
+			expectedErrMsg := "Missing required parameter \"broadcaster_id\""
+			if resp.ErrorMessage != expectedErrMsg {
+				t.Errorf("expected error message to be %s, got %s", expectedErrMsg, resp.ErrorMessage)
+			}
+
+			continue
+		}
+
+		if len(resp.Data.Events) != 5 {
+			t.Errorf("expected hype train events len to be 5, got %d", len(resp.Data.Events))
+		}
+
+		if len(resp.Data.Events[0].Event.TopContributions) != 2 {
+			t.Errorf("expected hype train event top contributors len to be 2, got %d", len(resp.Data.Events[0].Event.TopContributions))
+		}
+	}
+
+	// Test with HTTP Failure
+	options := &Options{
+		ClientID: "my-client-id",
+		HTTPClient: &badMockHTTPClient{
+			newMockHandler(0, "", nil),
+		},
+	}
+	c := &Client{
+		opts: options,
+	}
+
+	_, err := c.GetHypeTrainEvents(&HypeTrainEventsParams{})
+	if err == nil {
+		t.Error("expected error but got nil")
+	}
+
+	if err.Error() != "Failed to execute API request: Oops, that's bad :(" {
+		t.Error("expected error does match return error")
+	}
+}