add /deleteme request #47

This commit is contained in:
Umputun
2018-06-03 00:49:37 -05:00
parent 12b9ab4c98
commit 194ece1fa0
9 changed files with 840 additions and 697 deletions
+2 -3
View File
@@ -102,8 +102,8 @@ _instructions for google oauth2 setup borrowed from [oauth2_proxy](https://githu
#### Initial import from Disqus
1. Disqus provides an export of all comments on your site in a g-zipped file. This is found in your Moderation panel at Disqus Admin > Setup > Export. The export will be sent into a queue and then emailed to the address associated with your account once it's ready. Direct link to export will be something like `https://<siteud>.disqus.com/admin/discussions/export/`. See [importing-exporting](https://help.disqus.com/customer/portal/articles/1104797-importing-exporting) for more details.
2. Move this file to your remark42 host within `.var` and unzip, i.e. `gunzip <disqus-export-name>.xml.gz`.
3. Run import command - `docker-compose exec remark /srv/import-disqus.sh <disqus-export-name>.xml <your site id>`
2. Move this file to your remark42 host within `./var` and unzip, i.e. `gunzip <disqus-export-name>.xml.gz`.
3. Run import command - `docker-compose exec remark42 /srv/import-disqus.sh <disqus-export-name>.xml <your site id>`
#### Backup and restore
@@ -132,7 +132,6 @@ In addition to automatic backups user can make a backup manually. This command m
Backup file is a text file with all exported comments separated by EOL. Each backup record is a valid json with all key/value
unmarshaled from `Comment` struct (see below).
#### Admin users
Admins/moderators should be defined in `docker-compose.yml` as a list of user IDs or passed in the command line.
+1
View File
@@ -157,6 +157,7 @@ func (s *Rest) routes() chi.Router {
rauth.Get("/user", s.userInfoCtrl)
rauth.Put("/vote/{id}", s.voteCtrl)
rauth.Get("/userdata", s.userAllDataCtrl)
rauth.Post("/deleteme", s.deleteMeCtrl)
// admin routes, admin users only
rauth.Mount("/admin", s.adminService.routes(s.Authenticator.AdminOnly, Logger(nil, LogAll)))
+29
View File
@@ -10,11 +10,13 @@ import (
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/go-chi/chi"
"github.com/go-chi/render"
blackfriday "gopkg.in/russross/blackfriday.v2"
"github.com/umputun/remark/app/rest"
"github.com/umputun/remark/app/rest/auth"
"github.com/umputun/remark/app/store"
"github.com/umputun/remark/app/store/service"
)
@@ -211,3 +213,30 @@ func (s *Rest) userAllDataCtrl(w http.ResponseWriter, r *http.Request) {
}
}
}
// POST /deleteme?site_id=site
func (s *Rest) deleteMeCtrl(w http.ResponseWriter, r *http.Request) {
user, err := rest.GetUserInfo(r)
if err != nil {
rest.SendErrorJSON(w, r, http.StatusUnauthorized, err, "can't get user info")
return
}
siteID := r.URL.Query().Get("site")
claims := auth.CustomClaims{
SiteID: siteID,
StandardClaims: jwt.StandardClaims{
Issuer: "remark42",
ExpiresAt: time.Now().AddDate(0, 3, 0).Unix(),
NotBefore: time.Now().Add(-1 * time.Minute).Unix(),
},
User: &user,
}
tokenStr, err := s.Authenticator.JWTService.Token(&claims)
if err != nil {
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't make token")
return
}
render.JSON(w, r, JSON{"site": siteID, "user_id": user.ID, "token": tokenStr})
}
+263
View File
@@ -0,0 +1,263 @@
package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umputun/remark/app/store"
)
func TestRest_Create(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
resp, err := post(t, ts.URL+"/api/v1/comment",
`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
assert.Nil(t, err)
require.Equal(t, http.StatusCreated, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
c := JSON{}
err = json.Unmarshal(b, &c)
assert.Nil(t, err)
loc := c["locator"].(map[string]interface{})
assert.Equal(t, "radio-t", loc["site"])
assert.Equal(t, "https://radio-t.com/blah1", loc["url"])
assert.True(t, len(c["id"].(string)) > 8)
}
func TestRest_CreateOldPost(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
// make old, but not too old comment
old := store.Comment{Text: "test test old", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -5),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err := srv.DataService.Create(old)
assert.Nil(t, err)
comments, err := srv.DataService.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, "time")
assert.Nil(t, err)
assert.Equal(t, 1, len(comments))
// try to add new comment to the same old post
resp, err := post(t, ts.URL+"/api/v1/comment",
`{"text": "test 123", "locator":{"site": "radio-t","url": "https://radio-t.com/blah1"}}`)
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
assert.Nil(t, srv.DataService.DeleteAll("radio-t"))
// make too old comment
old = store.Comment{Text: "test test old", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -15),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err = srv.DataService.Create(old)
assert.Nil(t, err)
resp, err = post(t, ts.URL+"/api/v1/comment",
`{"text": "test 123", "locator":{"site": "radio-t","url": "https://radio-t.com/blah1"}}`)
assert.Nil(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
}
func TestRest_CreateTooBig(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
longComment := fmt.Sprintf(`{"text": "%4001s", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`, "Щ")
resp, err := post(t, ts.URL+"/api/v1/comment", longComment)
assert.Nil(t, err)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
c := JSON{}
err = json.Unmarshal(b, &c)
assert.Nil(t, err)
assert.Equal(t, "comment text exceeded max allowed size 4000 (4001)", c["error"])
assert.Equal(t, "invalid comment", c["details"])
}
func TestRest_CreateAndGet(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
// create comment
resp, err := post(t, ts.URL+"/api/v1/comment",
`{"text": "**test** *123* http://radio-t.com", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
require.Nil(t, err)
require.Equal(t, http.StatusCreated, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
c := JSON{}
err = json.Unmarshal(b, &c)
assert.Nil(t, err)
id := c["id"].(string)
// get created comment by id
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
assert.Equal(t, 200, code)
comment := store.Comment{}
err = json.Unmarshal([]byte(res), &comment)
assert.Nil(t, err)
assert.Equal(t, `<p><strong>test</strong> <em>123</em> <a href="http://radio-t.com" rel="nofollow">http://radio-t.com</a></p>`+"\n", comment.Text)
assert.Equal(t, "**test** *123* http://radio-t.com", comment.Orig)
assert.Equal(t, store.User{Name: "developer one", ID: "dev",
Picture: "/api/v1/avatar/remark.image", Admin: true, Blocked: false, IP: "dbc7c999343f003f189f70aaf52cc04443f90790"},
comment.User)
t.Logf("%+v", comment)
}
func TestRest_Update(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
id := addComment(t, c1, ts)
client := http.Client{}
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1",
strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
b, err := client.Do(req)
assert.Nil(t, err)
body, err := ioutil.ReadAll(b.Body)
assert.Nil(t, err)
assert.Equal(t, 200, b.StatusCode, string(body))
// comments returned by update
c2 := store.Comment{}
err = json.Unmarshal(body, &c2)
assert.Nil(t, err)
assert.Equal(t, id, c2.ID)
assert.Equal(t, "<p>updated text</p>\n", c2.Text)
assert.Equal(t, "updated text", c2.Orig)
assert.Equal(t, "my edit", c2.Edit.Summary)
assert.True(t, time.Since(c2.Edit.Timestamp) < 1*time.Second)
// read updated comment
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
assert.Equal(t, 200, code)
c3 := store.Comment{}
err = json.Unmarshal([]byte(res), &c3)
assert.Nil(t, err)
assert.Equal(t, c2, c3, "same as response from update")
}
func TestRest_UpdateNotOwner(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "xyz"}}
id1, err := srv.DataService.Create(c1)
assert.Nil(t, err)
client := http.Client{}
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
"?site=radio-t&url=https://radio-t.com/blah1", strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
b, err := client.Do(req)
assert.Nil(t, err)
body, err := ioutil.ReadAll(b.Body)
assert.Nil(t, err)
assert.Equal(t, 403, b.StatusCode, string(body), "update from non-owner")
assert.Equal(t, `{"details":"can not edit comments for other users","error":"rejected"}`+"\n", string(body))
client = http.Client{}
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
"?site=radio-t&url=https://radio-t.com/blah1", strings.NewReader(`ERRR "text":"updated text", "summary":"my"}`))
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
b, err = client.Do(req)
assert.Nil(t, err)
assert.Equal(t, 400, b.StatusCode, string(body), "update is not json")
}
func TestRest_Vote(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
id1 := addComment(t, c1, ts)
addComment(t, c2, ts)
vote := func(val int) int {
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("%s/api/v1/vote/%s?site=radio-t&url=https://radio-t.com/blah&vote=%d", ts.URL, id1, val), nil)
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
resp, err := client.Do(req)
assert.Nil(t, err)
return resp.StatusCode
}
assert.Equal(t, 200, vote(1), "first vote allowed")
assert.Equal(t, 400, vote(1), "second vote rejected")
body, code := get(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
assert.Equal(t, 200, code)
cr := store.Comment{}
err := json.Unmarshal([]byte(body), &cr)
assert.Nil(t, err)
assert.Equal(t, 1, cr.Score)
assert.Equal(t, map[string]bool{"dev": true}, cr.Votes)
assert.Equal(t, 200, vote(-1), "opposite vote allowed")
body, code = get(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
assert.Equal(t, 200, code)
cr = store.Comment{}
err = json.Unmarshal([]byte(body), &cr)
assert.Nil(t, err)
assert.Equal(t, 0, cr.Score)
assert.Equal(t, map[string]bool{}, cr.Votes)
}
func TestRest_DeleteMe(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
client := http.Client{}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/deleteme?site=radio-t", ts.URL), nil)
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
resp, err := client.Do(req)
assert.Nil(t, err)
body, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
m := map[string]string{}
err = json.Unmarshal(body, &m)
assert.Nil(t, err)
assert.Equal(t, "radio-t", m["site"])
assert.Equal(t, "dev", m["user_id"])
token := m["token"]
claims, err := srv.Authenticator.JWTService.Parse(token)
assert.Nil(t, err)
assert.Equal(t, "dev", claims.User.ID)
}
+465
View File
@@ -0,0 +1,465 @@
package api
import (
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umputun/remark/app/rest"
"github.com/umputun/remark/app/store"
)
func TestRest_Ping(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
res, code := get(t, ts.URL+"/api/v1/ping")
assert.Equal(t, "pong", res)
assert.Equal(t, 200, code)
}
func TestRest_Preview(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
resp, err := post(t, ts.URL+"/api/v1/preview", `{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.Equal(t, "<p>test 123</p>\n", string(b))
}
func TestRest_PreviewWithMD(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
text := `
# h1
BKT
func TestRest_Preview(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
}
BKT
`
text = strings.Replace(text, "BKT", "```", -1)
j := fmt.Sprintf(`{"text": "%s", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`, text)
j = strings.Replace(j, "\n", "\\n", -1)
t.Log(j)
resp, err := post(t, ts.URL+"/api/v1/preview", j)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.Equal(t, "<h1>h1</h1>\n\n<pre><code>func TestRest_Preview(t *testing.T) {\nsrv, ts := prep(t)\n require.NotNil(t, srv)\n}\n</code></pre>\n", string(b))
}
func TestRest_Find(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
_, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1")
assert.Equal(t, 400, code, "nothing in")
c1 := store.Comment{Text: "test test #1", ParentID: "",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
id1 := addComment(t, c1, ts)
c2 := store.Comment{Text: "test test #2", ParentID: id1,
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
id2 := addComment(t, c2, ts)
assert.NotEqual(t, id1, id2)
// get sorted by +time
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&sort=+time")
assert.Equal(t, 200, code)
comments := []store.Comment{}
err := json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
assert.Equal(t, id1, comments[0].ID)
assert.Equal(t, id2, comments[1].ID)
// get sorted by -time
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&sort=-time")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
assert.Equal(t, id1, comments[1].ID)
assert.Equal(t, id2, comments[0].ID)
// get in tree mode
tree := rest.Tree{}
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
assert.Nil(t, err)
assert.Equal(t, 1, len(tree.Nodes))
assert.Equal(t, 1, len(tree.Nodes[0].Replies))
assert.Equal(t, 2, tree.Info.Count)
assert.Equal(t, "https://radio-t.com/blah1", tree.Info.URL)
assert.False(t, tree.Info.ReadOnly, "post is fresh")
}
func TestRest_FindAge(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -5),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err := srv.DataService.Create(c1)
require.Nil(t, err)
c2 := store.Comment{Text: "test test #2", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -15),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}, User: store.User{ID: "u1"}}
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
tree := rest.Tree{}
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
assert.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah1", tree.Info.URL)
assert.False(t, tree.Info.ReadOnly, "post is fresh")
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah2&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
assert.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah2", tree.Info.URL)
assert.True(t, tree.Info.ReadOnly, "post is old")
}
func TestRest_FindReadOnly(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -1),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err := srv.DataService.Create(c1)
require.Nil(t, err)
c2 := store.Comment{Text: "test test #2", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -2),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}, User: store.User{ID: "u1"}}
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
// set post to read-only
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah1&ro=1", ts.URL), nil)
assert.Nil(t, err)
withBasicAuth(req, "dev", "password")
_, err = client.Do(req)
require.Nil(t, err)
tree := rest.Tree{}
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
require.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah1", tree.Info.URL)
assert.True(t, tree.Info.ReadOnly, "post is ro")
tree = rest.Tree{}
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah2&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
require.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah2", tree.Info.URL)
assert.False(t, tree.Info.ReadOnly, "post is writable")
}
func TestRest_Last(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
// add 3 comments
addComment(t, c1, ts)
id1 := addComment(t, c1, ts)
id2 := addComment(t, c2, ts)
res, code := get(t, ts.URL+"/api/v1/last/2?site=radio-t")
assert.Equal(t, 200, code)
comments := []store.Comment{}
err := json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
assert.Equal(t, id1, comments[1].ID)
assert.Equal(t, id2, comments[0].ID)
res, code = get(t, ts.URL+"/api/v1/last/5?site=radio-t")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 3, len(comments), "should have 3 comments")
res, code = get(t, ts.URL+"/api/v1/last/X?site=radio-t")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 3, len(comments), "should have 3 comments")
err = srv.DataService.Delete(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, id1, store.SoftDelete)
assert.Nil(t, err)
res, code = get(t, ts.URL+"/api/v1/last/5?site=radio-t")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
}
func TestRest_FindUserComments(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #3", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
// add 3 comments
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
_, code := get(t, ts.URL+"/api/v1/comments?site=radio-t&user=blah")
assert.Equal(t, 400, code, "noting for user blah")
res, code := get(t, ts.URL+"/api/v1/comments?site=radio-t&user=dev")
assert.Equal(t, 200, code)
resp := struct {
Comments []store.Comment
Count int
}{}
err := json.Unmarshal([]byte(res), &resp)
assert.Nil(t, err)
assert.Equal(t, 3, len(resp.Comments), "should have 3 comments")
assert.Equal(t, 3, resp.Count, "should have 3 count")
}
func TestRest_UserInfo(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
body, code := getWithAuth(t, ts.URL+"/api/v1/user?site=radio-t")
assert.Equal(t, 200, code)
user := store.User{}
err := json.Unmarshal([]byte(body), &user)
assert.Nil(t, err)
assert.Equal(t, store.User{Name: "developer one", ID: "dev",
Picture: "/api/v1/avatar/remark.image", Admin: true, Blocked: false, IP: ""}, user)
}
func TestRest_Count(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
body, code := get(t, ts.URL+"/api/v1/count?site=radio-t&url=https://radio-t.com/blah1")
assert.Equal(t, 200, code)
j := JSON{}
err := json.Unmarshal([]byte(body), &j)
assert.Nil(t, err)
assert.Equal(t, 3.0, j["count"])
body, code = get(t, ts.URL+"/api/v1/count?site=radio-t&url=https://radio-t.com/blah2")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(body), &j)
assert.Nil(t, err)
assert.Equal(t, 2.0, j["count"])
}
func TestRest_Counts(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
resp, err := post(t, ts.URL+"/api/v1/counts?site=radio-t", `["https://radio-t.com/blah1","https://radio-t.com/blah2"]`)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
j := []store.PostInfo{}
err = json.Unmarshal(body, &j)
assert.Nil(t, err)
assert.Equal(t, []store.PostInfo([]store.PostInfo{{URL: "https://radio-t.com/blah1", Count: 3},
{URL: "https://radio-t.com/blah2", Count: 2}}), j)
}
func TestRest_List(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
body, code := get(t, ts.URL+"/api/v1/list?site=radio-t")
assert.Equal(t, 200, code)
pi := []store.PostInfo{}
err := json.Unmarshal([]byte(body), &pi)
assert.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah2", pi[0].URL)
assert.Equal(t, 2, pi[0].Count)
assert.Equal(t, "https://radio-t.com/blah1", pi[1].URL)
assert.Equal(t, 3, pi[1].Count)
}
func TestRest_Config(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
body, code := get(t, ts.URL+"/api/v1/config?site=radio-t")
assert.Equal(t, 200, code)
j := JSON{}
err := json.Unmarshal([]byte(body), &j)
assert.Nil(t, err)
assert.Equal(t, 300., j["edit_duration"])
assert.EqualValues(t, []interface{}([]interface{}{"a1", "a2"}), j["admins"])
assert.Equal(t, 4000., j["max_comment_size"])
assert.Equal(t, -5., j["low_score"])
assert.Equal(t, -10., j["critical_score"])
assert.Equal(t, 10., j["readonly_age"])
t.Logf("%+v", j)
}
func TestRest_Info(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
user := store.User{ID: "user1", Name: "user name 1"}
c1 := store.Comment{User: user, Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 10, 0, time.Local)}
c2 := store.Comment{User: user, Text: "test test #2", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 20, 0, time.Local)}
c3 := store.Comment{User: user, Text: "test test #3", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 25, 0, time.Local)}
_, err := srv.DataService.Create(c1)
require.Nil(t, err, "%+v", err)
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
_, err = srv.DataService.Create(c3)
require.Nil(t, err)
body, code := get(t, ts.URL+"/api/v1/info?site=radio-t&url=https://radio-t.com/blah1")
assert.Equal(t, 200, code)
info := store.PostInfo{}
err = json.Unmarshal([]byte(body), &info)
assert.Nil(t, err)
exp := store.PostInfo{URL: "https://radio-t.com/blah1", Count: 3,
FirstTS: time.Date(2018, 05, 27, 1, 14, 10, 0, time.Local), LastTS: time.Date(2018, 05, 27, 1, 14, 25, 0, time.Local)}
assert.Equal(t, exp, info)
_, code = get(t, ts.URL+"/api/v1/info?site=radio-t&url=https://radio-t.com/blah-no")
assert.Equal(t, 400, code)
_, code = get(t, ts.URL+"/api/v1/info?site=radio-t-no&url=https://radio-t.com/blah-no")
assert.Equal(t, 400, code)
}
func TestRest_UserAllData(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
// write 3 comments
user := store.User{ID: "dev", Name: "user name 1"}
c1 := store.Comment{User: user, Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 10, 0, time.Local)}
c2 := store.Comment{User: user, Text: "test test #2", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 20, 0, time.Local)}
c3 := store.Comment{User: user, Text: "test test #3", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 25, 0, time.Local)}
_, err := srv.DataService.Create(c1)
require.Nil(t, err, "%+v", err)
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
_, err = srv.DataService.Create(c3)
require.Nil(t, err)
client := &http.Client{Timeout: 1 * time.Second}
req, err := http.NewRequest("GET", ts.URL+"/api/v1/userdata?site=radio-t", nil)
require.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, 200, resp.StatusCode)
require.Equal(t, "application/gzip", resp.Header.Get("Content-Type"))
ungzReader, err := gzip.NewReader(resp.Body)
assert.NoError(t, err)
ungzBody, err := ioutil.ReadAll(ungzReader)
assert.NoError(t, err)
assert.True(t, strings.HasPrefix(string(ungzBody),
`{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","admin":true}[`))
assert.Equal(t, 3, strings.Count(string(ungzBody), `"text":`), "3 comments inside")
t.Logf("%s", string(ungzBody))
}
+4 -675
View File
@@ -2,10 +2,8 @@ package api
import (
"bytes"
"compress/gzip"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
@@ -19,7 +17,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/umputun/remark/app/migrator"
"github.com/umputun/remark/app/rest"
"github.com/umputun/remark/app/rest/auth"
"github.com/umputun/remark/app/rest/proxy"
"github.com/umputun/remark/app/store"
@@ -30,675 +27,6 @@ import (
var testDb = "/tmp/test-remark.db"
var testHTML = "/tmp/test-remark.html"
func TestRest_Ping(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
res, code := get(t, ts.URL+"/api/v1/ping")
assert.Equal(t, "pong", res)
assert.Equal(t, 200, code)
}
func TestRest_Create(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
resp, err := post(t, ts.URL+"/api/v1/comment",
`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
assert.Nil(t, err)
require.Equal(t, http.StatusCreated, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
c := JSON{}
err = json.Unmarshal(b, &c)
assert.Nil(t, err)
loc := c["locator"].(map[string]interface{})
assert.Equal(t, "radio-t", loc["site"])
assert.Equal(t, "https://radio-t.com/blah1", loc["url"])
assert.True(t, len(c["id"].(string)) > 8)
}
func TestRest_CreateOldPost(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
// make old, but not too old comment
old := store.Comment{Text: "test test old", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -5),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err := srv.DataService.Create(old)
assert.Nil(t, err)
comments, err := srv.DataService.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, "time")
assert.Nil(t, err)
assert.Equal(t, 1, len(comments))
// try to add new comment to the same old post
resp, err := post(t, ts.URL+"/api/v1/comment",
`{"text": "test 123", "locator":{"site": "radio-t","url": "https://radio-t.com/blah1"}}`)
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
assert.Nil(t, srv.DataService.DeleteAll("radio-t"))
// make too old comment
old = store.Comment{Text: "test test old", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -15),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err = srv.DataService.Create(old)
assert.Nil(t, err)
resp, err = post(t, ts.URL+"/api/v1/comment",
`{"text": "test 123", "locator":{"site": "radio-t","url": "https://radio-t.com/blah1"}}`)
assert.Nil(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
}
func TestRest_CreateTooBig(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
longComment := fmt.Sprintf(`{"text": "%4001s", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`, "Щ")
resp, err := post(t, ts.URL+"/api/v1/comment", longComment)
assert.Nil(t, err)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
c := JSON{}
err = json.Unmarshal(b, &c)
assert.Nil(t, err)
assert.Equal(t, "comment text exceeded max allowed size 4000 (4001)", c["error"])
assert.Equal(t, "invalid comment", c["details"])
}
func TestRest_Preview(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
resp, err := post(t, ts.URL+"/api/v1/preview", `{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.Equal(t, "<p>test 123</p>\n", string(b))
}
func TestRest_PreviewWithMD(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
defer cleanup(ts)
text := `
# h1
BKT
func TestRest_Preview(t *testing.T) {
srv, ts := prep(t)
require.NotNil(t, srv)
}
BKT
`
text = strings.Replace(text, "BKT", "```", -1)
j := fmt.Sprintf(`{"text": "%s", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`, text)
j = strings.Replace(j, "\n", "\\n", -1)
t.Log(j)
resp, err := post(t, ts.URL+"/api/v1/preview", j)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.Equal(t, "<h1>h1</h1>\n\n<pre><code>func TestRest_Preview(t *testing.T) {\nsrv, ts := prep(t)\n require.NotNil(t, srv)\n}\n</code></pre>\n", string(b))
}
func TestRest_CreateAndGet(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
// create comment
resp, err := post(t, ts.URL+"/api/v1/comment",
`{"text": "**test** *123* http://radio-t.com", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
require.Nil(t, err)
require.Equal(t, http.StatusCreated, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
c := JSON{}
err = json.Unmarshal(b, &c)
assert.Nil(t, err)
id := c["id"].(string)
// get created comment by id
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
assert.Equal(t, 200, code)
comment := store.Comment{}
err = json.Unmarshal([]byte(res), &comment)
assert.Nil(t, err)
assert.Equal(t, `<p><strong>test</strong> <em>123</em> <a href="http://radio-t.com" rel="nofollow">http://radio-t.com</a></p>`+"\n", comment.Text)
assert.Equal(t, "**test** *123* http://radio-t.com", comment.Orig)
assert.Equal(t, store.User{Name: "developer one", ID: "dev",
Picture: "/api/v1/avatar/remark.image", Admin: true, Blocked: false, IP: "dbc7c999343f003f189f70aaf52cc04443f90790"},
comment.User)
t.Logf("%+v", comment)
}
func TestRest_Find(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
_, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1")
assert.Equal(t, 400, code, "nothing in")
c1 := store.Comment{Text: "test test #1", ParentID: "",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
id1 := addComment(t, c1, ts)
c2 := store.Comment{Text: "test test #2", ParentID: id1,
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
id2 := addComment(t, c2, ts)
assert.NotEqual(t, id1, id2)
// get sorted by +time
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&sort=+time")
assert.Equal(t, 200, code)
comments := []store.Comment{}
err := json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
assert.Equal(t, id1, comments[0].ID)
assert.Equal(t, id2, comments[1].ID)
// get sorted by -time
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&sort=-time")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
assert.Equal(t, id1, comments[1].ID)
assert.Equal(t, id2, comments[0].ID)
// get in tree mode
tree := rest.Tree{}
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
assert.Nil(t, err)
assert.Equal(t, 1, len(tree.Nodes))
assert.Equal(t, 1, len(tree.Nodes[0].Replies))
assert.Equal(t, 2, tree.Info.Count)
assert.Equal(t, "https://radio-t.com/blah1", tree.Info.URL)
assert.False(t, tree.Info.ReadOnly, "post is fresh")
}
func TestRest_FindAge(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -5),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err := srv.DataService.Create(c1)
require.Nil(t, err)
c2 := store.Comment{Text: "test test #2", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -15),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}, User: store.User{ID: "u1"}}
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
tree := rest.Tree{}
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
assert.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah1", tree.Info.URL)
assert.False(t, tree.Info.ReadOnly, "post is fresh")
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah2&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
assert.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah2", tree.Info.URL)
assert.True(t, tree.Info.ReadOnly, "post is old")
}
func TestRest_FindReadOnly(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -1),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
_, err := srv.DataService.Create(c1)
require.Nil(t, err)
c2 := store.Comment{Text: "test test #2", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -2),
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}, User: store.User{ID: "u1"}}
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
// set post to read-only
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah1&ro=1", ts.URL), nil)
assert.Nil(t, err)
withBasicAuth(req, "dev", "password")
_, err = client.Do(req)
require.Nil(t, err)
tree := rest.Tree{}
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
require.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah1", tree.Info.URL)
assert.True(t, tree.Info.ReadOnly, "post is ro")
tree = rest.Tree{}
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah2&format=tree")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &tree)
require.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah2", tree.Info.URL)
assert.False(t, tree.Info.ReadOnly, "post is writable")
}
func TestRest_Update(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
id := addComment(t, c1, ts)
client := http.Client{}
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1",
strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
b, err := client.Do(req)
assert.Nil(t, err)
body, err := ioutil.ReadAll(b.Body)
assert.Nil(t, err)
assert.Equal(t, 200, b.StatusCode, string(body))
// comments returned by update
c2 := store.Comment{}
err = json.Unmarshal(body, &c2)
assert.Nil(t, err)
assert.Equal(t, id, c2.ID)
assert.Equal(t, "<p>updated text</p>\n", c2.Text)
assert.Equal(t, "updated text", c2.Orig)
assert.Equal(t, "my edit", c2.Edit.Summary)
assert.True(t, time.Since(c2.Edit.Timestamp) < 1*time.Second)
// read updated comment
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
assert.Equal(t, 200, code)
c3 := store.Comment{}
err = json.Unmarshal([]byte(res), &c3)
assert.Nil(t, err)
assert.Equal(t, c2, c3, "same as response from update")
}
func TestRest_UpdateNotOwner(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "xyz"}}
id1, err := srv.DataService.Create(c1)
assert.Nil(t, err)
client := http.Client{}
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
"?site=radio-t&url=https://radio-t.com/blah1", strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
b, err := client.Do(req)
assert.Nil(t, err)
body, err := ioutil.ReadAll(b.Body)
assert.Nil(t, err)
assert.Equal(t, 403, b.StatusCode, string(body), "update from non-owner")
assert.Equal(t, `{"details":"can not edit comments for other users","error":"rejected"}`+"\n", string(body))
client = http.Client{}
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
"?site=radio-t&url=https://radio-t.com/blah1", strings.NewReader(`ERRR "text":"updated text", "summary":"my"}`))
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
b, err = client.Do(req)
assert.Nil(t, err)
assert.Equal(t, 400, b.StatusCode, string(body), "update is not json")
}
func TestRest_Last(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
// add 3 comments
addComment(t, c1, ts)
id1 := addComment(t, c1, ts)
id2 := addComment(t, c2, ts)
res, code := get(t, ts.URL+"/api/v1/last/2?site=radio-t")
assert.Equal(t, 200, code)
comments := []store.Comment{}
err := json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
assert.Equal(t, id1, comments[1].ID)
assert.Equal(t, id2, comments[0].ID)
res, code = get(t, ts.URL+"/api/v1/last/5?site=radio-t")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 3, len(comments), "should have 3 comments")
res, code = get(t, ts.URL+"/api/v1/last/X?site=radio-t")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 3, len(comments), "should have 3 comments")
err = srv.DataService.Delete(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, id1, store.SoftDelete)
assert.Nil(t, err)
res, code = get(t, ts.URL+"/api/v1/last/5?site=radio-t")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(res), &comments)
assert.Nil(t, err)
assert.Equal(t, 2, len(comments), "should have 2 comments")
}
func TestRest_FindUserComments(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #3", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
// add 3 comments
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
_, code := get(t, ts.URL+"/api/v1/comments?site=radio-t&user=blah")
assert.Equal(t, 400, code, "noting for user blah")
res, code := get(t, ts.URL+"/api/v1/comments?site=radio-t&user=dev")
assert.Equal(t, 200, code)
resp := struct {
Comments []store.Comment
Count int
}{}
err := json.Unmarshal([]byte(res), &resp)
assert.Nil(t, err)
assert.Equal(t, 3, len(resp.Comments), "should have 3 comments")
assert.Equal(t, 3, resp.Count, "should have 3 count")
}
func TestRest_UserInfo(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
body, code := getWithAuth(t, ts.URL+"/api/v1/user?site=radio-t")
assert.Equal(t, 200, code)
user := store.User{}
err := json.Unmarshal([]byte(body), &user)
assert.Nil(t, err)
assert.Equal(t, store.User{Name: "developer one", ID: "dev",
Picture: "/api/v1/avatar/remark.image", Admin: true, Blocked: false, IP: ""}, user)
}
func TestRest_Vote(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
id1 := addComment(t, c1, ts)
addComment(t, c2, ts)
vote := func(val int) int {
client := http.Client{}
req, err := http.NewRequest(http.MethodPut,
fmt.Sprintf("%s/api/v1/vote/%s?site=radio-t&url=https://radio-t.com/blah&vote=%d", ts.URL, id1, val), nil)
assert.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
resp, err := client.Do(req)
assert.Nil(t, err)
return resp.StatusCode
}
assert.Equal(t, 200, vote(1), "first vote allowed")
assert.Equal(t, 400, vote(1), "second vote rejected")
body, code := get(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
assert.Equal(t, 200, code)
cr := store.Comment{}
err := json.Unmarshal([]byte(body), &cr)
assert.Nil(t, err)
assert.Equal(t, 1, cr.Score)
assert.Equal(t, map[string]bool{"dev": true}, cr.Votes)
assert.Equal(t, 200, vote(-1), "opposite vote allowed")
body, code = get(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
assert.Equal(t, 200, code)
cr = store.Comment{}
err = json.Unmarshal([]byte(body), &cr)
assert.Nil(t, err)
assert.Equal(t, 0, cr.Score)
assert.Equal(t, map[string]bool{}, cr.Votes)
}
func TestRest_Count(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
body, code := get(t, ts.URL+"/api/v1/count?site=radio-t&url=https://radio-t.com/blah1")
assert.Equal(t, 200, code)
j := JSON{}
err := json.Unmarshal([]byte(body), &j)
assert.Nil(t, err)
assert.Equal(t, 3.0, j["count"])
body, code = get(t, ts.URL+"/api/v1/count?site=radio-t&url=https://radio-t.com/blah2")
assert.Equal(t, 200, code)
err = json.Unmarshal([]byte(body), &j)
assert.Nil(t, err)
assert.Equal(t, 2.0, j["count"])
}
func TestRest_Counts(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
resp, err := post(t, ts.URL+"/api/v1/counts?site=radio-t", `["https://radio-t.com/blah1","https://radio-t.com/blah2"]`)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
j := []store.PostInfo{}
err = json.Unmarshal(body, &j)
assert.Nil(t, err)
assert.Equal(t, []store.PostInfo([]store.PostInfo{{URL: "https://radio-t.com/blah1", Count: 3},
{URL: "https://radio-t.com/blah2", Count: 2}}), j)
}
func TestRest_List(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
c1 := store.Comment{Text: "test test #1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah2"}}
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c1, ts)
addComment(t, c2, ts)
addComment(t, c2, ts)
body, code := get(t, ts.URL+"/api/v1/list?site=radio-t")
assert.Equal(t, 200, code)
pi := []store.PostInfo{}
err := json.Unmarshal([]byte(body), &pi)
assert.Nil(t, err)
assert.Equal(t, "https://radio-t.com/blah2", pi[0].URL)
assert.Equal(t, 2, pi[0].Count)
assert.Equal(t, "https://radio-t.com/blah1", pi[1].URL)
assert.Equal(t, 3, pi[1].Count)
}
func TestRest_Config(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
body, code := get(t, ts.URL+"/api/v1/config?site=radio-t")
assert.Equal(t, 200, code)
j := JSON{}
err := json.Unmarshal([]byte(body), &j)
assert.Nil(t, err)
assert.Equal(t, 300., j["edit_duration"])
assert.EqualValues(t, []interface{}([]interface{}{"a1", "a2"}), j["admins"])
assert.Equal(t, 4000., j["max_comment_size"])
assert.Equal(t, -5., j["low_score"])
assert.Equal(t, -10., j["critical_score"])
assert.Equal(t, 10., j["readonly_age"])
t.Logf("%+v", j)
}
func TestRest_Info(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
user := store.User{ID: "user1", Name: "user name 1"}
c1 := store.Comment{User: user, Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 10, 0, time.Local)}
c2 := store.Comment{User: user, Text: "test test #2", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 20, 0, time.Local)}
c3 := store.Comment{User: user, Text: "test test #3", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 25, 0, time.Local)}
_, err := srv.DataService.Create(c1)
require.Nil(t, err, "%+v", err)
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
_, err = srv.DataService.Create(c3)
require.Nil(t, err)
body, code := get(t, ts.URL+"/api/v1/info?site=radio-t&url=https://radio-t.com/blah1")
assert.Equal(t, 200, code)
info := store.PostInfo{}
err = json.Unmarshal([]byte(body), &info)
assert.Nil(t, err)
exp := store.PostInfo{URL: "https://radio-t.com/blah1", Count: 3,
FirstTS: time.Date(2018, 05, 27, 1, 14, 10, 0, time.Local), LastTS: time.Date(2018, 05, 27, 1, 14, 25, 0, time.Local)}
assert.Equal(t, exp, info)
_, code = get(t, ts.URL+"/api/v1/info?site=radio-t&url=https://radio-t.com/blah-no")
assert.Equal(t, 400, code)
_, code = get(t, ts.URL+"/api/v1/info?site=radio-t-no&url=https://radio-t.com/blah-no")
assert.Equal(t, 400, code)
}
func TestRest_UserAllData(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
defer cleanup(ts)
// write 3 comments
user := store.User{ID: "dev", Name: "user name 1"}
c1 := store.Comment{User: user, Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 10, 0, time.Local)}
c2 := store.Comment{User: user, Text: "test test #2", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 20, 0, time.Local)}
c3 := store.Comment{User: user, Text: "test test #3", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t",
URL: "https://radio-t.com/blah1"}, Timestamp: time.Date(2018, 05, 27, 1, 14, 25, 0, time.Local)}
_, err := srv.DataService.Create(c1)
require.Nil(t, err, "%+v", err)
_, err = srv.DataService.Create(c2)
require.Nil(t, err)
_, err = srv.DataService.Create(c3)
require.Nil(t, err)
client := &http.Client{Timeout: 1 * time.Second}
req, err := http.NewRequest("GET", ts.URL+"/api/v1/userdata?site=radio-t", nil)
require.Nil(t, err)
req = withBasicAuth(req, "dev", "password")
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, 200, resp.StatusCode)
require.Equal(t, "application/gzip", resp.Header.Get("Content-Type"))
ungzReader, err := gzip.NewReader(resp.Body)
assert.NoError(t, err)
ungzBody, err := ioutil.ReadAll(ungzReader)
assert.NoError(t, err)
assert.True(t, strings.HasPrefix(string(ungzBody),
`{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","admin":true}[`))
assert.Equal(t, 3, strings.Count(string(ungzBody), `"text":`), "3 comments inside")
t.Logf("%s", string(ungzBody))
}
func TestRest_FileServer(t *testing.T) {
srv, ts := prep(t)
assert.NotNil(t, srv)
@@ -728,9 +56,10 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
srv = &Rest{
DataService: dataStore,
Authenticator: auth.Authenticator{
DevPasswd: "password",
Providers: nil,
Admins: []string{"a1", "a2"},
DevPasswd: "password",
Providers: nil,
Admins: []string{"a1", "a2"},
JWTService: auth.NewJWT("12345", false, time.Minute),
},
Exporter: &migrator.Remark{DataStore: &dataStore},
Cache: &mockCache{},
+36 -15
View File
@@ -45,16 +45,45 @@ func NewJWT(secret string, secureCookies bool, exp time.Duration) *JWT {
return &res
}
// Token makes jwt with claims
func (j *JWT) Token(claims *CustomClaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(j.secret))
if err != nil {
return "", errors.Wrap(err, "can't sign jwt token")
}
return tokenString, nil
}
// Parse token string and verify
func (j *JWT) Parse(tokenString string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(j.secret), nil
})
if err != nil {
return nil, errors.Wrap(err, "can't parse jwt")
}
claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
return nil, errors.New("invalid jwt")
}
return claims, nil
}
// Set creates jwt cookie with xsrf cookie and put it to ResponseWriter
// accepts claims and sets expiration if none defined. permanent flag means long-living cookie, false makes it session only.
func (j *JWT) Set(w http.ResponseWriter, claims *CustomClaims, sessionOnly bool) error {
if claims.ExpiresAt == 0 {
claims.ExpiresAt = time.Now().Add(j.exp).Unix()
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(j.secret))
tokenString, err := j.Token(claims)
if err != nil {
return errors.Wrap(err, "can't sign jwt token")
return errors.Wrap(err, "failed to make jwt token")
}
cookieExpiration := 0 // session cookie
@@ -80,10 +109,12 @@ func (j *JWT) Get(r *http.Request) (*CustomClaims, error) {
fromCookie := false
tokenString := ""
// try to get from X-JWT header
if tokenHeader := r.Header.Get(jwtHeaderKey); tokenHeader != "" {
tokenString = tokenHeader
}
// try to get from JWT cookie
if tokenString == "" {
fromCookie = true
jc, err := r.Cookie(jwtCookieName)
@@ -93,19 +124,9 @@ func (j *JWT) Get(r *http.Request) (*CustomClaims, error) {
tokenString = jc.Value
}
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(j.secret), nil
})
claims, err := j.Parse(tokenString)
if err != nil {
return nil, errors.Wrap(err, "can't parse jwt")
}
claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
return nil, errors.New("invalid jwt")
return nil, errors.Wrap(err, "failed to get jwt")
}
if fromCookie && claims.User != nil {
+39 -3
View File
@@ -26,6 +26,42 @@ var testJwtExpired = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjY4ODc4M
"ImlzcyI6InJlbWFyazQyIiwibmJmIjoxNTI2ODg0MjIyLCJ1c2VyIjp7Im5hbWUiOiJuYW1lMSIsImlkIjoiaWQxIiwicGljdHVyZSI6IiI" +
"sImFkbWluIjpmYWxzZX0sInN0YXRlIjoiMTIzNDU2IiwiZnJvbSI6ImZyb20ifQ.4_dCrY9ihyfZIedz-kZwBTxmxU1a52V7IqeJrOqTzE4"
func TestJWT_Token(t *testing.T) {
j := NewJWT("xyz 12345", false, time.Hour)
claims := &CustomClaims{
State: "123456",
From: "from",
User: &store.User{
ID: "id1",
Name: "name1",
},
StandardClaims: jwt.StandardClaims{
Id: "random id",
Issuer: "remark42",
ExpiresAt: time.Date(2058, 5, 21, 1, 30, 22, 0, time.Local).Unix(),
NotBefore: time.Date(2018, 5, 21, 1, 30, 22, 0, time.Local).Unix(),
},
}
res, err := j.Token(claims)
assert.Nil(t, err)
assert.Equal(t, testJwtValid, res)
}
func TestJWT_Parse(t *testing.T) {
j := NewJWT("xyz 12345", false, time.Hour)
claims, err := j.Parse(testJwtValid)
assert.NoError(t, err)
assert.Equal(t, &store.User{Name: "name1", ID: "id1"}, claims.User)
_, err = j.Parse(testJwtExpired)
assert.NotNil(t, err, "expired token")
_, err = j.Parse("bad")
assert.NotNil(t, err, "bad token")
}
func TestJWT_Set(t *testing.T) {
j := NewJWT("xyz 12345", false, time.Hour)
@@ -85,13 +121,13 @@ func TestJWT_GetFromHeader(t *testing.T) {
req.Header.Add(jwtHeaderKey, testJwtExpired)
_, err = j.Get(req)
assert.NotNil(t, err)
assert.True(t, strings.HasPrefix(err.Error(), "can't parse jwt: token is expired by"), err.Error())
assert.True(t, strings.Contains(err.Error(), "can't parse jwt: token is expired by"), err.Error())
req = httptest.NewRequest("GET", "/", nil)
req.Header.Add(jwtHeaderKey, "bad bad token")
_, err = j.Get(req)
assert.NotNil(t, err)
assert.True(t, strings.HasPrefix(err.Error(), "can't parse jwt: token contains an invalid number of segments"), err.Error())
assert.True(t, strings.Contains(err.Error(), "can't parse jwt: token contains an invalid number of segments"), err.Error())
}
@@ -209,7 +245,7 @@ func TestJWT_SetAndGetWithCookiesExpired(t *testing.T) {
req.Header.Add(xsrfHeaderKey, "random id")
_, err = j.Get(req)
assert.NotNil(t, err)
assert.True(t, strings.HasPrefix(err.Error(), "can't parse jwt: token is expired by"), err.Error())
assert.True(t, strings.Contains(err.Error(), "can't parse jwt: token is expired by"), err.Error())
}
func TestJWT_Refresh(t *testing.T) {
+1 -1
View File
@@ -4,7 +4,7 @@ services:
remark:
build: .
image: umputun/remark:master
container_name: "remark"
container_name: "remark42"
hostname: "remark"
restart: always