Repository: nicklaw5/helix
Pull request #186 (2023-03-01 01:22:30)

feat: Support for update custom reward redemption status
Thankful for the project, but it's missing this API that I need. I don't really know how to test it locally e2e so if you could explain that I can try it out. Otherwise, here you go. I just tried my best to follow the spec https://dev.twitch.tv/docs/api/reference/#update-redemption-status and the existing code/tests. Ran `go test ./... -cover` and it all passed. 


Patch:
diff --git a/SUPPORTED_ENDPOINTS.md b/SUPPORTED_ENDPOINTS.md
index 11657a6..bd78b92 100644
--- a/SUPPORTED_ENDPOINTS.md
+++ b/SUPPORTED_ENDPOINTS.md
@@ -22,7 +22,7 @@
 - [x] Get Custom Reward
 - [ ] Get Custom Reward Redemption
 - [x] Update Custom Reward
-- [ ] Update Redemption Status
+- [x] Update Redemption Status
 - [x] Get Channel Information
 - [x] Modify Channel Information
 - [x] Get Channel Editors
@@ -38,8 +38,8 @@
 - [x] Update Chat Settings
 - [x] Send Chat Announcement
 - [ ] Send a Shoutout
-- [x] Get User Chat Color
-- [x] Update User Chat Color
+- [ ] Get User Chat Color
+- [ ] Update User Chat Color
 - [x] Create Clip
 - [x] Get Clips
 - [x] Get Code Status
diff --git a/channels_points.go b/channels_points.go
index d866505..946fcac 100644
--- a/channels_points.go
+++ b/channels_points.go
@@ -104,6 +104,36 @@ type DeleteCustomRewardsResponse struct {
 	ResponseCommon
 }
 
