Skip to content

Commit 6269c19

Browse files
paskalumputun
authored andcommitted
add more tests for GET /find endpoint
1 parent 1313dee commit 6269c19

File tree

3 files changed

+174
-2
lines changed

3 files changed

+174
-2
lines changed

backend/app/rest/api/admin_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ func TestAdmin_ReadOnlyNoComments(t *testing.T) {
513513
_, err = srv.DataService.Info(store.Locator{SiteID: "remark42", URL: "https://radio-t.com/blah"}, 0)
514514
assert.Error(t, err)
515515

516+
// test format "tree"
516517
res, code := get(t, ts.URL+"/api/v1/find?site=remark42&url=https://radio-t.com/blah&format=tree")
517518
assert.Equal(t, http.StatusOK, code)
518519
comments := commentsWithInfo{}
@@ -521,6 +522,16 @@ func TestAdmin_ReadOnlyNoComments(t *testing.T) {
521522
assert.Equal(t, 0, len(comments.Comments), "should have 0 comments")
522523
assert.True(t, comments.Info.ReadOnly)
523524
t.Logf("%+v", comments)
525+
526+
// test format "plain"
527+
res, code = get(t, ts.URL+"/api/v1/find?site=remark42&url=https://radio-t.com/blah")
528+
assert.Equal(t, http.StatusOK, code)
529+
comments = commentsWithInfo{}
530+
err = json.Unmarshal([]byte(res), &comments)
531+
assert.NoError(t, err)
532+
assert.Equal(t, 0, len(comments.Comments), "should have 0 comments")
533+
assert.False(t, comments.Info.ReadOnly, "different from the plain format, should be fixed")
534+
t.Logf("%+v", comments)
524535
}
525536

526537
func TestAdmin_ReadOnlyWithAge(t *testing.T) {

backend/app/rest/api/rest_public_test.go

+154
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"os"
10+
"strconv"
1011
"strings"
1112
"testing"
1213
"time"
@@ -533,6 +534,159 @@ func TestRest_FindUserComments_CWE_918(t *testing.T) {
533534
assert.Equal(t, arbitraryServer.URL, resp.Comments[0].Locator.URL, "arbitrary URL provided by the request")
534535
}
535536

537+
func TestPublic_FindCommentsCtrl_ConsistentCount(t *testing.T) {
538+
// test that comment counting is consistent between tree and plain formats
539+
ts, srv, teardown := startupT(t)
540+
defer teardown()
541+
542+
commentLocator := store.Locator{URL: "test-url", SiteID: "remark42"}
543+
544+
// vote for comment multiple times
545+
setScore := func(locator store.Locator, id string, val int) {
546+
abs := func(x int) int {
547+
if x < 0 {
548+
return -x
549+
}
550+
return x
551+
}
552+
for i := 0; i < abs(val); i++ {
553+
_, err := srv.DataService.Vote(service.VoteReq{
554+
Locator: locator,
555+
CommentID: id,
556+
// unique user ID is needed for correct counting of controversial votes
557+
UserID: "user" + strconv.Itoa(val) + strconv.Itoa(i),
558+
Val: val > 0,
559+
})
560+
require.NoError(t, err)
561+
}
562+
}
563+
564+
// Adding initial comments (8 to test-url and 1 to another-url) and voting, and delete two of comments to the first post.
565+
// With sleep so that at least few millisecond pass between each comment
566+
// and later we would be able to use that in "since" filter with millisecond precision
567+
ids := make([]string, 9)
568+
timestamps := make([]time.Time, 9)
569+
c1 := store.Comment{Text: "top-level comment 1", Locator: commentLocator}
570+
ids[0], timestamps[0] = addCommentGetCreatedTime(t, c1, ts)
571+
// #3 by score
572+
setScore(commentLocator, ids[0], 1)
573+
time.Sleep(time.Millisecond * 5)
574+
575+
c2 := store.Comment{Text: "top-level comment 2", Locator: commentLocator}
576+
ids[1], timestamps[1] = addCommentGetCreatedTime(t, c2, ts)
577+
// #2 by score
578+
setScore(commentLocator, ids[1], 2)
579+
time.Sleep(time.Millisecond * 5)
580+
581+
c3 := store.Comment{Text: "second-level comment 1", ParentID: ids[0], Locator: commentLocator}
582+
ids[2], timestamps[2] = addCommentGetCreatedTime(t, c3, ts)
583+
// #1 by score
584+
setScore(commentLocator, ids[2], 10)
585+
time.Sleep(time.Millisecond * 5)
586+
587+
c4 := store.Comment{Text: "third-level comment 1", ParentID: ids[2], Locator: commentLocator}
588+
ids[3], timestamps[3] = addCommentGetCreatedTime(t, c4, ts)
589+
// #5 by score, #1 by controversy
590+
setScore(commentLocator, ids[3], 4)
591+
setScore(commentLocator, ids[3], -4)
592+
time.Sleep(time.Millisecond * 5)
593+
594+
c5 := store.Comment{Text: "second-level comment 2", ParentID: ids[1], Locator: commentLocator}
595+
ids[4], timestamps[4] = addCommentGetCreatedTime(t, c5, ts)
596+
// #5 by score, #2 by controversy
597+
setScore(commentLocator, ids[4], 2)
598+
setScore(commentLocator, ids[4], -3)
599+
time.Sleep(time.Millisecond * 5)
600+
601+
c6 := store.Comment{Text: "third-level comment 2", ParentID: ids[4], Locator: commentLocator}
602+
ids[5], timestamps[5] = addCommentGetCreatedTime(t, c6, ts)
603+
// deleted later so not visible in site-wide requests
604+
setScore(commentLocator, ids[5], 10)
605+
setScore(commentLocator, ids[5], -10)
606+
time.Sleep(time.Millisecond * 5)
607+
608+
c7 := store.Comment{Text: "top-level comment 3", Locator: commentLocator}
609+
ids[6], timestamps[6] = addCommentGetCreatedTime(t, c7, ts)
610+
// #6 by score, #4 by controversy
611+
setScore(commentLocator, ids[6], -3)
612+
setScore(commentLocator, ids[6], 1)
613+
time.Sleep(time.Millisecond * 5)
614+
615+
c8 := store.Comment{Text: "second-level comment 3", ParentID: ids[6], Locator: commentLocator}
616+
ids[7], timestamps[7] = addCommentGetCreatedTime(t, c8, ts)
617+
// deleted later so not visible in site-wide requests
618+
setScore(commentLocator, ids[7], -20)
619+
620+
c9 := store.Comment{Text: "comment to post 2", Locator: store.Locator{URL: "another-url", SiteID: "remark42"}}
621+
ids[8], timestamps[8] = addCommentGetCreatedTime(t, c9, ts)
622+
// #7 by score
623+
setScore(store.Locator{URL: "another-url", SiteID: "remark42"}, ids[8], -25)
624+
625+
// delete two comments bringing the total from 9 to 6
626+
err := srv.DataService.Delete(commentLocator, ids[7], store.SoftDelete)
627+
assert.NoError(t, err)
628+
err = srv.DataService.Delete(commentLocator, ids[5], store.HardDelete)
629+
assert.NoError(t, err)
630+
srv.Cache.Flush(cache.FlusherRequest{})
631+
632+
sinceTenSecondsAgo := strconv.FormatInt(time.Now().Add(-time.Second*10).UnixNano()/1000000, 10)
633+
sinceTS := make([]string, 9)
634+
formattedTS := make([]string, 9)
635+
for i, created := range timestamps {
636+
sinceTS[i] = strconv.FormatInt(created.UnixNano()/1000000, 10)
637+
formattedTS[i] = created.Format(time.RFC3339Nano)
638+
}
639+
t.Logf("last timestamp: %v", timestamps[7])
640+
641+
testCases := []struct {
642+
params string
643+
expectedBody string
644+
}{
645+
{"", fmt.Sprintf(`"info":{"count":7,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[8])},
646+
{"url=test-url", fmt.Sprintf(`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[7])},
647+
{"format=plain", fmt.Sprintf(`"info":{"count":7,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[8])},
648+
{"format=plain&url=test-url", fmt.Sprintf(`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[7])},
649+
{"since=" + sinceTenSecondsAgo, fmt.Sprintf(`"info":{"count":7,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[8])},
650+
{"url=test-url&since=" + sinceTenSecondsAgo, fmt.Sprintf(`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[7])},
651+
{"since=" + sinceTS[0], fmt.Sprintf(`"info":{"count":7,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[8])},
652+
{"url=test-url&since=" + sinceTS[0], fmt.Sprintf(`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[7])},
653+
{"since=" + sinceTS[1], fmt.Sprintf(`"info":{"count":7,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[8])},
654+
{"url=test-url&since=" + sinceTS[1], fmt.Sprintf(`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[7])},
655+
{"since=" + sinceTS[4], fmt.Sprintf(`"info":{"count":7,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[8])},
656+
{"url=test-url&since=" + sinceTS[4], fmt.Sprintf(`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}`, formattedTS[0], formattedTS[7])},
657+
{"format=tree", `"info":{"url":"test-url","count":7`},
658+
{"format=tree&url=test-url", `"info":{"url":"test-url","count":6`},
659+
{"format=tree&sort=+time", `"info":{"url":"test-url","count":7`},
660+
{"format=tree&url=test-url&sort=+time", `"info":{"url":"test-url","count":6`},
661+
{"format=tree&sort=-score", `"info":{"url":"test-url","count":7`},
662+
{"format=tree&url=test-url&sort=-score", `"info":{"url":"test-url","count":6`},
663+
{"sort=+time", fmt.Sprintf(`"score":-25,"vote":0,"time":%q}],"info":{"count":7`, formattedTS[8])},
664+
{"sort=-time", fmt.Sprintf(`"score":1,"vote":0,"time":%q}],"info":{"count":7`, formattedTS[0])},
665+
{"sort=+score", fmt.Sprintf(`"score":10,"vote":0,"time":%q}],"info":{"count":7`, formattedTS[2])},
666+
{"sort=+score&url=test-url", fmt.Sprintf(`"score":10,"vote":0,"time":%q}],"info":{"url":"test-url","count":6`, formattedTS[2])},
667+
{"sort=-score", fmt.Sprintf(`"score":-25,"vote":0,"time":%q}],"info":{"count":7`, formattedTS[8])},
668+
{"sort=-score&url=test-url", fmt.Sprintf(`"score":-2,"vote":0,"controversy":1.5874010519681994,"time":%q}],"info":{"url":"test-url","count":6`, formattedTS[6])},
669+
{"sort=-time&since=" + sinceTS[4], fmt.Sprintf(`"score":-1,"vote":0,"controversy":2.924017738212866,"time":%q}],"info":{"count":7`, formattedTS[4])},
670+
{"sort=-score&since=" + sinceTS[3], fmt.Sprintf(`"score":-25,"vote":0,"time":%q}],"info":{"count":7`, formattedTS[8])},
671+
{"sort=-score&url=test-url&since=" + sinceTS[3], fmt.Sprintf(`"score":-2,"vote":0,"controversy":1.5874010519681994,"time":%q}],"info":{"url":"test-url","count":6`, formattedTS[6])},
672+
{"sort=+controversy&url=test-url&since=" + sinceTS[5], fmt.Sprintf(`"score":-2,"vote":0,"controversy":1.5874010519681994,"time":%q}],"info":{"url":"test-url","count":6`, formattedTS[6])},
673+
// three comments of which last one deleted and doesn't have controversy so returned last
674+
{"sort=-controversy&url=test-url&since=" + sinceTS[5], fmt.Sprintf(`"score":0,"vote":0,"time":%q,"delete":true}],"info":{"url":"test-url","count":6`, formattedTS[7])},
675+
}
676+
677+
for _, tc := range testCases {
678+
t.Run(tc.params, func(t *testing.T) {
679+
url := fmt.Sprintf(ts.URL+"/api/v1/find?site=remark42&%s", tc.params)
680+
body, code := get(t, url)
681+
assert.Equal(t, http.StatusOK, code)
682+
assert.Contains(t, body, tc.expectedBody)
683+
t.Log(body)
684+
// prevent hit limiter from engaging
685+
time.Sleep(50 * time.Millisecond)
686+
})
687+
}
688+
}
689+
536690
func TestRest_UserInfo(t *testing.T) {
537691
ts, _, teardown := startupT(t)
538692
defer teardown()

backend/app/rest/api/rest_test.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ func post(t *testing.T, url, body string) (*http.Response, error) {
599599
return client.Do(req)
600600
}
601601

602-
func addComment(t *testing.T, c store.Comment, ts *httptest.Server) string {
602+
func addCommentGetCreatedTime(t *testing.T, c store.Comment, ts *httptest.Server) (id string, created time.Time) {
603603
b, err := json.Marshal(c)
604604
require.NoError(t, err, "can't marshal comment %+v", c)
605605

@@ -619,7 +619,14 @@ func addComment(t *testing.T, c store.Comment, ts *httptest.Server) string {
619619
err = json.Unmarshal(b, &crResp)
620620
require.NoError(t, err)
621621
time.Sleep(time.Nanosecond * 10)
622-
return crResp["id"].(string)
622+
created, err = time.Parse(time.RFC3339, crResp["time"].(string))
623+
require.NoError(t, err)
624+
return crResp["id"].(string), created
625+
}
626+
627+
func addComment(t *testing.T, c store.Comment, ts *httptest.Server) string {
628+
id, _ := addCommentGetCreatedTime(t, c, ts)
629+
return id
623630
}
624631

625632
func requireAdminOnly(t *testing.T, req *http.Request) {

0 commit comments

Comments
 (0)