split remote to common protocol package and engine implemetation

This commit is contained in:
Umputun
2019-06-25 20:06:30 -05:00
parent c2b56eae30
commit d8a2bb2f7b
4 changed files with 296 additions and 225 deletions
+104
View File
@@ -0,0 +1,104 @@
package engine
import (
"encoding/json"
"github.com/umputun/remark/backend/app/store"
"github.com/umputun/remark/backend/app/store/remote"
)
// Remote implements remote engine and delegates all Calls to remote http server
type Remote struct {
remote.Client
}
// Create comment and return ID
func (r *Remote) Create(comment store.Comment) (commentID string, err error) {
resp, err := r.Call("create", comment)
if err != nil {
return "", err
}
err = json.Unmarshal(*resp.Result, &commentID)
return commentID, err
}
// Get comment by ID
func (r *Remote) Get(locator store.Locator, commentID string) (comment store.Comment, err error) {
resp, err := r.Call("get", locator, commentID)
if err != nil {
return store.Comment{}, err
}
err = json.Unmarshal(*resp.Result, &comment)
return comment, err
}
// Update comment, mutable parts only
func (r *Remote) Update(locator store.Locator, comment store.Comment) error {
_, err := r.Call("update", locator, comment)
return err
}
// Find comments for locator
func (r *Remote) Find(req FindRequest) (comments []store.Comment, err error) {
resp, err := r.Call("find", req)
if err != nil {
return nil, err
}
err = json.Unmarshal(*resp.Result, &comments)
return comments, err
}
// Info returns post(s) meta info
func (r *Remote) Info(req InfoRequest) (info []store.PostInfo, err error) {
resp, err := r.Call("info", req)
if err != nil {
return nil, err
}
err = json.Unmarshal(*resp.Result, &info)
return info, err
}
// Flag sets and gets flags
func (r *Remote) Flag(req FlagRequest) (status bool, err error) {
resp, err := r.Call("flag", req)
if err != nil {
return false, err
}
err = json.Unmarshal(*resp.Result, &status)
return status, err
}
// ListFlags get list of flagged keys, like blocked & verified user
func (r *Remote) ListFlags(siteID string, flag Flag) (list []interface{}, err error) {
resp, err := r.Call("list_flags", siteID, flag)
if err != nil {
return nil, err
}
err = json.Unmarshal(*resp.Result, &list)
return list, err
}
// Count gets comments count by user or site
func (r *Remote) Count(req FindRequest) (count int, err error) {
resp, err := r.Call("count", req)
if err != nil {
return 0, err
}
err = json.Unmarshal(*resp.Result, &count)
return count, err
}
// Delete post(s) by id or by userID
func (r *Remote) Delete(req DeleteRequest) error {
_, err := r.Call("delete", req)
return err
}
// Close storage engine
func (r *Remote) Close() error {
_, err := r.Call("close")
return err
}
+171
View File
@@ -0,0 +1,171 @@
package engine
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umputun/remark/backend/app/store"
"github.com/umputun/remark/backend/app/store/remote"
)
func TestClient_Create(t *testing.T) {
ts := testServer(t, `{"method":"create","params":[{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"}]}`, `{"result":"12345"}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.Create(store.Comment{ID: "123", Locator: store.Locator{URL: "http://example.com/url", SiteID: "site"},
Text: "msg"})
assert.NoError(t, err)
assert.Equal(t, "12345", res)
t.Logf("%v %T", res, res)
}
func TestClient_Get(t *testing.T) {
ts := testServer(t, `{"method":"get","params":[{"url":"http://example.com/url"},"site"]}`,
`{"result":{"id":"123","pid":"","text":"msg","delete":true}}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.NoError(t, err)
assert.Equal(t, store.Comment{ID: "123", Text: "msg", Deleted: true}, res)
t.Logf("%v %T", res, res)
}
func TestClient_GetWithErrorResult(t *testing.T) {
ts := testServer(t, `{"method":"get","params":[{"url":"http://example.com/url"},"site"]}`, `{"error":"failed"}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.EqualError(t, err, "failed")
}
func TestClient_GetWithErrorDecode(t *testing.T) {
ts := testServer(t, `{"method":"get","params":[{"url":"http://example.com/url"},"site"]}`, ``)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.EqualError(t, err, "failed to decode response for get: EOF")
}
func TestClient_GetWithErrorRemote(t *testing.T) {
c := Remote{Client: remote.Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.NotNil(t, err)
assert.True(t, strings.Contains(err.Error(), "remote Call failed for get:"))
}
func TestClient_FailedStatus(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
t.Logf("req: %s", string(body))
w.WriteHeader(400)
}))
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.EqualError(t, err, "bad status 400 for get")
}
func TestClient_Update(t *testing.T) {
ts := testServer(t, `{"method":"update","params":[{"url":"http://example.com/url"},{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site123","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"}]}`, `{}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
err := c.Update(store.Locator{URL: "http://example.com/url"}, store.Comment{ID: "123",
Locator: store.Locator{URL: "http://example.com/url", SiteID: "site123"}, Text: "msg"})
assert.NoError(t, err)
}
func TestClient_Find(t *testing.T) {
ts := testServer(t, `{"method":"find","params":[{"locator":{"url":"http://example.com/url"},"sort":"-time","since":"0001-01-01T00:00:00Z","limit":10}]}`, `{"result":[{"text":"1"},{"text":"2"}]}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.Find(FindRequest{Locator: store.Locator{URL: "http://example.com/url"}, Sort: "-time", Limit: 10})
assert.NoError(t, err)
assert.Equal(t, []store.Comment{{Text: "1"}, {Text: "2"}}, res)
}
func TestClient_Info(t *testing.T) {
ts := testServer(t, `{"method":"info","params":[{"locator":{"url":"http://example.com/url"},"limit":10,"skip":5,"ro_age":10}]}`, `{"result":[{"url":"u1","count":22},{"url":"u2","count":33}]}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.Info(InfoRequest{Locator: store.Locator{URL: "http://example.com/url"},
Limit: 10, Skip: 5, ReadOnlyAge: 10})
assert.NoError(t, err)
assert.Equal(t, []store.PostInfo{{URL: "u1", Count: 22}, {URL: "u2", Count: 33}}, res)
}
func TestClient_Flag(t *testing.T) {
ts := testServer(t, `{"method":"flag","params":[{"flag":"verified","locator":{"url":"http://example.com/url"}}]}`,
`{"result":false}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.Flag(FlagRequest{Locator: store.Locator{URL: "http://example.com/url"}, Flag: Verified})
assert.NoError(t, err)
assert.Equal(t, false, res)
}
func TestClient_ListFlag(t *testing.T) {
ts := testServer(t, `{"method":"list_flags","params":["site_id","blocked"]}`, `{"result":[{"ID":"id1"},{"ID":"id2"}]}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.ListFlags("site_id", Blocked)
assert.NoError(t, err)
assert.Equal(t, []interface{}{map[string]interface{}{"ID": "id1"}, map[string]interface{}{"ID": "id2"}}, res)
}
func TestClient_Count(t *testing.T) {
ts := testServer(t, `{"method":"count","params":[{"locator":{"url":"http://example.com/url"},"since":"0001-01-01T00:00:00Z"}]}`,
`{"result":11}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
res, err := c.Count(FindRequest{Locator: store.Locator{URL: "http://example.com/url"}})
assert.NoError(t, err)
assert.Equal(t, 11, res)
}
func TestClient_Delete(t *testing.T) {
ts := testServer(t, `{"method":"delete","params":[{"locator":{"url":"http://example.com/url"},"del_mode":0}]}`, `{}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
err := c.Delete(DeleteRequest{Locator: store.Locator{URL: "http://example.com/url"}})
assert.NoError(t, err)
}
func TestClient_Close(t *testing.T) {
ts := testServer(t, `{"method":"close","params":null}`, `{}`)
defer ts.Close()
c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}}
err := c.Close()
assert.NoError(t, err)
}
func testServer(t *testing.T, req, resp string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
assert.Equal(t, req, string(body))
t.Logf("req: %s", string(body))
fmt.Fprintf(w, resp)
}))
}
+3 -96
View File
@@ -6,9 +6,6 @@ import (
"net/http"
"github.com/pkg/errors"
"github.com/umputun/remark/backend/app/store"
"github.com/umputun/remark/backend/app/store/engine"
)
// Client implements remote engine and delegates all calls to remote http server
@@ -31,98 +28,8 @@ type Response struct {
Error string `json:"error,omitempty"`
}
// Create comment and return ID
func (r *Client) Create(comment store.Comment) (commentID string, err error) {
resp, err := r.call("create", comment)
if err != nil {
return "", err
}
err = json.Unmarshal(*resp.Result, &commentID)
return commentID, err
}
// Get comment by ID
func (r *Client) Get(locator store.Locator, commentID string) (comment store.Comment, err error) {
resp, err := r.call("get", locator, commentID)
if err != nil {
return store.Comment{}, err
}
err = json.Unmarshal(*resp.Result, &comment)
return comment, err
}
// Update comment, mutable parts only
func (r *Client) Update(locator store.Locator, comment store.Comment) error {
_, err := r.call("update", locator, comment)
return err
}
// Find comments for locator
func (r *Client) Find(req engine.FindRequest) (comments []store.Comment, err error) {
resp, err := r.call("find", req)
if err != nil {
return nil, err
}
err = json.Unmarshal(*resp.Result, &comments)
return comments, err
}
// Info returns post(s) meta info
func (r *Client) Info(req engine.InfoRequest) (info []store.PostInfo, err error) {
resp, err := r.call("info", req)
if err != nil {
return nil, err
}
err = json.Unmarshal(*resp.Result, &info)
return info, err
}
// Flag sets and gets flags
func (r *Client) Flag(req engine.FlagRequest) (status bool, err error) {
resp, err := r.call("flag", req)
if err != nil {
return false, err
}
err = json.Unmarshal(*resp.Result, &status)
return status, err
}
// ListFlags get list of flagged keys, like blocked & verified user
func (r *Client) ListFlags(siteID string, flag engine.Flag) (list []interface{}, err error) {
resp, err := r.call("list_flags", siteID, flag)
if err != nil {
return nil, err
}
err = json.Unmarshal(*resp.Result, &list)
return list, err
}
// Count gets comments count by user or site
func (r *Client) Count(req engine.FindRequest) (count int, err error) {
resp, err := r.call("count", req)
if err != nil {
return 0, err
}
err = json.Unmarshal(*resp.Result, &count)
return count, err
}
// Delete post(s) by id or by userID
func (r *Client) Delete(req engine.DeleteRequest) error {
_, err := r.call("delete", req)
return err
}
// Close storage engine
func (r *Client) Close() error {
_, err := r.call("close")
return err
}
func (r *Client) call(method string, args ...interface{}) (*Response, error) {
// Call remote server with given method and arguments
func (r *Client) Call(method string, args ...interface{}) (*Response, error) {
b, err := json.Marshal(Request{Method: method, Params: args})
if err != nil {
@@ -137,7 +44,7 @@ func (r *Client) call(method string, args ...interface{}) (*Response, error) {
req.SetBasicAuth(r.AuthUser, r.AuthPasswd)
resp, err := r.Client.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "remote call failed for %s", method)
return nil, errors.Wrapf(err, "remote Call failed for %s", method)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
+18 -129
View File
@@ -1,163 +1,52 @@
package remote
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umputun/remark/backend/app/store"
"github.com/umputun/remark/backend/app/store/engine"
)
func TestClient_Create(t *testing.T) {
ts := testServer(t, `{"method":"create","params":[{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"}]}`, `{"result":"12345"}`)
func TestClient_Call(t *testing.T) {
ts := testServer(t, `{"method":"test","params":[123,"abc"]}`, `{"result":"12345"}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.Create(store.Comment{ID: "123", Locator: store.Locator{URL: "http://example.com/url", SiteID: "site"},
Text: "msg"})
resp, err := c.Call("test", 123, "abc")
assert.NoError(t, err)
res := ""
err = json.Unmarshal(*resp.Result, &res)
assert.Equal(t, "12345", res)
t.Logf("%v %T", res, res)
}
func TestClient_Get(t *testing.T) {
ts := testServer(t, `{"method":"get","params":[{"url":"http://example.com/url"},"site"]}`,
`{"result":{"id":"123","pid":"","text":"msg","delete":true}}`)
func TestClient_CallError(t *testing.T) {
ts := testServer(t, `{"method":"test","params":[123,"abc"]}`, `{"error":"some error"}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.NoError(t, err)
assert.Equal(t, store.Comment{ID: "123", Text: "msg", Deleted: true}, res)
t.Logf("%v %T", res, res)
_, err := c.Call("test", 123, "abc")
assert.EqualError(t, err, "some error")
}
func TestClient_GetWithErrorResult(t *testing.T) {
ts := testServer(t, `{"method":"get","params":[{"url":"http://example.com/url"},"site"]}`, `{"error":"failed"}`)
func TestClient_CallBadResponse(t *testing.T) {
ts := testServer(t, `{"method":"test","params":[123,"abc"]}`, `{"result":"12345 invalid}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.EqualError(t, err, "failed")
}
func TestClient_GetWithErrorDecode(t *testing.T) {
ts := testServer(t, `{"method":"get","params":[{"url":"http://example.com/url"},"site"]}`, ``)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.EqualError(t, err, "failed to decode response for get: EOF")
}
func TestClient_GetWithErrorRemote(t *testing.T) {
c := Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
_, err := c.Call("test", 123, "abc")
assert.NotNil(t, err)
assert.True(t, strings.Contains(err.Error(), "remote call failed for get:"))
}
func TestClient_FailedStatus(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
t.Logf("req: %s", string(body))
w.WriteHeader(400)
}))
func TestClient_CallBadRemote(t *testing.T) {
ts := testServer(t, `{"method":"test","params":[123,"abc"]}`, `{"result":"12345"}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
_, err := c.Get(store.Locator{URL: "http://example.com/url"}, "site")
assert.EqualError(t, err, "bad status 400 for get")
}
func TestClient_Update(t *testing.T) {
ts := testServer(t, `{"method":"update","params":[{"url":"http://example.com/url"},{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site123","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"}]}`, `{}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
err := c.Update(store.Locator{URL: "http://example.com/url"}, store.Comment{ID: "123",
Locator: store.Locator{URL: "http://example.com/url", SiteID: "site123"}, Text: "msg"})
assert.NoError(t, err)
}
func TestClient_Find(t *testing.T) {
ts := testServer(t, `{"method":"find","params":[{"locator":{"url":"http://example.com/url"},"sort":"-time","since":"0001-01-01T00:00:00Z","limit":10}]}`, `{"result":[{"text":"1"},{"text":"2"}]}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.Find(engine.FindRequest{Locator: store.Locator{URL: "http://example.com/url"}, Sort: "-time", Limit: 10})
assert.NoError(t, err)
assert.Equal(t, []store.Comment{{Text: "1"}, {Text: "2"}}, res)
}
func TestClient_Info(t *testing.T) {
ts := testServer(t, `{"method":"info","params":[{"locator":{"url":"http://example.com/url"},"limit":10,"skip":5,"ro_age":10}]}`, `{"result":[{"url":"u1","count":22},{"url":"u2","count":33}]}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.Info(engine.InfoRequest{Locator: store.Locator{URL: "http://example.com/url"},
Limit: 10, Skip: 5, ReadOnlyAge: 10})
assert.NoError(t, err)
assert.Equal(t, []store.PostInfo{{URL: "u1", Count: 22}, {URL: "u2", Count: 33}}, res)
}
func TestClient_Flag(t *testing.T) {
ts := testServer(t, `{"method":"flag","params":[{"flag":"verified","locator":{"url":"http://example.com/url"}}]}`,
`{"result":false}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.Flag(engine.FlagRequest{Locator: store.Locator{URL: "http://example.com/url"}, Flag: engine.Verified})
assert.NoError(t, err)
assert.Equal(t, false, res)
}
func TestClient_ListFlag(t *testing.T) {
ts := testServer(t, `{"method":"list_flags","params":["site_id","blocked"]}`, `{"result":[{"ID":"id1"},{"ID":"id2"}]}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.ListFlags("site_id", engine.Blocked)
assert.NoError(t, err)
assert.Equal(t, []interface{}{map[string]interface{}{"ID": "id1"}, map[string]interface{}{"ID": "id2"}}, res)
}
func TestClient_Count(t *testing.T) {
ts := testServer(t, `{"method":"count","params":[{"locator":{"url":"http://example.com/url"},"since":"0001-01-01T00:00:00Z"}]}`,
`{"result":11}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
res, err := c.Count(engine.FindRequest{Locator: store.Locator{URL: "http://example.com/url"}})
assert.NoError(t, err)
assert.Equal(t, 11, res)
}
func TestClient_Delete(t *testing.T) {
ts := testServer(t, `{"method":"delete","params":[{"locator":{"url":"http://example.com/url"},"del_mode":0}]}`, `{}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
err := c.Delete(engine.DeleteRequest{Locator: store.Locator{URL: "http://example.com/url"}})
assert.NoError(t, err)
}
func TestClient_Close(t *testing.T) {
ts := testServer(t, `{"method":"close","params":null}`, `{}`)
defer ts.Close()
c := Client{API: ts.URL, Client: http.Client{}}
err := c.Close()
assert.NoError(t, err)
c := Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}
_, err := c.Call("test", 123)
assert.NotNil(t, err)
}
func testServer(t *testing.T, req, resp string) *httptest.Server {