+type UpdateChannelCustomRewardsRedemptionStatusParams struct {
+	ID            string `query:"id"`
+	BroadcasterID string `query:"broadcaster_id"`
+	RewardID      string `query:"reward_id"`
+	Status        string `json:"status"`
+}
+
+type ChannelCustomRewardsRedemptionResponse struct {
+	ResponseCommon
+	Data ManyChannelCustomRewardsRedemptions
+}
+
+type ManyChannelCustomRewardsRedemptions struct {
+	Redemptions []ChannelCustomRewardsRedemption `json:"data"`
+}
+
+type ChannelCustomRewardsRedemption struct {
+	ID               string              `json:"id"`
+	BroadcasterID    string              `json:"broadcaster_id"`
+	BroadcasterLogin string              `json:"broadcaster_login"`
+	BroadcasterName  string              `json:"broadcaster_name"`
+	UserID           string              `json:"user_id"`
+	UserName         string              `json:"user_name"`
+	UserLogin        string              `json:"user_login"`
+	UserInput        string              `json:"user_input"`
+	Status           string              `json:"status"`
+	RedeemedAt       Time                `json:"redeemed_at"`
+	Reward           ChannelCustomReward `json:"reward"`
+}
+
 // CreateCustomReward : Creates a Custom Reward on a channel.
 // Required scope: channel:manage:redemptions
 func (c *Client) CreateCustomReward(params *ChannelCustomRewardsParams) (*ChannelCustomRewardResponse, error) {
@@ -162,3 +192,18 @@ func (c *Client) GetCustomRewards(params *GetCustomRewardsParams) (*ChannelCusto
 
 	return rewards, nil
 }
+
+// UpdateChannelCustomRewardsRedemptionStatus : Update a Custom Reward Redemption status on a channel.
+// Required scope: channel:manage:redemptions
+func (c *Client) UpdateChannelCustomRewardsRedemptionStatus(params *UpdateChannelCustomRewardsRedemptionStatusParams) (*ChannelCustomRewardsRedemptionResponse, error) {
+	resp, err := c.patchAsJSON("/channel_points/custom_rewards/redemptions", &ManyChannelCustomRewardsRedemptions{}, params)
+	if err != nil {
+		return nil, err
+	}
+
+	redemptions := &ChannelCustomRewardsRedemptionResponse{}
+	resp.HydrateResponseCommon(&redemptions.ResponseCommon)
+	redemptions.Data.Redemptions = resp.Data.(*ManyChannelCustomRewardsRedemptions).Redemptions
+
+	return redemptions, nil
+}
diff --git a/chat.go b/chat.go
index 4e6ec5d..6d9a68f 100644
--- a/chat.go
+++ b/chat.go
@@ -348,83 +348,3 @@ func (c *Client) UpdateChatSettings(params *UpdateChatSettingsParams) (*UpdateCh
 
 	return settings, nil
 }
-
-// UserChatColorResponse is the response from GetUserChatColor
-type UserChatColorResponse struct {
-	ResponseCommon
-	Data GetUserChatColorResponse
-}
-
-// GetUserChatColorParams are the parameters for GetUserChatColor
-type GetUserChatColorParams struct {
-	UserID string `json:"user_id"`
-}
-
-// GetUserChatColorResponse is the response data in UserChatColorResponse
-type GetUserChatColorResponse struct {
-	Data []GetUserChatColorUser `json:"data"`
-}
-
-// GetUserChatColorUser describes the user and their color
-type GetUserChatColorUser struct {
-	UserID    string `json:"user_id"`
-	UserLogin string `json:"user_login"`
-	UserName  string `json:"user_name"`
-	Color     string `json:"color"`
-}
-
-// GetUserChatColor fetches the color used for the user’s name in chat.
-func (c *Client) GetUserChatColor(params *GetUserChatColorParams) (*UserChatColorResponse, error) {
-	resp, err := c.get("/chat/color", &GetUserChatColorResponse{}, params)
-	if err != nil {
-		return nil, err
-	}
-
-	userColor := &UserChatColorResponse{}
-	resp.HydrateResponseCommon(&userColor.ResponseCommon)
-
-	return userColor, nil
-}
-
-// UpdateUserChatColorResponse is the response for UpdateUserChatColor
-type UpdateUserChatColorResponse struct {
-	ResponseCommon
-}
-
-// UpdateUserChatColorParams are the parameters for UpdateUserChatColor
-type UpdateUserChatColorParams struct {
-	UserID string `json:"user_id"`
-	Color  string `json:"color"`
-}
-
-// UpdateUserChatcolor updates the color used for the user’s name in chat.
-//
-// Required scope: user:manage:chat_color
-//
-// Prime and Turbo users can specify a Hex color code, everyone can use the default colors:
-//   - blue
-//   - blue_violet
-//   - cadet_blue
-//   - chocolate
-//   - coral
-//   - dodger_blue
-//   - firebrick
-//   - golden_rod
-//   - green
-//   - hot_pink
-//   - orange_red
-//   - red
-//   - sea_green
-//   - spring_green
-//   - yellow_green
-func (c *Client) UpdateUserChatColor(params *UpdateUserChatColorParams) (*UpdateUserChatColorResponse, error) {
-	resp, err := c.put("/chat/color", nil, params)
-	if err != nil {
-		return nil, err
-	}
-
-	update := &UpdateUserChatColorResponse{}
-	resp.HydrateResponseCommon(&update.ResponseCommon)
-
-	return update, nil
-}
diff --git a/docs/channels_points_docs.md b/docs/channels_points_docs.md
index 2caba3b..4575e88 100644
--- a/docs/channels_points_docs.md
+++ b/docs/channels_points_docs.md
@@ -93,3 +93,50 @@ if err != nil {
 
 fmt.Printf("%+v\n", resp)
 ```
+
+## Get Custom Reward Redemption Status
+
+This is an example of how to get the status of a custom reward redemption.
+
+```go
+client, err := helix.NewClient(&helix.Options{
+    ClientID: "your-client-id",
+})
+if err != nil {
+    // handle error
+}
+
+resp, err := client.GetCustomRewards(&helix.GetCustomRewardsParams{
+    BroadcasterID : "145328278",
+})
+if err != nil {
+    // handle error
+}
+
+fmt.Printf("%+v\n", resp)
+```
+
+## Update Custom Reward Redemption Status
+
+This is an example of how to update the status of a custom reward redemption.
+
+```go
+client, err := helix.NewClient(&helix.Options{
+    ClientID: "your-client-id",
+})
+if err != nil {
+    // handle error
+}
+
+resp, err := client.UpdateChannelCustomRewardsRedemptionStatus(&helix.UpdateChannelCustomRewardsRedemptionStatusParams{
+    ID            : "17fa2df1-ad76-4804-bfa5-a40ef63efe63",
+    BroadcasterID : "274637212",
+    RewardID      : "92af127c-7326-4483-a52b-b0da0be61c01",
+    Status        : "FULFILLED", // The only acceptable values are "CANCELED" and "FULFILLED"
+})
+if err != nil {
+    // handle error
+}
+
+fmt.Printf("%+v\n", resp)
+```
diff --git a/docs/chat_docs.md b/docs/chat_docs.md
index 6b70cf5..4ebfefb 100644
--- a/docs/chat_docs.md
+++ b/docs/chat_docs.md
@@ -128,45 +128,3 @@ if err != nil {
 
 fmt.Printf("%+v\n", resp)
 ```
-
-## Get User Chat Color
-Gets the color used for the user’s name in chat.
-
-```go
-client, err := helix.NewClient(&helix.Options{
-    ClientID: "your-client-id",
-})
-if err != nil {
-    // handle error
-}
-
-resp, err := client.GetUserChatColor(&GetUserChatColorParams{
-    UserID: "22484632",
-})
-if err != nil {
-    // handle error
-}
-
-fmt.Printf("%+v\n", resp)
-```
-
-## Update User Chat Color
-Updates the color used for the user’s name in chat.
-```go
-client, err := helix.NewClient(&helix.Options{
-    ClientID: "your-client-id",
-})
-if err != nil {
-    // handle error
-}
-
-resp, err := client.UpdateUserChatColor(&UpdateUserChatColorParams{
-    UserID: "22484632",
-    Color: "blue"
-})
-if err != nil {
-    // handle error
-}
-
-fmt.Printf("%+v\n", resp)
-```
\ No newline at end of file


Test patch:
diff --git a/channels_points_test.go b/channels_points_test.go
index 0844cac..21fa153 100644
--- a/channels_points_test.go
+++ b/channels_points_test.go
@@ -287,3 +287,67 @@ func TestGetCustomRewards(t *testing.T) {
 		t.Error("expected error does match return error")
 	}
 }
+
+func TestUpdateCustomRewardRedemptionStatus(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		statusCode int
+		options    *Options
+		params     *UpdateChannelCustomRewardsRedemptionStatusParams
+		respBody   string
+	}{
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id"},
+			&UpdateChannelCustomRewardsRedemptionStatusParams{
+				ID:            "17fa2df1-ad76-4804-bfa5-a40ef63efe63",
+				BroadcasterID: "274637212",
+				RewardID:      "92af127c-7326-4483-a52b-b0da0be61c01",
+				Status:        "CANCELLED",
+			},
+			`{"data": [{"broadcaster_name": "torpedo09", "broadcaster_login": "torpedo09", "broadcaster_id": "274637212", "id": "17fa2df1-ad76-4804-bfa5-a40ef63efe63", "user_id": "274637212", "user_name": "torpedo09", "user_login": "torpedo09", "user_input": "", "status": "CANCELED", "redeemed_at": "2020-07-01T18:37:32Z", "reward": { "id": "92af127c-7326-4483-a52b-b0da0be61c01", "title": "game analysis", "prompt": "", "cost": 50000}}]}`,
+		},
+		{
+			http.StatusBadRequest,
+			&Options{ClientID: "my-client-id"},
+			&UpdateChannelCustomRewardsRedemptionStatusParams{},
+			`{"error":"Bad Request","status":400,"message":"Missing required parameter \"id\""}`,
+		},
+	}
+
+	for _, testCase := range testCases {
+		c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
+
+		resp, err := c.UpdateChannelCustomRewardsRedemptionStatus(testCase.params)
+		if err != nil {
+			t.Error(err)
+		}
+
+		// Test Bad Request Responses
+		if resp.StatusCode == http.StatusBadRequest {
+			firstErrStr := "Missing required parameter \"id\""
+			if resp.ErrorMessage != firstErrStr {
+				t.Errorf("expected error message to be \"%s\", got \"%s\"", firstErrStr, resp.ErrorMessage)
+			}
+			continue
+		}
+
+		if resp.StatusCode != testCase.statusCode {
+			t.Errorf("expected status code to be \"%d\", got \"%d\"", testCase.statusCode, resp.StatusCode)
+		}
+
+		if testCase.statusCode == http.StatusOK {
+			numRedemptions := len(resp.Data.Redemptions)
+			if numRedemptions != 1 {
+				t.Errorf("expected 1 redemption, got %d", numRedemptions)
+				continue
+			}
+
+			title := resp.Data.Redemptions[0].Reward.Title
+			if title != "game analysis" {
+				t.Errorf("expected reward title to be \"game analysis\", got \"%s\"", title)
+			}
+		}
+	}
+}
diff --git a/chat_test.go b/chat_test.go
index e8f108d..9ddcb39 100644
--- a/chat_test.go
+++ b/chat_test.go
@@ -826,119 +826,3 @@ func TestUpdateChatSettings(t *testing.T) {
 		t.Errorf("expected error does match return error, got '%s'", err.Error())
 	}
 }
