Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1044 from winston-stripe/add-bookmarks
Add `bookmarks.<add|edit|remove|list>` support
- Loading branch information
Showing
2 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package slack | ||
|
||
import ( | ||
"context" | ||
"net/url" | ||
) | ||
|
||
type Bookmark struct { | ||
ID string `json:"id"` | ||
ChannelID string `json:"channel_id"` | ||
Title string `json:"title"` | ||
Link string `json:"link"` | ||
Emoji string `json:"emoji"` | ||
IconURL string `json:"icon_url"` | ||
Type string `json:"type"` | ||
Created JSONTime `json:"date_created"` | ||
Updated JSONTime `json:"date_updated"` | ||
Rank string `json:"rank"` | ||
|
||
LastUpdatedByUserID string `json:"last_updated_by_user_id"` | ||
LastUpdatedByTeamID string `json:"last_updated_by_team_id"` | ||
|
||
ShortcutID string `json:"shortcut_id"` | ||
EntityID string `json:"entity_id"` | ||
AppID string `json:"app_id"` | ||
} | ||
|
||
type AddBookmarkParameters struct { | ||
Title string // A required title for the bookmark | ||
Type string // A required type for the bookmark | ||
Link string // URL required for type:link | ||
Emoji string // An optional emoji | ||
EntityID string | ||
ParentID string | ||
} | ||
|
||
type EditBookmarkParameters struct { | ||
Title *string // Change the title. Set to "" to clear | ||
Emoji *string // Change the emoji. Set to "" to clear | ||
Link string // Change the link | ||
} | ||
|
||
type addBookmarkResponse struct { | ||
Bookmark Bookmark `json:"bookmark"` | ||
SlackResponse | ||
} | ||
|
||
type editBookmarkResponse struct { | ||
Bookmark Bookmark `json:"bookmark"` | ||
SlackResponse | ||
} | ||
|
||
type listBookmarksResponse struct { | ||
Bookmarks []Bookmark `json:"bookmarks"` | ||
SlackResponse | ||
} | ||
|
||
// AddBookmark adds a bookmark in a channel | ||
func (api *Client) AddBookmark(channelID string, params AddBookmarkParameters) (Bookmark, error) { | ||
return api.AddBookmarkContext(context.Background(), channelID, params) | ||
} | ||
|
||
// AddBookmarkContext adds a bookmark in a channel with a custom context | ||
func (api *Client) AddBookmarkContext(ctx context.Context, channelID string, params AddBookmarkParameters) (Bookmark, error) { | ||
values := url.Values{ | ||
"channel_id": {channelID}, | ||
"token": {api.token}, | ||
"title": {params.Title}, | ||
"type": {params.Type}, | ||
} | ||
if params.Link != "" { | ||
values.Set("link", params.Link) | ||
} | ||
if params.Emoji != "" { | ||
values.Set("emoji", params.Emoji) | ||
} | ||
if params.EntityID != "" { | ||
values.Set("entity_id", params.EntityID) | ||
} | ||
if params.ParentID != "" { | ||
values.Set("parent_id", params.ParentID) | ||
} | ||
|
||
response := &addBookmarkResponse{} | ||
if err := api.postMethod(ctx, "bookmarks.add", values, response); err != nil { | ||
return Bookmark{}, err | ||
} | ||
|
||
return response.Bookmark, response.Err() | ||
} | ||
|
||
// RemoveBookmark removes a bookmark from a channel | ||
func (api *Client) RemoveBookmark(channelID, bookmarkID string) error { | ||
return api.RemoveBookmarkContext(context.Background(), channelID, bookmarkID) | ||
} | ||
|
||
// RemoveBookmarkContext removes a bookmark from a channel with a custom context | ||
func (api *Client) RemoveBookmarkContext(ctx context.Context, channelID, bookmarkID string) error { | ||
values := url.Values{ | ||
"channel_id": {channelID}, | ||
"token": {api.token}, | ||
"bookmark_id": {bookmarkID}, | ||
} | ||
|
||
response := &SlackResponse{} | ||
if err := api.postMethod(ctx, "bookmarks.remove", values, response); err != nil { | ||
return err | ||
} | ||
|
||
return response.Err() | ||
} | ||
|
||
// ListBookmarks returns all bookmarks for a channel. | ||
func (api *Client) ListBookmarks(channelID string) ([]Bookmark, error) { | ||
return api.ListBookmarksContext(context.Background(), channelID) | ||
} | ||
|
||
// ListBookmarksContext returns all bookmarks for a channel with a custom context. | ||
func (api *Client) ListBookmarksContext(ctx context.Context, channelID string) ([]Bookmark, error) { | ||
values := url.Values{ | ||
"channel_id": {channelID}, | ||
"token": {api.token}, | ||
} | ||
|
||
response := &listBookmarksResponse{} | ||
err := api.postMethod(ctx, "bookmarks.list", values, response) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return response.Bookmarks, response.Err() | ||
} | ||
|
||
func (api *Client) EditBookmark(channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) { | ||
return api.EditBookmarkContext(context.Background(), channelID, bookmarkID, params) | ||
} | ||
|
||
func (api *Client) EditBookmarkContext(ctx context.Context, channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) { | ||
values := url.Values{ | ||
"channel_id": {channelID}, | ||
"token": {api.token}, | ||
"bookmark_id": {bookmarkID}, | ||
} | ||
if params.Link != "" { | ||
values.Set("link", params.Link) | ||
} | ||
if params.Emoji != nil { | ||
values.Set("emoji", *params.Emoji) | ||
} | ||
if params.Title != nil { | ||
values.Set("title", *params.Title) | ||
} | ||
|
||
response := &editBookmarkResponse{} | ||
if err := api.postMethod(ctx, "bookmarks.edit", values, response); err != nil { | ||
return Bookmark{}, err | ||
} | ||
|
||
return response.Bookmark, response.Err() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package slack | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func getTestBookmark(channelID, bookmarkID string) Bookmark { | ||
return Bookmark{ | ||
ID: bookmarkID, | ||
ChannelID: channelID, | ||
Title: "bookmark", | ||
Type: "link", | ||
Link: "https://example.com", | ||
IconURL: "https://example.com/icon.png", | ||
} | ||
} | ||
|
||
func addBookmarkLinkHandler(rw http.ResponseWriter, r *http.Request) { | ||
channelID := r.FormValue("channel_id") | ||
title := r.FormValue("title") | ||
bookmarkType := r.FormValue("type") | ||
link := r.FormValue("link") | ||
|
||
rw.Header().Set("Content-Type", "application/json") | ||
|
||
if bookmarkType == "link" && link != "" && channelID != "" && title != "" { | ||
bookmark := getTestBookmark(channelID, "Bk123RBZG8GZ") | ||
bookmark.Title = title | ||
bookmark.Type = bookmarkType | ||
bookmark.Link = link | ||
|
||
resp, _ := json.Marshal(&addBookmarkResponse{ | ||
SlackResponse: SlackResponse{Ok: true}, | ||
Bookmark: bookmark}) | ||
rw.Write(resp) | ||
} else { | ||
rw.Write([]byte(`{ "ok": false, "error": "errored" }`)) | ||
} | ||
} | ||
|
||
func TestAddBookmarkLink(t *testing.T) { | ||
http.HandleFunc("/bookmarks.add", addBookmarkLinkHandler) | ||
once.Do(startServer) | ||
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) | ||
params := AddBookmarkParameters{ | ||
Title: "test", | ||
Type: "link", | ||
Link: "https://example.com", | ||
} | ||
_, err := api.AddBookmark("CXXXXXXXX", params) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %s", err) | ||
} | ||
} | ||
|
||
func listBookmarksHandler(rw http.ResponseWriter, r *http.Request) { | ||
channelID := r.FormValue("channel_id") | ||
|
||
rw.Header().Set("Content-Type", "application/json") | ||
|
||
if channelID != "" { | ||
bookmarks := []Bookmark{ | ||
getTestBookmark(channelID, "Bk001"), | ||
getTestBookmark(channelID, "Bk002"), | ||
getTestBookmark(channelID, "Bk003"), | ||
getTestBookmark(channelID, "Bk004"), | ||
} | ||
|
||
resp, _ := json.Marshal(&listBookmarksResponse{ | ||
SlackResponse: SlackResponse{Ok: true}, | ||
Bookmarks: bookmarks}) | ||
rw.Write(resp) | ||
} else { | ||
rw.Write([]byte(`{ "ok": false, "error": "errored" }`)) | ||
} | ||
} | ||
|
||
func TestListBookmarks(t *testing.T) { | ||
http.HandleFunc("/bookmarks.list", listBookmarksHandler) | ||
once.Do(startServer) | ||
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) | ||
channel := "CXXXXXXXX" | ||
bookmarks, err := api.ListBookmarks(channel) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %s", err) | ||
} | ||
|
||
if !reflect.DeepEqual([]Bookmark{ | ||
getTestBookmark(channel, "Bk001"), | ||
getTestBookmark(channel, "Bk002"), | ||
getTestBookmark(channel, "Bk003"), | ||
getTestBookmark(channel, "Bk004"), | ||
}, bookmarks) { | ||
t.Fatal(ErrIncorrectResponse) | ||
} | ||
} | ||
|
||
func removeBookmarkHandler(bookmark *Bookmark) func(rw http.ResponseWriter, r *http.Request) { | ||
return func(rw http.ResponseWriter, r *http.Request) { | ||
channelID := r.FormValue("channel_id") | ||
bookmarkID := r.FormValue("bookmark_id") | ||
|
||
rw.Header().Set("Content-Type", "application/json") | ||
|
||
if channelID == bookmark.ChannelID && bookmarkID == bookmark.ID { | ||
rw.Write([]byte(`{ "ok": true }`)) | ||
} else { | ||
rw.Write([]byte(`{ "ok": false, "error": "errored" }`)) | ||
} | ||
} | ||
} | ||
|
||
func TestRemoveBookmark(t *testing.T) { | ||
channel := "CXXXXXXXX" | ||
bookmark := getTestBookmark(channel, "BkXXXXX") | ||
http.HandleFunc("/bookmarks.remove", removeBookmarkHandler(&bookmark)) | ||
once.Do(startServer) | ||
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) | ||
|
||
err := api.RemoveBookmark(channel, bookmark.ID) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %s", err) | ||
} | ||
} | ||
|
||
func editBookmarkHandler(bookmarks []Bookmark) func(rw http.ResponseWriter, r *http.Request) { | ||
return func(rw http.ResponseWriter, r *http.Request) { | ||
channelID := r.FormValue("channel_id") | ||
bookmarkID := r.FormValue("bookmark_id") | ||
|
||
rw.Header().Set("Content-Type", "application/json") | ||
if err := r.ParseForm(); err != nil { | ||
httpTestErrReply(rw, true, fmt.Sprintf("err parsing form: %s", err.Error())) | ||
return | ||
} | ||
|
||
for _, bookmark := range bookmarks { | ||
if bookmark.ID == bookmarkID && bookmark.ChannelID == channelID { | ||
if v := r.Form.Get("link"); v != "" { | ||
bookmark.Link = v | ||
} | ||
// Emoji and title require special handling since empty string sets to null | ||
if _, ok := r.Form["emoji"]; ok { | ||
bookmark.Emoji = r.Form.Get("emoji") | ||
} | ||
if _, ok := r.Form["title"]; ok { | ||
bookmark.Title = r.Form.Get("title") | ||
} | ||
resp, _ := json.Marshal(&editBookmarkResponse{ | ||
SlackResponse: SlackResponse{Ok: true}, | ||
Bookmark: bookmark}) | ||
rw.Write(resp) | ||
return | ||
} | ||
} | ||
// Fail if the bookmark doesn't exist | ||
rw.Write([]byte(`{ "ok": false, "error": "not_found" }`)) | ||
} | ||
} | ||
|
||
func TestEditBookmark(t *testing.T) { | ||
channel := "CXXXXXXXX" | ||
bookmarks := []Bookmark{ | ||
getTestBookmark(channel, "Bk001"), | ||
getTestBookmark(channel, "Bk002"), | ||
getTestBookmark(channel, "Bk003"), | ||
getTestBookmark(channel, "Bk004"), | ||
} | ||
http.HandleFunc("/bookmarks.edit", editBookmarkHandler(bookmarks)) | ||
once.Do(startServer) | ||
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) | ||
|
||
smileEmoji := ":smile:" | ||
empty := "" | ||
title := "hello, world!" | ||
changes := []struct { | ||
ID string | ||
Params EditBookmarkParameters | ||
}{ | ||
{ // add emoji | ||
ID: "Bk001", | ||
Params: EditBookmarkParameters{Emoji: &smileEmoji}, | ||
}, | ||
{ // delete emoji | ||
ID: "Bk001", | ||
Params: EditBookmarkParameters{Emoji: &empty}, | ||
}, | ||
{ // add title | ||
ID: "Bk002", | ||
Params: EditBookmarkParameters{Title: &title}, | ||
}, | ||
{ // delete title | ||
ID: "Bk002", | ||
Params: EditBookmarkParameters{Title: &empty}, | ||
}, | ||
{ // Change multiple fields at once | ||
ID: "Bk003", | ||
Params: EditBookmarkParameters{ | ||
Title: &title, | ||
Emoji: &empty, | ||
Link: "https://example.com/changed", | ||
}, | ||
}, | ||
{ // noop | ||
ID: "Bk004", | ||
}, | ||
} | ||
|
||
for _, change := range changes { | ||
bookmark, err := api.EditBookmark(channel, change.ID, change.Params) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %s", err) | ||
} | ||
if change.ID != bookmark.ID { | ||
t.Fatalf("expected to modify bookmark with ID = %s, got %s", change.ID, bookmark.ID) | ||
} | ||
if change.Params.Emoji != nil && bookmark.Emoji != *change.Params.Emoji { | ||
t.Fatalf("expected bookmark.Emoji = %s, got %s", *change.Params.Emoji, bookmark.Emoji) | ||
} | ||
if change.Params.Title != nil && bookmark.Title != *change.Params.Title { | ||
t.Fatalf("expected bookmark.Title = %s, got %s", *change.Params.Title, bookmark.Emoji) | ||
} | ||
if change.Params.Link != "" && change.Params.Link != bookmark.Link { | ||
t.Fatalf("expected bookmark.Link = %s, got %s", change.Params.Link, bookmark.Link) | ||
} | ||
} | ||
|
||
// Cover the final case of trying to edit a bookmark which doesn't exist | ||
bookmark, err := api.EditBookmark(channel, "BkMissing", EditBookmarkParameters{}) | ||
if err == nil { | ||
t.Fatalf("Expected not found error, but got bookmark %s", bookmark.ID) | ||
} | ||
} |