Finish MultiService + all tests for it

Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
Vartan Benohanian 2020-07-11 01:37:19 -04:00
parent ce1f3dceba
commit f8d5a31b52
5 changed files with 512 additions and 19 deletions

157
multi.go
View File

@ -24,15 +24,16 @@ type multiRoot struct {
// Users can create multis for custom navigation, instead of browsing
// one subreddit or all subreddits at a time.
type Multi struct {
Name string `json:"name,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Name string `json:"name,omitempty"`
DisplayName string `json:"display_name,omitempty"`
// Format: user/{username}/m/{multiname}
Path string `json:"path,omitempty"`
Description string `json:"description_md,omitempty"`
Subreddits SubredditNames `json:"subreddits"`
CopedFrom *string `json:"copied_from"`
CopiedFrom *string `json:"copied_from"`
Owner string `json:"owner,omitempty"`
OwnerID string `json:"ownerID,omitempty"`
OwnerID string `json:"owner_id,omitempty"`
Created *Timestamp `json:"created_utc,omitempty"`
NumberOfSubscribers int `json:"num_subscribers"`
@ -66,10 +67,23 @@ func (n *SubredditNames) UnmarshalJSON(data []byte) error {
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (n *SubredditNames) MarshalJSON() ([]byte, error) {
var subreddits []map[string]string
for _, sr := range *n {
subreddits = append(subreddits, map[string]string{
"name": sr,
})
}
return json.Marshal(subreddits)
}
// MultiCopyRequest represents a request to copy a multireddit.
type MultiCopyRequest struct {
From string
To string
FromPath string
ToPath string
// Raw markdown text.
Description string
// No longer than 50 characters.
@ -79,29 +93,38 @@ type MultiCopyRequest struct {
// Form parameterizes the fields and returns the form.
func (r *MultiCopyRequest) Form() url.Values {
form := url.Values{}
form.Set("from", r.From)
form.Set("to", r.To)
form.Set("from", r.FromPath)
form.Set("to", r.ToPath)
form.Set("description_md", r.Description)
form.Set("display_name", r.DisplayName)
return form
}
// MultiCreateRequest represents a request to create a multireddit.
type MultiCreateRequest struct {
Description string `json:"description_md,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Subreddits []string `json:"subreddits"`
Visibility string `json:"visibility,omitempty"`
// MultiCreateOrUpdateRequest represents a request to create/update a multireddit.
type MultiCreateOrUpdateRequest struct {
// For updates, this is the display name, i.e. the header of the multi.
// Not part of the path necessarily.
Name string `json:"display_name,omitempty"`
Description string `json:"description_md,omitempty"`
Subreddits SubredditNames `json:"subreddits,omitempty"`
// One of: private, public, hidden
Visibility string `json:"visibility,omitempty"`
}
// Form parameterizes the fields and returns the form.
func (r *MultiCreateRequest) Form() url.Values {
func (r *MultiCreateOrUpdateRequest) Form() url.Values {
byteValue, _ := json.Marshal(r)
form := url.Values{}
form.Set("model", string(byteValue))
return form
}
type rootMultiDescription struct {
Data struct {
Body string `json:"body_md"`
} `json:"data"`
}
// Get gets information about the multireddit from its url path.
func (s *MultiService) Get(ctx context.Context, multiPath string) (*Multi, *Response, error) {
path := fmt.Sprintf("api/multi/%s", multiPath)
@ -144,6 +167,7 @@ func (s *MultiService) Mine(ctx context.Context) ([]Multi, *Response, error) {
}
// Of returns the user's public multireddits.
// Or, if the user is you, all of your multireddits.
func (s *MultiService) Of(ctx context.Context, username string) ([]Multi, *Response, error) {
path := fmt.Sprintf("api/multi/user/%s", username)
@ -172,7 +196,7 @@ func (s *MultiService) Copy(ctx context.Context, copyRequest *MultiCopyRequest)
return nil, nil, errors.New("copyRequest cannot be nil")
}
path := fmt.Sprintf("api/multi/copy")
path := "api/multi/copy"
req, err := s.client.NewPostForm(path, copyRequest.Form())
if err != nil {
@ -189,14 +213,14 @@ func (s *MultiService) Copy(ctx context.Context, copyRequest *MultiCopyRequest)
}
// Create creates a multireddit.
func (s *MultiService) Create(ctx context.Context, createRequest *MultiCreateRequest) (*Multi, *Response, error) {
func (s *MultiService) Create(ctx context.Context, createRequest *MultiCreateOrUpdateRequest) (*Multi, *Response, error) {
if createRequest == nil {
return nil, nil, errors.New("createRequest cannot be nil")
}
path := fmt.Sprintf("api/multi/copy")
path := "api/multi"
req, err := s.client.NewRequest(http.MethodPost, path, createRequest)
req, err := s.client.NewPostForm(path, createRequest.Form())
if err != nil {
return nil, nil, err
}
@ -210,6 +234,31 @@ func (s *MultiService) Create(ctx context.Context, createRequest *MultiCreateReq
return root.Data, resp, nil
}
// Update updates a multireddit.
// If the multireddit does not exist, it will be created.
func (s *MultiService) Update(ctx context.Context, multiPath string, updateRequest *MultiCreateOrUpdateRequest) (*Multi, *Response, error) {
if updateRequest == nil {
return nil, nil, errors.New("updateRequest cannot be nil")
}
path := fmt.Sprintf("api/multi/%s", multiPath)
req, err := s.client.NewPostForm(path, updateRequest.Form())
if err != nil {
return nil, nil, err
}
// todo: fix this
req.Method = http.MethodPut
root := new(multiRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Data, resp, nil
}
// Delete deletes a multireddit.
func (s *MultiService) Delete(ctx context.Context, multiPath string) (*Response, error) {
path := fmt.Sprintf("api/multi/%s", multiPath)
@ -221,3 +270,73 @@ func (s *MultiService) Delete(ctx context.Context, multiPath string) (*Response,
return s.client.Do(ctx, req, nil)
}
// GetDescription gets a multireddit's description.
func (s *MultiService) GetDescription(ctx context.Context, multiPath string) (string, *Response, error) {
path := fmt.Sprintf("api/multi/%s/description", multiPath)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return "", nil, err
}
root := new(rootMultiDescription)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return "", resp, err
}
return root.Data.Body, resp, nil
}
// UpdateDescription updates a multireddit's description.
func (s *MultiService) UpdateDescription(ctx context.Context, multiPath string, description string) (string, *Response, error) {
path := fmt.Sprintf("api/multi/%s/description", multiPath)
form := url.Values{}
form.Set("model", fmt.Sprintf(`{"body_md":"%s"}`, description))
req, err := s.client.NewPostForm(path, form)
if err != nil {
return "", nil, err
}
// todo: fix this
req.Method = http.MethodPut
root := new(rootMultiDescription)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return "", resp, err
}
return root.Data.Body, resp, nil
}
// AddSubreddit adds a subreddit to a multireddit.
func (s *MultiService) AddSubreddit(ctx context.Context, multiPath string, subreddit string) (*Response, error) {
path := fmt.Sprintf("api/multi/%s/r/%s", multiPath, subreddit)
form := url.Values{}
form.Set("model", fmt.Sprintf(`{"name":"%s"}`, subreddit))
req, err := s.client.NewPostForm(path, form)
if err != nil {
return nil, err
}
// todo: fix this
req.Method = http.MethodPut
return s.client.Do(ctx, req, nil)
}
// DeleteSubreddit removes a subreddit from a multireddit.
func (s *MultiService) DeleteSubreddit(ctx context.Context, multiPath string, subreddit string) (*Response, error) {
path := fmt.Sprintf("api/multi/%s/r/%s", multiPath, subreddit)
req, err := s.client.NewRequest(http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

272
multi_test.go Normal file
View File

@ -0,0 +1,272 @@
package geddit
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var expectedMulti = &Multi{
Name: "test",
DisplayName: "test",
Path: "/user/v_95/m/test/",
Subreddits: []string{"nba", "golang"},
CopiedFrom: nil,
Owner: "v_95",
OwnerID: "t2_164ab8",
Created: &Timestamp{time.Date(2020, 7, 11, 4, 55, 12, 0, time.UTC)},
NumberOfSubscribers: 0,
Visibility: "private",
CanEdit: true,
}
var expectedMulti2 = &Multi{
Name: "test2",
DisplayName: "test2",
Path: "/user/v_95/m/test2/",
Subreddits: []string{"redditdev", "test"},
CopiedFrom: nil,
Owner: "v_95",
OwnerID: "t2_164ab8",
Created: &Timestamp{time.Date(2020, 7, 11, 4, 57, 3, 0, time.UTC)},
NumberOfSubscribers: 0,
Visibility: "private",
CanEdit: true,
}
func TestMultiService_Get(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/multi.json")
mux.HandleFunc("/api/multi/user/testuser/m/testmulti", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
multi, _, err := client.Multi.Get(ctx, "user/testuser/m/testmulti")
assert.NoError(t, err)
assert.Equal(t, expectedMulti, multi)
}
func TestMultiService_Mine(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/multis.json")
mux.HandleFunc("/api/multi/mine", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
multis, _, err := client.Multi.Mine(ctx)
assert.NoError(t, err)
assert.Equal(t, []Multi{*expectedMulti, *expectedMulti2}, multis)
}
func TestMultiService_Of(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/multis.json")
mux.HandleFunc("/api/multi/user/test", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
multis, _, err := client.Multi.Of(ctx, "test")
assert.NoError(t, err)
assert.Equal(t, []Multi{*expectedMulti, *expectedMulti2}, multis)
}
func TestMultiService_Copy(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/multi.json")
mux.HandleFunc("/api/multi/copy", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
form := url.Values{}
form.Set("from", "user/testuser/m/testmulti")
form.Set("to", "user/testuser2/m/testmulti2")
form.Set("description_md", "this is a multireddit")
form.Set("display_name", "hello")
err := r.ParseForm()
assert.NoError(t, err)
assert.Equal(t, form, r.Form)
fmt.Fprint(w, blob)
})
multi, _, err := client.Multi.Copy(ctx, &MultiCopyRequest{
FromPath: "user/testuser/m/testmulti",
ToPath: "user/testuser2/m/testmulti2",
Description: "this is a multireddit",
DisplayName: "hello",
})
assert.NoError(t, err)
assert.Equal(t, expectedMulti, multi)
}
func TestMultiService_Create(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/multi.json")
createRequest := &MultiCreateOrUpdateRequest{
Name: "testmulti",
Description: "this is a multireddit",
Subreddits: []string{"golang"},
Visibility: "public",
}
mux.HandleFunc("/api/multi", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
err := r.ParseForm()
assert.NoError(t, err)
model := r.Form.Get("model")
expectedCreateRequest := new(MultiCreateOrUpdateRequest)
err = json.Unmarshal([]byte(model), expectedCreateRequest)
assert.NoError(t, err)
assert.Equal(t, expectedCreateRequest, createRequest)
fmt.Fprint(w, blob)
})
multi, _, err := client.Multi.Create(ctx, createRequest)
assert.NoError(t, err)
assert.Equal(t, expectedMulti, multi)
}
func TestMultiService_Update(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/multi.json")
updateRequest := &MultiCreateOrUpdateRequest{
Name: "testmulti",
Description: "this is a multireddit",
Visibility: "public",
}
mux.HandleFunc("/api/multi/user/testuser/m/testmulti", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPut, r.Method)
err := r.ParseForm()
assert.NoError(t, err)
model := r.Form.Get("model")
expectedCreateRequest := new(MultiCreateOrUpdateRequest)
err = json.Unmarshal([]byte(model), expectedCreateRequest)
assert.NoError(t, err)
assert.Equal(t, expectedCreateRequest, updateRequest)
fmt.Fprint(w, blob)
})
multi, _, err := client.Multi.Update(ctx, "user/testuser/m/testmulti", updateRequest)
assert.NoError(t, err)
assert.Equal(t, expectedMulti, multi)
}
func TestMultiService_Delete(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/api/multi/user/testuser/m/testmulti", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodDelete, r.Method)
})
_, err := client.Multi.Delete(ctx, "user/testuser/m/testmulti")
assert.NoError(t, err)
}
func TestMultiService_GetDescription(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/description.json")
mux.HandleFunc("/api/multi/user/testuser/m/testmulti/description", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
description, _, err := client.Multi.GetDescription(ctx, "user/testuser/m/testmulti")
assert.NoError(t, err)
assert.Equal(t, "hello world", description)
}
func TestMultiService_UpdateDescription(t *testing.T) {
setup()
defer teardown()
blob := readFileContents(t, "testdata/multi/description.json")
mux.HandleFunc("/api/multi/user/testuser/m/testmulti/description", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPut, r.Method)
form := url.Values{}
form.Set("model", `{"body_md":"hello world"}`)
err := r.ParseForm()
assert.NoError(t, err)
assert.Equal(t, form, r.Form)
fmt.Fprint(w, blob)
})
description, _, err := client.Multi.UpdateDescription(ctx, "user/testuser/m/testmulti", "hello world")
assert.NoError(t, err)
assert.Equal(t, "hello world", description)
}
func TestMultiService_AddSubreddit(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/api/multi/user/testuser/m/testmulti/r/golang", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPut, r.Method)
form := url.Values{}
form.Set("model", `{"name":"golang"}`)
err := r.ParseForm()
assert.NoError(t, err)
assert.Equal(t, form, r.Form)
})
_, err := client.Multi.AddSubreddit(ctx, "user/testuser/m/testmulti", "golang")
assert.NoError(t, err)
}
func TestMultiService_DeleteSubreddit(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/api/multi/user/testuser/m/testmulti/r/golang", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodDelete, r.Method)
})
_, err := client.Multi.DeleteSubreddit(ctx, "user/testuser/m/testmulti", "golang")
assert.NoError(t, err)
}