-
-func TestGetUserChatColor(t *testing.T) {
-	t.Parallel()
-
-	testCases := []struct {
-		statusCode int
-		options    *Options
-		UserID     string
-		respBody   string
-	}{
-		{
-			http.StatusOK,
-			&Options{ClientID: "my-client-id"},
-			"22484632",
-			`{"data": [{"user_id": "11111","user_name": "SpeedySpeedster1","user_login": "speedyspeedster1","color": "#9146FF"},{"user_id": "44444","user_name": "SpeedySpeedster2","user_login": "speedyspeedster2","color": ""}]}`,
-		},
-		{
-			http.StatusBadRequest,
-			&Options{ClientID: "my-client-id"},
-			"22484632",
-			`{"error":"Bad Request","status":400,"message":"The ID in the user_id query parameter is not valid."}`,
-		},
-	}
-
-	for _, testCase := range testCases {
-		c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
-
-		resp, err := c.GetUserChatColor(&GetUserChatColorParams{
-			UserID: testCase.UserID,
-		})
-		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 := "The ID in the user_id query parameter is not valid."
-			if resp.ErrorMessage != expectedErrMsg {
-				t.Errorf("expected error message to be %s, got %s", expectedErrMsg, resp.ErrorMessage)
-			}
-
-			continue
-		}
-	}
-}
-
-func TestUpdateUserChatColor(t *testing.T) {
-	t.Parallel()
-
-	testCases := []struct {
-		statusCode int
-		options    *Options
-		UserID     string
-		Color      string
-		respBody   string
-	}{
-		{
-			http.StatusOK,
-			&Options{ClientID: "my-client-id"},
-			"22484632",
-			"blue",
-			``,
-		},
-		{
-			http.StatusBadRequest,
-			&Options{ClientID: "my-client-id"},
-			"22484632",
-			"bad_color",
-			`{"error":"Bad Request","status":400,"message":"The named color in the color query parameter is not valid."}`,
-		},
-	}
-
-	for _, testCase := range testCases {
-		c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
-
-		resp, err := c.UpdateUserChatColor(&UpdateUserChatColorParams{
-			UserID: testCase.UserID,
-			Color:  testCase.Color,
-		})
-		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 := "The named color in the color query parameter is not valid."
-			if resp.ErrorMessage != expectedErrMsg {
-				t.Errorf("expected error message to be %s, got %s", expectedErrMsg, resp.ErrorMessage)
-			}
-
-			continue
-		}
-	}
-}