Repository: line/line-bot-sdk-go
Pull request #280 (2021-06-24 03:18:38)

Add rich menu alias function calls
Ref: https://developers.line.biz/en/news/2021/06/21/switch-between-multiple-rich-menus/


Patch:
diff --git a/linebot/client.go b/linebot/client.go
index 16328f7..9941d7f 100644
--- a/linebot/client.go
+++ b/linebot/client.go
@@ -63,6 +63,12 @@ const (
 	APIEndpointBulkLinkRichMenu           = "/v2/bot/richmenu/bulk/link"
 	APIEndpointBulkUnlinkRichMenu         = "/v2/bot/richmenu/bulk/unlink"
 
+	APIEndpointCreateRichMenuAlias = "/v2/bot/richmenu/alias"
+	APIEndpointGetRichMenuAlias    = "/v2/bot/richmenu/alias/%s"
+	APIEndpointUpdateRichMenuAlias = "/v2/bot/richmenu/alias/%s"
+	APIEndpointDeleteRichMenuAlias = "/v2/bot/richmenu/alias/%s"
+	APIEndpointListRichMenuAlias   = "/v2/bot/richmenu/alias/list"
+
 	APIEndpointGetAllLIFFApps = "/liff/v1/apps"
 	APIEndpointAddLIFFApp     = "/liff/v1/apps"
 	APIEndpointUpdateLIFFApp  = "/liff/v1/apps/%s/view"
diff --git a/linebot/response.go b/linebot/response.go
index bdd5a6c..5937d86 100644
--- a/linebot/response.go
+++ b/linebot/response.go
@@ -244,6 +244,12 @@ type RichMenuResponse struct {
 	Areas       []AreaDetail `json:"areas"`
 }
 
+// RichMenuAliasResponse type
+type RichMenuAliasResponse struct {
+	RichMenuAliasID string `json:"richMenuAliasId"`
+	RichMenuID      string `json:"richMenuId"`
+}
+
 // LIFFAppsResponse type
 type LIFFAppsResponse struct {
 	Apps []LIFFApp `json:"apps"`
@@ -477,6 +483,32 @@ func decodeToRichMenuIDResponse(res *http.Response) (*RichMenuIDResponse, error)
 	return &result, nil
 }
 
+func decodeToRichMenuAliasResponse(res *http.Response) (*RichMenuAliasResponse, error) {
+	if err := checkResponse(res); err != nil {
+		return nil, err
+	}
+	decoder := json.NewDecoder(res.Body)
+	result := RichMenuAliasResponse{}
+	if err := decoder.Decode(&result); err != nil {
+		return nil, err
+	}
+	return &result, nil
+}
+
+func decodeToRichMenuAliasListResponse(res *http.Response) ([]*RichMenuAliasResponse, error) {
+	if err := checkResponse(res); err != nil {
+		return nil, err
+	}
+	decoder := json.NewDecoder(res.Body)
+	result := struct {
+		Aliases []*RichMenuAliasResponse `json:"aliases"`
+	}{}
+	if err := decoder.Decode(&result); err != nil {
+		return nil, err
+	}
+	return result.Aliases, nil
+}
+
 func decodeToLIFFResponse(res *http.Response) (*LIFFAppsResponse, error) {
 	if err := checkResponse(res); err != nil {
 		return nil, err
diff --git a/linebot/richmenu.go b/linebot/richmenu.go
index bfd1475..6e786e8 100644
--- a/linebot/richmenu.go
+++ b/linebot/richmenu.go
@@ -631,3 +631,195 @@ func (call *BulkUnlinkRichMenuCall) Do() (*BasicResponse, error) {
 	defer closeResponse(res)
 	return decodeToBasicResponse(res)
 }
+
+// CreateRichMenuAlias method
+func (client *Client) CreateRichMenuAlias(richMenuAliasID, richMenuID string) *CreateRichMenuAliasCall {
+	return &CreateRichMenuAliasCall{
+		c:               client,
+		richMenuAliasID: richMenuAliasID,
+		richMenuID:      richMenuID,
+	}
+}
+
+// CreateRichMenuAliasCall type
+type CreateRichMenuAliasCall struct {
+	c   *Client
+	ctx context.Context
+
+	richMenuAliasID string
+	richMenuID      string
+}
+
+// WithContext method
+func (call *CreateRichMenuAliasCall) WithContext(ctx context.Context) *CreateRichMenuAliasCall {
+	call.ctx = ctx
+	return call
+}
+
+func (call *CreateRichMenuAliasCall) encodeJSON(w io.Writer) error {
+	enc := json.NewEncoder(w)
+	return enc.Encode(&struct {
+		RichMenuAliasID string `json:"richMenuAliasId"`
+		RichMenuID      string `json:"richMenuId"`
+	}{
+		RichMenuAliasID: call.richMenuAliasID,
+		RichMenuID:      call.richMenuID,
+	})
+}
+
+// Do method
+func (call *CreateRichMenuAliasCall) Do() (*BasicResponse, error) {
+	var buf bytes.Buffer
+	if err := call.encodeJSON(&buf); err != nil {
+		return nil, err
+	}
+	res, err := call.c.post(call.ctx, APIEndpointCreateRichMenuAlias, &buf)
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(res)
+	return decodeToBasicResponse(res)
+}
+
+// UpdateRichMenuAlias method
+func (client *Client) UpdateRichMenuAlias(richMenuAliasID, richMenuID string) *UpdateRichMenuAliasCall {
+	return &UpdateRichMenuAliasCall{
+		c:               client,
+		richMenuAliasID: richMenuAliasID,
+		richMenuID:      richMenuID,
+	}
+}
+
+// UpdateRichMenuAliasCall type
+type UpdateRichMenuAliasCall struct {
+	c   *Client
+	ctx context.Context
+
+	richMenuAliasID string
+	richMenuID      string
+}
+
+// WithContext method
+func (call *UpdateRichMenuAliasCall) WithContext(ctx context.Context) *UpdateRichMenuAliasCall {
+	call.ctx = ctx
+	return call
+}
+
+func (call *UpdateRichMenuAliasCall) encodeJSON(w io.Writer) error {
+	enc := json.NewEncoder(w)
+	return enc.Encode(&struct {
+		RichMenuID string `json:"richMenuId"`
+	}{
+		RichMenuID: call.richMenuID,
+	})
+}
+
+// Do method
+func (call *UpdateRichMenuAliasCall) Do() (*BasicResponse, error) {
+	var buf bytes.Buffer
+	if err := call.encodeJSON(&buf); err != nil {
+		return nil, err
+	}
+	endpoint := fmt.Sprintf(APIEndpointUpdateRichMenuAlias, call.richMenuAliasID)
+	res, err := call.c.post(call.ctx, endpoint, &buf)
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(res)
+	return decodeToBasicResponse(res)
+}
+
+// DeleteRichMenuAlias method
+func (client *Client) DeleteRichMenuAlias(richMenuAliasID string) *DeleteRichMenuAliasCall {
+	return &DeleteRichMenuAliasCall{
+		c:               client,
+		richMenuAliasID: richMenuAliasID,
+	}
+}
+
+// DeleteRichMenuAliasCall type
+type DeleteRichMenuAliasCall struct {
+	c   *Client
+	ctx context.Context
+
+	richMenuAliasID string
+}
+
+// WithContext method
+func (call *DeleteRichMenuAliasCall) WithContext(ctx context.Context) *DeleteRichMenuAliasCall {
+	call.ctx = ctx
+	return call
+}
+
+// Do method
+func (call *DeleteRichMenuAliasCall) Do() (*BasicResponse, error) {
+	endpoint := fmt.Sprintf(APIEndpointDeleteRichMenuAlias, call.richMenuAliasID)
+	res, err := call.c.delete(call.ctx, endpoint)
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(res)
+	return decodeToBasicResponse(res)
+}
+
+// GetRichMenuAlias method
+func (client *Client) GetRichMenuAlias(richMenuAliasID string) *GetRichMenuAliasCall {
+	return &GetRichMenuAliasCall{
+		c:               client,
+		richMenuAliasID: richMenuAliasID,
+	}
+}
+
+// GetRichMenuAliasCall type
+type GetRichMenuAliasCall struct {
+	c   *Client
+	ctx context.Context
+
+	richMenuAliasID string
+}
+
+// WithContext method
+func (call *GetRichMenuAliasCall) WithContext(ctx context.Context) *GetRichMenuAliasCall {
+	call.ctx = ctx
+	return call
+}
+
+// Do method
+func (call *GetRichMenuAliasCall) Do() (*RichMenuAliasResponse, error) {
+	endpoint := fmt.Sprintf(APIEndpointGetRichMenuAlias, call.richMenuAliasID)
+	res, err := call.c.get(call.ctx, call.c.endpointBase, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(res)
+	return decodeToRichMenuAliasResponse(res)
+}
+
+// GetRichMenuAliasList method
+func (client *Client) GetRichMenuAliasList() *GetRichMenuAliasListCall {
+	return &GetRichMenuAliasListCall{
+		c: client,
+	}
+}
+
+// GetRichMenuAliasListCall type
+type GetRichMenuAliasListCall struct {
+	c   *Client
+	ctx context.Context
+}
+
+// WithContext method
+func (call *GetRichMenuAliasListCall) WithContext(ctx context.Context) *GetRichMenuAliasListCall {
+	call.ctx = ctx
+	return call
+}
+
+// Do method
+func (call *GetRichMenuAliasListCall) Do() ([]*RichMenuAliasResponse, error) {
+	res, err := call.c.get(call.ctx, call.c.endpointBase, APIEndpointListRichMenuAlias, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(res)
+	return decodeToRichMenuAliasListResponse(res)
+}


Test patch:
diff --git a/linebot/richmenu_test.go b/linebot/richmenu_test.go
index 62052d5..d6aa510 100644
--- a/linebot/richmenu_test.go
+++ b/linebot/richmenu_test.go
@@ -857,3 +857,247 @@ func TestDownloadRichMenuImage(t *testing.T) {
 		})
 	}
 }
+
+// Test method for CreateRichMenuAlias
+func (call *CreateRichMenuAliasCall) Test() {
+}
+
+// Test method for GetRichMenuAlias
+func (call *GetRichMenuAliasCall) Test() {
+}
+
+// Test method for UpdateRichMenuAlias
+func (call *UpdateRichMenuAliasCall) Test() {
+}
+
+// Test method for DeleteRichMenuAlias
+func (call *DeleteRichMenuAliasCall) Test() {
+}
+
+// TestCRUDRichMenuAlias tests CreateRichMenuAlias, GetRichMenuAlias, UpdateRichMenuAlias, DeleteRichMenuAlias
+func TestCRUDRichMenuAlias(t *testing.T) {
+	type testMethod interface {
+		Test()
+	}
+	type want struct {
+		URLPath    string
+		HTTPMethod string
+		Response   interface{}
+		Error      error
+	}
+	testCases := []struct {
+		TestMethod      testMethod
+		RichMenuID      string
+		RichMenuAliasID string
+		ResponseCode    int
+		Response        []byte
+		Want            want
+	}{
+		{
+			RichMenuID:      "654321",
+			RichMenuAliasID: "richmenu-alias-create",
+			TestMethod:      new(CreateRichMenuAliasCall),
+			ResponseCode:    200,
+			Response:        []byte(`{}`),
+			Want: want{
+				HTTPMethod: http.MethodPost,
+				URLPath:    APIEndpointCreateRichMenuAlias,
+				Response:   &BasicResponse{},
+			},
+		},
+		{
+			RichMenuID:      "N/A",
+			RichMenuAliasID: "richmenu-alias-get",
+			TestMethod:      new(GetRichMenuAliasCall),
+			ResponseCode:    200,
+			Response:        []byte(`{"richMenuAliasId":"richmenu-alias-get","richMenuId":"richmenu-88c05ef6921ae53f8b58a25f3a65faf7"}`),
+			Want: want{
+				HTTPMethod: http.MethodGet,
+				URLPath:    fmt.Sprintf(APIEndpointGetRichMenuAlias, "richmenu-alias-get"),
+				Response:   &RichMenuAliasResponse{RichMenuAliasID: "richmenu-alias-get", RichMenuID: "richmenu-88c05ef6921ae53f8b58a25f3a65faf7"},
+			},
+		},
+		{
+			RichMenuID:      "123456",
+			RichMenuAliasID: "richmenu-alias-update",
+			TestMethod:      new(UpdateRichMenuAliasCall),
+			ResponseCode:    200,
+			Response:        []byte(`{}`),
+			Want: want{
+				HTTPMethod: http.MethodPost,
+				URLPath:    fmt.Sprintf(APIEndpointGetRichMenuAlias, "richmenu-alias-update"),
+				Response:   &BasicResponse{},
+			},
+		},
+		{
+			RichMenuID:      "N/A",
+			RichMenuAliasID: "richmenu-alias-delete",
+			TestMethod:      new(DeleteRichMenuAliasCall),
+			ResponseCode:    200,
+			Response:        []byte(`{}`),
+			Want: want{
+				HTTPMethod: http.MethodDelete,
+				URLPath:    fmt.Sprintf(APIEndpointDeleteRichMenuAlias, "richmenu-alias-delete"),
+				Response:   &BasicResponse{},
+			},
+		},
+	}
+
+	var currentTestIdx int
+	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		tc := testCases[currentTestIdx]
+		if r.Method != tc.Want.HTTPMethod {
+			t.Errorf("Method %s; want %s", r.Method, tc.Want.HTTPMethod)
+		}
+		if r.URL.Path != tc.Want.URLPath {
+			t.Errorf("URLPath %s; want %s", r.URL.Path, tc.Want.URLPath)
+		}
+		_, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+		w.WriteHeader(tc.ResponseCode)
+		w.Write(tc.Response)
+	}))
+	defer server.Close()
+
+	dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		t.Error("Unexpected data API call")
+		w.WriteHeader(404)
+		w.Write([]byte(`{"message":"Not found"}`))
+	}))
+	defer dataServer.Close()
+
+	client, err := mockClient(server, dataServer)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var res interface{}
+	for i, tc := range testCases {
+		currentTestIdx = i
+		t.Run(strconv.Itoa(i)+"/"+tc.RichMenuAliasID+"."+string(tc.Response), func(t *testing.T) {
+			switch tc.TestMethod.(type) {
+			case *CreateRichMenuAliasCall:
+				res, err = client.CreateRichMenuAlias(tc.RichMenuAliasID, tc.RichMenuID).Do()
+			case *GetRichMenuAliasCall:
+				res, err = client.GetRichMenuAlias(tc.RichMenuAliasID).Do()
+			case *UpdateRichMenuAliasCall:
+				res, err = client.UpdateRichMenuAlias(tc.RichMenuAliasID, tc.RichMenuID).Do()
+			case *DeleteRichMenuAliasCall:
+				res, err = client.DeleteRichMenuAlias(tc.RichMenuAliasID).Do()
+			}
+			if tc.Want.Error != nil {
+				if !reflect.DeepEqual(err, tc.Want.Error) {
+					t.Errorf("Error %v; want %v", err, tc.Want.Error)
+				}
+			} else {
+				if err != nil {
+					t.Error(err)
+				}
+			}
+			if tc.Want.Response != nil {
+				if !reflect.DeepEqual(res, tc.Want.Response) {
+					t.Errorf("Response %v; want %v", res, tc.Want.Response)
+				}
+			}
+		})
+	}
+}
+
+// TestListRichMenuAlias
+func TestListRichMenuAlias(t *testing.T) {
+	type want struct {
+		URLPath  string
+		Response []*RichMenuAliasResponse
+		Error    error
+	}
+	testCases := []struct {
+		ResponseCode int
+		Response     []byte
+		Want         want
+	}{
+		{
+			ResponseCode: 200,
+			Response: []byte(`{"aliases":[` +
+				`{"richMenuAliasId":"richmenu-alias-a","richMenuId": "richmenu-862e6ad6c267d2ddf3f42bc78554f6a4"},` +
+				`{"richMenuAliasId": "richmenu-alias-b","richMenuId": "richmenu-88c05ef6921ae53f8b58a25f3a65faf7"}` +
+				`]}`),
+			Want: want{
+				URLPath: APIEndpointListRichMenuAlias,
+				Response: []*RichMenuAliasResponse{
+					{
+						RichMenuAliasID: "richmenu-alias-a",
+						RichMenuID:      "richmenu-862e6ad6c267d2ddf3f42bc78554f6a4",
+					},
+					{
+						RichMenuAliasID: "richmenu-alias-b",
+						RichMenuID:      "richmenu-88c05ef6921ae53f8b58a25f3a65faf7",
+					},
+				},
+			},
+		},
+		{
+			ResponseCode: 200,
+			Response: []byte(`{"aliases":[` +
+				`]}`),
+			Want: want{
+				URLPath:  APIEndpointListRichMenuAlias,
+				Response: []*RichMenuAliasResponse{},
+			},
+		},
+	}
+
+	var currentTestIdx int
+	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		tc := testCases[currentTestIdx]
+		if r.Method != http.MethodGet {
+			t.Errorf("Method %s; want %s", r.Method, http.MethodGet)
+		}
+		if r.URL.Path != tc.Want.URLPath {
+			t.Errorf("URLPath %s; want %s", r.URL.Path, tc.Want.URLPath)
+		}
+		_, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+		w.WriteHeader(tc.ResponseCode)
+		w.Write(tc.Response)
+	}))
+	defer server.Close()
+
+	dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		t.Error("Unexpected data API call")
+		w.WriteHeader(404)
+		w.Write([]byte(`{"message":"Not found"}`))
+	}))
+	defer dataServer.Close()
+
+	client, err := mockClient(server, dataServer)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i, tc := range testCases {
+		currentTestIdx = i
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			res, err := client.GetRichMenuAliasList().Do()
+			if tc.Want.Error != nil {
+				if !reflect.DeepEqual(err, tc.Want.Error) {
+					t.Errorf("Error %v; want %v", err, tc.Want.Error)
+				}
+			} else {
+				if err != nil {
+					t.Error(err)
+				}
+			}
+			if tc.Want.Response != nil {
+				if !reflect.DeepEqual(res, tc.Want.Response) {
+					t.Errorf("Response\n %v; want\n %v", res, tc.Want.Response)
+				}
+			}
+		})
+	}
+}