7
testdata/multi/description.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"kind": "LabeledMultiDescription",
"data": {
"body_html": "&lt;!-- SC_OFF --&gt;&lt;div class=\"md\"&gt;&lt;p&gt;hello world&lt;/p&gt;\n&lt;/div&gt;&lt;!-- SC_ON --&gt;",
"body_md": "hello world"
}
}

31
testdata/multi/multi.json vendored Normal file
View File

@ -0,0 +1,31 @@
{
"kind": "LabeledMulti",
"data": {
"can_edit": true,
"display_name": "test",
"name": "test",
"description_html": "",
"num_subscribers": 0,
"copied_from": null,
"icon_url": "https://www.redditstatic.com/custom_feeds/custom_feed_default_3.png",
"subreddits": [
{
"name": "nba"
},
{
"name": "golang"
}
],
"created_utc": 1594443312.0,
"visibility": "private",
"created": 1594472112.0,
"over_18": false,
"path": "/user/v_95/m/test/",
"owner": "v_95",
"key_color": null,
"is_subscriber": false,
"owner_id": "t2_164ab8",
"description_md": "",
"is_favorited": false
}
}

64
testdata/multi/multis.json vendored Normal file
View File

@ -0,0 +1,64 @@
[
{
"kind": "LabeledMulti",
"data": {
"can_edit": true,
"display_name": "test",
"name": "test",
"description_html": "",
"num_subscribers": 0,
"copied_from": null,
"icon_url": "https://www.redditstatic.com/custom_feeds/custom_feed_default_3.png",
"subreddits": [
{
"name": "nba"
},
{
"name": "golang"
}
],
"created_utc": 1594443312.0,
"visibility": "private",
"created": 1594472112.0,
"over_18": false,
"path": "/user/v_95/m/test/",
"owner": "v_95",
"key_color": null,
"is_subscriber": false,
"owner_id": "t2_164ab8",
"description_md": "",
"is_favorited": false
}
},
{
"kind": "LabeledMulti",
"data": {
"can_edit": true,
"display_name": "test2",
"name": "test2",
"description_html": "",
"num_subscribers": 0,
"copied_from": null,
"icon_url": "https://www.redditstatic.com/custom_feeds/custom_feed_default_5.png",
"subreddits": [
{
"name": "redditdev"
},
{
"name": "test"
}
],
"created_utc": 1594443423.0,
"visibility": "private",
"created": 1594472223.0,
"over_18": false,
"path": "/user/v_95/m/test2/",
"owner": "v_95",
"key_color": null,
"is_subscriber": false,
"owner_id": "t2_164ab8",
"description_md": "",
"is_favorited": false
}
}
]